Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce support for AAD Device Code Flow authentication #597

Merged
merged 17 commits into from Jul 27, 2020
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
30 changes: 30 additions & 0 deletions doc/samples/AADAuthenticationCustomDeviceFlowCallback.cs
@@ -0,0 +1,30 @@
//<Snippet1>
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=<myserver>.database.windows.net;Authentication=Active Directory Device Code Flow;Database=<db>;"))
{
sqlConnection.Open();
Console.WriteLine("Connected successfully!");
}
}

private Task CustomDeviceFlowCallback(DeviceCodeResult result)
cheenamalhotra marked this conversation as resolved.
Show resolved Hide resolved
{
// Provide custon logic to process result information and read device code.
cheenamalhotra marked this conversation as resolved.
Show resolved Hide resolved
Console.WriteLine(result.Message);
return Task.FromResult(0);
}
}
}
//</Snippet1>
@@ -0,0 +1,69 @@
<docs>
<members name="ActiveDirectoryAuthenticationProvider">
<ActiveDirectoryAuthenticationProvider>
<summary>
This class implements <see cref="T:Microsoft.Data.SqlClient.SqlAuthenticationProvider" /> and is used for active directory federated authentication mechanisms.
</summary>
</ActiveDirectoryAuthenticationProvider>
<ctor>
<summary>
Initializes the <see cref="T:Microsoft.Data.SqlClient.ActiveDirectoryAuthenticationProvider" /> class.
</summary>
</ctor>
<ctor2>
<param name="deviceCodeFlowCallbackMethod">The callback method to be used by driver when performing 'Active Directory Device Code Flow' authentication.</param>
cheenamalhotra marked this conversation as resolved.
Show resolved Hide resolved
<summary>
Initializes the <see cref="T:Microsoft.Data.SqlClient.ActiveDirectoryAuthenticationProvider" /> class with provided device code flow callback method.
cheenamalhotra marked this conversation as resolved.
Show resolved Hide resolved
</summary>
</ctor2>
<AcquireTokenAsync>
<param name="parameters">The Active Directory authentication parameters passed by the driver to authentication providers.</param>
cheenamalhotra marked this conversation as resolved.
Show resolved Hide resolved
<summary>Acquires a security token from the authority.</summary>
<returns>Represents an asynchronous operation that returns the AD authentication token.</returns>
cheenamalhotra marked this conversation as resolved.
Show resolved Hide resolved
</AcquireTokenAsync>
<SetDeviceCodeFlowCallback>
<param name="deviceCodeFlowCallbackMethod">The callback method to be used by driver when performing 'Active Directory Device Code Flow' authentication.</param>
cheenamalhotra marked this conversation as resolved.
Show resolved Hide resolved
<summary>Set's callback method that overrides driver's default implementation to process result when performing 'Active Directory Device Code Flow' authentication.</summary>
</SetDeviceCodeFlowCallback>
<BeforeLoad>
<param name="authentication">The authentication method.</param>
<summary>This method is called immediately before the provider is added to SQL drivers registry. </summary>
cheenamalhotra marked this conversation as resolved.
Show resolved Hide resolved
<remarks>Avoid performing long-waiting tasks in this method, since it can block other threads from accessing the provider registry.</remarks>
</BeforeLoad>
<BeforeUnload>
<param name="authentication">The authentication method.</param>
<summary>This method is called immediately before the provider is removed from the SQL drivers registry. </summary>
cheenamalhotra marked this conversation as resolved.
Show resolved Hide resolved
<remarks>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.</remarks>
cheenamalhotra marked this conversation as resolved.
Show resolved Hide resolved
</BeforeUnload>
<IsSupported>
<param name="authentication">The authentication method.</param>
<summary>Indicates whether the specified authentication method is supported.</summary>
<returns>
<see langword="true" /> if the specified authentication method is supported; otherwise, <see langword="false" />.
</returns>
<remarks>
<format type="text/markdown">
<![CDATA[

## Remarks
The supported authentication methods are:

|Authentication Method|
|----------------|
|<xref:Microsoft.Data.SqlClient.SqlAuthenticationMethod.ActiveDirectoryPassword%2A>|
|<xref:Microsoft.Data.SqlClient.SqlAuthenticationMethod.ActiveDirectoryIntegrated%2A>|
|<xref:Microsoft.Data.SqlClient.SqlAuthenticationMethod.ActiveDirectoryInteractive%2A>|
|<xref:Microsoft.Data.SqlClient.SqlAuthenticationMethod.ActiveDirectoryServicePrincipal%2A>|
|<xref:Microsoft.Data.SqlClient.SqlAuthenticationMethod.ActiveDirectoryDeviceCodeFlow%2A>|

## Examples
The following example demonstrates providing a custom device flow callback to SqlClient driver for Device Code Flow authentication method:
cheenamalhotra marked this conversation as resolved.
Show resolved Hide resolved

[!code-csharp[ActiveDirectory_DeviceCodeFlowCallback Example#1](~/../sqlclient/doc/samples/AADAuthenticationCustomDeviceFlowCallback.cs#1)]

]]>
</format>
</remarks>
</IsSupported>
</members>
</docs>
1 change: 1 addition & 0 deletions src/Microsoft.Data.SqlClient.sln
Expand Up @@ -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
Expand Down
Expand Up @@ -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;
Expand All @@ -12,21 +11,24 @@

namespace Microsoft.Data.SqlClient
{

/// <summary>
/// Default auth provider for AD Integrated.
/// </summary>
internal class ActiveDirectoryAuthenticationProvider : SqlAuthenticationProvider
/// <include file='../../../../../../doc/snippets/Microsoft.Data.SqlClient/ActiveDirectoryAuthenticationProvider.xml' path='docs/members[@name="ActiveDirectoryAuthenticationProvider"]/ActiveDirectoryAuthenticationProvider/*'/>
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<DeviceCodeResult, Task> _deviceCodeFlowCallback;

/// <include file='../../../../../../doc/snippets/Microsoft.Data.SqlClient/ActiveDirectoryAuthenticationProvider.xml' path='docs/members[@name="ActiveDirectoryAuthenticationProvider"]/ctor/*'/>
public ActiveDirectoryAuthenticationProvider() => new ActiveDirectoryAuthenticationProvider(DefaultDeviceFlowCallback);

/// <include file='../../../../../../doc/snippets/Microsoft.Data.SqlClient/ActiveDirectoryAuthenticationProvider.xml' path='docs/members[@name="ActiveDirectoryAuthenticationProvider"]/ctor2/*'/>
public ActiveDirectoryAuthenticationProvider(Func<DeviceCodeResult, Task> deviceCodeFlowCallbackMethod)
cheenamalhotra marked this conversation as resolved.
Show resolved Hide resolved
{
this._deviceCodeFlowCallback = deviceCodeFlowCallbackMethod;
}

/// <summary>
/// Get token.
/// </summary>
/// <param name="parameters"></param>
/// <returns>Authentication token</returns>
/// <include file='../../../../../../doc/snippets/Microsoft.Data.SqlClient/ActiveDirectoryAuthenticationProvider.xml' path='docs/members[@name="ActiveDirectoryAuthenticationProvider"]/AcquireTokenAsync/*'/>
public override Task<SqlAuthenticationToken> AcquireTokenAsync(SqlAuthenticationParameters parameters) => Task.Run(async () =>
{
AuthenticationResult result;
Expand Down Expand Up @@ -178,7 +180,7 @@ internal class ActiveDirectoryAuthenticationProvider : SqlAuthenticationProvider
else
{
AuthenticationResult result = await app.AcquireTokenWithDeviceCode(scopes,
deviceCodeResult => DeviceFlowCallback(deviceCodeResult)).ExecuteAsync();
deviceCodeResult => _deviceCodeFlowCallback(deviceCodeResult)).ExecuteAsync();
return result;
}
}
Expand All @@ -190,7 +192,7 @@ internal class ActiveDirectoryAuthenticationProvider : SqlAuthenticationProvider
}
}

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.
Expand All @@ -206,10 +208,13 @@ private Task DeviceFlowCallback(DeviceCodeResult result)
return Task.FromResult(0);
}

