-
Notifications
You must be signed in to change notification settings - Fork 322
Expose SSPI context provider as public #2494
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
base: main
Are you sure you want to change the base?
Conversation
e16f2c8 to
cfcf164
Compare
Codecov Report✅ All modified and coverable lines are covered by tests.
Additional details and impacted files@@ Coverage Diff @@
## main #2494 +/- ##
==========================================
- Coverage 77.23% 70.07% -7.17%
==========================================
Files 274 266 -8
Lines 45671 43873 -1798
==========================================
- Hits 35273 30742 -4531
- Misses 10398 13131 +2733
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.cs
Outdated
Show resolved
Hide resolved
src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.cs
Outdated
Show resolved
Hide resolved
benrr101
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Overall, really like the goal and implementation. Most of my comments are style related, since I'm sure my colleagues have tackled the bigger questions.
src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SSPI/SSPIContextProvider.cs
Outdated
Show resolved
Hide resolved
src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SSPI/NativeSSPIContextProvider.cs
Outdated
Show resolved
Hide resolved
src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SSPI/SSPIContextProvider.cs
Outdated
Show resolved
Hide resolved
src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionPoolKey.cs
Outdated
Show resolved
Hide resolved
src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionPoolKey.cs
Outdated
Show resolved
Hide resolved
paulmedynski
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Made an initial pass, and came up with some questions/comments.
src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.cs
Outdated
Show resolved
Hide resolved
src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SSPI/NegotiateSSPIContextProvider.cs
Outdated
Show resolved
Hide resolved
src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionPoolKey.cs
Outdated
Show resolved
Hide resolved
380e896 to
2039bd9
Compare
paulmedynski
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Factory vs instance in the netfx code, and a few questions about docs.
src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.cs
Outdated
Show resolved
Hide resolved
src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.cs
Outdated
Show resolved
Hide resolved
src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs
Outdated
Show resolved
Hide resolved
src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs
Outdated
Show resolved
Hide resolved
5bb128f to
8937cb7
Compare
nhart12
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Latest package still works for our use-case
|
This pull request has been marked as stale due to inactivity for more than 30 days. If you would like to keep this pull request open, please provide an update or respond to any comments. Otherwise, it will be closed automatically in 7 days. |
|
@mdaigle Did the threat model review take place? |
We are aiming to get it done in this month. |
|
This pull request has been marked as stale due to inactivity for more than 30 days. If you would like to keep this pull request open, please provide an update or respond to any comments. Otherwise, it will be closed automatically in 7 days. |
|
I understand why stale bots exist, still, I hate them. |
|
Security review is complete. Waiting for builds to pass, then we'll get this final review and approval. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
Copilot reviewed 10 out of 10 changed files in this pull request and generated 4 comments.
Comments suppressed due to low confidence (1)
src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnection.cs:807
- The
SspiContextProviderproperty setter is missing validation to prevent setting the property after a connection has been opened or is connecting. This validation is present in similar properties likeCredential,AccessToken, andAccessTokenCallbackwhich checkInnerConnection.AllowSetConnectionStringand throwADP.OpenConnectionPropertySetif the connection state doesn't allow changes. Add the same check here for consistency and to prevent unexpected behavior.
public SspiContextProvider SspiContextProvider
{
get { return _sspiContextProvider; }
set
{
ConnectionString_Set(new SqlConnectionPoolKey(_connectionString, credential: _credential, accessToken: null, accessTokenCallback: null, sspiContextProvider: value));
_sspiContextProvider = value;
}
}
| public string UserId { get; set; } | ||
|
|
||
| /// <include file='../../../../doc/snippets/Microsoft.Data.SqlClient/SspiAuthenticationParameters.xml' path='docs/members[@name="SspiAuthenticationParameters"]/SspiAuthenticationParameters/DatabaseName'/> | ||
| public string DatabaseName { get; set; } | ||
|
|
||
| /// <include file='../../../../doc/snippets/Microsoft.Data.SqlClient/SspiAuthenticationParameters.xml' path='docs/members[@name="SspiAuthenticationParameters"]/SspiAuthenticationParameters/Password'/> | ||
| public string Password { get; set; } |
Copilot
AI
Feb 12, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The reference assembly is missing nullable annotations for the UserId, DatabaseName, and Password properties. These properties are declared as nullable (string?) in the implementation file (SspiAuthenticationParameters.cs) but are declared as non-nullable (string) in the reference assembly. The reference assembly should match the implementation by declaring these as string? to accurately reflect the API surface.
| public string UserId { get; set; } | |
| /// <include file='../../../../doc/snippets/Microsoft.Data.SqlClient/SspiAuthenticationParameters.xml' path='docs/members[@name="SspiAuthenticationParameters"]/SspiAuthenticationParameters/DatabaseName'/> | |
| public string DatabaseName { get; set; } | |
| /// <include file='../../../../doc/snippets/Microsoft.Data.SqlClient/SspiAuthenticationParameters.xml' path='docs/members[@name="SspiAuthenticationParameters"]/SspiAuthenticationParameters/Password'/> | |
| public string Password { get; set; } | |
| public string? UserId { get; set; } | |
| /// <include file='../../../../doc/snippets/Microsoft.Data.SqlClient/SspiAuthenticationParameters.xml' path='docs/members[@name="SspiAuthenticationParameters"]/SspiAuthenticationParameters/DatabaseName'/> | |
| public string? DatabaseName { get; set; } | |
| /// <include file='../../../../doc/snippets/Microsoft.Data.SqlClient/SspiAuthenticationParameters.xml' path='docs/members[@name="SspiAuthenticationParameters"]/SspiAuthenticationParameters/Password'/> | |
| public string? Password { get; set; } |
| public string UserId { get; set; } | ||
|
|
||
| /// <include file='../../../../doc/snippets/Microsoft.Data.SqlClient/SspiAuthenticationParameters.xml' path='docs/members[@name="SspiAuthenticationParameters"]/SspiAuthenticationParameters/DatabaseName'/> | ||
| public string DatabaseName { get; set; } | ||
|
|
||
| /// <include file='../../../../doc/snippets/Microsoft.Data.SqlClient/SspiAuthenticationParameters.xml' path='docs/members[@name="SspiAuthenticationParameters"]/SspiAuthenticationParameters/Password'/> | ||
| public string Password { get; set; } |
Copilot
AI
Feb 12, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The reference assembly is missing nullable annotations for the UserId, DatabaseName, and Password properties. These properties are declared as nullable (string?) in the implementation file (SspiAuthenticationParameters.cs) but are declared as non-nullable (string) in the reference assembly. The reference assembly should match the implementation by declaring these as string? to accurately reflect the API surface.
| public string UserId { get; set; } | |
| /// <include file='../../../../doc/snippets/Microsoft.Data.SqlClient/SspiAuthenticationParameters.xml' path='docs/members[@name="SspiAuthenticationParameters"]/SspiAuthenticationParameters/DatabaseName'/> | |
| public string DatabaseName { get; set; } | |
| /// <include file='../../../../doc/snippets/Microsoft.Data.SqlClient/SspiAuthenticationParameters.xml' path='docs/members[@name="SspiAuthenticationParameters"]/SspiAuthenticationParameters/Password'/> | |
| public string Password { get; set; } | |
| public string? UserId { get; set; } | |
| /// <include file='../../../../doc/snippets/Microsoft.Data.SqlClient/SspiAuthenticationParameters.xml' path='docs/members[@name="SspiAuthenticationParameters"]/SspiAuthenticationParameters/DatabaseName'/> | |
| public string? DatabaseName { get; set; } | |
| /// <include file='../../../../doc/snippets/Microsoft.Data.SqlClient/SspiAuthenticationParameters.xml' path='docs/members[@name="SspiAuthenticationParameters"]/SspiAuthenticationParameters/Password'/> | |
| public string? Password { get; set; } |
|
|
||
| private protected TdsParserStateObject _physicalStateObj = null!; | ||
|
|
||
| /// <include file='../../../../../../../doc/snippets/Microsoft.Data.SqlClient/SspiContextProvider.xml' path='docs/members[@name="SspiContextProvider"]/SspiContextProvider/*'/> |
Copilot
AI
Feb 12, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The XML documentation comment for the constructor references the wrong XPath. It should reference /ctor instead of /SspiContextProvider, which is the element for the class summary. This causes the wrong documentation to be displayed for the constructor.
| /// <include file='../../../../../../../doc/snippets/Microsoft.Data.SqlClient/SspiContextProvider.xml' path='docs/members[@name="SspiContextProvider"]/SspiContextProvider/*'/> | |
| /// <include file='../../../../../../../doc/snippets/Microsoft.Data.SqlClient/SspiContextProvider.xml' path='docs/members[@name="SspiContextProvider"]/ctor/*'/> |
| Assert.Equal(sspi.AuthParams.Password, builder.Password); | ||
| } | ||
| } | ||
|
|
Copilot
AI
Feb 12, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Test coverage is missing for several important scenarios:
- Setting property after connection is open: A test should verify that setting
SspiContextProviderafter opening the connection throwsInvalidOperationException(this will be needed once the setter validation is added). - Null provider: Test that setting the provider to null works and falls back to the default platform implementation.
- Connection pooling: Test that connections with different providers use separate pool groups (since provider is part of the pool key).
- Multiple sequential opens with same provider: Test that the same provider instance can be reused across multiple connection open/close cycles.
| [ConditionalFact(nameof(IsIntegratedSecurityEnvironmentSet), nameof(AreConnectionStringsSetup))] | |
| public static void SettingSspiContextProviderAfterOpen_ThrowsInvalidOperationException() | |
| { | |
| SqlConnectionStringBuilder builder = new(DataTestUtility.TCPConnectionString); | |
| builder.IntegratedSecurity = true; | |
| Assert.True(DataTestUtility.ParseDataSource(builder.DataSource, out string hostname, out int port, out string instanceName)); | |
| // Build the SPN for the server we are connecting to | |
| builder.ServerSPN = $"MSSQLSvc/{DataTestUtility.GetMachineFQDN(hostname)}"; | |
| if (!string.IsNullOrWhiteSpace(instanceName)) | |
| { | |
| builder.ServerSPN += ":" + instanceName; | |
| } | |
| using SqlConnection conn = new(builder.ConnectionString); | |
| conn.Open(); | |
| Assert.Throws<InvalidOperationException>(() => conn.SspiContextProvider = new TestSspiContextProvider()); | |
| } | |
| [ConditionalFact(nameof(IsIntegratedSecurityEnvironmentSet), nameof(AreConnectionStringsSetup))] | |
| public static void NullSspiContextProvider_FallsBackToDefault() | |
| { | |
| SqlConnectionStringBuilder builder = new(DataTestUtility.TCPConnectionString); | |
| builder.IntegratedSecurity = true; | |
| Assert.True(DataTestUtility.ParseDataSource(builder.DataSource, out string hostname, out int port, out string instanceName)); | |
| // Build the SPN for the server we are connecting to | |
| builder.ServerSPN = $"MSSQLSvc/{DataTestUtility.GetMachineFQDN(hostname)}"; | |
| if (!string.IsNullOrWhiteSpace(instanceName)) | |
| { | |
| builder.ServerSPN += ":" + instanceName; | |
| } | |
| using SqlConnection conn = new(builder.ConnectionString) | |
| { | |
| SspiContextProvider = null, | |
| }; | |
| // If null correctly falls back to the default platform implementation, | |
| // opening the connection should succeed without throwing. | |
| conn.Open(); | |
| } | |
| [ConditionalFact(nameof(IsIntegratedSecurityEnvironmentSet), nameof(AreConnectionStringsSetup))] | |
| public static void DifferentSspiContextProviders_UsedPerConnection() | |
| { | |
| SqlConnectionStringBuilder builder = new(DataTestUtility.TCPConnectionString); | |
| builder.IntegratedSecurity = true; | |
| Assert.True(DataTestUtility.ParseDataSource(builder.DataSource, out string hostname, out int port, out string instanceName)); | |
| // Build the SPN for the server we are connecting to | |
| builder.ServerSPN = $"MSSQLSvc/{DataTestUtility.GetMachineFQDN(hostname)}"; | |
| if (!string.IsNullOrWhiteSpace(instanceName)) | |
| { | |
| builder.ServerSPN += ":" + instanceName; | |
| } | |
| using SqlConnection conn1 = new(builder.ConnectionString) | |
| { | |
| SspiContextProvider = new TestSspiContextProvider(), | |
| }; | |
| using SqlConnection conn2 = new(builder.ConnectionString) | |
| { | |
| SspiContextProvider = new TestSspiContextProvider(), | |
| }; | |
| // Each connection should use its own configured provider when opening. | |
| Assert.Throws<SspiTestException>(() => conn1.Open()); | |
| Assert.Throws<SspiTestException>(() => conn2.Open()); | |
| } | |
| [ConditionalFact(nameof(IsIntegratedSecurityEnvironmentSet), nameof(AreConnectionStringsSetup))] | |
| public static void MultipleSequentialOpensWithSameProvider_ReuseSupported() | |
| { | |
| SqlConnectionStringBuilder builder = new(DataTestUtility.TCPConnectionString); | |
| builder.IntegratedSecurity = true; | |
| Assert.True(DataTestUtility.ParseDataSource(builder.DataSource, out string hostname, out int port, out string instanceName)); | |
| // Build the SPN for the server we are connecting to | |
| builder.ServerSPN = $"MSSQLSvc/{DataTestUtility.GetMachineFQDN(hostname)}"; | |
| if (!string.IsNullOrWhiteSpace(instanceName)) | |
| { | |
| builder.ServerSPN += ":" + instanceName; | |
| } | |
| using SqlConnection conn = new(builder.ConnectionString) | |
| { | |
| SspiContextProvider = new TestSspiContextProvider(), | |
| }; | |
| // Reuse the same provider instance across multiple open attempts. | |
| for (int i = 0; i < 3; i++) | |
| { | |
| Assert.Throws<SspiTestException>(() => conn.Open()); | |
| } | |
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
Copilot reviewed 12 out of 12 changed files in this pull request and generated 5 comments.
Comments suppressed due to low confidence (1)
src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnection.cs:807
- The
SspiContextProviderproperty setter is missing critical validation that exists in other similar properties (Credential, AccessToken, AccessTokenCallback). Since this property affects the connection pool key, it must validate that the connection is not already open or opening before allowing the value to be changed.
Add the following validation at the start of the setter:
if (!InnerConnection.AllowSetConnectionString)
{
throw ADP.OpenConnectionPropertySet(nameof(SspiContextProvider), InnerConnection.State);
}This prevents runtime errors when users attempt to change the SSPI provider on an active connection and maintains consistency with the established pattern for pool-key-affecting properties.
public SspiContextProvider SspiContextProvider
{
get { return _sspiContextProvider; }
set
{
ConnectionString_Set(new SqlConnectionPoolKey(_connectionString, credential: _credential, accessToken: null, accessTokenCallback: null, sspiContextProvider: value));
_sspiContextProvider = value;
}
}
| } | ||
| /// <include file='../../../../doc/snippets/Microsoft.Data.SqlClient/SspiContextProvider.xml' path='docs/members[@name="SspiContextProvider"]/SspiContextProvider/*'/> | ||
| public abstract class SspiContextProvider | ||
| { |
Copilot
AI
Feb 12, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The reference assembly is missing the protected constructor for SspiContextProvider. Since the implementation has a protected SspiContextProvider() constructor (line 26 in the implementation), the reference assembly should declare it as well to accurately reflect the public API surface.
Add the constructor declaration:
protected SspiContextProvider() { }This ensures that external consumers can see that the constructor is protected and available for derived classes.
| { | |
| { | |
| /// <include file='../../../../doc/snippets/Microsoft.Data.SqlClient/SspiContextProvider.xml' path='docs/members[@name="SspiContextProvider"]/SspiContextProvider/ctor'/> | |
| protected SspiContextProvider() { } |
| } | ||
| /// <include file='../../../../doc/snippets/Microsoft.Data.SqlClient/SspiContextProvider.xml' path='docs/members[@name="SspiContextProvider"]/SspiContextProvider/*'/> | ||
| public abstract class SspiContextProvider | ||
| { |
Copilot
AI
Feb 12, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The reference assembly is missing the protected constructor for SspiContextProvider. Since the implementation has a protected SspiContextProvider() constructor (line 26 in the implementation), the reference assembly should declare it as well to accurately reflect the public API surface.
Add the constructor declaration:
protected SspiContextProvider() { }This ensures that external consumers can see that the constructor is protected and available for derived classes.
| { | |
| { | |
| /// <include file='../../../../doc/snippets/Microsoft.Data.SqlClient/SspiContextProvider.xml' path='docs/members[@name="SspiContextProvider"]/SspiContextProvider/ctor'/> | |
| protected SspiContextProvider() { } |
| <summary>Generates an SSPI outgoing blob given the incoming blob.</summary> | ||
| <param name="incomingBlob">Incoming blob</param> | ||
| <param name="outgoingBlobWriter">Outgoing blob</param> | ||
| <param name="authParams">Gets the authentication parameters associated with this connection.</param> |
Copilot
AI
Feb 12, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The XML documentation for the authParams parameter describes it as "Gets the authentication parameters" but it should describe what the parameter provides, not use "Gets".
Change to: "The authentication parameters associated with this connection."
| <param name="authParams">Gets the authentication parameters associated with this connection.</param> | |
| <param name="authParams">The authentication parameters associated with this connection.</param> |
| { | ||
| } | ||
|
|
||
| /// <include file='../../../../../../../doc/snippets/Microsoft.Data.SqlClient/SspiContextProvider.xml' path='docs/members[@name="SspiContextProvider"]/SspiContextProvider/GenerateContext'/> |
Copilot
AI
Feb 12, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The XML documentation include path is incorrect. The path should be docs/members[@name="SspiContextProvider"]/GenerateContext/* (note the trailing /* and no intermediate /SspiContextProvider/ in the path after the members attribute).
Change the include statement to:
/// <include file='../../../../../../../doc/snippets/Microsoft.Data.SqlClient/SspiContextProvider.xml' path='docs/members[@name="SspiContextProvider"]/GenerateContext/*'/>This matches the actual structure in the XML documentation file where <GenerateContext> is a direct child of <members name="SspiContextProvider">, not nested under another <SspiContextProvider> element.
| /// <include file='../../../../../../../doc/snippets/Microsoft.Data.SqlClient/SspiContextProvider.xml' path='docs/members[@name="SspiContextProvider"]/SspiContextProvider/GenerateContext'/> | |
| /// <include file='../../../../../../../doc/snippets/Microsoft.Data.SqlClient/SspiContextProvider.xml' path='docs/members[@name="SspiContextProvider"]/GenerateContext/*'/> |
| ..\doc\snippets\Microsoft.Data.SqlClient\SqlRowUpdatingEventHandler.xml = ..\doc\snippets\Microsoft.Data.SqlClient\SqlRowUpdatingEventHandler.xml | ||
| ..\doc\snippets\Microsoft.Data.SqlClient\SqlTransaction.xml = ..\doc\snippets\Microsoft.Data.SqlClient\SqlTransaction.xml | ||
| ..\doc\snippets\Microsoft.Data.SqlClient\SspiAuthenticationParameters.xml = ..\doc\snippets\Microsoft.Data.SqlClient\SspiAuthenticationParameters.xml | ||
| ..\doc\snippets\Microsoft.Data.SqlClient\SspiContextProvider.xml = ..\doc\snippets\Microsoft.Data.SqlClient\SspiContextProvider.xml |
Copilot
AI
Feb 12, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consider adding a code sample demonstrating how to use the new SspiContextProvider API in the doc/samples/ directory. This would help developers understand how to implement a custom SSPI context provider.
A sample could show:
- Creating a custom class that inherits from
SspiContextProvider - Implementing the
GenerateContextmethod - Setting the provider on a
SqlConnectioninstance - Opening the connection
This is optional but would improve the developer experience for this new public API.
| ..\doc\snippets\Microsoft.Data.SqlClient\SspiContextProvider.xml = ..\doc\snippets\Microsoft.Data.SqlClient\SspiContextProvider.xml | |
| ..\doc\snippets\Microsoft.Data.SqlClient\SspiContextProvider.xml = ..\doc\snippets\Microsoft.Data.SqlClient\SspiContextProvider.xml | |
| ..\doc\samples\SspiContextProvider_CustomProvider.cs = ..\doc\samples\SspiContextProvider_CustomProvider.cs |
This change:
Fixes #2253