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; } + } } }