Skip to content

Commit fbfc339

Browse files
committed
Expose SSPI context provider on SqlConnection
1 parent c8efb23 commit fbfc339

File tree

13 files changed

+113
-27
lines changed

13 files changed

+113
-27
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<?xml version="1.0"?>
2+
<docs>
3+
<members name="SSPIContextProvider">
4+
<SSPIContextProvider>
5+
<summary>Provides the ability to customize SSPI context generation.</summary>
6+
</SSPIContextProvider>
7+
<GenerateSspiClientContext>
8+
<summary>Generates a SSPI outgoing blob given the incoming blob.</summary>
9+
<param name="incomingBlob">Incoming blob</param>
10+
<param name="outgoingBlobWriter">Outgoing blob</param>
11+
<param name="authParams">Gets the authentication parameters associated with this connection.</param>
12+
</GenerateSspiClientContext>
13+
</members>
14+
</docs>

doc/snippets/Microsoft.Data.SqlClient/SqlConnection.xml

+5
Original file line numberDiff line numberDiff line change
@@ -3284,6 +3284,11 @@
32843284
Returns 0 if the connection is inactive on the client side.
32853285
</remarks>
32863286
</ServerProcessId>
3287+
<SSPIContextProviderFactory>
3288+
<summary>
3289+
Gets or sets the factory used to create a <see cref="SSPIContextProvider"/> instance for customizing the SSPI context. If not set, the default for the platform will be used
3290+
</summary>
3291+
</SSPIContextProviderFactory>
32873292
<State>
32883293
<summary>
32893294
Indicates the state of the <see cref="T:Microsoft.Data.SqlClient.SqlConnection" /> during the most recent network operation performed on the connection.

src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.cs

+8
Original file line numberDiff line numberDiff line change
@@ -907,6 +907,8 @@ public void RegisterColumnEncryptionKeyStoreProvidersOnConnection(System.Collect
907907
[System.ComponentModel.BrowsableAttribute(false)]
908908
[System.ComponentModel.DesignerSerializationVisibilityAttribute(0)]
909909
public Microsoft.Data.SqlClient.SqlCredential Credential { get { throw null; } set { } }
910+
/// <include file='../../../../doc/snippets/Microsoft.Data.SqlClient/SqlConnection.xml' path='docs/members[@name="SqlConnection"]/SSPIContextProviderFactory/*' />
911+
public System.Func<SSPIContextProvider> SSPIContextProviderFactory { get { throw null; } set { } }
910912
/// <include file='../../../../doc/snippets/Microsoft.Data.SqlClient/SqlConnection.xml' path='docs/members[@name="SqlConnection"]/Database/*'/>
911913
[System.ComponentModel.DesignerSerializationVisibilityAttribute(0)]
912914
public override string Database { get { throw null; } }
@@ -1938,6 +1940,12 @@ public sealed class SqlConfigurableRetryFactory
19381940
/// <include file='../../../../doc/snippets/Microsoft.Data.SqlClient/SqlConfigurableRetryFactory.xml' path='docs/members[@name="SqlConfigurableRetryFactory"]/CreateNoneRetryProvider/*' />
19391941
public static SqlRetryLogicBaseProvider CreateNoneRetryProvider() { throw null; }
19401942
}
1943+
/// <include file='../../../../doc/snippets/Microsoft.Data.SqlClient/SSPIContextProvider.xml' path='docs/members[@name="SSPIContextProvider"]/SSPIContextProvider/*'/>
1944+
public abstract class SSPIContextProvider
1945+
{
1946+
/// <include file='../../../../doc/snippets/Microsoft.Data.SqlClient/SSPIContextProvider.xml' path='docs/members[@name="SSPIContextProvider"]/GenerateSspiClientContext/*'/>
1947+
protected abstract void GenerateSspiClientContext(System.ReadOnlySpan<byte> incomingBlob, System.Buffers.IBufferWriter<byte> outgoingBlobWriter, SqlAuthenticationParameters authParams);
1948+
}
19411949
}
19421950
namespace Microsoft.Data.SqlClient.Diagnostics
19431951
{

src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlConnection.cs

+21-9
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ private static readonly Dictionary<string, SqlColumnEncryptionKeyStoreProvider>
8989
private IReadOnlyDictionary<string, SqlColumnEncryptionKeyStoreProvider> _customColumnEncryptionKeyStoreProviders;
9090

9191
private Func<SqlAuthenticationParameters, CancellationToken, Task<SqlAuthenticationToken>> _accessTokenCallback;
92+
private Func<SSPIContextProvider> _sspiContextProviderFactory;
9293

9394
internal bool HasColumnEncryptionKeyStoreProvidersRegistered =>
9495
_customColumnEncryptionKeyStoreProviders is not null && _customColumnEncryptionKeyStoreProviders.Count > 0;
@@ -648,7 +649,7 @@ public override string ConnectionString
648649
CheckAndThrowOnInvalidCombinationOfConnectionOptionAndAccessTokenCallback(connectionOptions);
649650
}
650651
}
651-
ConnectionString_Set(new SqlConnectionPoolKey(value, _credential, _accessToken, _accessTokenCallback));
652+
ConnectionString_Set(new SqlConnectionPoolKey(value, _credential, _accessToken, _accessTokenCallback, _sspiContextProviderFactory));
652653
_connectionString = value; // Change _connectionString value only after value is validated
653654
CacheConnectionStringProperties();
654655
}
@@ -708,7 +709,7 @@ public string AccessToken
708709
}
709710

