diff --git a/doc/samples/AADAuthenticationCustomDeviceFlowCallback.cs b/doc/samples/AADAuthenticationCustomDeviceFlowCallback.cs
new file mode 100644
index 0000000000..bda3554bbe
--- /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 static Task CustomDeviceFlowCallback(DeviceCodeResult result)
+ {
+ // Provide custom logic to process result information and read device code.
+ Console.WriteLine(result.Message);
+ return Task.FromResult(0);
+ }
+ }
+}
+//
diff --git a/doc/samples/CustomDeviceCodeFlowAzureAuthenticationProvider.cs b/doc/samples/CustomDeviceCodeFlowAzureAuthenticationProvider.cs
new file mode 100644
index 0000000000..19bc1f849d
--- /dev/null
+++ b/doc/samples/CustomDeviceCodeFlowAzureAuthenticationProvider.cs
@@ -0,0 +1,56 @@
+//
+using System;
+using System.Threading.Tasks;
+using Microsoft.Identity.Client;
+using Microsoft.Data.SqlClient;
+
+namespace CustomAuthenticationProviderExamples
+{
+ ///
+ /// 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
+ {
+ public override async 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) => authenticationMethod.Equals(SqlAuthenticationMethod.ActiveDirectoryDeviceCodeFlow);
+
+ 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/doc/snippets/Microsoft.Data.SqlClient/ActiveDirectoryAuthenticationProvider.xml b/doc/snippets/Microsoft.Data.SqlClient/ActiveDirectoryAuthenticationProvider.xml
new file mode 100644
index 0000000000..49a2e5616a
--- /dev/null
+++ b/doc/snippets/Microsoft.Data.SqlClient/ActiveDirectoryAuthenticationProvider.xml
@@ -0,0 +1,83 @@
+
+
+
+
+ This class implements and is used for active directory federated authentication mechanisms.
+
+
+
+
+ Initializes the class.
+
+
+
+ The callback method to be used when performing 'Active Directory Device Code Flow' authentication.
+
+ Initializes the class with the provided device code flow callback method.
+
+
+
+ The Active Directory authentication parameters passed to authentication providers.
+ Acquires a security token from the authority.
+ Represents an asynchronous operation that returns the authentication token.
+
+
+ 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 .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.
+
+
+ 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."
+
+
+ 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 .
+
+
+ The authentication method.
+ 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 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.
+ 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 for the Device Code Flow authentication method:
+
+ [!code-csharp[ActiveDirectory_DeviceCodeFlowCallback Example#1](~/../sqlclient/doc/samples/AADAuthenticationCustomDeviceFlowCallback.cs#1)]
+
+ ]]>
+
+
+
+
+
diff --git a/doc/snippets/Microsoft.Data.SqlClient/SqlAuthenticationMethod.xml b/doc/snippets/Microsoft.Data.SqlClient/SqlAuthenticationMethod.xml
index 4bb3bdb696..2ddb84b9a1 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..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.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/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 514a35ad36..c34dcddab7 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,26 @@ public SqlNotificationRequest(string userData, string options, int timeout) { }
}
namespace Microsoft.Data.SqlClient
{
+ ///
+ public sealed partial 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 void SetAcquireAuthorizationCodeAsyncCallback(System.Func> acquireAuthorizationCodeAsyncCallback) { }
+ ///
+ public override bool IsSupported(SqlAuthenticationMethod authentication) { throw null; }
+ ///
+ public override void BeforeLoad(SqlAuthenticationMethod authentication) { }
+ ///
+ public override void BeforeUnload(SqlAuthenticationMethod authentication) { }
+ }
///
public enum ApplicationIntent
{
@@ -69,6 +89,8 @@ public enum SqlAuthenticationMethod
ActiveDirectoryPassword = 2,
///
ActiveDirectoryServicePrincipal = 5,
+ ///
+ ActiveDirectoryDeviceCodeFlow = 6,
///
NotSpecified = 0,
///
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..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,4 +19,10 @@
+
+
+
+
+
+
\ No newline at end of file
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 746fe1d9ae..7d6f362461 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/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 ea3d80c02d..a139327841 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
@@ -147,6 +147,11 @@ public SqlConnection(string connectionString, SqlCredential credential) : this()
throw SQL.SettingCredentialWithInteractiveArgument();
}
+ if (UsesActiveDirectoryDeviceCodeFlow(connectionOptions))
+ {
+ throw SQL.SettingCredentialWithDeviceFlowArgument();
+ }
+
Credential = credential;
}
// else
@@ -402,6 +407,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;
@@ -458,7 +468,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))
@@ -469,6 +479,10 @@ public override string ConnectionString
{
throw SQL.SettingInteractiveWithCredential();
}
+ else if (UsesActiveDirectoryDeviceCodeFlow(connectionOptions))
+ {
+ throw SQL.SettingDeviceFlowWithCredential();
+ }
CheckAndThrowOnInvalidCombinationOfConnectionStringAndSqlCredential(connectionOptions);
}
@@ -752,6 +766,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 3c9f2116d2..0f0ce90477 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
@@ -171,9 +171,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;
@@ -182,7 +182,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 1999d4365b..3111fee442 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
@@ -1300,6 +1300,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))
@@ -2106,6 +2107,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.");
@@ -2336,6 +2338,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 5c8ec93400..37b42b7512 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 1d2dd44c0a..83005d7a03 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
@@ -250,6 +250,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
{
@@ -257,6 +258,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.
@@ -1131,6 +1133,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 a1d424120e..4d9c34247b 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
@@ -394,6 +394,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;
@@ -7795,6 +7798,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 23cbaa4efe..44caedd695 100644
--- a/src/Microsoft.Data.SqlClient/netcore/src/Resources/SR.Designer.cs
+++ b/src/Microsoft.Data.SqlClient/netcore/src/Resources/SR.Designer.cs
@@ -2652,6 +2652,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.
///
@@ -3057,6 +3066,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..
///
@@ -3075,6 +3093,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..
///
@@ -3210,6 +3237,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 47d203f9ff..b94391deac 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.
@@ -1893,4 +1896,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.
+
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 1b044d5373..76d4f44fbb 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,28 @@ 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 void SetAcquireAuthorizationCodeAsyncCallback(System.Func> acquireAuthorizationCodeAsyncCallback) { }
+ ///
+ public void SetIWin32WindowFunc(System.Func iWin32WindowFunc) { }
+ ///
+ public override bool IsSupported(SqlAuthenticationMethod authentication) { throw null; }
+ ///
+ public override void BeforeLoad(SqlAuthenticationMethod authentication) { }
+ ///
+ public override void BeforeUnload(SqlAuthenticationMethod authentication) { }
+ }
///
public enum ApplicationIntent
{
@@ -85,6 +107,8 @@ public enum SqlAuthenticationMethod
ActiveDirectoryPassword = 2,
///
ActiveDirectoryServicePrincipal = 5,
+ ///
+ ActiveDirectoryDeviceCodeFlow = 6,
///
NotSpecified = 0,
///
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.SqlClient.csproj b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj
index 6e247cc897..042ba6de5e 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;
@@ -505,4 +506,4 @@
-
+
\ 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..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.
///
@@ -21,6 +19,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 +39,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 +176,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 f023b3dacb..a0775464f4 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);
}
@@ -983,7 +997,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))
@@ -994,6 +1008,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 3d033f5a60..49f947acf2 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
@@ -1567,12 +1567,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))
{
@@ -1964,7 +1965,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.
@@ -2550,6 +2552,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.");
@@ -2770,6 +2773,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 6cb5b7ed8c..6318a1491f 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 cf8df1524b..c6ad7fea14 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
@@ -242,6 +242,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
{
@@ -249,6 +250,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.
@@ -1093,6 +1095,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 795a0ff841..03404784e0 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
@@ -545,6 +545,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;
@@ -8557,6 +8560,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 b6b1a532df..0501c22c3e 100644
--- a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.Designer.cs
+++ b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.Designer.cs
@@ -9441,6 +9441,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.
///
@@ -9918,6 +9927,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..
///
@@ -9936,6 +9954,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..
///
@@ -10152,6 +10179,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 1a90bdf991..c74e4526ef 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.
@@ -4563,4 +4566,13 @@
Server Process Id (SPID) of the active connection.
+
+ 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.
+
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..fe3317f17b 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ActiveDirectoryAuthenticationProvider.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ActiveDirectoryAuthenticationProvider.cs
@@ -3,28 +3,76 @@
// 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;
using System.Threading.Tasks;
using Microsoft.Identity.Client;
+using Microsoft.Identity.Client.Extensibility;
namespace Microsoft.Data.SqlClient
{
-
- ///
- /// Default auth provider for AD Integrated.
- ///
- internal class ActiveDirectoryAuthenticationProvider : SqlAuthenticationProvider
+ ///
+ public sealed 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;
+ private ICustomWebUi _customWebUI = null;
+
+ ///
+ public ActiveDirectoryAuthenticationProvider() => new ActiveDirectoryAuthenticationProvider(DefaultDeviceFlowCallback);
+
+ ///
+ public ActiveDirectoryAuthenticationProvider(Func deviceCodeFlowCallbackMethod)
+ {
+ SetDeviceCodeFlowCallback(deviceCodeFlowCallbackMethod);
+ }
+
+ ///
+ public void SetDeviceCodeFlowCallback(Func deviceCodeFlowCallbackMethod) => _deviceCodeFlowCallback = deviceCodeFlowCallbackMethod;
+
+ ///
+ 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}.");
+ }
- ///
- /// Get token.
- ///
+ ///
+ public override void BeforeUnload(SqlAuthenticationMethod authentication)
+ {
+ _logger.LogInfo(_type, "BeforeUnload", $"being unloaded from SqlAuthProviders for {authentication}.");
+ }
+
+#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 () =>
{
AuthenticationResult result;
@@ -44,23 +92,58 @@ public override Task AcquireTokenAsync(SqlAuthentication
return new SqlAuthenticationToken(result.AccessToken, result.ExpiresOn);
}
- IPublicClientApplication app = PublicClientApplicationBuilder.Create(ActiveDirectoryAuthentication.AdoClientId)
+ /*
+ * 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;
+
+#if netstandard
+ if (parentActivityOrWindowFunc != null)
+ {
+ 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")
+ .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)
{
@@ -88,9 +171,10 @@ 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();
+ System.Collections.Generic.IEnumerable accounts = await app.GetAccountsAsync();
IAccount account;
if (!string.IsNullOrEmpty(parameters.UserId))
{
@@ -109,12 +193,12 @@ public override Task AcquireTokenAsync(SqlAuthentication
}
catch (MsalUiRequiredException)
{
- result = await AcquireTokenInteractive(app, scopes, parameters.ConnectionId, parameters.UserId);
+ result = await AcquireTokenInteractiveDeviceFlowAsync(app, scopes, parameters.ConnectionId, parameters.UserId, parameters.AuthenticationMethod);
}
}
else
{
- result = await AcquireTokenInteractive(app, scopes, parameters.ConnectionId, parameters.UserId);
+ result = await AcquireTokenInteractiveDeviceFlowAsync(app, scopes, parameters.ConnectionId, parameters.UserId, parameters.AuthenticationMethod);
}
}
else
@@ -125,7 +209,9 @@ 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 AcquireTokenInteractiveDeviceFlowAsync(IPublicClientApplication app, string[] scopes, Guid connectionId, string userId,
+ SqlAuthenticationMethod authenticationMethod)
{
CancellationTokenSource cts = new CancellationTokenSource();
#if netcoreapp
@@ -142,55 +228,80 @@ 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)
+ {
+ 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:
+ *
+ * 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
+ */
+ return await app.AcquireTokenInteractive(scopes)
+ .WithCorrelationId(connectionId)
+ .WithLoginHint(userId)
+ .ExecuteAsync(cts.Token);
+ }
+ }
+ else
+ {
+ AuthenticationResult result = await app.AcquireTokenWithDeviceCode(scopes,
+ deviceCodeResult => _deviceCodeFlowCallback(deviceCodeResult)).ExecuteAsync();
+ return result;
+ }
}
catch (OperationCanceledException)
{
- throw SQL.ActiveDirectoryInteractiveTimeout();
+ throw (authenticationMethod == SqlAuthenticationMethod.ActiveDirectoryInteractive) ?
+ SQL.ActiveDirectoryInteractiveTimeout() :
+ SQL.ActiveDirectoryDeviceFlowTimeout();
}
}
- ///
- /// Checks support for authentication type in lower case.
- /// Interactive authentication added.
- ///
- public override bool IsSupported(SqlAuthenticationMethod authentication)
+ private Task DefaultDeviceFlowCallback(DeviceCodeResult result)
{
- return authentication == SqlAuthenticationMethod.ActiveDirectoryIntegrated
- || authentication == SqlAuthenticationMethod.ActiveDirectoryPassword
- || authentication == SqlAuthenticationMethod.ActiveDirectoryInteractive
- || authentication == SqlAuthenticationMethod.ActiveDirectoryServicePrincipal;
+ // 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);
}
- public override void BeforeLoad(SqlAuthenticationMethod authentication)
+ private class CustomWebUi : ICustomWebUi
{
- _logger.LogInfo(_type, "BeforeLoad", $"being loaded into SqlAuthProviders for {authentication}.");
- }
+ private readonly Func> _acquireAuthorizationCodeAsyncCallback;
- public override void BeforeUnload(SqlAuthenticationMethod authentication)
- {
- _logger.LogInfo(_type, "BeforeUnload", $"being unloaded from SqlAuthProviders for {authentication}.");
+ internal CustomWebUi(Func> acquireAuthorizationCodeAsyncCallback) => _acquireAuthorizationCodeAsyncCallback = acquireAuthorizationCodeAsyncCallback;
+
+ public Task AcquireAuthorizationCodeAsync(Uri authorizationUri, Uri redirectUri, CancellationToken cancellationToken)
+ => _acquireAuthorizationCodeAsyncCallback.Invoke(authorizationUri, redirectUri, cancellationToken);
}
}
}
diff --git a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/AADAccessTokenTest.cs b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/AADAuthenticationTests.cs
similarity index 72%
rename from src/Microsoft.Data.SqlClient/tests/FunctionalTests/AADAccessTokenTest.cs
rename to src/Microsoft.Data.SqlClient/tests/FunctionalTests/AADAuthenticationTests.cs
index ffb21e767b..0e695cdd4e 100644
--- a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/AADAccessTokenTest.cs
+++ b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/AADAuthenticationTests.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 AADAuthenticationTests
{
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..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 @@
-
+
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.