diff --git a/src/MSIdentityScaffolding/Microsoft.DotNet.MSIdentity/AuthenticationParameters/AzureAdProperties.cs b/src/MSIdentityScaffolding/Microsoft.DotNet.MSIdentity/AuthenticationParameters/AzureAdProperties.cs index 83a5ce91f..87d47d19f 100644 --- a/src/MSIdentityScaffolding/Microsoft.DotNet.MSIdentity/AuthenticationParameters/AzureAdProperties.cs +++ b/src/MSIdentityScaffolding/Microsoft.DotNet.MSIdentity/AuthenticationParameters/AzureAdProperties.cs @@ -3,7 +3,7 @@ namespace Microsoft.DotNet.MSIdentity.AuthenticationParameters { - public class PropertyNames + public static class PropertyNames { public const string Domain = nameof(Domain); public const string TenantId = nameof(TenantId); @@ -12,12 +12,14 @@ public class PropertyNames public const string ClientCertificates = nameof(ClientCertificates); public const string CallbackPath = nameof(CallbackPath); public const string Instance = nameof(Instance); - public const string Authority = nameof(Authority); public const string ValidateAuthority = nameof(ValidateAuthority); - public const string BaseUrl = nameof(BaseUrl); public const string Scopes = nameof(Scopes); + public const string SignUpSignInPolicyId = nameof(SignUpSignInPolicyId); + public const string ResetPasswordPolicyId = nameof(ResetPasswordPolicyId); + public const string EditProfilePolicyId = nameof(EditProfilePolicyId); + public const string SignedOutCallbackPath = nameof(SignedOutCallbackPath); } // getting default properties from @@ -30,10 +32,14 @@ public static class DefaultProperties public const string Instance = "https://login.microsoftonline.com/"; public const string CallbackPath = "/signin-oidc"; public const string ClientSecret = "Client secret from app-registration. Check user secrets/azure portal."; - - public const string Authority = "https://login.microsoftonline.com/22222222-2222-2222-2222-222222222222"; public const bool ValidateAuthority = true; + // B2C properties + public const string SignUpSignInPolicyId = "b2c_1_susi"; + public const string ResetPasswordPolicyId = "b2c_1_reset"; + public const string EditProfilePolicyId = "b2c_1_edit_profile"; + public const string SignedOutCallbackPath = "/signout/B2C_1_susi"; + public const string MicrosoftGraphBaseUrl = "https://graph.microsoft.com/v1.0"; public const string MicrosoftGraphScopes = "user.read"; public const string ApiScopes = "access_as_user"; @@ -43,59 +49,49 @@ public class AzureAdBlock { public bool IsBlazorWasm; public bool IsWebApi; + public bool IsB2C; public string? ClientId; - public string? Instance = DefaultProperties.Instance; + public string? Instance; public string? Domain; public string? TenantId; public string? Authority; - public string? CallbackPath = DefaultProperties.CallbackPath; + public string? CallbackPath; + public string? SignUpSignInPolicyId; + public string? ResetPasswordPolicyId = DefaultProperties.ResetPasswordPolicyId; + public string? EditProfilePolicyId = DefaultProperties.EditProfilePolicyId; + public string? SignedOutCallbackPath = DefaultProperties.SignedOutCallbackPath; public string? Scopes; - public string? ClientSecret = DefaultProperties.ClientSecret; + public string? ClientSecret; public string[]? ClientCertificates; - public AzureAdBlock(ApplicationParameters applicationParameters) + public AzureAdBlock(ApplicationParameters applicationParameters, JObject? existingBlock = null) { IsBlazorWasm = applicationParameters.IsBlazorWasm; IsWebApi = applicationParameters.IsWebApi.GetValueOrDefault(); - - Domain = !string.IsNullOrEmpty(applicationParameters.Domain) ? applicationParameters.Domain : null; - TenantId = !string.IsNullOrEmpty(applicationParameters.TenantId) ? applicationParameters.TenantId : null; - ClientId = !string.IsNullOrEmpty(applicationParameters.ClientId) ? applicationParameters.ClientId : null; - Instance = !string.IsNullOrEmpty(applicationParameters.Instance) ? applicationParameters.Instance : null; - Authority = !string.IsNullOrEmpty(applicationParameters.Authority) ? applicationParameters.Authority : null; - CallbackPath = !string.IsNullOrEmpty(applicationParameters.CallbackPath) ? applicationParameters.CallbackPath : null; - Scopes = !string.IsNullOrEmpty(applicationParameters.CalledApiScopes) ? applicationParameters.CalledApiScopes : null; - } - - /// - /// Updates AzureAdBlock object from existing appSettings.json - /// - /// - public AzureAdBlock UpdateFromJToken(JToken azureAdToken) - { - JObject azureAdObj = JObject.FromObject(azureAdToken); - - ClientId ??= azureAdObj.GetValue(PropertyNames.ClientId)?.ToString(); // here, if the applicationparameters value is null, we use the existing app settings value - Instance ??= azureAdObj.GetValue(PropertyNames.Instance)?.ToString(); - Domain ??= azureAdObj.GetValue(PropertyNames.Domain)?.ToString(); - TenantId ??= azureAdObj.GetValue(PropertyNames.TenantId)?.ToString(); - Authority ??= azureAdObj.GetValue(PropertyNames.Authority)?.ToString(); - CallbackPath ??= azureAdObj.GetValue(PropertyNames.CallbackPath)?.ToString(); - Scopes ??= azureAdObj.GetValue(PropertyNames.Scopes)?.ToString(); - ClientSecret ??= azureAdObj.GetValue(PropertyNames.ClientSecret)?.ToString(); - ClientCertificates ??= azureAdObj.GetValue(PropertyNames.ClientCertificates)?.ToObject(); - - return this; + IsB2C = applicationParameters.IsB2C; + + Domain = !string.IsNullOrEmpty(applicationParameters.Domain) ? applicationParameters.Domain : existingBlock?.GetValue(PropertyNames.Domain)?.ToString() ?? DefaultProperties.Domain; + TenantId = !string.IsNullOrEmpty(applicationParameters.TenantId) ? applicationParameters.TenantId : existingBlock?.GetValue(PropertyNames.TenantId)?.ToString() ?? DefaultProperties.TenantId; + ClientId = !string.IsNullOrEmpty(applicationParameters.ClientId) ? applicationParameters.ClientId : existingBlock?.GetValue(PropertyNames.ClientId)?.ToString() ?? DefaultProperties.ClientId; + Instance = !string.IsNullOrEmpty(applicationParameters.Instance) ? applicationParameters.Instance : existingBlock?.GetValue(PropertyNames.Instance)?.ToString() ?? DefaultProperties.Instance; + CallbackPath = !string.IsNullOrEmpty(applicationParameters.CallbackPath) ? applicationParameters.CallbackPath : existingBlock?.GetValue(PropertyNames.CallbackPath)?.ToString() ?? DefaultProperties.CallbackPath; + Scopes = !string.IsNullOrEmpty(applicationParameters.CalledApiScopes) ? applicationParameters.CalledApiScopes : existingBlock?.GetValue(PropertyNames.Scopes)?.ToString() + ?? (applicationParameters.CallsDownstreamApi ? DefaultProperties.ApiScopes : applicationParameters.CallsMicrosoftGraph ? DefaultProperties.MicrosoftGraphScopes : null); + SignUpSignInPolicyId = !string.IsNullOrEmpty(applicationParameters.SusiPolicy) ? applicationParameters.SusiPolicy : existingBlock?.GetValue(PropertyNames.SignUpSignInPolicyId)?.ToString() ?? DefaultProperties.SignUpSignInPolicyId; + // TODO determine the SusiPolicy from the graph beta + Authority = IsB2C ? $"{Instance}{TenantId}/{SignUpSignInPolicyId}" : $"{Instance}{TenantId}"; + ClientSecret = existingBlock?.GetValue(PropertyNames.ClientSecret)?.ToString() ?? DefaultProperties.ClientSecret; + ClientCertificates = existingBlock?.GetValue(PropertyNames.ClientCertificates)?.ToObject(); } public dynamic BlazorSettings => new { - ClientId = ClientId ?? DefaultProperties.ClientId, // here, if a value is null, we could use the default properties - Authority = Authority ?? (string.IsNullOrEmpty(Instance) || string.IsNullOrEmpty(TenantId) ? DefaultProperties.Authority : $"{Instance}{TenantId}"), - ValidateAuthority = true + ClientId, + Authority, + ValidateAuthority = !IsB2C }; public dynamic WebAppSettings => new @@ -119,18 +115,30 @@ public AzureAdBlock UpdateFromJToken(JToken azureAdToken) ClientCertificates = ClientCertificates ?? Array.Empty() }; + public dynamic B2CSettings => new + { + SignUpSignInPolicyId = SignUpSignInPolicyId ?? DefaultProperties.SignUpSignInPolicyId, + SignedOutCallbackPath = SignedOutCallbackPath ?? DefaultProperties.SignedOutCallbackPath, + ResetPasswordPolicyId = ResetPasswordPolicyId ?? DefaultProperties.ResetPasswordPolicyId, + EditProfilePolicyId = EditProfilePolicyId ?? DefaultProperties.EditProfilePolicyId, + EnablePiiLogging = true + }; + public JObject ToJObject() { if (IsBlazorWasm) { return JObject.FromObject(BlazorSettings); } - if (IsWebApi) + + var jObject = IsWebApi ? JObject.FromObject(WebApiSettings) : JObject.FromObject(WebAppSettings); + + if (IsB2C) { - return JObject.FromObject(WebApiSettings); + jObject.Merge(JObject.FromObject(B2CSettings)); } - return JObject.FromObject(WebAppSettings); + return jObject; } } diff --git a/src/MSIdentityScaffolding/Microsoft.DotNet.MSIdentity/MicrosoftIdentityPlatform/AppSettingsModifier.cs b/src/MSIdentityScaffolding/Microsoft.DotNet.MSIdentity/MicrosoftIdentityPlatform/AppSettingsModifier.cs index eb2222938..28214ab0c 100644 --- a/src/MSIdentityScaffolding/Microsoft.DotNet.MSIdentity/MicrosoftIdentityPlatform/AppSettingsModifier.cs +++ b/src/MSIdentityScaffolding/Microsoft.DotNet.MSIdentity/MicrosoftIdentityPlatform/AppSettingsModifier.cs @@ -132,34 +132,29 @@ public void ModifyAppSettings(ApplicationParameters applicationParameters, IEnum /// /// /// (bool changesMade, JObject? updatedBlock) - internal (bool changesMade, JObject? updatedBlock) GetModifiedAzureAdBlock(JObject appSettings, ApplicationParameters applicationParameters) + internal static (bool changesMade, JObject? updatedBlock) GetModifiedAzureAdBlock(JObject appSettings, ApplicationParameters applicationParameters) { - var azureAdBlock = new AzureAdBlock(applicationParameters); - if (!appSettings.TryGetValue("AzureAd", out var azureAdToken)) + var azAdToken = appSettings.GetValue("AzureAd") ?? appSettings.GetValue("AzureAdB2C"); // TODO test "AzureAdB2C" string, make sure that blazor WASM works + if (azAdToken is null) { - // Create and return AzureAd block if none exists, differs for Blazor apps - return (true, azureAdBlock.ToJObject()); + return (true, new AzureAdBlock(applicationParameters).ToJObject()); } - var existingBlock = JObject.FromObject(azureAdToken); - var updatedBlock = azureAdBlock.UpdateFromJToken(azureAdToken).ToJObject(); - if (NeedsUpdate(existingBlock, updatedBlock)) - { - return (true, updatedBlock); - } + var existingParameters = JObject.FromObject(azAdToken); + var newBlock = new AzureAdBlock(applicationParameters, existingParameters).ToJObject(); - return (false, null); // If no changes were made, return null + return (NeedsUpdate(existingParameters, newBlock), newBlock); } /// /// Checks all keys in updatedBlock, if any differ from existingBlock then update is necessary /// /// - /// + /// /// - internal bool NeedsUpdate(JObject existingBlock, JObject updatedBlock) + internal static bool NeedsUpdate(JObject existingBlock, JObject newBlock) { - foreach ((var key, var updatedValue) in updatedBlock) + foreach ((var key, var updatedValue) in newBlock) { if (existingBlock.GetValue(key) != updatedValue) { @@ -170,7 +165,7 @@ internal bool NeedsUpdate(JObject existingBlock, JObject updatedBlock) return false; } - private JObject? GetApiBlock(JObject appSettings, string key, string? scopes, string? baseUrl) + internal static JObject? GetApiBlock(JObject appSettings, string key, string? scopes, string? baseUrl) { var inputParameters = JObject.FromObject(new ApiSettingsBlock { @@ -189,7 +184,7 @@ internal bool NeedsUpdate(JObject existingBlock, JObject updatedBlock) return inputParameters; } - private bool ModifyAppSettingsObject(JObject existingSettings, JObject inputProperties) + internal static bool ModifyAppSettingsObject(JObject existingSettings, JObject inputProperties) { bool changesMade = false; foreach ((var propertyName, var newValue) in inputProperties) diff --git a/src/MSIdentityScaffolding/Microsoft.DotNet.MSIdentity/MicrosoftIdentityPlatform/MicrosoftIdentityPlatformApplicationManager.cs b/src/MSIdentityScaffolding/Microsoft.DotNet.MSIdentity/MicrosoftIdentityPlatform/MicrosoftIdentityPlatformApplicationManager.cs index 9c1df5cb1..d160b959b 100644 --- a/src/MSIdentityScaffolding/Microsoft.DotNet.MSIdentity/MicrosoftIdentityPlatform/MicrosoftIdentityPlatformApplicationManager.cs +++ b/src/MSIdentityScaffolding/Microsoft.DotNet.MSIdentity/MicrosoftIdentityPlatform/MicrosoftIdentityPlatformApplicationManager.cs @@ -78,21 +78,18 @@ public class MicrosoftIdentityPlatformApplicationManager .Request() .AddAsync(servicePrincipal).ConfigureAwait(false); - if (applicationParameters.IsB2C) // TODO B2C not fully supported at the moment + // B2C does not allow user consent, and therefore we need to explicity grant permissions + if (applicationParameters.IsB2C) { - // B2C does not allow user consent, and therefore we need to explicity grant permissions - if (applicationParameters.IsB2C) - { - IEnumerable>? scopesPerResource = await AddApiPermissions( - applicationParameters, - graphServiceClient, - application).ConfigureAwait(false); - - await AddAdminConsentToApiPermissions( - graphServiceClient, - createdServicePrincipal, - scopesPerResource); - } + IEnumerable>? scopesPerResource = await AddApiPermissions( + applicationParameters, + graphServiceClient, + application).ConfigureAwait(false); + + await AddAdminConsentToApiPermissions( + graphServiceClient, + createdServicePrincipal, + scopesPerResource); } // For web API, we need to know the appId of the created app to compute the Identifier URI, @@ -188,6 +185,7 @@ internal async Task UpdateApplication( var graphServiceClient = GetGraphServiceClient(tokenCredential); + // TODO: Add if it's B2C, acquire or create the SUSI Policy var remoteApp = (await graphServiceClient.Applications.Request() .Filter($"appId eq '{parameters.ClientId}'").GetAsync()).FirstOrDefault(app => app.AppId.Equals(parameters.ClientId)); @@ -196,7 +194,7 @@ internal async Task UpdateApplication( return new JsonResponse(commandName, State.Fail, output: string.Format(Resources.NotFound, parameters.ClientId)); } - (bool needsUpdates, Application appUpdates) = GetApplicationUpdates(remoteApp, toolOptions); + (bool needsUpdates, Application appUpdates) = GetApplicationUpdates(remoteApp, toolOptions, parameters); if (!needsUpdates) { return new JsonResponse(commandName, State.Success, output: string.Format(Resources.NoUpdateNecessary, remoteApp.DisplayName, remoteApp.AppId)); @@ -220,7 +218,10 @@ internal async Task UpdateApplication( /// /// /// Updated Application if changes were made, otherwise null - internal static (bool needsUpdate, Application appUpdates) GetApplicationUpdates(Application existingApplication, ProvisioningToolOptions toolOptions) + internal static (bool needsUpdate, Application appUpdates) GetApplicationUpdates( + Application existingApplication, + ProvisioningToolOptions toolOptions, + ApplicationParameters parameters) { bool needsUpdate = false; @@ -233,7 +234,7 @@ internal static (bool needsUpdate, Application appUpdates) GetApplicationUpdates // Make updates if necessary needsUpdate |= UpdateRedirectUris(updatedApp, toolOptions); - needsUpdate |= UpdateImplicitGrantSettings(updatedApp, toolOptions); + needsUpdate |= UpdateImplicitGrantSettings(updatedApp, toolOptions, parameters.IsB2C); if (toolOptions.IsBlazorWasmHostedServer) { needsUpdate |= PreAuthorizeBlazorWasmClientApp(existingApplication, toolOptions, updatedApp); @@ -347,32 +348,30 @@ private static string UpdateCallbackPath(string redirectUri, bool isBlazorWasm = /// /// /// true if ImplicitGrantSettings require updates, else false - internal static bool UpdateImplicitGrantSettings(Application app, ProvisioningToolOptions toolOptions) + internal static bool UpdateImplicitGrantSettings(Application app, ProvisioningToolOptions toolOptions, bool isB2C = false) { bool needsUpdate = false; var currentSettings = app.Web.ImplicitGrantSettings; - if (toolOptions.IsBlazorWasm) // In the case of Blazor WASM, Access Tokens and Id Tokens must both be true. + // In the case of Blazor WASM and B2C, Access Tokens and Id Tokens must both be true. + if ((toolOptions.IsBlazorWasm || isB2C) + && (currentSettings.EnableAccessTokenIssuance is false + || currentSettings.EnableAccessTokenIssuance is false)) { - if (currentSettings.EnableAccessTokenIssuance is true || currentSettings.EnableIdTokenIssuance is true) - { - app.Web.ImplicitGrantSettings.EnableAccessTokenIssuance = false; - app.Web.ImplicitGrantSettings.EnableIdTokenIssuance = false; - - needsUpdate = true; - } + app.Web.ImplicitGrantSettings.EnableAccessTokenIssuance = true; + app.Web.ImplicitGrantSettings.EnableIdTokenIssuance = true; + needsUpdate = true; } - else // Otherwise we make changes only when the tool options differ from the existing settings. + // Otherwise we make changes only when the tool options differ from the existing settings. + else { - if (toolOptions.EnableAccessToken.HasValue && - currentSettings.EnableAccessTokenIssuance != toolOptions.EnableAccessToken.Value) + if (toolOptions.EnableAccessToken.HasValue && toolOptions.EnableAccessToken.Value != currentSettings.EnableAccessTokenIssuance) { app.Web.ImplicitGrantSettings.EnableAccessTokenIssuance = toolOptions.EnableAccessToken.Value; needsUpdate = true; } - if (toolOptions.EnableIdToken.HasValue && - currentSettings.EnableIdTokenIssuance != toolOptions.EnableIdToken.Value) + if (toolOptions.EnableIdToken.HasValue && toolOptions.EnableIdToken.Value != currentSettings.EnableIdTokenIssuance) { app.Web.ImplicitGrantSettings.EnableIdTokenIssuance = toolOptions.EnableIdToken.Value; needsUpdate = true; diff --git a/src/MSIdentityScaffolding/Microsoft.DotNet.MSIdentity/ProjectDescriptions/ProjectTypes.cs b/src/MSIdentityScaffolding/Microsoft.DotNet.MSIdentity/ProjectDescriptions/ProjectTypes.cs new file mode 100644 index 000000000..54a836832 --- /dev/null +++ b/src/MSIdentityScaffolding/Microsoft.DotNet.MSIdentity/ProjectDescriptions/ProjectTypes.cs @@ -0,0 +1,11 @@ +namespace Microsoft.DotNet.MSIdentity +{ + public static class ProjectTypes + { + public const string BlazorServer = "blazorserver"; + public const string WebApp = "webapp"; + public const string WebApi = "webapi"; + public const string BlazorWasmClient = "blazorwasm-client"; + public const string BlazorWasm = "blazorwasm"; + } +} diff --git a/src/MSIdentityScaffolding/Microsoft.DotNet.MSIdentity/Tool/AppProvisioningTool.cs b/src/MSIdentityScaffolding/Microsoft.DotNet.MSIdentity/Tool/AppProvisioningTool.cs index 88b5d6845..8026a505e 100644 --- a/src/MSIdentityScaffolding/Microsoft.DotNet.MSIdentity/Tool/AppProvisioningTool.cs +++ b/src/MSIdentityScaffolding/Microsoft.DotNet.MSIdentity/Tool/AppProvisioningTool.cs @@ -2,7 +2,6 @@ // Licensed under the MIT License. using System; using System.Collections.Generic; -using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; @@ -42,7 +41,7 @@ public class AppProvisioningTool : IMsAADTool private ProjectDescriptionReader? _projectDescriptionReader; private ProjectDescriptionReader ProjectDescriptionReader => _projectDescriptionReader ??= new ProjectDescriptionReader(FilePaths); - public AppProvisioningTool(string commandName, ProvisioningToolOptions provisioningToolOptions, bool silent = false) // TODO silent is temporary + public AppProvisioningTool(string commandName, ProvisioningToolOptions provisioningToolOptions, bool silent = false) { CommandName = commandName; ProvisioningToolOptions = provisioningToolOptions; @@ -202,12 +201,12 @@ private ProjectAuthenticationSettings InferApplicationParameters( // there can multiple project types if (!string.IsNullOrEmpty(provisioningToolOptions.ProjectType)) { - if (provisioningToolOptions.ProjectType.Equals("webapp", StringComparison.OrdinalIgnoreCase) - || provisioningToolOptions.ProjectType.Equals("blazorserver", StringComparison.OrdinalIgnoreCase)) + if (provisioningToolOptions.ProjectType.Equals(ProjectTypes.WebApp, StringComparison.OrdinalIgnoreCase) + || provisioningToolOptions.ProjectType.Equals(ProjectTypes.BlazorServer, StringComparison.OrdinalIgnoreCase)) { projectSettings.ApplicationParameters.IsWebApp = projectSettings.ApplicationParameters.IsWebApp ?? true; } - if (provisioningToolOptions.ProjectType.Equals("webapi", StringComparison.OrdinalIgnoreCase) || provisioningToolOptions.IsBlazorWasmHostedServer) + if (provisioningToolOptions.ProjectType.Equals(ProjectTypes.WebApi, StringComparison.OrdinalIgnoreCase) || provisioningToolOptions.IsBlazorWasmHostedServer) { projectSettings.ApplicationParameters.IsWebApi = projectSettings.ApplicationParameters.IsWebApi ?? true; } @@ -304,7 +303,6 @@ private async Task UpdateAppRegistration(TokenCredential tokenCredential, Applic output.Append(jsonResponse.Output); var response = new JsonResponse(CommandName, jsonResponse.State, output: output.ToString()); - ConsoleLogger.LogJsonMessage(response); } /// @@ -323,7 +321,7 @@ private async Task ConfigureBlazorWasmHostedClientAsync(A clientToolOptions.ProjectFilePath = ProvisioningToolOptions.ClientProject ?? string.Empty; clientToolOptions.ClientId = null; clientToolOptions.ClientProject = null; - clientToolOptions.ProjectType = "blazorwasm-client"; + clientToolOptions.ProjectType = ProjectTypes.BlazorWasmClient; clientToolOptions.AppDisplayName = string.Concat(clientToolOptions.AppDisplayName ?? serverApplicationParameters.ApplicationDisplayName, "-Client"); clientToolOptions.HostedAppIdUri = serverApplicationParameters.AppIdUri; clientToolOptions.HostedApiScopes = $"{serverApplicationParameters.AppIdUri}/{DefaultProperties.ApiScopes}"; diff --git a/src/MSIdentityScaffolding/Microsoft.DotNet.MSIdentity/Tool/ProvisioningToolOptions.cs b/src/MSIdentityScaffolding/Microsoft.DotNet.MSIdentity/Tool/ProvisioningToolOptions.cs index 8d23ab72c..f56d9cd03 100644 --- a/src/MSIdentityScaffolding/Microsoft.DotNet.MSIdentity/Tool/ProvisioningToolOptions.cs +++ b/src/MSIdentityScaffolding/Microsoft.DotNet.MSIdentity/Tool/ProvisioningToolOptions.cs @@ -173,7 +173,7 @@ public string ProjectTypeIdentifier /// /// Determines if the project type is blazor wasm /// - public bool IsBlazorWasm => "blazorwasm".Equals(ProjectType) || "blazorwasm-client".Equals(ProjectType); + public bool IsBlazorWasm => ProjectTypes.BlazorWasm.Equals(ProjectType) || ProjectTypes.BlazorWasmClient.Equals(ProjectType); /// /// App registration ID associated with the Blazor WASM hosted client, Used for the Blazor WASM hosted server API in order to pre-authorize the client app diff --git a/test/MSIdentityScaffolding/Microsoft.DotNet.MSIdentity.UnitTests.Tests/AppProvisioningToolTests.cs b/test/MSIdentityScaffolding/Microsoft.DotNet.MSIdentity.UnitTests.Tests/AppProvisioningToolTests.cs index 6761be9c5..443c3e55a 100644 --- a/test/MSIdentityScaffolding/Microsoft.DotNet.MSIdentity.UnitTests.Tests/AppProvisioningToolTests.cs +++ b/test/MSIdentityScaffolding/Microsoft.DotNet.MSIdentity.UnitTests.Tests/AppProvisioningToolTests.cs @@ -12,12 +12,14 @@ public class AppProvisioningToolTests const string existingClientId = "existing client Id"; const string existingInstance = "existing Instance"; const string existingCallbackPath = "existing Callback Path"; + const string existingSusi = "existing_signup_policy"; const string inputDomain = "input domain"; - const string inputTenantId = "input tenant Id"; - const string inputClientId = "input client Id"; + const string inputTenantId = "input_tenant_Id"; + const string inputClientId = "input_client_Id"; const string inputInstance = "http://inputInstance/"; const string inputCallbackPath = "input Callback Path"; + const string inputSusi = "input_signup_policy"; // TODO test input for susi for blazor and webapp [Fact] public void ModifyAppSettings_NoInput_DefaultOutput() @@ -35,7 +37,32 @@ public void ModifyAppSettings_NoInput_DefaultOutput() DefaultProperties.CallbackPath }); - (bool needsUpdate, JObject modifications) = modifier.GetModifiedAzureAdBlock(appSettings, parameters); + (bool needsUpdate, JObject modifications) = AppSettingsModifier.GetModifiedAzureAdBlock(appSettings, parameters); + Assert.True(JToken.DeepEquals(expected, modifications)); + } + + [Fact] + public void ModifyAppSettings_NoInput_DefaultOutput_B2C() + { + var modifier = new AppSettingsModifier(new Tool.ProvisioningToolOptions()); + var appSettings = new Newtonsoft.Json.Linq.JObject(); + var parameters = new AuthenticationParameters.ApplicationParameters { IsB2C = true }; + + var expected = JToken.FromObject(new + { + DefaultProperties.Domain, + DefaultProperties.TenantId, + DefaultProperties.ClientId, + DefaultProperties.Instance, + DefaultProperties.CallbackPath, + DefaultProperties.SignUpSignInPolicyId, + DefaultProperties.SignedOutCallbackPath, + DefaultProperties.ResetPasswordPolicyId, + DefaultProperties.EditProfilePolicyId, + EnablePiiLogging = true + }); + + (bool needsUpdate, JObject modifications) = AppSettingsModifier.GetModifiedAzureAdBlock(appSettings, parameters); Assert.True(JToken.DeepEquals(expected, modifications)); } @@ -48,12 +75,30 @@ public void ModifyAppSettings_NoInput_Blazor_DefaultOutput() var expected = JObject.FromObject(new { - DefaultProperties.Authority, + Authority = $"{DefaultProperties.Instance}{DefaultProperties.TenantId}", DefaultProperties.ClientId, DefaultProperties.ValidateAuthority }); - (bool needsUpdate, JObject modifications) = modifier.GetModifiedAzureAdBlock(appSettings, parameters); + (bool needsUpdate, JObject modifications) = AppSettingsModifier.GetModifiedAzureAdBlock(appSettings, parameters); + Assert.True(JToken.DeepEquals(expected, modifications)); + } + + [Fact] + public void ModifyAppSettings_NoInput_BlazorB2C_DefaultOutput() + { + var modifier = new AppSettingsModifier(new Tool.ProvisioningToolOptions()); + var appSettings = new Newtonsoft.Json.Linq.JObject(); + var parameters = new AuthenticationParameters.ApplicationParameters { IsBlazorWasm = true, IsB2C = true }; + + var expected = JObject.FromObject(new + { + Authority = $"{DefaultProperties.Instance}{DefaultProperties.TenantId}/{DefaultProperties.SignUpSignInPolicyId}", + DefaultProperties.ClientId, + ValidateAuthority = false + }); + + (bool needsUpdate, JObject modifications) = AppSettingsModifier.GetModifiedAzureAdBlock(appSettings, parameters); Assert.True(JToken.DeepEquals(expected, modifications)); } @@ -81,7 +126,41 @@ public void ModifyAppSettings_HasInput_NoExistingProperties() CallbackPath = inputCallbackPath }; - (bool needsUpdate, JObject modifications) = modifier.GetModifiedAzureAdBlock(appSettings, parameters); + (bool needsUpdate, JObject modifications) = AppSettingsModifier.GetModifiedAzureAdBlock(appSettings, parameters); + Assert.True(JToken.DeepEquals(expected, modifications)); + } + + [Fact] + public void ModifyAppSettings_HasInput_NoExistingProperties_B2C() + { + var modifier = new AppSettingsModifier(new Tool.ProvisioningToolOptions()); + var appSettings = new Newtonsoft.Json.Linq.JObject(); + + var expected = JObject.FromObject(new + { + Domain = inputDomain, + TenantId = inputTenantId, + ClientId = inputClientId, + Instance = inputInstance, + CallbackPath = inputCallbackPath, + DefaultProperties.SignUpSignInPolicyId, + DefaultProperties.SignedOutCallbackPath, + DefaultProperties.ResetPasswordPolicyId, + DefaultProperties.EditProfilePolicyId, + EnablePiiLogging = true + }); + + var parameters = new AuthenticationParameters.ApplicationParameters + { + Domain = inputDomain, + TenantId = inputTenantId, + ClientId = inputClientId, + Instance = inputInstance, + CallbackPath = inputCallbackPath, + IsB2C = true + }; + + (bool needsUpdate, JObject modifications) = AppSettingsModifier.GetModifiedAzureAdBlock(appSettings, parameters); Assert.True(JToken.DeepEquals(expected, modifications)); } @@ -118,7 +197,50 @@ public void ModifyAppSettings_HasInput_SomePropertiesMissing_InsertDefaults() ClientId = inputClientId }; - (bool needsUpdate, JObject modifications) = modifier.GetModifiedAzureAdBlock(appSettings, parameters); + (bool needsUpdate, JObject modifications) = AppSettingsModifier.GetModifiedAzureAdBlock(appSettings, parameters); + Assert.True(JToken.DeepEquals(expected, modifications)); + } + + [Fact] + public void ModifyAppSettings_HasInput_SomePropertiesMissing_InsertDefaults_B2C() + { + var modifier = new AppSettingsModifier(new Tool.ProvisioningToolOptions()); + var appSettings = new Newtonsoft.Json.Linq.JObject + { + { + "AzureAd", + JToken.FromObject(new + { + Domain = existingDomain, + TenantId = existingTenantId, + ClientId = existingClientId + }) + } + }; + + var expected = JObject.FromObject(new + { + Domain = inputDomain, + TenantId = inputTenantId, + ClientId = inputClientId, + Instance = DefaultProperties.Instance, + CallbackPath = DefaultProperties.CallbackPath, + DefaultProperties.SignUpSignInPolicyId, + DefaultProperties.SignedOutCallbackPath, + DefaultProperties.ResetPasswordPolicyId, + DefaultProperties.EditProfilePolicyId, + EnablePiiLogging = true + }); + + var parameters = new AuthenticationParameters.ApplicationParameters + { + Domain = inputDomain, + TenantId = inputTenantId, + ClientId = inputClientId, + IsB2C = true + }; + + (bool needsUpdate, JObject modifications) = AppSettingsModifier.GetModifiedAzureAdBlock(appSettings, parameters); Assert.True(JToken.DeepEquals(expected, modifications)); } @@ -159,7 +281,57 @@ public void ModifyAppSettings_HasAllInputParameters_ExistingPropertiesDiffer() CallbackPath = inputCallbackPath }); - (bool needsUpdate, JObject modifications) = modifier.GetModifiedAzureAdBlock(appSettings, parameters); + (bool needsUpdate, JObject modifications) = AppSettingsModifier.GetModifiedAzureAdBlock(appSettings, parameters); + Assert.True(JToken.DeepEquals(expected, modifications)); + } + + [Fact] + public void ModifyAppSettings_HasAllInputParameters_ExistingPropertiesDiffer_B2C() + { + var modifier = new AppSettingsModifier(new Tool.ProvisioningToolOptions()); + var appSettings = new Newtonsoft.Json.Linq.JObject + { + { + "AzureAd", + JToken.FromObject(new + { + Domain = existingDomain, + TenantId = existingTenantId, + ClientId = existingClientId, + Instance = existingInstance, + CallbackPath = existingCallbackPath, + SignUpSignInPolicyId = existingSusi + }) + } + }; + + var parameters = new AuthenticationParameters.ApplicationParameters + { + Domain = inputDomain, + TenantId = inputTenantId, + ClientId = inputClientId, + Instance = inputInstance, + CallbackPath = inputCallbackPath, + SusiPolicy = inputSusi, + IsB2C = true + }; + + var expected = JObject.FromObject(new + { + Domain = inputDomain, + TenantId = inputTenantId, + ClientId = inputClientId, + Instance = inputInstance, + CallbackPath = inputCallbackPath, + SignUpSignInPolicyId = inputSusi, + SignedOutCallbackPath = DefaultProperties.SignedOutCallbackPath, + ResetPasswordPolicyId = DefaultProperties.ResetPasswordPolicyId, + EditProfilePolicyId = DefaultProperties.EditProfilePolicyId, + EnablePiiLogging = true + }); + + (bool needsUpdate, JObject modifications) = AppSettingsModifier.GetModifiedAzureAdBlock(appSettings, parameters); + Assert.True(JToken.DeepEquals(expected, modifications)); } @@ -197,7 +369,51 @@ public void ModifyAppSettings_HasSomeInputParameters_ExistingPropertiesDiffer() CallbackPath = existingCallbackPath }); - (bool needsUpdate, JObject modifications) = modifier.GetModifiedAzureAdBlock(appSettings, parameters); + (bool needsUpdate, JObject modifications) = AppSettingsModifier.GetModifiedAzureAdBlock(appSettings, parameters); + Assert.True(JToken.DeepEquals(expected, modifications)); + } + + [Fact] + public void ModifyAppSettings_HasSomeInputParameters_ExistingPropertiesDiffer_B2C() + { + var modifier = new AppSettingsModifier(new Tool.ProvisioningToolOptions()); + + var appSettings = new Newtonsoft.Json.Linq.JObject + { + { + "AzureAd", + JToken.FromObject(new + { + Domain = existingDomain, + TenantId = existingTenantId, + ClientId = existingClientId, + Instance = existingInstance, + CallbackPath = existingCallbackPath + }) + } + }; + + var parameters = new AuthenticationParameters.ApplicationParameters + { + Domain = inputDomain, + IsB2C = true + }; + + var expected = JObject.FromObject(new + { + Domain = inputDomain, + TenantId = existingTenantId, + ClientId = existingClientId, + Instance = existingInstance, + CallbackPath = existingCallbackPath, + DefaultProperties.SignUpSignInPolicyId, + DefaultProperties.SignedOutCallbackPath, + DefaultProperties.ResetPasswordPolicyId, + DefaultProperties.EditProfilePolicyId, + EnablePiiLogging = true + }); + + (bool needsUpdate, JObject modifications) = AppSettingsModifier.GetModifiedAzureAdBlock(appSettings, parameters); Assert.True(JToken.DeepEquals(expected, modifications)); } @@ -224,7 +440,7 @@ public void ModifyAppSettings_HasEmptyInputParameter_ExistingPropertiesNotModifi { Domain = inputDomain, TenantId = "", - ClientId = "" + ClientId = "", }; var expected = JObject.FromObject(new @@ -236,10 +452,54 @@ public void ModifyAppSettings_HasEmptyInputParameter_ExistingPropertiesNotModifi CallbackPath = existingCallbackPath }); - (bool needsUpdate, JObject modifications) = modifier.GetModifiedAzureAdBlock(appSettings, parameters); + (bool needsUpdate, JObject modifications) = AppSettingsModifier.GetModifiedAzureAdBlock(appSettings, parameters); Assert.True(JToken.DeepEquals(expected, modifications)); } + [Fact] + public void ModifyAppSettings_HasEmptyInputParameter_ExistingPropertiesNotModified_B2C() + { + var modifier = new AppSettingsModifier(new Tool.ProvisioningToolOptions()); + var appSettings = new Newtonsoft.Json.Linq.JObject + { + { + "AzureAd", + JToken.FromObject(new + { + Domain = existingDomain, + TenantId = existingTenantId, + ClientId = existingClientId, + Instance = existingInstance, + CallbackPath = existingCallbackPath + }) + } + }; + + var parameters = new AuthenticationParameters.ApplicationParameters + { + Domain = inputDomain, + TenantId = "", + ClientId = "", + IsB2C = true + }; + + var expected = JObject.FromObject(new + { + Domain = inputDomain, + TenantId = existingTenantId, + ClientId = existingClientId, + Instance = existingInstance, + CallbackPath = existingCallbackPath, + DefaultProperties.SignUpSignInPolicyId, + DefaultProperties.SignedOutCallbackPath, + DefaultProperties.ResetPasswordPolicyId, + DefaultProperties.EditProfilePolicyId, + EnablePiiLogging = true + }); + + (bool needsUpdate, JObject modifications) = AppSettingsModifier.GetModifiedAzureAdBlock(appSettings, parameters); + Assert.True(JToken.DeepEquals(expected, modifications)); + } [Fact] public void ModifyAppSettings_BlazorWasm_AuthorityIsCorrect() @@ -276,7 +536,48 @@ public void ModifyAppSettings_BlazorWasm_AuthorityIsCorrect() ValidateAuthority = true }); - (bool needsUpdate, JObject modifications) = modifier.GetModifiedAzureAdBlock(appSettings, parameters); + (bool needsUpdate, JObject modifications) = AppSettingsModifier.GetModifiedAzureAdBlock(appSettings, parameters); + Assert.True(JToken.DeepEquals(expected, modifications)); + } + + [Fact] + public void ModifyAppSettings_BlazorWasmB2C_AuthorityIsCorrect() + { + var modifier = new AppSettingsModifier(new Tool.ProvisioningToolOptions()); + var appSettings = new Newtonsoft.Json.Linq.JObject + { + { + "AzureAd", + JToken.FromObject(new + { + Domain = existingDomain, + TenantId = existingTenantId, + ClientId = existingClientId, + Instance = existingInstance, + CallbackPath = existingCallbackPath, + SignUpSignInPolicyId = existingSusi + }) + } + }; + + var parameters = new AuthenticationParameters.ApplicationParameters + { + Domain = inputDomain, + TenantId = inputTenantId, + ClientId = inputClientId, + Instance = inputInstance, + IsBlazorWasm = true, + IsB2C = true + }; + + var expected = JObject.FromObject(new + { + ClientId = inputClientId, + Authority = $"{inputInstance}{inputTenantId}/{existingSusi}", + ValidateAuthority = false + }); + + (bool needsUpdate, JObject modifications) = AppSettingsModifier.GetModifiedAzureAdBlock(appSettings, parameters); Assert.True(JToken.DeepEquals(expected, modifications)); } diff --git a/test/MSIdentityScaffolding/Microsoft.DotNet.MSIdentity.UnitTests.Tests/MicrosoftIdentityPlatformApplicationManagerTests.cs b/test/MSIdentityScaffolding/Microsoft.DotNet.MSIdentity.UnitTests.Tests/MicrosoftIdentityPlatformApplicationManagerTests.cs index a61b5024a..6acf3d036 100644 --- a/test/MSIdentityScaffolding/Microsoft.DotNet.MSIdentity.UnitTests.Tests/MicrosoftIdentityPlatformApplicationManagerTests.cs +++ b/test/MSIdentityScaffolding/Microsoft.DotNet.MSIdentity.UnitTests.Tests/MicrosoftIdentityPlatformApplicationManagerTests.cs @@ -165,7 +165,7 @@ public void PreAuthorizeBlazorWasmClientAppTest_NoMatchingPreAuthorizedApplicati } [Fact] - public void UpdateImplicitGrantSettingsTest_WhenBlazorWasm_SetCheckboxesFalse() + public void UpdateImplicitGrantSettingsTest_WhenBlazorWasm_SetCheckboxesTrue() { var originalApp = new Graph.Application { @@ -173,8 +173,8 @@ public void UpdateImplicitGrantSettingsTest_WhenBlazorWasm_SetCheckboxesFalse() { ImplicitGrantSettings = new ImplicitGrantSettings { - EnableAccessTokenIssuance = true, - EnableIdTokenIssuance = true + EnableAccessTokenIssuance = false, + EnableIdTokenIssuance = false } } }; @@ -183,10 +183,10 @@ public void UpdateImplicitGrantSettingsTest_WhenBlazorWasm_SetCheckboxesFalse() ProjectType = "blazorwasm" }; - var output = MicrosoftIdentityPlatformApplicationManager.UpdateImplicitGrantSettings(originalApp, toolOptions); // TODO unit tests + var output = MicrosoftIdentityPlatformApplicationManager.UpdateImplicitGrantSettings(originalApp, toolOptions); Assert.True(output); - Assert.False(originalApp.Web.ImplicitGrantSettings.EnableAccessTokenIssuance); - Assert.False(originalApp.Web.ImplicitGrantSettings.EnableIdTokenIssuance); + Assert.True(originalApp.Web.ImplicitGrantSettings.EnableAccessTokenIssuance); + Assert.True(originalApp.Web.ImplicitGrantSettings.EnableIdTokenIssuance); } [Theory]