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

API | AccessTokenCallback support #1260

Merged
merged 41 commits into from Jun 27, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
b974ffb
POC for TokenCredential support
christothes Sep 10, 2021
8f76542
POC 2 - callback abstraction
christothes Oct 18, 2021
7ffc10c
merge
christothes Oct 18, 2021
9ed36d7
POC 3
christothes Oct 22, 2021
8321090
fix
christothes Oct 25, 2021
2aee2b2
Merge remote-tracking branch 'upstream/main' into chriss/ADCreds
christothes Oct 27, 2021
ea47be2
Merge remote-tracking branch 'upstream/main' into chriss/ADCreds
christothes Nov 8, 2021
10f5a95
merge
christothes Nov 8, 2021
d281983
cleanups
christothes Nov 12, 2021
fcfb66e
formatting
christothes Nov 12, 2021
4c718b5
netfx consistency
christothes Nov 12, 2021
4ccc412
fix
christothes Nov 12, 2021
68bf511
cleanup
christothes Nov 15, 2021
0671d58
merge
christothes Mar 8, 2023
d9570b3
nuget
christothes Mar 8, 2023
aa517b6
source ref
christothes Mar 8, 2023
c5def10
revert nuget.config change
christothes Mar 10, 2023
727406f
Update src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlCli…
christothes Mar 10, 2023
5c1b5ef
cast timeout to int
christothes Mar 13, 2023
d82f3bb
PR feedback
christothes Apr 14, 2023
9a9cd87
tests
christothes Apr 18, 2023
677f729
fix resources
christothes Apr 19, 2023
afc5fff
fix error messages for fx
christothes Apr 19, 2023
2673bd4
fixes
christothes Apr 19, 2023
b08a251
rename AzureADTokenRequestContext
christothes Apr 20, 2023
be1263e
use SqlAuthenticationParameters in callback
christothes May 2, 2023
52a84f9
docs and simple sample
christothes May 2, 2023
9be9391
tests for password and userId with callback
christothes May 3, 2023
a2233f5
add to SqlClient.cs API listing
christothes May 3, 2023
c1df1f4
Allow credential with callback and pass to the callback, if available
David-Engel May 12, 2023
a4aca8f
Merge pull request #1 from David-Engel/ADCreds
christothes Jun 5, 2023
fc57e98
Merge remote-tracking branch 'upstream/main' into chriss/ADCreds
DavoudEshtehari Jun 5, 2023
2ab55b8
fb
christothes Jun 6, 2023
ada78c2
Apply suggestions from code review
christothes Jun 6, 2023
1e531dc
Merge branch 'chriss/ADCreds' of https://github.com/christothes/SqlCl…
christothes Jun 6, 2023
caa6c3e
fb
christothes Jun 6, 2023
36a59e8
Apply suggestions from code review
christothes Jun 6, 2023
cd922db
Merge branch 'chriss/ADCreds' of https://github.com/christothes/SqlCl…
christothes Jun 6, 2023
16693f6
fb
christothes Jun 6, 2023
78b9e4a
fb
christothes Jun 13, 2023
67bfd01
Add tests
DavoudEshtehari Jun 17, 2023
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
2 changes: 2 additions & 0 deletions .gitignore
Expand Up @@ -361,3 +361,5 @@ MigrationBackup/

# Config Json file
**/config.json