710711
// Need to call ConnectionString_Set to do proper pool group check
711-
ConnectionString_Set(new SqlConnectionPoolKey(_connectionString, credential: _credential, accessToken: value, accessTokenCallback: null));
712+
ConnectionString_Set(new SqlConnectionPoolKey(_connectionString, credential: _credential, accessToken: value, accessTokenCallback: null, sspiContextProviderFactory: _sspiContextProviderFactory));
712713
_accessToken = value;
713714
}
714715
}
@@ -731,11 +732,22 @@ public Func<SqlAuthenticationParameters, CancellationToken, Task<SqlAuthenticati
731732
CheckAndThrowOnInvalidCombinationOfConnectionOptionAndAccessTokenCallback((SqlConnectionString)ConnectionOptions);
732733
}
733734

734-
ConnectionString_Set(new SqlConnectionPoolKey(_connectionString, credential: _credential, accessToken: null, accessTokenCallback: value));
735+
ConnectionString_Set(new SqlConnectionPoolKey(_connectionString, credential: _credential, accessToken: null, accessTokenCallback: value, sspiContextProviderFactory: _sspiContextProviderFactory));
735736
_accessTokenCallback = value;
736737
}
737738
}
738739

740+
/// <include file='../../../../../../../doc/snippets/Microsoft.Data.SqlClient/SqlConnection.xml' path='docs/members[@name="SqlConnection"]/SSPIContextProviderFactory/*' />
741+
public Func<SSPIContextProvider> SSPIContextProviderFactory
742+
{
743+
get { return _sspiContextProviderFactory; }
744+
set
745+
{
746+
ConnectionString_Set(new SqlConnectionPoolKey(_connectionString, credential: _credential, accessToken: null, accessTokenCallback: _accessTokenCallback, sspiContextProviderFactory: value));
747+
_sspiContextProviderFactory = value;
748+
}
749+
}
750+
739751
/// <include file='../../../../../../../doc/snippets/Microsoft.Data.SqlClient/SqlConnection.xml' path='docs/members[@name="SqlConnection"]/Database/*' />
740752
[ResDescription(StringsHelper.ResourceNames.SqlConnection_Database)]
741753
[ResCategory(StringsHelper.ResourceNames.SqlConnection_DataSource)]
@@ -1030,7 +1042,7 @@ public SqlCredential Credential
10301042
_credential = value;
10311043

10321044
// Need to call ConnectionString_Set to do proper pool group check
1033-
ConnectionString_Set(new SqlConnectionPoolKey(_connectionString, _credential, accessToken: _accessToken, accessTokenCallback: _accessTokenCallback));
1045+
ConnectionString_Set(new SqlConnectionPoolKey(_connectionString, _credential, accessToken: _accessToken, accessTokenCallback: _accessTokenCallback, _sspiContextProviderFactory));
10341046
}
10351047
}
10361048

@@ -1078,7 +1090,7 @@ private void CheckAndThrowOnInvalidCombinationOfConnectionOptionAndAccessToken(S
10781090
throw ADP.InvalidMixedUsageOfCredentialAndAccessToken();
10791091
}
10801092

1081-
if(_accessTokenCallback != null)
1093+
if (_accessTokenCallback != null)
10821094
{
10831095
throw ADP.InvalidMixedUsageOfAccessTokenAndTokenCallback();
10841096
}
@@ -1100,7 +1112,7 @@ private void CheckAndThrowOnInvalidCombinationOfConnectionOptionAndAccessTokenCa
11001112
throw ADP.InvalidMixedUsageOfAccessTokenCallbackAndAuthentication();
11011113
}
11021114

