diff --git a/doc/snippets/Microsoft.Data.SqlClient/SqlConnection.xml b/doc/snippets/Microsoft.Data.SqlClient/SqlConnection.xml
index 11602bd078..161326f6d1 100644
--- a/doc/snippets/Microsoft.Data.SqlClient/SqlConnection.xml
+++ b/doc/snippets/Microsoft.Data.SqlClient/SqlConnection.xml
@@ -2133,6 +2133,19 @@ The following sample tries to open a connection to an invalid database to simula
Returns 0 if the connection is inactive on the client side.
+
+
+ Gets or sets the instance for customizing the SSPI context. If not set, the default for the platform will be used.
+
+
+ An instance.
+
+
+
+ The SspiContextProvider is a part of the connection pool key. Care should be taken when using this property to ensure the implementation returns a stable identity per resource.
+
+
+
Indicates the state of the during the most recent network operation performed on the connection.
diff --git a/doc/snippets/Microsoft.Data.SqlClient/SspiAuthenticationParameters.xml b/doc/snippets/Microsoft.Data.SqlClient/SspiAuthenticationParameters.xml
new file mode 100644
index 0000000000..4623d86052
--- /dev/null
+++ b/doc/snippets/Microsoft.Data.SqlClient/SspiAuthenticationParameters.xml
@@ -0,0 +1,28 @@
+
+
+
+
+ Provides parameters used during SSPI authentication.
+
+
+ Creates an instance of the SspiAuthenticationParameters.
+ The name of the server.
+ The resource (often the server service principal name).
+
+
+ Gets the resource (often the server service principal name).
+
+
+ Gets the server name.
+
+
+ Gets or sets the user id if available.
+
+
+ Gets or sets the database name if available.
+
+
+ Gets or sets the password if available.
+
+
+
diff --git a/doc/snippets/Microsoft.Data.SqlClient/SspiContextProvider.xml b/doc/snippets/Microsoft.Data.SqlClient/SspiContextProvider.xml
new file mode 100644
index 0000000000..2ea58cb80b
--- /dev/null
+++ b/doc/snippets/Microsoft.Data.SqlClient/SspiContextProvider.xml
@@ -0,0 +1,20 @@
+
+
+
+
+ Provides the ability to customize SSPI context generation.
+
+
+ Creates an instance of the SSPIContextProvider.
+
+
+ Generates an SSPI outgoing blob given the incoming blob.
+ Incoming blob
+ Outgoing blob
+ Gets the authentication parameters associated with this connection.
+
+ true if the context was generated, otherwise false.
+
+
+
+
diff --git a/src/Microsoft.Data.SqlClient.sln b/src/Microsoft.Data.SqlClient.sln
index e4d29d999c..bf0cbe8cc2 100644
--- a/src/Microsoft.Data.SqlClient.sln
+++ b/src/Microsoft.Data.SqlClient.sln
@@ -149,6 +149,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Microsoft.Data.SqlClient",
..\doc\snippets\Microsoft.Data.SqlClient\SqlRowUpdatingEventArgs.xml = ..\doc\snippets\Microsoft.Data.SqlClient\SqlRowUpdatingEventArgs.xml
..\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
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Microsoft.Data.SqlClient.DataClassification", "Microsoft.Data.SqlClient.DataClassification", "{5D1F0032-7B0D-4FB6-A969-FCFB25C9EA1D}"
diff --git a/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.cs b/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.cs
index 3e26aa251a..0409ac83ad 100644
--- a/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.cs
+++ b/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.cs
@@ -929,6 +929,8 @@ public void RegisterColumnEncryptionKeyStoreProvidersOnConnection(System.Collect
[System.ComponentModel.BrowsableAttribute(false)]
[System.ComponentModel.DesignerSerializationVisibilityAttribute(0)]
public Microsoft.Data.SqlClient.SqlCredential Credential { get { throw null; } set { } }
+ ///
+ public SspiContextProvider SspiContextProvider { get { throw null; } set { } }
///
[System.ComponentModel.DesignerSerializationVisibilityAttribute(0)]
public override string Database { get { throw null; } }
@@ -1976,6 +1978,37 @@ public sealed class SqlConfigurableRetryFactory
///
public static SqlRetryLogicBaseProvider CreateNoneRetryProvider() { throw null; }
}
+ ///
+ public abstract class SspiContextProvider
+ {
+ ///
+ protected abstract bool GenerateContext(System.ReadOnlySpan incomingBlob, System.Buffers.IBufferWriter outgoingBlobWriter, SspiAuthenticationParameters authParams);
+ }
+ ///
+ public sealed class SspiAuthenticationParameters
+ {
+ ///
+ public SspiAuthenticationParameters(string serverName, string resource)
+ {
+ ServerName = serverName;
+ Resource = resource;
+ }
+
+ ///
+ public string Resource { get; }
+
+ ///
+ public string ServerName { get; }
+
+ ///
+ public string UserId { get; set; }
+
+ ///
+ public string DatabaseName { get; set; }
+
+ ///
+ public string Password { get; set; }
+ }
}
namespace Microsoft.Data.SqlClient.Diagnostics
{
diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlConnection.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlConnection.cs
index d4f13efd80..7478282d48 100644
--- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlConnection.cs
+++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlConnection.cs
@@ -729,7 +729,8 @@ public Func
+ public SspiContextProvider SspiContextProvider
{
get { return _sspiContextProvider; }
set
diff --git a/src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.cs b/src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.cs
index a3bb3b46c2..50ddd52f0e 100644
--- a/src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.cs
+++ b/src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.cs
@@ -810,6 +810,8 @@ public SqlConnection(string connectionString, Microsoft.Data.SqlClient.SqlCreden
[System.ComponentModel.BrowsableAttribute(false)]
[System.ComponentModel.DesignerSerializationVisibilityAttribute(0)]
public Microsoft.Data.SqlClient.SqlCredential Credential { get { throw null; } set { } }
+ ///
+ public SspiContextProvider SspiContextProvider { get { throw null; } set { } }
///
[System.ComponentModel.DesignerSerializationVisibilityAttribute(0)]
public override string Database { get { throw null; } }
@@ -1959,6 +1961,37 @@ public sealed class SqlConfigurableRetryFactory
///
public static SqlRetryLogicBaseProvider CreateNoneRetryProvider() { throw null; }
}
+ ///
+ public abstract class SspiContextProvider
+ {
+ ///
+ protected abstract bool GenerateContext(System.ReadOnlySpan incomingBlob, System.Buffers.IBufferWriter outgoingBlobWriter, SspiAuthenticationParameters authParams);
+ }
+ ///
+ public sealed class SspiAuthenticationParameters
+ {
+ ///
+ public SspiAuthenticationParameters(string serverName, string resource)
+ {
+ ServerName = serverName;
+ Resource = resource;
+ }
+
+ ///
+ public string Resource { get; }
+
+ ///
+ public string ServerName { get; }
+
+ ///
+ public string UserId { get; set; }
+
+ ///
+ public string DatabaseName { get; set; }
+
+ ///
+ public string Password { get; set; }
+ }
}
namespace Microsoft.Data.SqlClient.Server
{
diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlConnection.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlConnection.cs
index 24cd7dbe28..be87ea602c 100644
--- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlConnection.cs
+++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlConnection.cs
@@ -579,7 +579,8 @@ internal int ConnectRetryInterval
get => ((SqlConnectionString)ConnectionOptions).ConnectRetryInterval;
}
- internal SspiContextProvider SspiContextProvider
+ ///
+ public SspiContextProvider SspiContextProvider
{
get { return _sspiContextProvider; }
set
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SSPI/SspiAuthenticationParameters.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SSPI/SspiAuthenticationParameters.cs
index dce0858360..ad6c92853f 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SSPI/SspiAuthenticationParameters.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SSPI/SspiAuthenticationParameters.cs
@@ -2,22 +2,29 @@
namespace Microsoft.Data.SqlClient
{
- internal sealed class SspiAuthenticationParameters
+ ///
+ public sealed class SspiAuthenticationParameters
{
+ ///
public SspiAuthenticationParameters(string serverName, string resource)
{
ServerName = serverName;
Resource = resource;
}
+ ///
public string Resource { get; }
+ ///
public string ServerName { get; }
+ ///
public string? UserId { get; set; }
+ ///
public string? DatabaseName { get; set; }
+ ///
public string? Password { get; set; }
}
}
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SSPI/SspiContextProvider.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SSPI/SspiContextProvider.cs
index d23200410c..1aa0767349 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SSPI/SspiContextProvider.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SSPI/SspiContextProvider.cs
@@ -6,7 +6,8 @@
namespace Microsoft.Data.SqlClient
{
- internal abstract class SspiContextProvider
+ ///
+ public abstract class SspiContextProvider
{
private TdsParser _parser = null!;
private ServerInfo _serverInfo = null!;
@@ -16,6 +17,7 @@ internal abstract class SspiContextProvider
private protected TdsParserStateObject _physicalStateObj = null!;
+ ///
protected SspiContextProvider()
{
}
@@ -62,6 +64,7 @@ private protected virtual void Initialize()
{
}
+ ///
protected abstract bool GenerateContext(ReadOnlySpan incomingBlob, IBufferWriter outgoingBlobWriter, SspiAuthenticationParameters authParams);
internal void WriteSSPIContext(ReadOnlySpan receivedBuff, IBufferWriter outgoingBlobWriter)
diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/IntegratedAuthenticationTest/IntegratedAuthenticationTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/IntegratedAuthenticationTest/IntegratedAuthenticationTest.cs
index e043b2253c..af650a408e 100644
--- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/IntegratedAuthenticationTest/IntegratedAuthenticationTest.cs
+++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/IntegratedAuthenticationTest/IntegratedAuthenticationTest.cs
@@ -2,6 +2,8 @@
// 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.Buffers;
using Xunit;
namespace Microsoft.Data.SqlClient.ManualTesting.Tests
@@ -56,6 +58,39 @@ public static void IntegratedAuthenticationTest_ServerSPN()
TryOpenConnectionWithIntegratedAuthentication(builder.ConnectionString);
}
+ [ConditionalFact(nameof(IsIntegratedSecurityEnvironmentSet), nameof(AreConnectionStringsSetup))]
+ public static void CustomSspiContextGeneratorTest()
+ {
+ 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(),
+ };
+
+ try
+ {
+ conn.Open();
+
+ Assert.Fail("Expected to use custom SSPI context provider");
+ }
+ catch (SspiTestException sspi)
+ {
+ Assert.Equal(sspi.AuthParams.ServerName, builder.DataSource);
+ Assert.Equal(sspi.AuthParams.DatabaseName, builder.InitialCatalog);
+ Assert.Equal(sspi.AuthParams.UserId, builder.UserID);
+ Assert.Equal(sspi.AuthParams.Password, builder.Password);
+ }
+ }
+
private static void TryOpenConnectionWithIntegratedAuthentication(string connectionString)
{
using (SqlConnection connection = new SqlConnection(connectionString))
@@ -63,5 +98,23 @@ private static void TryOpenConnectionWithIntegratedAuthentication(string connect
connection.Open();
}
}
+
+ private sealed class TestSspiContextProvider : SspiContextProvider
+ {
+ protected override bool GenerateContext(ReadOnlySpan incomingBlob, IBufferWriter outgoingBlobWriter, SspiAuthenticationParameters authParams)
+ {
+ throw new SspiTestException(authParams);
+ }
+ }
+
+ private sealed class SspiTestException : Exception
+ {
+ public SspiTestException(SspiAuthenticationParameters authParams)
+ {
+ AuthParams = authParams;
+ }
+
+ public SspiAuthenticationParameters AuthParams { get; }
+ }
}
}