.idea/
christothes marked this conversation as resolved.
Show resolved Hide resolved
15 changes: 15 additions & 0 deletions src/Microsoft.Data.SqlClient.sln
Expand Up @@ -200,6 +200,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Data.SqlClient.Do
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Data.SqlClient.PerformanceTests", "Microsoft.Data.SqlClient\tests\PerformanceTests\Microsoft.Data.SqlClient.PerformanceTests.csproj", "{599A336B-2A5F-473D-8442-1223ED37C93E}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Data.SqlClient.AzureAadAuthProvider", "Microsoft.Data.SqlClient\add-ons\AzureActiveDirectoryAuthenticationProvider\Microsoft.Data.SqlClient.AzureAadAuthProvider.csproj", "{48800BF7-13D9-48C9-A2CC-6F8017DF21B6}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -472,6 +474,18 @@ Global
{599A336B-2A5F-473D-8442-1223ED37C93E}.Release|x64.Build.0 = Release|x64
{599A336B-2A5F-473D-8442-1223ED37C93E}.Release|x86.ActiveCfg = Release|x86
{599A336B-2A5F-473D-8442-1223ED37C93E}.Release|x86.Build.0 = Release|x86
{48800BF7-13D9-48C9-A2CC-6F8017DF21B6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{48800BF7-13D9-48C9-A2CC-6F8017DF21B6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{48800BF7-13D9-48C9-A2CC-6F8017DF21B6}.Debug|x64.ActiveCfg = Debug|x64
{48800BF7-13D9-48C9-A2CC-6F8017DF21B6}.Debug|x64.Build.0 = Debug|x64
{48800BF7-13D9-48C9-A2CC-6F8017DF21B6}.Debug|x86.ActiveCfg = Debug|x86
{48800BF7-13D9-48C9-A2CC-6F8017DF21B6}.Debug|x86.Build.0 = Debug|x86
{48800BF7-13D9-48C9-A2CC-6F8017DF21B6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{48800BF7-13D9-48C9-A2CC-6F8017DF21B6}.Release|Any CPU.Build.0 = Release|Any CPU
{48800BF7-13D9-48C9-A2CC-6F8017DF21B6}.Release|x64.ActiveCfg = Release|x64
{48800BF7-13D9-48C9-A2CC-6F8017DF21B6}.Release|x64.Build.0 = Release|x64
{48800BF7-13D9-48C9-A2CC-6F8017DF21B6}.Release|x86.ActiveCfg = Release|x86
{48800BF7-13D9-48C9-A2CC-6F8017DF21B6}.Release|x86.Build.0 = Release|x86
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down Expand Up @@ -507,6 +521,7 @@ Global
{B499E477-C9B1-4087-A5CF-5C762D90E433} = {0CC4817A-12F3-4357-912C-09315FAAD008}
{B93A3149-67E8-491E-A1E5-19D65F9D9E98} = {0CC4817A-12F3-4357-912C-09315FAAD008}
{599A336B-2A5F-473D-8442-1223ED37C93E} = {0CC4817A-12F3-4357-912C-09315FAAD008}
{48800BF7-13D9-48C9-A2CC-6F8017DF21B6} = {C9726AED-D6A3-4AAC-BA04-92DD1F079594}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {01D48116-37A2-4D33-B9EC-94793C702431}
Expand Down
@@ -0,0 +1,89 @@
// 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.

using System;
using System.Threading;
using System.Threading.Tasks;
using Azure.Core;

namespace Microsoft.Data.SqlClient
{
/// <summary>
///
/// </summary>
public sealed class AzureActiveDirectoryAuthenticationProvider : SqlAuthenticationProvider
{
private static readonly string s_defaultScopeSuffix = "/.default";
private readonly string _type = typeof(AzureActiveDirectoryAuthenticationProvider).Name;
private readonly SqlClientLogger _logger = new();
// private readonly string _applicationClientId = ActiveDirectoryAuthentication.AdoClientId;
christothes marked this conversation as resolved.
Show resolved Hide resolved
private readonly TokenCredential _credential;

/// <summary>
///
/// </summary>
public AzureActiveDirectoryAuthenticationProvider(TokenCredential credential)
{
_credential = credential;
}

/// <summary>
///
/// </summary>
/// <param name="authentication"></param>
/// <returns></returns>
public override bool IsSupported(SqlAuthenticationMethod authentication) => authentication switch
{
SqlAuthenticationMethod.ActiveDirectoryTokenCredential => true,
SqlAuthenticationMethod.ActiveDirectoryIntegrated => true,
SqlAuthenticationMethod.ActiveDirectoryPassword => true,
SqlAuthenticationMethod.ActiveDirectoryInteractive => true,
SqlAuthenticationMethod.ActiveDirectoryServicePrincipal => true,
SqlAuthenticationMethod.ActiveDirectoryDeviceCodeFlow => true,
SqlAuthenticationMethod.ActiveDirectoryManagedIdentity => true,
SqlAuthenticationMethod.ActiveDirectoryMSI => true,
SqlAuthenticationMethod.ActiveDirectoryDefault => true,
_ => false
};

/// <summary>
///
/// </summary>
/// <param name="authentication"></param>
public override void BeforeLoad(SqlAuthenticationMethod authentication)
{
_logger.LogInfo(_type, "BeforeLoad", $"being loaded into SqlAuthProviders for {authentication}.");
}

/// <summary>
///
/// </summary>
/// <param name="authentication"></param>
public override void BeforeUnload(SqlAuthenticationMethod authentication)
{
_logger.LogInfo(_type, "BeforeUnload", $"being unloaded from SqlAuthProviders for {authentication}.");
}

/// <summary>
///
/// </summary>
/// <param name="parameters"></param>
/// <returns></returns>
/// <exception cref="Exception"></exception>
public override async Task<SqlAuthenticationToken> AcquireTokenAsync(SqlAuthenticationParameters parameters)
{
CancellationTokenSource cts = new();

// Use Connection timeout value to cancel token acquire request after certain period of time.
cts.CancelAfter(parameters.ConnectionTimeout * 1000); // Convert to milliseconds

string scope = parameters.Resource.EndsWith(s_defaultScopeSuffix) ? parameters.Resource : parameters.Resource + s_defaultScopeSuffix;
string[] scopes = { scope };
TokenRequestContext tokenRequestContext = new(scopes);
string clientId = string.IsNullOrWhiteSpace(parameters.UserId) ? null : parameters.UserId;
AccessToken accessToken = await _credential.GetTokenAsync(tokenRequestContext, cts.Token);
return new SqlAuthenticationToken(accessToken.Token, accessToken.ExpiresOn);
}
}
}
@@ -0,0 +1,28 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<RootNamespace>Microsoft.Data.SqlClient.AzureAadAuthenticationProvider</RootNamespace>
<AssemblyName>Microsoft.Data.SqlClient.AzureAadAuthenticationProvider</AssemblyName>
<AddOnName>AzureAadAuthenticationProvider</AddOnName>
<ProjectGuid>{48800BF7-13D9-48C9-A2CC-6F8017DF21B6}</ProjectGuid>
<TargetGroup Condition="$(TargetFramework.StartsWith('netcoreapp')) OR $(TargetFramework.StartsWith('netstandard'))">netcoreapp</TargetGroup>
<TargetGroup Condition="$(TargetFramework.StartsWith('net4'))">netfx</TargetGroup>
<Configurations>Debug;Release;</Configurations>
<Platforms>AnyCPU;x86;x64</Platforms>
<IntermediateOutputPath>$(ObjFolder)$(Configuration).$(Platform)\$(AddOnName)</IntermediateOutputPath>
<OutputPath>$(BinFolder)$(Configuration).$(Platform)\$(AddOnName)</OutputPath>
<DocumentationFile>$(BinFolder)$(Configuration).$(Platform)\$(AssemblyName).xml</DocumentationFile>
<!--BuildProjectReferences should be kept false to avoid test issues-->
<BuildProjectReferences>false</BuildProjectReferences>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance>
</PropertyGroup>
<ItemGroup>
<ProjectReference Condition="'$(TargetGroup)'=='netcoreapp' AND !$(ReferenceType.Contains('Package'))" Include="$(NetCoreSource)src\Microsoft.Data.SqlClient.csproj" />
<ProjectReference Condition="'$(TargetGroup)'=='netfx' AND !$(ReferenceType.Contains('Package'))" Include="$(NetFxSource)src\Microsoft.Data.SqlClient.csproj" />
<PackageReference Condition="$(ReferenceType)=='Package'" Include="Microsoft.Data.SqlClient" Version="$(TestMicrosoftDataSqlClientVersion)" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="$(MicrosoftSourceLinkGitHubVersion)" PrivateAssets="All" />
<PackageReference Include="Azure.Core" Version="$(AzureCoreVersion)" />
</ItemGroup>
</Project>
Expand Up @@ -109,6 +109,7 @@ internal static string ConvertToString(object value)
internal const string ActiveDirectoryManagedIdentityString = "Active Directory Managed Identity";
internal const string ActiveDirectoryMSIString = "Active Directory MSI";
internal const string ActiveDirectoryDefaultString = "Active Directory Default";
internal const string ActiveDirectoryTokenCredentialString = "Active Directory TokenCredential";

#if DEBUG
private static string[] s_supportedAuthenticationModes =
Expand All @@ -122,7 +123,8 @@ internal static string ConvertToString(object value)
"ActiveDirectoryDeviceCodeFlow",
"ActiveDirectoryManagedIdentity",
"ActiveDirectoryMSI",
"ActiveDirectoryDefault"
"ActiveDirectoryDefault",
"ActiveDirectoryTokenCredential"
};

private static bool IsValidAuthenticationMethodEnum()
Expand Down Expand Up @@ -204,6 +206,12 @@ internal static bool TryConvertToAuthenticationType(string value, out SqlAuthent
result = SqlAuthenticationMethod.ActiveDirectoryDefault;
isSuccess = true;
}
else if (StringComparer.InvariantCultureIgnoreCase.Equals(value, ActiveDirectoryTokenCredentialString)
|| StringComparer.InvariantCultureIgnoreCase.Equals(value, Convert.ToString(SqlAuthenticationMethod.ActiveDirectoryTokenCredential, CultureInfo.InvariantCulture)))
{
result = SqlAuthenticationMethod.ActiveDirectoryTokenCredential;
isSuccess = true;
}
else
{
result = DbConnectionStringDefaults.Authentication;
Expand Down Expand Up @@ -648,7 +656,7 @@ internal static ApplicationIntent ConvertToApplicationIntent(string keyword, obj

internal static bool IsValidAuthenticationTypeValue(SqlAuthenticationMethod value)
{
Debug.Assert(Enum.GetNames(typeof(SqlAuthenticationMethod)).Length == 10, "SqlAuthenticationMethod enum has changed, update needed");
Debug.Assert(Enum.GetNames(typeof(SqlAuthenticationMethod)).Length == 11, "SqlAuthenticationMethod enum has changed, update needed");
return value == SqlAuthenticationMethod.SqlPassword
|| value == SqlAuthenticationMethod.ActiveDirectoryPassword
|| value == SqlAuthenticationMethod.ActiveDirectoryIntegrated
Expand All @@ -658,6 +666,7 @@ internal static bool IsValidAuthenticationTypeValue(SqlAuthenticationMethod valu
|| value == SqlAuthenticationMethod.ActiveDirectoryManagedIdentity
|| value == SqlAuthenticationMethod.ActiveDirectoryMSI
|| value == SqlAuthenticationMethod.ActiveDirectoryDefault
|| value == SqlAuthenticationMethod.ActiveDirectoryTokenCredential
|| value == SqlAuthenticationMethod.NotSpecified;
}

Expand Down
Expand Up @@ -1315,6 +1315,7 @@ private void Login(ServerInfo server, TimeoutTimer timeout, string newPassword,
|| ConnectionOptions.Authentication == SqlAuthenticationMethod.ActiveDirectoryManagedIdentity
|| ConnectionOptions.Authentication == SqlAuthenticationMethod.ActiveDirectoryMSI
|| ConnectionOptions.Authentication == SqlAuthenticationMethod.ActiveDirectoryDefault
|| ConnectionOptions.Authentication == SqlAuthenticationMethod.ActiveDirectoryTokenCredential
// Since AD Integrated may be acting like Windows integrated, additionally check _fedAuthRequired
|| (ConnectionOptions.Authentication == SqlAuthenticationMethod.ActiveDirectoryIntegrated && _fedAuthRequired))
{
Expand Down Expand Up @@ -2124,6 +2125,7 @@ internal void OnFedAuthInfo(SqlFedAuthInfo fedAuthInfo)
|| ConnectionOptions.Authentication == SqlAuthenticationMethod.ActiveDirectoryManagedIdentity
|| ConnectionOptions.Authentication == SqlAuthenticationMethod.ActiveDirectoryMSI
|| ConnectionOptions.Authentication == SqlAuthenticationMethod.ActiveDirectoryDefault
|| ConnectionOptions.Authentication == SqlAuthenticationMethod.ActiveDirectoryTokenCredential
|| (ConnectionOptions.Authentication == SqlAuthenticationMethod.ActiveDirectoryIntegrated && _fedAuthRequired),
"Credentials aren't provided for calling MSAL");
Debug.Assert(fedAuthInfo != null, "info should not be null.");
Expand Down Expand Up @@ -2369,6 +2371,7 @@ internal SqlFedAuthToken GetFedAuthToken(SqlFedAuthInfo fedAuthInfo)
case SqlAuthenticationMethod.ActiveDirectoryManagedIdentity:
case SqlAuthenticationMethod.ActiveDirectoryMSI:
case SqlAuthenticationMethod.ActiveDirectoryDefault:
case SqlAuthenticationMethod.ActiveDirectoryTokenCredential:
if (_activeDirectoryAuthTimeoutRetryHelper.State == ActiveDirectoryAuthenticationTimeoutRetryState.Retrying)
{
_fedAuthToken = _activeDirectoryAuthTimeoutRetryHelper.CachedToken;
Expand Down
Expand Up @@ -253,6 +253,7 @@ public enum FedAuthLibrary : byte
public const byte MSALWORKFLOW_ACTIVEDIRECTORYDEVICECODEFLOW = 0x03; // Using the Interactive byte as that is the closest we have
public const byte MSALWORKFLOW_ACTIVEDIRECTORYMANAGEDIDENTITY = 0x03; // Using the Interactive byte as that's supported for Identity based authentication
public const byte MSALWORKFLOW_ACTIVEDIRECTORYDEFAULT = 0x03; // Using the Interactive byte as that is the closest we have to non-password based authentication modes
public const byte MSALWORKFLOW_ACTIVEDIRECTORYTOKENCREDENTIAL = 0x03; // Using the Interactive byte as that is the closest we have to non-password based authentication modes

public enum ActiveDirectoryWorkflow : byte
{
Expand All @@ -263,6 +264,7 @@ public enum ActiveDirectoryWorkflow : byte
DeviceCodeFlow = MSALWORKFLOW_ACTIVEDIRECTORYDEVICECODEFLOW,
ManagedIdentity = MSALWORKFLOW_ACTIVEDIRECTORYMANAGEDIDENTITY,
Default = MSALWORKFLOW_ACTIVEDIRECTORYDEFAULT,
TokenCredential = MSALWORKFLOW_ACTIVEDIRECTORYTOKENCREDENTIAL,
}

// The string used for username in the error message when Authentication = Active Directory Integrated with FedAuth is used, if authentication fails.
Expand Down Expand Up @@ -1160,7 +1162,11 @@ public enum SqlAuthenticationMethod
ActiveDirectoryMSI,

/// <include file='../../../../../../../doc/snippets/Microsoft.Data.SqlClient/SqlAuthenticationMethod.xml' path='docs/members[@name="SqlAuthenticationMethod"]/ActiveDirectoryDefault/*'/>
ActiveDirectoryDefault
ActiveDirectoryDefault,
/// <summary>
///
/// </summary>
ActiveDirectoryTokenCredential
}
// 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.
Expand Down
Expand Up @@ -7818,6 +7818,9 @@ internal int WriteSessionRecoveryFeatureRequest(SessionData reconnectData, bool
case SqlAuthenticationMethod.ActiveDirectoryDefault:
workflow = TdsEnums.MSALWORKFLOW_ACTIVEDIRECTORYDEFAULT;
break;
case SqlAuthenticationMethod.ActiveDirectoryTokenCredential:
workflow = TdsEnums.MSALWORKFLOW_ACTIVEDIRECTORYTOKENCREDENTIAL;
break;
default:
Debug.Assert(false, "Unrecognized Authentication type for fedauth MSAL request");
break;
Expand Down
Expand Up @@ -540,7 +540,8 @@ internal static ApplicationIntent ConvertToApplicationIntent(string keyword, obj
"ActiveDirectoryDeviceCodeFlow",
"ActiveDirectoryManagedIdentity",
"ActiveDirectoryMSI",
"ActiveDirectoryDefault"
"ActiveDirectoryDefault",
"ActiveDirectoryTokenCredential"
};

private static bool IsValidAuthenticationMethodEnum()
Expand Down Expand Up @@ -705,7 +706,7 @@ internal static string ColumnEncryptionSettingToString(SqlConnectionColumnEncryp

internal static bool IsValidAuthenticationTypeValue(SqlAuthenticationMethod value)
{
Debug.Assert(Enum.GetNames(typeof(SqlAuthenticationMethod)).Length == 10, "SqlAuthenticationMethod enum has changed, update needed");
Debug.Assert(Enum.GetNames(typeof(SqlAuthenticationMethod)).Length == 11, "SqlAuthenticationMethod enum has changed, update needed");
return value == SqlAuthenticationMethod.SqlPassword
|| value == SqlAuthenticationMethod.ActiveDirectoryPassword
|| value == SqlAuthenticationMethod.ActiveDirectoryIntegrated
Expand All @@ -715,6 +716,7 @@ internal static bool IsValidAuthenticationTypeValue(SqlAuthenticationMethod valu
|| value == SqlAuthenticationMethod.ActiveDirectoryManagedIdentity
|| value == SqlAuthenticationMethod.ActiveDirectoryMSI
|| value == SqlAuthenticationMethod.ActiveDirectoryDefault
|| value == SqlAuthenticationMethod.ActiveDirectoryTokenCredential
#if ADONET_CERT_AUTH
|| value == SqlAuthenticationMethod.SqlCertificate
#endif
Expand Down
Expand Up @@ -2572,6 +2572,7 @@ internal void OnFedAuthInfo(SqlFedAuthInfo fedAuthInfo)
|| ConnectionOptions.Authentication == SqlAuthenticationMethod.ActiveDirectoryManagedIdentity
|| ConnectionOptions.Authentication == SqlAuthenticationMethod.ActiveDirectoryMSI
|| ConnectionOptions.Authentication == SqlAuthenticationMethod.ActiveDirectoryDefault
|| ConnectionOptions.Authentication == SqlAuthenticationMethod.ActiveDirectoryTokenCredential
|| ConnectionOptions.Authentication == SqlAuthenticationMethod.ActiveDirectoryDeviceCodeFlow
|| (ConnectionOptions.Authentication == SqlAuthenticationMethod.ActiveDirectoryIntegrated && _fedAuthRequired),
"Credentials aren't provided for calling MSAL");
Expand Down