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 10685c5e26..170c13f4c6 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 @@ -8321,6 +8321,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 0def855ddd..790aeb4197 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; @@ -9138,6 +9141,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 726410f741..0a1b4f584d 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; } /// @@ -190,6 +191,15 @@ public override async Task AcquireTokenAsync(SqlAuthenti return new SqlAuthenticationToken(accessToken.Token, accessToken.ExpiresOn); } + if (parameters.AuthenticationMethod == SqlAuthenticationMethod.ActiveDirectoryFederatedIdentityCredentials) + { + // 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); + } + /* * 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 @@ -615,6 +625,54 @@ 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 = 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) => + { + 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; + }); + } + + if (clientAssertionCredential == null) + { + throw new CredentialUnavailableException(Strings.AAD_FIC_Invalid_Setup); + } + + return new TokenCredentialData(clientAssertionCredential, null); + } // This should never be reached, but if it is, throw an exception that will be noticed during development throw new ArgumentException(nameof(ActiveDirectoryAuthenticationProvider)); 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/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.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/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 43c69919d6..c28624f1f8 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConnectivityTests/AADConnectionTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConnectivityTests/AADConnectionTest.cs @@ -475,6 +475,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() {