From a1dd7689e9f3e75597a199e9a01b2bee90342ebd Mon Sep 17 00:00:00 2001 From: Yiwen Wang Date: Sat, 8 Jun 2024 01:57:06 +0800 Subject: [PATCH 1/4] Implement FIC + MSI support --- .../SqlAuthenticationMethod.xml | 4 ++ .../netcore/ref/Microsoft.Data.SqlClient.cs | 4 +- ...uthenticationProviderManager.NetCoreApp.cs | 2 + .../SqlAuthenticationProviderManager.cs | 2 + .../Microsoft/Data/SqlClient/SqlConnection.cs | 17 +++++++ .../SqlClient/SqlInternalConnectionTds.cs | 3 ++ .../src/Microsoft/Data/SqlClient/TdsParser.cs | 3 ++ .../netfx/ref/Microsoft.Data.SqlClient.cs | 4 +- .../SqlAuthenticationProviderManager.cs | 4 ++ .../Microsoft/Data/SqlClient/SqlConnection.cs | 18 ++++++++ .../SqlClient/SqlInternalConnectionTds.cs | 6 ++- .../src/Microsoft/Data/SqlClient/TdsParser.cs | 6 +++ .../Data/Common/DbConnectionStringCommon.cs | 10 ++++ .../ActiveDirectoryAuthenticationProvider.cs | 46 ++++++++++++++++++- .../Data/SqlClient/SqlConnectionString.cs | 5 ++ .../src/Microsoft/Data/SqlClient/TdsEnums.cs | 5 ++ .../SqlAuthenticationProviderTest.cs | 1 + .../SqlConnectionBasicTests.cs | 1 + .../SqlConnectionStringBuilderTest.cs | 2 + .../ConnectivityTests/AADConnectionTest.cs | 21 +++++++++ 20 files changed, 160 insertions(+), 4 deletions(-) diff --git a/doc/snippets/Microsoft.Data.SqlClient/SqlAuthenticationMethod.xml b/doc/snippets/Microsoft.Data.SqlClient/SqlAuthenticationMethod.xml index d179ce1196..086854230d 100644 --- a/doc/snippets/Microsoft.Data.SqlClient/SqlAuthenticationMethod.xml +++ b/doc/snippets/Microsoft.Data.SqlClient/SqlAuthenticationMethod.xml @@ -49,5 +49,9 @@ The authentication method uses Active Directory Workload Identity. Use a federated User Assigned Managed Identity to connect to SQL Database from Azure client environments that have enabled support for Workload Identity. The 'User Id' or 'UID' is required to be set to the "client ID" of the user identity. 10 + + The authentication method uses Active Directory Federated Identity Credentials. Use a federated User Assigned Managed Identity to connect to SQL Database from Azure client environments that have enabled support for Managed Identity. The 'User Id' or 'UID' is required to be set to the "client ID" of the user identity. + 11 + diff --git a/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.cs b/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.cs index 5dfbf8af3f..a68dd90a47 100644 --- a/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.cs +++ b/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.cs @@ -183,7 +183,9 @@ public enum SqlAuthenticationMethod /// ActiveDirectoryDefault = 9, /// - ActiveDirectoryWorkloadIdentity = 10 + ActiveDirectoryWorkloadIdentity = 10, + /// + ActiveDirectoryFederatedIdentityCredentials = 11 } /// public class SqlAuthenticationParameters diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlAuthenticationProviderManager.NetCoreApp.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlAuthenticationProviderManager.NetCoreApp.cs index dd57c60da8..e190cedc77 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlAuthenticationProviderManager.NetCoreApp.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlAuthenticationProviderManager.NetCoreApp.cs @@ -155,6 +155,8 @@ private static SqlAuthenticationMethod AuthenticationEnumFromString(string authe return SqlAuthenticationMethod.ActiveDirectoryDefault; case ActiveDirectoryWorkloadIdentity: return SqlAuthenticationMethod.ActiveDirectoryWorkloadIdentity; + case ActiveDirectoryFederatedIdentityCredentials: + return SqlAuthenticationMethod.ActiveDirectoryFederatedIdentityCredentials; default: throw SQL.UnsupportedAuthentication(authentication); } diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlAuthenticationProviderManager.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlAuthenticationProviderManager.cs index 0cb7a9b54c..d79b8b20af 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlAuthenticationProviderManager.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlAuthenticationProviderManager.cs @@ -21,6 +21,7 @@ internal partial class SqlAuthenticationProviderManager private const string ActiveDirectoryMSI = "active directory msi"; private const string ActiveDirectoryDefault = "active directory default"; private const string ActiveDirectoryWorkloadIdentity = "active directory workload identity"; + private const string ActiveDirectoryFederatedIdentityCredentials = "active directory federated identity credentials"; private readonly IReadOnlyCollection _authenticationsWithAppSpecifiedProvider; private readonly ConcurrentDictionary _providers; @@ -47,6 +48,7 @@ private static void SetDefaultAuthProviders(SqlAuthenticationProviderManager ins instance.SetProvider(SqlAuthenticationMethod.ActiveDirectoryMSI, activeDirectoryAuthProvider); instance.SetProvider(SqlAuthenticationMethod.ActiveDirectoryDefault, activeDirectoryAuthProvider); instance.SetProvider(SqlAuthenticationMethod.ActiveDirectoryWorkloadIdentity, activeDirectoryAuthProvider); + instance.SetProvider(SqlAuthenticationMethod.ActiveDirectoryFederatedIdentityCredentials, activeDirectoryAuthProvider); } } /// diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlConnection.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlConnection.cs index 697315abc9..007995d4eb 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlConnection.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlConnection.cs @@ -207,6 +207,10 @@ public SqlConnection(string connectionString, SqlCredential credential) : this() { throw SQL.SettingCredentialWithNonInteractiveArgument(DbConnectionStringBuilderUtil.ActiveDirectoryWorkloadIdentityString); } + else if (UsesActiveDirectoryFederatedIdentityCredentials(connectionOptions)) + { + throw SQL.SettingCredentialWithNonInteractiveArgument(DbConnectionStringBuilderUtil.ActiveDirectoryFederatedIdentityCredentialsString); + } Credential = credential; } @@ -541,6 +545,11 @@ private bool UsesActiveDirectoryWorkloadIdentity(SqlConnectionString opt) return opt != null && opt.Authentication == SqlAuthenticationMethod.ActiveDirectoryWorkloadIdentity; } + private bool UsesActiveDirectoryFederatedIdentityCredentials(SqlConnectionString opt) + { + return opt != null && opt.Authentication == SqlAuthenticationMethod.ActiveDirectoryFederatedIdentityCredentials; + } + private bool UsesAuthentication(SqlConnectionString opt) { return opt != null && opt.Authentication != SqlAuthenticationMethod.NotSpecified; @@ -634,6 +643,10 @@ public override string ConnectionString { throw SQL.SettingNonInteractiveWithCredential(DbConnectionStringBuilderUtil.ActiveDirectoryWorkloadIdentityString); } + else if (UsesActiveDirectoryFederatedIdentityCredentials(connectionOptions)) + { + throw SQL.SettingNonInteractiveWithCredential(DbConnectionStringBuilderUtil.ActiveDirectoryFederatedIdentityCredentialsString); + } CheckAndThrowOnInvalidCombinationOfConnectionStringAndSqlCredential(connectionOptions); } @@ -1018,6 +1031,10 @@ public SqlCredential Credential { throw SQL.SettingCredentialWithNonInteractiveInvalid(DbConnectionStringBuilderUtil.ActiveDirectoryWorkloadIdentityString); } + else if (UsesActiveDirectoryFederatedIdentityCredentials(connectionOptions)) + { + throw SQL.SettingCredentialWithNonInteractiveInvalid(DbConnectionStringBuilderUtil.ActiveDirectoryFederatedIdentityCredentialsString); + } CheckAndThrowOnInvalidCombinationOfConnectionStringAndSqlCredential(connectionOptions); diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs index 5ef3d781ea..13240337cd 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs @@ -1331,6 +1331,7 @@ private void Login(ServerInfo server, TimeoutTimer timeout, string newPassword, || ConnectionOptions.Authentication == SqlAuthenticationMethod.ActiveDirectoryMSI || ConnectionOptions.Authentication == SqlAuthenticationMethod.ActiveDirectoryDefault || ConnectionOptions.Authentication == SqlAuthenticationMethod.ActiveDirectoryWorkloadIdentity + || ConnectionOptions.Authentication == SqlAuthenticationMethod.ActiveDirectoryFederatedIdentityCredentials // Since AD Integrated may be acting like Windows integrated, additionally check _fedAuthRequired || (ConnectionOptions.Authentication == SqlAuthenticationMethod.ActiveDirectoryIntegrated && _fedAuthRequired) || _accessTokenCallback != null) @@ -2161,6 +2162,7 @@ internal void OnFedAuthInfo(SqlFedAuthInfo fedAuthInfo) || ConnectionOptions.Authentication == SqlAuthenticationMethod.ActiveDirectoryMSI || ConnectionOptions.Authentication == SqlAuthenticationMethod.ActiveDirectoryDefault || ConnectionOptions.Authentication == SqlAuthenticationMethod.ActiveDirectoryWorkloadIdentity + || ConnectionOptions.Authentication == SqlAuthenticationMethod.ActiveDirectoryFederatedIdentityCredentials || (ConnectionOptions.Authentication == SqlAuthenticationMethod.ActiveDirectoryIntegrated && _fedAuthRequired), "Credentials aren't provided for calling MSAL"); Debug.Assert(fedAuthInfo != null, "info should not be null."); @@ -2414,6 +2416,7 @@ internal SqlFedAuthToken GetFedAuthToken(SqlFedAuthInfo fedAuthInfo) case SqlAuthenticationMethod.ActiveDirectoryMSI: case SqlAuthenticationMethod.ActiveDirectoryDefault: case SqlAuthenticationMethod.ActiveDirectoryWorkloadIdentity: + case SqlAuthenticationMethod.ActiveDirectoryFederatedIdentityCredentials: if (_activeDirectoryAuthTimeoutRetryHelper.State == ActiveDirectoryAuthenticationTimeoutRetryState.Retrying) { _fedAuthToken = _activeDirectoryAuthTimeoutRetryHelper.CachedToken; diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.cs index 337752a068..1cd5b034f9 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.cs @@ -8012,6 +8012,9 @@ internal int WriteFedAuthFeatureRequest(FederatedAuthenticationFeatureExtensionD case SqlAuthenticationMethod.ActiveDirectoryWorkloadIdentity: workflow = TdsEnums.MSALWORKFLOW_ACTIVEDIRECTORYWORKLOADIDENTITY; break; + case SqlAuthenticationMethod.ActiveDirectoryFederatedIdentityCredentials: + workflow = TdsEnums.MSALWORKFLOW_ACTIVEDIRECTORYFEDERATEDIDENTITYCREDENTIALS; + break; default: if (_connHandler._accessTokenCallback != null) { diff --git a/src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.cs b/src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.cs index d8bbb83106..aeb2c2f872 100644 --- a/src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.cs +++ b/src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.cs @@ -137,7 +137,9 @@ public enum SqlAuthenticationMethod /// ActiveDirectoryDefault = 9, /// - ActiveDirectoryWorkloadIdentity = 10 + ActiveDirectoryWorkloadIdentity = 10, + /// + ActiveDirectoryFederatedIdentityCredentials = 11 } /// public class SqlAuthenticationParameters diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlAuthenticationProviderManager.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlAuthenticationProviderManager.cs index bc654f39f7..756ef7cab8 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlAuthenticationProviderManager.cs +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlAuthenticationProviderManager.cs @@ -23,6 +23,7 @@ internal class SqlAuthenticationProviderManager private const string ActiveDirectoryMSI = "active directory msi"; private const string ActiveDirectoryDefault = "active directory default"; private const string ActiveDirectoryWorkloadIdentity = "active directory workload identity"; + private const string ActiveDirectoryFederatedIdentityCredentials = "active directory federated identity credentials"; static SqlAuthenticationProviderManager() { @@ -54,6 +55,7 @@ static SqlAuthenticationProviderManager() Instance.SetProvider(SqlAuthenticationMethod.ActiveDirectoryMSI, activeDirectoryAuthProvider); Instance.SetProvider(SqlAuthenticationMethod.ActiveDirectoryDefault, activeDirectoryAuthProvider); Instance.SetProvider(SqlAuthenticationMethod.ActiveDirectoryWorkloadIdentity, activeDirectoryAuthProvider); + Instance.SetProvider(SqlAuthenticationMethod.ActiveDirectoryFederatedIdentityCredentials, activeDirectoryAuthProvider); } public static readonly SqlAuthenticationProviderManager Instance; @@ -235,6 +237,8 @@ private static SqlAuthenticationMethod AuthenticationEnumFromString(string authe return SqlAuthenticationMethod.ActiveDirectoryDefault; case ActiveDirectoryWorkloadIdentity: return SqlAuthenticationMethod.ActiveDirectoryWorkloadIdentity; + case ActiveDirectoryFederatedIdentityCredentials: + return SqlAuthenticationMethod.ActiveDirectoryFederatedIdentityCredentials; default: throw SQL.UnsupportedAuthentication(authentication); } diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlConnection.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlConnection.cs index a7b0f76c12..1fa92360ff 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlConnection.cs +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlConnection.cs @@ -410,6 +410,11 @@ public SqlConnection(string connectionString, SqlCredential credential) : this() throw SQL.SettingCredentialWithNonInteractiveArgument(DbConnectionStringBuilderUtil.ActiveDirectoryWorkloadIdentityString); } + if (UsesActiveDirectoryFederatedIdentityCredentials(connectionOptions)) + { + throw SQL.SettingCredentialWithNonInteractiveArgument(DbConnectionStringBuilderUtil.ActiveDirectoryFederatedIdentityCredentialsString); + } + Credential = credential; } // else @@ -638,6 +643,11 @@ private bool UsesActiveDirectoryWorkloadIdentity(SqlConnectionString opt) return opt != null && opt.Authentication == SqlAuthenticationMethod.ActiveDirectoryWorkloadIdentity; } + private bool UsesActiveDirectoryFederatedIdentityCredentials(SqlConnectionString opt) + { + return opt != null && opt.Authentication == SqlAuthenticationMethod.ActiveDirectoryFederatedIdentityCredentials; + } + private bool UsesAuthentication(SqlConnectionString opt) { return opt != null && opt.Authentication != SqlAuthenticationMethod.NotSpecified; @@ -849,6 +859,10 @@ override public string ConnectionString { throw SQL.SettingNonInteractiveWithCredential(DbConnectionStringBuilderUtil.ActiveDirectoryWorkloadIdentityString); } + else if (UsesActiveDirectoryFederatedIdentityCredentials(connectionOptions)) + { + throw SQL.SettingNonInteractiveWithCredential(DbConnectionStringBuilderUtil.ActiveDirectoryFederatedIdentityCredentialsString); + } CheckAndThrowOnInvalidCombinationOfConnectionStringAndSqlCredential(connectionOptions); } @@ -1202,6 +1216,10 @@ public SqlCredential Credential { throw SQL.SettingCredentialWithNonInteractiveInvalid(DbConnectionStringBuilderUtil.ActiveDirectoryWorkloadIdentityString); } + else if (UsesActiveDirectoryFederatedIdentityCredentials(connectionOptions)) + { + throw SQL.SettingCredentialWithNonInteractiveInvalid(DbConnectionStringBuilderUtil.ActiveDirectoryFederatedIdentityCredentialsString); + } CheckAndThrowOnInvalidCombinationOfConnectionStringAndSqlCredential(connectionOptions); diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs index cc80af6767..fc8dbf0e23 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs @@ -1596,6 +1596,7 @@ private void Login(ServerInfo server, TimeoutTimer timeout, string newPassword, || ConnectionOptions.Authentication == SqlAuthenticationMethod.ActiveDirectoryMSI || ConnectionOptions.Authentication == SqlAuthenticationMethod.ActiveDirectoryDefault || ConnectionOptions.Authentication == SqlAuthenticationMethod.ActiveDirectoryWorkloadIdentity + || ConnectionOptions.Authentication == SqlAuthenticationMethod.ActiveDirectoryFederatedIdentityCredentials // Since AD Integrated may be acting like Windows integrated, additionally check _fedAuthRequired || (ConnectionOptions.Authentication == SqlAuthenticationMethod.ActiveDirectoryIntegrated && _fedAuthRequired) || _accessTokenCallback != null) @@ -1993,7 +1994,8 @@ private bool ShouldDisableTnir(SqlConnectionString connectionOptions) connectionOptions.Authentication == SqlAuthenticationMethod.ActiveDirectoryManagedIdentity || connectionOptions.Authentication == SqlAuthenticationMethod.ActiveDirectoryMSI || connectionOptions.Authentication == SqlAuthenticationMethod.ActiveDirectoryDefault || - connectionOptions.Authentication == SqlAuthenticationMethod.ActiveDirectoryWorkloadIdentity; + connectionOptions.Authentication == SqlAuthenticationMethod.ActiveDirectoryWorkloadIdentity || + connectionOptions.Authentication == SqlAuthenticationMethod.ActiveDirectoryFederatedIdentityCredentials; // Check if the user had explicitly specified the TNIR option in the connection string or the connection string builder. // If the user has specified the option in the connection string explicitly, then we shouldn't disable TNIR. @@ -2588,6 +2590,7 @@ internal void OnFedAuthInfo(SqlFedAuthInfo fedAuthInfo) || ConnectionOptions.Authentication == SqlAuthenticationMethod.ActiveDirectoryMSI || ConnectionOptions.Authentication == SqlAuthenticationMethod.ActiveDirectoryDefault || ConnectionOptions.Authentication == SqlAuthenticationMethod.ActiveDirectoryWorkloadIdentity + || ConnectionOptions.Authentication == SqlAuthenticationMethod.ActiveDirectoryFederatedIdentityCredentials || ConnectionOptions.Authentication == SqlAuthenticationMethod.ActiveDirectoryDeviceCodeFlow || (ConnectionOptions.Authentication == SqlAuthenticationMethod.ActiveDirectoryIntegrated && _fedAuthRequired), "Credentials aren't provided for calling MSAL"); @@ -2829,6 +2832,7 @@ internal SqlFedAuthToken GetFedAuthToken(SqlFedAuthInfo fedAuthInfo) case SqlAuthenticationMethod.ActiveDirectoryMSI: case SqlAuthenticationMethod.ActiveDirectoryDefault: case SqlAuthenticationMethod.ActiveDirectoryWorkloadIdentity: + case SqlAuthenticationMethod.ActiveDirectoryFederatedIdentityCredentials: if (_activeDirectoryAuthTimeoutRetryHelper.State == ActiveDirectoryAuthenticationTimeoutRetryState.Retrying) { _fedAuthToken = _activeDirectoryAuthTimeoutRetryHelper.CachedToken; diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParser.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParser.cs index afa5220563..709216679c 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParser.cs +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParser.cs @@ -594,6 +594,9 @@ internal void Connect(ServerInfo serverInfo, case SqlAuthenticationMethod.ActiveDirectoryWorkloadIdentity: SqlClientEventSource.Log.TryTraceEvent(" Active Directory Workload Identity authentication"); break; + case SqlAuthenticationMethod.ActiveDirectoryFederatedIdentityCredentials: + SqlClientEventSource.Log.TryTraceEvent(" Active Directory Federated Identity Credentials authentication"); + break; case SqlAuthenticationMethod.SqlPassword: SqlClientEventSource.Log.TryTraceEvent(" SQL Password authentication"); break; @@ -8792,6 +8795,9 @@ internal int WriteFedAuthFeatureRequest(FederatedAuthenticationFeatureExtensionD case SqlAuthenticationMethod.ActiveDirectoryWorkloadIdentity: workflow = TdsEnums.MSALWORKFLOW_ACTIVEDIRECTORYWORKLOADIDENTITY; break; + case SqlAuthenticationMethod.ActiveDirectoryFederatedIdentityCredentials: + workflow = TdsEnums.MSALWORKFLOW_ACTIVEDIRECTORYFEDERATEDIDENTITYCREDENTIALS; + break; default: if (_connHandler._accessTokenCallback != null) { diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/DbConnectionStringCommon.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/DbConnectionStringCommon.cs index 95a54538f9..df31844423 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/DbConnectionStringCommon.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/DbConnectionStringCommon.cs @@ -348,6 +348,7 @@ internal static ApplicationIntent ConvertToApplicationIntent(string keyword, obj internal const string ActiveDirectoryMSIString = "Active Directory MSI"; internal const string ActiveDirectoryDefaultString = "Active Directory Default"; internal const string ActiveDirectoryWorkloadIdentityString = "Active Directory Workload Identity"; + internal const string ActiveDirectoryFederatedIdentityCredentialsString = "Active Directory Federated Identity Credentials"; const string SqlCertificateString = "Sql Certificate"; #if DEBUG @@ -364,6 +365,7 @@ internal static ApplicationIntent ConvertToApplicationIntent(string keyword, obj "ActiveDirectoryMSI", "ActiveDirectoryDefault", "ActiveDirectoryWorkloadIdentity", + "ActiveDirectoryFederatedIdentityCredentials", }; private static bool IsValidAuthenticationMethodEnum() @@ -459,6 +461,12 @@ internal static bool TryConvertToAuthenticationType(string value, out SqlAuthent result = SqlAuthenticationMethod.ActiveDirectoryWorkloadIdentity; isSuccess = true; } + else if (StringComparer.InvariantCultureIgnoreCase.Equals(value, ActiveDirectoryFederatedIdentityCredentialsString) + || StringComparer.InvariantCultureIgnoreCase.Equals(value, Convert.ToString(SqlAuthenticationMethod.ActiveDirectoryFederatedIdentityCredentials, CultureInfo.InvariantCulture))) + { + result = SqlAuthenticationMethod.ActiveDirectoryFederatedIdentityCredentials; + isSuccess = true; + } else { result = DbConnectionStringDefaults.Authentication; @@ -542,6 +550,7 @@ internal static bool IsValidAuthenticationTypeValue(SqlAuthenticationMethod valu || value == SqlAuthenticationMethod.SqlCertificate #endif || value == SqlAuthenticationMethod.ActiveDirectoryWorkloadIdentity + || value == SqlAuthenticationMethod.ActiveDirectoryFederatedIdentityCredentials || value == SqlAuthenticationMethod.NotSpecified; } @@ -564,6 +573,7 @@ internal static string AuthenticationTypeToString(SqlAuthenticationMethod value) SqlAuthenticationMethod.SqlCertificate => SqlCertificateString, #endif SqlAuthenticationMethod.ActiveDirectoryWorkloadIdentity => ActiveDirectoryWorkloadIdentityString, + SqlAuthenticationMethod.ActiveDirectoryFederatedIdentityCredentials => ActiveDirectoryFederatedIdentityCredentialsString, _ => null }; } diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ActiveDirectoryAuthenticationProvider.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ActiveDirectoryAuthenticationProvider.cs index acf224204a..e2fdc40db5 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ActiveDirectoryAuthenticationProvider.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ActiveDirectoryAuthenticationProvider.cs @@ -91,7 +91,8 @@ public override bool IsSupported(SqlAuthenticationMethod authentication) || authentication == SqlAuthenticationMethod.ActiveDirectoryManagedIdentity || authentication == SqlAuthenticationMethod.ActiveDirectoryMSI || authentication == SqlAuthenticationMethod.ActiveDirectoryDefault - || authentication == SqlAuthenticationMethod.ActiveDirectoryWorkloadIdentity; + || authentication == SqlAuthenticationMethod.ActiveDirectoryWorkloadIdentity + || authentication == SqlAuthenticationMethod.ActiveDirectoryFederatedIdentityCredentials; } /// @@ -186,6 +187,49 @@ public override async Task AcquireTokenAsync(SqlAuthenti return new SqlAuthenticationToken(accessToken.Token, accessToken.ExpiresOn); } + if (parameters.AuthenticationMethod == SqlAuthenticationMethod.ActiveDirectoryFederatedIdentityCredentials) + { + // We'll keep using common environment variables defined in Azure.Identity library + // AZURE_TENANT_ID: The tenant ID where target resource hosts + // AZURE_CLIENT_ID: The client id of multi-tenant entra id app. May be overridden by the User Id. + // AZURE_MSI_CLIENT_ID (new): The client id of managed identity + string tenantId = Environment.GetEnvironmentVariable("AZURE_TENANT_ID"); + tenantId = string.IsNullOrWhiteSpace(tenantId) ? null : tenantId; + + if (clientId is null) + { + clientId = Environment.GetEnvironmentVariable("AZURE_CLIENT_ID"); + clientId = string.IsNullOrWhiteSpace(clientId) ? null : clientId; + } + + string managedIdentityClientId = Environment.GetEnvironmentVariable("AZURE_MSI_CLIENT_ID"); + managedIdentityClientId = string.IsNullOrWhiteSpace(managedIdentityClientId) ? null : managedIdentityClientId; + + ClientAssertionCredential clientAssertionCredential = null; + // If either tenant id, client id, or the managed client id are not specified when fetching the token, + // a CredentialUnavailableException will be thrown instead + if (!string.IsNullOrEmpty(tenantId) && !string.IsNullOrEmpty(clientId) && !string.IsNullOrEmpty(managedIdentityClientId)) + { + clientAssertionCredential = new ClientAssertionCredential( + tenantId, + clientId, + async (cancellationToken) => + { + ManagedIdentityCredential miCredential = new ManagedIdentityCredential(managedIdentityClientId); + var token = await miCredential.GetTokenAsync(new TokenRequestContext(new string[] { $"api://AzureADTokenExchange/.default" })).ConfigureAwait(false); + return token.Token; + }); + } + + if (clientAssertionCredential == null) + { + throw new CredentialUnavailableException("FederatedIdentityCredentials authentication unavailable. The required environment variables are not fully configured."); + } + AccessToken accessToken = await clientAssertionCredential.GetTokenAsync(tokenRequestContext, cts.Token).ConfigureAwait(false); + SqlClientEventSource.Log.TryTraceEvent("AcquireTokenAsync | Acquired access token for Federated Identity Credentials auth mode. Expiry Time: {0}", accessToken.ExpiresOn); + return new SqlAuthenticationToken(accessToken.Token, accessToken.ExpiresOn); + } + /* * Today, MSAL.NET uses another redirect URI by default in desktop applications that run on Windows * (urn:ietf:wg:oauth:2.0:oob). In the future, we'll want to change this default, so we recommend diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionString.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionString.cs index 243efb3db1..1f45234f2a 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionString.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionString.cs @@ -631,6 +631,11 @@ internal SqlConnectionString(string connectionString) : base(connectionString, G throw SQL.NonInteractiveWithPassword(DbConnectionStringBuilderUtil.ActiveDirectoryWorkloadIdentityString); } + if (Authentication == SqlAuthenticationMethod.ActiveDirectoryFederatedIdentityCredentials && _hasPasswordKeyword) + { + throw SQL.NonInteractiveWithPassword(DbConnectionStringBuilderUtil.ActiveDirectoryFederatedIdentityCredentialsString); + } + #if ADONET_CERT_AUTH && NETFRAMEWORK if (!DbConnectionStringBuilderUtil.IsValidCertificateValue(_certificate)) diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsEnums.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsEnums.cs index 8c7119a695..4645129bfb 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsEnums.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsEnums.cs @@ -277,6 +277,7 @@ public enum FedAuthLibrary : byte public const byte MSALWORKFLOW_ACTIVEDIRECTORYDEFAULT = 0x03; // Using the Interactive byte as that is the closest we have to non-password based authentication modes public const byte MSALWORKFLOW_ACTIVEDIRECTORYTOKENCREDENTIAL = 0x03; // Using the Interactive byte as that is the closest we have to non-password based authentication modes public const byte MSALWORKFLOW_ACTIVEDIRECTORYWORKLOADIDENTITY = 0x03; // Using the Interactive byte as that's supported for Identity based authentication + public const byte MSALWORKFLOW_ACTIVEDIRECTORYFEDERATEDIDENTITYCREDENTIALS = 0x03; // Using the Interactive byte as that's supported for Identity based authentication public enum ActiveDirectoryWorkflow : byte { @@ -288,6 +289,7 @@ public enum ActiveDirectoryWorkflow : byte ManagedIdentity = MSALWORKFLOW_ACTIVEDIRECTORYMANAGEDIDENTITY, Default = MSALWORKFLOW_ACTIVEDIRECTORYDEFAULT, WorkloadIdentity = MSALWORKFLOW_ACTIVEDIRECTORYWORKLOADIDENTITY, + FederatedIdentityCredentials = MSALWORKFLOW_ACTIVEDIRECTORYFEDERATEDIDENTITYCREDENTIALS, } // The string used for username in the error message when Authentication = Active Directory Integrated with FedAuth is used, if authentication fails. @@ -1173,6 +1175,9 @@ public enum SqlAuthenticationMethod /// ActiveDirectoryWorkloadIdentity, + /// + ActiveDirectoryFederatedIdentityCredentials, + #if ADONET_CERT_AUTH && NETFRAMEWORK SqlCertificate, #endif diff --git a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlAuthenticationProviderTest.cs b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlAuthenticationProviderTest.cs index d41f4b40d1..46b33f120d 100644 --- a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlAuthenticationProviderTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlAuthenticationProviderTest.cs @@ -18,6 +18,7 @@ public class SqlAuthenticationProviderTest [InlineData(SqlAuthenticationMethod.ActiveDirectoryMSI)] [InlineData(SqlAuthenticationMethod.ActiveDirectoryDefault)] [InlineData(SqlAuthenticationMethod.ActiveDirectoryWorkloadIdentity)] + [InlineData(SqlAuthenticationMethod.ActiveDirectoryFederatedIdentityCredentials)] public void DefaultAuthenticationProviders(SqlAuthenticationMethod method) { Assert.IsType(SqlAuthenticationProvider.GetProvider(method)); diff --git a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlConnectionBasicTests.cs b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlConnectionBasicTests.cs index a164149a60..de37a2cdf8 100644 --- a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlConnectionBasicTests.cs +++ b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlConnectionBasicTests.cs @@ -269,6 +269,7 @@ public void ConnectionTestInvalidCredentialCombination() [InlineData(SqlAuthenticationMethod.ActiveDirectoryMSI)] [InlineData(SqlAuthenticationMethod.ActiveDirectoryDefault)] [InlineData(SqlAuthenticationMethod.ActiveDirectoryWorkloadIdentity)] + [InlineData(SqlAuthenticationMethod.ActiveDirectoryFederatedIdentityCredentials)] public void ConnectionTestInvalidCredentialAndAuthentication(SqlAuthenticationMethod authentication) { var connectionString = $"Authentication={authentication}"; diff --git a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlConnectionStringBuilderTest.cs b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlConnectionStringBuilderTest.cs index 05b3ca5b48..74583245f3 100644 --- a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlConnectionStringBuilderTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlConnectionStringBuilderTest.cs @@ -41,6 +41,8 @@ public partial class SqlConnectionStringBuilderTest [InlineData("Authentication = ActiveDirectoryDefault ")] [InlineData("Authentication = Active Directory Workload Identity ")] [InlineData("Authentication = ActiveDirectoryWorkloadIdentity ")] + [InlineData("Authentication = Active Directory Federated Identity Credentials ")] + [InlineData("Authentication = ActiveDirectoryFederatedIdentityCredentials ")] [InlineData("Command Timeout = 5")] [InlineData("Command Timeout = 15")] [InlineData("Command Timeout = 0")] diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConnectivityTests/AADConnectionTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConnectivityTests/AADConnectionTest.cs index 7ef0ed4bcc..288714ba51 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConnectivityTests/AADConnectionTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConnectivityTests/AADConnectionTest.cs @@ -474,6 +474,27 @@ public static void ActiveDirectoryWorkloadIdentityWithCredentialsMustFail() Assert.Contains(expectedMessage, e.Message); } + [ConditionalFact(nameof(IsAADConnStringsSetup))] + public static void ActiveDirectoryFederatedIdentityCredentialsWithCredentialsMustFail() + { + // connection fails with expected error message. + string[] credKeys = { "Authentication", "User ID", "Password", "UID", "PWD" }; + string connStrWithNoCred = DataTestUtility.RemoveKeysInConnStr(DataTestUtility.AADPasswordConnectionString, credKeys) + + "Authentication=Active Directory Federated Identity Credentials;"; + + SecureString str = new SecureString(); + foreach (char c in "hello") + { + str.AppendChar(c); + } + str.MakeReadOnly(); + SqlCredential credential = new SqlCredential("someuser", str); + InvalidOperationException e = Assert.Throws(() => ConnectAndDisconnect(connStrWithNoCred, credential)); + + string expectedMessage = "Cannot set the Credential property if 'Authentication=Active Directory Federated Identity Credentials' has been specified in the connection string."; + Assert.Contains(expectedMessage, e.Message); + } + [ConditionalFact(nameof(IsAADConnStringsSetup), nameof(IsManagedIdentitySetup))] public static void ActiveDirectoryManagedIdentityWithPasswordMustFail() { From 90672ad952be90ef8a7efb7fdca668b36edb8fee Mon Sep 17 00:00:00 2001 From: Yiwen Wang Date: Sun, 9 Jun 2024 02:25:26 +0800 Subject: [PATCH 2/4] Use string from resource --- .../SqlClient/ActiveDirectoryAuthenticationProvider.cs | 2 +- .../src/Resources/Strings.Designer.cs | 9 +++++++++ .../src/Resources/Strings.de.resx | 3 +++ .../src/Resources/Strings.es.resx | 3 +++ .../src/Resources/Strings.fr.resx | 3 +++ .../src/Resources/Strings.it.resx | 3 +++ .../src/Resources/Strings.ja.resx | 3 +++ .../src/Resources/Strings.ko.resx | 3 +++ .../src/Resources/Strings.pt-BR.resx | 3 +++ src/Microsoft.Data.SqlClient/src/Resources/Strings.resx | 5 ++++- .../src/Resources/Strings.ru.resx | 3 +++ .../src/Resources/Strings.zh-Hans.resx | 3 +++ .../src/Resources/Strings.zh-Hant.resx | 3 +++ 13 files changed, 44 insertions(+), 2 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ActiveDirectoryAuthenticationProvider.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ActiveDirectoryAuthenticationProvider.cs index e2fdc40db5..bba22d2e2a 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ActiveDirectoryAuthenticationProvider.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ActiveDirectoryAuthenticationProvider.cs @@ -223,7 +223,7 @@ public override async Task AcquireTokenAsync(SqlAuthenti if (clientAssertionCredential == null) { - throw new CredentialUnavailableException("FederatedIdentityCredentials authentication unavailable. The required environment variables are not fully configured."); + throw new CredentialUnavailableException(Strings.AAD_FIC_Invalid_Setup); } AccessToken accessToken = await clientAssertionCredential.GetTokenAsync(tokenRequestContext, cts.Token).ConfigureAwait(false); SqlClientEventSource.Log.TryTraceEvent("AcquireTokenAsync | Acquired access token for Federated Identity Credentials auth mode. Expiry Time: {0}", accessToken.ExpiresOn); diff --git a/src/Microsoft.Data.SqlClient/src/Resources/Strings.Designer.cs b/src/Microsoft.Data.SqlClient/src/Resources/Strings.Designer.cs index a685787e9e..764051b40e 100644 --- a/src/Microsoft.Data.SqlClient/src/Resources/Strings.Designer.cs +++ b/src/Microsoft.Data.SqlClient/src/Resources/Strings.Designer.cs @@ -60,6 +60,15 @@ internal Strings() { } } + /// + /// Looks up a localized string similar to Federated Identity Credentials authentication unavailable. The required environment variables are not fully configured.. + /// + internal static string AAD_FIC_Invalid_Setup { + get { + return ResourceManager.GetString("AAD_FIC_Invalid_Setup", resourceCulture); + } + } + /// /// Looks up a localized string similar to Connection timed out while retrieving an access token using '{0}' authentication method. Last error: {1}: {2}. /// diff --git a/src/Microsoft.Data.SqlClient/src/Resources/Strings.de.resx b/src/Microsoft.Data.SqlClient/src/Resources/Strings.de.resx index 4a6d349fd8..b3c9073cfd 100644 --- a/src/Microsoft.Data.SqlClient/src/Resources/Strings.de.resx +++ b/src/Microsoft.Data.SqlClient/src/Resources/Strings.de.resx @@ -4737,4 +4737,7 @@ Das Zertifikat ist während der Überprüfung des Zertifikats nicht verfügbar. + + Die Authentifizierung mit Federated Identity Credentials ist nicht verfügbar. Die erforderlichen Umgebungsvariablen sind nicht vollständig konfiguriert. + \ No newline at end of file diff --git a/src/Microsoft.Data.SqlClient/src/Resources/Strings.es.resx b/src/Microsoft.Data.SqlClient/src/Resources/Strings.es.resx index ff71f45c02..dd3a781a72 100644 --- a/src/Microsoft.Data.SqlClient/src/Resources/Strings.es.resx +++ b/src/Microsoft.Data.SqlClient/src/Resources/Strings.es.resx @@ -4737,4 +4737,7 @@ El certificado no está disponible al validar el certificado. + + La autenticación de credenciales de identidad federada no está disponible. Las variables de entorno necesarias no están totalmente configuradas. + \ No newline at end of file diff --git a/src/Microsoft.Data.SqlClient/src/Resources/Strings.fr.resx b/src/Microsoft.Data.SqlClient/src/Resources/Strings.fr.resx index 8b426074c2..75bb56d70c 100644 --- a/src/Microsoft.Data.SqlClient/src/Resources/Strings.fr.resx +++ b/src/Microsoft.Data.SqlClient/src/Resources/Strings.fr.resx @@ -4737,4 +4737,7 @@ Certificat non disponible lors de sa validation. + + L'authentification Federated Identity Credentials n'est pas disponible. Les variables d'environnement requises ne sont pas entièrement configurées. + \ No newline at end of file diff --git a/src/Microsoft.Data.SqlClient/src/Resources/Strings.it.resx b/src/Microsoft.Data.SqlClient/src/Resources/Strings.it.resx index b5cbd33bdd..6c97b894d5 100644 --- a/src/Microsoft.Data.SqlClient/src/Resources/Strings.it.resx +++ b/src/Microsoft.Data.SqlClient/src/Resources/Strings.it.resx @@ -4737,4 +4737,7 @@ Certificato non disponibile durante la convalida del certificato. + + Autenticazione Federated Identity Credentials non disponibile. Le variabili d'ambiente richieste non sono completamente configurate. + \ No newline at end of file diff --git a/src/Microsoft.Data.SqlClient/src/Resources/Strings.ja.resx b/src/Microsoft.Data.SqlClient/src/Resources/Strings.ja.resx index de5a1c1830..150115891b 100644 --- a/src/Microsoft.Data.SqlClient/src/Resources/Strings.ja.resx +++ b/src/Microsoft.Data.SqlClient/src/Resources/Strings.ja.resx @@ -4737,4 +4737,7 @@ 証明書の検証中は証明書を使用できません。 + + Federated Identity Credentials 認証が使用できません。必要な環境変数が完全に構成されていません。 + \ No newline at end of file diff --git a/src/Microsoft.Data.SqlClient/src/Resources/Strings.ko.resx b/src/Microsoft.Data.SqlClient/src/Resources/Strings.ko.resx index 20db99df33..db9fbdb1c8 100644 --- a/src/Microsoft.Data.SqlClient/src/Resources/Strings.ko.resx +++ b/src/Microsoft.Data.SqlClient/src/Resources/Strings.ko.resx @@ -4737,4 +4737,7 @@ 인증서의 유효성을 검사하는 동안에는 인증서를 사용할 수 없습니다. + + 페더레이션 ID 자격 증명 인증을 사용할 수 없습니다. 필수 환경 변수가 완전히 구성되지 않았습니다. + \ No newline at end of file diff --git a/src/Microsoft.Data.SqlClient/src/Resources/Strings.pt-BR.resx b/src/Microsoft.Data.SqlClient/src/Resources/Strings.pt-BR.resx index 911334848e..7657530a47 100644 --- a/src/Microsoft.Data.SqlClient/src/Resources/Strings.pt-BR.resx +++ b/src/Microsoft.Data.SqlClient/src/Resources/Strings.pt-BR.resx @@ -4737,4 +4737,7 @@ Certificado não disponível durante a validação do certificado. + + Autenticação de Credenciais de Identidade Federada indisponível. As variáveis de ambiente necessárias não estão totalmente configuradas. + \ No newline at end of file diff --git a/src/Microsoft.Data.SqlClient/src/Resources/Strings.resx b/src/Microsoft.Data.SqlClient/src/Resources/Strings.resx index c2dd68b867..3342e9da43 100644 --- a/src/Microsoft.Data.SqlClient/src/Resources/Strings.resx +++ b/src/Microsoft.Data.SqlClient/src/Resources/Strings.resx @@ -4740,4 +4740,7 @@ The certificate provided by the server does not match the certificate provided by the ServerCertificate option. - + + Federated Identity Credentials authentication unavailable. The required environment variables are not fully configured. + + \ No newline at end of file diff --git a/src/Microsoft.Data.SqlClient/src/Resources/Strings.ru.resx b/src/Microsoft.Data.SqlClient/src/Resources/Strings.ru.resx index cae6234541..acd63b6a06 100644 --- a/src/Microsoft.Data.SqlClient/src/Resources/Strings.ru.resx +++ b/src/Microsoft.Data.SqlClient/src/Resources/Strings.ru.resx @@ -4737,4 +4737,7 @@ Сертификат недоступен при проверке сертификата. + + Недоступна аутентификация Federated Identity Credentials. Необходимые переменные среды настроены не полностью. + \ No newline at end of file diff --git a/src/Microsoft.Data.SqlClient/src/Resources/Strings.zh-Hans.resx b/src/Microsoft.Data.SqlClient/src/Resources/Strings.zh-Hans.resx index 2b866bd6ed..6910a36ca0 100644 --- a/src/Microsoft.Data.SqlClient/src/Resources/Strings.zh-Hans.resx +++ b/src/Microsoft.Data.SqlClient/src/Resources/Strings.zh-Hans.resx @@ -4737,4 +4737,7 @@ 验证证书时证书不可用。 + + 联合身份凭证验证不可用。所需环境变量配置不全。 + \ No newline at end of file diff --git a/src/Microsoft.Data.SqlClient/src/Resources/Strings.zh-Hant.resx b/src/Microsoft.Data.SqlClient/src/Resources/Strings.zh-Hant.resx index 404f048cf6..2df58675bf 100644 --- a/src/Microsoft.Data.SqlClient/src/Resources/Strings.zh-Hant.resx +++ b/src/Microsoft.Data.SqlClient/src/Resources/Strings.zh-Hant.resx @@ -4737,4 +4737,7 @@ 驗證在進行憑證驗證時無法使用。 + + 聯合身份憑証騐証不可用。所需環境變量配置不全。 + \ No newline at end of file From f034c2611e8760aacc38da51c8a2cb492ea18881 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yiwen=20Wang=20=F0=9F=8C=8A?= Date: Sat, 15 Jun 2024 13:49:50 +0800 Subject: [PATCH 3/4] Cache fetched tokens --- .../ActiveDirectoryAuthenticationProvider.cs | 84 +++++++++++-------- 1 file changed, 47 insertions(+), 37 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ActiveDirectoryAuthenticationProvider.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ActiveDirectoryAuthenticationProvider.cs index bba22d2e2a..29dbb5c308 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ActiveDirectoryAuthenticationProvider.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ActiveDirectoryAuthenticationProvider.cs @@ -189,43 +189,9 @@ public override async Task AcquireTokenAsync(SqlAuthenti if (parameters.AuthenticationMethod == SqlAuthenticationMethod.ActiveDirectoryFederatedIdentityCredentials) { - // We'll keep using common environment variables defined in Azure.Identity library - // AZURE_TENANT_ID: The tenant ID where target resource hosts - // AZURE_CLIENT_ID: The client id of multi-tenant entra id app. May be overridden by the User Id. - // AZURE_MSI_CLIENT_ID (new): The client id of managed identity - string tenantId = Environment.GetEnvironmentVariable("AZURE_TENANT_ID"); - tenantId = string.IsNullOrWhiteSpace(tenantId) ? null : tenantId; - - if (clientId is null) - { - clientId = Environment.GetEnvironmentVariable("AZURE_CLIENT_ID"); - clientId = string.IsNullOrWhiteSpace(clientId) ? null : clientId; - } - - string managedIdentityClientId = Environment.GetEnvironmentVariable("AZURE_MSI_CLIENT_ID"); - managedIdentityClientId = string.IsNullOrWhiteSpace(managedIdentityClientId) ? null : managedIdentityClientId; - - ClientAssertionCredential clientAssertionCredential = null; - // If either tenant id, client id, or the managed client id are not specified when fetching the token, - // a CredentialUnavailableException will be thrown instead - if (!string.IsNullOrEmpty(tenantId) && !string.IsNullOrEmpty(clientId) && !string.IsNullOrEmpty(managedIdentityClientId)) - { - clientAssertionCredential = new ClientAssertionCredential( - tenantId, - clientId, - async (cancellationToken) => - { - ManagedIdentityCredential miCredential = new ManagedIdentityCredential(managedIdentityClientId); - var token = await miCredential.GetTokenAsync(new TokenRequestContext(new string[] { $"api://AzureADTokenExchange/.default" })).ConfigureAwait(false); - return token.Token; - }); - } - - if (clientAssertionCredential == null) - { - throw new CredentialUnavailableException(Strings.AAD_FIC_Invalid_Setup); - } - AccessToken accessToken = await clientAssertionCredential.GetTokenAsync(tokenRequestContext, cts.Token).ConfigureAwait(false); + // Cache FederatedIdentityCredentials based on authority and clientId + TokenCredentialKey tokenCredentialKey = new(typeof(ClientAssertionCredential), authority, string.Empty, string.Empty, clientId); + AccessToken accessToken = await GetTokenAsync(tokenCredentialKey, string.Empty, tokenRequestContext, cts.Token).ConfigureAwait(false); SqlClientEventSource.Log.TryTraceEvent("AcquireTokenAsync | Acquired access token for Federated Identity Credentials auth mode. Expiry Time: {0}", accessToken.ExpiresOn); return new SqlAuthenticationToken(accessToken.Token, accessToken.ExpiresOn); } @@ -655,6 +621,50 @@ private static TokenCredentialData CreateTokenCredentialInstance(TokenCredential return new TokenCredentialData(new WorkloadIdentityCredential(options), GetHash(secret)); } + else if (tokenCredentialKey._tokenCredentialType == typeof(ClientAssertionCredential)) + { + // We'll keep using common environment variables defined in Azure.Identity library + // AZURE_TENANT_ID: The tenant ID where target resource hosts + // AZURE_CLIENT_ID: The client id of multi-tenant entra id app. May be overridden by the User Id. + // AZURE_MSI_CLIENT_ID (new): The client id of managed identity + string tenantId = Environment.GetEnvironmentVariable("AZURE_TENANT_ID"); + tenantId = string.IsNullOrWhiteSpace(tenantId) ? null : tenantId; + + string clientId = tokenCredentialKey._clientId; + if (clientId is null) + { + clientId = Environment.GetEnvironmentVariable("AZURE_CLIENT_ID"); + clientId = string.IsNullOrWhiteSpace(clientId) ? null : clientId; + } + + string managedIdentityClientId = Environment.GetEnvironmentVariable("AZURE_MSI_CLIENT_ID"); + managedIdentityClientId = string.IsNullOrWhiteSpace(managedIdentityClientId) ? null : managedIdentityClientId; + + ClientAssertionCredential clientAssertionCredential = null; + // If either tenant id, client id, or the managed identity client id are not specified when fetching the token, + // a CredentialUnavailableException will be thrown instead + if (!string.IsNullOrEmpty(tenantId) && !string.IsNullOrEmpty(clientId) && !string.IsNullOrEmpty(managedIdentityClientId)) + { + clientAssertionCredential = new ClientAssertionCredential( + tenantId, + clientId, + async (cancellationToken) => + { + ManagedIdentityCredential miCredential = new ManagedIdentityCredential(managedIdentityClientId); + AccessToken token = await miCredential.GetTokenAsync( + new TokenRequestContext(new string[] { $"api://AzureADTokenExchange/.default" }), + cancellationToken).ConfigureAwait(false); + return token.Token; + }); + } + + if (clientAssertionCredential == null) + { + throw new CredentialUnavailableException(Strings.AAD_FIC_Invalid_Setup); + } + + return new TokenCredentialData(clientAssertionCredential, GetHash(secret)); + } // This should never be reached, but if it is, throw an exception that will be noticed during development throw new ArgumentException(nameof(ActiveDirectoryAuthenticationProvider)); From f43f52c253d40381d14a767e896c362cd355fe58 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yiwen=20Wang=20=F0=9F=8C=8A?= Date: Thu, 25 Jul 2024 02:21:35 +0800 Subject: [PATCH 4/4] Fix comments --- .../ActiveDirectoryAuthenticationProvider.cs | 24 +++++++++++-------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ActiveDirectoryAuthenticationProvider.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ActiveDirectoryAuthenticationProvider.cs index 9120ae2c55..0a1b4f584d 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ActiveDirectoryAuthenticationProvider.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ActiveDirectoryAuthenticationProvider.cs @@ -634,12 +634,8 @@ private static TokenCredentialData CreateTokenCredentialInstance(TokenCredential string tenantId = Environment.GetEnvironmentVariable("AZURE_TENANT_ID"); tenantId = string.IsNullOrWhiteSpace(tenantId) ? null : tenantId; - string clientId = tokenCredentialKey._clientId; - if (clientId is null) - { - clientId = Environment.GetEnvironmentVariable("AZURE_CLIENT_ID"); - clientId = string.IsNullOrWhiteSpace(clientId) ? null : clientId; - } + string clientId = Environment.GetEnvironmentVariable("AZURE_CLIENT_ID"); + clientId = string.IsNullOrWhiteSpace(clientId) ? null : clientId; string managedIdentityClientId = Environment.GetEnvironmentVariable("AZURE_MSI_CLIENT_ID"); managedIdentityClientId = string.IsNullOrWhiteSpace(managedIdentityClientId) ? null : managedIdentityClientId; @@ -654,9 +650,17 @@ private static TokenCredentialData CreateTokenCredentialInstance(TokenCredential clientId, async (cancellationToken) => { - ManagedIdentityCredential miCredential = new ManagedIdentityCredential(managedIdentityClientId); - AccessToken token = await miCredential.GetTokenAsync( - new TokenRequestContext(new string[] { $"api://AzureADTokenExchange/.default" }), + string tokenExchangeScope = "api://AzureADTokenExchange/.default"; + TokenCredentialKey miCredentialKey = new( + typeof(ManagedIdentityCredential), + tokenCredentialKey._authority, + tokenExchangeScope, + string.Empty, + managedIdentityClientId); + AccessToken token = await GetTokenAsync( + miCredentialKey, + string.Empty, + new TokenRequestContext(new string[] { tokenExchangeScope }), cancellationToken).ConfigureAwait(false); return token.Token; }); @@ -667,7 +671,7 @@ private static TokenCredentialData CreateTokenCredentialInstance(TokenCredential throw new CredentialUnavailableException(Strings.AAD_FIC_Invalid_Setup); } - return new TokenCredentialData(clientAssertionCredential, GetHash(secret)); + return new TokenCredentialData(clientAssertionCredential, null); } // This should never be reached, but if it is, throw an exception that will be noticed during development