From 94b968fdb8181f2a26794ebc78e310343d3d41fa Mon Sep 17 00:00:00 2001 From: Preben Huybrechts Date: Thu, 28 Sep 2023 16:09:31 +0200 Subject: [PATCH 1/3] Add a SQL Authentication Method for AzureCli credential --- .../SqlAuthenticationMethod.xml | 4 ++++ .../netcore/ref/Microsoft.Data.SqlClient.cs | 2 ++ ...AuthenticationProviderManager.NetCoreApp.cs | 2 ++ .../SqlAuthenticationProviderManager.cs | 2 ++ .../Microsoft/Data/SqlClient/SqlConnection.cs | 17 +++++++++++++++++ .../Data/SqlClient/SqlInternalConnectionTds.cs | 3 +++ .../src/Microsoft/Data/SqlClient/TdsParser.cs | 1 + .../SqlAuthenticationProviderManager.cs | 4 ++++ .../Microsoft/Data/SqlClient/SqlConnection.cs | 18 ++++++++++++++++++ .../Data/SqlClient/SqlInternalConnectionTds.cs | 6 +++++- .../src/Microsoft/Data/SqlClient/TdsParser.cs | 4 ++++ .../Data/Common/DbConnectionStringCommon.cs | 12 +++++++++++- .../ActiveDirectoryAuthenticationProvider.cs | 14 +++++++++++++- .../Data/SqlClient/SqlConnectionString.cs | 4 ++++ .../src/Microsoft/Data/SqlClient/TdsEnums.cs | 2 ++ 15 files changed, 92 insertions(+), 3 deletions(-) diff --git a/doc/snippets/Microsoft.Data.SqlClient/SqlAuthenticationMethod.xml b/doc/snippets/Microsoft.Data.SqlClient/SqlAuthenticationMethod.xml index a52a2ec41a..f795a15bf1 100644 --- a/doc/snippets/Microsoft.Data.SqlClient/SqlAuthenticationMethod.xml +++ b/doc/snippets/Microsoft.Data.SqlClient/SqlAuthenticationMethod.xml @@ -45,5 +45,9 @@ The authentication method uses Active Directory Default. Use this mode to connect to a SQL Database using multiple non-interactive authentication methods tried sequentially to acquire an access token. This method does not fallback to the "Active Directory Interactive" authentication method. 9 + + The authentication method uses Active Directory Azure CLI credential. Use this mode to connect to a SQL Database using AzureCliCredential to acquire an access token. This method does not fallback to the "Active Directory Interactive" authentication method. + 9 + 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 92cb042b76..dc1f80196a 100644 --- a/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.cs +++ b/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.cs @@ -176,6 +176,8 @@ public enum SqlAuthenticationMethod ActiveDirectoryMSI = 8, /// ActiveDirectoryDefault = 9, + /// + ActiveDirectoryAzureCli = 10, /// NotSpecified = 0, /// 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 094114f357..7129b7941e 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 @@ -153,6 +153,8 @@ private static SqlAuthenticationMethod AuthenticationEnumFromString(string authe return SqlAuthenticationMethod.ActiveDirectoryMSI; case ActiveDirectoryDefault: return SqlAuthenticationMethod.ActiveDirectoryDefault; + case ActiveDirectoryAzureCli: + return SqlAuthenticationMethod.ActiveDirectoryAzureCli; 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 401fc23466..90a8a838da 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 @@ -20,6 +20,7 @@ internal partial class SqlAuthenticationProviderManager private const string ActiveDirectoryManagedIdentity = "active directory managed identity"; private const string ActiveDirectoryMSI = "active directory msi"; private const string ActiveDirectoryDefault = "active directory default"; + private const string ActiveDirectoryAzureCli = "active directory azure cli"; private readonly IReadOnlyCollection _authenticationsWithAppSpecifiedProvider; private readonly ConcurrentDictionary _providers; @@ -45,6 +46,7 @@ private static void SetDefaultAuthProviders(SqlAuthenticationProviderManager ins instance.SetProvider(SqlAuthenticationMethod.ActiveDirectoryManagedIdentity, activeDirectoryAuthProvider); instance.SetProvider(SqlAuthenticationMethod.ActiveDirectoryMSI, activeDirectoryAuthProvider); instance.SetProvider(SqlAuthenticationMethod.ActiveDirectoryDefault, activeDirectoryAuthProvider); + instance.SetProvider(SqlAuthenticationMethod.ActiveDirectoryAzureCli, 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 e314171b23..bf4cc0c881 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 @@ -202,6 +202,10 @@ public SqlConnection(string connectionString, SqlCredential credential) : this() { throw SQL.SettingCredentialWithNonInteractiveArgument(DbConnectionStringBuilderUtil.ActiveDirectoryDefaultString); } + else if (UsesActiveDirectoryAzureCli(connectionOptions)) + { + throw SQL.SettingCredentialWithNonInteractiveArgument(DbConnectionStringBuilderUtil.ActiveDirectoryAzureCliString); + } Credential = credential; } @@ -530,6 +534,11 @@ private bool UsesActiveDirectoryDefault(SqlConnectionString opt) return opt != null && opt.Authentication == SqlAuthenticationMethod.ActiveDirectoryDefault; } + private bool UsesActiveDirectoryAzureCli(SqlConnectionString opt) + { + return opt != null && opt.Authentication == SqlAuthenticationMethod.ActiveDirectoryAzureCli; + } + private bool UsesAuthentication(SqlConnectionString opt) { return opt != null && opt.Authentication != SqlAuthenticationMethod.NotSpecified; @@ -619,6 +628,10 @@ public override string ConnectionString { throw SQL.SettingNonInteractiveWithCredential(DbConnectionStringBuilderUtil.ActiveDirectoryDefaultString); } + else if (UsesActiveDirectoryAzureCli(connectionOptions)) + { + throw SQL.SettingNonInteractiveWithCredential(DbConnectionStringBuilderUtil.ActiveDirectoryAzureCliString); + } CheckAndThrowOnInvalidCombinationOfConnectionStringAndSqlCredential(connectionOptions); } @@ -999,6 +1012,10 @@ public SqlCredential Credential { throw SQL.SettingCredentialWithNonInteractiveInvalid(DbConnectionStringBuilderUtil.ActiveDirectoryDefaultString); } + else if (UsesActiveDirectoryAzureCli(connectionOptions)) + { + throw SQL.SettingCredentialWithNonInteractiveInvalid(DbConnectionStringBuilderUtil.ActiveDirectoryAzureCliString); + } 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 6a0ee2e0e0..f4e7e479cd 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 @@ -1330,6 +1330,7 @@ private void Login(ServerInfo server, TimeoutTimer timeout, string newPassword, || ConnectionOptions.Authentication == SqlAuthenticationMethod.ActiveDirectoryManagedIdentity || ConnectionOptions.Authentication == SqlAuthenticationMethod.ActiveDirectoryMSI || ConnectionOptions.Authentication == SqlAuthenticationMethod.ActiveDirectoryDefault + || ConnectionOptions.Authentication == SqlAuthenticationMethod.ActiveDirectoryAzureCli // Since AD Integrated may be acting like Windows integrated, additionally check _fedAuthRequired || (ConnectionOptions.Authentication == SqlAuthenticationMethod.ActiveDirectoryIntegrated && _fedAuthRequired) || _accessTokenCallback != null) @@ -2159,6 +2160,7 @@ internal void OnFedAuthInfo(SqlFedAuthInfo fedAuthInfo) || ConnectionOptions.Authentication == SqlAuthenticationMethod.ActiveDirectoryManagedIdentity || ConnectionOptions.Authentication == SqlAuthenticationMethod.ActiveDirectoryMSI || ConnectionOptions.Authentication == SqlAuthenticationMethod.ActiveDirectoryDefault + || ConnectionOptions.Authentication == SqlAuthenticationMethod.ActiveDirectoryAzureCli || (ConnectionOptions.Authentication == SqlAuthenticationMethod.ActiveDirectoryIntegrated && _fedAuthRequired), "Credentials aren't provided for calling MSAL"); Debug.Assert(fedAuthInfo != null, "info should not be null."); @@ -2406,6 +2408,7 @@ internal SqlFedAuthToken GetFedAuthToken(SqlFedAuthInfo fedAuthInfo) case SqlAuthenticationMethod.ActiveDirectoryManagedIdentity: case SqlAuthenticationMethod.ActiveDirectoryMSI: case SqlAuthenticationMethod.ActiveDirectoryDefault: + case SqlAuthenticationMethod.ActiveDirectoryAzureCli: 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 90084028b3..a6b4025a56 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 @@ -7944,6 +7944,7 @@ internal int WriteFedAuthFeatureRequest(FederatedAuthenticationFeatureExtensionD workflow = TdsEnums.MSALWORKFLOW_ACTIVEDIRECTORYMANAGEDIDENTITY; break; case SqlAuthenticationMethod.ActiveDirectoryDefault: + case SqlAuthenticationMethod.ActiveDirectoryAzureCli: workflow = TdsEnums.MSALWORKFLOW_ACTIVEDIRECTORYDEFAULT; break; default: 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 d0757807be..e849a181ac 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 @@ -22,6 +22,7 @@ internal class SqlAuthenticationProviderManager private const string ActiveDirectoryManagedIdentity = "active directory managed identity"; private const string ActiveDirectoryMSI = "active directory msi"; private const string ActiveDirectoryDefault = "active directory default"; + private const string ActiveDirectoryAzureCli = "active directory azure cli"; static SqlAuthenticationProviderManager() { @@ -52,6 +53,7 @@ static SqlAuthenticationProviderManager() Instance.SetProvider(SqlAuthenticationMethod.ActiveDirectoryManagedIdentity, activeDirectoryAuthProvider); Instance.SetProvider(SqlAuthenticationMethod.ActiveDirectoryMSI, activeDirectoryAuthProvider); Instance.SetProvider(SqlAuthenticationMethod.ActiveDirectoryDefault, activeDirectoryAuthProvider); + Instance.SetProvider(SqlAuthenticationMethod.ActiveDirectoryAzureCli, activeDirectoryAuthProvider); } public static readonly SqlAuthenticationProviderManager Instance; @@ -231,6 +233,8 @@ private static SqlAuthenticationMethod AuthenticationEnumFromString(string authe return SqlAuthenticationMethod.ActiveDirectoryMSI; case ActiveDirectoryDefault: return SqlAuthenticationMethod.ActiveDirectoryDefault; + case ActiveDirectoryAzureCli: + return SqlAuthenticationMethod.ActiveDirectoryAzureCli; 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 a329d626b0..b5d330aa75 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 @@ -405,6 +405,11 @@ public SqlConnection(string connectionString, SqlCredential credential) : this() throw SQL.SettingCredentialWithNonInteractiveArgument(DbConnectionStringBuilderUtil.ActiveDirectoryDefaultString); } + if (UsesActiveDirectoryAzureCli(connectionOptions)) + { + throw SQL.SettingCredentialWithNonInteractiveArgument(DbConnectionStringBuilderUtil.ActiveDirectoryAzureCliString); + } + Credential = credential; } // else @@ -627,6 +632,11 @@ private bool UsesActiveDirectoryDefault(SqlConnectionString opt) return opt != null && opt.Authentication == SqlAuthenticationMethod.ActiveDirectoryDefault; } + private bool UsesActiveDirectoryAzureCli(SqlConnectionString opt) + { + return opt != null && opt.Authentication == SqlAuthenticationMethod.ActiveDirectoryAzureCli; + } + private bool UsesAuthentication(SqlConnectionString opt) { return opt != null && opt.Authentication != SqlAuthenticationMethod.NotSpecified; @@ -834,6 +844,10 @@ override public string ConnectionString { throw SQL.SettingNonInteractiveWithCredential(DbConnectionStringBuilderUtil.ActiveDirectoryDefaultString); } + else if (UsesActiveDirectoryAzureCli(connectionOptions)) + { + throw SQL.SettingNonInteractiveWithCredential(DbConnectionStringBuilderUtil.ActiveDirectoryAzureCliString); + } CheckAndThrowOnInvalidCombinationOfConnectionStringAndSqlCredential(connectionOptions); } @@ -1183,6 +1197,10 @@ public SqlCredential Credential { throw SQL.SettingCredentialWithNonInteractiveInvalid(DbConnectionStringBuilderUtil.ActiveDirectoryDefaultString); } + else if (UsesActiveDirectoryAzureCli(connectionOptions)) + { + throw SQL.SettingCredentialWithNonInteractiveInvalid(DbConnectionStringBuilderUtil.ActiveDirectoryAzureCliString); + } 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 068b37dc71..499e432247 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 @@ -1595,6 +1595,7 @@ private void Login(ServerInfo server, TimeoutTimer timeout, string newPassword, || ConnectionOptions.Authentication == SqlAuthenticationMethod.ActiveDirectoryManagedIdentity || ConnectionOptions.Authentication == SqlAuthenticationMethod.ActiveDirectoryMSI || ConnectionOptions.Authentication == SqlAuthenticationMethod.ActiveDirectoryDefault + || ConnectionOptions.Authentication == SqlAuthenticationMethod.ActiveDirectoryAzureCli // Since AD Integrated may be acting like Windows integrated, additionally check _fedAuthRequired || (ConnectionOptions.Authentication == SqlAuthenticationMethod.ActiveDirectoryIntegrated && _fedAuthRequired) || _accessTokenCallback != null) @@ -1991,7 +1992,8 @@ private bool ShouldDisableTnir(SqlConnectionString connectionOptions) connectionOptions.Authentication == SqlAuthenticationMethod.ActiveDirectoryDeviceCodeFlow || connectionOptions.Authentication == SqlAuthenticationMethod.ActiveDirectoryManagedIdentity || connectionOptions.Authentication == SqlAuthenticationMethod.ActiveDirectoryMSI || - connectionOptions.Authentication == SqlAuthenticationMethod.ActiveDirectoryDefault; + connectionOptions.Authentication == SqlAuthenticationMethod.ActiveDirectoryDefault || + connectionOptions.Authentication == SqlAuthenticationMethod.ActiveDirectoryAzureCli; // 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. @@ -2585,6 +2587,7 @@ internal void OnFedAuthInfo(SqlFedAuthInfo fedAuthInfo) || ConnectionOptions.Authentication == SqlAuthenticationMethod.ActiveDirectoryManagedIdentity || ConnectionOptions.Authentication == SqlAuthenticationMethod.ActiveDirectoryMSI || ConnectionOptions.Authentication == SqlAuthenticationMethod.ActiveDirectoryDefault + || ConnectionOptions.Authentication == SqlAuthenticationMethod.ActiveDirectoryAzureCli || ConnectionOptions.Authentication == SqlAuthenticationMethod.ActiveDirectoryDeviceCodeFlow || (ConnectionOptions.Authentication == SqlAuthenticationMethod.ActiveDirectoryIntegrated && _fedAuthRequired), "Credentials aren't provided for calling MSAL"); @@ -2819,6 +2822,7 @@ internal SqlFedAuthToken GetFedAuthToken(SqlFedAuthInfo fedAuthInfo) case SqlAuthenticationMethod.ActiveDirectoryDeviceCodeFlow: case SqlAuthenticationMethod.ActiveDirectoryManagedIdentity: case SqlAuthenticationMethod.ActiveDirectoryMSI: + case SqlAuthenticationMethod.ActiveDirectoryAzureCli: case SqlAuthenticationMethod.ActiveDirectoryDefault: if (_activeDirectoryAuthTimeoutRetryHelper.State == ActiveDirectoryAuthenticationTimeoutRetryState.Retrying) { 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 b06bb167da..a37b801861 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.ActiveDirectoryDefault: SqlClientEventSource.Log.TryTraceEvent(" Active Directory Default authentication"); break; + case SqlAuthenticationMethod.ActiveDirectoryAzureCli: + SqlClientEventSource.Log.TryTraceEvent(" Active Directory Azure CLI authentication"); + break; case SqlAuthenticationMethod.SqlPassword: SqlClientEventSource.Log.TryTraceEvent(" SQL Password authentication"); break; @@ -8741,6 +8744,7 @@ internal int WriteFedAuthFeatureRequest(FederatedAuthenticationFeatureExtensionD case SqlAuthenticationMethod.ActiveDirectoryMSI: workflow = TdsEnums.MSALWORKFLOW_ACTIVEDIRECTORYMANAGEDIDENTITY; break; + case SqlAuthenticationMethod.ActiveDirectoryAzureCli: case SqlAuthenticationMethod.ActiveDirectoryDefault: workflow = TdsEnums.MSALWORKFLOW_ACTIVEDIRECTORYDEFAULT; break; 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 6bef73963b..0940ee3da9 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/DbConnectionStringCommon.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/DbConnectionStringCommon.cs @@ -347,6 +347,7 @@ internal static ApplicationIntent ConvertToApplicationIntent(string keyword, obj internal const string ActiveDirectoryManagedIdentityString = "Active Directory Managed Identity"; internal const string ActiveDirectoryMSIString = "Active Directory MSI"; internal const string ActiveDirectoryDefaultString = "Active Directory Default"; + internal const string ActiveDirectoryAzureCliString = "Active Directory Azure CLI"; const string SqlCertificateString = "Sql Certificate"; #if DEBUG @@ -361,7 +362,8 @@ internal static ApplicationIntent ConvertToApplicationIntent(string keyword, obj "ActiveDirectoryDeviceCodeFlow", "ActiveDirectoryManagedIdentity", "ActiveDirectoryMSI", - "ActiveDirectoryDefault" + "ActiveDirectoryDefault", + "ActiveDirectoryAzureCli" }; private static bool IsValidAuthenticationMethodEnum() @@ -444,6 +446,12 @@ internal static bool TryConvertToAuthenticationType(string value, out SqlAuthent result = SqlAuthenticationMethod.ActiveDirectoryDefault; isSuccess = true; } + else if (StringComparer.InvariantCultureIgnoreCase.Equals(value, ActiveDirectoryAzureCliString) + || StringComparer.InvariantCultureIgnoreCase.Equals(value, Convert.ToString(SqlAuthenticationMethod.ActiveDirectoryAzureCli, CultureInfo.InvariantCulture))) + { + result = SqlAuthenticationMethod.ActiveDirectoryAzureCli; + isSuccess = true; + } #if ADONET_CERT_AUTH && NETFRAMEWORK else if (StringComparer.InvariantCultureIgnoreCase.Equals(value, SqlCertificateString) || StringComparer.InvariantCultureIgnoreCase.Equals(value, Convert.ToString(SqlAuthenticationMethod.SqlCertificate, CultureInfo.InvariantCulture))) { @@ -526,6 +534,7 @@ internal static bool IsValidAuthenticationTypeValue(SqlAuthenticationMethod valu || value == SqlAuthenticationMethod.ActiveDirectoryManagedIdentity || value == SqlAuthenticationMethod.ActiveDirectoryMSI || value == SqlAuthenticationMethod.ActiveDirectoryDefault + || value == SqlAuthenticationMethod.ActiveDirectoryAzureCli #if ADONET_CERT_AUTH && NETFRAMEWORK || value == SqlAuthenticationMethod.SqlCertificate #endif @@ -547,6 +556,7 @@ internal static string AuthenticationTypeToString(SqlAuthenticationMethod value) SqlAuthenticationMethod.ActiveDirectoryManagedIdentity => ActiveDirectoryManagedIdentityString, SqlAuthenticationMethod.ActiveDirectoryMSI => ActiveDirectoryMSIString, SqlAuthenticationMethod.ActiveDirectoryDefault => ActiveDirectoryDefaultString, + SqlAuthenticationMethod.ActiveDirectoryAzureCli => ActiveDirectoryAzureCliString, #if ADONET_CERT_AUTH && NETFRAMEWORK SqlAuthenticationMethod.SqlCertificate => SqlCertificateString, #endif 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 60151f1be1..088a8c93a1 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ActiveDirectoryAuthenticationProvider.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ActiveDirectoryAuthenticationProvider.cs @@ -84,7 +84,8 @@ public override bool IsSupported(SqlAuthenticationMethod authentication) || authentication == SqlAuthenticationMethod.ActiveDirectoryDeviceCodeFlow || authentication == SqlAuthenticationMethod.ActiveDirectoryManagedIdentity || authentication == SqlAuthenticationMethod.ActiveDirectoryMSI - || authentication == SqlAuthenticationMethod.ActiveDirectoryDefault; + || authentication == SqlAuthenticationMethod.ActiveDirectoryDefault + || authentication == SqlAuthenticationMethod.ActiveDirectoryAzureCli; } /// @@ -167,6 +168,17 @@ public override async Task AcquireTokenAsync(SqlAuthenti SqlClientEventSource.Log.TryTraceEvent("AcquireTokenAsync | Acquired access token for Default auth mode. Expiry Time: {0}", accessToken.ExpiresOn); return new SqlAuthenticationToken(accessToken.Token, accessToken.ExpiresOn); } + if (parameters.AuthenticationMethod == SqlAuthenticationMethod.ActiveDirectoryAzureCli) + { + AzureCliCredentialOptions azureCliCredentialOptions = new() + { + AuthorityHost = new Uri(authority), + }; + + AccessToken accessToken = await new AzureCliCredential(azureCliCredentialOptions).GetTokenAsync(tokenRequestContext, cts.Token).ConfigureAwait(false); + SqlClientEventSource.Log.TryTraceEvent("AcquireTokenAsync | Acquired access token for Azure CLI auth mode. Expiry Time: {0}", accessToken.ExpiresOn); + return new SqlAuthenticationToken(accessToken.Token, accessToken.ExpiresOn); + } TokenCredentialOptions tokenCredentialOptions = new TokenCredentialOptions() { AuthorityHost = new Uri(authority) }; 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 65d8dcc9cf..814c7a2972 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionString.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionString.cs @@ -626,6 +626,10 @@ internal SqlConnectionString(string connectionString) : base(connectionString, G { throw SQL.NonInteractiveWithPassword(DbConnectionStringBuilderUtil.ActiveDirectoryDefaultString); } + if (Authentication == SqlAuthenticationMethod.ActiveDirectoryAzureCli && _hasPasswordKeyword) + { + throw SQL.NonInteractiveWithPassword(DbConnectionStringBuilderUtil.ActiveDirectoryAzureCliString); + } #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 2e727be277..d39d6d32e5 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsEnums.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsEnums.cs @@ -1212,6 +1212,8 @@ public enum SqlAuthenticationMethod /// ActiveDirectoryDefault, + /// + ActiveDirectoryAzureCli, #if ADONET_CERT_AUTH && NETFRAMEWORK SqlCertificate #endif From fc43100ffb476e34163f788a35cefa63da5a2a8a Mon Sep 17 00:00:00 2001 From: Preben Huybrechts Date: Fri, 29 Sep 2023 07:23:05 +0200 Subject: [PATCH 2/3] Added tests --- .../Data/Common/DbConnectionStringCommon.cs | 2 +- .../SqlConnectionStringBuilderTest.cs | 1 + .../ConnectivityTests/AADConnectionTest.cs | 58 +++++++++++++++++++ 3 files changed, 60 insertions(+), 1 deletion(-) 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 0940ee3da9..705009ddfe 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/DbConnectionStringCommon.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/DbConnectionStringCommon.cs @@ -524,7 +524,7 @@ internal static string ColumnEncryptionSettingToString(SqlConnectionColumnEncryp internal static bool IsValidAuthenticationTypeValue(SqlAuthenticationMethod value) { - Debug.Assert(Enum.GetNames(typeof(SqlAuthenticationMethod)).Length == 10, "SqlAuthenticationMethod enum has changed, update needed"); + Debug.Assert(Enum.GetNames(typeof(SqlAuthenticationMethod)).Length == 11, "SqlAuthenticationMethod enum has changed, update needed"); return value == SqlAuthenticationMethod.SqlPassword || value == SqlAuthenticationMethod.ActiveDirectoryPassword || value == SqlAuthenticationMethod.ActiveDirectoryIntegrated diff --git a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlConnectionStringBuilderTest.cs b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlConnectionStringBuilderTest.cs index b3a090f58f..f1a8f3168b 100644 --- a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlConnectionStringBuilderTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlConnectionStringBuilderTest.cs @@ -38,6 +38,7 @@ public partial class SqlConnectionStringBuilderTest [InlineData("Authentication = Active Directory MSI ")] [InlineData("Authentication = ActiveDirectoryMSI ")] [InlineData("Authentication = Active Directory Default ")] + [InlineData("Authentication = Active Directory Azure CLI ")] [InlineData("Authentication = ActiveDirectoryDefault ")] [InlineData("Command Timeout = 5")] [InlineData("Command Timeout = 15")] 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 06080a9ca6..a388511c5f 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConnectivityTests/AADConnectionTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConnectivityTests/AADConnectionTest.cs @@ -576,6 +576,64 @@ public static void ActiveDirectoryDefaultWithAccessTokenCallbackMustFail() Assert.Contains(expectedMessage, e.Message); } + [ConditionalFact(nameof(IsAADConnStringsSetup))] + public static void ActiveDirectoryAzureCliWithCredentialsMustFail() + { + // connection fails with expected error message. + string[] credKeys = { "Authentication", "User ID", "Password", "UID", "PWD" }; + string connStrWithNoCred = DataTestUtility.RemoveKeysInConnStr(DataTestUtility.AADPasswordConnectionString, credKeys) + + "Authentication=Active Directory Azure CLI;"; + + 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 Azure CLI' has been specified in the connection string."; + Assert.Contains(expectedMessage, e.Message); + } + + [ConditionalFact(nameof(IsAADConnStringsSetup))] + public static void ActiveDirectoryAzureCliWithPasswordMustFail() + { + // connection fails with expected error message. + string[] credKeys = { "Authentication", "User ID", "Password", "UID", "PWD" }; + string connStrWithNoCred = DataTestUtility.RemoveKeysInConnStr(DataTestUtility.AADPasswordConnectionString, credKeys) + + "Authentication=ActiveDirectoryAzureCLI; Password=anything"; + + ArgumentException e = Assert.Throws(() => ConnectAndDisconnect(connStrWithNoCred)); + + string expectedMessage = "Cannot use 'Authentication=Active Directory Azure CLI' with 'Password' or 'PWD' connection string keywords."; + Assert.Contains(expectedMessage, e.Message); + } + + [ConditionalFact(nameof(IsAADConnStringsSetup))] + public static void ActiveDirectoryAzureCliWithAccessTokenCallbackMustFail() + { + // connection fails with expected error message. + string[] credKeys = { "Authentication", "User ID", "Password", "UID", "PWD" }; + string connStrWithNoCred = DataTestUtility.RemoveKeysInConnStr(DataTestUtility.AADPasswordConnectionString, credKeys) + + "Authentication=ActiveDirectoryAzureCLI"; + InvalidOperationException e = Assert.Throws(() => + { + using (SqlConnection conn = new SqlConnection(connStrWithNoCred)) + { + conn.AccessTokenCallback = (ctx, token) => + Task.FromResult(new SqlAuthenticationToken("my token", DateTimeOffset.MaxValue)); + conn.Open(); + + Assert.NotEqual(System.Data.ConnectionState.Open, conn.State); + } + }); + + string expectedMessage = "Cannot set the AccessTokenCallback property if 'Authentication=Active Directory Default' has been specified in the connection string."; + Assert.Contains(expectedMessage, e.Message); + } + [ConditionalFact(nameof(IsAADConnStringsSetup))] public static void AccessTokenCallbackMustOpenPassAndChangePropertyFail() { From fb307ad0b76a533526af27c44571b6a863377723 Mon Sep 17 00:00:00 2001 From: Preben Huybrechts <1079843+pregress@users.noreply.github.com> Date: Thu, 2 Nov 2023 08:22:27 +0100 Subject: [PATCH 3/3] Update doc/snippets/Microsoft.Data.SqlClient/SqlAuthenticationMethod.xml Co-authored-by: campersau --- .../Microsoft.Data.SqlClient/SqlAuthenticationMethod.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/snippets/Microsoft.Data.SqlClient/SqlAuthenticationMethod.xml b/doc/snippets/Microsoft.Data.SqlClient/SqlAuthenticationMethod.xml index f795a15bf1..91f6c6b1a5 100644 --- a/doc/snippets/Microsoft.Data.SqlClient/SqlAuthenticationMethod.xml +++ b/doc/snippets/Microsoft.Data.SqlClient/SqlAuthenticationMethod.xml @@ -47,7 +47,7 @@ The authentication method uses Active Directory Azure CLI credential. Use this mode to connect to a SQL Database using AzureCliCredential to acquire an access token. This method does not fallback to the "Active Directory Interactive" authentication method. - 9 + 10