/// <summary>
/// Checks support for authentication type in lower case.
/// Interactive authentication added.
/// </summary>
/// <include file='../../../../../../doc/snippets/Microsoft.Data.SqlClient/ActiveDirectoryAuthenticationProvider.xml' path='docs/members[@name="ActiveDirectoryAuthenticationProvider"]/SetDeviceCodeFlowCallback/*'/>
public void SetDeviceCodeFlowCallback(Func<DeviceCodeResult, Task> deviceCodeFlowCallbackMethod)
{
_deviceCodeFlowCallback = deviceCodeFlowCallbackMethod;
}

/// <include file='../../../../../../doc/snippets/Microsoft.Data.SqlClient/ActiveDirectoryAuthenticationProvider.xml' path='docs/members[@name="ActiveDirectoryAuthenticationProvider"]/IsSupported/*'/>
public override bool IsSupported(SqlAuthenticationMethod authentication)
{
return authentication == SqlAuthenticationMethod.ActiveDirectoryIntegrated
Expand All @@ -219,11 +224,13 @@ public override bool IsSupported(SqlAuthenticationMethod authentication)
|| authentication == SqlAuthenticationMethod.ActiveDirectoryDeviceCodeFlow;
}

/// <include file='../../../../../../doc/snippets/Microsoft.Data.SqlClient/ActiveDirectoryAuthenticationProvider.xml' path='docs/members[@name="ActiveDirectoryAuthenticationProvider"]/BeforeLoad/*'/>
public override void BeforeLoad(SqlAuthenticationMethod authentication)
{
_logger.LogInfo(_type, "BeforeLoad", $"being loaded into SqlAuthProviders for {authentication}.");
}

/// <include file='../../../../../../doc/snippets/Microsoft.Data.SqlClient/ActiveDirectoryAuthenticationProvider.xml' path='docs/members[@name="ActiveDirectoryAuthenticationProvider"]/BeforeUnload/*'/>
public override void BeforeUnload(SqlAuthenticationMethod authentication)
{
_logger.LogInfo(_type, "BeforeUnload", $"being unloaded from SqlAuthProviders for {authentication}.");
Expand Down
Expand Up @@ -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
cheenamalhotra marked this conversation as resolved.
Show resolved Hide resolved
{
private SqlConnectionStringBuilder _builder;
private SqlCredential _credential = null;
Expand Down Expand Up @@ -38,12 +40,27 @@ public void InvalidCombinationOfAccessToken(string description, object[] Params)
InvalidCombinationCheck(_credential);
}


private void InvalidCombinationCheck(SqlCredential credential)
{
using (SqlConnection connection = new SqlConnection(_builder.ConnectionString, credential))
{
Assert.Throws<InvalidOperationException>(() => 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);
}
}
}
Expand Up @@ -31,7 +31,7 @@
<Compile Include="SqlConnectionBasicTests.cs" />
<Compile Include="SqlCommandTest.cs" />
<Compile Include="SqlConnectionTest.cs" />
<Compile Include="AADAccessTokenTest.cs" />
<Compile Include="AADTests.cs" />
<Compile Include="CloneTests.cs" />
<Compile Include="BaseProviderAsyncTest\BaseProviderAsyncTest.cs" />
<Compile Include="BaseProviderAsyncTest\MockCommand.cs" />
Expand Down