diff --git a/sdk/identity/Azure.Identity/CHANGELOG.md b/sdk/identity/Azure.Identity/CHANGELOG.md index 008244796c2e..7880fe761159 100644 --- a/sdk/identity/Azure.Identity/CHANGELOG.md +++ b/sdk/identity/Azure.Identity/CHANGELOG.md @@ -9,6 +9,7 @@ - Fixed an issue with `TokenCachePersistenceOptions` where credentials in the same process would share the same cache, even if they had different configured names. - ManagedIdentityCredential now ignores empty ClientId values. [#37100](https://github.com/Azure/azure-sdk-for-net/issues/37100) - ManagedIdentityCredential will no longer attempt to parse invalid json payloads on responses from the managed identity endpoint. +- When utilizing `EnvironmentCredential` from `DefaultAzureCredential` the credential will now override the `TENANT_ID` environment value if the TenantId value is set in `DefaultAzureCredentialOptions`. ### Other Changes - All developer credentials in the `DefaultAzureCredential` credential chain will fall through to the next credential in the chain on any failure. Previously, some exceptions would throw `AuthenticationFailedException`, which stops further progress in the chain. diff --git a/sdk/identity/Azure.Identity/src/DefaultAzureCredentialFactory.cs b/sdk/identity/Azure.Identity/src/DefaultAzureCredentialFactory.cs index e6221a782488..f4e661948585 100644 --- a/sdk/identity/Azure.Identity/src/DefaultAzureCredentialFactory.cs +++ b/sdk/identity/Azure.Identity/src/DefaultAzureCredentialFactory.cs @@ -99,6 +99,11 @@ public virtual TokenCredential CreateEnvironmentCredential() { var options = Options.Clone(); + if (!string.IsNullOrEmpty(options.TenantId)) + { + options.TenantId = Options.TenantId; + } + return new EnvironmentCredential(Pipeline, options); } diff --git a/sdk/identity/Azure.Identity/tests/CredentialTestHelpers.cs b/sdk/identity/Azure.Identity/tests/CredentialTestHelpers.cs index 28c28f1bb1c7..b3cea0a69065 100644 --- a/sdk/identity/Azure.Identity/tests/CredentialTestHelpers.cs +++ b/sdk/identity/Azure.Identity/tests/CredentialTestHelpers.cs @@ -517,6 +517,23 @@ public static string[] ExtractAdditionalTenantProperty(TokenCredential cred) return additionallyAllowedTenantIds; } + public static bool TryGetConfiguredTenantIdForMsalCredential(TokenCredential cred, out string tenantID) + { + var targetCred = cred is EnvironmentCredential environmentCredential ? environmentCredential.Credential : cred; + object clientObject = targetCred.GetType().GetProperty("Client", BindingFlags.Instance | BindingFlags.NonPublic)?.GetValue(targetCred); + tenantID = clientObject switch + { + MsalPublicClient msalPub => msalPub?.TenantId, + MsalConfidentialClient msalConf => msalConf?.TenantId, + _ => null + }; + if (tenantID == null) + { + tenantID = targetCred.GetType().GetProperty("TenantId", BindingFlags.Instance | BindingFlags.NonPublic)?.GetValue(targetCred) as string; + } + return tenantID != null; + } + public static bool IsMsalCredential(TokenCredential cred) { var clientType = GetMsalClientType(cred); diff --git a/sdk/identity/Azure.Identity/tests/DefaultAzureCredentialTests.cs b/sdk/identity/Azure.Identity/tests/DefaultAzureCredentialTests.cs index 9ab026764af1..814fcef837a1 100644 --- a/sdk/identity/Azure.Identity/tests/DefaultAzureCredentialTests.cs +++ b/sdk/identity/Azure.Identity/tests/DefaultAzureCredentialTests.cs @@ -422,6 +422,41 @@ public void AdditionallyAllowedTenantsOptionIsHonored(Type availableCredential) } } + [Test] + [TestCaseSource(nameof(AllCredentialTypes))] + public void TenantIdOptionOverridesEnvironment(Type availableCredential) + { + using (new TestEnvVar(new Dictionary { + { "AZURE_CLIENT_ID", "mockclientid" }, + { "AZURE_CLIENT_SECRET", null}, + { "AZURE_TENANT_ID", "mocktenantid" }, + {"AZURE_USERNAME", "mockusername" }, + { "AZURE_PASSWORD", "mockpassword" }, + { "AZURE_CLIENT_CERTIFICATE_PATH", null }, + { "AZURE_FEDERATED_TOKEN_FILE", "c:/temp/token" } + })) + { + DefaultAzureCredentialOptions options = GetDacOptions(availableCredential, false, "overridetenantid"); + var additionalTenant = Guid.NewGuid().ToString(); + options.AdditionallyAllowedTenants.Add(additionalTenant); + + var credential = new DefaultAzureCredential(options); + Assert.AreEqual(1, credential._sources.Length); + var targetCred = credential._sources[0]; + if (CredentialTestHelpers.TryGetConfiguredTenantIdForMsalCredential(targetCred, out string tenantId)) + { + if (availableCredential == typeof(ManagedIdentityCredential)) + { + Assert.Ignore("ManagedIdentityCredential does not include a TenantId option."); + } + else + { + Assert.AreEqual("overridetenantid", tenantId); + } + } + } + } + [Test] public void ExcludeWorkloadIdentityCredential_Disables_TokenExchangeManagedIdentitySource() { @@ -449,9 +484,9 @@ public void ExcludeWorkloadIdentityCredential_Disables_TokenExchangeManagedIdent } } - private static DefaultAzureCredentialOptions GetDacOptions(Type availableCredential, bool disableInstanceDiscovery) + private static DefaultAzureCredentialOptions GetDacOptions(Type availableCredential, bool disableInstanceDiscovery, string tenantId = null) { - return new DefaultAzureCredentialOptions + var options = new DefaultAzureCredentialOptions { ExcludeEnvironmentCredential = availableCredential != typeof(EnvironmentCredential), ExcludeWorkloadIdentityCredential = availableCredential != typeof(WorkloadIdentityCredential), @@ -463,8 +498,13 @@ private static DefaultAzureCredentialOptions GetDacOptions(Type availableCredent ExcludeAzureCliCredential = availableCredential != typeof(AzureCliCredential), ExcludeAzurePowerShellCredential = availableCredential != typeof(AzurePowerShellCredential), ExcludeInteractiveBrowserCredential = availableCredential != typeof(InteractiveBrowserCredential), - DisableInstanceDiscovery = disableInstanceDiscovery + DisableInstanceDiscovery = disableInstanceDiscovery, }; + if (tenantId != null) + { + options.TenantId = tenantId; + } + return options; } private static Type GetTargetCredentialOptionType(Type availableCredential)