Skip to content

Conversation

@twsouthwick
Copy link
Member

@twsouthwick twsouthwick commented May 8, 2024

This change:

  • Adds a property to SqlConnection to allow setting a provider
  • Plumbs that property into the TdsParser so that it can be used if set

Fixes #2253

@twsouthwick twsouthwick changed the title Expose SSPI context provider as public Expose SSPI context provider as public May 8, 2024
@twsouthwick twsouthwick force-pushed the public branch 3 times, most recently from e16f2c8 to cfcf164 Compare March 3, 2025 19:42
@codecov
Copy link

codecov bot commented Mar 3, 2025

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 70.07%. Comparing base (d05100e) to head (77cab44).
⚠️ Report is 110 commits behind head on main.

❗ There is a different number of reports uploaded between BASE (d05100e) and HEAD (77cab44). Click for more details.

HEAD has 1 upload less than BASE
Flag BASE (d05100e) HEAD (77cab44)
addons 1 0
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     
Flag Coverage Δ
addons ?
netcore 70.09% <ø> (-7.08%) ⬇️
netfx 69.44% <ø> (-6.98%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Copy link
Contributor

@benrr101 benrr101 left a 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.

Copy link
Contributor

@paulmedynski paulmedynski left a 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.

@paulmedynski paulmedynski self-assigned this Apr 2, 2025
@twsouthwick twsouthwick force-pushed the public branch 6 times, most recently from 380e896 to 2039bd9 Compare May 1, 2025 19:37
Copy link
Contributor

@paulmedynski paulmedynski left a 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.

@twsouthwick twsouthwick force-pushed the public branch 4 times, most recently from 5bb128f to 8937cb7 Compare May 13, 2025 14:58
@twsouthwick twsouthwick marked this pull request as ready for review May 13, 2025 18:40
Copy link

@nhart12 nhart12 left a 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

paulmedynski
paulmedynski previously approved these changes Nov 6, 2025
@mdaigle mdaigle modified the milestones: 7.0.0-preview3, 7.0.0-preview4 Dec 2, 2025
@github-actions
Copy link

github-actions bot commented Jan 2, 2026

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.

@github-actions github-actions bot added the Stale The Issue or PR has become stale and will be automatically closed shortly if no activity occurs. label Jan 2, 2026
@0xced
Copy link
Contributor

0xced commented Jan 7, 2026

@mdaigle Did the threat model review take place?

@cheenamalhotra
Copy link
Member

@mdaigle Did the threat model review take place?

We are aiming to get it done in this month.

@cheenamalhotra cheenamalhotra removed the Stale The Issue or PR has become stale and will be automatically closed shortly if no activity occurs. label Jan 7, 2026
@cheenamalhotra cheenamalhotra modified the milestones: 7.0.0-preview4, 7.0.0-preview5 Jan 7, 2026
@cheenamalhotra cheenamalhotra added the Public API 🆕 Issues/PRs that introduce new APIs to the driver. label Jan 13, 2026
@github-actions
Copy link

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.

@github-actions github-actions bot added the Stale The Issue or PR has become stale and will be automatically closed shortly if no activity occurs. label Feb 12, 2026
@0xced
Copy link
Contributor

0xced commented Feb 12, 2026

I understand why stale bots exist, still, I hate them.

Copilot AI review requested due to automatic review settings February 12, 2026 19:41
@mdaigle
Copy link
Contributor

mdaigle commented Feb 12, 2026

Security review is complete. Waiting for builds to pass, then we'll get this final review and approval.

Copy link
Contributor

Copilot AI left a 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 SspiContextProvider property 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 like Credential, AccessToken, and AccessTokenCallback which check InnerConnection.AllowSetConnectionString and throw ADP.OpenConnectionPropertySet if 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;
            }
        }

Comment on lines +1885 to +1891
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; }
Copy link

Copilot AI Feb 12, 2026

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.

Suggested change
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 uses AI. Check for mistakes.
Comment on lines +1904 to +1910
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; }
Copy link

