From a9cd9ac587eab575aee79590c02e5b71e899bd0b Mon Sep 17 00:00:00 2001 From: Zachary Halzel Date: Wed, 30 Nov 2022 12:48:25 -0800 Subject: [PATCH 1/6] output error for app registrations, and formatting nit (#2129) --- .../Tool/MsAADTool.cs | 92 ++++++++++--------- .../CodeModifier/DocumentBuilder.cs | 2 +- 2 files changed, 50 insertions(+), 44 deletions(-) diff --git a/src/MSIdentityScaffolding/Microsoft.DotNet.MSIdentity/Tool/MsAADTool.cs b/src/MSIdentityScaffolding/Microsoft.DotNet.MSIdentity/Tool/MsAADTool.cs index 8066ef003..c32cc29bc 100644 --- a/src/MSIdentityScaffolding/Microsoft.DotNet.MSIdentity/Tool/MsAADTool.cs +++ b/src/MSIdentityScaffolding/Microsoft.DotNet.MSIdentity/Tool/MsAADTool.cs @@ -20,7 +20,7 @@ internal class MsAADTool : IMsAADTool private ProvisioningToolOptions ProvisioningToolOptions { get; set; } private string CommandName { get; } public GraphServiceClient GraphServiceClient { get; set; } - public IAzureManagementAuthenticationProvider AzureManagementAPI { get; set;} + public IAzureManagementAuthenticationProvider AzureManagementAPI { get; set; } private MsalTokenCredential TokenCredential { get; set; } public MsAADTool(string commandName, ProvisioningToolOptions provisioningToolOptions) @@ -35,10 +35,10 @@ public MsAADTool(string commandName, ProvisioningToolOptions provisioningToolOpt public async Task Run() { - string outputJsonString = string.Empty; + string outputJsonString = string.Empty; if (TokenCredential != null && GraphServiceClient != null) { - switch(CommandName) + switch (CommandName) { //--list-aad-apps case Commands.LIST_AAD_APPS_COMMAND: @@ -63,14 +63,14 @@ public MsAADTool(string commandName, ProvisioningToolOptions provisioningToolOpt return null; } - private async Task> GetGraphObjects() + private async Task?> GetGraphObjects() { List graphObjectsList = new List(); try { var graphObjects = await GraphServiceClient.Me.OwnedObjects - .Request() - .GetAsync(); + .Request() + .GetAsync(); if (graphObjects != null) { @@ -96,6 +96,7 @@ private async Task> GetGraphObjects() { nextPage = null; ConsoleLogger.LogMessage(Resources.FailedToRetrieveADObjectsError, LogMessageType.Error); + return null; } } } @@ -103,6 +104,7 @@ private async Task> GetGraphObjects() catch (ServiceException) { ConsoleLogger.LogMessage(Resources.FailedToRetrieveADObjectsError, LogMessageType.Error); + return null; } return graphObjectsList; @@ -112,46 +114,48 @@ internal async Task PrintApplicationsList() { string outputJsonString = string.Empty; var graphObjectsList = await GetGraphObjects(); + if (graphObjectsList is null) + { + return new JsonResponse(CommandName, State.Fail, Resources.FailedToRetrieveADObjectsError).ToJsonString(); + } + IList applicationList = new List(); - if (graphObjectsList != null && graphObjectsList.Any()) + foreach (var graphObj in graphObjectsList) { - foreach (var graphObj in graphObjectsList) + if (graphObj is Application app) { - if (graphObj is Application app) - { - applicationList.Add(app); - } + applicationList.Add(app); } + } - if (applicationList.Any()) + if (applicationList.Any()) + { + Organization? tenant = await GetTenant(GraphServiceClient); + if (tenant != null && tenant.TenantType.Equals("AAD B2C", StringComparison.OrdinalIgnoreCase)) { - Organization? tenant = await GetTenant(GraphServiceClient); - if (tenant != null && tenant.TenantType.Equals("AAD B2C", StringComparison.OrdinalIgnoreCase)) + foreach (Application app in applicationList) { - foreach (Application app in applicationList) - { - app.AdditionalData.Add("IsB2C", true); - } + app.AdditionalData.Add("IsB2C", true); } + } - //order list by created date. - applicationList = applicationList.OrderByDescending(app => app.CreatedDateTime).ToList(); + //order list by created date. + applicationList = applicationList.OrderByDescending(app => app.CreatedDateTime).ToList(); - if (ProvisioningToolOptions.Json) - { - JsonResponse jsonResponse = new JsonResponse(CommandName, State.Success, applicationList); - outputJsonString = jsonResponse.ToJsonString(); - } - else + if (ProvisioningToolOptions.Json) + { + JsonResponse jsonResponse = new JsonResponse(CommandName, State.Success, applicationList); + outputJsonString = jsonResponse.ToJsonString(); + } + else + { + Console.Write( + "--------------------------------------------------------------\n" + + "Application Name\t\t\t\tApplication ID\n" + + "--------------------------------------------------------------\n\n"); + foreach (var app in applicationList) { - Console.Write( - "--------------------------------------------------------------\n" + - "Application Name\t\t\t\tApplication ID\n" + - "--------------------------------------------------------------\n\n"); - foreach (var app in applicationList) - { - Console.WriteLine($"{app.DisplayName.PadRight(35)}\t\t{app.AppId}"); - } + Console.WriteLine($"{app.DisplayName.PadRight(35)}\t\t{app.AppId}"); } } } @@ -161,7 +165,7 @@ internal async Task PrintApplicationsList() private static async Task GetTenant(GraphServiceClient graphServiceClient) { - Organization? tenant = null; + Organization? tenant; try { tenant = (await graphServiceClient.Organization @@ -170,23 +174,25 @@ internal async Task PrintApplicationsList() } catch (ServiceException ex) { + string? errorMessage; if (ex.InnerException != null) { - Console.WriteLine(ex.InnerException.Message); + errorMessage = 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."); + errorMessage = "User was not found.\nUse both --tenant-id --username .\nAnd re-run the tool."; } else { - Console.WriteLine(ex.Message); + errorMessage = ex.Message; } } - Environment.Exit(1); + Console.WriteLine(errorMessage); + return null; } return tenant; @@ -275,13 +281,13 @@ internal async Task PrintTenantsList() JsonResponse jsonResponse = new JsonResponse(CommandName, State.Success, tenantList); outputJsonString = jsonResponse.ToJsonString(); } - else + else { Console.Write( - "--------------------------------------------------------------------------------------------------------------------------------\n" + + "--------------------------------------------------------------------------------------------------------------------------------\n" + "Display Name\t\t\tDefault Domain\t\t\t\tTenant Type\tTenant Id\n" + "--------------------------------------------------------------------------------------------------------------------------------\n\n"); - foreach(var tenant in tenantList) + foreach (var tenant in tenantList) { Console.WriteLine($"{(tenant.DisplayName ?? string.Empty).PadRight(16)}\t\t{(tenant.DefaultDomain ?? string.Empty).PadRight(20)}\t\t{(tenant.TenantType ?? string.Empty).PadRight(10)}\t{(tenant.TenantId ?? string.Empty)}"); } diff --git a/src/Shared/Microsoft.DotNet.Scaffolding.Shared/CodeModifier/DocumentBuilder.cs b/src/Shared/Microsoft.DotNet.Scaffolding.Shared/CodeModifier/DocumentBuilder.cs index 0ae47d815..be6711211 100644 --- a/src/Shared/Microsoft.DotNet.Scaffolding.Shared/CodeModifier/DocumentBuilder.cs +++ b/src/Shared/Microsoft.DotNet.Scaffolding.Shared/CodeModifier/DocumentBuilder.cs @@ -673,7 +673,7 @@ internal static SyntaxNode AddLambdaToParent(SyntaxNode parent, IEnumerable Date: Wed, 7 Dec 2022 14:47:18 -0800 Subject: [PATCH 2/6] Fix output for app registrations (#2144) Adding issue: https://github.com/dotnet/Scaffolding/issues/2146 --- ...osoftIdentityPlatformApplicationManager.cs | 2 +- .../Tool/MsAADTool.cs | 29 +++++++++---------- 2 files changed, 15 insertions(+), 16 deletions(-) diff --git a/src/MSIdentityScaffolding/Microsoft.DotNet.MSIdentity/MicrosoftIdentityPlatform/MicrosoftIdentityPlatformApplicationManager.cs b/src/MSIdentityScaffolding/Microsoft.DotNet.MSIdentity/MicrosoftIdentityPlatform/MicrosoftIdentityPlatformApplicationManager.cs index 8c424b2e4..83cb04c0e 100644 --- a/src/MSIdentityScaffolding/Microsoft.DotNet.MSIdentity/MicrosoftIdentityPlatform/MicrosoftIdentityPlatformApplicationManager.cs +++ b/src/MSIdentityScaffolding/Microsoft.DotNet.MSIdentity/MicrosoftIdentityPlatform/MicrosoftIdentityPlatformApplicationManager.cs @@ -17,7 +17,7 @@ namespace Microsoft.DotNet.MSIdentity.MicrosoftIdentityPlatformApplication { public class MicrosoftIdentityPlatformApplicationManager { - private StringBuilder _output = new StringBuilder(); + private readonly StringBuilder _output = new StringBuilder(); const string MicrosoftGraphAppId = "00000003-0000-0000-c000-000000000000"; const string ScopeType = "Scope"; private const string DefaultCallbackPath = "signin-oidc"; diff --git a/src/MSIdentityScaffolding/Microsoft.DotNet.MSIdentity/Tool/MsAADTool.cs b/src/MSIdentityScaffolding/Microsoft.DotNet.MSIdentity/Tool/MsAADTool.cs index c32cc29bc..84c3735b8 100644 --- a/src/MSIdentityScaffolding/Microsoft.DotNet.MSIdentity/Tool/MsAADTool.cs +++ b/src/MSIdentityScaffolding/Microsoft.DotNet.MSIdentity/Tool/MsAADTool.cs @@ -116,7 +116,7 @@ internal async Task PrintApplicationsList() var graphObjectsList = await GetGraphObjects(); if (graphObjectsList is null) { - return new JsonResponse(CommandName, State.Fail, Resources.FailedToRetrieveADObjectsError).ToJsonString(); + return new JsonResponse(CommandName, State.Fail, output: Resources.FailedToRetrieveADObjectsError).ToJsonString(); } IList applicationList = new List(); @@ -141,22 +141,21 @@ internal async Task PrintApplicationsList() //order list by created date. applicationList = applicationList.OrderByDescending(app => app.CreatedDateTime).ToList(); + } - if (ProvisioningToolOptions.Json) - { - JsonResponse jsonResponse = new JsonResponse(CommandName, State.Success, applicationList); - outputJsonString = jsonResponse.ToJsonString(); - } - else + if (ProvisioningToolOptions.Json) + { + outputJsonString = new JsonResponse(CommandName, State.Success, applicationList).ToJsonString(); + } + else + { + Console.Write( + "--------------------------------------------------------------\n" + + "Application Name\t\t\t\tApplication ID\n" + + "--------------------------------------------------------------\n\n"); + foreach (var app in applicationList) { - Console.Write( - "--------------------------------------------------------------\n" + - "Application Name\t\t\t\tApplication ID\n" + - "--------------------------------------------------------------\n\n"); - foreach (var app in applicationList) - { - Console.WriteLine($"{app.DisplayName.PadRight(35)}\t\t{app.AppId}"); - } + Console.WriteLine($"{app.DisplayName.PadRight(35)}\t\t{app.AppId}"); } } From 499b966986ad859c60cb2560d28431272ea165a7 Mon Sep 17 00:00:00 2001 From: Zachary Halzel Date: Mon, 12 Dec 2022 13:09:08 -0800 Subject: [PATCH 3/6] Log token authentication failure to JsonResponse, Refactor getting app registrations (#2149) * Refactor getting app registrations * Fix test * Refactor ConsoleLogger failure logging --- .../CodeReaderWriter/ProjectModifier.cs | 2 +- .../DeveloperCredentialsReader.cs | 8 +- .../MsalTokenCredential.cs | 37 ++-- ...osoftIdentityPlatformApplicationManager.cs | 4 +- .../TokenCredentialAuthenticationProvider.cs | 3 +- .../Properties/Resources.Designer.cs | 18 ++ .../Properties/Resources.resx | 6 + .../Tool/AppProvisioningTool.cs | 32 ++-- .../Tool/GraphObjectRetriever.cs | 118 ++++++++++++ .../Tool/MsAADTool.cs | 172 +++++------------- .../ConsoleLogger.cs | 23 ++- .../IConsoleLogger.cs | 1 + .../MsAADToolTests.cs | 45 +++++ 13 files changed, 306 insertions(+), 163 deletions(-) create mode 100644 src/MSIdentityScaffolding/Microsoft.DotNet.MSIdentity/Tool/GraphObjectRetriever.cs diff --git a/src/MSIdentityScaffolding/Microsoft.DotNet.MSIdentity/CodeReaderWriter/ProjectModifier.cs b/src/MSIdentityScaffolding/Microsoft.DotNet.MSIdentity/CodeReaderWriter/ProjectModifier.cs index 254d7f47f..f7658b247 100644 --- a/src/MSIdentityScaffolding/Microsoft.DotNet.MSIdentity/CodeReaderWriter/ProjectModifier.cs +++ b/src/MSIdentityScaffolding/Microsoft.DotNet.MSIdentity/CodeReaderWriter/ProjectModifier.cs @@ -50,7 +50,7 @@ public async Task AddAuthCodeAsync() if (csProjFiles.Count() != 1) { var errorMsg = string.Format(Resources.ProjectPathError, _toolOptions.ProjectFilePath); - _consoleLogger.LogJsonMessage(new JsonResponse(Commands.UPDATE_PROJECT_COMMAND, State.Fail, output: errorMsg)); + _consoleLogger.LogFailure(errorMsg, Commands.UPDATE_PROJECT_COMMAND); return; } diff --git a/src/MSIdentityScaffolding/Microsoft.DotNet.MSIdentity/DeveloperCredentials/DeveloperCredentialsReader.cs b/src/MSIdentityScaffolding/Microsoft.DotNet.MSIdentity/DeveloperCredentials/DeveloperCredentialsReader.cs index e426ebd49..14693d742 100644 --- a/src/MSIdentityScaffolding/Microsoft.DotNet.MSIdentity/DeveloperCredentials/DeveloperCredentialsReader.cs +++ b/src/MSIdentityScaffolding/Microsoft.DotNet.MSIdentity/DeveloperCredentials/DeveloperCredentialsReader.cs @@ -1,13 +1,14 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. using Azure.Core; +using Microsoft.DotNet.MSIdentity.Shared; namespace Microsoft.DotNet.MSIdentity.DeveloperCredentials { public class DeveloperCredentialsReader { - public TokenCredential GetDeveloperCredentials(string? username, string? currentApplicationTenantId) + public TokenCredential GetDeveloperCredentials(string? username, string? currentApplicationTenantId, IConsoleLogger consoleLogger) { #if AzureSDK * Tried but does not work if another tenant than the home tenant id is specified @@ -28,7 +29,8 @@ public TokenCredential GetDeveloperCredentials(string? username, string? current #endif TokenCredential tokenCredential = new MsalTokenCredential( currentApplicationTenantId, - username); + username, + consoleLogger); return tokenCredential; } } diff --git a/src/MSIdentityScaffolding/Microsoft.DotNet.MSIdentity/DeveloperCredentials/MsalTokenCredential.cs b/src/MSIdentityScaffolding/Microsoft.DotNet.MSIdentity/DeveloperCredentials/MsalTokenCredential.cs index 5d36c0ee7..43e18e5a2 100644 --- a/src/MSIdentityScaffolding/Microsoft.DotNet.MSIdentity/DeveloperCredentials/MsalTokenCredential.cs +++ b/src/MSIdentityScaffolding/Microsoft.DotNet.MSIdentity/DeveloperCredentials/MsalTokenCredential.cs @@ -1,15 +1,15 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Azure.Core; -using Microsoft.Graph; -using Microsoft.Identity.Client; -using Microsoft.Identity.Client.Extensions.Msal; using System; using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; +using Azure.Core; +using Microsoft.DotNet.MSIdentity.Shared; +using Microsoft.Identity.Client; +using Microsoft.Identity.Client.Extensions.Msal; namespace Microsoft.DotNet.MSIdentity.DeveloperCredentials { @@ -19,11 +19,17 @@ public class MsalTokenCredential : TokenCredential private const string RedirectUri = "http://localhost"; #pragma warning restore S1075 // URIs should not be hardcoded - public MsalTokenCredential(string? tenantId, string? username, string instance = "https://login.microsoftonline.com") + private readonly IConsoleLogger _consoleLogger; + + public MsalTokenCredential( + string? tenantId, + string? username, + IConsoleLogger consoleLogger) { + _consoleLogger = consoleLogger; TenantId = tenantId ?? "organizations"; // MSA-passthrough Username = username; - Instance = instance; + Instance = "https://login.microsoftonline.com"; } private IPublicClientApplication? App { get; set; } @@ -99,7 +105,10 @@ public override async ValueTask GetTokenAsync(TokenRequestContext r { if (account == null && !string.IsNullOrEmpty(Username)) { - Console.WriteLine($"No valid tokens found in the cache.\nPlease sign-in to Visual Studio with this account:\n\n{Username}.\n\nAfter signing-in, re-run the tool.\n"); + _consoleLogger.LogFailure( + $"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."); } result = await app.AcquireTokenInteractive(requestContext.Scopes) .WithAccount(account) @@ -111,19 +120,19 @@ public override async ValueTask GetTokenAsync(TokenRequestContext r { if (ex.Message.Contains("AADSTS70002")) // "The client does not exist or is not enabled for consumers" { - Console.WriteLine("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. "); + _consoleLogger.LogFailure( + "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. } - Console.WriteLine("Error encountered with sign-in. See error message for details:\n{0} ", - ex.Message); + _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. } catch (Exception ex) { - Console.WriteLine("Error encountered with sign-in. See error message for details:\n{0} ", - ex.Message); + _consoleLogger.LogFailure($"Error encountered with sign-in. See error message for details:\n{ex.Message}"); Environment.Exit(1); } 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 83cb04c0e..9cefe62db 100644 --- a/src/MSIdentityScaffolding/Microsoft.DotNet.MSIdentity/MicrosoftIdentityPlatform/MicrosoftIdentityPlatformApplicationManager.cs +++ b/src/MSIdentityScaffolding/Microsoft.DotNet.MSIdentity/MicrosoftIdentityPlatform/MicrosoftIdentityPlatformApplicationManager.cs @@ -80,7 +80,7 @@ public class MicrosoftIdentityPlatformApplicationManager if (createdSp is null) { - consoleLogger.LogJsonMessage(new JsonResponse(commandName, State.Fail, output: Resources.FailedToGetServicePrincipal)); + consoleLogger.LogFailure(Resources.FailedToGetServicePrincipal, commandName); return null; } @@ -107,7 +107,7 @@ public class MicrosoftIdentityPlatformApplicationManager // log json console message inside this method since we need the Microsoft.Graph.Application if (createdApplication is null) { - consoleLogger.LogJsonMessage(new JsonResponse(commandName, State.Fail, output: Resources.FailedToCreateApp)); + consoleLogger.LogFailure(Resources.FailedToCreateApp, commandName); return null; } diff --git a/src/MSIdentityScaffolding/Microsoft.DotNet.MSIdentity/MicrosoftIdentityPlatform/TokenCredentialAuthenticationProvider.cs b/src/MSIdentityScaffolding/Microsoft.DotNet.MSIdentity/MicrosoftIdentityPlatform/TokenCredentialAuthenticationProvider.cs index 3243830e7..27a244f0e 100644 --- a/src/MSIdentityScaffolding/Microsoft.DotNet.MSIdentity/MicrosoftIdentityPlatform/TokenCredentialAuthenticationProvider.cs +++ b/src/MSIdentityScaffolding/Microsoft.DotNet.MSIdentity/MicrosoftIdentityPlatform/TokenCredentialAuthenticationProvider.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. using Azure.Core; @@ -31,7 +31,6 @@ public TokenCredentialAuthenticationProvider( public async Task AuthenticateRequestAsync(HttpRequestMessage request) { // Try with the Shared token cache credentials - TokenRequestContext context = new TokenRequestContext(_initialScopes.ToArray()); AccessToken token = await _tokenCredentials.GetTokenAsync(context, CancellationToken.None); diff --git a/src/MSIdentityScaffolding/Microsoft.DotNet.MSIdentity/Properties/Resources.Designer.cs b/src/MSIdentityScaffolding/Microsoft.DotNet.MSIdentity/Properties/Resources.Designer.cs index 30f7f7b58..d20a50561 100644 --- a/src/MSIdentityScaffolding/Microsoft.DotNet.MSIdentity/Properties/Resources.Designer.cs +++ b/src/MSIdentityScaffolding/Microsoft.DotNet.MSIdentity/Properties/Resources.Designer.cs @@ -574,6 +574,15 @@ internal static string FailedToModifyCodeFile { } } + /// + /// Looks up a localized string similar to Failed to provision Client Application. + /// + internal static string FailedToProvisionClientApp { + get { + return ResourceManager.GetString("FailedToProvisionClientApp", resourceCulture); + } + } + /// /// Looks up a localized string similar to Failed to retrieve all Azure AD/AD B2C objects(apps/service principals. /// @@ -601,6 +610,15 @@ internal static string FailedToUpdateAppNull { } } + /// + /// Looks up a localized string similar to Failed to update client app program.cs file. + /// + internal static string FailedToUpdateClientAppCode { + get { + return ResourceManager.GetString("FailedToUpdateClientAppCode", resourceCulture); + } + } + /// /// Looks up a localized string similar to Initializing User Secrets . . .. /// diff --git a/src/MSIdentityScaffolding/Microsoft.DotNet.MSIdentity/Properties/Resources.resx b/src/MSIdentityScaffolding/Microsoft.DotNet.MSIdentity/Properties/Resources.resx index 6b9559ae3..ff7d2facd 100644 --- a/src/MSIdentityScaffolding/Microsoft.DotNet.MSIdentity/Properties/Resources.resx +++ b/src/MSIdentityScaffolding/Microsoft.DotNet.MSIdentity/Properties/Resources.resx @@ -250,6 +250,9 @@ Failed to modify code file {0}, {1} 0 = File name, 1 = Exception message + + Failed to provision Client Application + Failed to retrieve all Azure AD/AD B2C objects(apps/service principals @@ -261,6 +264,9 @@ Failed to update Azure AD app, null {0} 0 = null object + + Failed to update client app program.cs file + Initializing User Secrets . . . diff --git a/src/MSIdentityScaffolding/Microsoft.DotNet.MSIdentity/Tool/AppProvisioningTool.cs b/src/MSIdentityScaffolding/Microsoft.DotNet.MSIdentity/Tool/AppProvisioningTool.cs index de3fbbe07..40f7e8033 100644 --- a/src/MSIdentityScaffolding/Microsoft.DotNet.MSIdentity/Tool/AppProvisioningTool.cs +++ b/src/MSIdentityScaffolding/Microsoft.DotNet.MSIdentity/Tool/AppProvisioningTool.cs @@ -59,7 +59,7 @@ public AppProvisioningTool(string commandName, ProvisioningToolOptions provision if (projectDescription == null) { var errorMessage = string.Format(Resources.NoProjectDescriptionFound, ProvisioningToolOptions.ProjectTypeIdentifier); - ConsoleLogger.LogJsonMessage(new JsonResponse(CommandName, State.Fail, output: errorMessage)); + ConsoleLogger.LogFailure(errorMessage, CommandName); Environment.Exit(1); } @@ -74,7 +74,8 @@ public AppProvisioningTool(string commandName, ProvisioningToolOptions provision // Get developer credentials TokenCredential tokenCredential = GetTokenCredential( ProvisioningToolOptions, - ProvisioningToolOptions.TenantId ?? projectSettings.ApplicationParameters.EffectiveTenantId ?? projectSettings.ApplicationParameters.EffectiveDomain); + ProvisioningToolOptions.TenantId ?? projectSettings.ApplicationParameters.EffectiveTenantId ?? projectSettings.ApplicationParameters.EffectiveDomain, + ConsoleLogger); ApplicationParameters applicationParameters; switch (CommandName) @@ -177,7 +178,7 @@ private bool ValidateProjectPath() } var errorMsg = string.Format(Resources.ProjectPathError, ProvisioningToolOptions.ProjectFilePath); - ConsoleLogger.LogJsonMessage(new JsonResponse(CommandName, State.Fail, output: errorMsg)); + ConsoleLogger.LogFailure(errorMsg, CommandName); return false; } @@ -230,12 +231,13 @@ private ProjectAuthenticationSettings InferApplicationParameters( /// /// /// - internal static TokenCredential GetTokenCredential(ProvisioningToolOptions provisioningToolOptions, string? currentApplicationTenantId) + internal static TokenCredential GetTokenCredential(ProvisioningToolOptions provisioningToolOptions, string? currentApplicationTenantId, IConsoleLogger consoleLogger) { DeveloperCredentialsReader developerCredentialsReader = new DeveloperCredentialsReader(); return developerCredentialsReader.GetDeveloperCredentials( provisioningToolOptions.Username, - currentApplicationTenantId ?? provisioningToolOptions.TenantId); + currentApplicationTenantId ?? provisioningToolOptions.TenantId, + consoleLogger); } /// @@ -296,6 +298,12 @@ 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); + } + ProvisioningToolOptions.BlazorWasmClientAppId = clientApplicationParameters.ClientId; output.AppendLine(string.Format(Resources.ConfiguredBlazorWasmClient, clientApplicationParameters.ApplicationDisplayName, clientApplicationParameters.ClientId)); } @@ -317,7 +325,7 @@ private async Task UpdateAppRegistration(TokenCredential tokenCredential, Applic /// /// /// - private async Task ConfigureBlazorWasmHostedClientAsync(ApplicationParameters serverApplicationParameters) + private async Task ConfigureBlazorWasmHostedClientAsync(ApplicationParameters serverApplicationParameters) { // Processes the Blazorwasm client var clientToolOptions = ProvisioningToolOptions.Clone(); @@ -337,10 +345,8 @@ private async Task ConfigureBlazorWasmHostedClientAsync(A var clientApplicationParameters = await provisionClientAppRegistration.Run(); if (clientApplicationParameters == null) { - var exception = new ArgumentNullException(nameof(clientApplicationParameters)); - - ConsoleLogger.LogJsonMessage(new JsonResponse(CommandName, State.Fail, output: exception.Message)); - throw exception; + ConsoleLogger.LogFailure(Resources.FailedToProvisionClientApp, CommandName); + return null; } // Update program.cs file @@ -349,10 +355,8 @@ private async Task ConfigureBlazorWasmHostedClientAsync(A clientApplicationParameters = await updateCode.Run(); if (clientApplicationParameters == null) { - var exception = new ArgumentNullException(nameof(clientApplicationParameters)); - - ConsoleLogger.LogJsonMessage(new JsonResponse(CommandName, State.Fail, output: exception.Message)); - throw exception; + ConsoleLogger.LogFailure(Resources.FailedToUpdateClientAppCode, CommandName); + return null; } return clientApplicationParameters; diff --git a/src/MSIdentityScaffolding/Microsoft.DotNet.MSIdentity/Tool/GraphObjectRetriever.cs b/src/MSIdentityScaffolding/Microsoft.DotNet.MSIdentity/Tool/GraphObjectRetriever.cs new file mode 100644 index 000000000..b30535394 --- /dev/null +++ b/src/MSIdentityScaffolding/Microsoft.DotNet.MSIdentity/Tool/GraphObjectRetriever.cs @@ -0,0 +1,118 @@ +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.DotNet.MSIdentity.Properties; +using Microsoft.DotNet.MSIdentity.Shared; +using Microsoft.Graph; + +namespace Microsoft.DotNet.MSIdentity.Tool +{ + public interface IGraphObjectRetriever + { + /// + /// Requests all directory objects from the graph service client for the given account + /// + /// + public Task?> GetGraphObjects(); + + /// + /// Retrieves the Tenant object associated with the GraphServiceClient + /// + /// + public Task GetTenant(); + } + + public class GraphObjectRetriever : IGraphObjectRetriever + { + private readonly GraphServiceClient _graphServiceClient; + private readonly IConsoleLogger _consoleLogger; + + public GraphObjectRetriever(GraphServiceClient graphServiceClient, IConsoleLogger consoleLogger) + { + _graphServiceClient = graphServiceClient; + _consoleLogger = consoleLogger; + } + + public async Task?> GetGraphObjects() + { + List graphObjectsList = new List(); + try + { + var graphObjects = await _graphServiceClient.Me.OwnedObjects + .Request() + .GetAsync(); + + if (graphObjects != null) + { + graphObjectsList.AddRange(graphObjects.ToList()); + + var nextPage = graphObjects.NextPageRequest; + while (nextPage != null) + { + try + { + var additionalGraphObjects = await nextPage.GetAsync(); + if (additionalGraphObjects != null) + { + graphObjectsList.AddRange(additionalGraphObjects.ToList()); + nextPage = additionalGraphObjects.NextPageRequest; + } + else + { + nextPage = null; + } + } + catch (ServiceException) + { + nextPage = null; + _consoleLogger.LogFailure(Resources.FailedToRetrieveADObjectsError); + return null; + } + } + } + } + catch (ServiceException) + { + _consoleLogger.LogFailure(Resources.FailedToRetrieveADObjectsError); + return null; + } + + return graphObjectsList; + } + + public async Task GetTenant() + { + Organization? tenant; + try + { + tenant = (await _graphServiceClient.Organization + .Request() + .GetAsync()).FirstOrDefault(); + } + catch (ServiceException ex) + { + string? errorMessage; + if (ex.InnerException != null) + { + errorMessage = ex.InnerException.Message; + } + else + { + if (ex.Message.Contains("User was not found") || ex.Message.Contains("not found in tenant")) + { + errorMessage = "User was not found.\nUse both --tenant-id --username .\nAnd re-run the tool."; + } + else + { + errorMessage = ex.Message; + } + } + + _consoleLogger.LogFailure(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 84c3735b8..921b22c10 100644 --- a/src/MSIdentityScaffolding/Microsoft.DotNet.MSIdentity/Tool/MsAADTool.cs +++ b/src/MSIdentityScaffolding/Microsoft.DotNet.MSIdentity/Tool/MsAADTool.cs @@ -21,16 +21,18 @@ internal class MsAADTool : IMsAADTool private string CommandName { get; } public GraphServiceClient GraphServiceClient { get; set; } public IAzureManagementAuthenticationProvider AzureManagementAPI { get; set; } + public IGraphObjectRetriever GraphObjectRetriever { get; set; } private MsalTokenCredential TokenCredential { get; set; } public MsAADTool(string commandName, ProvisioningToolOptions provisioningToolOptions) { ProvisioningToolOptions = provisioningToolOptions; CommandName = commandName; - TokenCredential = new MsalTokenCredential(ProvisioningToolOptions.TenantId, ProvisioningToolOptions.Username); + ConsoleLogger = new ConsoleLogger(ProvisioningToolOptions.Json); + TokenCredential = new MsalTokenCredential(ProvisioningToolOptions.TenantId, ProvisioningToolOptions.Username, ConsoleLogger); GraphServiceClient = new GraphServiceClient(new TokenCredentialAuthenticationProvider(TokenCredential)); AzureManagementAPI = new AzureManagementAuthenticationProvider(TokenCredential); - ConsoleLogger = new ConsoleLogger(ProvisioningToolOptions.Json); + GraphObjectRetriever = new GraphObjectRetriever(GraphServiceClient, ConsoleLogger); } public async Task Run() @@ -63,60 +65,36 @@ public MsAADTool(string commandName, ProvisioningToolOptions provisioningToolOpt return null; } - private async Task?> GetGraphObjects() + internal async Task PrintApplicationsList() { - List graphObjectsList = new List(); - try + string outputJsonString = string.Empty; + var applications = await GetApplicationsAsync(); + if (ProvisioningToolOptions.Json) { - var graphObjects = await GraphServiceClient.Me.OwnedObjects - .Request() - .GetAsync(); - - if (graphObjects != null) - { - graphObjectsList.AddRange(graphObjects.ToList()); - - var nextPage = graphObjects.NextPageRequest; - while (nextPage != null) - { - try - { - var additionalGraphObjects = await nextPage.GetAsync(); - if (additionalGraphObjects != null) - { - graphObjectsList.AddRange(additionalGraphObjects.ToList()); - nextPage = additionalGraphObjects.NextPageRequest; - } - else - { - nextPage = null; - } - } - catch (ServiceException) - { - nextPage = null; - ConsoleLogger.LogMessage(Resources.FailedToRetrieveADObjectsError, LogMessageType.Error); - return null; - } - } - } + outputJsonString = new JsonResponse(CommandName, State.Success, applications).ToJsonString(); } - catch (ServiceException) + else { - ConsoleLogger.LogMessage(Resources.FailedToRetrieveADObjectsError, LogMessageType.Error); - return null; + Console.Write( + "--------------------------------------------------------------\n" + + "Application Name\t\t\t\tApplication ID\n" + + "--------------------------------------------------------------\n\n"); + foreach (var app in applications) + { + Console.WriteLine($"{app.DisplayName,-35}\t\t{app.AppId}"); + } } - return graphObjectsList; + return outputJsonString; } - internal async Task PrintApplicationsList() + internal async Task> GetApplicationsAsync() { - string outputJsonString = string.Empty; - var graphObjectsList = await GetGraphObjects(); + var graphObjectsList = await GraphObjectRetriever.GetGraphObjects(); if (graphObjectsList is null) { - return new JsonResponse(CommandName, State.Fail, output: Resources.FailedToRetrieveADObjectsError).ToJsonString(); + ConsoleLogger.LogFailure(Resources.FailedToRetrieveADObjectsError, CommandName); + Environment.Exit(1); } IList applicationList = new List(); @@ -130,10 +108,10 @@ internal async Task PrintApplicationsList() if (applicationList.Any()) { - Organization? tenant = await GetTenant(GraphServiceClient); + Organization? tenant = await GraphObjectRetriever.GetTenant(); if (tenant != null && tenant.TenantType.Equals("AAD B2C", StringComparison.OrdinalIgnoreCase)) { - foreach (Application app in applicationList) + foreach (var app in applicationList) { app.AdditionalData.Add("IsB2C", true); } @@ -143,65 +121,13 @@ internal async Task PrintApplicationsList() applicationList = applicationList.OrderByDescending(app => app.CreatedDateTime).ToList(); } - if (ProvisioningToolOptions.Json) - { - outputJsonString = new JsonResponse(CommandName, State.Success, applicationList).ToJsonString(); - } - else - { - Console.Write( - "--------------------------------------------------------------\n" + - "Application Name\t\t\t\tApplication ID\n" + - "--------------------------------------------------------------\n\n"); - foreach (var app in applicationList) - { - Console.WriteLine($"{app.DisplayName.PadRight(35)}\t\t{app.AppId}"); - } - } - - return outputJsonString; + return applicationList; } - private static async Task GetTenant(GraphServiceClient graphServiceClient) - { - Organization? tenant; - try - { - tenant = (await graphServiceClient.Organization - .Request() - .GetAsync()).FirstOrDefault(); - } - catch (ServiceException ex) - { - string? errorMessage; - if (ex.InnerException != null) - { - errorMessage = ex.InnerException.Message; - } - else - { - if (ex.Message.Contains("User was not found") || ex.Message.Contains("not found in tenant")) - { - errorMessage = "User was not found.\nUse both --tenant-id --username .\nAnd re-run the tool."; - } - else - { - errorMessage = ex.Message; - } - } - - Console.WriteLine(errorMessage); - return null; - } - - return tenant; - } - - internal async Task PrintServicePrincipalList() { string outputJsonString = string.Empty; - var graphObjectsList = await GetGraphObjects(); + var graphObjectsList = await GraphObjectRetriever.GetGraphObjects(); IList servicePrincipalList = new List(); if (graphObjectsList != null && graphObjectsList.Any()) @@ -228,7 +154,7 @@ internal async Task PrintServicePrincipalList() "--------------------------------------------------------------\n\n"); foreach (var sp in servicePrincipalList) { - Console.WriteLine($"{sp.DisplayName.PadRight(35)}\t\t{sp.AppId}"); + Console.WriteLine($"{sp.DisplayName,-35}\t\t{sp.AppId}"); } } } @@ -245,30 +171,28 @@ internal async Task PrintTenantsList() var tenantsJsonString = await AzureManagementAPI.ListTenantsAsync(); if (!string.IsNullOrEmpty(tenantsJsonString)) { - using (JsonDocument document = JsonDocument.Parse(tenantsJsonString)) + using JsonDocument document = JsonDocument.Parse(tenantsJsonString); + if (document.RootElement.TryGetProperty("value", out JsonElement jsonTenantElement)) { - if (document.RootElement.TryGetProperty("value", out JsonElement jsonTenantElement)) + var jsonTenantEnumerator = jsonTenantElement.EnumerateArray(); + if (jsonTenantEnumerator.Any()) { - var jsonTenantEnumerator = jsonTenantElement.EnumerateArray(); - if (jsonTenantEnumerator.Any()) + while (jsonTenantEnumerator.MoveNext()) { - while (jsonTenantEnumerator.MoveNext()) - { - JsonElement current = jsonTenantEnumerator.Current; - string? tenantId = current.GetProperty("tenantId").GetString(); - string? tenantType = current.GetProperty("tenantType").GetString(); - string? defaultDomain = current.GetProperty("defaultDomain").GetString(); - string? displayName = current.GetProperty("displayName").GetString(); - - tenantList.Add( - new TenantInformation() - { - TenantId = tenantId, - TenantType = tenantType, - DefaultDomain = defaultDomain, - DisplayName = displayName - }); - } + JsonElement current = jsonTenantEnumerator.Current; + string? tenantId = current.GetProperty("tenantId").GetString(); + string? tenantType = current.GetProperty("tenantType").GetString(); + string? defaultDomain = current.GetProperty("defaultDomain").GetString(); + string? displayName = current.GetProperty("displayName").GetString(); + + tenantList.Add( + new TenantInformation() + { + TenantId = tenantId, + TenantType = tenantType, + DefaultDomain = defaultDomain, + DisplayName = displayName + }); } } } @@ -288,7 +212,7 @@ internal async Task PrintTenantsList() "--------------------------------------------------------------------------------------------------------------------------------\n\n"); foreach (var tenant in tenantList) { - Console.WriteLine($"{(tenant.DisplayName ?? string.Empty).PadRight(16)}\t\t{(tenant.DefaultDomain ?? string.Empty).PadRight(20)}\t\t{(tenant.TenantType ?? string.Empty).PadRight(10)}\t{(tenant.TenantId ?? string.Empty)}"); + Console.WriteLine($"{tenant.DisplayName ?? string.Empty,-16}\t\t{tenant.DefaultDomain ?? string.Empty,-20}\t\t{tenant.TenantType ?? string.Empty,-10}\t{tenant.TenantId ?? string.Empty}"); } } return outputJsonString; diff --git a/src/Shared/Microsoft.DotNet.Scaffolding.Shared/ConsoleLogger.cs b/src/Shared/Microsoft.DotNet.Scaffolding.Shared/ConsoleLogger.cs index 8ab64b15f..bc97ddefd 100644 --- a/src/Shared/Microsoft.DotNet.Scaffolding.Shared/ConsoleLogger.cs +++ b/src/Shared/Microsoft.DotNet.Scaffolding.Shared/ConsoleLogger.cs @@ -6,7 +6,7 @@ namespace Microsoft.DotNet.MSIdentity.Shared internal class ConsoleLogger : IConsoleLogger { private readonly bool _jsonOutput; - private bool _silent; + private readonly bool _silent; public ConsoleLogger(bool jsonOutput = false, bool silent = false) { @@ -60,8 +60,10 @@ public void LogJsonMessage(JsonResponse jsonMessage) { LogMessage(jsonMessage.Output, LogMessageType.Error); } - - LogMessage(jsonMessage.Output); + else + { + LogMessage(jsonMessage.Output); + } } } } @@ -73,5 +75,20 @@ public void LogMessage(string message, bool removeNewLine = false) LogMessage(message, LogMessageType.Information, removeNewLine); } } + + public void LogFailure(string failureMessage, string commandName = null) + { + if (!_silent) + { + if (_jsonOutput) + { + LogJsonMessage(new JsonResponse(commandName, State.Fail, output: failureMessage)); + } + else + { + LogMessage(failureMessage, LogMessageType.Error); + } + } + } } } diff --git a/src/Shared/Microsoft.DotNet.Scaffolding.Shared/IConsoleLogger.cs b/src/Shared/Microsoft.DotNet.Scaffolding.Shared/IConsoleLogger.cs index 90cd50cc8..44ac24fb2 100644 --- a/src/Shared/Microsoft.DotNet.Scaffolding.Shared/IConsoleLogger.cs +++ b/src/Shared/Microsoft.DotNet.Scaffolding.Shared/IConsoleLogger.cs @@ -5,6 +5,7 @@ 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); } public enum LogMessageType diff --git a/test/MSIdentityScaffolding/Microsoft.DotNet.MSIdentity.UnitTests.Tests/MsAADToolTests.cs b/test/MSIdentityScaffolding/Microsoft.DotNet.MSIdentity.UnitTests.Tests/MsAADToolTests.cs index 15c950950..c83405619 100644 --- a/test/MSIdentityScaffolding/Microsoft.DotNet.MSIdentity.UnitTests.Tests/MsAADToolTests.cs +++ b/test/MSIdentityScaffolding/Microsoft.DotNet.MSIdentity.UnitTests.Tests/MsAADToolTests.cs @@ -1,3 +1,4 @@ +using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Text.Json; @@ -6,6 +7,7 @@ using Microsoft.DotNet.MSIdentity.MicrosoftIdentityPlatformApplication; using Microsoft.DotNet.MSIdentity.Shared; using Microsoft.DotNet.MSIdentity.Tool; +using Microsoft.Graph; using Moq; using Xunit; @@ -58,5 +60,48 @@ public async void TestPrintTenantsList() Assert.True(aadB2CApp.TenantType.Equals("AAD B2C")); Assert.True(!string.IsNullOrEmpty(aadB2CApp.TenantId)); } + + [Fact] + public async void TestPrintApplicationsList_NoGraphObjects() + { + var directoryObjects = new List(); + + Mock graphObjectRetriever = new Mock(); + graphObjectRetriever.Setup(g => g.GetGraphObjects()).Returns(Task.FromResult(directoryObjects)); + + MsAADTool jsonAppTool = new MsAADTool(Commands.LIST_AAD_APPS_COMMAND, ToolOptions) + { + GraphObjectRetriever = graphObjectRetriever.Object + }; + + var expected = "{\"Command\":\"--list-aad-apps\",\"State\":\"Success\",\"Content\":[],\"Output\":null}"; + + var appsResponse = await jsonAppTool.PrintApplicationsList(); + Assert.Equal(expected, appsResponse); + } + + [Fact] + public async void TestGetApplications_OneAppB2C() + { + var app = new Application { AdditionalData = new Dictionary() }; + var directoryObjects = new List { app }; + var tenant = new Organization { TenantType = "AAD B2C" }; + Mock graphObjectRetriever = new Mock(); + graphObjectRetriever.Setup(g => g.GetGraphObjects()).Returns(Task.FromResult(directoryObjects)); + graphObjectRetriever.Setup(g => g.GetTenant()).Returns(Task.FromResult(tenant)); + + MsAADTool jsonAppTool = new MsAADTool(Commands.LIST_AAD_APPS_COMMAND, ToolOptions) + { + GraphObjectRetriever = graphObjectRetriever.Object + }; + + var apps = await jsonAppTool.GetApplicationsAsync(); + Assert.Equal(1, apps.Count); + var additionalData = apps.First()?.AdditionalData; + Assert.NotNull(additionalData); + Assert.True(additionalData.ContainsKey("IsB2C")); + additionalData.TryGetValue("IsB2C", out var isB2C); + Assert.True((bool)isB2C); + } } } From c47acbd705e44cdaa1123c5ea8b60067adc77688 Mon Sep 17 00:00:00 2001 From: Zachary Halzel Date: Thu, 29 Dec 2022 11:02:43 -0500 Subject: [PATCH 4/6] Fix newline (#2181) --- .../CodeReaderWriter/ProjectModifier.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/MSIdentityScaffolding/Microsoft.DotNet.MSIdentity/CodeReaderWriter/ProjectModifier.cs b/src/MSIdentityScaffolding/Microsoft.DotNet.MSIdentity/CodeReaderWriter/ProjectModifier.cs index f7658b247..a9ce71c27 100644 --- a/src/MSIdentityScaffolding/Microsoft.DotNet.MSIdentity/CodeReaderWriter/ProjectModifier.cs +++ b/src/MSIdentityScaffolding/Microsoft.DotNet.MSIdentity/CodeReaderWriter/ProjectModifier.cs @@ -306,7 +306,7 @@ private async Task HandleCodeFileAsync(CodeFile file, CodeAnalysis.Project proje break; } - _output.Append(string.Format(Resources.ModifiedCodeFile, file.FileName)); + _output.AppendLine(string.Format(Resources.ModifiedCodeFile, file.FileName)); } } catch (Exception e) From a6e89f09bda80165ba34e4a81f22da147403bf82 Mon Sep 17 00:00:00 2001 From: deepchoudhery Date: Wed, 4 Jan 2023 13:13:01 -0800 Subject: [PATCH 5/6] bumping dotnet-msidentity version --- eng/Versions.MSIdentity.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eng/Versions.MSIdentity.props b/eng/Versions.MSIdentity.props index a6556fff4..1739cf428 100644 --- a/eng/Versions.MSIdentity.props +++ b/eng/Versions.MSIdentity.props @@ -6,7 +6,7 @@ true - 2.0.0 + 2.0.1 rtm true