1103-
if(_accessToken != null)
1115+
if (_accessToken != null)
11041116
{
11051117
throw ADP.InvalidMixedUsageOfAccessTokenAndTokenCallback();
11061118
}
@@ -2214,7 +2226,7 @@ public static void ChangePassword(string connectionString, string newPassword)
22142226
throw ADP.InvalidArgumentLength(nameof(newPassword), TdsEnums.MAXLEN_NEWPASSWORD);
22152227
}
22162228

2217-
SqlConnectionPoolKey key = new SqlConnectionPoolKey(connectionString, credential: null, accessToken: null, accessTokenCallback: null);
2229+
SqlConnectionPoolKey key = new SqlConnectionPoolKey(connectionString, credential: null, accessToken: null, accessTokenCallback: null, sspiContextProviderFactory: null);
22182230

22192231
SqlConnectionString connectionOptions = SqlConnectionFactory.FindSqlConnectionOptions(key);
22202232
if (connectionOptions.IntegratedSecurity)
@@ -2263,7 +2275,7 @@ public static void ChangePassword(string connectionString, SqlCredential credent
22632275
throw ADP.InvalidArgumentLength(nameof(newSecurePassword), TdsEnums.MAXLEN_NEWPASSWORD);
22642276
}
22652277

2266-
SqlConnectionPoolKey key = new SqlConnectionPoolKey(connectionString, credential, accessToken: null, accessTokenCallback: null);
2278+
SqlConnectionPoolKey key = new SqlConnectionPoolKey(connectionString, credential, accessToken: null, accessTokenCallback: null, sspiContextProviderFactory: null);
22672279

22682280
SqlConnectionString connectionOptions = SqlConnectionFactory.FindSqlConnectionOptions(key);
22692281

@@ -2302,7 +2314,7 @@ private static void ChangePassword(string connectionString, SqlConnectionString
23022314
if (con != null)
23032315
con.Dispose();
23042316
}
2305-
SqlConnectionPoolKey key = new SqlConnectionPoolKey(connectionString, credential, accessToken: null, accessTokenCallback: null);
2317+
SqlConnectionPoolKey key = new SqlConnectionPoolKey(connectionString, credential, accessToken: null, accessTokenCallback: null, sspiContextProviderFactory: null);
23062318

23072319
SqlConnectionFactory.SingletonInstance.ClearPool(key);
23082320
}

src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlConnectionFactory.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ override protected DbConnectionInternal CreateConnection(DbConnectionOptions opt
9595
// This first connection is established to SqlExpress to get the instance name
9696
// of the UserInstance.
9797
SqlConnectionString sseopt = new SqlConnectionString(opt, opt.DataSource, userInstance: true, setEnlistValue: false);
98-
sseConnection = new SqlInternalConnectionTds(identity, sseopt, key.Credential, null, "", null, false, applyTransientFaultHandling: applyTransientFaultHandling);
98+
sseConnection = new SqlInternalConnectionTds(identity, sseopt, key.Credential, null, "", null, false, applyTransientFaultHandling: applyTransientFaultHandling, sspiContextProviderFactory: key.SSPIContextProviderFactory);
9999
// NOTE: Retrieve <UserInstanceName> here. This user instance name will be used below to connect to the Sql Express User Instance.
100100
instanceName = sseConnection.InstanceName;
101101

@@ -135,7 +135,7 @@ override protected DbConnectionInternal CreateConnection(DbConnectionOptions opt
135135
opt = new SqlConnectionString(opt, instanceName, userInstance: false, setEnlistValue: null);
136136
poolGroupProviderInfo = null; // null so we do not pass to constructor below...
137137
}
138-
return new SqlInternalConnectionTds(identity, opt, key.Credential, poolGroupProviderInfo, "", null, redirectedUserInstance, userOpt, recoverySessionData, applyTransientFaultHandling: applyTransientFaultHandling, key.AccessToken, pool, key.AccessTokenCallback);
138+
return new SqlInternalConnectionTds(identity, opt, key.Credential, poolGroupProviderInfo, "", null, redirectedUserInstance, userOpt, recoverySessionData, applyTransientFaultHandling: applyTransientFaultHandling, key.AccessToken, pool, key.AccessTokenCallback, key.SSPIContextProviderFactory);
139139
}
140140

141141
protected override DbConnectionOptions CreateConnectionOptions(string connectionString, DbConnectionOptions previous)

src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs

