diff --git a/Directory.Build.props b/Directory.Build.props index 852b40b56..da15dbaa8 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -91,7 +91,7 @@ 4.36.0 4.57.0-preview 3.1.3 - 7.1.0 + 7.2.0 9.0.0-rc.2.24473.5 9.0.0-rc.2.24474.3 diff --git a/src/Microsoft.Identity.Web.TokenAcquisition/MergedOptions.cs b/src/Microsoft.Identity.Web.TokenAcquisition/MergedOptions.cs index aea7d25ca..8f15d5285 100644 --- a/src/Microsoft.Identity.Web.TokenAcquisition/MergedOptions.cs +++ b/src/Microsoft.Identity.Web.TokenAcquisition/MergedOptions.cs @@ -37,6 +37,7 @@ public ConfidentialClientApplicationOptions ConfidentialClientApplicationOptions // Properties of ConfidentialClientApplication which are not in MicrosoftIdentityOptions public AadAuthorityAudience AadAuthorityAudience { get; set; } + public string? AppHomeTenantId { get; set; } public AzureCloudInstance AzureCloudInstance { get; set; } public string? AzureRegion { get; set; } public IEnumerable? ClientCapabilities { get; set; } @@ -537,6 +538,8 @@ public static void UpdateMergedOptionsFromMicrosoftIdentityApplicationOptions(Mi mergedOptions.TenantId = microsoftIdentityApplicationOptions.TenantId; } + mergedOptions.AppHomeTenantId = microsoftIdentityApplicationOptions.AppHomeTenantId; + mergedOptions.WithSpaAuthCode |= microsoftIdentityApplicationOptions.WithSpaAuthCode; if ((mergedOptions.ClientCredentials == null || !mergedOptions.ClientCredentials.Any()) && microsoftIdentityApplicationOptions.ClientCredentials != null) diff --git a/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/net462/InternalAPI.Unshipped.txt b/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/net462/InternalAPI.Unshipped.txt index 7b9aca83f..74f7de658 100644 --- a/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/net462/InternalAPI.Unshipped.txt +++ b/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/net462/InternalAPI.Unshipped.txt @@ -1,3 +1,6 @@ #nullable enable +Microsoft.Identity.Web.MergedOptions.AppHomeTenantId.get -> string? +Microsoft.Identity.Web.MergedOptions.AppHomeTenantId.set -> void Microsoft.Identity.Web.TokenAcquisitionExtensionOptions.InvokeOnBeforeTokenAcquisitionForApp(Microsoft.Identity.Client.AcquireTokenForClientParameterBuilder! builder, Microsoft.Identity.Abstractions.AcquireTokenOptions? acquireTokenOptions) -> void readonly Microsoft.Identity.Web.TokenAcquisition.tokenAcquisitionExtensionOptionsMonitor -> Microsoft.Extensions.Options.IOptionsMonitor? +static Microsoft.Identity.Web.TokenAcquisition.ResolveTenant(string? tenant, Microsoft.Identity.Web.MergedOptions! mergedOptions) -> string? diff --git a/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/net472/InternalAPI.Unshipped.txt b/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/net472/InternalAPI.Unshipped.txt index 6137a9f55..74f7de658 100644 --- a/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/net472/InternalAPI.Unshipped.txt +++ b/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/net472/InternalAPI.Unshipped.txt @@ -1,4 +1,6 @@ #nullable enable +Microsoft.Identity.Web.MergedOptions.AppHomeTenantId.get -> string? +Microsoft.Identity.Web.MergedOptions.AppHomeTenantId.set -> void Microsoft.Identity.Web.TokenAcquisitionExtensionOptions.InvokeOnBeforeTokenAcquisitionForApp(Microsoft.Identity.Client.AcquireTokenForClientParameterBuilder! builder, Microsoft.Identity.Abstractions.AcquireTokenOptions? acquireTokenOptions) -> void readonly Microsoft.Identity.Web.TokenAcquisition.tokenAcquisitionExtensionOptionsMonitor -> Microsoft.Extensions.Options.IOptionsMonitor? - +static Microsoft.Identity.Web.TokenAcquisition.ResolveTenant(string? tenant, Microsoft.Identity.Web.MergedOptions! mergedOptions) -> string? diff --git a/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/net6.0/InternalAPI.Unshipped.txt b/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/net6.0/InternalAPI.Unshipped.txt index 7b9aca83f..74f7de658 100644 --- a/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/net6.0/InternalAPI.Unshipped.txt +++ b/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/net6.0/InternalAPI.Unshipped.txt @@ -1,3 +1,6 @@ #nullable enable +Microsoft.Identity.Web.MergedOptions.AppHomeTenantId.get -> string? +Microsoft.Identity.Web.MergedOptions.AppHomeTenantId.set -> void Microsoft.Identity.Web.TokenAcquisitionExtensionOptions.InvokeOnBeforeTokenAcquisitionForApp(Microsoft.Identity.Client.AcquireTokenForClientParameterBuilder! builder, Microsoft.Identity.Abstractions.AcquireTokenOptions? acquireTokenOptions) -> void readonly Microsoft.Identity.Web.TokenAcquisition.tokenAcquisitionExtensionOptionsMonitor -> Microsoft.Extensions.Options.IOptionsMonitor? +static Microsoft.Identity.Web.TokenAcquisition.ResolveTenant(string? tenant, Microsoft.Identity.Web.MergedOptions! mergedOptions) -> string? diff --git a/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/net7.0/InternalAPI.Unshipped.txt b/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/net7.0/InternalAPI.Unshipped.txt index 7b9aca83f..74f7de658 100644 --- a/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/net7.0/InternalAPI.Unshipped.txt +++ b/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/net7.0/InternalAPI.Unshipped.txt @@ -1,3 +1,6 @@ #nullable enable +Microsoft.Identity.Web.MergedOptions.AppHomeTenantId.get -> string? +Microsoft.Identity.Web.MergedOptions.AppHomeTenantId.set -> void Microsoft.Identity.Web.TokenAcquisitionExtensionOptions.InvokeOnBeforeTokenAcquisitionForApp(Microsoft.Identity.Client.AcquireTokenForClientParameterBuilder! builder, Microsoft.Identity.Abstractions.AcquireTokenOptions? acquireTokenOptions) -> void readonly Microsoft.Identity.Web.TokenAcquisition.tokenAcquisitionExtensionOptionsMonitor -> Microsoft.Extensions.Options.IOptionsMonitor? +static Microsoft.Identity.Web.TokenAcquisition.ResolveTenant(string? tenant, Microsoft.Identity.Web.MergedOptions! mergedOptions) -> string? diff --git a/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/net8.0/InternalAPI.Unshipped.txt b/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/net8.0/InternalAPI.Unshipped.txt index 7b9aca83f..74f7de658 100644 --- a/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/net8.0/InternalAPI.Unshipped.txt +++ b/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/net8.0/InternalAPI.Unshipped.txt @@ -1,3 +1,6 @@ #nullable enable +Microsoft.Identity.Web.MergedOptions.AppHomeTenantId.get -> string? +Microsoft.Identity.Web.MergedOptions.AppHomeTenantId.set -> void Microsoft.Identity.Web.TokenAcquisitionExtensionOptions.InvokeOnBeforeTokenAcquisitionForApp(Microsoft.Identity.Client.AcquireTokenForClientParameterBuilder! builder, Microsoft.Identity.Abstractions.AcquireTokenOptions? acquireTokenOptions) -> void readonly Microsoft.Identity.Web.TokenAcquisition.tokenAcquisitionExtensionOptionsMonitor -> Microsoft.Extensions.Options.IOptionsMonitor? +static Microsoft.Identity.Web.TokenAcquisition.ResolveTenant(string? tenant, Microsoft.Identity.Web.MergedOptions! mergedOptions) -> string? diff --git a/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/net9.0/InternalAPI.Unshipped.txt b/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/net9.0/InternalAPI.Unshipped.txt index 7b9aca83f..74f7de658 100644 --- a/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/net9.0/InternalAPI.Unshipped.txt +++ b/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/net9.0/InternalAPI.Unshipped.txt @@ -1,3 +1,6 @@ #nullable enable +Microsoft.Identity.Web.MergedOptions.AppHomeTenantId.get -> string? +Microsoft.Identity.Web.MergedOptions.AppHomeTenantId.set -> void Microsoft.Identity.Web.TokenAcquisitionExtensionOptions.InvokeOnBeforeTokenAcquisitionForApp(Microsoft.Identity.Client.AcquireTokenForClientParameterBuilder! builder, Microsoft.Identity.Abstractions.AcquireTokenOptions? acquireTokenOptions) -> void readonly Microsoft.Identity.Web.TokenAcquisition.tokenAcquisitionExtensionOptionsMonitor -> Microsoft.Extensions.Options.IOptionsMonitor? +static Microsoft.Identity.Web.TokenAcquisition.ResolveTenant(string? tenant, Microsoft.Identity.Web.MergedOptions! mergedOptions) -> string? diff --git a/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/netstandard2.0/InternalAPI.Unshipped.txt b/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/netstandard2.0/InternalAPI.Unshipped.txt index 7b9aca83f..74f7de658 100644 --- a/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/netstandard2.0/InternalAPI.Unshipped.txt +++ b/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/netstandard2.0/InternalAPI.Unshipped.txt @@ -1,3 +1,6 @@ #nullable enable +Microsoft.Identity.Web.MergedOptions.AppHomeTenantId.get -> string? +Microsoft.Identity.Web.MergedOptions.AppHomeTenantId.set -> void Microsoft.Identity.Web.TokenAcquisitionExtensionOptions.InvokeOnBeforeTokenAcquisitionForApp(Microsoft.Identity.Client.AcquireTokenForClientParameterBuilder! builder, Microsoft.Identity.Abstractions.AcquireTokenOptions? acquireTokenOptions) -> void readonly Microsoft.Identity.Web.TokenAcquisition.tokenAcquisitionExtensionOptionsMonitor -> Microsoft.Extensions.Options.IOptionsMonitor? +static Microsoft.Identity.Web.TokenAcquisition.ResolveTenant(string? tenant, Microsoft.Identity.Web.MergedOptions! mergedOptions) -> string? diff --git a/src/Microsoft.Identity.Web.TokenAcquisition/TokenAcquisition.cs b/src/Microsoft.Identity.Web.TokenAcquisition/TokenAcquisition.cs index a97db66da..1c7573616 100644 --- a/src/Microsoft.Identity.Web.TokenAcquisition/TokenAcquisition.cs +++ b/src/Microsoft.Identity.Web.TokenAcquisition/TokenAcquisition.cs @@ -72,7 +72,7 @@ class OAuthConstants /// /// Meta-tenant identifiers which are not allowed in client credentials. /// - private readonly HashSet _metaTenantIdentifiers = new HashSet( + private static readonly HashSet _metaTenantIdentifiers = new HashSet( new[] { Constants.Common, @@ -397,6 +397,32 @@ private void LogAuthResult(AuthenticationResult? authenticationResult) } } + /// + /// Resolves the tenant based on if the tenant is already set or the TenantId configured + /// in the options or the AppHomeTenantId if the TenantId is a meta tenant. + /// + /// Provided tenant or null if not provided + /// Merged configuration from which to retrieve tenant value as necessary + /// Resolved tenant + internal static string? ResolveTenant(string? tenant, MergedOptions mergedOptions) + { + if (string.IsNullOrEmpty(tenant)) + { + tenant = mergedOptions.TenantId; + if (!string.IsNullOrEmpty(tenant) && _metaTenantIdentifiers.Contains(tenant!) && !string.IsNullOrEmpty(mergedOptions.AppHomeTenantId)) + { + tenant = mergedOptions.AppHomeTenantId; + } + } + + if (!string.IsNullOrEmpty(tenant) && _metaTenantIdentifiers.Contains(tenant!)) + { + throw new ArgumentException(IDWebErrorMessage.ClientCredentialTenantShouldBeTenanted, nameof(tenant)); + } + + return tenant; + } + /// /// Acquires an authentication result from the authority configured in the app, for the confidential client itself (not on behalf of a user) /// using either a client credentials or managed identity flow. See https://aka.ms/msal-net-client-credentials for client credentials or @@ -427,15 +453,7 @@ public async Task GetAuthenticationResultForAppAsync( MergedOptions mergedOptions = _tokenAcquisitionHost.GetOptions(authenticationScheme ?? tokenAcquisitionOptions?.AuthenticationOptionsName, out _); - if (string.IsNullOrEmpty(tenant)) - { - tenant = mergedOptions.TenantId; - } - - if (!string.IsNullOrEmpty(tenant) && _metaTenantIdentifiers.Contains(tenant!)) - { - throw new ArgumentException(IDWebErrorMessage.ClientCredentialTenantShouldBeTenanted, nameof(tenant)); - } + tenant = ResolveTenant(tenant, mergedOptions); // If using managed identity if (tokenAcquisitionOptions != null && tokenAcquisitionOptions.ManagedIdentity != null) diff --git a/tests/Microsoft.Identity.Web.Test/TokenAcquisitionTests.cs b/tests/Microsoft.Identity.Web.Test/TokenAcquisitionTests.cs new file mode 100644 index 000000000..d375a5f7f --- /dev/null +++ b/tests/Microsoft.Identity.Web.Test/TokenAcquisitionTests.cs @@ -0,0 +1,49 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using Xunit; + +namespace Microsoft.Identity.Web.Test +{ + public class TokenAcquisitionTests + { + private const string Tenant = "tenant"; + private const string TenantId = "tenant-id"; + private const string AppHomeTenantId = "app-home-tenant-id"; + + [Theory] + [InlineData(null, null, null, null)] + [InlineData(null, null, AppHomeTenantId, null)] + [InlineData(Tenant, null, null, Tenant)] + [InlineData(Tenant, TenantId, null, Tenant)] + [InlineData(Tenant, null, AppHomeTenantId, Tenant)] + [InlineData(Tenant, TenantId, AppHomeTenantId, Tenant)] + [InlineData(null, TenantId, null, TenantId)] + [InlineData(null, TenantId, AppHomeTenantId, TenantId)] + [InlineData(null, Constants.Common, AppHomeTenantId, AppHomeTenantId)] + [InlineData(null, Constants.Organizations, AppHomeTenantId, AppHomeTenantId)] + public void TestResolveTenantReturnsCorrectTenant(string? tenant, string? tenantId, string? appHomeTenantId, string? expectedValue) + { + string? resolvedTenant = TokenAcquisition.ResolveTenant(tenant, new MergedOptions { TenantId = tenantId, AppHomeTenantId = appHomeTenantId }); + Assert.Equal(expectedValue, resolvedTenant); + } + + [Theory] + [InlineData(Constants.Common, null)] + [InlineData(Constants.Organizations, null)] + [InlineData(Constants.Common, TenantId)] + [InlineData(Constants.Organizations, TenantId)] + [InlineData(Constants.Common, Constants.Common)] + [InlineData(Constants.Common, Constants.Organizations)] + [InlineData(Constants.Organizations, Constants.Organizations)] + [InlineData(Constants.Organizations, Constants.Common)] + [InlineData(null, Constants.Common)] + [InlineData(null, Constants.Organizations)] + public void TestResolveTenantThrowsWhenMetaTenant(string? tenant, string? tenantId) + { + var exception = Assert.Throws(() => TokenAcquisition.ResolveTenant(tenant, new MergedOptions { TenantId = tenantId })); + Assert.StartsWith(IDWebErrorMessage.ClientCredentialTenantShouldBeTenanted, exception.Message, StringComparison.Ordinal); + } + } +}