Copilot AI Feb 12, 2026

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.

Suggested change
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 uses AI. Check for mistakes.

private protected TdsParserStateObject _physicalStateObj = null!;

/// <include file='../../../../../../../doc/snippets/Microsoft.Data.SqlClient/SspiContextProvider.xml' path='docs/members[@name="SspiContextProvider"]/SspiContextProvider/*'/>
Copy link

Copilot AI Feb 12, 2026

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.

Suggested change
/// <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/*'/>

Copilot uses AI. Check for mistakes.
Assert.Equal(sspi.AuthParams.Password, builder.Password);
}
}

Copy link

Copilot AI Feb 12, 2026

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:

  1. Setting property after connection is open: A test should verify that setting SspiContextProvider after opening the connection throws InvalidOperationException (this will be needed once the setter validation is added).
  2. Null provider: Test that setting the provider to null works and falls back to the default platform implementation.
  3. Connection pooling: Test that connections with different providers use separate pool groups (since provider is part of the pool key).
  4. Multiple sequential opens with same provider: Test that the same provider instance can be reused across multiple connection open/close cycles.
Suggested change
[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());
}
}

Copilot uses AI. Check for mistakes.
@paulmedynski paulmedynski modified the milestones: 7.0.0-preview5, 7.0.0-preview4 Feb 12, 2026
mdaigle
mdaigle previously approved these changes Feb 12, 2026
Copilot AI review requested due to automatic review settings February 12, 2026 23:12
Copy link
Contributor

Copilot AI left a 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 SspiContextProvider property 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
{
Copy link

Copilot AI Feb 12, 2026

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.

Suggested change
{
{
/// <include file='../../../../doc/snippets/Microsoft.Data.SqlClient/SspiContextProvider.xml' path='docs/members[@name="SspiContextProvider"]/SspiContextProvider/ctor'/>
protected SspiContextProvider() { }

Copilot uses AI. Check for mistakes.
}
/// <include file='../../../../doc/snippets/Microsoft.Data.SqlClient/SspiContextProvider.xml' path='docs/members[@name="SspiContextProvider"]/SspiContextProvider/*'/>
public abstract class SspiContextProvider
{
Copy link

Copilot AI Feb 12, 2026

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.

Suggested change
{
{
/// <include file='../../../../doc/snippets/Microsoft.Data.SqlClient/SspiContextProvider.xml' path='docs/members[@name="SspiContextProvider"]/SspiContextProvider/ctor'/>
protected SspiContextProvider() { }

Copilot uses AI. Check for mistakes.
<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>
Copy link

Copilot AI Feb 12, 2026

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."

Suggested change
<param name="authParams">Gets the authentication parameters associated with this connection.</param>
<param name="authParams">The authentication parameters associated with this connection.</param>

Copilot uses AI. Check for mistakes.
{
}

/// <include file='../../../../../../../doc/snippets/Microsoft.Data.SqlClient/SspiContextProvider.xml' path='docs/members[@name="SspiContextProvider"]/SspiContextProvider/GenerateContext'/>
Copy link

Copilot AI Feb 12, 2026

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.

Suggested change
/// <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/*'/>

Copilot uses AI. Check for mistakes.
..\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
Copy link

Copilot AI Feb 12, 2026

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:

  1. Creating a custom class that inherits from SspiContextProvider
  2. Implementing the GenerateContext method
  3. Setting the provider on a SqlConnection instance
  4. Opening the connection

This is optional but would improve the developer experience for this new public API.

Suggested change
..\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

Copilot uses AI. Check for mistakes.
@github-actions github-actions bot removed the Stale The Issue or PR has become stale and will be automatically closed shortly if no activity occurs. label Feb 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Partner Approval Needed 🤝 Issues/PRs that require approval/feedback from partner teams Public API 🆕 Issues/PRs that introduce new APIs to the driver.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add hook for custom SSPI context for SqlConnection instances

8 participants