diff --git a/src/MSIdentityScaffolding/Microsoft.DotNet.MSIdentity/DeveloperCredentials/DeveloperCredentialsReader.cs b/src/MSIdentityScaffolding/Microsoft.DotNet.MSIdentity/DeveloperCredentials/DeveloperCredentialsReader.cs index 8f68ef780..bf47e07cb 100644 --- a/src/MSIdentityScaffolding/Microsoft.DotNet.MSIdentity/DeveloperCredentials/DeveloperCredentialsReader.cs +++ b/src/MSIdentityScaffolding/Microsoft.DotNet.MSIdentity/DeveloperCredentials/DeveloperCredentialsReader.cs @@ -8,7 +8,7 @@ namespace Microsoft.DotNet.MSIdentity.DeveloperCredentials { public class DeveloperCredentialsReader { - public TokenCredential GetDeveloperCredentials(string? username, string? currentApplicationTenantId, IConsoleLogger consoleLogger) + public TokenCredential GetDeveloperCredentials(string? username, string? currentApplicationTenantId, string? instance, IConsoleLogger consoleLogger) { #if AzureSDK * Tried but does not work if another tenant than the home tenant id is specified @@ -30,6 +30,7 @@ public TokenCredential GetDeveloperCredentials(string? username, string? current TokenCredential tokenCredential = new MsalTokenCredential( currentApplicationTenantId, username, + instance, consoleLogger); return tokenCredential; } diff --git a/src/MSIdentityScaffolding/Microsoft.DotNet.MSIdentity/DeveloperCredentials/MsalTokenCredential.cs b/src/MSIdentityScaffolding/Microsoft.DotNet.MSIdentity/DeveloperCredentials/MsalTokenCredential.cs index 6c85a0585..c95fb29f7 100644 --- a/src/MSIdentityScaffolding/Microsoft.DotNet.MSIdentity/DeveloperCredentials/MsalTokenCredential.cs +++ b/src/MSIdentityScaffolding/Microsoft.DotNet.MSIdentity/DeveloperCredentials/MsalTokenCredential.cs @@ -25,12 +25,13 @@ public class MsalTokenCredential : TokenCredential public MsalTokenCredential( string? tenantId, string? username, + string? instance, IConsoleLogger consoleLogger) { _consoleLogger = consoleLogger; TenantId = tenantId ?? "organizations"; // MSA-passthrough Username = username; - Instance = "https://login.microsoftonline.com"; + Instance = instance ?? "https://login.microsoftonline.com"; // default instance } private IPublicClientApplication? App { get; set; } @@ -71,6 +72,7 @@ private async Task GetOrCreateApp() .Build(); App = PublicClientApplicationBuilder.Create(clientId) + .WithAuthority(Instance, TenantId) .WithRedirectUri(RedirectUri) .Build(); diff --git a/src/MSIdentityScaffolding/Microsoft.DotNet.MSIdentity/Properties/Resources.Designer.cs b/src/MSIdentityScaffolding/Microsoft.DotNet.MSIdentity/Properties/Resources.Designer.cs index 7189d5987..cb1fd10ae 100644 --- a/src/MSIdentityScaffolding/Microsoft.DotNet.MSIdentity/Properties/Resources.Designer.cs +++ b/src/MSIdentityScaffolding/Microsoft.DotNet.MSIdentity/Properties/Resources.Designer.cs @@ -644,7 +644,7 @@ internal static string FailedToProvisionClientApp { } /// - /// Looks up a localized string similar to Failed to retrieve all Azure AD/AD B2C objects(apps/service principals. + /// Looks up a localized string similar to Failed to retrieve all Azure AD/AD B2C objects (apps/service principals), exception: {0}. /// internal static string FailedToRetrieveADObjectsError { get { diff --git a/src/MSIdentityScaffolding/Microsoft.DotNet.MSIdentity/Properties/Resources.resx b/src/MSIdentityScaffolding/Microsoft.DotNet.MSIdentity/Properties/Resources.resx index 73d20cf61..fce3fc0e4 100644 --- a/src/MSIdentityScaffolding/Microsoft.DotNet.MSIdentity/Properties/Resources.resx +++ b/src/MSIdentityScaffolding/Microsoft.DotNet.MSIdentity/Properties/Resources.resx @@ -269,7 +269,8 @@ Failed to provision Client Application for Blazor WASM hosted project - Failed to retrieve all Azure AD/AD B2C objects(apps/service principals + Failed to retrieve all Azure AD/AD B2C objects (apps/service principals), exception: {0} + 0 = error message Failed to retrieve application parameters. @@ -347,4 +348,4 @@ Updating project packages ... - \ No newline at end of file + diff --git a/src/MSIdentityScaffolding/Microsoft.DotNet.MSIdentity/Tool/AppProvisioningTool.cs b/src/MSIdentityScaffolding/Microsoft.DotNet.MSIdentity/Tool/AppProvisioningTool.cs index 50f5e829e..bd0c0dd69 100644 --- a/src/MSIdentityScaffolding/Microsoft.DotNet.MSIdentity/Tool/AppProvisioningTool.cs +++ b/src/MSIdentityScaffolding/Microsoft.DotNet.MSIdentity/Tool/AppProvisioningTool.cs @@ -235,6 +235,7 @@ internal static TokenCredential GetTokenCredential(ProvisioningToolOptions provi return developerCredentialsReader.GetDeveloperCredentials( provisioningToolOptions.Username, currentApplicationTenantId ?? provisioningToolOptions.TenantId, + provisioningToolOptions.Instance, consoleLogger); } diff --git a/src/MSIdentityScaffolding/Microsoft.DotNet.MSIdentity/Tool/GraphObjectRetriever.cs b/src/MSIdentityScaffolding/Microsoft.DotNet.MSIdentity/Tool/GraphObjectRetriever.cs index 20033705f..66f53a139 100644 --- a/src/MSIdentityScaffolding/Microsoft.DotNet.MSIdentity/Tool/GraphObjectRetriever.cs +++ b/src/MSIdentityScaffolding/Microsoft.DotNet.MSIdentity/Tool/GraphObjectRetriever.cs @@ -65,17 +65,17 @@ public GraphObjectRetriever(GraphServiceClient graphServiceClient, IConsoleLogge nextPage = null; } } - catch (ServiceException) + catch (ServiceException se) { nextPage = null; - _consoleLogger.LogFailureAndExit(Resources.FailedToRetrieveADObjectsError); + _consoleLogger.LogFailureAndExit(string.Format(Resources.FailedToRetrieveADObjectsError, se.Message)); } } } } - catch (ServiceException) + catch (ServiceException se) { - _consoleLogger.LogFailureAndExit(Resources.FailedToRetrieveADObjectsError); + _consoleLogger.LogFailureAndExit(string.Format(Resources.FailedToRetrieveADObjectsError, se.Message)); } return graphObjectsList; diff --git a/src/MSIdentityScaffolding/Microsoft.DotNet.MSIdentity/Tool/MsAADTool.cs b/src/MSIdentityScaffolding/Microsoft.DotNet.MSIdentity/Tool/MsAADTool.cs index 69000a633..ed6d7a437 100644 --- a/src/MSIdentityScaffolding/Microsoft.DotNet.MSIdentity/Tool/MsAADTool.cs +++ b/src/MSIdentityScaffolding/Microsoft.DotNet.MSIdentity/Tool/MsAADTool.cs @@ -30,7 +30,7 @@ public MsAADTool(string commandName, ProvisioningToolOptions provisioningToolOpt ProvisioningToolOptions = provisioningToolOptions; CommandName = commandName; ConsoleLogger = new ConsoleLogger(CommandName, ProvisioningToolOptions.Json); - TokenCredential = new MsalTokenCredential(ProvisioningToolOptions.TenantId, ProvisioningToolOptions.Username, ConsoleLogger); + TokenCredential = new MsalTokenCredential(ProvisioningToolOptions.TenantId, ProvisioningToolOptions.Username, ProvisioningToolOptions.Instance, ConsoleLogger); GraphServiceClient = new GraphServiceClient(new TokenCredentialAuthenticationProvider(TokenCredential)); AzureManagementAPI = new AzureManagementAuthenticationProvider(TokenCredential); GraphObjectRetriever = new GraphObjectRetriever(GraphServiceClient, ConsoleLogger); @@ -91,13 +91,9 @@ internal async Task PrintApplicationsList() internal async Task> GetApplicationsAsync() { - var graphObjectsList = await GraphObjectRetriever.GetGraphObjects(); - if (graphObjectsList is null) - { - ConsoleLogger.LogFailureAndExit(Resources.FailedToRetrieveADObjectsError); - } - IList applicationList = new List(); + + var graphObjectsList = await GraphObjectRetriever.GetGraphObjects(); // Will exit early if call fails foreach (var graphObj in graphObjectsList!) { if (graphObj is Application app) diff --git a/src/MSIdentityScaffolding/Microsoft.DotNet.MSIdentity/Tool/ProvisioningToolOptions.cs b/src/MSIdentityScaffolding/Microsoft.DotNet.MSIdentity/Tool/ProvisioningToolOptions.cs index e8c2b9091..3e8cd1704 100644 --- a/src/MSIdentityScaffolding/Microsoft.DotNet.MSIdentity/Tool/ProvisioningToolOptions.cs +++ b/src/MSIdentityScaffolding/Microsoft.DotNet.MSIdentity/Tool/ProvisioningToolOptions.cs @@ -101,6 +101,12 @@ public string ProjectTypeIdentifier /// public string? TenantId { get; set; } + /// + /// URL that indicates a directory that MSAL can request tokens from. + /// e.g. https://login.microsoftonline.com/, https://login.microsoftonline.us/ + /// + public string? Instance { get; set; } + /// /// Required for the creation of a B2C application. /// Represents the sign-up/sign-in user flow. diff --git a/tools/dotnet-msidentity/Program.cs b/tools/dotnet-msidentity/Program.cs index dd8000538..916737d5c 100644 --- a/tools/dotnet-msidentity/Program.cs +++ b/tools/dotnet-msidentity/Program.cs @@ -36,7 +36,8 @@ public static async Task Main(string[] args) ApiClientIdOption, SusiPolicyIdOption, TenantOption, - UsernameOption); + UsernameOption, + InstanceOption); //internal commands var listAadAppsCommand = ListAADAppsCommand(); @@ -191,29 +192,29 @@ internal static async Task HandleClientSecrets(ProvisioningToolOptions prov } internal static RootCommand MsIdentityCommand() => - new RootCommand( + new( description: "Creates or updates an Azure AD / Azure AD B2C application, and updates the project, using your developer credentials (from Visual Studio, Azure CLI, Azure RM PowerShell, VS Code).\n") { }; internal static Command ListAADAppsCommand() => - new Command( + new( name: Commands.LIST_AAD_APPS_COMMAND, description: "Lists AAD Applications for a given tenant/username.\n") { - TenantOption, UsernameOption, JsonOption + TenantOption, UsernameOption, InstanceOption, JsonOption }; internal static Command ListServicePrincipalsCommand() => - new Command( + new( name: Commands.LIST_SERVICE_PRINCIPALS_COMMAND, description: "Lists AAD Service Principals.\n") { - TenantOption, UsernameOption, JsonOption + TenantOption, UsernameOption, InstanceOption, JsonOption }; internal static Command ListTenantsCommand() => - new Command( + new( name: Commands.LIST_TENANTS_COMMAND, description: "Lists AAD and AAD B2C tenants for a given user.\n") { @@ -221,47 +222,47 @@ internal static Command ListTenantsCommand() => }; internal static Command CreateClientSecretCommand() => - new Command( + new( name: Commands.ADD_CLIENT_SECRET, description: "Create client secret for an Azure AD or AD B2C app registration.\n") { - TenantOption, UsernameOption, JsonOption, ClientIdOption, ProjectFilePathOption, UpdateUserSecretsOption + TenantOption, UsernameOption, InstanceOption, JsonOption, ClientIdOption, ProjectFilePathOption, UpdateUserSecretsOption }; internal static Command RegisterApplicationCommand() => - new Command( + new( name: Commands.REGISTER_APPLICATIION_COMMAND, description: "Register an Azure AD or Azure AD B2C app registration in Azure and update the project." + "\n\t- Updates the appsettings.json file.\n") { - TenantOption, UsernameOption, JsonOption, ClientIdOption, ClientSecretOption, HostedAppIdUriOption, ApiClientIdOption, SusiPolicyIdOption, ProjectFilePathOption + TenantOption, UsernameOption, InstanceOption, JsonOption, ClientIdOption, ClientSecretOption, HostedAppIdUriOption, ApiClientIdOption, SusiPolicyIdOption, ProjectFilePathOption }; internal static Command UpdateProjectCommand() => - new Command( + new( name: Commands.UPDATE_PROJECT_COMMAND, description: "Update an Azure AD/AD B2C app registration in Azure and the project." + "\n\t- Updates the appsettings.json file." + "\n\t- Updates the Startup.cs file." + "\n\t- Updates the user secrets.\n") { - TenantOption, UsernameOption, ClientIdOption, JsonOption, ProjectFilePathOption, ConfigUpdateOption, CodeUpdateOption, PackagesUpdateOption, CallsGraphOption, CallsDownstreamApiOption, UpdateUserSecretsOption, RedirectUriOption, SusiPolicyIdOption + TenantOption, UsernameOption, InstanceOption, ClientIdOption, JsonOption, ProjectFilePathOption, ConfigUpdateOption, CodeUpdateOption, PackagesUpdateOption, CallsGraphOption, CallsDownstreamApiOption, UpdateUserSecretsOption, RedirectUriOption, SusiPolicyIdOption }; internal static Command UpdateAppRegistrationCommand() => - new Command( + new( name: Commands.UPDATE_APP_REGISTRATION_COMMAND, description: "Update an Azure AD/AD B2C app registration in Azure.\n") { - TenantOption, UsernameOption, JsonOption, HostedAppIdUriOption, ClientIdOption, RedirectUriOption, EnableIdTokenOption, EnableAccessToken, ClientProjectOption, ApiScopesOption + TenantOption, UsernameOption, InstanceOption, JsonOption, HostedAppIdUriOption, ClientIdOption, RedirectUriOption, EnableIdTokenOption, EnableAccessToken, ClientProjectOption, ApiScopesOption }; internal static Command CreateAppRegistrationCommand() => - new Command( + new( name: Commands.CREATE_APP_REGISTRATION_COMMAND, description: "Create an Azure AD/AD B2C app registration in Azure.\n") { - TenantOption, UsernameOption, JsonOption, AppDisplayName, ProjectFilePathOption, ProjectType, ClientProjectOption + TenantOption, UsernameOption, InstanceOption, JsonOption, AppDisplayName, ProjectFilePathOption, ProjectType, ClientProjectOption }; internal static Command UnregisterApplicationCommand() => @@ -271,7 +272,7 @@ internal static Command UnregisterApplicationCommand() => description: "Unregister an Azure AD or Azure AD B2C app registration in Azure." + "\n\t- Updates the appsettings.json file.\n") { - TenantOption, UsernameOption, JsonOption, HostedAppIdUriOption, ProjectFilePathOption, ClientIdOption + TenantOption, UsernameOption, InstanceOption, JsonOption, HostedAppIdUriOption, ProjectFilePathOption, ClientIdOption }; internal static Option JsonOption { get; } = @@ -457,5 +458,14 @@ internal static Command UnregisterApplicationCommand() => { IsRequired = false }; + + internal static Option InstanceOption { get; } = + new( + aliases: new[] { "-i", "--instance" }, + description: "Instance where the Azure AD or Azure AD B2C tenant is located.\n" + + "If not specified, will default to https://login.microsoftonline.com/") + { + IsRequired = false + }; } } diff --git a/tools/dotnet-msidentity/ProvisioningToolOptionsBinder.cs b/tools/dotnet-msidentity/ProvisioningToolOptionsBinder.cs index 1a2206031..7fe48fe75 100644 --- a/tools/dotnet-msidentity/ProvisioningToolOptionsBinder.cs +++ b/tools/dotnet-msidentity/ProvisioningToolOptionsBinder.cs @@ -35,6 +35,7 @@ internal class ProvisioningToolOptionsBinder : BinderBase _susiPolicyIdOption; private readonly Option _tenantOption; private readonly Option _usernameOption; + private readonly Option _instanceOption; public ProvisioningToolOptionsBinder( Option jsonOption, @@ -58,7 +59,8 @@ public ProvisioningToolOptionsBinder( Option apiClientIdOption, Option susiPolicyIdOption, Option tenantOption, - Option usernameOption) + Option usernameOption, + Option instanceOption) { _jsonOption = jsonOption; _enableIdTokenOption = enableIdTokenOption; @@ -82,6 +84,7 @@ public ProvisioningToolOptionsBinder( _susiPolicyIdOption = susiPolicyIdOption; _tenantOption = tenantOption; _usernameOption = usernameOption; + _instanceOption = instanceOption; } protected override ProvisioningToolOptions GetBoundValue(BindingContext bindingContext) @@ -89,7 +92,6 @@ protected override ProvisioningToolOptions GetBoundValue(BindingContext bindingC IList redirectUriList = bindingContext.ParseResult.GetValue(_redirectUriOption) ?? new List(); return new ProvisioningToolOptions { - HostedApiScopes = bindingContext.ParseResult.GetValue(_hostedAppIdUriOption), CallsDownstreamApi = bindingContext.ParseResult.GetValue(_callsDownstreamApiOption), UpdateUserSecrets = bindingContext.ParseResult.GetValue(_updateUserSecretsOption), @@ -112,7 +114,8 @@ protected override ProvisioningToolOptions GetBoundValue(BindingContext bindingC CodeUpdate = bindingContext.ParseResult.GetValue(_codeUpdateOption), PackagesUpdate = bindingContext.ParseResult.GetValue(_packagesUpdateOption), ApiScopes = bindingContext.ParseResult.GetValue(_apiScopesOption), - ClientProject = bindingContext.ParseResult.GetValue(_clientProjectOption) + ClientProject = bindingContext.ParseResult.GetValue(_clientProjectOption), + Instance = bindingContext.ParseResult.GetValue(_instanceOption) }; } } diff --git a/tools/dotnet-scaffold/Program.cs b/tools/dotnet-scaffold/Program.cs index 7a4e37035..7be05ed2c 100644 --- a/tools/dotnet-scaffold/Program.cs +++ b/tools/dotnet-scaffold/Program.cs @@ -66,7 +66,8 @@ public static int Main(string[] args) MsIdentity.ApiClientIdOption, MsIdentity.SusiPolicyIdOption, MsIdentity.TenantOption, - MsIdentity.UsernameOption); + MsIdentity.UsernameOption, + MsIdentity.InstanceOption); //internal commands var listAadAppsCommand = MsIdentity.ListAADAppsCommand();