From 7687984ddef4d0411ffa13f9862d4be61fd173e5 Mon Sep 17 00:00:00 2001 From: Cheena Malhotra Date: Tue, 9 Jun 2020 18:22:14 -0700 Subject: [PATCH 01/14] Introduce support for AAD Device Code Flow authentication --- .../SqlAuthenticationMethod.xml | 4 + .../SqlAuthenticationProvider.xml | 6 + .../netcore/ref/Microsoft.Data.SqlClient.cs | 4 + .../ref/Microsoft.Data.SqlClient.csproj | 3 + .../Data/Common/DbConnectionStringCommon.cs | 12 +- ...uthenticationProviderManager.NetCoreApp.cs | 3 + ...thenticationProviderManager.NetStandard.cs | 1 + .../SqlAuthenticationProviderManager.cs | 1 + .../Microsoft/Data/SqlClient/SqlConnection.cs | 20 ++- .../Data/SqlClient/SqlConnectionFactory.cs | 6 +- .../Data/SqlClient/SqlConnectionString.cs | 5 + .../SqlClient/SqlInternalConnectionTds.cs | 3 + .../src/Microsoft/Data/SqlClient/SqlUtil.cs | 21 +++ .../src/Microsoft/Data/SqlClient/TdsEnums.cs | 5 + .../src/Microsoft/Data/SqlClient/TdsParser.cs | 6 + .../netcore/src/Resources/SR.Designer.cs | 36 +++++ .../netcore/src/Resources/SR.resx | 12 ++ .../netfx/ref/Microsoft.Data.SqlClient.cs | 4 + .../netfx/ref/Microsoft.Data.SqlClient.csproj | 3 + .../Data/Common/DbConnectionStringCommon.cs | 12 +- .../SqlAuthenticationProviderManager.cs | 4 + .../Microsoft/Data/SqlClient/SqlConnection.cs | 22 ++- .../Data/SqlClient/SqlConnectionString.cs | 5 + .../SqlClient/SqlInternalConnectionTds.cs | 8 +- .../src/Microsoft/Data/SqlClient/SqlUtil.cs | 23 +++- .../src/Microsoft/Data/SqlClient/TdsEnums.cs | 5 + .../src/Microsoft/Data/SqlClient/TdsParser.cs | 6 + .../netfx/src/Resources/Strings.Designer.cs | 36 +++++ .../netfx/src/Resources/Strings.resx | 12 ++ .../ActiveDirectoryAuthenticationProvider.cs | 127 ++++++++++++------ .../SqlClient/SqlAuthenticationProvider.cs | 5 + 31 files changed, 367 insertions(+), 53 deletions(-) diff --git a/doc/snippets/Microsoft.Data.SqlClient/SqlAuthenticationMethod.xml b/doc/snippets/Microsoft.Data.SqlClient/SqlAuthenticationMethod.xml index 4bb3bdb696..bf32a65435 100644 --- a/doc/snippets/Microsoft.Data.SqlClient/SqlAuthenticationMethod.xml +++ b/doc/snippets/Microsoft.Data.SqlClient/SqlAuthenticationMethod.xml @@ -29,5 +29,9 @@ The authentication method uses Active Directory Service Principal. Use Active Directory Service Principal to connect to a SQL Database using the client ID and secret of a service principal identity. 5 + + The authentication method uses Active Directory Device Code Flow. Use Active Directory Device Code Flow to connect to a SQL Database from devices and operating systems that do not provide a Web browser, using another device to perform Interactive authentication. + 6 + diff --git a/doc/snippets/Microsoft.Data.SqlClient/SqlAuthenticationProvider.xml b/doc/snippets/Microsoft.Data.SqlClient/SqlAuthenticationProvider.xml index e6f373c300..83e66a9473 100644 --- a/doc/snippets/Microsoft.Data.SqlClient/SqlAuthenticationProvider.xml +++ b/doc/snippets/Microsoft.Data.SqlClient/SqlAuthenticationProvider.xml @@ -45,5 +45,11 @@ Represents an asynchronous operation that returns the AD authentication token. To be added. + + The Active Directory authentication parameters passed by the driver to authentication providers. + The callback function that receives Device Code Flow Result with Device Code that is used for authentication by 'Active Directory Device Code Flow' authentication provider. + Acquires a security token from the authority. + Represents an asynchronous operation that returns the AD authentication token. + 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 b3b5097ed6..8da7326ace 100644 --- a/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.cs +++ b/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.cs @@ -69,6 +69,8 @@ public enum SqlAuthenticationMethod ActiveDirectoryPassword = 2, /// ActiveDirectoryServicePrincipal = 5, + /// + ActiveDirectoryDeviceCodeFlow = 6, /// NotSpecified = 0, /// @@ -103,6 +105,8 @@ public abstract partial class SqlAuthenticationProvider protected SqlAuthenticationProvider() { } /// public abstract System.Threading.Tasks.Task AcquireTokenAsync(Microsoft.Data.SqlClient.SqlAuthenticationParameters parameters); + /// + public abstract System.Threading.Tasks.Task AcquireTokenAsync(Microsoft.Data.SqlClient.SqlAuthenticationParameters parameters, System.Func deviceCodeResultCallback); /// public virtual void BeforeLoad(Microsoft.Data.SqlClient.SqlAuthenticationMethod authenticationMethod) { } /// diff --git a/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.csproj b/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.csproj index b1934599e6..c1c48f2891 100644 --- a/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.csproj @@ -15,6 +15,9 @@ + + + diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/Common/DbConnectionStringCommon.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/Common/DbConnectionStringCommon.cs index 73f005d548..7bd9f2c909 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/Common/DbConnectionStringCommon.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/Common/DbConnectionStringCommon.cs @@ -103,10 +103,11 @@ internal static string ConvertToString(object value) const string ActiveDirectoryIntegratedString = "Active Directory Integrated"; const string ActiveDirectoryInteractiveString = "Active Directory Interactive"; const string ActiveDirectoryServicePrincipalString = "Active Directory Service Principal"; + const string ActiveDirectoryDeviceCodeFlowString = "Active Directory Device Code Flow"; internal static bool TryConvertToAuthenticationType(string value, out SqlAuthenticationMethod result) { - Debug.Assert(Enum.GetNames(typeof(SqlAuthenticationMethod)).Length == 6, "SqlAuthenticationMethod enum has changed, update needed"); + Debug.Assert(Enum.GetNames(typeof(SqlAuthenticationMethod)).Length == 7, "SqlAuthenticationMethod enum has changed, update needed"); bool isSuccess = false; @@ -140,6 +141,12 @@ internal static bool TryConvertToAuthenticationType(string value, out SqlAuthent result = SqlAuthenticationMethod.ActiveDirectoryServicePrincipal; isSuccess = true; } + else if (StringComparer.InvariantCultureIgnoreCase.Equals(value, ActiveDirectoryDeviceCodeFlowString) + || StringComparer.InvariantCultureIgnoreCase.Equals(value, Convert.ToString(SqlAuthenticationMethod.ActiveDirectoryDeviceCodeFlow, CultureInfo.InvariantCulture))) + { + result = SqlAuthenticationMethod.ActiveDirectoryDeviceCodeFlow; + isSuccess = true; + } else { result = DbConnectionStringDefaults.Authentication; @@ -486,6 +493,7 @@ internal static bool IsValidAuthenticationTypeValue(SqlAuthenticationMethod valu || value == SqlAuthenticationMethod.ActiveDirectoryIntegrated || value == SqlAuthenticationMethod.ActiveDirectoryInteractive || value == SqlAuthenticationMethod.ActiveDirectoryServicePrincipal + || value == SqlAuthenticationMethod.ActiveDirectoryDeviceCodeFlow || value == SqlAuthenticationMethod.NotSpecified; } @@ -505,6 +513,8 @@ internal static string AuthenticationTypeToString(SqlAuthenticationMethod value) return ActiveDirectoryInteractiveString; case SqlAuthenticationMethod.ActiveDirectoryServicePrincipal: return ActiveDirectoryServicePrincipalString; + case SqlAuthenticationMethod.ActiveDirectoryDeviceCodeFlow: + return ActiveDirectoryDeviceCodeFlowString; default: return null; } 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 3d6f135df1..eb3e03b1e2 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 @@ -33,6 +33,7 @@ static SqlAuthenticationProviderManager() Instance.SetProvider(SqlAuthenticationMethod.ActiveDirectoryPassword, activeDirectoryAuthProvider); Instance.SetProvider(SqlAuthenticationMethod.ActiveDirectoryInteractive, activeDirectoryAuthProvider); Instance.SetProvider(SqlAuthenticationMethod.ActiveDirectoryServicePrincipal, activeDirectoryAuthProvider); + Instance.SetProvider(SqlAuthenticationMethod.ActiveDirectoryDeviceCodeFlow, activeDirectoryAuthProvider); } /// @@ -116,6 +117,8 @@ private static SqlAuthenticationMethod AuthenticationEnumFromString(string authe return SqlAuthenticationMethod.ActiveDirectoryInteractive; case ActiveDirectoryServicePrincipal: return SqlAuthenticationMethod.ActiveDirectoryServicePrincipal; + case ActiveDirectoryDeviceCodeFlow: + return SqlAuthenticationMethod.ActiveDirectoryDeviceCodeFlow; default: throw SQL.UnsupportedAuthentication(authentication); } diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlAuthenticationProviderManager.NetStandard.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlAuthenticationProviderManager.NetStandard.cs index e56b491bd7..e3d5fd5e11 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlAuthenticationProviderManager.NetStandard.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlAuthenticationProviderManager.NetStandard.cs @@ -14,6 +14,7 @@ static SqlAuthenticationProviderManager() Instance.SetProvider(SqlAuthenticationMethod.ActiveDirectoryIntegrated, activeDirectoryAuthProvider); Instance.SetProvider(SqlAuthenticationMethod.ActiveDirectoryInteractive, activeDirectoryAuthProvider); Instance.SetProvider(SqlAuthenticationMethod.ActiveDirectoryServicePrincipal, activeDirectoryAuthProvider); + Instance.SetProvider(SqlAuthenticationMethod.ActiveDirectoryDeviceCodeFlow, activeDirectoryAuthProvider); } } } 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 2a22d3c9fa..8d85802486 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 @@ -18,6 +18,7 @@ internal partial class SqlAuthenticationProviderManager private const string ActiveDirectoryIntegrated = "active directory integrated"; private const string ActiveDirectoryInteractive = "active directory interactive"; private const string ActiveDirectoryServicePrincipal = "active directory service principal"; + private const string ActiveDirectoryDeviceCodeFlow = "active directory device code flow"; private readonly string _typeName; private readonly IReadOnlyCollection _authenticationsWithAppSpecifiedProvider; 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 4fccb2cee8..4f2a24bd0d 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 @@ -146,6 +146,11 @@ public SqlConnection(string connectionString, SqlCredential credential) : this() throw SQL.SettingCredentialWithInteractiveArgument(); } + if (UsesActiveDirectoryDeviceCodeFlow(connectionOptions)) + { + throw SQL.SettingCredentialWithDeviceFlowArgument(); + } + Credential = credential; } // else @@ -401,6 +406,11 @@ private bool UsesActiveDirectoryInteractive(SqlConnectionString opt) return opt != null ? opt.Authentication == SqlAuthenticationMethod.ActiveDirectoryInteractive : false; } + private bool UsesActiveDirectoryDeviceCodeFlow(SqlConnectionString opt) + { + return opt != null ? opt.Authentication == SqlAuthenticationMethod.ActiveDirectoryDeviceCodeFlow : false; + } + private bool UsesAuthentication(SqlConnectionString opt) { return opt != null ? opt.Authentication != SqlAuthenticationMethod.NotSpecified : false; @@ -457,7 +467,7 @@ public override string ConnectionString SqlConnectionString connectionOptions = new SqlConnectionString(value); if (_credential != null) { - // Check for Credential being used with Authentication=ActiveDirectoryIntegrated/ActiveDirectoryInteractive. Since a different error string is used + // Check for Credential being used with Authentication=ActiveDirectoryIntegrated/ActiveDirectoryInteractive/ActiveDirectoryDeviceCodeFlow. Since a different error string is used // for this case in ConnectionString setter vs in Credential setter, check for this error case before calling // CheckAndThrowOnInvalidCombinationOfConnectionStringAndSqlCredential, which is common to both setters. if (UsesActiveDirectoryIntegrated(connectionOptions)) @@ -468,6 +478,10 @@ public override string ConnectionString { throw SQL.SettingInteractiveWithCredential(); } + else if (UsesActiveDirectoryDeviceCodeFlow(connectionOptions)) + { + throw SQL.SettingDeviceFlowWithCredential(); + } CheckAndThrowOnInvalidCombinationOfConnectionStringAndSqlCredential(connectionOptions); } @@ -698,6 +712,10 @@ public SqlCredential Credential { throw SQL.SettingCredentialWithInteractiveInvalid(); } + else if (UsesActiveDirectoryDeviceCodeFlow((SqlConnectionString)ConnectionOptions)) + { + throw SQL.SettingCredentialWithDeviceFlowInvalid(); + } CheckAndThrowOnInvalidCombinationOfConnectionStringAndSqlCredential((SqlConnectionString)ConnectionOptions); if (_accessToken != null) diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlConnectionFactory.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlConnectionFactory.cs index 03fdf79d0b..704fe7aa59 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlConnectionFactory.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlConnectionFactory.cs @@ -173,9 +173,9 @@ override protected DbConnectionPoolGroupOptions CreateConnectionPoolGroupOptions connectionTimeout = int.MaxValue; } - if (opt.Authentication == SqlAuthenticationMethod.ActiveDirectoryInteractive) + if (opt.Authentication == SqlAuthenticationMethod.ActiveDirectoryInteractive || opt.Authentication == SqlAuthenticationMethod.ActiveDirectoryDeviceCodeFlow) { - // interactive mode will always have pool's CreateTimeout = 10 x ConnectTimeout. + // interactive/device code flow mode will always have pool's CreateTimeout = 10 x ConnectTimeout. if (connectionTimeout >= Int32.MaxValue / 10) { connectionTimeout = Int32.MaxValue; @@ -184,7 +184,7 @@ override protected DbConnectionPoolGroupOptions CreateConnectionPoolGroupOptions { connectionTimeout *= 10; } - SqlClientEventSource.Log.TraceEvent("Set connection pool CreateTimeout={0} when AD Interactive is in use.", connectionTimeout); + SqlClientEventSource.Log.TraceEvent("Set connection pool CreateTimeout={0} when {1} is in use.", connectionTimeout, opt.Authentication); } poolingOptions = new DbConnectionPoolGroupOptions( opt.IntegratedSecurity, diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlConnectionString.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlConnectionString.cs index c14bd52ad5..90010dc058 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlConnectionString.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlConnectionString.cs @@ -462,6 +462,11 @@ internal SqlConnectionString(string connectionString) : base(connectionString, G { throw SQL.InteractiveWithPassword(); } + + if (Authentication == SqlAuthenticationMethod.ActiveDirectoryDeviceCodeFlow && (HasUserIdKeyword || HasPasswordKeyword)) + { + throw SQL.DeviceFlowWithUsernamePassword(); + } } // This c-tor is used to create SSE and user instance connection strings when user instance is set to true 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 c7f796fafe..3369fa35f3 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 @@ -1217,6 +1217,7 @@ private void Login(ServerInfo server, TimeoutTimer timeout, string newPassword, // in Login7, indicating the intent to use Active Directory Authentication for SQL Server. if (ConnectionOptions.Authentication == SqlAuthenticationMethod.ActiveDirectoryPassword || ConnectionOptions.Authentication == SqlAuthenticationMethod.ActiveDirectoryInteractive + || ConnectionOptions.Authentication == SqlAuthenticationMethod.ActiveDirectoryDeviceCodeFlow || ConnectionOptions.Authentication == SqlAuthenticationMethod.ActiveDirectoryServicePrincipal // Since AD Integrated may be acting like Windows integrated, additionally check _fedAuthRequired || (ConnectionOptions.Authentication == SqlAuthenticationMethod.ActiveDirectoryIntegrated && _fedAuthRequired)) @@ -2020,6 +2021,7 @@ internal void OnFedAuthInfo(SqlFedAuthInfo fedAuthInfo) Debug.Assert((ConnectionOptions.HasUserIdKeyword && ConnectionOptions.HasPasswordKeyword) || _credential != null || ConnectionOptions.Authentication == SqlAuthenticationMethod.ActiveDirectoryInteractive + || ConnectionOptions.Authentication == SqlAuthenticationMethod.ActiveDirectoryDeviceCodeFlow || (ConnectionOptions.Authentication == SqlAuthenticationMethod.ActiveDirectoryIntegrated && _fedAuthRequired), "Credentials aren't provided for calling MSAL"); Debug.Assert(fedAuthInfo != null, "info should not be null."); @@ -2251,6 +2253,7 @@ internal SqlFedAuthToken GetFedAuthToken(SqlFedAuthInfo fedAuthInfo) } break; case SqlAuthenticationMethod.ActiveDirectoryInteractive: + case SqlAuthenticationMethod.ActiveDirectoryDeviceCodeFlow: if (_activeDirectoryAuthTimeoutRetryHelper.State == ActiveDirectoryAuthenticationTimeoutRetryState.Retrying) { fedAuthToken = _activeDirectoryAuthTimeoutRetryHelper.CachedToken; diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlUtil.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlUtil.cs index 0905a3ba0f..c98f671151 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlUtil.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlUtil.cs @@ -277,6 +277,10 @@ internal static Exception InteractiveWithPassword() { return ADP.Argument(System.SRHelper.GetString(SR.SQL_InteractiveWithPassword)); } + internal static Exception DeviceFlowWithUsernamePassword() + { + return ADP.Argument(System.SRHelper.GetString(SR.SQL_DeviceFlowWithUsernamePassword)); + } static internal Exception SettingIntegratedWithCredential() { return ADP.InvalidOperation(System.SRHelper.GetString(SR.SQL_SettingIntegratedWithCredential)); @@ -285,6 +289,10 @@ static internal Exception SettingInteractiveWithCredential() { return ADP.InvalidOperation(System.SRHelper.GetString(SR.SQL_SettingInteractiveWithCredential)); } + static internal Exception SettingDeviceFlowWithCredential() + { + return ADP.InvalidOperation(System.SRHelper.GetString(SR.SQL_SettingDeviceFlowWithCredential)); + } static internal Exception SettingCredentialWithIntegratedArgument() { return ADP.Argument(System.SRHelper.GetString(SR.SQL_SettingCredentialWithIntegrated)); @@ -293,6 +301,10 @@ static internal Exception SettingCredentialWithInteractiveArgument() { return ADP.Argument(System.SRHelper.GetString(SR.SQL_SettingCredentialWithInteractive)); } + static internal Exception SettingCredentialWithDeviceFlowArgument() + { + return ADP.Argument(System.SRHelper.GetString(SR.SQL_SettingCredentialWithDeviceFlow)); + } static internal Exception SettingCredentialWithIntegratedInvalid() { return ADP.InvalidOperation(System.SRHelper.GetString(SR.SQL_SettingCredentialWithIntegrated)); @@ -301,6 +313,10 @@ static internal Exception SettingCredentialWithInteractiveInvalid() { return ADP.InvalidOperation(System.SRHelper.GetString(SR.SQL_SettingCredentialWithInteractive)); } + static internal Exception SettingCredentialWithDeviceFlowInvalid() + { + return ADP.InvalidOperation(System.SRHelper.GetString(SR.SQL_SettingCredentialWithDeviceFlow)); + } internal static Exception NullEmptyTransactionName() { return ADP.Argument(System.SRHelper.GetString(SR.SQL_NullEmptyTransactionName)); @@ -447,6 +463,11 @@ internal static Exception ActiveDirectoryInteractiveTimeout() return ADP.TimeoutException(SR.SQL_Timeout_Active_Directory_Interactive_Authentication); } + internal static Exception ActiveDirectoryDeviceFlowTimeout() + { + return ADP.TimeoutException(SR.SQL_Timeout_Active_Directory_DeviceFlow_Authentication); + } + // // SQL.DataCommand diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsEnums.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsEnums.cs index eda89f3a42..8b5a19abd7 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsEnums.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsEnums.cs @@ -247,6 +247,7 @@ public enum FedAuthLibrary : byte public const byte MSALWORKFLOW_ACTIVEDIRECTORYINTEGRATED = 0x02; public const byte MSALWORKFLOW_ACTIVEDIRECTORYINTERACTIVE = 0x03; public const byte MSALWORKFLOW_ACTIVEDIRECTORYSERVICEPRINCIPAL = 0x01; // Using the Password byte as that is the closest we have + public const byte MSALWORKFLOW_ACTIVEDIRECTORYDEVICECODEFLOW = 0x03; // Using the Interactive byte as that is the closest we have public enum ActiveDirectoryWorkflow : byte { @@ -254,6 +255,7 @@ public enum ActiveDirectoryWorkflow : byte Integrated = MSALWORKFLOW_ACTIVEDIRECTORYINTEGRATED, Interactive = MSALWORKFLOW_ACTIVEDIRECTORYINTERACTIVE, ServicePrincipal = MSALWORKFLOW_ACTIVEDIRECTORYSERVICEPRINCIPAL, + DeviceCodeFlow = MSALWORKFLOW_ACTIVEDIRECTORYDEVICECODEFLOW, } // The string used for username in the error message when Authentication = Active Directory Integrated with FedAuth is used, if authentication fails. @@ -1127,6 +1129,9 @@ public enum SqlAuthenticationMethod /// ActiveDirectoryServicePrincipal, + + /// + ActiveDirectoryDeviceCodeFlow, } // This enum indicates the state of TransparentNetworkIPResolution // The first attempt when TNIR is on should be sequential. If the first attempt failes next attempts should be parallel. 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 eedcf86f8f..e7294a6068 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 @@ -381,6 +381,9 @@ internal void Connect( case SqlAuthenticationMethod.ActiveDirectoryServicePrincipal: SqlClientEventSource.Log.TraceEvent(" Active Directory Service Principal authentication", "SEC"); break; + case SqlAuthenticationMethod.ActiveDirectoryDeviceCodeFlow: + SqlClientEventSource.Log.TraceEvent(" Active Directory Device Code Flow authentication", "SEC"); + break; case SqlAuthenticationMethod.SqlPassword: SqlClientEventSource.Log.TraceEvent(" SQL Password authentication", "SEC"); break; @@ -7724,6 +7727,9 @@ internal int WriteFedAuthFeatureRequest(FederatedAuthenticationFeatureExtensionD case SqlAuthenticationMethod.ActiveDirectoryServicePrincipal: workflow = TdsEnums.MSALWORKFLOW_ACTIVEDIRECTORYSERVICEPRINCIPAL; break; + case SqlAuthenticationMethod.ActiveDirectoryDeviceCodeFlow: + workflow = TdsEnums.MSALWORKFLOW_ACTIVEDIRECTORYDEVICECODEFLOW; + break; default: Debug.Assert(false, "Unrecognized Authentication type for fedauth MSAL request"); break; diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Resources/SR.Designer.cs b/src/Microsoft.Data.SqlClient/netcore/src/Resources/SR.Designer.cs index 10848064a8..028553a424 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Resources/SR.Designer.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Resources/SR.Designer.cs @@ -2607,6 +2607,15 @@ internal static string SQL_InteractiveWithPassword { } } + /// + /// Looks up a localized string similar to Cannot use 'Authentication=Active Directory Device Code Flow' with 'User ID', 'UID', 'Password' or 'PWD' connection string keywords.. + /// + internal static string SQL_DeviceFlowWithUsernamePassword { + get { + return ResourceManager.GetString("SQL_DeviceFlowWithUsernamePassword", resourceCulture); + } + } + /// /// Looks up a localized string similar to Internal Error. /// @@ -3012,6 +3021,15 @@ internal static string SQL_SettingCredentialWithInteractive { } } + /// + /// Looks up a localized string similar to Cannot set the Credential property if 'Authentication=Active Directory Device Code Flow' has been specified in the connection string.. + /// + internal static string SQL_SettingCredentialWithDeviceFlow { + get { + return ResourceManager.GetString("SQL_SettingCredentialWithDeviceFlow", resourceCulture); + } + } + /// /// Looks up a localized string similar to Cannot use 'Authentication=Active Directory Integrated', if the Credential property has been set.. /// @@ -3030,6 +3048,15 @@ internal static string SQL_SettingInteractiveWithCredential { } } + /// + /// Looks up a localized string similar to Cannot use 'Authentication=Active Directory Device Code Flow', if the Credential property has been set.. + /// + internal static string SQL_SettingDeviceFlowWithCredential { + get { + return ResourceManager.GetString("SQL_SettingDeviceFlowWithCredential", resourceCulture); + } + } + /// /// Looks up a localized string similar to A severe error occurred on the current command. The results, if any, should be discarded.. /// @@ -3165,6 +3192,15 @@ internal static string SQL_Timeout_Active_Directory_Interactive_Authentication { } } + /// + /// Looks up a localized string similar to Active Directory Device Code Flow authentication timed out. The user took too long to respond to the authentication request.. + /// + internal static string SQL_Timeout_Active_Directory_DeviceFlow_Authentication { + get { + return ResourceManager.GetString("SQL_Timeout_Active_Directory_DeviceFlow_Authentication", resourceCulture); + } + } + /// /// Looks up a localized string similar to Execution Timeout Expired. The timeout period elapsed prior to completion of the operation or the server is not responding.. /// diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Resources/SR.resx b/src/Microsoft.Data.SqlClient/netcore/src/Resources/SR.resx index 18a556eb63..8dfe189491 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Resources/SR.resx +++ b/src/Microsoft.Data.SqlClient/netcore/src/Resources/SR.resx @@ -408,6 +408,9 @@ Cannot use 'Authentication=Active Directory Interactive' with 'Password' or 'PWD' connection string keywords. + + Cannot use 'Authentication=Active Directory Device Code Flow' with 'User ID', 'UID', 'Password' or 'PWD' connection string keywords. + The instance of SQL Server you attempted to connect to requires encryption but this machine does not support it. @@ -1878,4 +1881,13 @@ Cannot use 'Authentication=Active Directory Interactive', if the Credential property has been set. + + Active Directory Device Code Flow authentication timed out. The user took too long to respond to the authentication request. + + + Cannot set the Credential property if 'Authentication=Active Directory Device Code Flow' has been specified in the connection string. + + + Cannot use 'Authentication=Active Directory Device Code Flow', if the Credential property has been set. + \ No newline at end of file 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 9238acc23f..0d853bc509 100644 --- a/src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.cs +++ b/src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.cs @@ -85,6 +85,8 @@ public enum SqlAuthenticationMethod ActiveDirectoryPassword = 2, /// ActiveDirectoryServicePrincipal = 5, + /// + ActiveDirectoryDeviceCodeFlow = 6, /// NotSpecified = 0, /// @@ -119,6 +121,8 @@ public abstract partial class SqlAuthenticationProvider protected SqlAuthenticationProvider() { } /// public abstract System.Threading.Tasks.Task AcquireTokenAsync(Microsoft.Data.SqlClient.SqlAuthenticationParameters parameters); + /// + public abstract System.Threading.Tasks.Task AcquireTokenAsync(Microsoft.Data.SqlClient.SqlAuthenticationParameters parameters, System.Func deviceCodeResultCallback); /// public virtual void BeforeLoad(Microsoft.Data.SqlClient.SqlAuthenticationMethod authenticationMethod) { } /// diff --git a/src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.csproj b/src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.csproj index 4d5f63e325..fa07c461b8 100644 --- a/src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.csproj @@ -15,4 +15,7 @@ + + + \ No newline at end of file diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/Common/DbConnectionStringCommon.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/Common/DbConnectionStringCommon.cs index 68add3559c..9979e9f6de 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/Common/DbConnectionStringCommon.cs +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/Common/DbConnectionStringCommon.cs @@ -521,11 +521,12 @@ internal static ApplicationIntent ConvertToApplicationIntent(string keyword, obj const string ActiveDirectoryIntegratedString = "Active Directory Integrated"; const string ActiveDirectoryInteractiveString = "Active Directory Interactive"; const string ActiveDirectoryServicePrincipalString = "Active Directory Service Principal"; + const string ActiveDirectoryDeviceCodeFlowString = "Active Directory Device Code Flow"; const string SqlCertificateString = "Sql Certificate"; internal static bool TryConvertToAuthenticationType(string value, out SqlAuthenticationMethod result) { - Debug.Assert(Enum.GetNames(typeof(SqlAuthenticationMethod)).Length == 6, "SqlAuthenticationMethod enum has changed, update needed"); + Debug.Assert(Enum.GetNames(typeof(SqlAuthenticationMethod)).Length == 7, "SqlAuthenticationMethod enum has changed, update needed"); bool isSuccess = false; @@ -559,6 +560,12 @@ internal static bool TryConvertToAuthenticationType(string value, out SqlAuthent result = SqlAuthenticationMethod.ActiveDirectoryServicePrincipal; isSuccess = true; } + else if (StringComparer.InvariantCultureIgnoreCase.Equals(value, ActiveDirectoryDeviceCodeFlowString) + || StringComparer.InvariantCultureIgnoreCase.Equals(value, Convert.ToString(SqlAuthenticationMethod.ActiveDirectoryDeviceCodeFlow, CultureInfo.InvariantCulture))) + { + result = SqlAuthenticationMethod.ActiveDirectoryDeviceCodeFlow; + isSuccess = true; + } #if ADONET_CERT_AUTH else if (StringComparer.InvariantCultureIgnoreCase.Equals(value, SqlCertificateString) || StringComparer.InvariantCultureIgnoreCase.Equals(value, Convert.ToString(SqlAuthenticationMethod.SqlCertificate, CultureInfo.InvariantCulture))) { @@ -647,6 +654,7 @@ internal static bool IsValidAuthenticationTypeValue(SqlAuthenticationMethod valu || value == SqlAuthenticationMethod.ActiveDirectoryIntegrated || value == SqlAuthenticationMethod.ActiveDirectoryInteractive || value == SqlAuthenticationMethod.ActiveDirectoryServicePrincipal + || value == SqlAuthenticationMethod.ActiveDirectoryDeviceCodeFlow #if ADONET_CERT_AUTH || value == SqlAuthenticationMethod.SqlCertificate #endif @@ -669,6 +677,8 @@ internal static string AuthenticationTypeToString(SqlAuthenticationMethod value) return ActiveDirectoryInteractiveString; case SqlAuthenticationMethod.ActiveDirectoryServicePrincipal: return ActiveDirectoryServicePrincipalString; + case SqlAuthenticationMethod.ActiveDirectoryDeviceCodeFlow: + return ActiveDirectoryDeviceCodeFlowString; #if ADONET_CERT_AUTH case SqlAuthenticationMethod.SqlCertificate: return SqlCertificateString; 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 de50e441b8..c803863d2b 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 @@ -21,6 +21,7 @@ internal class SqlAuthenticationProviderManager private const string ActiveDirectoryIntegrated = "active directory integrated"; private const string ActiveDirectoryInteractive = "active directory interactive"; private const string ActiveDirectoryServicePrincipal = "active directory service principal"; + private const string ActiveDirectoryDeviceCodeFlow = "active directory device code flow"; static SqlAuthenticationProviderManager() { @@ -40,6 +41,7 @@ static SqlAuthenticationProviderManager() Instance.SetProvider(SqlAuthenticationMethod.ActiveDirectoryPassword, activeDirectoryAuthProvider); Instance.SetProvider(SqlAuthenticationMethod.ActiveDirectoryInteractive, activeDirectoryAuthProvider); Instance.SetProvider(SqlAuthenticationMethod.ActiveDirectoryServicePrincipal, activeDirectoryAuthProvider); + Instance.SetProvider(SqlAuthenticationMethod.ActiveDirectoryDeviceCodeFlow, activeDirectoryAuthProvider); } public static readonly SqlAuthenticationProviderManager Instance; @@ -176,6 +178,8 @@ private static SqlAuthenticationMethod AuthenticationEnumFromString(string authe return SqlAuthenticationMethod.ActiveDirectoryInteractive; case ActiveDirectoryServicePrincipal: return SqlAuthenticationMethod.ActiveDirectoryServicePrincipal; + case ActiveDirectoryDeviceCodeFlow: + return SqlAuthenticationMethod.ActiveDirectoryDeviceCodeFlow; 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 ba55d29380..3440ea14f6 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 @@ -326,6 +326,11 @@ public SqlConnection(string connectionString, SqlCredential credential) : this() throw SQL.SettingCredentialWithInteractiveArgument(); } + if (UsesActiveDirectoryDeviceCodeFlow(connectionOptions)) + { + throw SQL.SettingCredentialWithDeviceFlowArgument(); + } + Credential = credential; } // else @@ -515,6 +520,11 @@ private bool UsesActiveDirectoryInteractive(SqlConnectionString opt) return opt != null ? opt.Authentication == SqlAuthenticationMethod.ActiveDirectoryInteractive : false; } + private bool UsesActiveDirectoryDeviceCodeFlow(SqlConnectionString opt) + { + return opt != null ? opt.Authentication == SqlAuthenticationMethod.ActiveDirectoryDeviceCodeFlow : false; + } + private bool UsesAuthentication(SqlConnectionString opt) { return opt != null ? opt.Authentication != SqlAuthenticationMethod.NotSpecified : false; @@ -658,7 +668,7 @@ override public string ConnectionString SqlConnectionString connectionOptions = new SqlConnectionString(value); if (_credential != null) { - // Check for Credential being used with Authentication=ActiveDirectoryIntegrated/ActiveDirectoryInteractive. Since a different error string is used + // Check for Credential being used with Authentication=ActiveDirectoryIntegrated/ActiveDirectoryInteractive/ActiveDirectoryDeviceCodeFlow. Since a different error string is used // for this case in ConnectionString setter vs in Credential setter, check for this error case before calling // CheckAndThrowOnInvalidCombinationOfConnectionStringAndSqlCredential, which is common to both setters. if (UsesActiveDirectoryIntegrated(connectionOptions)) @@ -669,6 +679,10 @@ override public string ConnectionString { throw SQL.SettingInteractiveWithCredential(); } + else if (UsesActiveDirectoryDeviceCodeFlow(connectionOptions)) + { + throw SQL.SettingDeviceFlowWithCredential(); + } CheckAndThrowOnInvalidCombinationOfConnectionStringAndSqlCredential(connectionOptions); } @@ -923,7 +937,7 @@ public SqlCredential Credential // check if the usage of credential has any conflict with the keys used in connection string if (value != null) { - // Check for Credential being used with Authentication=ActiveDirectoryIntegrated/ActiveDirectoryInteractive. Since a different error string is used + // Check for Credential being used with Authentication=ActiveDirectoryIntegrated/ActiveDirectoryInteractive/ActiveDirectoryDeviceCodeFlow. Since a different error string is used // for this case in ConnectionString setter vs in Credential setter, check for this error case before calling // CheckAndThrowOnInvalidCombinationOfConnectionStringAndSqlCredential, which is common to both setters. if (UsesActiveDirectoryIntegrated((SqlConnectionString)ConnectionOptions)) @@ -934,6 +948,10 @@ public SqlCredential Credential { throw SQL.SettingCredentialWithInteractiveInvalid(); } + else if (UsesActiveDirectoryDeviceCodeFlow((SqlConnectionString)ConnectionOptions)) + { + throw SQL.SettingCredentialWithDeviceFlowInvalid(); + } CheckAndThrowOnInvalidCombinationOfConnectionStringAndSqlCredential((SqlConnectionString)ConnectionOptions); if (_accessToken != null) diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlConnectionString.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlConnectionString.cs index 972e97ae91..d696512b51 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlConnectionString.cs +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlConnectionString.cs @@ -551,6 +551,11 @@ internal SqlConnectionString(string connectionString) : base(connectionString, G throw SQL.InteractiveWithPassword(); } + if (Authentication == SqlAuthenticationMethod.ActiveDirectoryDeviceCodeFlow && (HasUserIdKeyword || HasPasswordKeyword)) + { + throw SQL.DeviceFlowWithUsernamePassword(); + } + #if ADONET_CERT_AUTH if (!DbConnectionStringBuilderUtil.IsValidCertificateValue(_certificate)) { 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 b3bcb0bb6e..1ba6c09ed5 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 @@ -1487,12 +1487,13 @@ private void Login(ServerInfo server, TimeoutTimer timeout, string newPassword, _sessionRecoveryRequested = true; } - // If the workflow being used is Active Directory Password/Integrated/Interactive/Service Principal and server's prelogin response + // If the workflow being used is Active Directory Password/Integrated/Interactive/Service Principal/Device Code Flow and server's prelogin response // for FEDAUTHREQUIRED option indicates Federated Authentication is required, we have to insert FedAuth Feature Extension // in Login7, indicating the intent to use Active Directory Authentication for SQL Server. if (ConnectionOptions.Authentication == SqlAuthenticationMethod.ActiveDirectoryPassword || ConnectionOptions.Authentication == SqlAuthenticationMethod.ActiveDirectoryInteractive || ConnectionOptions.Authentication == SqlAuthenticationMethod.ActiveDirectoryServicePrincipal + || ConnectionOptions.Authentication == SqlAuthenticationMethod.ActiveDirectoryDeviceCodeFlow // Since AD Integrated may be acting like Windows integrated, additionally check _fedAuthRequired || (ConnectionOptions.Authentication == SqlAuthenticationMethod.ActiveDirectoryIntegrated && _fedAuthRequired)) { @@ -1881,7 +1882,8 @@ private bool ShouldDisableTnir(SqlConnectionString connectionOptions) connectionOptions.Authentication == SqlAuthenticationMethod.ActiveDirectoryPassword || connectionOptions.Authentication == SqlAuthenticationMethod.ActiveDirectoryIntegrated || connectionOptions.Authentication == SqlAuthenticationMethod.ActiveDirectoryInteractive || - connectionOptions.Authentication == SqlAuthenticationMethod.ActiveDirectoryServicePrincipal; + connectionOptions.Authentication == SqlAuthenticationMethod.ActiveDirectoryServicePrincipal || + connectionOptions.Authentication == SqlAuthenticationMethod.ActiveDirectoryDeviceCodeFlow; // 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. @@ -2467,6 +2469,7 @@ internal void OnFedAuthInfo(SqlFedAuthInfo fedAuthInfo) Debug.Assert((ConnectionOptions.HasUserIdKeyword && ConnectionOptions.HasPasswordKeyword) || _credential != null || ConnectionOptions.Authentication == SqlAuthenticationMethod.ActiveDirectoryInteractive + || ConnectionOptions.Authentication == SqlAuthenticationMethod.ActiveDirectoryDeviceCodeFlow || (ConnectionOptions.Authentication == SqlAuthenticationMethod.ActiveDirectoryIntegrated && _fedAuthRequired), "Credentials aren't provided for calling MSAL"); Debug.Assert(fedAuthInfo != null, "info should not be null."); @@ -2689,6 +2692,7 @@ internal SqlFedAuthToken GetFedAuthToken(SqlFedAuthInfo fedAuthInfo) } break; case SqlAuthenticationMethod.ActiveDirectoryInteractive: + case SqlAuthenticationMethod.ActiveDirectoryDeviceCodeFlow: if (_activeDirectoryAuthTimeoutRetryHelper.State == ActiveDirectoryAuthenticationTimeoutRetryState.Retrying) { fedAuthToken = _activeDirectoryAuthTimeoutRetryHelper.CachedToken; diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlUtil.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlUtil.cs index 49bec90d25..4e2319f3a7 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlUtil.cs +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlUtil.cs @@ -325,12 +325,14 @@ static internal Exception IntegratedWithUserIDAndPassword() { return ADP.Argument(StringsHelper.GetString(Strings.SQL_IntegratedWithUserIDAndPassword)); } - static internal Exception InteractiveWithPassword() { return ADP.Argument(StringsHelper.GetString(Strings.SQL_InteractiveWithPassword)); } - + static internal Exception DeviceFlowWithUsernamePassword() + { + return ADP.Argument(StringsHelper.GetString(Strings.SQL_DeviceFlowWithUsernamePassword)); + } static internal Exception SettingIntegratedWithCredential() { return ADP.InvalidOperation(StringsHelper.GetString(Strings.SQL_SettingIntegratedWithCredential)); @@ -339,6 +341,10 @@ static internal Exception SettingInteractiveWithCredential() { return ADP.InvalidOperation(StringsHelper.GetString(Strings.SQL_SettingInteractiveWithCredential)); } + static internal Exception SettingDeviceFlowWithCredential() + { + return ADP.InvalidOperation(StringsHelper.GetString(Strings.SQL_SettingDeviceFlowWithCredential)); + } static internal Exception SettingCredentialWithIntegratedArgument() { return ADP.Argument(StringsHelper.GetString(Strings.SQL_SettingCredentialWithIntegrated)); @@ -347,6 +353,10 @@ static internal Exception SettingCredentialWithInteractiveArgument() { return ADP.Argument(StringsHelper.GetString(Strings.SQL_SettingCredentialWithInteractive)); } + static internal Exception SettingCredentialWithDeviceFlowArgument() + { + return ADP.Argument(StringsHelper.GetString(Strings.SQL_SettingCredentialWithDeviceFlow)); + } static internal Exception SettingCredentialWithIntegratedInvalid() { return ADP.InvalidOperation(StringsHelper.GetString(Strings.SQL_SettingCredentialWithIntegrated)); @@ -355,6 +365,10 @@ static internal Exception SettingCredentialWithInteractiveInvalid() { return ADP.InvalidOperation(StringsHelper.GetString(Strings.SQL_SettingCredentialWithInteractive)); } + static internal Exception SettingCredentialWithDeviceFlowInvalid() + { + return ADP.InvalidOperation(StringsHelper.GetString(Strings.SQL_SettingCredentialWithDeviceFlow)); + } static internal Exception InvalidSQLServerVersionUnknown() { return ADP.DataAdapter(StringsHelper.GetString(Strings.SQL_InvalidSQLServerVersionUnknown)); @@ -478,6 +492,11 @@ static internal Exception ActiveDirectoryInteractiveTimeout() return ADP.TimeoutException(Strings.SQL_Timeout_Active_Directory_Interactive_Authentication); } + static internal Exception ActiveDirectoryDeviceFlowTimeout() + { + return ADP.TimeoutException(Strings.SQL_Timeout_Active_Directory_DeviceFlow_Authentication); + } + // // SQL.DataCommand // diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsEnums.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsEnums.cs index 92e089b298..d85f7ff84c 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsEnums.cs +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsEnums.cs @@ -239,6 +239,7 @@ public enum FedAuthLibrary : byte public const byte MSALWORKFLOW_ACTIVEDIRECTORYINTEGRATED = 0x02; public const byte MSALWORKFLOW_ACTIVEDIRECTORYINTERACTIVE = 0x03; public const byte MSALWORKFLOW_ACTIVEDIRECTORYSERVICEPRINCIPAL = 0x01; // Using the Password byte as that is the closest we have + public const byte MSALWORKFLOW_ACTIVEDIRECTORYDEVICECODEFLOW = 0x03; // Using the Interactive byte as that is the closest we have public enum ActiveDirectoryWorkflow : byte { @@ -246,6 +247,7 @@ public enum ActiveDirectoryWorkflow : byte Integrated = MSALWORKFLOW_ACTIVEDIRECTORYINTEGRATED, Interactive = MSALWORKFLOW_ACTIVEDIRECTORYINTERACTIVE, ServicePrincipal = MSALWORKFLOW_ACTIVEDIRECTORYSERVICEPRINCIPAL, + DeviceCodeFlow = MSALWORKFLOW_ACTIVEDIRECTORYDEVICECODEFLOW, } // The string used for username in the error message when Authentication = Active Directory Integrated with FedAuth is used, if authentication fails. @@ -1089,6 +1091,9 @@ public enum SqlAuthenticationMethod /// ActiveDirectoryServicePrincipal, + + /// + ActiveDirectoryDeviceCodeFlow, #if ADONET_CERT_AUTH SqlCertificate #endif 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 692d3ef85c..2a37a5c7a3 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 @@ -532,6 +532,9 @@ internal void Connect(ServerInfo serverInfo, case SqlAuthenticationMethod.ActiveDirectoryServicePrincipal: SqlClientEventSource.Log.TraceEvent(" Active Directory Service Principal authentication", "SEC"); break; + case SqlAuthenticationMethod.ActiveDirectoryDeviceCodeFlow: + SqlClientEventSource.Log.TraceEvent(" Active Directory Device Code Flow authentication", "SEC"); + break; case SqlAuthenticationMethod.SqlPassword: SqlClientEventSource.Log.TraceEvent(" SQL Password authentication", "SEC"); break; @@ -8443,6 +8446,9 @@ internal int WriteFedAuthFeatureRequest(FederatedAuthenticationFeatureExtensionD case SqlAuthenticationMethod.ActiveDirectoryServicePrincipal: workflow = TdsEnums.MSALWORKFLOW_ACTIVEDIRECTORYSERVICEPRINCIPAL; break; + case SqlAuthenticationMethod.ActiveDirectoryDeviceCodeFlow: + workflow = TdsEnums.MSALWORKFLOW_ACTIVEDIRECTORYDEVICECODEFLOW; + break; default: Debug.Assert(false, "Unrecognized Authentication type for fedauth MSAL request"); break; diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.Designer.cs b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.Designer.cs index 2d0d237f75..4387aa34aa 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.Designer.cs +++ b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.Designer.cs @@ -9387,6 +9387,15 @@ internal static string SQL_InteractiveWithPassword { } } + /// + /// Looks up a localized string similar to Cannot use 'Authentication=Active Directory Device Code Flow' with 'User ID', 'UID', 'Password' or 'PWD' connection string keywords.. + /// + internal static string SQL_DeviceFlowWithUsernamePassword { + get { + return ResourceManager.GetString("SQL_DeviceFlowWithUsernamePassword", resourceCulture); + } + } + /// /// Looks up a localized string similar to Internal Error. /// @@ -9864,6 +9873,15 @@ internal static string SQL_SettingCredentialWithInteractive { } } + /// + /// Looks up a localized string similar to Cannot set the Credential property if 'Authentication=Active Directory Device Code Flow' has been specified in the connection string.. + /// + internal static string SQL_SettingCredentialWithDeviceFlow { + get { + return ResourceManager.GetString("SQL_SettingCredentialWithDeviceFlow", resourceCulture); + } + } + /// /// Looks up a localized string similar to Cannot use 'Authentication=Active Directory Integrated', if the Credential property has been set.. /// @@ -9882,6 +9900,15 @@ internal static string SQL_SettingInteractiveWithCredential { } } + /// + /// Looks up a localized string similar to Cannot use 'Authentication=Active Directory Device Code Flow', if the Credential property has been set.. + /// + internal static string SQL_SettingDeviceFlowWithCredential { + get { + return ResourceManager.GetString("SQL_SettingDeviceFlowWithCredential", resourceCulture); + } + } + /// /// Looks up a localized string similar to A severe error occurred on the current command. The results, if any, should be discarded.. /// @@ -10098,6 +10125,15 @@ internal static string SQL_Timeout_Active_Directory_Interactive_Authentication { } } + /// + /// Looks up a localized string similar to Active Directory Device Code Flow authentication timed out. The user took too long to respond to the authentication request.. + /// + internal static string SQL_Timeout_Active_Directory_DeviceFlow_Authentication { + get { + return ResourceManager.GetString("SQL_Timeout_Active_Directory_DeviceFlow_Authentication", resourceCulture); + } + } + /// /// Looks up a localized string similar to Execution Timeout Expired. The timeout period elapsed prior to completion of the operation or the server is not responding.. /// diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.resx b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.resx index 628471f836..187ccd3944 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.resx +++ b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.resx @@ -2499,6 +2499,9 @@ Cannot use 'Authentication=Active Directory Interactive' with 'Password' or 'PWD' connection string keywords. + + Cannot use 'Authentication=Active Directory Device Code Flow' with 'User ID', 'UID', 'Password' or 'PWD' connection string keywords. + Cannot use 'Authentication=Active Directory Integrated', if the Credential property has been set. @@ -4542,4 +4545,13 @@ Cannot set the Credential property if 'Authentication=Active Directory Interactive' has been specified in the connection string. + + Active Directory Device Code Flow authentication timed out. The user took too long to respond to the authentication request. + + + Cannot set the Credential property if 'Authentication=Active Directory Device Code Flow' has been specified in the connection string. + + + Cannot use 'Authentication=Active Directory Device Code Flow', if the Credential property has been set. + \ No newline at end of file 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 7b5541fa6b..6a2e3dbe18 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ActiveDirectoryAuthenticationProvider.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ActiveDirectoryAuthenticationProvider.cs @@ -22,10 +22,21 @@ internal class ActiveDirectoryAuthenticationProvider : SqlAuthenticationProvider private readonly string _type = typeof(ActiveDirectoryAuthenticationProvider).Name; private readonly SqlClientLogger _logger = new SqlClientLogger(); + /// + /// Get Token + /// + /// + /// Authentication token + public override Task AcquireTokenAsync(SqlAuthenticationParameters parameters) => + AcquireTokenAsync(parameters, DefaultDeviceFlowCallback); + /// /// Get token. /// - public override Task AcquireTokenAsync(SqlAuthenticationParameters parameters) => Task.Run(async () => + /// + /// + /// Authentication token + public override Task AcquireTokenAsync(SqlAuthenticationParameters parameters, Func deviceCodeResultCallback) => Task.Run(async () => { AuthenticationResult result; string scope = parameters.Resource.EndsWith(s_defaultScopeSuffix) ? parameters.Resource : parameters.Resource + s_defaultScopeSuffix; @@ -44,22 +55,26 @@ public override Task AcquireTokenAsync(SqlAuthentication return new SqlAuthenticationToken(result.AccessToken, result.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 + * that you use https://login.microsoftonline.com/common/oauth2/nativeclient. + * + * https://docs.microsoft.com/en-us/azure/active-directory/develop/scenario-desktop-app-registration#redirect-uris + */ + string redirectURI = "https://login.microsoftonline.com/common/oauth2/nativeclient"; + +#if netcoreapp + if(parameters.AuthenticationMethod != SqlAuthenticationMethod.ActiveDirectoryDeviceCodeFlow) + { + redirectURI = "http://localhost"; + } +#endif IPublicClientApplication app = PublicClientApplicationBuilder.Create(ActiveDirectoryAuthentication.AdoClientId) .WithAuthority(parameters.Authority) .WithClientName(Common.DbConnectionStringDefaults.ApplicationName) .WithClientVersion(Common.ADP.GetAssemblyVersion().ToString()) -#if netcoreapp - .WithRedirectUri("http://localhost") -#else - /* - * 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 - * that you use https://login.microsoftonline.com/common/oauth2/nativeclient. - * - * https://docs.microsoft.com/en-us/azure/active-directory/develop/scenario-desktop-app-registration#redirect-uris - */ - .WithRedirectUri("https://login.microsoftonline.com/oauth2/nativeclient") -#endif + .WithRedirectUri(redirectURI) .Build(); if (parameters.AuthenticationMethod == SqlAuthenticationMethod.ActiveDirectoryIntegrated) @@ -88,7 +103,8 @@ public override Task AcquireTokenAsync(SqlAuthentication .WithCorrelationId(parameters.ConnectionId) .ExecuteAsync().Result; } - else if (parameters.AuthenticationMethod == SqlAuthenticationMethod.ActiveDirectoryInteractive) + else if (parameters.AuthenticationMethod == SqlAuthenticationMethod.ActiveDirectoryInteractive || + parameters.AuthenticationMethod == SqlAuthenticationMethod.ActiveDirectoryDeviceCodeFlow) { var accounts = await app.GetAccountsAsync(); IAccount account; @@ -109,12 +125,12 @@ public override Task AcquireTokenAsync(SqlAuthentication } catch (MsalUiRequiredException) { - result = await AcquireTokenInteractive(app, scopes, parameters.ConnectionId, parameters.UserId); + result = await AcquireTokenInteractiveDeviceFlow(app, scopes, parameters.ConnectionId, parameters.UserId, parameters.AuthenticationMethod, deviceCodeResultCallback); } } else { - result = await AcquireTokenInteractive(app, scopes, parameters.ConnectionId, parameters.UserId); + result = await AcquireTokenInteractiveDeviceFlow(app, scopes, parameters.ConnectionId, parameters.UserId, parameters.AuthenticationMethod, deviceCodeResultCallback); } } else @@ -125,7 +141,8 @@ public override Task AcquireTokenAsync(SqlAuthentication return new SqlAuthenticationToken(result.AccessToken, result.ExpiresOn); }); - private async Task AcquireTokenInteractive(IPublicClientApplication app, string[] scopes, Guid connectionId, string userId) + private async Task AcquireTokenInteractiveDeviceFlow(IPublicClientApplication app, string[] scopes, Guid connectionId, string userId, + SqlAuthenticationMethod authenticationMethod, Func deviceCodeResultCallback) { CancellationTokenSource cts = new CancellationTokenSource(); #if netcoreapp @@ -142,35 +159,62 @@ private async Task AcquireTokenInteractive(IPublicClientAp #endif try { - return await app.AcquireTokenInteractive(scopes) - /* - * We will use the MSAL Embedded or System web browser which changes by Default in MSAL according to this table: - * - * Framework Embedded System Default - * ------------------------------------------- - * .NET Classic Yes Yes^ Embedded - * .NET Core No Yes^ System - * .NET Standard No No NONE - * UWP Yes No Embedded - * Xamarin.Android Yes Yes System - * Xamarin.iOS Yes Yes System - * Xamarin.Mac Yes No Embedded - * - * ^ Requires "http://localhost" redirect URI - * - * https://github.com/AzureAD/microsoft-authentication-library-for-dotnet/wiki/MSAL.NET-uses-web-browser#at-a-glance - */ - //.WithUseEmbeddedWebView(true) - .WithCorrelationId(connectionId) - .WithLoginHint(userId) - .ExecuteAsync(cts.Token); + if (authenticationMethod == SqlAuthenticationMethod.ActiveDirectoryInteractive) + { + return await app.AcquireTokenInteractive(scopes) + /* + * We will use the MSAL Embedded or System web browser which changes by Default in MSAL according to this table: + * + * Framework Embedded System Default + * ------------------------------------------- + * .NET Classic Yes Yes^ Embedded + * .NET Core No Yes^ System + * .NET Standard No No NONE + * UWP Yes No Embedded + * Xamarin.Android Yes Yes System + * Xamarin.iOS Yes Yes System + * Xamarin.Mac Yes No Embedded + * + * ^ Requires "http://localhost" redirect URI + * + * https://github.com/AzureAD/microsoft-authentication-library-for-dotnet/wiki/MSAL.NET-uses-web-browser#at-a-glance + */ + //.WithUseEmbeddedWebView(true) + .WithCorrelationId(connectionId) + .WithLoginHint(userId) + .ExecuteAsync(cts.Token); + } + else + { + AuthenticationResult result = await app.AcquireTokenWithDeviceCode(scopes, + deviceCodeResult => deviceCodeResultCallback(deviceCodeResult)).ExecuteAsync(); + return result; + } } catch (OperationCanceledException) { - throw SQL.ActiveDirectoryInteractiveTimeout(); + throw (authenticationMethod == SqlAuthenticationMethod.ActiveDirectoryInteractive) ? + SQL.ActiveDirectoryInteractiveTimeout() : + SQL.ActiveDirectoryDeviceFlowTimeout(); } } + private Task DefaultDeviceFlowCallback(DeviceCodeResult result) + { + // This will print the message on the console which tells the user where to go sign-in using + // a separate browser and the code to enter once they sign in. + // The AcquireTokenWithDeviceCode() method will poll the server after firing this + // device code callback to look for the successful login of the user via that browser. + // This background polling (whose interval and timeout data is also provided as fields in the + // deviceCodeCallback class) will occur until: + // * The user has successfully logged in via browser and entered the proper code + // * The timeout specified by the server for the lifetime of this code (typically ~15 minutes) has been reached + // * The developing application calls the Cancel() method on a CancellationToken sent into the method. + // If this occurs, an OperationCanceledException will be thrown (see catch below for more details). + Console.WriteLine(result.Message); + return Task.FromResult(0); + } + /// /// Checks support for authentication type in lower case. /// Interactive authentication added. @@ -180,7 +224,8 @@ public override bool IsSupported(SqlAuthenticationMethod authentication) return authentication == SqlAuthenticationMethod.ActiveDirectoryIntegrated || authentication == SqlAuthenticationMethod.ActiveDirectoryPassword || authentication == SqlAuthenticationMethod.ActiveDirectoryInteractive - || authentication == SqlAuthenticationMethod.ActiveDirectoryServicePrincipal; + || authentication == SqlAuthenticationMethod.ActiveDirectoryServicePrincipal + || authentication == SqlAuthenticationMethod.ActiveDirectoryDeviceCodeFlow; } public override void BeforeLoad(SqlAuthenticationMethod authentication) diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlAuthenticationProvider.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlAuthenticationProvider.cs index 25e1cf006e..8f73aeed74 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlAuthenticationProvider.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlAuthenticationProvider.cs @@ -2,7 +2,9 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System; using System.Threading.Tasks; +using Microsoft.Identity.Client; namespace Microsoft.Data.SqlClient { @@ -33,5 +35,8 @@ public virtual void BeforeUnload(SqlAuthenticationMethod authenticationMethod) { /// public abstract Task AcquireTokenAsync(SqlAuthenticationParameters parameters); + + /// + public abstract Task AcquireTokenAsync(SqlAuthenticationParameters parameters, Func deviceCodeResultCallback); } } From ae4114447b84f9ebedfa0f805549b04a89bd829e Mon Sep 17 00:00:00 2001 From: Cheena Malhotra Date: Tue, 9 Jun 2020 22:58:53 -0700 Subject: [PATCH 02/14] Add tests + sample = modified implementation --- ...viceCodeFlowAzureAuthenticationProvider.cs | 63 +++++++++++++++++++ .../SqlAuthenticationProviderManager.cs | 2 - .../ActiveDirectoryAuthenticationProvider.cs | 21 ++----- .../SqlClient/SqlAuthenticationProvider.cs | 3 - .../ConnectivityTests/AADConnectionTest.cs | 39 +++++++++++- 5 files changed, 106 insertions(+), 22 deletions(-) create mode 100644 doc/samples/CustomDeviceCodeFlowAzureAuthenticationProvider.cs diff --git a/doc/samples/CustomDeviceCodeFlowAzureAuthenticationProvider.cs b/doc/samples/CustomDeviceCodeFlowAzureAuthenticationProvider.cs new file mode 100644 index 0000000000..f26441f343 --- /dev/null +++ b/doc/samples/CustomDeviceCodeFlowAzureAuthenticationProvider.cs @@ -0,0 +1,63 @@ +// +using System; +using System.Threading.Tasks; +using Microsoft.Identity.Client; +using Microsoft.Data.SqlClient; + +namespace CustomAuthenticationProviderExamples +{ + /// + /// Example demonstrating creating custom device code flow authentication provider and attaching it with the driver. + /// This is helpful for applications that wish to override Callback of Device Code Result as implemented by SqlClient driver. + /// + public class CustomDeviceCodeFlowAzureAuthenticationProvider : SqlAuthenticationProvider + { + public async override Task AcquireTokenAsync(SqlAuthenticationParameters parameters) + { + string clientId = "my-client-id"; + string clientName = "My Application Name"; + string s_defaultScopeSuffix = "/.default"; + + string[] scopes = new string[] { parameters.Resource.EndsWith(s_defaultScopeSuffix) ? parameters.Resource : parameters.Resource + s_defaultScopeSuffix }; + + IPublicClientApplication app = PublicClientApplicationBuilder.Create(clientId) + .WithAuthority(parameters.Authority) + .WithClientName(clientName) + .WithRedirectUri("https://login.microsoftonline.com/common/oauth2/nativeclient") + .Build(); + + AuthenticationResult result = await app.AcquireTokenWithDeviceCode(scopes, + deviceCodeResult => CustomDeviceFlowCallback(deviceCodeResult)).ExecuteAsync(); + return new SqlAuthenticationToken(result.AccessToken, result.ExpiresOn); + } + + public override bool IsSupported(SqlAuthenticationMethod authenticationMethod) + { + if (authenticationMethod.Equals(SqlAuthenticationMethod.ActiveDirectoryDeviceCodeFlow)) + { + return true; + } + return false; + } + + private Task CustomDeviceFlowCallback(DeviceCodeResult result) + { + Console.WriteLine(result.Message); + return Task.FromResult(0); + } + } + + public class Program + { + public static void Main() + { + SqlAuthenticationProvider.SetProvider(SqlAuthenticationMethod.ActiveDirectoryDeviceCodeFlow, new CustomDeviceCodeFlowAzureAuthenticationProvider()); + using (SqlConnection sqlConnection = new SqlConnection("Server=.database.windows.net;Authentication=Active Directory Device Code Flow;Database=;")) + { + sqlConnection.Open(); + Console.WriteLine("Connected successfully!"); + } + } + } +} +// 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 c803863d2b..144a782acb 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 @@ -10,8 +10,6 @@ namespace Microsoft.Data.SqlClient { - - /// /// Authentication provider manager. /// 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 6a2e3dbe18..5bceccad2f 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ActiveDirectoryAuthenticationProvider.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ActiveDirectoryAuthenticationProvider.cs @@ -22,21 +22,12 @@ internal class ActiveDirectoryAuthenticationProvider : SqlAuthenticationProvider private readonly string _type = typeof(ActiveDirectoryAuthenticationProvider).Name; private readonly SqlClientLogger _logger = new SqlClientLogger(); - /// - /// Get Token - /// - /// - /// Authentication token - public override Task AcquireTokenAsync(SqlAuthenticationParameters parameters) => - AcquireTokenAsync(parameters, DefaultDeviceFlowCallback); - /// /// Get token. /// /// - /// /// Authentication token - public override Task AcquireTokenAsync(SqlAuthenticationParameters parameters, Func deviceCodeResultCallback) => Task.Run(async () => + public override Task AcquireTokenAsync(SqlAuthenticationParameters parameters) => Task.Run(async () => { AuthenticationResult result; string scope = parameters.Resource.EndsWith(s_defaultScopeSuffix) ? parameters.Resource : parameters.Resource + s_defaultScopeSuffix; @@ -125,12 +116,12 @@ public override Task AcquireTokenAsync(SqlAuthentication } catch (MsalUiRequiredException) { - result = await AcquireTokenInteractiveDeviceFlow(app, scopes, parameters.ConnectionId, parameters.UserId, parameters.AuthenticationMethod, deviceCodeResultCallback); + result = await AcquireTokenInteractiveDeviceFlow(app, scopes, parameters.ConnectionId, parameters.UserId, parameters.AuthenticationMethod); } } else { - result = await AcquireTokenInteractiveDeviceFlow(app, scopes, parameters.ConnectionId, parameters.UserId, parameters.AuthenticationMethod, deviceCodeResultCallback); + result = await AcquireTokenInteractiveDeviceFlow(app, scopes, parameters.ConnectionId, parameters.UserId, parameters.AuthenticationMethod); } } else @@ -142,7 +133,7 @@ public override Task AcquireTokenAsync(SqlAuthentication }); private async Task AcquireTokenInteractiveDeviceFlow(IPublicClientApplication app, string[] scopes, Guid connectionId, string userId, - SqlAuthenticationMethod authenticationMethod, Func deviceCodeResultCallback) + SqlAuthenticationMethod authenticationMethod) { CancellationTokenSource cts = new CancellationTokenSource(); #if netcoreapp @@ -187,7 +178,7 @@ private async Task AcquireTokenInteractiveDeviceFlow(IPubl else { AuthenticationResult result = await app.AcquireTokenWithDeviceCode(scopes, - deviceCodeResult => deviceCodeResultCallback(deviceCodeResult)).ExecuteAsync(); + deviceCodeResult => DeviceFlowCallback(deviceCodeResult)).ExecuteAsync(); return result; } } @@ -199,7 +190,7 @@ private async Task AcquireTokenInteractiveDeviceFlow(IPubl } } - private Task DefaultDeviceFlowCallback(DeviceCodeResult result) + private Task DeviceFlowCallback(DeviceCodeResult result) { // This will print the message on the console which tells the user where to go sign-in using // a separate browser and the code to enter once they sign in. diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlAuthenticationProvider.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlAuthenticationProvider.cs index 8f73aeed74..70267e4e8a 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlAuthenticationProvider.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlAuthenticationProvider.cs @@ -35,8 +35,5 @@ public virtual void BeforeUnload(SqlAuthenticationMethod authenticationMethod) { /// public abstract Task AcquireTokenAsync(SqlAuthenticationParameters parameters); - - /// - public abstract Task AcquireTokenAsync(SqlAuthenticationParameters parameters, Func deviceCodeResultCallback); } } 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 c4014b4d19..68919c39eb 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConnectivityTests/AADConnectionTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConnectivityTests/AADConnectionTest.cs @@ -4,6 +4,7 @@ using System; using System.Diagnostics; +using System.Security; using Xunit; namespace Microsoft.Data.SqlClient.ManualTesting.Tests @@ -307,7 +308,7 @@ public static void NoCredentialsActiveDirectoryServicePrincipal() // test Passes with correct connection string. string[] removeKeys = { "Authentication", "User ID", "Password", "UID", "PWD" }; string connStr = DataTestUtility.RemoveKeysInConnStr(DataTestUtility.AADPasswordConnectionString, removeKeys) + - $"Authentication=Active Directory Service Principal; User ID={DataTestUtility.AADServicePrincipalId}; Password={DataTestUtility.AADServicePrincipalSecret};"; + $"Authentication=Active Directory Service Principal; User ID={DataTestUtility.AADServicePrincipalId}; PWD={DataTestUtility.AADServicePrincipalSecret};"; ConnectAndDisconnect(connStr); // connection fails with expected error message. @@ -320,7 +321,41 @@ public static void NoCredentialsActiveDirectoryServicePrincipal() Assert.Contains(expectedMessage, e.Message); } - [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.IsIntegratedSecuritySetup), nameof(DataTestUtility.AreConnStringsSetup)) ] + [ConditionalFact(nameof(IsAADConnStringsSetup))] + public static void ActiveDirectoryDeviceCodeFlowWithUserIdMustFail() + { + // connection fails with expected error message. + string[] credKeys = { "Authentication", "User ID", "Password", "UID", "PWD" }; + string connStrWithUID = DataTestUtility.RemoveKeysInConnStr(DataTestUtility.AADPasswordConnectionString, credKeys) + + "Authentication=Active Directory Device Code Flow; UID=someuser;"; + ArgumentException e = Assert.Throws(() => ConnectAndDisconnect(connStrWithUID)); + + string expectedMessage = "Cannot use 'Authentication=Active Directory Device Code Flow' with 'User ID', 'UID', 'Password' or 'PWD' connection string keywords."; + Assert.Contains(expectedMessage, e.Message); + } + + [ConditionalFact(nameof(IsAADConnStringsSetup))] + public static void ActiveDirectoryDeviceCodeFlowWithCredentialsMustFail() + { + // connection fails with expected error message. + string[] credKeys = { "Authentication", "User ID", "Password", "UID", "PWD" }; + string connStrWithNoCred = DataTestUtility.RemoveKeysInConnStr(DataTestUtility.AADPasswordConnectionString, credKeys) + + "Authentication=Active Directory Device Code Flow;"; + + 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 Device Code Flow' has been specified in the connection string."; + Assert.Contains(expectedMessage, e.Message); + } + + [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.IsIntegratedSecuritySetup), nameof(DataTestUtility.AreConnStringsSetup))] public static void ADInteractiveUsingSSPI() { // test Passes with correct connection string. From fa6aa17bffeeace1cedad599ea86a042437f4d3b Mon Sep 17 00:00:00 2001 From: Cheena Malhotra Date: Wed, 10 Jun 2020 11:21:05 -0700 Subject: [PATCH 03/14] Reflect comments + cleanup --- ...CustomDeviceCodeFlowAzureAuthenticationProvider.cs | 11 ++--------- .../SqlAuthenticationMethod.xml | 2 +- .../SqlAuthenticationProvider.xml | 6 ------ .../netcore/ref/Microsoft.Data.SqlClient.cs | 2 -- .../netcore/ref/Microsoft.Data.SqlClient.csproj | 3 --- .../netfx/ref/Microsoft.Data.SqlClient.cs | 2 -- .../netfx/ref/Microsoft.Data.SqlClient.csproj | 3 --- .../Data/SqlClient/SqlAuthenticationProvider.cs | 2 -- 8 files changed, 3 insertions(+), 28 deletions(-) diff --git a/doc/samples/CustomDeviceCodeFlowAzureAuthenticationProvider.cs b/doc/samples/CustomDeviceCodeFlowAzureAuthenticationProvider.cs index f26441f343..a09e9856b6 100644 --- a/doc/samples/CustomDeviceCodeFlowAzureAuthenticationProvider.cs +++ b/doc/samples/CustomDeviceCodeFlowAzureAuthenticationProvider.cs @@ -12,7 +12,7 @@ namespace CustomAuthenticationProviderExamples /// public class CustomDeviceCodeFlowAzureAuthenticationProvider : SqlAuthenticationProvider { - public async override Task AcquireTokenAsync(SqlAuthenticationParameters parameters) + public override async Task AcquireTokenAsync(SqlAuthenticationParameters parameters) { string clientId = "my-client-id"; string clientName = "My Application Name"; @@ -31,14 +31,7 @@ public async override Task AcquireTokenAsync(SqlAuthenti return new SqlAuthenticationToken(result.AccessToken, result.ExpiresOn); } - public override bool IsSupported(SqlAuthenticationMethod authenticationMethod) - { - if (authenticationMethod.Equals(SqlAuthenticationMethod.ActiveDirectoryDeviceCodeFlow)) - { - return true; - } - return false; - } + public override bool IsSupported(SqlAuthenticationMethod authenticationMethod) => authenticationMethod.Equals(SqlAuthenticationMethod.ActiveDirectoryDeviceCodeFlow); private Task CustomDeviceFlowCallback(DeviceCodeResult result) { diff --git a/doc/snippets/Microsoft.Data.SqlClient/SqlAuthenticationMethod.xml b/doc/snippets/Microsoft.Data.SqlClient/SqlAuthenticationMethod.xml index bf32a65435..2ddb84b9a1 100644 --- a/doc/snippets/Microsoft.Data.SqlClient/SqlAuthenticationMethod.xml +++ b/doc/snippets/Microsoft.Data.SqlClient/SqlAuthenticationMethod.xml @@ -30,7 +30,7 @@ 5 - The authentication method uses Active Directory Device Code Flow. Use Active Directory Device Code Flow to connect to a SQL Database from devices and operating systems that do not provide a Web browser, using another device to perform Interactive authentication. + The authentication method uses Active Directory Device Code Flow. Use Active Directory Device Code Flow to connect to a SQL Database from devices and operating systems that do not provide a Web browser, using another device to perform interactive authentication. 6 diff --git a/doc/snippets/Microsoft.Data.SqlClient/SqlAuthenticationProvider.xml b/doc/snippets/Microsoft.Data.SqlClient/SqlAuthenticationProvider.xml index 83e66a9473..e6f373c300 100644 --- a/doc/snippets/Microsoft.Data.SqlClient/SqlAuthenticationProvider.xml +++ b/doc/snippets/Microsoft.Data.SqlClient/SqlAuthenticationProvider.xml @@ -45,11 +45,5 @@ Represents an asynchronous operation that returns the AD authentication token. To be added. - - The Active Directory authentication parameters passed by the driver to authentication providers. - The callback function that receives Device Code Flow Result with Device Code that is used for authentication by 'Active Directory Device Code Flow' authentication provider. - Acquires a security token from the authority. - Represents an asynchronous operation that returns the AD authentication token. - 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 8da7326ace..932361e8c7 100644 --- a/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.cs +++ b/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.cs @@ -105,8 +105,6 @@ public abstract partial class SqlAuthenticationProvider protected SqlAuthenticationProvider() { } /// public abstract System.Threading.Tasks.Task AcquireTokenAsync(Microsoft.Data.SqlClient.SqlAuthenticationParameters parameters); - /// - public abstract System.Threading.Tasks.Task AcquireTokenAsync(Microsoft.Data.SqlClient.SqlAuthenticationParameters parameters, System.Func deviceCodeResultCallback); /// public virtual void BeforeLoad(Microsoft.Data.SqlClient.SqlAuthenticationMethod authenticationMethod) { } /// diff --git a/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.csproj b/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.csproj index c1c48f2891..b1934599e6 100644 --- a/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.csproj @@ -15,9 +15,6 @@ - - - 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 0d853bc509..e25078fd5e 100644 --- a/src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.cs +++ b/src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.cs @@ -121,8 +121,6 @@ public abstract partial class SqlAuthenticationProvider protected SqlAuthenticationProvider() { } /// public abstract System.Threading.Tasks.Task AcquireTokenAsync(Microsoft.Data.SqlClient.SqlAuthenticationParameters parameters); - /// - public abstract System.Threading.Tasks.Task AcquireTokenAsync(Microsoft.Data.SqlClient.SqlAuthenticationParameters parameters, System.Func deviceCodeResultCallback); /// public virtual void BeforeLoad(Microsoft.Data.SqlClient.SqlAuthenticationMethod authenticationMethod) { } /// diff --git a/src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.csproj b/src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.csproj index fa07c461b8..4d5f63e325 100644 --- a/src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.csproj @@ -15,7 +15,4 @@ - - - \ No newline at end of file diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlAuthenticationProvider.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlAuthenticationProvider.cs index 70267e4e8a..25e1cf006e 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlAuthenticationProvider.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlAuthenticationProvider.cs @@ -2,9 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System; using System.Threading.Tasks; -using Microsoft.Identity.Client; namespace Microsoft.Data.SqlClient { From da572e7de7836c54a5341924eb6cf58d83b531d2 Mon Sep 17 00:00:00 2001 From: Cheena Malhotra Date: Wed, 10 Jun 2020 12:16:27 -0700 Subject: [PATCH 04/14] Apply suggestions from code review Co-authored-by: David Engel --- .../CustomDeviceCodeFlowAzureAuthenticationProvider.cs | 4 ++-- .../Data/SqlClient/ActiveDirectoryAuthenticationProvider.cs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/samples/CustomDeviceCodeFlowAzureAuthenticationProvider.cs b/doc/samples/CustomDeviceCodeFlowAzureAuthenticationProvider.cs index a09e9856b6..19bc1f849d 100644 --- a/doc/samples/CustomDeviceCodeFlowAzureAuthenticationProvider.cs +++ b/doc/samples/CustomDeviceCodeFlowAzureAuthenticationProvider.cs @@ -7,8 +7,8 @@ namespace CustomAuthenticationProviderExamples { /// - /// Example demonstrating creating custom device code flow authentication provider and attaching it with the driver. - /// This is helpful for applications that wish to override Callback of Device Code Result as implemented by SqlClient driver. + /// Example demonstrating creating a custom device code flow authentication provider and attaching it to the driver. + /// This is helpful for applications that wish to override the Callback for the Device Code Result implemented by the SqlClient driver. /// public class CustomDeviceCodeFlowAzureAuthenticationProvider : SqlAuthenticationProvider { 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 5bceccad2f..d55a4c8efa 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ActiveDirectoryAuthenticationProvider.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ActiveDirectoryAuthenticationProvider.cs @@ -178,7 +178,7 @@ private async Task AcquireTokenInteractiveDeviceFlow(IPubl else { AuthenticationResult result = await app.AcquireTokenWithDeviceCode(scopes, - deviceCodeResult => DeviceFlowCallback(deviceCodeResult)).ExecuteAsync(); + deviceCodeResult => DeviceFlowCallback(deviceCodeResult)).ExecuteAsync(); return result; } } From c9bff318e884675b2c59dbbd7b40e17e916e847a Mon Sep 17 00:00:00 2001 From: Cheena Malhotra Date: Wed, 10 Jun 2020 13:05:30 -0700 Subject: [PATCH 05/14] Do not log to console with LogError --- .../netcore/src/Microsoft/Data/SqlClient/TdsParser.cs | 3 ++- .../netfx/src/Microsoft/Data/SqlClient/TdsParser.cs | 3 ++- .../src/Microsoft/Data/SqlClient/SqlClientLogger.cs | 1 - 3 files changed, 4 insertions(+), 3 deletions(-) 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 de708b05d6..c487f9ab41 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 @@ -921,8 +921,9 @@ private PreLoginHandshakeStatus ConsumePreLoginHandshake(bool encrypt, bool trus SslProtocols protocol = (SslProtocols)protocolVersion; string warningMessage = protocol.GetProtocolWarning(); - if(!string.IsNullOrEmpty(warningMessage)) + if (!string.IsNullOrEmpty(warningMessage)) { + // This logs console warning of insecure protocol in use. _logger.LogWarning(_typeName, MethodBase.GetCurrentMethod().Name, warningMessage); } 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 13bd64c853..1298983119 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 @@ -1223,6 +1223,7 @@ private PreLoginHandshakeStatus ConsumePreLoginHandshake(SqlAuthenticationMethod string warningMessage = SslProtocolsHelper.GetProtocolWarning(protocolVersion); if (!string.IsNullOrEmpty(warningMessage)) { + // This logs console warning of insecure protocol in use. _logger.LogWarning(_typeName, MethodBase.GetCurrentMethod().Name, warningMessage); } @@ -9717,7 +9718,7 @@ internal Task TdsExecuteRPC(SqlCommand cmd, _SqlRPC[] rpcArray, int timeout, boo // Stream out parameters SqlParameter[] parameters = rpcext.parameters; - + bool isAdvancedTraceOn = SqlClientEventSource.Log.IsAdvancedTraceOn(); for (int i = (ii == startRpc) ? startParam : 0; i < parameters.Length; i++) diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlClientLogger.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlClientLogger.cs index 68afb07e49..5150862224 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlClientLogger.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlClientLogger.cs @@ -32,7 +32,6 @@ public void LogWarning(string type, string method, string message) /// public void LogError(string type, string method, string message) { - Console.Out.WriteLine(message); SqlClientEventSource.Log.TraceEvent("{3}", type, method, LogLevel.Error, message); } From aa754c7592d351656c7025592249fd46b41cc7e8 Mon Sep 17 00:00:00 2001 From: Cheena Malhotra Date: Thu, 11 Jun 2020 10:40:29 -0700 Subject: [PATCH 06/14] Make Active Directory Authentication Provider public --- ...DAuthenticationCustomDeviceFlowCallback.cs | 30 ++++++++ .../ActiveDirectoryAuthenticationProvider.xml | 69 +++++++++++++++++++ src/Microsoft.Data.SqlClient.sln | 1 + .../ActiveDirectoryAuthenticationProvider.cs | 41 ++++++----- .../{AADAccessTokenTest.cs => AADTests.cs} | 19 ++++- .../Microsoft.Data.SqlClient.Tests.csproj | 2 +- 6 files changed, 143 insertions(+), 19 deletions(-) create mode 100644 doc/samples/AADAuthenticationCustomDeviceFlowCallback.cs create mode 100644 doc/snippets/Microsoft.Data.SqlClient/ActiveDirectoryAuthenticationProvider.xml rename src/Microsoft.Data.SqlClient/tests/FunctionalTests/{AADAccessTokenTest.cs => AADTests.cs} (72%) diff --git a/doc/samples/AADAuthenticationCustomDeviceFlowCallback.cs b/doc/samples/AADAuthenticationCustomDeviceFlowCallback.cs new file mode 100644 index 0000000000..bce949536b --- /dev/null +++ b/doc/samples/AADAuthenticationCustomDeviceFlowCallback.cs @@ -0,0 +1,30 @@ +// +using System; +using System.Threading.Tasks; +using Microsoft.Identity.Client; +using Microsoft.Data.SqlClient; + +namespace CustomAuthenticationProviderExamples +{ + public class Program + { + public static void Main() + { + SqlAuthenticationProvider authProvider = new ActiveDirectoryAuthenticationProvider(CustomDeviceFlowCallback); + SqlAuthenticationProvider.SetProvider(SqlAuthenticationMethod.ActiveDirectoryDeviceCodeFlow, authProvider); + using (SqlConnection sqlConnection = new SqlConnection("Server=.database.windows.net;Authentication=Active Directory Device Code Flow;Database=;")) + { + sqlConnection.Open(); + Console.WriteLine("Connected successfully!"); + } + } + + private Task CustomDeviceFlowCallback(DeviceCodeResult result) + { + // Provide custon logic to process result information and read device code. + Console.WriteLine(result.Message); + return Task.FromResult(0); + } + } +} +// diff --git a/doc/snippets/Microsoft.Data.SqlClient/ActiveDirectoryAuthenticationProvider.xml b/doc/snippets/Microsoft.Data.SqlClient/ActiveDirectoryAuthenticationProvider.xml new file mode 100644 index 0000000000..607ee31342 --- /dev/null +++ b/doc/snippets/Microsoft.Data.SqlClient/ActiveDirectoryAuthenticationProvider.xml @@ -0,0 +1,69 @@ + + + + + This class implements and is used for active directory federated authentication mechanisms. + + + + + Initializes the class. + + + + The callback method to be used by driver when performing 'Active Directory Device Code Flow' authentication. + + Initializes the class with provided device code flow callback method. + + + + The Active Directory authentication parameters passed by the driver to authentication providers. + Acquires a security token from the authority. + Represents an asynchronous operation that returns the AD authentication token. + + + The callback method to be used by driver when performing 'Active Directory Device Code Flow' authentication. + Set's callback method that overrides driver's default implementation to process result when performing 'Active Directory Device Code Flow' authentication. + + + The authentication method. + This method is called immediately before the provider is added to SQL drivers registry. + Avoid performing long-waiting tasks in this method, since it can block other threads from accessing the provider registry. + + + The authentication method. + This method is called immediately before the provider is removed from the SQL drivers registry. + For example, this method is called when a different provider with the same authentication method overrides this provider in the SQL drivers registry. Avoid performing long-waiting task in this method, since it can block other threads from accessing the provider registry. + + + The authentication method. + Indicates whether the specified authentication method is supported. + + if the specified authentication method is supported; otherwise, . + + + + | +|| +|| +|| +|| + +## Examples + The following example demonstrates providing a custom device flow callback to SqlClient driver for Device Code Flow authentication method: + + [!code-csharp[ActiveDirectory_DeviceCodeFlowCallback Example#1](~/../sqlclient/doc/samples/AADAuthenticationCustomDeviceFlowCallback.cs#1)] + + ]]> + + + + + diff --git a/src/Microsoft.Data.SqlClient.sln b/src/Microsoft.Data.SqlClient.sln index 9077063825..eea16852c3 100644 --- a/src/Microsoft.Data.SqlClient.sln +++ b/src/Microsoft.Data.SqlClient.sln @@ -78,6 +78,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Microsoft.Data.Sql", "Micro EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Microsoft.Data.SqlClient", "Microsoft.Data.SqlClient", "{C05F4FFE-6A14-4409-AA0A-10630BE4F1EE}" ProjectSection(SolutionItems) = preProject + ..\doc\snippets\Microsoft.Data.SqlClient\ActiveDirectoryAuthenticationProvider.xml = ..\doc\snippets\Microsoft.Data.SqlClient\ActiveDirectoryAuthenticationProvider.xml ..\doc\snippets\Microsoft.Data.SqlClient\ApplicationIntent.xml = ..\doc\snippets\Microsoft.Data.SqlClient\ApplicationIntent.xml ..\doc\snippets\Microsoft.Data.SqlClient\OnChangeEventHandler.xml = ..\doc\snippets\Microsoft.Data.SqlClient\OnChangeEventHandler.xml ..\doc\snippets\Microsoft.Data.SqlClient\PoolBlockingPeriod.xml = ..\doc\snippets\Microsoft.Data.SqlClient\PoolBlockingPeriod.xml 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 d55a4c8efa..ed084708bf 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ActiveDirectoryAuthenticationProvider.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ActiveDirectoryAuthenticationProvider.cs @@ -3,7 +3,6 @@ // See the LICENSE file in the project root for more information. using System; -using System.Diagnostics; using System.Linq; using System.Security; using System.Threading; @@ -12,21 +11,24 @@ namespace Microsoft.Data.SqlClient { - - /// - /// Default auth provider for AD Integrated. - /// - internal class ActiveDirectoryAuthenticationProvider : SqlAuthenticationProvider + /// + public class ActiveDirectoryAuthenticationProvider : SqlAuthenticationProvider { private static readonly string s_defaultScopeSuffix = "/.default"; private readonly string _type = typeof(ActiveDirectoryAuthenticationProvider).Name; private readonly SqlClientLogger _logger = new SqlClientLogger(); + private Func _deviceCodeFlowCallback; + + /// + public ActiveDirectoryAuthenticationProvider() => new ActiveDirectoryAuthenticationProvider(DefaultDeviceFlowCallback); + + /// + public ActiveDirectoryAuthenticationProvider(Func deviceCodeFlowCallbackMethod) + { + this._deviceCodeFlowCallback = deviceCodeFlowCallbackMethod; + } - /// - /// Get token. - /// - /// - /// Authentication token + /// public override Task AcquireTokenAsync(SqlAuthenticationParameters parameters) => Task.Run(async () => { AuthenticationResult result; @@ -178,7 +180,7 @@ private async Task AcquireTokenInteractiveDeviceFlow(IPubl else { AuthenticationResult result = await app.AcquireTokenWithDeviceCode(scopes, - deviceCodeResult => DeviceFlowCallback(deviceCodeResult)).ExecuteAsync(); + deviceCodeResult => _deviceCodeFlowCallback(deviceCodeResult)).ExecuteAsync(); return result; } } @@ -190,7 +192,7 @@ private async Task AcquireTokenInteractiveDeviceFlow(IPubl } } - private Task DeviceFlowCallback(DeviceCodeResult result) + private Task DefaultDeviceFlowCallback(DeviceCodeResult result) { // This will print the message on the console which tells the user where to go sign-in using // a separate browser and the code to enter once they sign in. @@ -206,10 +208,13 @@ private Task DeviceFlowCallback(DeviceCodeResult result) return Task.FromResult(0); } - /// - /// Checks support for authentication type in lower case. - /// Interactive authentication added. - /// + /// + public void SetDeviceCodeFlowCallback(Func deviceCodeFlowCallbackMethod) + { + _deviceCodeFlowCallback = deviceCodeFlowCallbackMethod; + } + + /// public override bool IsSupported(SqlAuthenticationMethod authentication) { return authentication == SqlAuthenticationMethod.ActiveDirectoryIntegrated @@ -219,11 +224,13 @@ public override bool IsSupported(SqlAuthenticationMethod authentication) || authentication == SqlAuthenticationMethod.ActiveDirectoryDeviceCodeFlow; } + /// public override void BeforeLoad(SqlAuthenticationMethod authentication) { _logger.LogInfo(_type, "BeforeLoad", $"being loaded into SqlAuthProviders for {authentication}."); } + /// public override void BeforeUnload(SqlAuthenticationMethod authentication) { _logger.LogInfo(_type, "BeforeUnload", $"being unloaded from SqlAuthProviders for {authentication}."); diff --git a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/AADAccessTokenTest.cs b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/AADTests.cs similarity index 72% rename from src/Microsoft.Data.SqlClient/tests/FunctionalTests/AADAccessTokenTest.cs rename to src/Microsoft.Data.SqlClient/tests/FunctionalTests/AADTests.cs index ffb21e767b..ec3cefda72 100644 --- a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/AADAccessTokenTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/AADTests.cs @@ -4,11 +4,13 @@ using System; using System.Security; +using System.Threading.Tasks; +using Microsoft.Identity.Client; using Xunit; namespace Microsoft.Data.SqlClient.Tests { - public class AADAccessTokenTest + public class AADTests { private SqlConnectionStringBuilder _builder; private SqlCredential _credential = null; @@ -38,6 +40,7 @@ public void InvalidCombinationOfAccessToken(string description, object[] Params) InvalidCombinationCheck(_credential); } + private void InvalidCombinationCheck(SqlCredential credential) { using (SqlConnection connection = new SqlConnection(_builder.ConnectionString, credential)) @@ -45,5 +48,19 @@ private void InvalidCombinationCheck(SqlCredential credential) Assert.Throws(() => connection.AccessToken = "SampleAccessToken"); } } + + [Fact] + public void CustomActiveDirectoryProviderTest() + { + SqlAuthenticationProvider authProvider = new ActiveDirectoryAuthenticationProvider(CustomDeviceFlowCallback); + SqlAuthenticationProvider.SetProvider(SqlAuthenticationMethod.ActiveDirectoryDeviceCodeFlow, authProvider); + Assert.Equal(authProvider, SqlAuthenticationProvider.GetProvider(SqlAuthenticationMethod.ActiveDirectoryDeviceCodeFlow)); + } + + private Task CustomDeviceFlowCallback(DeviceCodeResult result) + { + Console.WriteLine(result.Message); + return Task.FromResult(0); + } } } diff --git a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/Microsoft.Data.SqlClient.Tests.csproj b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/Microsoft.Data.SqlClient.Tests.csproj index c0cdf6fd85..d114f6ced9 100644 --- a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/Microsoft.Data.SqlClient.Tests.csproj +++ b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/Microsoft.Data.SqlClient.Tests.csproj @@ -31,7 +31,7 @@ - + From 1a99c9d2277446eb641ad1226128f56677825671 Mon Sep 17 00:00:00 2001 From: Cheena Malhotra Date: Thu, 11 Jun 2020 12:15:59 -0700 Subject: [PATCH 07/14] Improvements --- .../SqlClient/ActiveDirectoryAuthenticationProvider.cs | 7 ++----- .../{AADTests.cs => AADAuthenticationTests.cs} | 2 +- .../FunctionalTests/Microsoft.Data.SqlClient.Tests.csproj | 2 +- 3 files changed, 4 insertions(+), 7 deletions(-) rename src/Microsoft.Data.SqlClient/tests/FunctionalTests/{AADTests.cs => AADAuthenticationTests.cs} (98%) 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 ed084708bf..8b43f79f83 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ActiveDirectoryAuthenticationProvider.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ActiveDirectoryAuthenticationProvider.cs @@ -25,7 +25,7 @@ public class ActiveDirectoryAuthenticationProvider : SqlAuthenticationProvider /// public ActiveDirectoryAuthenticationProvider(Func deviceCodeFlowCallbackMethod) { - this._deviceCodeFlowCallback = deviceCodeFlowCallbackMethod; + SetDeviceCodeFlowCallback(deviceCodeFlowCallbackMethod); } /// @@ -209,10 +209,7 @@ private Task DefaultDeviceFlowCallback(DeviceCodeResult result) } /// - public void SetDeviceCodeFlowCallback(Func deviceCodeFlowCallbackMethod) - { - _deviceCodeFlowCallback = deviceCodeFlowCallbackMethod; - } + public void SetDeviceCodeFlowCallback(Func deviceCodeFlowCallbackMethod) => _deviceCodeFlowCallback = deviceCodeFlowCallbackMethod; /// public override bool IsSupported(SqlAuthenticationMethod authentication) diff --git a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/AADTests.cs b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/AADAuthenticationTests.cs similarity index 98% rename from src/Microsoft.Data.SqlClient/tests/FunctionalTests/AADTests.cs rename to src/Microsoft.Data.SqlClient/tests/FunctionalTests/AADAuthenticationTests.cs index ec3cefda72..0e695cdd4e 100644 --- a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/AADTests.cs +++ b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/AADAuthenticationTests.cs @@ -10,7 +10,7 @@ namespace Microsoft.Data.SqlClient.Tests { - public class AADTests + public class AADAuthenticationTests { private SqlConnectionStringBuilder _builder; private SqlCredential _credential = null; diff --git a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/Microsoft.Data.SqlClient.Tests.csproj b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/Microsoft.Data.SqlClient.Tests.csproj index d114f6ced9..ed7e701212 100644 --- a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/Microsoft.Data.SqlClient.Tests.csproj +++ b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/Microsoft.Data.SqlClient.Tests.csproj @@ -31,7 +31,7 @@ - + From 4c22829b44d130cd4b3b3b1d064f0dd4989bde39 Mon Sep 17 00:00:00 2001 From: Cheena Malhotra Date: Thu, 11 Jun 2020 13:22:34 -0700 Subject: [PATCH 08/14] Add refs + sealed class --- .../netcore/ref/Microsoft.Data.SqlClient.cs | 18 ++++++++++++++++++ .../ref/Microsoft.Data.SqlClient.csproj | 3 +++ .../netfx/ref/Microsoft.Data.SqlClient.cs | 18 ++++++++++++++++++ .../netfx/ref/Microsoft.Data.SqlClient.csproj | 3 +++ .../ActiveDirectoryAuthenticationProvider.cs | 2 +- 5 files changed, 43 insertions(+), 1 deletion(-) 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 2d49d15cda..679f5d32fc 100644 --- a/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.cs +++ b/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.cs @@ -30,6 +30,24 @@ public SqlNotificationRequest(string userData, string options, int timeout) { } } namespace Microsoft.Data.SqlClient { + /// + public sealed class ActiveDirectoryAuthenticationProvider : SqlAuthenticationProvider + { + /// + public ActiveDirectoryAuthenticationProvider() { } + /// + public ActiveDirectoryAuthenticationProvider(System.Func deviceCodeFlowCallbackMethod) { } + /// + public override System.Threading.Tasks.Task AcquireTokenAsync(SqlAuthenticationParameters parameters) { throw null; } + /// + public void SetDeviceCodeFlowCallback(System.Func deviceCodeFlowCallbackMethod) { } + /// + public override bool IsSupported(SqlAuthenticationMethod authentication) { throw null; } + /// + public override void BeforeLoad(SqlAuthenticationMethod authentication) { } + /// + public override void BeforeUnload(SqlAuthenticationMethod authentication) { } + } /// public enum ApplicationIntent { diff --git a/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.csproj b/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.csproj index b1934599e6..661a54a500 100644 --- a/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.csproj @@ -19,4 +19,7 @@ + + + \ No newline at end of file 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 46a228facb..4081ed7707 100644 --- a/src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.cs +++ b/src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.cs @@ -35,6 +35,24 @@ public SqlNotificationRequest(string userData, string options, int timeout) { } namespace Microsoft.Data.SqlClient { + /// + public sealed class ActiveDirectoryAuthenticationProvider : SqlAuthenticationProvider + { + /// + public ActiveDirectoryAuthenticationProvider() { } + /// + public ActiveDirectoryAuthenticationProvider(System.Func deviceCodeFlowCallbackMethod) { } + /// + public override System.Threading.Tasks.Task AcquireTokenAsync(SqlAuthenticationParameters parameters) { throw null; } + /// + public void SetDeviceCodeFlowCallback(System.Func deviceCodeFlowCallbackMethod) { } + /// + public override bool IsSupported(SqlAuthenticationMethod authentication) { throw null; } + /// + public override void BeforeLoad(SqlAuthenticationMethod authentication) { } + /// + public override void BeforeUnload(SqlAuthenticationMethod authentication) { } + } /// public enum ApplicationIntent { diff --git a/src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.csproj b/src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.csproj index 4d5f63e325..fa07c461b8 100644 --- a/src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.csproj @@ -15,4 +15,7 @@ + + + \ No newline at end of file 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 8b43f79f83..6589f9a077 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ActiveDirectoryAuthenticationProvider.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ActiveDirectoryAuthenticationProvider.cs @@ -12,7 +12,7 @@ namespace Microsoft.Data.SqlClient { /// - public class ActiveDirectoryAuthenticationProvider : SqlAuthenticationProvider + public sealed class ActiveDirectoryAuthenticationProvider : SqlAuthenticationProvider { private static readonly string s_defaultScopeSuffix = "/.default"; private readonly string _type = typeof(ActiveDirectoryAuthenticationProvider).Name; From 4b34acfe765be0458fe191cf7d2f24e515ab569a Mon Sep 17 00:00:00 2001 From: Cheena Malhotra Date: Tue, 23 Jun 2020 08:06:05 -0700 Subject: [PATCH 09/14] New APIs for custom window/UI support for AD Interactive authentication --- .../ActiveDirectoryAuthenticationProvider.xml | 15 +++- .../src/Microsoft.Data.SqlClient.csproj | 3 + .../netfx/src/Microsoft.Data.SqlClient.csproj | 3 +- .../ActiveDirectoryAuthenticationProvider.cs | 80 ++++++++++++++++--- 4 files changed, 90 insertions(+), 11 deletions(-) diff --git a/doc/snippets/Microsoft.Data.SqlClient/ActiveDirectoryAuthenticationProvider.xml b/doc/snippets/Microsoft.Data.SqlClient/ActiveDirectoryAuthenticationProvider.xml index 607ee31342..ba39b5cdd2 100644 --- a/doc/snippets/Microsoft.Data.SqlClient/ActiveDirectoryAuthenticationProvider.xml +++ b/doc/snippets/Microsoft.Data.SqlClient/ActiveDirectoryAuthenticationProvider.xml @@ -23,8 +23,21 @@ The callback method to be used by driver when performing 'Active Directory Device Code Flow' authentication. - Set's callback method that overrides driver's default implementation to process result when performing 'Active Directory Device Code Flow' authentication. + Sets callback method that overrides driver's default implementation to process result when performing 'Active Directory Device Code Flow' authentication. + + The parent as an object, in order to be used from shared NetStandard assemblies. + Sets a reference to the ViewController (if using Xamarin.iOS), Activity (if using Xamarin.Android) IWin32Window or IntPtr (if using .Net Framework). Used for invoking the browser for Active Directory Interactive authentication. + Mandatory only on Android to be set. See https://aka.ms/msal-net-android-activity for further documentation and details. + + + A function to return the current window. + Sets a reference to the current that triggers the browser to be shown. Used to center the browser that pop-up onto this window." + + + Customer implementation for the Web UI + Sets a custom Web UI that will let the user sign-in with Azure AD, present consent if needed, and get back the authorization code. Applicable when working with Active Directory Interactive authentication. + The authentication method. This method is called immediately before the provider is added to SQL drivers registry. diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj index 4328cfeebc..2b26ed8f88 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj @@ -21,6 +21,9 @@ $(DefineConstants);netcoreapp; + + $(DefineConstants);netstandard; + $(DefineConstants);NETCORE3 diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj index f90f36784d..7c795258e6 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj @@ -20,6 +20,7 @@ True + $(DefineConstants);netfx; @@ -456,4 +457,4 @@ - + \ No newline at end of file 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 6589f9a077..9bd7f074d5 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ActiveDirectoryAuthenticationProvider.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ActiveDirectoryAuthenticationProvider.cs @@ -8,6 +8,7 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.Identity.Client; +using Microsoft.Identity.Client.Extensibility; namespace Microsoft.Data.SqlClient { @@ -18,6 +19,7 @@ public sealed class ActiveDirectoryAuthenticationProvider : SqlAuthenticationPro private readonly string _type = typeof(ActiveDirectoryAuthenticationProvider).Name; private readonly SqlClientLogger _logger = new SqlClientLogger(); private Func _deviceCodeFlowCallback; + private ICustomWebUi customWebUI = null; /// public ActiveDirectoryAuthenticationProvider() => new ActiveDirectoryAuthenticationProvider(DefaultDeviceFlowCallback); @@ -28,6 +30,26 @@ public ActiveDirectoryAuthenticationProvider(Func device SetDeviceCodeFlowCallback(deviceCodeFlowCallbackMethod); } + /// + public void SetDeviceCodeFlowCallback(Func deviceCodeFlowCallbackMethod) => _deviceCodeFlowCallback = deviceCodeFlowCallbackMethod; + + /// + public void SetCustomWebUI(ICustomWebUi customWebUi) => this.customWebUI = customWebUi; + +#if netstandard + private Func parentActivityOrWindowFunc = null; + + /// + public void SetParentActivityOrWindowFunc(Func parentActivityOrWindowFunc) => this.parentActivityOrWindowFunc = parentActivityOrWindowFunc; +#endif + +#if netfx + private Func iWin32WindowFunc = null; + + /// + public void SetIWin32WindowFunc(Func iWin32WindowFunc) => this.iWin32WindowFunc = iWin32WindowFunc; +#endif + /// public override Task AcquireTokenAsync(SqlAuthenticationParameters parameters) => Task.Run(async () => { @@ -63,12 +85,44 @@ public override Task AcquireTokenAsync(SqlAuthentication redirectURI = "http://localhost"; } #endif - IPublicClientApplication app = PublicClientApplicationBuilder.Create(ActiveDirectoryAuthentication.AdoClientId) + IPublicClientApplication app; + +#if netstandard + if (parentActivityOrWindowFunc != null) + { + app = PublicClientApplicationBuilder.Create(ActiveDirectoryAuthentication.AdoClientId) + .WithAuthority(parameters.Authority) + .WithClientName(Common.DbConnectionStringDefaults.ApplicationName) + .WithClientVersion(Common.ADP.GetAssemblyVersion().ToString()) + .WithRedirectUri(redirectURI) + .WithParentActivityOrWindow(parentActivityOrWindowFunc) + .Build(); + } +#endif +#if netfx + if (iWin32WindowFunc != null) + { + app = PublicClientApplicationBuilder.Create(ActiveDirectoryAuthentication.AdoClientId) .WithAuthority(parameters.Authority) .WithClientName(Common.DbConnectionStringDefaults.ApplicationName) .WithClientVersion(Common.ADP.GetAssemblyVersion().ToString()) .WithRedirectUri(redirectURI) + .WithParentActivityOrWindow(iWin32WindowFunc) .Build(); + } +#endif +#if !netcoreapp + else +#endif + { + + app = PublicClientApplicationBuilder.Create(ActiveDirectoryAuthentication.AdoClientId) + .WithAuthority(parameters.Authority) + .WithClientName(Common.DbConnectionStringDefaults.ApplicationName) + .WithClientVersion(Common.ADP.GetAssemblyVersion().ToString()) + .WithRedirectUri(redirectURI) + .Build(); + } if (parameters.AuthenticationMethod == SqlAuthenticationMethod.ActiveDirectoryIntegrated) { @@ -134,6 +188,7 @@ public override Task AcquireTokenAsync(SqlAuthentication return new SqlAuthenticationToken(result.AccessToken, result.ExpiresOn); }); + private async Task AcquireTokenInteractiveDeviceFlow(IPublicClientApplication app, string[] scopes, Guid connectionId, string userId, SqlAuthenticationMethod authenticationMethod) { @@ -154,7 +209,16 @@ private async Task AcquireTokenInteractiveDeviceFlow(IPubl { if (authenticationMethod == SqlAuthenticationMethod.ActiveDirectoryInteractive) { - return await app.AcquireTokenInteractive(scopes) + if (customWebUI != null) + { + return await app.AcquireTokenInteractive(scopes) + .WithCorrelationId(connectionId) + .WithCustomWebUi(customWebUI) + .WithLoginHint(userId) + .ExecuteAsync(cts.Token); + } + else + { /* * We will use the MSAL Embedded or System web browser which changes by Default in MSAL according to this table: * @@ -172,10 +236,11 @@ private async Task AcquireTokenInteractiveDeviceFlow(IPubl * * https://github.com/AzureAD/microsoft-authentication-library-for-dotnet/wiki/MSAL.NET-uses-web-browser#at-a-glance */ - //.WithUseEmbeddedWebView(true) - .WithCorrelationId(connectionId) - .WithLoginHint(userId) - .ExecuteAsync(cts.Token); + return await app.AcquireTokenInteractive(scopes) + .WithCorrelationId(connectionId) + .WithLoginHint(userId) + .ExecuteAsync(cts.Token); + } } else { @@ -208,9 +273,6 @@ private Task DefaultDeviceFlowCallback(DeviceCodeResult result) return Task.FromResult(0); } - /// - public void SetDeviceCodeFlowCallback(Func deviceCodeFlowCallbackMethod) => _deviceCodeFlowCallback = deviceCodeFlowCallbackMethod; - /// public override bool IsSupported(SqlAuthenticationMethod authentication) { From f1be61f0debfb8586ea0126d2fa53795b6ffeac9 Mon Sep 17 00:00:00 2001 From: Cheena Malhotra Date: Thu, 25 Jun 2020 09:31:58 -0700 Subject: [PATCH 10/14] More changes --- .../ref/Microsoft.Data.SqlClient.NetStandard.cs | 16 ++++++++++++++++ .../netcore/ref/Microsoft.Data.SqlClient.cs | 4 +++- .../netcore/ref/Microsoft.Data.SqlClient.csproj | 3 +++ .../netfx/ref/Microsoft.Data.SqlClient.cs | 4 ++++ .../ActiveDirectoryAuthenticationProvider.cs | 3 +-- 5 files changed, 27 insertions(+), 3 deletions(-) create mode 100644 src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.NetStandard.cs diff --git a/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.NetStandard.cs b/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.NetStandard.cs new file mode 100644 index 0000000000..bdb2042b84 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.NetStandard.cs @@ -0,0 +1,16 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. +// ------------------------------------------------------------------------------ +// Changes to this file must follow the http://aka.ms/api-review process. +// ------------------------------------------------------------------------------ + +namespace Microsoft.Data.SqlClient +{ + /// + public sealed partial class ActiveDirectoryAuthenticationProvider : SqlAuthenticationProvider + { + /// + public void SetParentActivityOrWindowFunc(System.Func parentActivityOrWindowFunc) { } + } +} 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 bb52f2dbb6..45b0110a57 100644 --- a/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.cs +++ b/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.cs @@ -31,7 +31,7 @@ public SqlNotificationRequest(string userData, string options, int timeout) { } namespace Microsoft.Data.SqlClient { /// - public sealed class ActiveDirectoryAuthenticationProvider : SqlAuthenticationProvider + public sealed partial class ActiveDirectoryAuthenticationProvider : SqlAuthenticationProvider { /// public ActiveDirectoryAuthenticationProvider() { } @@ -41,6 +41,8 @@ public ActiveDirectoryAuthenticationProvider(System.Func AcquireTokenAsync(SqlAuthenticationParameters parameters) { throw null; } /// public void SetDeviceCodeFlowCallback(System.Func deviceCodeFlowCallbackMethod) { } + /// + public void SetCustomWebUI(Microsoft.Identity.Client.Extensibility.ICustomWebUi customWebUi) { } /// public override bool IsSupported(SqlAuthenticationMethod authentication) { throw null; } /// diff --git a/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.csproj b/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.csproj index 661a54a500..dad15bc623 100644 --- a/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.csproj @@ -19,6 +19,9 @@ + + + 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 a574b75859..9bb8c796dc 100644 --- a/src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.cs +++ b/src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.cs @@ -46,6 +46,10 @@ public ActiveDirectoryAuthenticationProvider(System.Func AcquireTokenAsync(SqlAuthenticationParameters parameters) { throw null; } /// public void SetDeviceCodeFlowCallback(System.Func deviceCodeFlowCallbackMethod) { } + /// + public void SetCustomWebUI(Microsoft.Identity.Client.Extensibility.ICustomWebUi customWebUi) { } + /// + public void SetIWin32WindowFunc(System.Func iWin32WindowFunc) { } /// public override bool IsSupported(SqlAuthenticationMethod authentication) { throw 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 9bd7f074d5..15a2bcb12d 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ActiveDirectoryAuthenticationProvider.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ActiveDirectoryAuthenticationProvider.cs @@ -115,7 +115,6 @@ public override Task AcquireTokenAsync(SqlAuthentication else #endif { - app = PublicClientApplicationBuilder.Create(ActiveDirectoryAuthentication.AdoClientId) .WithAuthority(parameters.Authority) .WithClientName(Common.DbConnectionStringDefaults.ApplicationName) @@ -153,7 +152,7 @@ public override Task AcquireTokenAsync(SqlAuthentication else if (parameters.AuthenticationMethod == SqlAuthenticationMethod.ActiveDirectoryInteractive || parameters.AuthenticationMethod == SqlAuthenticationMethod.ActiveDirectoryDeviceCodeFlow) { - var accounts = await app.GetAccountsAsync(); + System.Collections.Generic.IEnumerable accounts = await app.GetAccountsAsync(); IAccount account; if (!string.IsNullOrEmpty(parameters.UserId)) { From 095237bc24beb681d464daf56d686ba5818a2aef Mon Sep 17 00:00:00 2001 From: Cheena Malhotra Date: Mon, 20 Jul 2020 13:09:08 -0700 Subject: [PATCH 11/14] Apply suggestions from code review Co-authored-by: David Engel --- .../ActiveDirectoryAuthenticationProvider.xml | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/doc/snippets/Microsoft.Data.SqlClient/ActiveDirectoryAuthenticationProvider.xml b/doc/snippets/Microsoft.Data.SqlClient/ActiveDirectoryAuthenticationProvider.xml index ba39b5cdd2..f98c296ada 100644 --- a/doc/snippets/Microsoft.Data.SqlClient/ActiveDirectoryAuthenticationProvider.xml +++ b/doc/snippets/Microsoft.Data.SqlClient/ActiveDirectoryAuthenticationProvider.xml @@ -11,42 +11,42 @@ - The callback method to be used by driver when performing 'Active Directory Device Code Flow' authentication. + The callback method to be used when performing 'Active Directory Device Code Flow' authentication. - Initializes the class with provided device code flow callback method. + Initializes the class with the provided device code flow callback method. - The Active Directory authentication parameters passed by the driver to authentication providers. + The Active Directory authentication parameters passed to authentication providers. Acquires a security token from the authority. - Represents an asynchronous operation that returns the AD authentication token. + Represents an asynchronous operation that returns the authentication token. - The callback method to be used by driver when performing 'Active Directory Device Code Flow' authentication. - Sets callback method that overrides driver's default implementation to process result when performing 'Active Directory Device Code Flow' authentication. + The callback method to be used when performing 'Active Directory Device Code Flow' authentication. + Sets the callback method, overriding the default implementation that processes the result when performing 'Active Directory Device Code Flow' authentication. The parent as an object, in order to be used from shared NetStandard assemblies. - Sets a reference to the ViewController (if using Xamarin.iOS), Activity (if using Xamarin.Android) IWin32Window or IntPtr (if using .Net Framework). Used for invoking the browser for Active Directory Interactive authentication. - Mandatory only on Android to be set. See https://aka.ms/msal-net-android-activity for further documentation and details. + Sets a reference to the ViewController (if using Xamarin.iOS), Activity (if using Xamarin.Android) IWin32Window or IntPtr (if using .NET Framework). Used for invoking the browser for Active Directory Interactive authentication. + Mandatory to be set only on Android. See https://aka.ms/msal-net-android-activity for further documentation and details. A function to return the current window. - Sets a reference to the current that triggers the browser to be shown. Used to center the browser that pop-up onto this window." + Sets a reference to the current that triggers the browser to be shown. Used to center the browser pop-up onto this window." Customer implementation for the Web UI - Sets a custom Web UI that will let the user sign-in with Azure AD, present consent if needed, and get back the authorization code. Applicable when working with Active Directory Interactive authentication. + Sets a custom Web UI that will let the user sign-in with Azure Active Directory, present consent if needed, and get back the authorization code. Applicable when working with Active Directory Interactive authentication. The authentication method. - This method is called immediately before the provider is added to SQL drivers registry. + This method is called immediately before the provider is added to authentication provider registry. Avoid performing long-waiting tasks in this method, since it can block other threads from accessing the provider registry. The authentication method. - This method is called immediately before the provider is removed from the SQL drivers registry. - For example, this method is called when a different provider with the same authentication method overrides this provider in the SQL drivers registry. Avoid performing long-waiting task in this method, since it can block other threads from accessing the provider registry. + This method is called immediately before the provider is removed from the authentication provider registry. + For example, this method is called when a different provider with the same authentication method overrides this provider in the authentication provider registry. Avoid performing long-waiting task in this method, since it can block other threads from accessing the provider registry. The authentication method. @@ -70,7 +70,7 @@ || ## Examples - The following example demonstrates providing a custom device flow callback to SqlClient driver for Device Code Flow authentication method: + The following example demonstrates providing a custom device flow callback to SqlClient for the Device Code Flow authentication method: [!code-csharp[ActiveDirectory_DeviceCodeFlowCallback Example#1](~/../sqlclient/doc/samples/AADAuthenticationCustomDeviceFlowCallback.cs#1)] From a6cd9216340aaf2b865098f5a3449414db8a0200 Mon Sep 17 00:00:00 2001 From: Cheena Malhotra Date: Mon, 20 Jul 2020 14:22:03 -0700 Subject: [PATCH 12/14] Update API to accept Function as parameter --- .../ActiveDirectoryAuthenticationProvider.xml | 9 +- .../SqlAuthenticationProvider.xml | 113 ++++++++++-------- .../netcore/ref/Microsoft.Data.SqlClient.cs | 4 +- .../netfx/ref/Microsoft.Data.SqlClient.cs | 4 +- .../ActiveDirectoryAuthenticationProvider.cs | 64 +++++----- 5 files changed, 112 insertions(+), 82 deletions(-) diff --git a/doc/snippets/Microsoft.Data.SqlClient/ActiveDirectoryAuthenticationProvider.xml b/doc/snippets/Microsoft.Data.SqlClient/ActiveDirectoryAuthenticationProvider.xml index f98c296ada..1a2d5a95a1 100644 --- a/doc/snippets/Microsoft.Data.SqlClient/ActiveDirectoryAuthenticationProvider.xml +++ b/doc/snippets/Microsoft.Data.SqlClient/ActiveDirectoryAuthenticationProvider.xml @@ -34,10 +34,11 @@ A function to return the current window. Sets a reference to the current that triggers the browser to be shown. Used to center the browser pop-up onto this window." - - Customer implementation for the Web UI - Sets a custom Web UI that will let the user sign-in with Azure Active Directory, present consent if needed, and get back the authorization code. Applicable when working with Active Directory Interactive authentication. - + + The callback method to be called by MSAL.NET to delegate the authentication code Web with the Secure Token Service (STS). + Sets a callback method which is invoked with a custom Web UI instance that will let the user sign-in with Azure Active Directory, present consent if needed, and get back the authorization code. Applicable when working with Active Directory Interactive authentication. + The "authorizationUri" is crafted to leverage PKCE in order to protect the token from a man in the middle attack. Only MSAL.NET can redeem the code. In the event of cancellation, the implementer should return . + The authentication method. This method is called immediately before the provider is added to authentication provider registry. diff --git a/doc/snippets/Microsoft.Data.SqlClient/SqlAuthenticationProvider.xml b/doc/snippets/Microsoft.Data.SqlClient/SqlAuthenticationProvider.xml index e6f373c300..4f21b9626f 100644 --- a/doc/snippets/Microsoft.Data.SqlClient/SqlAuthenticationProvider.xml +++ b/doc/snippets/Microsoft.Data.SqlClient/SqlAuthenticationProvider.xml @@ -1,49 +1,68 @@ - - - Defines the core behavior of authentication providers and provides a base class for derived classes. - Derived classes must provide a parameterless constructor if they can be instantiated from the app.config file. - - - Called from constructors in derived classes to initialize the class. - - - The authentication method. - Gets an authentication provider by method. - The authentication provider or if not found. - To be added. - - - The authentication method. - The authentication provider. - Sets an authentication provider by method. - - if the operation succeeded; otherwise, (for example, the existing provider disallows overriding). - - To be added. - - - The authentication method. - This method is called immediately before the provider is added to SQL drivers registry. - Avoid performing long-waiting tasks in this method, since it can block other threads from accessing the provider registry. - - - The authentication method. - This method is called immediately before the provider is removed from the SQL drivers registry. - For example, this method is called when a different provider with the same authentication method overrides this provider in the SQL drivers registry. Avoid performing long-waiting task in this method, since it can block other threads from accessing the provider registry. - - - The authentication method. - Indicates whether the specified authentication method is supported. - - if the specified authentication method is supported; otherwise, . - To be added. - - - The Active Directory authentication parameters passed by the driver to authentication providers. - Acquires a security token from the authority. - Represents an asynchronous operation that returns the AD authentication token. - To be added. - - + + + Defines the core behavior of authentication providers and provides a base class for derived classes. + + + + + + + + + Called from constructors in derived classes to initialize the class. + + + + The authentication method. + Gets an authentication provider by method. + + The authentication provider or if not found. + + To be added. + + + The authentication method. + The authentication provider. + Sets an authentication provider by method. + + if the operation succeeded; otherwise, (for example, the existing provider disallows overriding). + + To be added. + + + The authentication method. + This method is called immediately before the provider is added to SQL drivers registry. + Avoid performing long-waiting tasks in this method, since it can block other threads from accessing the provider registry. + + + The authentication method. + This method is called immediately before the provider is removed from the SQL drivers registry. + For example, this method is called when a different provider with the same authentication method overrides this provider in the SQL drivers registry. Avoid performing long-waiting task in this method, since it can block other threads from accessing the provider registry. + + + The authentication method. + Indicates whether the specified authentication method is supported. + + if the specified authentication method is supported; otherwise, . + + To be added. + + + The Active Directory authentication parameters passed by the driver to authentication providers. + Acquires a security token from the authority. + Represents an asynchronous operation that returns the AD authentication token. + To be added. + + 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 45b0110a57..b09ab4450f 100644 --- a/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.cs +++ b/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.cs @@ -41,8 +41,8 @@ public ActiveDirectoryAuthenticationProvider(System.Func AcquireTokenAsync(SqlAuthenticationParameters parameters) { throw null; } /// public void SetDeviceCodeFlowCallback(System.Func deviceCodeFlowCallbackMethod) { } - /// - public void SetCustomWebUI(Microsoft.Identity.Client.Extensibility.ICustomWebUi customWebUi) { } + /// + public void SetAcquireAuthorizationCodeAsyncCallback(System.Func> acquireAuthorizationCodeAsyncCallback) { } /// public override bool IsSupported(SqlAuthenticationMethod authentication) { throw 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 9bb8c796dc..569e0c9040 100644 --- a/src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.cs +++ b/src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.cs @@ -46,8 +46,8 @@ public ActiveDirectoryAuthenticationProvider(System.Func AcquireTokenAsync(SqlAuthenticationParameters parameters) { throw null; } /// public void SetDeviceCodeFlowCallback(System.Func deviceCodeFlowCallbackMethod) { } - /// - public void SetCustomWebUI(Microsoft.Identity.Client.Extensibility.ICustomWebUi customWebUi) { } + /// + public void SetAcquireAuthorizationCodeAsyncCallback(System.Func> acquireAuthorizationCodeAsyncCallback) { } /// public void SetIWin32WindowFunc(System.Func iWin32WindowFunc) { } /// 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 15a2bcb12d..8d1d6efafe 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ActiveDirectoryAuthenticationProvider.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ActiveDirectoryAuthenticationProvider.cs @@ -19,7 +19,7 @@ public sealed class ActiveDirectoryAuthenticationProvider : SqlAuthenticationPro private readonly string _type = typeof(ActiveDirectoryAuthenticationProvider).Name; private readonly SqlClientLogger _logger = new SqlClientLogger(); private Func _deviceCodeFlowCallback; - private ICustomWebUi customWebUI = null; + private ICustomWebUi _customWebUI = null; /// public ActiveDirectoryAuthenticationProvider() => new ActiveDirectoryAuthenticationProvider(DefaultDeviceFlowCallback); @@ -33,8 +33,30 @@ public ActiveDirectoryAuthenticationProvider(Func device /// public void SetDeviceCodeFlowCallback(Func deviceCodeFlowCallbackMethod) => _deviceCodeFlowCallback = deviceCodeFlowCallbackMethod; - /// - public void SetCustomWebUI(ICustomWebUi customWebUi) => this.customWebUI = customWebUi; + /// + public void SetAcquireAuthorizationCodeAsyncCallback(Func> acquireAuthorizationCodeAsyncCallback) => _customWebUI = new CustomWebUi(acquireAuthorizationCodeAsyncCallback); + + /// + public override bool IsSupported(SqlAuthenticationMethod authentication) + { + return authentication == SqlAuthenticationMethod.ActiveDirectoryIntegrated + || authentication == SqlAuthenticationMethod.ActiveDirectoryPassword + || authentication == SqlAuthenticationMethod.ActiveDirectoryInteractive + || authentication == SqlAuthenticationMethod.ActiveDirectoryServicePrincipal + || authentication == SqlAuthenticationMethod.ActiveDirectoryDeviceCodeFlow; + } + + /// + public override void BeforeLoad(SqlAuthenticationMethod authentication) + { + _logger.LogInfo(_type, "BeforeLoad", $"being loaded into SqlAuthProviders for {authentication}."); + } + + /// + public override void BeforeUnload(SqlAuthenticationMethod authentication) + { + _logger.LogInfo(_type, "BeforeUnload", $"being unloaded from SqlAuthProviders for {authentication}."); + } #if netstandard private Func parentActivityOrWindowFunc = null; @@ -44,10 +66,10 @@ public ActiveDirectoryAuthenticationProvider(Func device #endif #if netfx - private Func iWin32WindowFunc = null; - + private Func _iWin32WindowFunc = null; + /// - public void SetIWin32WindowFunc(Func iWin32WindowFunc) => this.iWin32WindowFunc = iWin32WindowFunc; + public void SetIWin32WindowFunc(Func iWin32WindowFunc) => this._iWin32WindowFunc = iWin32WindowFunc; #endif /// @@ -100,14 +122,14 @@ public override Task AcquireTokenAsync(SqlAuthentication } #endif #if netfx - if (iWin32WindowFunc != null) + if (_iWin32WindowFunc != null) { app = PublicClientApplicationBuilder.Create(ActiveDirectoryAuthentication.AdoClientId) .WithAuthority(parameters.Authority) .WithClientName(Common.DbConnectionStringDefaults.ApplicationName) .WithClientVersion(Common.ADP.GetAssemblyVersion().ToString()) .WithRedirectUri(redirectURI) - .WithParentActivityOrWindow(iWin32WindowFunc) + .WithParentActivityOrWindow(_iWin32WindowFunc) .Build(); } #endif @@ -208,11 +230,11 @@ private async Task AcquireTokenInteractiveDeviceFlow(IPubl { if (authenticationMethod == SqlAuthenticationMethod.ActiveDirectoryInteractive) { - if (customWebUI != null) + if (_customWebUI != null) { return await app.AcquireTokenInteractive(scopes) .WithCorrelationId(connectionId) - .WithCustomWebUi(customWebUI) + .WithCustomWebUi(_customWebUI) .WithLoginHint(userId) .ExecuteAsync(cts.Token); } @@ -272,26 +294,14 @@ private Task DefaultDeviceFlowCallback(DeviceCodeResult result) return Task.FromResult(0); } - /// - public override bool IsSupported(SqlAuthenticationMethod authentication) + private class CustomWebUi : ICustomWebUi { - return authentication == SqlAuthenticationMethod.ActiveDirectoryIntegrated - || authentication == SqlAuthenticationMethod.ActiveDirectoryPassword - || authentication == SqlAuthenticationMethod.ActiveDirectoryInteractive - || authentication == SqlAuthenticationMethod.ActiveDirectoryServicePrincipal - || authentication == SqlAuthenticationMethod.ActiveDirectoryDeviceCodeFlow; - } + private readonly Func> _acquireAuthorizationCodeAsyncCallback; - /// - public override void BeforeLoad(SqlAuthenticationMethod authentication) - { - _logger.LogInfo(_type, "BeforeLoad", $"being loaded into SqlAuthProviders for {authentication}."); - } + internal CustomWebUi(Func> acquireAuthorizationCodeAsyncCallback) => _acquireAuthorizationCodeAsyncCallback = acquireAuthorizationCodeAsyncCallback; - /// - public override void BeforeUnload(SqlAuthenticationMethod authentication) - { - _logger.LogInfo(_type, "BeforeUnload", $"being unloaded from SqlAuthProviders for {authentication}."); + public Task AcquireAuthorizationCodeAsync(Uri authorizationUri, Uri redirectUri, CancellationToken cancellationToken) + => _acquireAuthorizationCodeAsyncCallback.Invoke(authorizationUri, redirectUri, cancellationToken); } } } From b15a202aefe612042310df1f2796bf015784104b Mon Sep 17 00:00:00 2001 From: Cheena Malhotra Date: Mon, 20 Jul 2020 17:55:26 -0700 Subject: [PATCH 13/14] Apply suggestions from code review Co-authored-by: David Engel --- .../ActiveDirectoryAuthenticationProvider.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/snippets/Microsoft.Data.SqlClient/ActiveDirectoryAuthenticationProvider.xml b/doc/snippets/Microsoft.Data.SqlClient/ActiveDirectoryAuthenticationProvider.xml index 1a2d5a95a1..49a2e5616a 100644 --- a/doc/snippets/Microsoft.Data.SqlClient/ActiveDirectoryAuthenticationProvider.xml +++ b/doc/snippets/Microsoft.Data.SqlClient/ActiveDirectoryAuthenticationProvider.xml @@ -26,7 +26,7 @@ Sets the callback method, overriding the default implementation that processes the result when performing 'Active Directory Device Code Flow' authentication. - The parent as an object, in order to be used from shared NetStandard assemblies. + The parent as an object, in order to be used from shared .NET Standard assemblies. Sets a reference to the ViewController (if using Xamarin.iOS), Activity (if using Xamarin.Android) IWin32Window or IntPtr (if using .NET Framework). Used for invoking the browser for Active Directory Interactive authentication. Mandatory to be set only on Android. See https://aka.ms/msal-net-android-activity for further documentation and details. @@ -35,7 +35,7 @@ Sets a reference to the current that triggers the browser to be shown. Used to center the browser pop-up onto this window." - The callback method to be called by MSAL.NET to delegate the authentication code Web with the Secure Token Service (STS). + The callback method to be called by MSAL.NET to delegate the Web user interface with the Secure Token Service (STS). Sets a callback method which is invoked with a custom Web UI instance that will let the user sign-in with Azure Active Directory, present consent if needed, and get back the authorization code. Applicable when working with Active Directory Interactive authentication. The "authorizationUri" is crafted to leverage PKCE in order to protect the token from a man in the middle attack. Only MSAL.NET can redeem the code. In the event of cancellation, the implementer should return . From 146d563478a8e447f0636ede78ea253b19ebfdc9 Mon Sep 17 00:00:00 2001 From: Cheena Malhotra Date: Thu, 23 Jul 2020 12:41:13 -0700 Subject: [PATCH 14/14] Edits --- doc/samples/AADAuthenticationCustomDeviceFlowCallback.cs | 4 ++-- .../Data/SqlClient/ActiveDirectoryAuthenticationProvider.cs | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/doc/samples/AADAuthenticationCustomDeviceFlowCallback.cs b/doc/samples/AADAuthenticationCustomDeviceFlowCallback.cs index bce949536b..bda3554bbe 100644 --- a/doc/samples/AADAuthenticationCustomDeviceFlowCallback.cs +++ b/doc/samples/AADAuthenticationCustomDeviceFlowCallback.cs @@ -19,9 +19,9 @@ public static void Main() } } - private Task CustomDeviceFlowCallback(DeviceCodeResult result) + private static Task CustomDeviceFlowCallback(DeviceCodeResult result) { - // Provide custon logic to process result information and read device code. + // Provide custom logic to process result information and read device code. Console.WriteLine(result.Message); return Task.FromResult(0); } 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 8d1d6efafe..fe3317f17b 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ActiveDirectoryAuthenticationProvider.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ActiveDirectoryAuthenticationProvider.cs @@ -193,12 +193,12 @@ public override Task AcquireTokenAsync(SqlAuthentication } catch (MsalUiRequiredException) { - result = await AcquireTokenInteractiveDeviceFlow(app, scopes, parameters.ConnectionId, parameters.UserId, parameters.AuthenticationMethod); + result = await AcquireTokenInteractiveDeviceFlowAsync(app, scopes, parameters.ConnectionId, parameters.UserId, parameters.AuthenticationMethod); } } else { - result = await AcquireTokenInteractiveDeviceFlow(app, scopes, parameters.ConnectionId, parameters.UserId, parameters.AuthenticationMethod); + result = await AcquireTokenInteractiveDeviceFlowAsync(app, scopes, parameters.ConnectionId, parameters.UserId, parameters.AuthenticationMethod); } } else @@ -210,7 +210,7 @@ public override Task AcquireTokenAsync(SqlAuthentication }); - private async Task AcquireTokenInteractiveDeviceFlow(IPublicClientApplication app, string[] scopes, Guid connectionId, string userId, + private async Task AcquireTokenInteractiveDeviceFlowAsync(IPublicClientApplication app, string[] scopes, Guid connectionId, string userId, SqlAuthenticationMethod authenticationMethod) { CancellationTokenSource cts = new CancellationTokenSource();