diff --git a/sdk/identity/Azure.Identity/BREAKING_CHANGES.md b/sdk/identity/Azure.Identity/BREAKING_CHANGES.md
index 846d26424cd0..e32e4ea6941e 100644
--- a/sdk/identity/Azure.Identity/BREAKING_CHANGES.md
+++ b/sdk/identity/Azure.Identity/BREAKING_CHANGES.md
@@ -1,5 +1,33 @@
# Breaking Changes
+## 1.7.0
+
+### Behavioral change to credential types supporting multi-tenant authentication
+
+As of `Azure.Identity` 1.7.0, the default behavior of credentials supporting multi-tenant authentication has changed. Each of these credentials will throw an `AuthenticationFailedException` if the requested `TenantId` doesn't match the tenant ID originally configured on the credential. Apps must now do one of the following things:
+
+- Add all IDs, of tenants from which tokens should be acquired, to the `AdditionallyAllowedTenants` list in the credential options. For example:
+
+```C# Snippet:Identity_BreakingChanges_AddExplicitAdditionallyAllowedTenants
+var credential = new DefaultAzureCredential(new DefaultAzureCredentialOptions
+{
+ AdditionallyAllowedTenants = { "", "" }
+});
+```
+
+- Add `*` to enable token acquisition from any tenant. This is the original behavior and is compatible with previous versions supporting multi tenant authentication. For example:
+
+```C# Snippet:Identity_BreakingChanges_AddAllAdditionallyAllowedTenants
+var credential = new DefaultAzureCredential(new DefaultAzureCredentialOptions
+{
+ AdditionallyAllowedTenants = { "*" }
+});
+```
+
+Note: Credential types which do not require a `TenantId` on construction will only throw `AuthenticationFailedException` when the application has provided a value for `TenantId` either in the options or via a constructor overload. If no `TenantId` is specified when constructing the credential, the credential will acquire tokens for any requested `TenantId` regardless of the value of `AdditionallyAllowedTenants`.
+
+More information on this change and the consideration behind it can be found [here](https://aka.ms/azsdk/blog/multi-tenant-guidance).
+
## 1.4.0
### Changed `ExcludeSharedTokenCacheCredential` default value from __false__ to __true__ on `DefaultAzureCredentialsOptions`
diff --git a/sdk/identity/Azure.Identity/CHANGELOG.md b/sdk/identity/Azure.Identity/CHANGELOG.md
index 13b8b4c3868e..cbb1720b0dc3 100644
--- a/sdk/identity/Azure.Identity/CHANGELOG.md
+++ b/sdk/identity/Azure.Identity/CHANGELOG.md
@@ -1,5 +1,34 @@
# Release History
+## 1.7.0 (2022-09-19)
+
+### Features Added
+- Added `AdditionallyAllowedTenants` to the following credential options to force explicit opt-in behavior for multi-tenant authentication:
+ - `AuthorizationCodeCredentialOptions`
+ - `AzureCliCredentialOptions`
+ - `AzurePowerShellCredentialOptions`
+ - `ClientAssertionCredentialOptions`
+ - `ClientCertificateCredentialOptions`
+ - `ClientSecretCredentialOptions`
+ - `DefaultAzureCredentialOptions`
+ - `OnBehalfOfCredentialOptions`
+ - `UsernamePasswordCredentialOptions`
+ - `VisualStudioCodeCredentialOptions`
+ - `VisualStudioCredentialOptions`
+- Added `TenantId` to `DefaultAzureCredentialOptions` to avoid having to set `InteractiveBrowserTenantId`, `SharedTokenCacheTenantId`, `VisualStudioCodeTenantId`, and `VisualStudioTenantId` individually.
+
+### Bugs Fixed
+- Fixed overly restrictive scope validation to allow the '_' character, for common scopes such as `user_impersonation` [#30647](https://github.com/Azure/azure-sdk-for-net/issues/30647)
+
+### Breaking Changes
+- Credential types supporting multi-tenant authentication will now throw `AuthenticationFailedException` if the requested tenant ID doesn't match the credential's tenant ID, and is not included in the `AdditionallyAllowedTenants` option. Applications must now explicitly add additional tenants to the `AdditionallyAllowedTenants` list, or add '*' to list, to enable acquiring tokens from tenants other than the originally specified tenant ID. See [BREAKING_CHANGES.md](https://github.com/Azure/azure-sdk-for-net/blob/main/sdk/identity/Azure.Identity/BREAKING_CHANGES.md#170).
+- `ManagedIdentityCredential` token caching added in 1.7.0-beta.1 has been removed from this release and will be added back in 1.8.0-beta.1
+
+## 1.7.0-beta.1 (2022-08-09)
+
+### Features Added
+- `ManagedIdentityCredential` will now internally cache tokens. Apps can call `GetToken` or `GetTokenAsync` directly without needing to cache to avoid throttling.
+
## 1.6.1 (2022-08-08)
### Bugs Fixed
diff --git a/sdk/identity/Azure.Identity/TROUBLESHOOTING.md b/sdk/identity/Azure.Identity/TROUBLESHOOTING.md
index 4755b278758c..ba1350f2a938 100644
--- a/sdk/identity/Azure.Identity/TROUBLESHOOTING.md
+++ b/sdk/identity/Azure.Identity/TROUBLESHOOTING.md
@@ -21,6 +21,7 @@ This troubleshooting guide covers failure investigation techniques, common error
- [Troubleshoot VisualStudioCredential Authentication Issues](#troubleshoot-visualstudiocredential-authenticaton-issues)
- [Troubleshoot AzureCliCredential Authentication Issues](#troubleshoot-azureclicredential-authentication-issues)
- [Troubleshoot AzurePowerShellCredential Authentication Issues](#troubleshoot-azurepowershellcredential-authentication-issues)
+- [Troubleshoot Multi Tenant Authentication Issues](#troubleshoot-multi-tenant-authentication-issues)
- [Get Additional Help](#get-additional-help)
## Handle Azure Identity Exceptions
@@ -246,6 +247,13 @@ Get-AzAccessToken -ResourceUrl "https://management.core.windows.net"
```
>Note that output of this command will contain a valid access token, and SHOULD NOT BE SHARED to avoid compromising account security.
+## Troubleshoot Multi Tenant Authentication Issues
+`AuthenticationFailedException`
+
+| Error Message |Description| Mitigation |
+|---|---|---|
+|The current credential is not configured to acquire tokens for tenant |The application must configure the credential to allow acquiring tokens from the requested tenant.|Add the requested tenant ID it to the AdditionallyAllowedTenants on the credential options, or add \"*\" to AdditionallyAllowedTenants to allow acquiring tokens for any tenant.
This exception was added as part of functional a breaking change to multi tenant authentication in version `1.7.0`. Users experiencing this error after upgrading can find details on the change and migration in [BREAKING_CHANGES.md](https://github.com/Azure/azure-sdk-for-net/blob/main/sdk/identity/Azure.Identity/BREAKING_CHANGES.md#170) |
+
## Get Additional Help
Additional information on ways to reach out for support can be found in the [SUPPORT.md](https://github.com/Azure/azure-sdk-for-net/blob/main/SUPPORT.md) at the root of the repo.
diff --git a/sdk/identity/Azure.Identity/api/Azure.Identity.netstandard2.0.cs b/sdk/identity/Azure.Identity/api/Azure.Identity.netstandard2.0.cs
index fcfd14424150..d2162c6c9098 100644
--- a/sdk/identity/Azure.Identity/api/Azure.Identity.netstandard2.0.cs
+++ b/sdk/identity/Azure.Identity/api/Azure.Identity.netstandard2.0.cs
@@ -39,6 +39,7 @@ public AuthorizationCodeCredential(string tenantId, string clientId, string clie
public partial class AuthorizationCodeCredentialOptions : Azure.Identity.TokenCredentialOptions
{
public AuthorizationCodeCredentialOptions() { }
+ public System.Collections.Generic.IList AdditionallyAllowedTenants { get { throw null; } }
public System.Uri RedirectUri { get { throw null; } set { } }
}
public static partial class AzureAuthorityHosts
@@ -58,6 +59,7 @@ public AzureCliCredential(Azure.Identity.AzureCliCredentialOptions options) { }
public partial class AzureCliCredentialOptions : Azure.Identity.TokenCredentialOptions
{
public AzureCliCredentialOptions() { }
+ public System.Collections.Generic.IList AdditionallyAllowedTenants { get { throw null; } }
public string TenantId { get { throw null; } set { } }
}
public partial class AzurePowerShellCredential : Azure.Core.TokenCredential
@@ -70,6 +72,7 @@ public AzurePowerShellCredential(Azure.Identity.AzurePowerShellCredentialOptions
public partial class AzurePowerShellCredentialOptions : Azure.Identity.TokenCredentialOptions
{
public AzurePowerShellCredentialOptions() { }
+ public System.Collections.Generic.IList AdditionallyAllowedTenants { get { throw null; } }
public string TenantId { get { throw null; } set { } }
}
public partial class ChainedTokenCredential : Azure.Core.TokenCredential
@@ -89,6 +92,7 @@ public ClientAssertionCredential(string tenantId, string clientId, System.Func AdditionallyAllowedTenants { get { throw null; } }
}
public partial class ClientCertificateCredential : Azure.Core.TokenCredential
{
@@ -105,6 +109,7 @@ public ClientCertificateCredential(string tenantId, string clientId, string clie
public partial class ClientCertificateCredentialOptions : Azure.Identity.TokenCredentialOptions
{
public ClientCertificateCredentialOptions() { }
+ public System.Collections.Generic.IList AdditionallyAllowedTenants { get { throw null; } }
public bool SendCertificateChain { get { throw null; } set { } }
public Azure.Identity.TokenCachePersistenceOptions TokenCachePersistenceOptions { get { throw null; } set { } }
}
@@ -120,6 +125,7 @@ public ClientSecretCredential(string tenantId, string clientId, string clientSec
public partial class ClientSecretCredentialOptions : Azure.Identity.TokenCredentialOptions
{
public ClientSecretCredentialOptions() { }
+ public System.Collections.Generic.IList AdditionallyAllowedTenants { get { throw null; } }
public Azure.Identity.TokenCachePersistenceOptions TokenCachePersistenceOptions { get { throw null; } set { } }
}
public partial class CredentialUnavailableException : Azure.Identity.AuthenticationFailedException
@@ -138,6 +144,7 @@ public DefaultAzureCredential(bool includeInteractiveCredentials = false) { }
public partial class DefaultAzureCredentialOptions : Azure.Identity.TokenCredentialOptions
{
public DefaultAzureCredentialOptions() { }
+ public System.Collections.Generic.IList AdditionallyAllowedTenants { get { throw null; } }
public bool ExcludeAzureCliCredential { get { throw null; } set { } }
public bool ExcludeAzurePowerShellCredential { get { throw null; } set { } }
public bool ExcludeEnvironmentCredential { get { throw null; } set { } }
@@ -147,12 +154,17 @@ public DefaultAzureCredentialOptions() { }
public bool ExcludeVisualStudioCodeCredential { get { throw null; } set { } }
public bool ExcludeVisualStudioCredential { get { throw null; } set { } }
public string InteractiveBrowserCredentialClientId { get { throw null; } set { } }
+ [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)]
public string InteractiveBrowserTenantId { get { throw null; } set { } }
public string ManagedIdentityClientId { get { throw null; } set { } }
public Azure.Core.ResourceIdentifier ManagedIdentityResourceId { get { throw null; } set { } }
+ [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)]
public string SharedTokenCacheTenantId { get { throw null; } set { } }
public string SharedTokenCacheUsername { get { throw null; } set { } }
+ public string TenantId { get { throw null; } set { } }
+ [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)]
public string VisualStudioCodeTenantId { get { throw null; } set { } }
+ [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)]
public string VisualStudioTenantId { get { throw null; } set { } }
}
public partial class DeviceCodeCredential : Azure.Core.TokenCredential
@@ -173,6 +185,7 @@ public DeviceCodeCredential(System.Func AdditionallyAllowedTenants { get { throw null; } }
public Azure.Identity.AuthenticationRecord AuthenticationRecord { get { throw null; } set { } }
public string ClientId { get { throw null; } set { } }
public System.Func DeviceCodeCallback { get { throw null; } set { } }
@@ -223,6 +236,7 @@ public InteractiveBrowserCredential(string tenantId, string clientId, Azure.Iden
public partial class InteractiveBrowserCredentialOptions : Azure.Identity.TokenCredentialOptions
{
public InteractiveBrowserCredentialOptions() { }
+ public System.Collections.Generic.IList AdditionallyAllowedTenants { get { throw null; } }
public Azure.Identity.AuthenticationRecord AuthenticationRecord { get { throw null; } set { } }
public string ClientId { get { throw null; } set { } }
public bool DisableAutomaticAuthentication { get { throw null; } set { } }
@@ -252,6 +266,7 @@ public OnBehalfOfCredential(string tenantId, string clientId, string clientSecre
public partial class OnBehalfOfCredentialOptions : Azure.Identity.TokenCredentialOptions
{
public OnBehalfOfCredentialOptions() { }
+ public System.Collections.Generic.IList AdditionallyAllowedTenants { get { throw null; } }
public bool SendCertificateChain { get { throw null; } set { } }
public Azure.Identity.TokenCachePersistenceOptions TokenCachePersistenceOptions { get { throw null; } set { } }
}
@@ -333,6 +348,7 @@ public UsernamePasswordCredential(string username, string password, string tenan
public partial class UsernamePasswordCredentialOptions : Azure.Identity.TokenCredentialOptions
{
public UsernamePasswordCredentialOptions() { }
+ public System.Collections.Generic.IList AdditionallyAllowedTenants { get { throw null; } }
public Azure.Identity.TokenCachePersistenceOptions TokenCachePersistenceOptions { get { throw null; } set { } }
}
public partial class VisualStudioCodeCredential : Azure.Core.TokenCredential
@@ -345,6 +361,7 @@ public VisualStudioCodeCredential(Azure.Identity.VisualStudioCodeCredentialOptio
public partial class VisualStudioCodeCredentialOptions : Azure.Identity.TokenCredentialOptions
{
public VisualStudioCodeCredentialOptions() { }
+ public System.Collections.Generic.IList AdditionallyAllowedTenants { get { throw null; } }
public string TenantId { get { throw null; } set { } }
}
public partial class VisualStudioCredential : Azure.Core.TokenCredential
@@ -357,6 +374,7 @@ public VisualStudioCredential(Azure.Identity.VisualStudioCredentialOptions optio
public partial class VisualStudioCredentialOptions : Azure.Identity.TokenCredentialOptions
{
public VisualStudioCredentialOptions() { }
+ public System.Collections.Generic.IList AdditionallyAllowedTenants { get { throw null; } }
public string TenantId { get { throw null; } set { } }
}
}
diff --git a/sdk/identity/Azure.Identity/src/Azure.Identity.csproj b/sdk/identity/Azure.Identity/src/Azure.Identity.csproj
index 5fbd51dd5ff7..5e1b464a381d 100644
--- a/sdk/identity/Azure.Identity/src/Azure.Identity.csproj
+++ b/sdk/identity/Azure.Identity/src/Azure.Identity.csproj
@@ -2,9 +2,9 @@
This is the implementation of the Azure SDK Client Library for Azure IdentityMicrosoft Azure.Identity Component
- 1.6.1
+ 1.7.0
- 1.6.0
+ 1.6.1Microsoft Azure Identity;$(PackageCommonTags)$(RequiredTargetFrameworks)$(NoWarn);3021;AZC0011
diff --git a/sdk/identity/Azure.Identity/src/Credentials/AuthorizationCodeCredential.cs b/sdk/identity/Azure.Identity/src/Credentials/AuthorizationCodeCredential.cs
index 4f7c623c7a33..164b75b9fdce 100644
--- a/sdk/identity/Azure.Identity/src/Credentials/AuthorizationCodeCredential.cs
+++ b/sdk/identity/Azure.Identity/src/Credentials/AuthorizationCodeCredential.cs
@@ -3,6 +3,7 @@
using System;
using System.ComponentModel;
+using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Azure.Core;
@@ -25,6 +26,7 @@ public class AuthorizationCodeCredential : TokenCredential
private readonly MsalConfidentialClient _client;
private readonly string _redirectUri;
private readonly string _tenantId;
+ private readonly string[] _additionallyAllowedTenantIds;
///
/// Protected constructor for mocking.
@@ -101,6 +103,8 @@ internal AuthorizationCodeCredential(string tenantId, string clientId, string cl
clientSecret,
_redirectUri,
options);
+
+ _additionallyAllowedTenantIds = TenantIdResolver.ResolveAddionallyAllowedTenantIds(options?.AdditionallyAllowedTenantsCore);
}
///
@@ -134,7 +138,7 @@ private async ValueTask GetTokenImplAsync(bool async, TokenRequestC
try
{
AccessToken token;
- var tenantId = TenantIdResolver.Resolve(_tenantId, requestContext);
+ var tenantId = TenantIdResolver.Resolve(_tenantId, requestContext, _additionallyAllowedTenantIds);
if (_record is null)
{
diff --git a/sdk/identity/Azure.Identity/src/Credentials/AuthorizationCodeCredentialOptions.cs b/sdk/identity/Azure.Identity/src/Credentials/AuthorizationCodeCredentialOptions.cs
index d58279ee1cc7..2b91afaf986e 100644
--- a/sdk/identity/Azure.Identity/src/Credentials/AuthorizationCodeCredentialOptions.cs
+++ b/sdk/identity/Azure.Identity/src/Credentials/AuthorizationCodeCredentialOptions.cs
@@ -2,6 +2,8 @@
// Licensed under the MIT License.
using System;
+using System.Collections;
+using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Azure.Core;
@@ -20,5 +22,10 @@ public class AuthorizationCodeCredentialOptions : TokenCredentialOptions
/// The redirect Uri that will be sent with the GetToken request.
///
public Uri RedirectUri { get; set; }
+
+ ///
+ /// For multi-tenant applications, specifies additional tenants for which the credential may acquire tokens. Add the wildcard value "*" to allow the credential to acquire tokens for any tenant in which the application is installed.
+ ///
+ public IList AdditionallyAllowedTenants => AdditionallyAllowedTenantsCore;
}
}
diff --git a/sdk/identity/Azure.Identity/src/Credentials/AzureCliCredential.cs b/sdk/identity/Azure.Identity/src/Credentials/AzureCliCredential.cs
index 5cce5ae52da0..8165cd0da80e 100644
--- a/sdk/identity/Azure.Identity/src/Credentials/AzureCliCredential.cs
+++ b/sdk/identity/Azure.Identity/src/Credentials/AzureCliCredential.cs
@@ -46,9 +46,10 @@ public class AzureCliCredential : TokenCredential
private readonly CredentialPipeline _pipeline;
private readonly IProcessService _processService;
- private readonly string _tenantId;
private readonly bool _logPII;
private readonly bool _logAccountDetails;
+ internal string TenantId { get; }
+ internal string[] AdditionallyAllowedTenantIds { get; }
///
/// Create an instance of CliCredential class.
@@ -72,7 +73,8 @@ internal AzureCliCredential(CredentialPipeline pipeline, IProcessService process
_pipeline = pipeline;
_path = !string.IsNullOrEmpty(EnvironmentVariables.Path) ? EnvironmentVariables.Path : DefaultPath;
_processService = processService ?? ProcessService.Default;
- _tenantId = options?.TenantId;
+ TenantId = options?.TenantId;
+ AdditionallyAllowedTenantIds = TenantIdResolver.ResolveAddionallyAllowedTenantIds(options?.AdditionallyAllowedTenantsCore);
}
///
@@ -115,7 +117,7 @@ private async ValueTask GetTokenImplAsync(bool async, TokenRequestC
private async ValueTask RequestCliAccessTokenAsync(bool async, TokenRequestContext context, CancellationToken cancellationToken)
{
string resource = ScopeUtilities.ScopesToResource(context.Scopes);
- string tenantId = TenantIdResolver.Resolve(_tenantId, context);
+ string tenantId = TenantIdResolver.Resolve(TenantId, context, AdditionallyAllowedTenantIds);
ScopeUtilities.ValidateScope(resource);
@@ -167,7 +169,7 @@ private async ValueTask RequestCliAccessTokenAsync(bool async, Toke
if (_logAccountDetails)
{
var accountDetails = TokenHelper.ParseAccountInfoFromToken(token.Token);
- AzureIdentityEventSource.Singleton.AuthenticatedAccountDetails(accountDetails.ClientId, accountDetails.TenantId ?? _tenantId, accountDetails.Upn, accountDetails.ObjectId);
+ AzureIdentityEventSource.Singleton.AuthenticatedAccountDetails(accountDetails.ClientId, accountDetails.TenantId ?? TenantId, accountDetails.Upn, accountDetails.ObjectId);
}
return token;
diff --git a/sdk/identity/Azure.Identity/src/Credentials/AzureCliCredentialOptions.cs b/sdk/identity/Azure.Identity/src/Credentials/AzureCliCredentialOptions.cs
index 03b3f90859b3..f876e98c59d2 100644
--- a/sdk/identity/Azure.Identity/src/Credentials/AzureCliCredentialOptions.cs
+++ b/sdk/identity/Azure.Identity/src/Credentials/AzureCliCredentialOptions.cs
@@ -1,6 +1,8 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
+using System.Collections.Generic;
+
namespace Azure.Identity
{
///
@@ -9,8 +11,15 @@ namespace Azure.Identity
public class AzureCliCredentialOptions : TokenCredentialOptions
{
///
- /// The Azure Active Directory tenant (directory) Id of the service principal
+ /// The ID of the tenant to which the credential will authenticate by default. If not specified, the credential will authenticate to any requested tenant, and will default to the tenant provided to the 'az login' command.
///
public string TenantId { get; set; }
+
+ ///
+ /// Specifies tenants in addition to the specified for which the credential may acquire tokens.
+ /// Add the wildcard value "*" to allow the credential to acquire tokens for any tenant the logged in account can access.
+ /// If no value is specified for this option will have no effect, and the credential will acquire tokens for any requested tenant.
+ ///
+ public IList AdditionallyAllowedTenants => AdditionallyAllowedTenantsCore;
}
}
diff --git a/sdk/identity/Azure.Identity/src/Credentials/AzurePowerShellCredential.cs b/sdk/identity/Azure.Identity/src/Credentials/AzurePowerShellCredential.cs
index 069ab0f5f9dd..1b40126b8623 100644
--- a/sdk/identity/Azure.Identity/src/Credentials/AzurePowerShellCredential.cs
+++ b/sdk/identity/Azure.Identity/src/Credentials/AzurePowerShellCredential.cs
@@ -36,7 +36,8 @@ public class AzurePowerShellCredential : TokenCredential
private static readonly string DefaultWorkingDirWindows = Environment.GetFolderPath(Environment.SpecialFolder.System);
private const string DefaultWorkingDirNonWindows = "/bin/";
private static readonly string DefaultWorkingDir = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? DefaultWorkingDirWindows : DefaultWorkingDirNonWindows;
- private readonly string _tenantId;
+ internal string TenantId { get; }
+ internal string[] AdditionallyAllowedTenantIds { get; }
private readonly bool _logPII;
private readonly bool _logAccountDetails;
internal const string AzurePowerShellNotLogInError = "Please run 'Connect-AzAccount' to set up account.";
@@ -62,9 +63,10 @@ internal AzurePowerShellCredential(AzurePowerShellCredentialOptions options, Cre
UseLegacyPowerShell = false;
_logPII = options?.IsLoggingPIIEnabled ?? false;
_logAccountDetails = options?.Diagnostics?.IsAccountIdentifierLoggingEnabled ?? false;
- _tenantId = options?.TenantId;
+ TenantId = options?.TenantId;
_pipeline = pipeline ?? CredentialPipeline.GetInstance(options);
_processService = processService ?? ProcessService.Default;
+ AdditionallyAllowedTenantIds = TenantIdResolver.ResolveAddionallyAllowedTenantIds(options?.AdditionallyAllowedTenantsCore);
}
///
@@ -99,7 +101,7 @@ private async ValueTask GetTokenImplAsync(bool async, TokenRequestC
if (_logAccountDetails)
{
var accountDetails = TokenHelper.ParseAccountInfoFromToken(token.Token);
- AzureIdentityEventSource.Singleton.AuthenticatedAccountDetails(accountDetails.ClientId, accountDetails.TenantId ?? _tenantId, accountDetails.Upn, accountDetails.ObjectId);
+ AzureIdentityEventSource.Singleton.AuthenticatedAccountDetails(accountDetails.ClientId, accountDetails.TenantId ?? TenantId, accountDetails.Upn, accountDetails.ObjectId);
}
return scope.Succeeded(token);
}
@@ -115,7 +117,7 @@ private async ValueTask GetTokenImplAsync(bool async, TokenRequestC
if (_logAccountDetails)
{
- AzureIdentityEventSource.Singleton.AuthenticatedAccountDetails(null, _tenantId, null, null);
+ AzureIdentityEventSource.Singleton.AuthenticatedAccountDetails(null, TenantId, null, null);
}
return scope.Succeeded(token);
@@ -136,7 +138,7 @@ private async ValueTask RequestAzurePowerShellAccessTokenAsync(bool
string resource = ScopeUtilities.ScopesToResource(context.Scopes);
ScopeUtilities.ValidateScope(resource);
- var tenantId = TenantIdResolver.Resolve(_tenantId, context);
+ var tenantId = TenantIdResolver.Resolve(TenantId, context, AdditionallyAllowedTenantIds);
GetFileNameAndArguments(resource, tenantId, out string fileName, out string argument);
ProcessStartInfo processStartInfo = GetAzurePowerShellProcessStartInfo(fileName, argument);
diff --git a/sdk/identity/Azure.Identity/src/Credentials/AzurePowerShellCredentialOptions.cs b/sdk/identity/Azure.Identity/src/Credentials/AzurePowerShellCredentialOptions.cs
index 4853f19fefbd..0a22f998177b 100644
--- a/sdk/identity/Azure.Identity/src/Credentials/AzurePowerShellCredentialOptions.cs
+++ b/sdk/identity/Azure.Identity/src/Credentials/AzurePowerShellCredentialOptions.cs
@@ -1,6 +1,8 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
+using System.Collections.Generic;
+
namespace Azure.Identity
{
///
@@ -9,8 +11,15 @@ namespace Azure.Identity
public class AzurePowerShellCredentialOptions : TokenCredentialOptions
{
///
- /// The Azure Active Directory tenant (directory) Id of the service principal
+ /// The ID of the tenant to which the credential will authenticate by default. If not specified, the credential will authenticate to any requested tenant, and will default to the tenant provided to the 'Connect-AzAccount' cmdlet.
///
public string TenantId { get; set; }
+
+ ///
+ /// Specifies tenants in addition to the specified for which the credential may acquire tokens.
+ /// Add the wildcard value "*" to allow the credential to acquire tokens for any tenant the logged in account can access.
+ /// If no value is specified for , this option will have no effect, and the credential will acquire tokens for any requested tenant.
+ ///
+ public IList AdditionallyAllowedTenants => AdditionallyAllowedTenantsCore;
}
}
diff --git a/sdk/identity/Azure.Identity/src/Credentials/ClientAssertionCredential.cs b/sdk/identity/Azure.Identity/src/Credentials/ClientAssertionCredential.cs
index 16905d339e07..210c7dc2d02f 100644
--- a/sdk/identity/Azure.Identity/src/Credentials/ClientAssertionCredential.cs
+++ b/sdk/identity/Azure.Identity/src/Credentials/ClientAssertionCredential.cs
@@ -17,9 +17,12 @@ namespace Azure.Identity
///
public class ClientAssertionCredential : TokenCredential
{
+ private readonly string[] _additionallyAllowedTenantIds;
+
internal string TenantId { get; }
internal string ClientId { get; }
internal MsalConfidentialClient Client { get; }
+ internal CredentialPipeline Pipeline { get; }
internal bool AllowMultiTenantAuthentication { get; }
///
@@ -43,6 +46,8 @@ public ClientAssertionCredential(string tenantId, string clientId, Func
@@ -60,6 +65,8 @@ public ClientAssertionCredential(string tenantId, string clientId, Func
ClientId = clientId;
Client = options?.MsalClient ?? new MsalConfidentialClient(options?.Pipeline ?? CredentialPipeline.GetInstance(options), tenantId, clientId, assertionCallback, options);
+ Pipeline = options?.Pipeline ?? Client.Pipeline;
+ _additionallyAllowedTenantIds = TenantIdResolver.ResolveAddionallyAllowedTenantIds(options?.AdditionallyAllowedTenantsCore);
}
///
@@ -70,11 +77,11 @@ public ClientAssertionCredential(string tenantId, string clientId, Func
/// An which can be used to authenticate service client calls.
public override AccessToken GetToken(TokenRequestContext requestContext, CancellationToken cancellationToken = default)
{
- using CredentialDiagnosticScope scope = Client.Pipeline.StartGetTokenScope("ClientAssertionCredential.GetToken", requestContext);
+ using CredentialDiagnosticScope scope = Pipeline.StartGetTokenScope("ClientAssertionCredential.GetToken", requestContext);
try
{
- var tenantId = TenantIdResolver.Resolve(TenantId, requestContext);
+ var tenantId = TenantIdResolver.Resolve(TenantId, requestContext, _additionallyAllowedTenantIds);
AuthenticationResult result = Client.AcquireTokenForClientAsync(requestContext.Scopes, tenantId, false, cancellationToken).EnsureCompleted();
@@ -94,11 +101,11 @@ public override AccessToken GetToken(TokenRequestContext requestContext, Cancell
/// An which can be used to authenticate service client calls.
public async override ValueTask GetTokenAsync(TokenRequestContext requestContext, CancellationToken cancellationToken = default)
{
- using CredentialDiagnosticScope scope = Client.Pipeline.StartGetTokenScope("ClientAssertionCredential.GetToken", requestContext);
+ using CredentialDiagnosticScope scope = Pipeline.StartGetTokenScope("ClientAssertionCredential.GetToken", requestContext);
try
{
- var tenantId = TenantIdResolver.Resolve(TenantId, requestContext);
+ var tenantId = TenantIdResolver.Resolve(TenantId, requestContext, _additionallyAllowedTenantIds);
AuthenticationResult result = await Client.AcquireTokenForClientAsync(requestContext.Scopes, tenantId, true, cancellationToken).ConfigureAwait(false);
diff --git a/sdk/identity/Azure.Identity/src/Credentials/ClientAssertionCredentialOptions.cs b/sdk/identity/Azure.Identity/src/Credentials/ClientAssertionCredentialOptions.cs
index 075d0a1a58cf..359b7fd08296 100644
--- a/sdk/identity/Azure.Identity/src/Credentials/ClientAssertionCredentialOptions.cs
+++ b/sdk/identity/Azure.Identity/src/Credentials/ClientAssertionCredentialOptions.cs
@@ -1,6 +1,8 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
+using System.Collections.Generic;
+
namespace Azure.Identity
{
///
@@ -11,5 +13,10 @@ public class ClientAssertionCredentialOptions : TokenCredentialOptions
internal CredentialPipeline Pipeline { get; set; }
internal MsalConfidentialClient MsalClient { get; set; }
+
+ ///
+ /// For multi-tenant applications, specifies additional tenants for which the credential may acquire tokens. Add the wildcard value "*" to allow the credential to acquire tokens for any tenant in which the application is installed.
+ ///
+ public IList AdditionallyAllowedTenants => AdditionallyAllowedTenantsCore;
}
}
diff --git a/sdk/identity/Azure.Identity/src/Credentials/ClientCertificateCredential.cs b/sdk/identity/Azure.Identity/src/Credentials/ClientCertificateCredential.cs
index 09a2614eb5dd..40aaa2008ffa 100644
--- a/sdk/identity/Azure.Identity/src/Credentials/ClientCertificateCredential.cs
+++ b/sdk/identity/Azure.Identity/src/Credentials/ClientCertificateCredential.cs
@@ -36,6 +36,8 @@ public class ClientCertificateCredential : TokenCredential
private readonly CredentialPipeline _pipeline;
+ private readonly string[] _additionallyAllowedTenantIds;
+
///
/// Protected constructor for mocking.
///
@@ -160,6 +162,8 @@ internal ClientCertificateCredential(
certificateProvider,
certCredOptions?.SendCertificateChain ?? false,
options);
+
+ _additionallyAllowedTenantIds = TenantIdResolver.ResolveAddionallyAllowedTenantIds(options?.AdditionallyAllowedTenantsCore);
}
///
@@ -174,7 +178,7 @@ public override AccessToken GetToken(TokenRequestContext requestContext, Cancell
try
{
- var tenantId = TenantIdResolver.Resolve(TenantId, requestContext);
+ var tenantId = TenantIdResolver.Resolve(TenantId, requestContext, _additionallyAllowedTenantIds);
AuthenticationResult result = Client.AcquireTokenForClientAsync(requestContext.Scopes, tenantId, false, cancellationToken).EnsureCompleted();
return scope.Succeeded(new AccessToken(result.AccessToken, result.ExpiresOn));
@@ -197,7 +201,7 @@ public override async ValueTask GetTokenAsync(TokenRequestContext r
try
{
- var tenantId = TenantIdResolver.Resolve(TenantId, requestContext);
+ var tenantId = TenantIdResolver.Resolve(TenantId, requestContext, _additionallyAllowedTenantIds);
AuthenticationResult result = await Client
.AcquireTokenForClientAsync(requestContext.Scopes, tenantId, true, cancellationToken)
.ConfigureAwait(false);
diff --git a/sdk/identity/Azure.Identity/src/Credentials/ClientCertificateCredentialOptions.cs b/sdk/identity/Azure.Identity/src/Credentials/ClientCertificateCredentialOptions.cs
index 3fef89a2c225..bf06b51396d0 100644
--- a/sdk/identity/Azure.Identity/src/Credentials/ClientCertificateCredentialOptions.cs
+++ b/sdk/identity/Azure.Identity/src/Credentials/ClientCertificateCredentialOptions.cs
@@ -1,6 +1,8 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
+using System.Collections.Generic;
+
namespace Azure.Identity
{
///
@@ -17,5 +19,10 @@ public class ClientCertificateCredentialOptions : TokenCredentialOptions, IToken
/// Will include x5c header in client claims when acquiring a token to enable subject name / issuer based authentication for the .
///
public bool SendCertificateChain { get; set; }
+
+ ///
+ /// For multi-tenant applications, specifies additional tenants for which the credential may acquire tokens. Add the wildcard value "*" to allow the credential to acquire tokens for any tenant in which the application is installed.
+ ///
+ public IList AdditionallyAllowedTenants => AdditionallyAllowedTenantsCore;
}
}
diff --git a/sdk/identity/Azure.Identity/src/Credentials/ClientSecretCredential.cs b/sdk/identity/Azure.Identity/src/Credentials/ClientSecretCredential.cs
index 4118405de442..91af8b65f38e 100644
--- a/sdk/identity/Azure.Identity/src/Credentials/ClientSecretCredential.cs
+++ b/sdk/identity/Azure.Identity/src/Credentials/ClientSecretCredential.cs
@@ -19,6 +19,8 @@ public class ClientSecretCredential : TokenCredential
{
private readonly CredentialPipeline _pipeline;
+ private readonly string[] _additionallyAllowedTenantIds;
+
internal MsalConfidentialClient Client { get; }
///
@@ -95,6 +97,8 @@ internal ClientSecretCredential(string tenantId, string clientId, string clientS
clientSecret,
null,
options);
+
+ _additionallyAllowedTenantIds = TenantIdResolver.ResolveAddionallyAllowedTenantIds(options?.AdditionallyAllowedTenantsCore);
}
///
@@ -109,7 +113,7 @@ public override async ValueTask GetTokenAsync(TokenRequestContext r
try
{
- var tenantId = TenantIdResolver.Resolve(TenantId, requestContext);
+ var tenantId = TenantIdResolver.Resolve(TenantId, requestContext, _additionallyAllowedTenantIds);
AuthenticationResult result = await Client.AcquireTokenForClientAsync(requestContext.Scopes, tenantId, true, cancellationToken).ConfigureAwait(false);
return scope.Succeeded(new AccessToken(result.AccessToken, result.ExpiresOn));
@@ -132,7 +136,7 @@ public override AccessToken GetToken(TokenRequestContext requestContext, Cancell
try
{
- var tenantId = TenantIdResolver.Resolve(TenantId, requestContext);
+ var tenantId = TenantIdResolver.Resolve(TenantId, requestContext, _additionallyAllowedTenantIds);
AuthenticationResult result = Client.AcquireTokenForClientAsync(requestContext.Scopes, tenantId, false, cancellationToken).EnsureCompleted();
return scope.Succeeded(new AccessToken(result.AccessToken, result.ExpiresOn));
diff --git a/sdk/identity/Azure.Identity/src/Credentials/ClientSecretCredentialOptions.cs b/sdk/identity/Azure.Identity/src/Credentials/ClientSecretCredentialOptions.cs
index 48f41b0090b6..65eb91713ce0 100644
--- a/sdk/identity/Azure.Identity/src/Credentials/ClientSecretCredentialOptions.cs
+++ b/sdk/identity/Azure.Identity/src/Credentials/ClientSecretCredentialOptions.cs
@@ -1,6 +1,8 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
+using System.Collections.Generic;
+
namespace Azure.Identity
{
///
@@ -12,5 +14,10 @@ public class ClientSecretCredentialOptions : TokenCredentialOptions, ITokenCache
/// Specifies the to be used by the credential. If not options are specified, the token cache will not be persisted to disk.
///
public TokenCachePersistenceOptions TokenCachePersistenceOptions { get; set; }
+
+ ///
+ /// For multi-tenant applications, specifies additional tenants for which the credential may acquire tokens. Add the wildcard value "*" to allow the credential to acquire tokens for any tenant in which the application is installed.
+ ///
+ public IList AdditionallyAllowedTenants => AdditionallyAllowedTenantsCore;
}
}
diff --git a/sdk/identity/Azure.Identity/src/Credentials/DefaultAzureCredential.cs b/sdk/identity/Azure.Identity/src/Credentials/DefaultAzureCredential.cs
index 496e44d1a682..f7f02214e90b 100644
--- a/sdk/identity/Azure.Identity/src/Credentials/DefaultAzureCredential.cs
+++ b/sdk/identity/Azure.Identity/src/Credentials/DefaultAzureCredential.cs
@@ -50,7 +50,6 @@ public class DefaultAzureCredential : TokenCredential
private const string Troubleshooting = "See the troubleshooting guide for more information. https://aka.ms/azsdk/net/identity/defaultazurecredential/troubleshoot";
private const string DefaultExceptionMessage = "DefaultAzureCredential failed to retrieve a token from the included credentials. " + Troubleshooting;
private const string UnhandledExceptionMessage = "DefaultAzureCredential authentication failed due to an unhandled exception: ";
- private static readonly TokenCredential[] s_defaultCredentialChain = GetDefaultAzureCredentialChain(new DefaultAzureCredentialFactory(null), new DefaultAzureCredentialOptions());
private readonly CredentialPipeline _pipeline;
private readonly AsyncLockWithValue _credentialLock;
@@ -75,14 +74,14 @@ public DefaultAzureCredential(bool includeInteractiveCredentials = false)
public DefaultAzureCredential(DefaultAzureCredentialOptions options)
// we call ValidateAuthoriyHostOption to validate that we have a valid authority host before constructing the DAC chain
// if we don't validate this up front it will end up throwing an exception out of a static initializer which obscures the error.
- : this(new DefaultAzureCredentialFactory(ValidateAuthorityHostOption(options)), options)
+ : this(new DefaultAzureCredentialFactory(ValidateAuthorityHostOption(options)))
{
}
- internal DefaultAzureCredential(DefaultAzureCredentialFactory factory, DefaultAzureCredentialOptions options)
+ internal DefaultAzureCredential(DefaultAzureCredentialFactory factory)
{
_pipeline = factory.Pipeline;
- _sources = GetDefaultAzureCredentialChain(factory, options);
+ _sources = factory.CreateCredentialChain();
_credentialLock = new AsyncLockWithValue();
}
@@ -183,64 +182,6 @@ private static async ValueTask GetTokenFromCredentialAsync(TokenCre
throw CredentialUnavailableException.CreateAggregateException(DefaultExceptionMessage, exceptions);
}
- private static TokenCredential[] GetDefaultAzureCredentialChain(DefaultAzureCredentialFactory factory, DefaultAzureCredentialOptions options)
- {
- if (options is null)
- {
- return s_defaultCredentialChain;
- }
-
- int i = 0;
- TokenCredential[] chain = new TokenCredential[8];
-
- if (!options.ExcludeEnvironmentCredential)
- {
- chain[i++] = factory.CreateEnvironmentCredential();
- }
-
- if (!options.ExcludeManagedIdentityCredential)
- {
- chain[i++] = factory.CreateManagedIdentityCredential(options);
- }
-
- if (!options.ExcludeSharedTokenCacheCredential)
- {
- chain[i++] = factory.CreateSharedTokenCacheCredential(options.SharedTokenCacheTenantId, options.SharedTokenCacheUsername);
- }
-
- if (!options.ExcludeVisualStudioCredential)
- {
- chain[i++] = factory.CreateVisualStudioCredential(options.VisualStudioTenantId);
- }
-
- if (!options.ExcludeVisualStudioCodeCredential)
- {
- chain[i++] = factory.CreateVisualStudioCodeCredential(options.VisualStudioCodeTenantId);
- }
-
- if (!options.ExcludeAzureCliCredential)
- {
- chain[i++] = factory.CreateAzureCliCredential();
- }
-
- if (!options.ExcludeAzurePowerShellCredential)
- {
- chain[i++] = factory.CreateAzurePowerShellCredential();
- }
-
- if (!options.ExcludeInteractiveBrowserCredential)
- {
- chain[i++] = factory.CreateInteractiveBrowserCredential(options.InteractiveBrowserTenantId, options.InteractiveBrowserCredentialClientId);
- }
-
- if (i == 0)
- {
- throw new ArgumentException("At least one credential type must be included in the authentication flow.", nameof(options));
- }
-
- return chain;
- }
-
private static DefaultAzureCredentialOptions ValidateAuthorityHostOption(DefaultAzureCredentialOptions options)
{
Validations.ValidateAuthorityHost(options?.AuthorityHost ?? AzureAuthorityHosts.GetDefault());
diff --git a/sdk/identity/Azure.Identity/src/Credentials/DefaultAzureCredentialOptions.cs b/sdk/identity/Azure.Identity/src/Credentials/DefaultAzureCredentialOptions.cs
index 01e701042af2..50cbca268b47 100644
--- a/sdk/identity/Azure.Identity/src/Credentials/DefaultAzureCredentialOptions.cs
+++ b/sdk/identity/Azure.Identity/src/Credentials/DefaultAzureCredentialOptions.cs
@@ -2,6 +2,9 @@
// Licensed under the MIT License.
using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Net;
using Azure.Core;
namespace Azure.Identity
@@ -11,12 +14,90 @@ namespace Azure.Identity
///
public class DefaultAzureCredentialOptions : TokenCredentialOptions
{
+ private struct UpdateTracker
+ {
+ private bool _updated;
+ private T _value;
+
+ public UpdateTracker(T initialValue)
+ {
+ _value = initialValue;
+ _updated = false;
+ }
+
+ public T Value
+ {
+ get => _value;
+ set
+ {
+ _value = value;
+ _updated = true;
+ }
+ }
+
+ public bool Updated => _updated;
+ }
+
+ private UpdateTracker _tenantId = new UpdateTracker(GetNonEmptyStringOrNull(EnvironmentVariables.TenantId));
+ private UpdateTracker _interactiveBrowserTenantId = new UpdateTracker(GetNonEmptyStringOrNull(EnvironmentVariables.TenantId));
+ private UpdateTracker _sharedTokenCacheTenantId = new UpdateTracker(GetNonEmptyStringOrNull(EnvironmentVariables.TenantId));
+ private UpdateTracker _visualStudioTenantId = new UpdateTracker(GetNonEmptyStringOrNull(EnvironmentVariables.TenantId));
+ private UpdateTracker _visualStudioCodeTenantId = new UpdateTracker(GetNonEmptyStringOrNull(EnvironmentVariables.TenantId));
+
+ ///
+ /// The ID of the tenant to which the credential will authenticate by default. If not specified, the credential will authenticate to any requested tenant, and will default to the tenant to which the chosen authentication method was originally authenticated.
+ ///
+ public string TenantId
+ {
+ get => _tenantId.Value;
+ set
+ {
+ if (_interactiveBrowserTenantId.Updated && value != _interactiveBrowserTenantId.Value)
+ {
+ throw new InvalidOperationException("Applications should not set both TenantId and InteractiveBrowserTenantId. TenantId is preferred, and is functionally equivalent. InteractiveBrowserTenantId exists only to provide backwards compatibility.");
+ }
+
+ if (_sharedTokenCacheTenantId.Updated && value != _sharedTokenCacheTenantId.Value)
+ {
+ throw new InvalidOperationException("Applications should not set both TenantId and SharedTokenCacheTenantId. TenantId is preferred, and is functionally equivalent. SharedTokenCacheTenantId exists only to provide backwards compatibility.");
+ }
+
+ if (_visualStudioTenantId.Updated && value != _visualStudioTenantId.Value)
+ {
+ throw new InvalidOperationException("Applications should not set both TenantId and VisualStudioTenantId. TenantId is preferred, and is functionally equivalent. VisualStudioTenantId exists only to provide backwards compatibility.");
+ }
+
+ if (_visualStudioCodeTenantId.Updated && value != _visualStudioCodeTenantId.Value)
+ {
+ throw new InvalidOperationException("Applications should not set both TenantId and VisualStudioCodeTenantId. TenantId is preferred, and is functionally equivalent. VisualStudioCodeTenantId exists only to provide backwards compatibility.");
+ }
+ _tenantId.Value = value;
+ _interactiveBrowserTenantId.Value = value;
+ _sharedTokenCacheTenantId.Value = value;
+ _visualStudioCodeTenantId.Value = value;
+ _visualStudioTenantId.Value = value;
+ }
+ }
+
///
/// The tenant id of the user to authenticate, in the case the authenticates through, the
/// . The default is null and will authenticate users to their default tenant.
/// The value can also be set by setting the environment variable AZURE_TENANT_ID.
///
- public string InteractiveBrowserTenantId { get; set; } = GetNonEmptyStringOrNull(EnvironmentVariables.TenantId);
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public string InteractiveBrowserTenantId
+ {
+ get => _interactiveBrowserTenantId.Value;
+ set
+ {
+ if (_tenantId.Updated && value != _tenantId.Value)
+ {
+ throw new InvalidOperationException("Applications should not set both TenantId and InteractiveBrowserTenantId. TenantId is preferred, and is functionally equivalent. InteractiveBrowserTenantId exists only to provide backwards compatibility.");
+ }
+
+ _interactiveBrowserTenantId.Value = value;
+ }
+ }
///
/// Specifies the tenant id of the preferred authentication account, to be retrieved from the shared token cache for single sign on authentication with
@@ -26,21 +107,68 @@ public class DefaultAzureCredentialOptions : TokenCredentialOptions
/// If multiple accounts are found in the shared token cache and no value is specified, or the specified value matches no accounts in
/// the cache the SharedTokenCacheCredential will not be used for authentication.
///
- public string SharedTokenCacheTenantId { get; set; } = GetNonEmptyStringOrNull(EnvironmentVariables.TenantId);
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public string SharedTokenCacheTenantId
+ {
+ get => _sharedTokenCacheTenantId.Value;
+ set
+ {
+ if (_tenantId.Updated && value != _tenantId.Value)
+ {
+ throw new InvalidOperationException("Applications should not set both TenantId and SharedTokenCacheTenantId. TenantId is preferred, and is functionally equivalent. SharedTokenCacheTenantId exists only to provide backwards compatibility.");
+ }
+
+ _sharedTokenCacheTenantId.Value = value;
+ }
+ }
///
/// The tenant id of the user to authenticate, in the case the authenticates through, the
/// . The default is null and will authenticate users to their default tenant.
/// The value can also be set by setting the environment variable AZURE_TENANT_ID.
///
- public string VisualStudioTenantId { get; set; } = GetNonEmptyStringOrNull(EnvironmentVariables.TenantId);
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public string VisualStudioTenantId
+ {
+ get => _visualStudioTenantId.Value;
+ set
+ {
+ if (_tenantId.Updated && value != _tenantId.Value)
+ {
+ throw new InvalidOperationException("Applications should not set both TenantId and VisualStudioTenantId. TenantId is preferred, and is functionally equivalent. VisualStudioTenantId exists only to provide backwards compatibility.");
+ }
+
+ _visualStudioTenantId.Value = value;
+ }
+ }
///
- /// The tenant id of the user to authenticate, in the case the authenticates through, the
+ /// The tenant ID of the user to authenticate, in the case the authenticates through, the
/// . The default is null and will authenticate users to their default tenant.
/// The value can also be set by setting the environment variable AZURE_TENANT_ID.
///
- public string VisualStudioCodeTenantId { get; set; } = GetNonEmptyStringOrNull(EnvironmentVariables.TenantId);
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public string VisualStudioCodeTenantId
+ {
+ get => _visualStudioCodeTenantId.Value;
+ set
+ {
+ if (_tenantId.Updated && value != _tenantId.Value)
+ {
+ throw new InvalidOperationException("Applications should not set both TenantId and VisualStudioCodeTenantId. TenantId is preferred, and is functionally equivalent. VisualStudioCodeTenantId exists only to provide backwards compatibility.");
+ }
+
+ _visualStudioCodeTenantId.Value = value;
+ }
+ }
+
+ ///
+ /// Specifies tenants in addition to the specified for which the credential may acquire tokens.
+ /// Add the wildcard value "*" to allow the credential to acquire tokens for any tenant the logged in account can access.
+ /// If no value is specified for , this option will have no effect on that authentication method, and the credential will acquire tokens for any requested tenant when using that method.
+ /// This value can also be set by setting the environment variable AZURE_ADDITOINAL_ALLOWED_TENANTS.
+ ///
+ public IList AdditionallyAllowedTenants { get; private set; } = EnvironmentVariables.AdditionallyAllowedTenants;
///
/// Specifies the preferred authentication account to be retrieved from the shared token cache for single sign on authentication with
@@ -108,14 +236,45 @@ public class DefaultAzureCredentialOptions : TokenCredentialOptions
///
public bool ExcludeVisualStudioCodeCredential { get; set; }
- private static string GetNonEmptyStringOrNull(string str)
- {
- return !string.IsNullOrEmpty(str) ? str : null;
- }
-
///
/// Specifies whether the will be excluded from the authentication flow.
///
public bool ExcludeAzurePowerShellCredential { get; set; }
+
+ internal DefaultAzureCredentialOptions ShallowClone()
+ {
+ var options = new DefaultAzureCredentialOptions
+ {
+ _tenantId = _tenantId,
+ _interactiveBrowserTenantId = _interactiveBrowserTenantId,
+ _sharedTokenCacheTenantId = _sharedTokenCacheTenantId,
+ _visualStudioTenantId = _visualStudioTenantId,
+ _visualStudioCodeTenantId = _visualStudioCodeTenantId,
+ SharedTokenCacheUsername = SharedTokenCacheUsername,
+ InteractiveBrowserCredentialClientId = InteractiveBrowserCredentialClientId,
+ ManagedIdentityClientId = ManagedIdentityClientId,
+ ManagedIdentityResourceId = ManagedIdentityResourceId,
+ ExcludeEnvironmentCredential = ExcludeEnvironmentCredential,
+ ExcludeManagedIdentityCredential = ExcludeManagedIdentityCredential,
+ ExcludeSharedTokenCacheCredential = ExcludeSharedTokenCacheCredential,
+ ExcludeInteractiveBrowserCredential = ExcludeInteractiveBrowserCredential,
+ ExcludeAzureCliCredential = ExcludeAzureCliCredential,
+ ExcludeVisualStudioCredential = ExcludeVisualStudioCredential,
+ ExcludeVisualStudioCodeCredential = ExcludeVisualStudioCodeCredential,
+ ExcludeAzurePowerShellCredential = ExcludeAzurePowerShellCredential,
+ AuthorityHost = AuthorityHost
+ };
+
+ foreach (var addlTenant in AdditionallyAllowedTenants)
+ {
+ options.AdditionallyAllowedTenants.Add(addlTenant);
+ }
+
+ return options;
+ }
+ private static string GetNonEmptyStringOrNull(string str)
+ {
+ return !string.IsNullOrEmpty(str) ? str : null;
+ }
}
}
diff --git a/sdk/identity/Azure.Identity/src/Credentials/DeviceCodeCredential.cs b/sdk/identity/Azure.Identity/src/Credentials/DeviceCodeCredential.cs
index 665ca7169243..bc80bb78c9d1 100644
--- a/sdk/identity/Azure.Identity/src/Credentials/DeviceCodeCredential.cs
+++ b/sdk/identity/Azure.Identity/src/Credentials/DeviceCodeCredential.cs
@@ -18,6 +18,7 @@ namespace Azure.Identity
public class DeviceCodeCredential : TokenCredential
{
private readonly string _tenantId;
+ private readonly string[] _additionallyAllowedTenantIds;
internal MsalPublicClient Client { get; set; }
internal string ClientId { get; }
internal bool DisableAutomaticAuthentication { get; }
@@ -92,6 +93,7 @@ internal DeviceCodeCredential(Func devi
ClientId,
AzureAuthorityHosts.GetDeviceCodeRedirectUri(Pipeline.AuthorityHost).AbsoluteUri,
options);
+ _additionallyAllowedTenantIds = TenantIdResolver.ResolveAddionallyAllowedTenantIds(options?.AdditionallyAllowedTenantsCore);
}
///
@@ -197,11 +199,12 @@ private async ValueTask GetTokenImplAsync(bool async, TokenRequestC
{
Exception inner = null;
+ var tenantId = TenantIdResolver.Resolve(_tenantId, requestContext, _additionallyAllowedTenantIds);
+
if (Record != null)
{
try
{
- var tenantId = TenantIdResolver.Resolve(_tenantId, requestContext);
AuthenticationResult result = await Client
.AcquireTokenSilentAsync(requestContext.Scopes, requestContext.Claims, Record, tenantId, async, cancellationToken)
.ConfigureAwait(false);
diff --git a/sdk/identity/Azure.Identity/src/Credentials/DeviceCodeCredentialOptions.cs b/sdk/identity/Azure.Identity/src/Credentials/DeviceCodeCredentialOptions.cs
index a47f2b06fd6d..d160bc3f6b53 100644
--- a/sdk/identity/Azure.Identity/src/Credentials/DeviceCodeCredentialOptions.cs
+++ b/sdk/identity/Azure.Identity/src/Credentials/DeviceCodeCredentialOptions.cs
@@ -2,6 +2,7 @@
// Licensed under the MIT License.
using System;
+using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
@@ -29,6 +30,13 @@ public string TenantId
set { _tenantId = Validations.ValidateTenantId(value, allowNull: true); }
}
+ ///
+ /// Specifies tenants in addition to the specified for which the credential may acquire tokens.
+ /// Add the wildcard value "*" to allow the credential to acquire tokens for any tenant the logged in account can access.
+ /// If no value is specified for , this option will have no effect, and the credential will acquire tokens for any requested tenant.
+ ///
+ public IList AdditionallyAllowedTenants => AdditionallyAllowedTenantsCore;
+
///
/// The client ID of the application used to authenticate the user. If not specified the user will be authenticated with an Azure development application.
///
diff --git a/sdk/identity/Azure.Identity/src/Credentials/EnvironmentCredential.cs b/sdk/identity/Azure.Identity/src/Credentials/EnvironmentCredential.cs
index 24cc6cccb273..f3f53d9a4f28 100644
--- a/sdk/identity/Azure.Identity/src/Credentials/EnvironmentCredential.cs
+++ b/sdk/identity/Azure.Identity/src/Credentials/EnvironmentCredential.cs
@@ -7,6 +7,8 @@
using System.Threading;
using System.Threading.Tasks;
using Azure.Core.Pipeline;
+using System.Linq;
+using System.Collections.Generic;
namespace Azure.Identity
{
@@ -65,6 +67,15 @@ internal EnvironmentCredential(CredentialPipeline pipeline, TokenCredentialOptio
string username = EnvironmentVariables.Username;
string password = EnvironmentVariables.Password;
+ // Since the AdditionallyAllowedTenantsCore is internal it cannot be set by the application.
+ // Currently this is only set by the DefaultAzureCredential where it will default to the value
+ // of EnvironmentVariables.AdditionallyAllowedTenants, but can also be altered by the application.
+ // In either case we don't want to alter it.
+ if (_options.AdditionallyAllowedTenantsCore.Count == 0)
+ {
+ _options.AdditionallyAllowedTenantsCore = EnvironmentVariables.AdditionallyAllowedTenants;
+ }
+
if (!string.IsNullOrEmpty(tenantId) && !string.IsNullOrEmpty(clientId))
{
if (!string.IsNullOrEmpty(clientSecret))
@@ -85,6 +96,7 @@ internal EnvironmentCredential(CredentialPipeline pipeline, TokenCredentialOptio
AuthorityHost = _options.AuthorityHost,
IsLoggingPIIEnabled = _options.IsLoggingPIIEnabled,
Transport = _options.Transport,
+ AdditionallyAllowedTenantsCore = new List(_options.AdditionallyAllowedTenantsCore),
SendCertificateChain = sendCertificateChain
};
Credential = new ClientCertificateCredential(tenantId, clientId, clientCertificatePath, clientCertificateCredentialOptions, _pipeline, null);
diff --git a/sdk/identity/Azure.Identity/src/Credentials/InteractiveBrowserCredential.cs b/sdk/identity/Azure.Identity/src/Credentials/InteractiveBrowserCredential.cs
index 66b7c8bb2a0a..e5519b85f45a 100644
--- a/sdk/identity/Azure.Identity/src/Credentials/InteractiveBrowserCredential.cs
+++ b/sdk/identity/Azure.Identity/src/Credentials/InteractiveBrowserCredential.cs
@@ -17,7 +17,8 @@ namespace Azure.Identity
///
public class InteractiveBrowserCredential : TokenCredential
{
- private readonly string _tenantId;
+ internal string TenantId { get; }
+ internal string[] AdditionallyAllowedTenantIds { get; }
internal string ClientId { get; }
internal string LoginHint { get; }
internal MsalPublicClient Client { get; }
@@ -76,11 +77,12 @@ internal InteractiveBrowserCredential(string tenantId, string clientId, TokenCre
Argument.AssertNotNull(clientId, nameof(clientId));
ClientId = clientId;
- _tenantId = tenantId;
+ TenantId = tenantId;
Pipeline = pipeline ?? CredentialPipeline.GetInstance(options);
LoginHint = (options as InteractiveBrowserCredentialOptions)?.LoginHint;
var redirectUrl = (options as InteractiveBrowserCredentialOptions)?.RedirectUri?.AbsoluteUri ?? Constants.DefaultRedirectUrl;
Client = client ?? new MsalPublicClient(Pipeline, tenantId, clientId, redirectUrl, options);
+ AdditionallyAllowedTenantIds = TenantIdResolver.ResolveAddionallyAllowedTenantIds(options?.AdditionallyAllowedTenantsCore);
}
///
@@ -177,11 +179,12 @@ private async ValueTask GetTokenImplAsync(bool async, TokenRequestC
{
Exception inner = null;
+ var tenantId = TenantIdResolver.Resolve(TenantId ?? Record?.TenantId, requestContext, AdditionallyAllowedTenantIds);
+
if (Record != null)
{
try
{
- var tenantId = TenantIdResolver.Resolve(_tenantId ?? Record.TenantId, requestContext);
AuthenticationResult result = await Client
.AcquireTokenSilentAsync(requestContext.Scopes, requestContext.Claims, Record, tenantId, async, cancellationToken)
.ConfigureAwait(false);
@@ -215,7 +218,7 @@ private async Task GetTokenViaBrowserLoginAsync(TokenRequestContext
_ => Prompt.NoPrompt
};
- var tenantId = TenantIdResolver.Resolve(_tenantId ?? Record?.TenantId, context);
+ var tenantId = TenantIdResolver.Resolve(TenantId ?? Record?.TenantId, context, AdditionallyAllowedTenantIds);
AuthenticationResult result = await Client
.AcquireTokenInteractiveAsync(context.Scopes, context.Claims, prompt, LoginHint, tenantId, async, cancellationToken)
.ConfigureAwait(false);
diff --git a/sdk/identity/Azure.Identity/src/Credentials/InteractiveBrowserCredentialOptions.cs b/sdk/identity/Azure.Identity/src/Credentials/InteractiveBrowserCredentialOptions.cs
index 4c6b59531022..74798f6e8d83 100644
--- a/sdk/identity/Azure.Identity/src/Credentials/InteractiveBrowserCredentialOptions.cs
+++ b/sdk/identity/Azure.Identity/src/Credentials/InteractiveBrowserCredentialOptions.cs
@@ -2,6 +2,7 @@
// Licensed under the MIT License.
using System;
+using System.Collections.Generic;
using System.Threading;
namespace Azure.Identity
@@ -28,6 +29,13 @@ public string TenantId
set { _tenantId = Validations.ValidateTenantId(value, allowNull: true); }
}
+ ///
+ /// Specifies tenants in addition to the specified for which the credential may acquire tokens.
+ /// Add the wildcard value "*" to allow the credential to acquire tokens for any tenant the logged in account can access.
+ /// If no value is specified for , this option will have no effect, and the credential will acquire tokens for any requested tenant.
+ ///
+ public IList AdditionallyAllowedTenants => AdditionallyAllowedTenantsCore;
+
///
/// The client ID of the application used to authenticate the user. If not specified the user will be authenticated with an Azure development application.
///
diff --git a/sdk/identity/Azure.Identity/src/Credentials/ManagedIdentityCredential.cs b/sdk/identity/Azure.Identity/src/Credentials/ManagedIdentityCredential.cs
index 2ef15ee10302..94b32042fc2d 100644
--- a/sdk/identity/Azure.Identity/src/Credentials/ManagedIdentityCredential.cs
+++ b/sdk/identity/Azure.Identity/src/Credentials/ManagedIdentityCredential.cs
@@ -24,7 +24,7 @@ public class ManagedIdentityCredential : TokenCredential
internal const string MsiUnavailableError = "No managed identity endpoint found.";
private readonly CredentialPipeline _pipeline;
- private readonly ManagedIdentityClient _client;
+ internal ManagedIdentityClient Client { get; }
private readonly string _clientId;
private readonly bool _logAccountDetails;
@@ -82,7 +82,7 @@ internal ManagedIdentityCredential(ResourceIdentifier resourceId, CredentialPipe
internal ManagedIdentityCredential(ManagedIdentityClient client)
{
_pipeline = client.Pipeline;
- _client = client;
+ Client = client;
}
///
@@ -113,7 +113,7 @@ private async ValueTask GetTokenImplAsync(bool async, TokenRequestC
try
{
- AccessToken result = await _client.AuthenticateAsync(async, requestContext, cancellationToken).ConfigureAwait(false);
+ AccessToken result = await Client.AuthenticateAsync(async, requestContext, cancellationToken).ConfigureAwait(false);
if (_logAccountDetails)
{
var accountDetails = TokenHelper.ParseAccountInfoFromToken(result.Token);
diff --git a/sdk/identity/Azure.Identity/src/Credentials/OnBehalfOfCredential.cs b/sdk/identity/Azure.Identity/src/Credentials/OnBehalfOfCredential.cs
index 2d585b7e732d..69b8f255b74b 100644
--- a/sdk/identity/Azure.Identity/src/Credentials/OnBehalfOfCredential.cs
+++ b/sdk/identity/Azure.Identity/src/Credentials/OnBehalfOfCredential.cs
@@ -22,6 +22,7 @@ public class OnBehalfOfCredential : TokenCredential
private readonly string _clientId;
private readonly string _clientSecret;
private readonly UserAssertion _userAssertion;
+ private readonly string[] _additionallyAllowedTenantIds;
///
/// Protected constructor for mocking.
@@ -125,6 +126,8 @@ internal OnBehalfOfCredential(
certificateProvider,
options.SendCertificateChain,
options);
+
+ _additionallyAllowedTenantIds = TenantIdResolver.ResolveAddionallyAllowedTenantIds(options?.AdditionallyAllowedTenantsCore);
}
internal OnBehalfOfCredential(
@@ -146,6 +149,8 @@ internal OnBehalfOfCredential(
_clientSecret = clientSecret;
_userAssertion = new UserAssertion(userAssertion);
_client = client ?? new MsalConfidentialClient(_pipeline, _tenantId, _clientId, _clientSecret, null, options);
+
+ _additionallyAllowedTenantIds = TenantIdResolver.ResolveAddionallyAllowedTenantIds(options?.AdditionallyAllowedTenants);
}
///
@@ -162,7 +167,7 @@ internal async ValueTask GetTokenInternalAsync(TokenRequestContext
try
{
- var tenantId = TenantIdResolver.Resolve(_tenantId, requestContext);
+ var tenantId = TenantIdResolver.Resolve(_tenantId, requestContext, _additionallyAllowedTenantIds);
AuthenticationResult result = await _client
.AcquireTokenOnBehalfOfAsync(requestContext.Scopes, tenantId, _userAssertion, async, cancellationToken)
diff --git a/sdk/identity/Azure.Identity/src/Credentials/OnBehalfOfCredentialOptions.cs b/sdk/identity/Azure.Identity/src/Credentials/OnBehalfOfCredentialOptions.cs
index cd179c191498..26470e5d788c 100644
--- a/sdk/identity/Azure.Identity/src/Credentials/OnBehalfOfCredentialOptions.cs
+++ b/sdk/identity/Azure.Identity/src/Credentials/OnBehalfOfCredentialOptions.cs
@@ -1,6 +1,8 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
+using System.Collections.Generic;
+
namespace Azure.Identity
{
///
@@ -17,5 +19,10 @@ public class OnBehalfOfCredentialOptions : TokenCredentialOptions, ITokenCacheOp
/// Will include x5c header in client claims when acquiring a token to enable subject name / issuer based authentication for the .
///
public bool SendCertificateChain { get; set; }
+
+ ///
+ /// For multi-tenant applications, specifies additional tenants for which the credential may acquire tokens. Add the wildcard value "*" to allow the credential to acquire tokens for any tenant in which the application is installed.
+ ///
+ public IList AdditionallyAllowedTenants => AdditionallyAllowedTenantsCore;
}
}
diff --git a/sdk/identity/Azure.Identity/src/Credentials/SharedTokenCacheCredential.cs b/sdk/identity/Azure.Identity/src/Credentials/SharedTokenCacheCredential.cs
index 83301a0c0219..abe9e1eb00f0 100644
--- a/sdk/identity/Azure.Identity/src/Credentials/SharedTokenCacheCredential.cs
+++ b/sdk/identity/Azure.Identity/src/Credentials/SharedTokenCacheCredential.cs
@@ -25,12 +25,13 @@ public class SharedTokenCacheCredential : TokenCredential
internal const string MultipleMatchingAccountsInCacheMessage = "SharedTokenCacheCredential authentication unavailable. Multiple accounts matching the specified{0}{1} were found in the cache.";
private static readonly SharedTokenCacheCredentialOptions s_DefaultCacheOptions = new SharedTokenCacheCredentialOptions();
private readonly CredentialPipeline _pipeline;
- private readonly string _tenantId;
- private readonly string _username;
private readonly bool _skipTenantValidation;
private readonly AuthenticationRecord _record;
private readonly AsyncLockWithValue _accountAsyncLock;
+ internal string TenantId { get; }
+ internal string Username { get; }
+
internal MsalPublicClient Client { get; }
///
@@ -68,8 +69,8 @@ internal SharedTokenCacheCredential(string tenantId, string username, TokenCrede
internal SharedTokenCacheCredential(string tenantId, string username, TokenCredentialOptions options, CredentialPipeline pipeline, MsalPublicClient client)
{
- _tenantId = tenantId;
- _username = username;
+ TenantId = tenantId;
+ Username = username;
var sharedTokenCredentialOptions = options as SharedTokenCacheCredentialOptions;
_skipTenantValidation = sharedTokenCredentialOptions?.EnableGuestTenantAuthentication ?? false;
_record = sharedTokenCredentialOptions?.AuthenticationRecord;
@@ -111,7 +112,7 @@ private async ValueTask GetTokenImplAsync(bool async, TokenRequestC
try
{
- var tenantId = TenantIdResolver.Resolve(_tenantId, requestContext);
+ var tenantId = TenantIdResolver.Resolve(TenantId, requestContext, TenantIdResolver.AllTenants);
IAccount account = await GetAccountAsync(tenantId, async, cancellationToken).ConfigureAwait(false);
AuthenticationResult result = await Client.AcquireTokenSilentAsync(requestContext.Scopes, requestContext.Claims, account, tenantId, async, cancellationToken).ConfigureAwait(false);
return scope.Succeeded(new AccessToken(result.AccessToken, result.ExpiresOn));
@@ -120,7 +121,7 @@ private async ValueTask GetTokenImplAsync(bool async, TokenRequestC
{
throw scope.FailWrapAndThrow(
new CredentialUnavailableException(
- $"{nameof(SharedTokenCacheCredential)} authentication unavailable. Token acquisition failed for user {_username}. Ensure that you have authenticated with a developer tool that supports Azure single sign on.",
+ $"{nameof(SharedTokenCacheCredential)} authentication unavailable. Token acquisition failed for user {Username}. Ensure that you have authenticated with a developer tool that supports Azure single sign on.",
ex));
}
catch (Exception e)
@@ -155,7 +156,7 @@ private async ValueTask GetAccountAsync(string tenantId, bool async, C
// filter the accounts to those matching the specified user and tenant
List filteredAccounts = accounts.Where(a =>
// if _username is specified it must match the account
- (string.IsNullOrEmpty(_username) || string.Compare(a.Username, _username, StringComparison.OrdinalIgnoreCase) == 0)
+ (string.IsNullOrEmpty(Username) || string.Compare(a.Username, Username, StringComparison.OrdinalIgnoreCase) == 0)
&&
// if _skipTenantValidation is false and _tenantId is specified it must match the account
(_skipTenantValidation || string.IsNullOrEmpty(tenantId) || string.Compare(a.HomeAccountId?.TenantId, tenantId, StringComparison.OrdinalIgnoreCase) == 0)
@@ -181,13 +182,13 @@ private async ValueTask GetAccountAsync(string tenantId, bool async, C
private string GetCredentialUnavailableMessage(List filteredAccounts)
{
- if (string.IsNullOrEmpty(_username) && string.IsNullOrEmpty(_tenantId))
+ if (string.IsNullOrEmpty(Username) && string.IsNullOrEmpty(TenantId))
{
return string.Format(CultureInfo.InvariantCulture, MultipleAccountsInCacheMessage);
}
- var usernameStr = string.IsNullOrEmpty(_username) ? string.Empty : $" username: {_username}";
- var tenantIdStr = string.IsNullOrEmpty(_tenantId) ? string.Empty : $" tenantId: {_tenantId}";
+ var usernameStr = string.IsNullOrEmpty(Username) ? string.Empty : $" username: {Username}";
+ var tenantIdStr = string.IsNullOrEmpty(TenantId) ? string.Empty : $" tenantId: {TenantId}";
if (filteredAccounts.Count == 0)
{
diff --git a/sdk/identity/Azure.Identity/src/Credentials/TokenCredentialOptions.cs b/sdk/identity/Azure.Identity/src/Credentials/TokenCredentialOptions.cs
index 00727e83fd69..272e633d22da 100644
--- a/sdk/identity/Azure.Identity/src/Credentials/TokenCredentialOptions.cs
+++ b/sdk/identity/Azure.Identity/src/Credentials/TokenCredentialOptions.cs
@@ -2,7 +2,11 @@
// Licensed under the MIT License.
using System;
+using System.Collections.Generic;
+using System.Net;
+using System.Runtime.CompilerServices;
using Azure.Core;
+using Azure.Core.Pipeline;
namespace Azure.Identity
{
@@ -37,6 +41,11 @@ public Uri AuthorityHost
///
internal bool IsLoggingPIIEnabled { get; set; }
+ ///
+ /// For multi-tenant applications, specifies additional tenants for which the credential may acquire tokens. Add the wildcard value "*" to allow the credential to acquire tokens for any tenant in which the application is installed.
+ ///
+ internal List AdditionallyAllowedTenantsCore { get; set; } = new List();
+
///
/// Gets the credential diagnostic options.
///
diff --git a/sdk/identity/Azure.Identity/src/Credentials/UsernamePasswordCredential.cs b/sdk/identity/Azure.Identity/src/Credentials/UsernamePasswordCredential.cs
index 703535def508..6c79ac502518 100644
--- a/sdk/identity/Azure.Identity/src/Credentials/UsernamePasswordCredential.cs
+++ b/sdk/identity/Azure.Identity/src/Credentials/UsernamePasswordCredential.cs
@@ -27,6 +27,7 @@ public class UsernamePasswordCredential : TokenCredential
private readonly SecureString _password;
private AuthenticationRecord _record;
private readonly string _tenantId;
+ private readonly string[] _additionallyAllowedTenantIds;
internal MsalPublicClient Client { get; }
///
@@ -92,6 +93,8 @@ internal UsernamePasswordCredential(
_clientId = clientId;
_pipeline = pipeline ?? CredentialPipeline.GetInstance(options);
Client = client ?? new MsalPublicClient(_pipeline, tenantId, clientId, null, options);
+
+ _additionallyAllowedTenantIds = TenantIdResolver.ResolveAddionallyAllowedTenantIds(options?.AdditionallyAllowedTenantsCore);
}
///
@@ -175,7 +178,7 @@ private async Task AuthenticateImplAsync(bool async, Token
using CredentialDiagnosticScope scope = _pipeline.StartGetTokenScope($"{nameof(UsernamePasswordCredential)}.{nameof(Authenticate)}", requestContext);
try
{
- var tenantId = TenantIdResolver.Resolve(_tenantId, requestContext);
+ var tenantId = TenantIdResolver.Resolve(_tenantId, requestContext, _additionallyAllowedTenantIds);
AuthenticationResult result = await Client
.AcquireTokenByUsernamePasswordAsync(requestContext.Scopes, requestContext.Claims, _username, _password, tenantId, async, cancellationToken)
@@ -198,7 +201,7 @@ private async Task GetTokenImplAsync(bool async, TokenRequestContex
AuthenticationResult result;
if (_record != null)
{
- var tenantId = TenantIdResolver.Resolve(_tenantId, requestContext);
+ var tenantId = TenantIdResolver.Resolve(_tenantId, requestContext, _additionallyAllowedTenantIds);
try
{
result = await Client.AcquireTokenSilentAsync(requestContext.Scopes, requestContext.Claims, _record, tenantId, async, cancellationToken)
diff --git a/sdk/identity/Azure.Identity/src/Credentials/UsernamePasswordCredentialOptions.cs b/sdk/identity/Azure.Identity/src/Credentials/UsernamePasswordCredentialOptions.cs
index 4479d8edbd10..3a4dd2752d31 100644
--- a/sdk/identity/Azure.Identity/src/Credentials/UsernamePasswordCredentialOptions.cs
+++ b/sdk/identity/Azure.Identity/src/Credentials/UsernamePasswordCredentialOptions.cs
@@ -1,6 +1,8 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
+using System.Collections.Generic;
+
namespace Azure.Identity
{
///
@@ -12,5 +14,10 @@ public class UsernamePasswordCredentialOptions : TokenCredentialOptions, ITokenC
/// Specifies the to be used by the credential. If not options are specified, the token cache will not be persisted to disk.
///
public TokenCachePersistenceOptions TokenCachePersistenceOptions { get; set; }
+
+ ///
+ /// For multi-tenant applications, specifies additional tenants for which the credential may acquire tokens. Add the wildcard value "*" to allow the credential to acquire tokens for any tenant in which the application is installed.
+ ///
+ public IList AdditionallyAllowedTenants => AdditionallyAllowedTenantsCore;
}
}
diff --git a/sdk/identity/Azure.Identity/src/Credentials/VisualStudioCodeCredential.cs b/sdk/identity/Azure.Identity/src/Credentials/VisualStudioCodeCredential.cs
index 5eabd014e7a4..4cf75ebee270 100644
--- a/sdk/identity/Azure.Identity/src/Credentials/VisualStudioCodeCredential.cs
+++ b/sdk/identity/Azure.Identity/src/Credentials/VisualStudioCodeCredential.cs
@@ -24,7 +24,8 @@ public class VisualStudioCodeCredential : TokenCredential
private readonly IVisualStudioCodeAdapter _vscAdapter;
private readonly IFileSystemService _fileSystem;
private readonly CredentialPipeline _pipeline;
- private readonly string _tenantId;
+ internal string TenantId { get; }
+ internal string[] AdditionallyAllowedTenantIds;
private const string _commonTenant = "common";
private const string Troubleshooting = "See the troubleshooting guide for more information. https://aka.ms/azsdk/net/identity/vscodecredential/troubleshoot";
internal MsalPublicClient Client { get; }
@@ -43,11 +44,12 @@ public VisualStudioCodeCredential(VisualStudioCodeCredentialOptions options) : t
internal VisualStudioCodeCredential(VisualStudioCodeCredentialOptions options, CredentialPipeline pipeline, MsalPublicClient client, IFileSystemService fileSystem,
IVisualStudioCodeAdapter vscAdapter)
{
- _tenantId = options?.TenantId ?? _commonTenant;
+ TenantId = options?.TenantId;
_pipeline = pipeline ?? CredentialPipeline.GetInstance(options);
- Client = client ?? new MsalPublicClient(_pipeline, options?.TenantId, ClientId, null, options);
+ Client = client ?? new MsalPublicClient(_pipeline, TenantId, ClientId, null, options);
_fileSystem = fileSystem ?? FileSystemService.Default;
_vscAdapter = vscAdapter ?? GetVscAdapter();
+ AdditionallyAllowedTenantIds = TenantIdResolver.ResolveAddionallyAllowedTenantIds(options?.AdditionallyAllowedTenantsCore);
}
///
@@ -65,7 +67,8 @@ private async ValueTask GetTokenImplAsync(TokenRequestContext reque
try
{
GetUserSettings(out var tenant, out var environmentName);
- var tenantId = TenantIdResolver.Resolve(tenant, requestContext);
+
+ var tenantId = TenantIdResolver.Resolve(TenantId, requestContext, AdditionallyAllowedTenantIds) ?? tenant;
if (string.Equals(tenantId, Constants.AdfsTenantId, StringComparison.Ordinal))
{
@@ -128,7 +131,7 @@ private static bool IsRefreshTokenString(string str)
private void GetUserSettings(out string tenant, out string environmentName)
{
var path = _vscAdapter.GetUserSettingsPath();
- tenant = _tenantId;
+ tenant = TenantId ?? _commonTenant;
environmentName = "AzureCloud";
try
diff --git a/sdk/identity/Azure.Identity/src/Credentials/VisualStudioCodeCredentialOptions.cs b/sdk/identity/Azure.Identity/src/Credentials/VisualStudioCodeCredentialOptions.cs
index 863ac65b7757..52f7e9b3f7ac 100644
--- a/sdk/identity/Azure.Identity/src/Credentials/VisualStudioCodeCredentialOptions.cs
+++ b/sdk/identity/Azure.Identity/src/Credentials/VisualStudioCodeCredentialOptions.cs
@@ -15,12 +15,19 @@ public class VisualStudioCodeCredentialOptions : TokenCredentialOptions
private string _tenantId;
///
- /// The tenant ID the user will be authenticated to. If not specified the user will be authenticated to the tenant the user originally authenticated to via the Visual Studio Code Azure Account plugin.
+ /// The tenant ID the user will be authenticated to. If not specified, the user will be authenticated to any requested tenant, and by default to the tenant the user originally authenticated to via the Visual Studio Code Azure Account extension.
///
public string TenantId
{
get { return _tenantId; }
set { _tenantId = Validations.ValidateTenantId(value, allowNull: true); }
}
+
+ ///
+ /// Specifies tenants in addition to the specified for which the credential may acquire tokens.
+ /// Add the wildcard value "*" to allow the credential to acquire tokens for any tenant the logged in account can access.
+ /// If no value is specified for , this option will have no effect, and the credential will acquire tokens for any requested tenant.
+ ///
+ public IList AdditionallyAllowedTenants => AdditionallyAllowedTenantsCore;
}
}
diff --git a/sdk/identity/Azure.Identity/src/Credentials/VisualStudioCredential.cs b/sdk/identity/Azure.Identity/src/Credentials/VisualStudioCredential.cs
index e9c82145fa1d..63e4ba0430ac 100644
--- a/sdk/identity/Azure.Identity/src/Credentials/VisualStudioCredential.cs
+++ b/sdk/identity/Azure.Identity/src/Credentials/VisualStudioCredential.cs
@@ -27,7 +27,8 @@ public class VisualStudioCredential : TokenCredential
private const string TenantArgumentName = "--tenant";
private readonly CredentialPipeline _pipeline;
- private readonly string _tenantId;
+ internal string TenantId { get; }
+ internal string[] AdditionallyAllowedTenantIds { get; }
private readonly IFileSystemService _fileSystem;
private readonly IProcessService _processService;
private readonly bool _logPII;
@@ -42,7 +43,7 @@ public VisualStudioCredential() : this(null) { }
/// Creates a new instance of the with the specified options.
///
/// Options for configuring the credential.
- public VisualStudioCredential(VisualStudioCredentialOptions options) : this(options?.TenantId, CredentialPipeline.GetInstance(options), default, default)
+ public VisualStudioCredential(VisualStudioCredentialOptions options) : this(options?.TenantId, CredentialPipeline.GetInstance(options), default, default, options)
{
}
@@ -50,10 +51,11 @@ internal VisualStudioCredential(string tenantId, CredentialPipeline pipeline, IF
{
_logPII = options?.IsLoggingPIIEnabled ?? false;
_logAccountDetails = options?.Diagnostics?.IsAccountIdentifierLoggingEnabled ?? false;
- _tenantId = tenantId;
+ TenantId = tenantId;
_pipeline = pipeline ?? CredentialPipeline.GetInstance(null);
_fileSystem = fileSystem ?? FileSystemService.Default;
_processService = processService ?? ProcessService.Default;
+ AdditionallyAllowedTenantIds = TenantIdResolver.ResolveAddionallyAllowedTenantIds(options?.AdditionallyAllowedTenantsCore);
}
///
@@ -70,7 +72,7 @@ private async ValueTask GetTokenImplAsync(TokenRequestContext reque
try
{
- if (string.Equals(_tenantId, Constants.AdfsTenantId, StringComparison.Ordinal))
+ if (string.Equals(TenantId, Constants.AdfsTenantId, StringComparison.Ordinal))
{
throw new CredentialUnavailableException("VisualStudioCredential authentication unavailable. ADFS tenant/authorities are not supported.");
}
@@ -91,7 +93,7 @@ private async ValueTask GetTokenImplAsync(TokenRequestContext reque
if (_logAccountDetails)
{
var accountDetails = TokenHelper.ParseAccountInfoFromToken(accessToken.Token);
- AzureIdentityEventSource.Singleton.AuthenticatedAccountDetails(accountDetails.ClientId, accountDetails.TenantId ?? _tenantId, accountDetails.Upn, accountDetails.ObjectId);
+ AzureIdentityEventSource.Singleton.AuthenticatedAccountDetails(accountDetails.ClientId, accountDetails.TenantId ?? TenantId, accountDetails.Upn, accountDetails.ObjectId);
}
return scope.Succeeded(accessToken);
@@ -174,7 +176,7 @@ private List GetProcessStartInfos(VisualStudioTokenProvider[]
arguments.Clear();
arguments.Append(ResourceArgumentName).Append(' ').Append(resource);
- var tenantId = TenantIdResolver.Resolve(_tenantId, requestContext);
+ var tenantId = TenantIdResolver.Resolve(TenantId, requestContext, AdditionallyAllowedTenantIds);
if (tenantId != default)
{
arguments.Append(' ').Append(TenantArgumentName).Append(' ').Append(tenantId);
diff --git a/sdk/identity/Azure.Identity/src/Credentials/VisualStudioCredentialOptions.cs b/sdk/identity/Azure.Identity/src/Credentials/VisualStudioCredentialOptions.cs
index 1cd789bb3d33..aaf5b93b56df 100644
--- a/sdk/identity/Azure.Identity/src/Credentials/VisualStudioCredentialOptions.cs
+++ b/sdk/identity/Azure.Identity/src/Credentials/VisualStudioCredentialOptions.cs
@@ -1,6 +1,8 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
+using System.Collections.Generic;
+
namespace Azure.Identity
{
///
@@ -11,12 +13,19 @@ public class VisualStudioCredentialOptions : TokenCredentialOptions
private string _tenantId;
///
- /// The tenant ID the user will be authenticated to. If not specified the user will be authenticated to their home tenant.
+ /// The tenant ID the credential will be authenticated to by default. If not specified, the credential will authenticate to any requested tenant, and will default to the tenant the user originally authenticated to via the Visual Studio Azure Service Account dialog.
///
public string TenantId
{
get { return _tenantId; }
set { _tenantId = Validations.ValidateTenantId(value, allowNull: true); }
}
+
+ ///
+ /// Specifies tenants in addition to the specified for which the credential may acquire tokens.
+ /// Add the wildcard value "*" to allow the credential to acquire tokens for any tenant the logged in account can access.
+ /// If no value is specified for , this option will have no effect, and the credential will acquire tokens for any requested tenant.
+ ///
+ public IList AdditionallyAllowedTenants => AdditionallyAllowedTenantsCore;
}
}
diff --git a/sdk/identity/Azure.Identity/src/DefaultAzureCredentialFactory.cs b/sdk/identity/Azure.Identity/src/DefaultAzureCredentialFactory.cs
index 26d002a3c573..b186cba1e903 100644
--- a/sdk/identity/Azure.Identity/src/DefaultAzureCredentialFactory.cs
+++ b/sdk/identity/Azure.Identity/src/DefaultAzureCredentialFactory.cs
@@ -2,74 +2,195 @@
// Licensed under the MIT License.
using System;
+using System.Linq;
using Azure.Core;
namespace Azure.Identity
{
internal class DefaultAzureCredentialFactory
{
- public DefaultAzureCredentialFactory(TokenCredentialOptions options)
- : this(CredentialPipeline.GetInstance(options))
+ private static readonly TokenCredential[] s_defaultCredentialChain = new DefaultAzureCredentialFactory(new DefaultAzureCredentialOptions()).CreateCredentialChain();
+ private bool _useDefaultCredentialChain;
+
+ public DefaultAzureCredentialFactory(DefaultAzureCredentialOptions options)
+ : this(options, CredentialPipeline.GetInstance(options))
{ }
- protected DefaultAzureCredentialFactory(CredentialPipeline pipeline)
+ protected DefaultAzureCredentialFactory(DefaultAzureCredentialOptions options, CredentialPipeline pipeline)
{
Pipeline = pipeline;
+
+ _useDefaultCredentialChain = options == null;
+
+ Options = options?.ShallowClone() ?? new DefaultAzureCredentialOptions();
+
+ Options.AdditionallyAllowedTenantsCore = Options.AdditionallyAllowedTenants.ToList();
}
+ public DefaultAzureCredentialOptions Options { get; }
public CredentialPipeline Pipeline { get; }
+ public TokenCredential[] CreateCredentialChain()
+ {
+ if (_useDefaultCredentialChain)
+ {
+ return s_defaultCredentialChain;
+ }
+
+ int i = 0;
+ TokenCredential[] chain = new TokenCredential[8];
+
+ if (!Options.ExcludeEnvironmentCredential)
+ {
+ chain[i++] = CreateEnvironmentCredential();
+ }
+
+ if (!Options.ExcludeManagedIdentityCredential)
+ {
+ chain[i++] = CreateManagedIdentityCredential();
+ }
+
+ if (!Options.ExcludeSharedTokenCacheCredential)
+ {
+ chain[i++] = CreateSharedTokenCacheCredential();
+ }
+
+ if (!Options.ExcludeVisualStudioCredential)
+ {
+ chain[i++] = CreateVisualStudioCredential();
+ }
+
+ if (!Options.ExcludeVisualStudioCodeCredential)
+ {
+ chain[i++] = CreateVisualStudioCodeCredential();
+ }
+
+ if (!Options.ExcludeAzureCliCredential)
+ {
+ chain[i++] = CreateAzureCliCredential();
+ }
+
+ if (!Options.ExcludeAzurePowerShellCredential)
+ {
+ chain[i++] = CreateAzurePowerShellCredential();
+ }
+
+ if (!Options.ExcludeInteractiveBrowserCredential)
+ {
+ chain[i++] = CreateInteractiveBrowserCredential();
+ }
+
+ if (i == 0)
+ {
+ throw new ArgumentException("At least one credential type must be included in the authentication flow.", "options");
+ }
+
+ return chain;
+ }
+
public virtual TokenCredential CreateEnvironmentCredential()
{
- return new EnvironmentCredential(Pipeline);
+ return new EnvironmentCredential(Pipeline, Options);
}
- public virtual TokenCredential CreateManagedIdentityCredential(DefaultAzureCredentialOptions options)
+ public virtual TokenCredential CreateManagedIdentityCredential()
{
return new ManagedIdentityCredential(new ManagedIdentityClient(
new ManagedIdentityClientOptions
{
- ResourceIdentifier = options.ManagedIdentityResourceId,
- ClientId = options.ManagedIdentityClientId,
+ ResourceIdentifier = Options.ManagedIdentityResourceId,
+ ClientId = Options.ManagedIdentityClientId,
Pipeline = Pipeline,
- Options = options,
+ Options = Options,
InitialImdsConnectionTimeout = TimeSpan.FromSeconds(1)
})
);
}
- public virtual TokenCredential CreateSharedTokenCacheCredential(string tenantId, string username)
+ public virtual TokenCredential CreateSharedTokenCacheCredential()
{
- return new SharedTokenCacheCredential(tenantId, username, null, Pipeline);
+ return new SharedTokenCacheCredential(Options.SharedTokenCacheTenantId, Options.SharedTokenCacheUsername, Options, Pipeline);
}
- public virtual TokenCredential CreateInteractiveBrowserCredential(string tenantId, string clientId)
+ public virtual TokenCredential CreateInteractiveBrowserCredential()
{
+ var options = new InteractiveBrowserCredentialOptions
+ {
+ TokenCachePersistenceOptions = new TokenCachePersistenceOptions(),
+ AuthorityHost = Options.AuthorityHost,
+ TenantId = Options.InteractiveBrowserTenantId
+ };
+
+ foreach (var addlTenant in Options.AdditionallyAllowedTenants)
+ {
+ options.AdditionallyAllowedTenants.Add(addlTenant);
+ }
+
return new InteractiveBrowserCredential(
- tenantId,
- clientId ?? Constants.DeveloperSignOnClientId,
- new InteractiveBrowserCredentialOptions { TokenCachePersistenceOptions = new TokenCachePersistenceOptions() },
+ Options.InteractiveBrowserTenantId,
+ Options.InteractiveBrowserCredentialClientId ?? Constants.DeveloperSignOnClientId,
+ options,
Pipeline);
}
public virtual TokenCredential CreateAzureCliCredential()
{
- return new AzureCliCredential(Pipeline, default);
+ var options = new AzureCliCredentialOptions
+ {
+ TenantId = Options.TenantId,
+ };
+
+ foreach (var addlTenant in Options.AdditionallyAllowedTenants)
+ {
+ options.AdditionallyAllowedTenants.Add(addlTenant);
+ }
+
+ return new AzureCliCredential(Pipeline, default, options);
}
- public virtual TokenCredential CreateVisualStudioCredential(string tenantId)
+ public virtual TokenCredential CreateVisualStudioCredential()
{
- return new VisualStudioCredential(tenantId, Pipeline, default, default);
+ var options = new VisualStudioCredentialOptions
+ {
+ TenantId = Options.VisualStudioTenantId,
+ };
+
+ foreach (var addlTenant in Options.AdditionallyAllowedTenants)
+ {
+ options.AdditionallyAllowedTenants.Add(addlTenant);
+ }
+
+ return new VisualStudioCredential(Options.VisualStudioTenantId, Pipeline, default, default, options);
}
- public virtual TokenCredential CreateVisualStudioCodeCredential(string tenantId)
+ public virtual TokenCredential CreateVisualStudioCodeCredential()
{
- return new VisualStudioCodeCredential(new VisualStudioCodeCredentialOptions { TenantId = tenantId }, Pipeline, default, default, default);
+ var options = new VisualStudioCodeCredentialOptions
+ {
+ TenantId = Options.VisualStudioCodeTenantId,
+ };
+
+ foreach (var addlTenant in Options.AdditionallyAllowedTenants)
+ {
+ options.AdditionallyAllowedTenants.Add(addlTenant);
+ }
+
+ return new VisualStudioCodeCredential(options, Pipeline, default, default, default);
}
public virtual TokenCredential CreateAzurePowerShellCredential()
{
- return new AzurePowerShellCredential(new AzurePowerShellCredentialOptions(), Pipeline, default);
+ var options = new AzurePowerShellCredentialOptions
+ {
+ TenantId = Options.VisualStudioCodeTenantId,
+ };
+
+ foreach (var addlTenant in Options.AdditionallyAllowedTenants)
+ {
+ options.AdditionallyAllowedTenants.Add(addlTenant);
+ }
+
+ return new AzurePowerShellCredential(options, Pipeline, default);
}
}
}
diff --git a/sdk/identity/Azure.Identity/src/EnvironmentVariables.cs b/sdk/identity/Azure.Identity/src/EnvironmentVariables.cs
index 9f38354b6498..4d6900eb53ed 100644
--- a/sdk/identity/Azure.Identity/src/EnvironmentVariables.cs
+++ b/sdk/identity/Azure.Identity/src/EnvironmentVariables.cs
@@ -2,6 +2,9 @@
// Licensed under the MIT License.
using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Net.Http.Headers;
namespace Azure.Identity
{
@@ -10,6 +13,7 @@ internal class EnvironmentVariables
public static string Username => Environment.GetEnvironmentVariable("AZURE_USERNAME");
public static string Password => Environment.GetEnvironmentVariable("AZURE_PASSWORD");
public static string TenantId => Environment.GetEnvironmentVariable("AZURE_TENANT_ID");
+ public static List AdditionallyAllowedTenants => (Environment.GetEnvironmentVariable("AZURE_ADDITIONALLY_ALLOWED_TENANTS") ?? string.Empty).Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries).ToList();
public static string ClientId => Environment.GetEnvironmentVariable("AZURE_CLIENT_ID");
public static string ClientSecret => Environment.GetEnvironmentVariable("AZURE_CLIENT_SECRET");
public static string ClientCertificatePath => Environment.GetEnvironmentVariable("AZURE_CLIENT_CERTIFICATE_PATH");
diff --git a/sdk/identity/Azure.Identity/src/ManagedIdentityClient.cs b/sdk/identity/Azure.Identity/src/ManagedIdentityClient.cs
index f98f86fefdc6..9a1c1b382b0f 100644
--- a/sdk/identity/Azure.Identity/src/ManagedIdentityClient.cs
+++ b/sdk/identity/Azure.Identity/src/ManagedIdentityClient.cs
@@ -38,13 +38,16 @@ public ManagedIdentityClient(ManagedIdentityClientOptions options)
}
ClientId = options.ClientId;
+ ResourceIdentifier = options.ResourceIdentifier;
Pipeline = options.Pipeline;
_identitySource = new Lazy(() => SelectManagedIdentitySource(options));
}
internal CredentialPipeline Pipeline { get; }
- protected string ClientId { get; }
+ internal protected string ClientId { get; }
+
+ internal ResourceIdentifier ResourceIdentifier { get; }
public virtual async ValueTask AuthenticateAsync(bool async, TokenRequestContext context,
CancellationToken cancellationToken)
diff --git a/sdk/identity/Azure.Identity/src/ScopeUtilities.cs b/sdk/identity/Azure.Identity/src/ScopeUtilities.cs
index ffad084ec066..ac7b7f54ee1f 100644
--- a/sdk/identity/Azure.Identity/src/ScopeUtilities.cs
+++ b/sdk/identity/Azure.Identity/src/ScopeUtilities.cs
@@ -13,7 +13,7 @@ namespace Azure.Identity
internal static class ScopeUtilities
{
private const string DefaultSuffix = "/.default";
- private const string ScopePattern = "^[0-9a-zA-Z-.:/]+$";
+ private const string ScopePattern = "^[0-9a-zA-Z-_.:/]+$";
private const string InvalidScopeMessage = "The specified scope is not in expected format. Only alphanumeric characters, '.', '-', ':', and '/' are allowed";
private static readonly Regex scopeRegex = new Regex(ScopePattern);
diff --git a/sdk/identity/Azure.Identity/src/TenantIdResolver.cs b/sdk/identity/Azure.Identity/src/TenantIdResolver.cs
index 0e8fab8de64c..b8e9263149f4 100644
--- a/sdk/identity/Azure.Identity/src/TenantIdResolver.cs
+++ b/sdk/identity/Azure.Identity/src/TenantIdResolver.cs
@@ -1,19 +1,25 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
+using System;
+using System.Collections.Generic;
+using System.Linq;
using Azure.Core;
namespace Azure.Identity
{
internal static class TenantIdResolver
{
+ public static readonly string[] AllTenants = new string[] { "*" };
+
///
/// Resolves the tenantId based on the supplied configuration values.
///
/// The tenantId passed to the ctor of the Credential.
/// The .
+ /// Additional tenants the credential is configured to acquire tokens for.
/// The tenantId to be used for authorization.
- public static string Resolve(string explicitTenantId, TokenRequestContext context)
+ public static string Resolve(string explicitTenantId, TokenRequestContext context, string[] additionallyAllowedTenantIds)
{
bool disableMultiTenantAuth = IdentityCompatSwitches.DisableTenantDiscovery;
@@ -29,12 +35,34 @@ public static string Resolve(string explicitTenantId, TokenRequestContext contex
}
}
- return disableMultiTenantAuth switch
+ string resolvedTenantId = disableMultiTenantAuth switch
{
true => explicitTenantId,
false when explicitTenantId == Constants.AdfsTenantId => explicitTenantId,
_ => context.TenantId ?? explicitTenantId
};
+
+ if (explicitTenantId != null && resolvedTenantId != explicitTenantId && additionallyAllowedTenantIds != AllTenants && Array.BinarySearch(additionallyAllowedTenantIds, resolvedTenantId, StringComparer.OrdinalIgnoreCase) < 0)
+ {
+ throw new AuthenticationFailedException($"The current credential is not configured to acquire tokens for tenant {resolvedTenantId}. To enable acquiring tokens for this tenant add it to the AdditionallyAllowedTenants on the credential options, or add \"*\" to AdditionallyAllowedTenants to allow acquiring tokens for any tenant. See the troubleshooting guide for more information. https://aka.ms/azsdk/net/identity/multitenant/troubleshoot");
+ }
+
+ return resolvedTenantId;
+ }
+
+ public static string[] ResolveAddionallyAllowedTenantIds(IList additionallyAllowedTenants)
+ {
+ if (additionallyAllowedTenants == null || additionallyAllowedTenants.Count == 0)
+ {
+ return Array.Empty();
+ }
+
+ if (additionallyAllowedTenants.Contains("*"))
+ {
+ return AllTenants;
+ }
+
+ return additionallyAllowedTenants.OrderBy(s => s).ToArray();
}
}
}
diff --git a/sdk/identity/Azure.Identity/tests/AuthorizationCodeCredentialTests.cs b/sdk/identity/Azure.Identity/tests/AuthorizationCodeCredentialTests.cs
index 05d07ca17262..5a92879404c6 100644
--- a/sdk/identity/Azure.Identity/tests/AuthorizationCodeCredentialTests.cs
+++ b/sdk/identity/Azure.Identity/tests/AuthorizationCodeCredentialTests.cs
@@ -9,6 +9,8 @@
using Azure.Core;
using Azure.Core.Diagnostics;
using Azure.Core.TestFramework;
+using Azure.Identity.Tests.Mock;
+using Microsoft.Identity.Client;
using NUnit.Framework;
namespace Azure.Identity.Tests
@@ -57,8 +59,9 @@ public async Task AuthenticateWithAuthCodeHonorsReplyUrl([Values(null, ReplyUrl)
public async Task AuthenticateWithAuthCodeHonorsTenantId([Values(null, TenantIdHint)] string tenantId, [Values(true)] bool allowMultiTenantAuthentication)
{
var context = new TokenRequestContext(new[] { Scope }, tenantId: tenantId);
- expectedTenantId = TenantIdResolver.Resolve(TenantId, context);
+ expectedTenantId = TenantIdResolver.Resolve(TenantId, context, TenantIdResolver.AllTenants);
+ var options = new AuthorizationCodeCredentialOptions { AdditionallyAllowedTenants = { TenantIdHint } };
AuthorizationCodeCredential cred = InstrumentClient(
new AuthorizationCodeCredential(TenantId, ClientId, clientSecret, authCode, options, mockConfidentialMsalClient));
@@ -71,6 +74,30 @@ public async Task AuthenticateWithAuthCodeHonorsTenantId([Values(null, TenantIdH
Assert.AreEqual(token2.Token, expectedToken, "Should be the expected token value");
}
+ public override async Task VerifyAllowedTenantEnforcement(AllowedTenantsTestParameters parameters)
+ {
+ Console.WriteLine(parameters.ToDebugString());
+
+ // no need to test with null TenantId since we can't construct this credential without it
+ if (parameters.TenantId == null)
+ {
+ Assert.Ignore("Null TenantId test does not apply to this credential");
+ }
+
+ var options = new AuthorizationCodeCredentialOptions();
+
+ foreach (var addlTenant in parameters.AdditionallyAllowedTenants)
+ {
+ options.AdditionallyAllowedTenants.Add(addlTenant);
+ }
+
+ var msalClientMock = new MockMsalConfidentialClient(AuthenticationResultFactory.Create());
+
+ var cred = InstrumentClient(new AuthorizationCodeCredential(parameters.TenantId, ClientId, "secret", "authcode", options, msalClientMock));
+
+ await AssertAllowedTenantIdsEnforcedAsync(parameters, cred);
+ }
+
[Test]
public async Task AuthenticateWithAutCodeHonorsRedirectUri([Values(null, redirectUriString)] string redirectUri)
{
diff --git a/sdk/identity/Azure.Identity/tests/AzureCliCredentialTests.cs b/sdk/identity/Azure.Identity/tests/AzureCliCredentialTests.cs
index e6850030e2cc..76e8f18fe4c7 100644
--- a/sdk/identity/Azure.Identity/tests/AzureCliCredentialTests.cs
+++ b/sdk/identity/Azure.Identity/tests/AzureCliCredentialTests.cs
@@ -33,8 +33,8 @@ public async Task AuthenticateWithCliCredential(
[Values(null, TenantId)] string explicitTenantId)
{
var context = new TokenRequestContext(new[] { Scope }, tenantId: tenantId);
- var options = new AzureCliCredentialOptions { TenantId = explicitTenantId };
- string expectedTenantId = TenantIdResolver.Resolve(explicitTenantId, context);
+ var options = new AzureCliCredentialOptions { TenantId = explicitTenantId, AdditionallyAllowedTenants = { TenantIdHint } };
+ string expectedTenantId = TenantIdResolver.Resolve(explicitTenantId, context, TenantIdResolver.AllTenants);
var (expectedToken, expectedExpiresOn, processOutput) = CredentialTestHelpers.CreateTokenForAzureCli();
var testProcess = new TestProcess { Output = processOutput };
@@ -56,6 +56,25 @@ public async Task AuthenticateWithCliCredential(
}
}
+ public override async Task VerifyAllowedTenantEnforcement(AllowedTenantsTestParameters parameters)
+ {
+ Console.WriteLine(parameters.ToDebugString());
+
+ var options = new AzureCliCredentialOptions { TenantId = parameters.TenantId};
+
+ foreach (var addlTenant in parameters.AdditionallyAllowedTenants)
+ {
+ options.AdditionallyAllowedTenants.Add(addlTenant);
+ }
+
+ var (expectedToken, expectedExpiresOn, processOutput) = CredentialTestHelpers.CreateTokenForAzureCli();
+ var testProcess = new TestProcess { Output = processOutput };
+ AzureCliCredential credential =
+ InstrumentClient(new AzureCliCredential(CredentialPipeline.GetInstance(null), new TestProcessService(testProcess, true), options));
+
+ await AssertAllowedTenantIdsEnforcedAsync(parameters, credential);
+ }
+
[Test]
public async Task AuthenticateWithCliCredential_ExpiresIn()
{
diff --git a/sdk/identity/Azure.Identity/tests/AzurePowerShellCredentialsTests.cs b/sdk/identity/Azure.Identity/tests/AzurePowerShellCredentialsTests.cs
index 1dd51b45b2c6..0f9f1416610e 100644
--- a/sdk/identity/Azure.Identity/tests/AzurePowerShellCredentialsTests.cs
+++ b/sdk/identity/Azure.Identity/tests/AzurePowerShellCredentialsTests.cs
@@ -42,8 +42,8 @@ public async Task AuthenticateWithAzurePowerShellCredential(
[Values(null, TenantId)] string explicitTenantId)
{
var context = new TokenRequestContext(new[] { Scope }, tenantId: tenantId);
- var options = new AzurePowerShellCredentialOptions { TenantId = explicitTenantId };
- string expectedTenantId = TenantIdResolver.Resolve(explicitTenantId, context);
+ var options = new AzurePowerShellCredentialOptions { TenantId = explicitTenantId, AdditionallyAllowedTenants = { TenantIdHint } };
+ string expectedTenantId = TenantIdResolver.Resolve(explicitTenantId, context, TenantIdResolver.AllTenants);
var (expectedToken, expectedExpiresOn, processOutput) = CredentialTestHelpers.CreateTokenForAzurePowerShell(TimeSpan.FromSeconds(30));
var testProcess = new TestProcess { Output = processOutput };
@@ -72,6 +72,25 @@ public async Task AuthenticateWithAzurePowerShellCredential(
}
}
+ public override async Task VerifyAllowedTenantEnforcement(AllowedTenantsTestParameters parameters)
+ {
+ Console.WriteLine(parameters.ToDebugString());
+
+ var options = new AzurePowerShellCredentialOptions { TenantId = parameters.TenantId };
+
+ foreach (var addlTenant in parameters.AdditionallyAllowedTenants)
+ {
+ options.AdditionallyAllowedTenants.Add(addlTenant);
+ }
+
+ var (expectedToken, expectedExpiresOn, processOutput) = CredentialTestHelpers.CreateTokenForAzurePowerShell(TimeSpan.FromSeconds(30));
+ var testProcess = new TestProcess { Output = processOutput };
+ AzurePowerShellCredential credential = InstrumentClient(
+ new AzurePowerShellCredential(options, CredentialPipeline.GetInstance(null), new TestProcessService(testProcess, true)));
+
+ await AssertAllowedTenantIdsEnforcedAsync(parameters, credential);
+ }
+
private static IEnumerable