+4-2
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,7 @@ internal sealed class SqlInternalConnectionTds : SqlInternalConnection, IDisposa
135135
SqlFedAuthToken _fedAuthToken = null;
136136
internal byte[] _accessTokenInBytes;
137137
internal readonly Func<SqlAuthenticationParameters, CancellationToken, Task<SqlAuthenticationToken>> _accessTokenCallback;
138+
internal readonly Func<SSPIContextProvider> _sspiContextProviderFactory;
138139

139140
private readonly ActiveDirectoryAuthenticationTimeoutRetryHelper _activeDirectoryAuthTimeoutRetryHelper;
140141

@@ -453,8 +454,8 @@ internal SqlInternalConnectionTds(
453454
bool applyTransientFaultHandling = false,
454455
string accessToken = null,
455456
DbConnectionPool pool = null,
456-
Func<SqlAuthenticationParameters, CancellationToken,
457-
Task<SqlAuthenticationToken>> accessTokenCallback = null) : base(connectionOptions)
457+
Func<SqlAuthenticationParameters, CancellationToken, Task<SqlAuthenticationToken>> accessTokenCallback = null,
458+
Func<SSPIContextProvider> sspiContextProviderFactory = null) : base(connectionOptions)
458459

459460
{
460461
#if DEBUG
@@ -488,6 +489,7 @@ internal SqlInternalConnectionTds(
488489
}
489490

490491
_accessTokenCallback = accessTokenCallback;
492+
_sspiContextProviderFactory = sspiContextProviderFactory;
491493

492494
_activeDirectoryAuthTimeoutRetryHelper = new ActiveDirectoryAuthenticationTimeoutRetryHelper();
493495

src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -407,7 +407,7 @@ internal void Connect(
407407
// AD Integrated behaves like Windows integrated when connecting to a non-fedAuth server
408408
if (integratedSecurity || authType == SqlAuthenticationMethod.ActiveDirectoryIntegrated)
409409
{
410-
_authenticationProvider = _physicalStateObj.CreateSSPIContextProvider();
410+
_authenticationProvider = Connection._sspiContextProviderFactory?.Invoke() ?? _physicalStateObj.CreateSSPIContextProvider();
411411
SqlClientEventSource.Log.TryTraceEvent("TdsParser.Connect | SEC | SSPI or Active Directory Authentication Library loaded for SQL Server based integrated authentication");
412412
}
413413

src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.cs

+8
Original file line numberDiff line numberDiff line change
@@ -809,6 +809,8 @@ public SqlConnection(string connectionString, Microsoft.Data.SqlClient.SqlCreden
809809
[System.ComponentModel.BrowsableAttribute(false)]
810810
[System.ComponentModel.DesignerSerializationVisibilityAttribute(0)]
811811
public Microsoft.Data.SqlClient.SqlCredential Credential { get { throw null; } set { } }
812+
/// <include file='../../../../doc/snippets/Microsoft.Data.SqlClient/SqlConnection.xml' path='docs/members[@name="SqlConnection"]/SSPIContextProviderFactory/*' />
813+
public System.Func<SSPIContextProvider> SSPIContextProviderFactory { get { throw null; } set { } }
812814
/// <include file='../../../../doc/snippets/Microsoft.Data.SqlClient/SqlConnection.xml' path='docs/members[@name="SqlConnection"]/Database/*'/>
813815
[System.ComponentModel.DesignerSerializationVisibilityAttribute(0)]
814816
public override string Database { get { throw null; } }
@@ -1952,6 +1954,12 @@ public sealed class SqlConfigurableRetryFactory
19521954
/// <include file='../../../../doc/snippets/Microsoft.Data.SqlClient/SqlConfigurableRetryFactory.xml' path='docs/members[@name="SqlConfigurableRetryFactory"]/CreateNoneRetryProvider/*' />
19531955
public static SqlRetryLogicBaseProvider CreateNoneRetryProvider() { throw null; }
19541956
}
1957+
/// <include file='../../../../doc/snippets/Microsoft.Data.SqlClient/SSPIContextProvider.xml' path='docs/members[@name="SSPIContextProvider"]/SSPIContextProvider/*'/>
1958+
public abstract class SSPIContextProvider
1959+
{
1960+
/// <include file='../../../../doc/snippets/Microsoft.Data.SqlClient/SSPIContextProvider.xml' path='docs/members[@name="SSPIContextProvider"]/GenerateSspiClientContext/*'/>
1961+
protected abstract void GenerateSspiClientContext(System.ReadOnlySpan<byte> incomingBlob, System.Buffers.IBufferWriter<byte> outgoingBlobWriter, SqlAuthenticationParameters authParams);
1962+
}
19551963
}
19561964
namespace Microsoft.Data.SqlClient.Server
19571965
{

0 commit comments

Comments
 (0)