diff --git a/src/MSIdentityScaffolding/Microsoft.DotNet.MSIdentity/CodeReaderWriter/ProjectModifier.cs b/src/MSIdentityScaffolding/Microsoft.DotNet.MSIdentity/CodeReaderWriter/ProjectModifier.cs index a9ce71c274..e9fa5217cb 100644 --- a/src/MSIdentityScaffolding/Microsoft.DotNet.MSIdentity/CodeReaderWriter/ProjectModifier.cs +++ b/src/MSIdentityScaffolding/Microsoft.DotNet.MSIdentity/CodeReaderWriter/ProjectModifier.cs @@ -50,8 +50,7 @@ public async Task AddAuthCodeAsync() if (csProjFiles.Count() != 1) { var errorMsg = string.Format(Resources.ProjectPathError, _toolOptions.ProjectFilePath); - _consoleLogger.LogFailure(errorMsg, Commands.UPDATE_PROJECT_COMMAND); - return; + _consoleLogger.LogFailureAndExit(errorMsg); } _toolOptions.ProjectFilePath = csProjFiles.First(); @@ -89,7 +88,7 @@ public async Task AddAuthCodeAsync() await HandleCodeFileAsync(file, project, options, codeModifierConfig.Identifier); } - _consoleLogger.LogJsonMessage(new JsonResponse(Commands.UPDATE_PROJECT_COMMAND, State.Success, output: _output.ToString().TrimEnd())); + _consoleLogger.LogJsonMessage(State.Success, output: _output.ToString().TrimEnd()); } internal static string GetCodeFileString(CodeFile file, string identifier) // todo make all code files strings diff --git a/src/MSIdentityScaffolding/Microsoft.DotNet.MSIdentity/DeveloperCredentials/MsalTokenCredential.cs b/src/MSIdentityScaffolding/Microsoft.DotNet.MSIdentity/DeveloperCredentials/MsalTokenCredential.cs index 43e18e5a26..620ae32b8a 100644 --- a/src/MSIdentityScaffolding/Microsoft.DotNet.MSIdentity/DeveloperCredentials/MsalTokenCredential.cs +++ b/src/MSIdentityScaffolding/Microsoft.DotNet.MSIdentity/DeveloperCredentials/MsalTokenCredential.cs @@ -7,6 +7,7 @@ using System.Threading; using System.Threading.Tasks; using Azure.Core; +using Microsoft.DotNet.MSIdentity.Properties; using Microsoft.DotNet.MSIdentity.Shared; using Microsoft.Identity.Client; using Microsoft.Identity.Client.Extensions.Msal; @@ -105,7 +106,7 @@ public override async ValueTask GetTokenAsync(TokenRequestContext r { if (account == null && !string.IsNullOrEmpty(Username)) { - _consoleLogger.LogFailure( + _consoleLogger.LogFailureAndExit( $"No valid tokens found in the cache.\n" + $"Please sign-in to Visual Studio with this account: {Username}.\n\n" + $"After signing-in, re-run the tool."); @@ -118,24 +119,30 @@ public override async ValueTask GetTokenAsync(TokenRequestContext r } catch (MsalServiceException ex) { + // AAD error codes: https://learn.microsoft.com/en-us/azure/active-directory/develop/reference-aadsts-error-codes if (ex.Message.Contains("AADSTS70002")) // "The client does not exist or is not enabled for consumers" { - _consoleLogger.LogFailure( + // We want to exit here because this is probably an MSA without an AAD tenant. + _consoleLogger.LogFailureAndExit( "An Azure AD tenant, and a user in that tenant, " + "needs to be created for this account before an application can be created. " + "See https://aka.ms/ms-identity-app/create-a-tenant. "); - Environment.Exit(1); // we want to exit here because this is probably an MSA without an AAD tenant. } - _consoleLogger.LogFailure($"Error encountered with sign-in. See error message for details:\n{ex.Message}"); - Environment.Exit(1); // we want to exit here. Re-sign in will not resolve the issue. + // we want to exit here. Re-sign in will not resolve the issue. + _consoleLogger.LogFailureAndExit(string.Join(Environment.NewLine, Resources.SignInError, ex.Message)); } catch (Exception ex) { - _consoleLogger.LogFailure($"Error encountered with sign-in. See error message for details:\n{ex.Message}"); - Environment.Exit(1); + _consoleLogger.LogFailureAndExit(string.Join(Environment.NewLine, Resources.SignInError, ex.Message)); } - return new AccessToken(result.AccessToken, result.ExpiresOn); + + if (result is null) + { + _consoleLogger.LogFailureAndExit(Resources.FailedToAcquireToken); + } + + return new AccessToken(result!.AccessToken, result.ExpiresOn); } } } diff --git a/src/MSIdentityScaffolding/Microsoft.DotNet.MSIdentity/MicrosoftIdentityPlatform/MicrosoftIdentityPlatformApplicationManager.cs b/src/MSIdentityScaffolding/Microsoft.DotNet.MSIdentity/MicrosoftIdentityPlatform/MicrosoftIdentityPlatformApplicationManager.cs index 9cefe62db8..d27f075285 100644 --- a/src/MSIdentityScaffolding/Microsoft.DotNet.MSIdentity/MicrosoftIdentityPlatform/MicrosoftIdentityPlatformApplicationManager.cs +++ b/src/MSIdentityScaffolding/Microsoft.DotNet.MSIdentity/MicrosoftIdentityPlatform/MicrosoftIdentityPlatformApplicationManager.cs @@ -11,6 +11,7 @@ using Microsoft.DotNet.MSIdentity.Properties; using Microsoft.DotNet.MSIdentity.Shared; using Microsoft.DotNet.MSIdentity.Tool; +using Microsoft.DotNet.Scaffolding.Shared; using Microsoft.Graph; namespace Microsoft.DotNet.MSIdentity.MicrosoftIdentityPlatformApplication @@ -27,109 +28,106 @@ public class MicrosoftIdentityPlatformApplicationManager internal async Task CreateNewAppAsync( TokenCredential tokenCredential, ApplicationParameters applicationParameters, - IConsoleLogger consoleLogger, - string commandName) + IConsoleLogger consoleLogger) { - var graphServiceClient = GetGraphServiceClient(tokenCredential); - - // Get the tenant - Organization? tenant = await GetTenant(graphServiceClient); - if (tenant != null && tenant.TenantType.Equals("AAD B2C", StringComparison.OrdinalIgnoreCase)) - { - applicationParameters.IsB2C = true; - } - // Create the app. - Application application = new Application() + try { - DisplayName = applicationParameters.ApplicationDisplayName, - SignInAudience = AppParameterAudienceToMicrosoftIdentityPlatformAppAudience(applicationParameters.SignInAudience!), - Description = applicationParameters.Description - }; + var graphServiceClient = GetGraphServiceClient(tokenCredential); - if (applicationParameters.IsWebApi.GetValueOrDefault()) - { - application.Api = new ApiApplication() + // Get the tenant + Organization? tenant = await GetTenant(graphServiceClient, consoleLogger); + if (tenant != null && tenant.TenantType.Equals("AAD B2C", StringComparison.OrdinalIgnoreCase)) { - RequestedAccessTokenVersion = 2, + applicationParameters.IsB2C = true; + } + // Create the app. + Application application = new Application() + { + DisplayName = applicationParameters.ApplicationDisplayName, + SignInAudience = AppParameterAudienceToMicrosoftIdentityPlatformAppAudience(applicationParameters.SignInAudience!), + Description = applicationParameters.Description }; - } - if (applicationParameters.IsWebApp.GetValueOrDefault()) - { - AddWebAppPlatform(application, applicationParameters); - } - else if (applicationParameters.IsBlazorWasm) - { - AddSpaPlatform(application, applicationParameters.WebRedirectUris); - } + if (applicationParameters.IsWebApi.GetValueOrDefault()) + { + application.Api = new ApiApplication() + { + RequestedAccessTokenVersion = 2, + }; + } - var createdApplication = await graphServiceClient.Applications - .Request() - .AddAsync(application); + if (applicationParameters.IsWebApp.GetValueOrDefault()) + { + AddWebAppPlatform(application, applicationParameters); + } + else if (applicationParameters.IsBlazorWasm) + { + AddSpaPlatform(application, applicationParameters.WebRedirectUris); + } - // Create service principal, necessary for Web API applications - // and useful for Blazorwasm hosted applications. We create it always. - ServicePrincipal servicePrincipal = new ServicePrincipal - { - AppId = createdApplication.AppId, - }; + var createdApplication = await graphServiceClient.Applications + .Request() + .AddAsync(application); - ServicePrincipal? createdSp = await graphServiceClient.ServicePrincipals - .Request() - .AddAsync(servicePrincipal); + // Create service principal, necessary for Web API applications + // and useful for Blazorwasm hosted applications. We create it always. + var createdSp = await GetOrCreateSP(graphServiceClient, createdApplication.AppId, consoleLogger); - if (createdSp is null) - { - consoleLogger.LogFailure(Resources.FailedToGetServicePrincipal, commandName); - return null; - } + // B2C does not allow user consent, and therefore we need to explicity grant permissions + if (applicationParameters.IsB2C) + { + string scopes = GetMsGraphScopes(applicationParameters); // Explicit usage of MicrosoftGraph openid and offline_access in the case of Azure AD B2C. + await AddDownstreamApiPermissions(scopes, graphServiceClient, application, createdSp); + } - // B2C does not allow user consent, and therefore we need to explicity grant permissions - if (applicationParameters.IsB2C) - { - string scopes = GetMsGraphScopes(applicationParameters); // Explicit usage of MicrosoftGraph openid and offline_access in the case of Azure AD B2C. - await AddDownstreamApiPermissions(scopes, graphServiceClient, application, createdSp); - } + // For web API, we need to know the appId of the created app to compute the Identifier URI, + // and therefore we need to do it after the app is created (updating the app) + if (applicationParameters.IsWebApi.GetValueOrDefault() && createdApplication.Api != null) + { + await ExposeWebApiScopes(graphServiceClient, createdApplication, applicationParameters); - // For web API, we need to know the appId of the created app to compute the Identifier URI, - // and therefore we need to do it after the app is created (updating the app) - if (applicationParameters.IsWebApi.GetValueOrDefault() && createdApplication.Api != null) - { - await ExposeWebApiScopes(graphServiceClient, createdApplication, applicationParameters); + // Re-reading the app to be sure to have everything. + createdApplication = (await graphServiceClient.Applications + .Request() + .Filter($"appId eq '{createdApplication.AppId}'") + .GetAsync()).FirstOrDefault(); + } - // Re-reading the app to be sure to have everything. - createdApplication = (await graphServiceClient.Applications - .Request() - .Filter($"appId eq '{createdApplication.AppId}'") - .GetAsync()).FirstOrDefault(); - } + // log json console message inside this method since we need the Microsoft.Graph.Application + if (createdApplication is null) + { + consoleLogger.LogFailureAndExit(Resources.FailedToCreateApp); + return null; + } - // log json console message inside this method since we need the Microsoft.Graph.Application - if (createdApplication is null) - { - consoleLogger.LogFailure(Resources.FailedToCreateApp, commandName); - return null; - } + if (applicationParameters.IsB2C) + { + createdApplication!.AdditionalData.Add("IsB2C", true); + } - if (applicationParameters.IsB2C) - { - createdApplication.AdditionalData.Add("IsB2C", true); - } + ApplicationParameters? effectiveApplicationParameters = GetEffectiveApplicationParameters(tenant!, createdApplication, applicationParameters); - ApplicationParameters? effectiveApplicationParameters = GetEffectiveApplicationParameters(tenant!, createdApplication, applicationParameters); + // Add password credentials + if (applicationParameters.CallsMicrosoftGraph || applicationParameters.CallsDownstreamApi) + { + await AddPasswordCredentialsAsync( + graphServiceClient, + createdApplication.Id, + effectiveApplicationParameters, + consoleLogger); + } + + var output = string.Format(Resources.CreatedAppRegistration, effectiveApplicationParameters.ApplicationDisplayName, effectiveApplicationParameters.ClientId); + consoleLogger.LogJsonMessage(State.Success, content: createdApplication, output: output); - // Add password credentials - if (applicationParameters.CallsMicrosoftGraph || applicationParameters.CallsDownstreamApi) + return effectiveApplicationParameters; + } + catch (Exception ex) { - await AddPasswordCredentialsAsync( - graphServiceClient, - createdApplication.Id, - effectiveApplicationParameters, - consoleLogger); + var errorMessage = string.IsNullOrEmpty(ex.Message) ? ex.ToString() : ex.Message; + consoleLogger.LogFailureAndExit(errorMessage); + return null; } - - consoleLogger.LogJsonMessage(new JsonResponse(commandName, State.Success, createdApplication)); - return effectiveApplicationParameters; } private static string GetMsGraphScopes(ApplicationParameters applicationParameters) @@ -147,7 +145,7 @@ private static string GetMsGraphScopes(ApplicationParameters applicationParamete return apiScopes.Trim(); } - private static async Task GetTenant(GraphServiceClient graphServiceClient) + private static async Task GetTenant(GraphServiceClient graphServiceClient, IConsoleLogger consoleLogger) { Organization? tenant = null; try @@ -160,21 +158,19 @@ private static string GetMsGraphScopes(ApplicationParameters applicationParamete { if (ex.InnerException != null) { - Console.WriteLine(ex.InnerException.Message); + consoleLogger.LogFailureAndExit(ex.InnerException.Message); } else { if (ex.Message.Contains("User was not found") || ex.Message.Contains("not found in tenant")) { - Console.WriteLine("User was not found.\nUse both --tenant-id --username .\nAnd re-run the tool."); + consoleLogger.LogFailureAndExit("User was not found.\nUse both --tenant-id --username .\nAnd re-run the tool."); } else { - Console.WriteLine(ex.Message); + consoleLogger.LogFailureAndExit(ex.Message); } } - - Environment.Exit(1); } return tenant; @@ -189,38 +185,36 @@ private static string GetMsGraphScopes(ApplicationParameters applicationParamete /// /// /// - internal async Task UpdateApplication( + internal async Task UpdateApplication( TokenCredential tokenCredential, ApplicationParameters? parameters, ProvisioningToolOptions toolOptions, - string commandName) + IConsoleLogger consoleLogger, + StringBuilder? output = null) { if (parameters is null) { - return new JsonResponse(commandName, State.Fail, output: string.Format(Resources.FailedToUpdateAppNull, nameof(ApplicationParameters))); + consoleLogger.LogFailureAndExit(string.Format(Resources.FailedToUpdateAppNull, nameof(ApplicationParameters))); } var graphServiceClient = GetGraphServiceClient(tokenCredential); var remoteApp = (await graphServiceClient.Applications.Request() - .Filter($"appId eq '{parameters.ClientId}'").GetAsync()).FirstOrDefault(app => app.AppId.Equals(parameters.ClientId)); + .Filter($"appId eq '{parameters!.ClientId}'").GetAsync()).FirstOrDefault(app => app.AppId.Equals(parameters.ClientId)); if (remoteApp is null) { - return new JsonResponse(commandName, State.Fail, output: string.Format(Resources.NotFound, parameters.ClientId)); + consoleLogger.LogFailureAndExit(string.Format(Resources.NotFound, parameters.ClientId)); + return; } (bool needsUpdates, Application appUpdates) = GetApplicationUpdates(remoteApp, toolOptions, parameters); - StringBuilder output = new StringBuilder(); + output ??= new StringBuilder(); // B2C does not allow user consent, and therefore we need to explicity grant permissions if (parameters.IsB2C && parameters.CallsDownstreamApi && !string.IsNullOrEmpty(toolOptions.ApiScopes)) { // TODO: Add if it's B2C, acquire or create the SUSI Policy - var servicePrincipal = await GetOrCreateSP(graphServiceClient, parameters.ClientId); - if (servicePrincipal is null) - { - return new JsonResponse(commandName, State.Fail, output: Resources.FailedToGetServicePrincipal); - } + var servicePrincipal = await GetOrCreateSP(graphServiceClient, parameters.ClientId, consoleLogger); await AddDownstreamApiPermissions(toolOptions.ApiScopes, graphServiceClient, appUpdates, servicePrincipal, output); needsUpdates = true; @@ -228,7 +222,8 @@ internal async Task UpdateApplication( if (!needsUpdates) { - return new JsonResponse(commandName, State.Success, output: string.Format(Resources.NoUpdateNecessary, remoteApp.DisplayName, remoteApp.AppId)); + consoleLogger.LogJsonMessage(State.Success, output: string.Format(Resources.NoUpdateNecessary, remoteApp.DisplayName, remoteApp.AppId)); + return; } try @@ -236,12 +231,12 @@ internal async Task UpdateApplication( // TODO: update other fields, see https://github.com/jmprieur/app-provisonning-tool/issues/10 var updatedApp = await graphServiceClient.Applications[remoteApp.Id].Request().UpdateAsync(appUpdates); output.Append(string.Format(Resources.SuccessfullyUpdatedApp, remoteApp.DisplayName, remoteApp.AppId)); - return new JsonResponse(commandName, State.Success, output.ToString()); + consoleLogger.LogJsonMessage(State.Success, output: output.ToString()); } catch (ServiceException se) { output.Append(se.Error?.Message); - return new JsonResponse(commandName, State.Fail, output.ToString()); + consoleLogger.LogFailureAndExit(output.ToString()); } } @@ -260,7 +255,7 @@ await AddAdminConsentToApiPermissions( output); } - private static async Task GetOrCreateSP(GraphServiceClient graphServiceClient, string? clientId) + private static async Task GetOrCreateSP(GraphServiceClient graphServiceClient, string? clientId, IConsoleLogger consoleLogger) { var servicePrincipal = (await graphServiceClient.ServicePrincipals .Request() @@ -281,7 +276,12 @@ await AddAdminConsentToApiPermissions( .AddAsync(sp); } - return servicePrincipal; + if (servicePrincipal is null) + { + consoleLogger.LogFailureAndExit(Resources.FailedToGetServicePrincipal); + } + + return servicePrincipal!; } /// @@ -810,7 +810,7 @@ internal GraphServiceClient GetGraphServiceClient(TokenCredential tokenCredentia } var graphServiceClient = GetGraphServiceClient(tokenCredential); - Organization? tenant = await GetTenant(graphServiceClient); + Organization? tenant = await GetTenant(graphServiceClient, consoleLogger); var application = await GetApplication(tokenCredential, applicationParameters); if (application is null) { @@ -824,6 +824,11 @@ internal GraphServiceClient GetGraphServiceClient(TokenCredential tokenCredentia application, applicationParameters); + if (effectiveApplicationParameters is null) + { + consoleLogger.LogFailureAndExit(Resources.FailedToRetrieveApplicationParameters); + } + return effectiveApplicationParameters; } diff --git a/src/MSIdentityScaffolding/Microsoft.DotNet.MSIdentity/Properties/Resources.Designer.cs b/src/MSIdentityScaffolding/Microsoft.DotNet.MSIdentity/Properties/Resources.Designer.cs index d20a505610..b078207050 100644 --- a/src/MSIdentityScaffolding/Microsoft.DotNet.MSIdentity/Properties/Resources.Designer.cs +++ b/src/MSIdentityScaffolding/Microsoft.DotNet.MSIdentity/Properties/Resources.Designer.cs @@ -547,6 +547,15 @@ internal static string FailedClientSecretWithApp { } } + /// + /// Looks up a localized string similar to Failed to acquire a token.. + /// + internal static string FailedToAcquireToken { + get { + return ResourceManager.GetString("FailedToAcquireToken", resourceCulture); + } + } + /// /// Looks up a localized string similar to Failed to create Azure AD/AD B2C app registration.. /// @@ -575,7 +584,7 @@ internal static string FailedToModifyCodeFile { } /// - /// Looks up a localized string similar to Failed to provision Client Application. + /// Looks up a localized string similar to Failed to provision Client Application for Blazor WASM hosted project. /// internal static string FailedToProvisionClientApp { get { @@ -592,6 +601,15 @@ internal static string FailedToRetrieveADObjectsError { } } + /// + /// Looks up a localized string similar to Failed to retrieve application parameters.. + /// + internal static string FailedToRetrieveApplicationParameters { + get { + return ResourceManager.GetString("FailedToRetrieveApplicationParameters", resourceCulture); + } + } + /// /// Looks up a localized string similar to Failed to update Azure AD app registration {0} ({1}). /// @@ -611,7 +629,7 @@ internal static string FailedToUpdateAppNull { } /// - /// Looks up a localized string similar to Failed to update client app program.cs file. + /// Looks up a localized string similar to Failed to update client app program.cs file for Blazor WASM hosted project. /// internal static string FailedToUpdateClientAppCode { get { @@ -722,6 +740,15 @@ internal static string ResourceFileParseError { } } + /// + /// Looks up a localized string similar to Error encountered with sign-in. See error message for details:. + /// + internal static string SignInError { + get { + return ResourceManager.GetString("SignInError", resourceCulture); + } + } + /// /// Looks up a localized string similar to SUCCESS. /// diff --git a/src/MSIdentityScaffolding/Microsoft.DotNet.MSIdentity/Properties/Resources.resx b/src/MSIdentityScaffolding/Microsoft.DotNet.MSIdentity/Properties/Resources.resx index ff7d2facd1..0dab9f7de6 100644 --- a/src/MSIdentityScaffolding/Microsoft.DotNet.MSIdentity/Properties/Resources.resx +++ b/src/MSIdentityScaffolding/Microsoft.DotNet.MSIdentity/Properties/Resources.resx @@ -240,6 +240,9 @@ Failed to add client secret for Azure AD app : {0}({1}) + + Failed to acquire a token. + Failed to create Azure AD/AD B2C app registration. @@ -251,11 +254,14 @@ 0 = File name, 1 = Exception message - Failed to provision Client Application + 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 application parameters. + Failed to update Azure AD app registration {0} ({1}) 0 = Display Name, 1 = App Id @@ -265,7 +271,7 @@ 0 = null object - Failed to update client app program.cs file + Failed to update client app program.cs file for Blazor WASM hosted project Initializing User Secrets . . . @@ -307,6 +313,9 @@ Resource file {0} could not be parsed. 0 = CodeModifierConfigPropertyInfo.Name + + Error encountered with sign-in. See error message for details: + SUCCESS diff --git a/src/MSIdentityScaffolding/Microsoft.DotNet.MSIdentity/Tool/AppProvisioningTool.cs b/src/MSIdentityScaffolding/Microsoft.DotNet.MSIdentity/Tool/AppProvisioningTool.cs index 40f7e80335..12b6f6add7 100644 --- a/src/MSIdentityScaffolding/Microsoft.DotNet.MSIdentity/Tool/AppProvisioningTool.cs +++ b/src/MSIdentityScaffolding/Microsoft.DotNet.MSIdentity/Tool/AppProvisioningTool.cs @@ -18,6 +18,7 @@ using Microsoft.DotNet.MSIdentity.Shared; using Microsoft.DotNet.MSIdentity.Tool; using Microsoft.Graph; +using ConsoleLogger = Microsoft.DotNet.MSIdentity.Shared.ConsoleLogger; using Directory = System.IO.Directory; using ProjectDescription = Microsoft.DotNet.MSIdentity.Project.ProjectDescription; @@ -45,25 +46,21 @@ public AppProvisioningTool(string commandName, ProvisioningToolOptions provision { CommandName = commandName; ProvisioningToolOptions = provisioningToolOptions; - ConsoleLogger = new ConsoleLogger(ProvisioningToolOptions.Json, silent); + ConsoleLogger = new ConsoleLogger(CommandName, ProvisioningToolOptions.Json, silent); } public async Task Run() { - if (!ValidateProjectPath()) - { - Environment.Exit(1); - } + ValidateProjectPath(); var projectDescription = ProjectDescriptionReader.GetProjectDescription(ProvisioningToolOptions.ProjectTypeIdentifier); if (projectDescription == null) { var errorMessage = string.Format(Resources.NoProjectDescriptionFound, ProvisioningToolOptions.ProjectTypeIdentifier); - ConsoleLogger.LogFailure(errorMessage, CommandName); - Environment.Exit(1); + ConsoleLogger.LogFailureAndExit(errorMessage); } - ConsoleLogger.LogMessage(string.Format(Resources.DetectedProjectType, projectDescription.Identifier)); + ConsoleLogger.LogMessage(string.Format(Resources.DetectedProjectType, projectDescription!.Identifier)); ProvisioningToolOptions.ProjectType ??= projectDescription.Identifier?.Replace("dotnet-", ""); ProjectAuthenticationSettings projectSettings = InferApplicationParameters( @@ -156,14 +153,15 @@ await WriteApplicationRegistration( } /// - /// Ensures that ProjectPath is updated if ProjectFilePath argument exists + /// Ensures that ProjectPath is updated if ProjectFilePath argument exists, + /// logs failure and exits if ProjectFilePath cannot be determined /// - /// true if valid else false - private bool ValidateProjectPath() + /// + private void ValidateProjectPath() { if (string.IsNullOrEmpty(ProvisioningToolOptions.ProjectFilePath)) { - return true; + return; } if (System.IO.File.Exists(ProvisioningToolOptions.ProjectFilePath) @@ -174,12 +172,11 @@ private bool ValidateProjectPath() ProvisioningToolOptions.ProjectPath = projectPath; } - return true; + return; } var errorMsg = string.Format(Resources.ProjectPathError, ProvisioningToolOptions.ProjectFilePath); - ConsoleLogger.LogFailure(errorMsg, CommandName); - return false; + ConsoleLogger.LogFailureAndExit(errorMsg); } private ProjectAuthenticationSettings InferApplicationParameters( @@ -248,15 +245,12 @@ internal static TokenCredential GetTokenCredential(ProvisioningToolOptions provi /// private async Task CreateAppRegistration(TokenCredential tokenCredential, ApplicationParameters applicationParameters) { - ApplicationParameters? resultAppParameters = await MicrosoftIdentityPlatformApplicationManager.CreateNewAppAsync(tokenCredential, applicationParameters, ConsoleLogger, CommandName); + ApplicationParameters? resultAppParameters = await MicrosoftIdentityPlatformApplicationManager.CreateNewAppAsync(tokenCredential, applicationParameters, ConsoleLogger); if (resultAppParameters is null || string.IsNullOrEmpty(resultAppParameters.ClientId)) { - string failMessage = Resources.FailedToCreateApp; - ConsoleLogger.LogMessage(failMessage, LogMessageType.Error); - return null; + ConsoleLogger.LogFailureAndExit(Resources.FailedToCreateApp); } - ConsoleLogger.LogMessage(string.Format(Resources.CreatedAppRegistration, resultAppParameters.ApplicationDisplayName, resultAppParameters.ClientId)); return resultAppParameters; } @@ -271,12 +265,8 @@ private async Task ReadMicrosoftIdentityApplication( ApplicationParameters applicationParameters) { var currentApplicationParameters = await MicrosoftIdentityPlatformApplicationManager.ReadApplication(tokenCredential, applicationParameters, ConsoleLogger); - if (currentApplicationParameters is null) - { - Environment.Exit(1); - } - return currentApplicationParameters; + return currentApplicationParameters!; } /// @@ -300,23 +290,20 @@ private async Task UpdateAppRegistration(TokenCredential tokenCredential, Applic var clientApplicationParameters = await ConfigureBlazorWasmHostedClientAsync(serverApplicationParameters: applicationParameters); if (clientApplicationParameters is null) { - ConsoleLogger.LogFailure("Failed to provision Blazor Wasm hosted scenario"); - Environment.Exit(1); + ConsoleLogger.LogFailureAndExit("Failed to provision Blazor Wasm hosted scenario"); // TODO string + return; } ProvisioningToolOptions.BlazorWasmClientAppId = clientApplicationParameters.ClientId; output.AppendLine(string.Format(Resources.ConfiguredBlazorWasmClient, clientApplicationParameters.ApplicationDisplayName, clientApplicationParameters.ClientId)); } - var jsonResponse = await MicrosoftIdentityPlatformApplicationManager.UpdateApplication( + await MicrosoftIdentityPlatformApplicationManager.UpdateApplication( tokenCredential, applicationParameters, ProvisioningToolOptions, - CommandName); - - output.Append(jsonResponse.Output); - - ConsoleLogger.LogJsonMessage(new JsonResponse(CommandName, jsonResponse.State, output: output.ToString())); + ConsoleLogger, + output); } /// @@ -345,18 +332,16 @@ private async Task UpdateAppRegistration(TokenCredential tokenCredential, Applic var clientApplicationParameters = await provisionClientAppRegistration.Run(); if (clientApplicationParameters == null) { - ConsoleLogger.LogFailure(Resources.FailedToProvisionClientApp, CommandName); - return null; + ConsoleLogger.LogFailureAndExit(Resources.FailedToProvisionClientApp); } // Update program.cs file - clientToolOptions.ClientId = clientApplicationParameters.ClientId; + clientToolOptions.ClientId = clientApplicationParameters!.ClientId; var updateCode = new AppProvisioningTool(Commands.UPDATE_PROJECT_COMMAND, clientToolOptions, silent: true); clientApplicationParameters = await updateCode.Run(); if (clientApplicationParameters == null) { - ConsoleLogger.LogFailure(Resources.FailedToUpdateClientAppCode, CommandName); - return null; + ConsoleLogger.LogFailureAndExit(Resources.FailedToUpdateClientAppCode); } return clientApplicationParameters; @@ -476,12 +461,10 @@ await MicrosoftIdentityPlatformApplicationManager.AddPasswordCredentialsAsync( private async Task AddClientSecret(TokenCredential tokenCredential, ApplicationParameters applicationParameters) { string? output; - JsonResponse jsonResponse = new JsonResponse(CommandName); if (string.IsNullOrEmpty(applicationParameters.GraphEntityId)) { - jsonResponse.State = State.Fail; - jsonResponse.Output = Resources.FailedClientSecret; + ConsoleLogger.LogFailureAndExit(Resources.FailedClientSecret); } else { @@ -502,25 +485,18 @@ private async Task AddClientSecret(TokenCredential tokenCredential, ApplicationP if (!string.IsNullOrEmpty(password)) { - output = string.Format(Resources.ClientSecret, password); - jsonResponse.State = State.Success; - jsonResponse.Content = new KeyValuePair("ClientSecret", password); + ConsoleLogger.LogJsonMessage(State.Success, content: new KeyValuePair("ClientSecret", password)); } else { output = string.Format(Resources.FailedClientSecretWithApp, applicationParameters.ApplicationDisplayName, applicationParameters.ClientId); - jsonResponse.State = State.Fail; - jsonResponse.Content = "TODO Empty password"; + ConsoleLogger.LogFailureAndExit(output); } } catch (ServiceException se) { - output = se.Error?.ToString(); - jsonResponse.State = State.Fail; - jsonResponse.Output = se.Error?.Code; // TODO refactor + ConsoleLogger.LogFailureAndExit(se.Error?.Code); } - - ConsoleLogger.LogJsonMessage(jsonResponse); } } @@ -533,20 +509,15 @@ private async Task AddClientSecret(TokenCredential tokenCredential, ApplicationP private async Task UnregisterApplication(TokenCredential tokenCredential, ApplicationParameters applicationParameters) { bool unregisterSuccess = await MicrosoftIdentityPlatformApplicationManager.UnregisterAsync(tokenCredential, applicationParameters); - JsonResponse jsonResponse = new JsonResponse(CommandName); if (unregisterSuccess) { string outputMessage = $"Unregistered the Azure AD w/ client id = {applicationParameters.ClientId}\n"; - jsonResponse.State = State.Success; - jsonResponse.Output = outputMessage; - ConsoleLogger.LogJsonMessage(jsonResponse); + ConsoleLogger.LogJsonMessage(State.Success, output: outputMessage); } else { string outputMessage = $"Unable to unregister the Azure AD w/ client id = {applicationParameters.ClientId}\n"; - jsonResponse.State = State.Fail; - jsonResponse.Output = outputMessage; - ConsoleLogger.LogJsonMessage(jsonResponse); + ConsoleLogger.LogFailureAndExit(outputMessage); } } @@ -619,7 +590,7 @@ private void WriteSummary(Summary summary) private async Task WriteApplicationRegistration(Summary summary, ApplicationParameters reconcialedApplicationParameters, TokenCredential tokenCredential) { summary.changes.Add(new Change($"Writing the project AppId = {reconcialedApplicationParameters.ClientId}")); - await MicrosoftIdentityPlatformApplicationManager.UpdateApplication(tokenCredential, reconcialedApplicationParameters, ProvisioningToolOptions, CommandName); + await MicrosoftIdentityPlatformApplicationManager.UpdateApplication(tokenCredential, reconcialedApplicationParameters, ProvisioningToolOptions, ConsoleLogger); } /// @@ -681,7 +652,7 @@ private bool Reconciliate(ApplicationParameters applicationParameters, Applicati if (currentApplicationParameters == null && !ProvisioningToolOptions.Unregister) { - currentApplicationParameters = await MicrosoftIdentityPlatformApplicationManager.CreateNewAppAsync(tokenCredential, applicationParameters, ConsoleLogger, CommandName); + currentApplicationParameters = await MicrosoftIdentityPlatformApplicationManager.CreateNewAppAsync(tokenCredential, applicationParameters, ConsoleLogger); if (currentApplicationParameters != null) { ConsoleLogger.LogMessage($"Created app {currentApplicationParameters.ApplicationDisplayName} - {currentApplicationParameters.ClientId}. "); diff --git a/src/MSIdentityScaffolding/Microsoft.DotNet.MSIdentity/Tool/GraphObjectRetriever.cs b/src/MSIdentityScaffolding/Microsoft.DotNet.MSIdentity/Tool/GraphObjectRetriever.cs index b305353949..2cd3380d44 100644 --- a/src/MSIdentityScaffolding/Microsoft.DotNet.MSIdentity/Tool/GraphObjectRetriever.cs +++ b/src/MSIdentityScaffolding/Microsoft.DotNet.MSIdentity/Tool/GraphObjectRetriever.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; @@ -65,16 +66,14 @@ public GraphObjectRetriever(GraphServiceClient graphServiceClient, IConsoleLogge catch (ServiceException) { nextPage = null; - _consoleLogger.LogFailure(Resources.FailedToRetrieveADObjectsError); - return null; + _consoleLogger.LogFailureAndExit(Resources.FailedToRetrieveADObjectsError); } } } } catch (ServiceException) { - _consoleLogger.LogFailure(Resources.FailedToRetrieveADObjectsError); - return null; + _consoleLogger.LogFailureAndExit(Resources.FailedToRetrieveADObjectsError); } return graphObjectsList; @@ -88,6 +87,8 @@ public GraphObjectRetriever(GraphServiceClient graphServiceClient, IConsoleLogge tenant = (await _graphServiceClient.Organization .Request() .GetAsync()).FirstOrDefault(); + + return tenant; } catch (ServiceException ex) { @@ -108,11 +109,9 @@ public GraphObjectRetriever(GraphServiceClient graphServiceClient, IConsoleLogge } } - _consoleLogger.LogFailure(errorMessage); + _consoleLogger.LogFailureAndExit(errorMessage); return null; } - - return tenant; } } } diff --git a/src/MSIdentityScaffolding/Microsoft.DotNet.MSIdentity/Tool/MsAADTool.cs b/src/MSIdentityScaffolding/Microsoft.DotNet.MSIdentity/Tool/MsAADTool.cs index 921b22c105..9766c1ec7d 100644 --- a/src/MSIdentityScaffolding/Microsoft.DotNet.MSIdentity/Tool/MsAADTool.cs +++ b/src/MSIdentityScaffolding/Microsoft.DotNet.MSIdentity/Tool/MsAADTool.cs @@ -28,7 +28,7 @@ public MsAADTool(string commandName, ProvisioningToolOptions provisioningToolOpt { ProvisioningToolOptions = provisioningToolOptions; CommandName = commandName; - ConsoleLogger = new ConsoleLogger(ProvisioningToolOptions.Json); + ConsoleLogger = new ConsoleLogger(CommandName, ProvisioningToolOptions.Json); TokenCredential = new MsalTokenCredential(ProvisioningToolOptions.TenantId, ProvisioningToolOptions.Username, ConsoleLogger); GraphServiceClient = new GraphServiceClient(new TokenCredentialAuthenticationProvider(TokenCredential)); AzureManagementAPI = new AzureManagementAuthenticationProvider(TokenCredential); @@ -93,12 +93,11 @@ internal async Task> GetApplicationsAsync() var graphObjectsList = await GraphObjectRetriever.GetGraphObjects(); if (graphObjectsList is null) { - ConsoleLogger.LogFailure(Resources.FailedToRetrieveADObjectsError, CommandName); - Environment.Exit(1); + ConsoleLogger.LogFailureAndExit(Resources.FailedToRetrieveADObjectsError); } IList applicationList = new List(); - foreach (var graphObj in graphObjectsList) + foreach (var graphObj in graphObjectsList!) { if (graphObj is Application app) { @@ -108,7 +107,7 @@ internal async Task> GetApplicationsAsync() if (applicationList.Any()) { - Organization? tenant = await GraphObjectRetriever.GetTenant(); + var tenant = await GraphObjectRetriever.GetTenant(); if (tenant != null && tenant.TenantType.Equals("AAD B2C", StringComparison.OrdinalIgnoreCase)) { foreach (var app in applicationList) diff --git a/src/Shared/Microsoft.DotNet.Scaffolding.Shared/ConsoleLogger.cs b/src/Shared/Microsoft.DotNet.Scaffolding.Shared/ConsoleLogger.cs index bc97ddefd2..72469238fc 100644 --- a/src/Shared/Microsoft.DotNet.Scaffolding.Shared/ConsoleLogger.cs +++ b/src/Shared/Microsoft.DotNet.Scaffolding.Shared/ConsoleLogger.cs @@ -8,8 +8,11 @@ internal class ConsoleLogger : IConsoleLogger private readonly bool _jsonOutput; private readonly bool _silent; - public ConsoleLogger(bool jsonOutput = false, bool silent = false) + private string CommandName { get; } + + public ConsoleLogger(string commandName = null, bool jsonOutput = false, bool silent = false) { + CommandName = commandName ?? string.Empty; _jsonOutput = jsonOutput; _silent = silent; Console.OutputEncoding = Encoding.UTF8; @@ -46,23 +49,24 @@ public void LogMessage(string message, LogMessageType level, bool removeNewLine } } - public void LogJsonMessage(JsonResponse jsonMessage) + public void LogJsonMessage(string state = null, object content = null, string output = null) { if (!_silent) { if (_jsonOutput) { + var jsonMessage = new JsonResponse(CommandName, state, content, output); Console.WriteLine(jsonMessage.ToJsonString()); } else { - if (jsonMessage.State == State.Fail) + if (state == State.Fail) { - LogMessage(jsonMessage.Output, LogMessageType.Error); + LogMessage(output, LogMessageType.Error); } else { - LogMessage(jsonMessage.Output); + LogMessage(output); } } } @@ -76,19 +80,18 @@ public void LogMessage(string message, bool removeNewLine = false) } } - public void LogFailure(string failureMessage, string commandName = null) + public void LogFailureAndExit(string failureMessage) { - if (!_silent) + if (_jsonOutput) { - if (_jsonOutput) - { - LogJsonMessage(new JsonResponse(commandName, State.Fail, output: failureMessage)); - } - else - { - LogMessage(failureMessage, LogMessageType.Error); - } + LogJsonMessage(State.Fail, output: failureMessage); } + else + { + LogMessage(failureMessage, LogMessageType.Error); + } + + Environment.Exit(1); } } } diff --git a/src/Shared/Microsoft.DotNet.Scaffolding.Shared/IConsoleLogger.cs b/src/Shared/Microsoft.DotNet.Scaffolding.Shared/IConsoleLogger.cs index 44ac24fb2b..50b1f01782 100644 --- a/src/Shared/Microsoft.DotNet.Scaffolding.Shared/IConsoleLogger.cs +++ b/src/Shared/Microsoft.DotNet.Scaffolding.Shared/IConsoleLogger.cs @@ -4,8 +4,8 @@ public interface IConsoleLogger { void LogMessage(string message, LogMessageType level, bool removeNewLine = false); void LogMessage(string message, bool removeNewLine = false); - void LogJsonMessage(JsonResponse jsonMessage); - void LogFailure(string failureMessage, string commandName = null); + void LogJsonMessage(string state = null, object content = null, string output = null); + void LogFailureAndExit(string failureMessage); } public enum LogMessageType diff --git a/src/Shared/Microsoft.DotNet.Scaffolding.Shared/JsonResponse.cs b/src/Shared/Microsoft.DotNet.Scaffolding.Shared/JsonResponse.cs index 273fc7aed6..e50e7faf4c 100644 --- a/src/Shared/Microsoft.DotNet.Scaffolding.Shared/JsonResponse.cs +++ b/src/Shared/Microsoft.DotNet.Scaffolding.Shared/JsonResponse.cs @@ -1,4 +1,3 @@ -using System; using System.Text.Json; namespace Microsoft.DotNet.MSIdentity.Shared @@ -12,9 +11,9 @@ public class JsonResponse public JsonResponse(string command, string state = null, object content = null, string output = null) { + Command = command; State = state; Content = content; - Command = command ?? throw new ArgumentNullException(nameof(command)); Output = output; }