From 9cc974f98e5d2f18091ebbb4e63bcec8091ff036 Mon Sep 17 00:00:00 2001 From: Matt Connew Date: Wed, 2 Sep 2020 21:13:23 -0700 Subject: [PATCH] Add support for AuthenticationSchemes.IntegratedWindowsAuthentication --- .../src/Resources/Strings.resx | 4 +- .../Channels/HttpChannelFactory.cs | 42 +++++++++++++++---- .../Channels/HttpChannelHelpers.cs | 1 + .../Channels/HttpTransportBindingElement.cs | 2 +- .../ServiceModel/HttpClientCredentialType.cs | 1 + .../ClientCredentialsSecurityTokenManager.cs | 2 +- .../Http/ClientCredentialTypeTests.4.1.0.cs | 30 +++++++++++-- .../Https/ClientCredentialTypeTests.4.1.0.cs | 11 +++-- 8 files changed, 72 insertions(+), 21 deletions(-) diff --git a/src/System.Private.ServiceModel/src/Resources/Strings.resx b/src/System.Private.ServiceModel/src/Resources/Strings.resx index 34dcff2a7d3..da36422a27c 100644 --- a/src/System.Private.ServiceModel/src/Resources/Strings.resx +++ b/src/System.Private.ServiceModel/src/Resources/Strings.resx @@ -862,10 +862,10 @@ Cannot resolve the host name of URI "{0}" using DNS. - The '{0}' authentication scheme has been specified on the HTTP factory. However, the factory only supports specification of exactly one authentication scheme. Valid authentication schemes are Digest, Negotiate, NTLM, Basic, or Anonymous. + The '{0}' authentication scheme has been specified on the HTTP factory. However, the factory only supports specification of exactly one authentication scheme. Valid authentication schemes are Digest, Negotiate, NTLM, Basic, IntegratedWindowsAuthentication, or Anonymous. - The value specified for the AuthenticationScheme property on the HttpTransportBindingElement ('{0}') is not allowed when building a ChannelFactory. If you used a standard binding, ensure the ClientCredentialType is not set to HttpClientCredentialType.InheritedFromHost, a value which is invalid on a client. If you set the value to '{0}' directly on the HttpTransportBindingElement, please set it to Digest, Negotiate, NTLM, Basic, or Anonymous. + The value specified for the AuthenticationScheme property on the HttpTransportBindingElement ('{0}') is not allowed when building a ChannelFactory. If you used a standard binding, ensure the ClientCredentialType is not set to HttpClientCredentialType.InheritedFromHost, a value which is invalid on a client. If you set the value to '{0}' directly on the HttpTransportBindingElement, please set it to Digest, Negotiate, NTLM, Basic, IntegratedWindowsAuthentication, or Anonymous. The HTTP request is unauthorized with client authentication scheme '{0}'. The authentication header received from the server was '{1}'. diff --git a/src/System.Private.ServiceModel/src/System/ServiceModel/Channels/HttpChannelFactory.cs b/src/System.Private.ServiceModel/src/System/ServiceModel/Channels/HttpChannelFactory.cs index 4aa42d25871..898930a238c 100644 --- a/src/System.Private.ServiceModel/src/System/ServiceModel/Channels/HttpChannelFactory.cs +++ b/src/System.Private.ServiceModel/src/System/ServiceModel/Channels/HttpChannelFactory.cs @@ -89,7 +89,7 @@ internal HttpChannelFactory(HttpTransportBindingElement bindingElement, BindingC _httpCookieContainerManager = new HttpCookieContainerManager(); } - if (!bindingElement.AuthenticationScheme.IsSingleton()) + if (!bindingElement.AuthenticationScheme.IsSingleton() && bindingElement.AuthenticationScheme != AuthenticationSchemes.IntegratedWindowsAuthentication) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgument("value", SR.Format(SR.HttpRequiresSingleAuthScheme, bindingElement.AuthenticationScheme)); @@ -328,8 +328,20 @@ internal async Task GetHttpClientAsync(EndpointAddress to, Uri via, else { CredentialCache credentials = new CredentialCache(); - credentials.Add(GetCredentialCacheUriPrefix(via), - AuthenticationSchemesHelper.ToString(AuthenticationScheme), credential); + Uri credentialCacheUriPrefix = GetCredentialCacheUriPrefix(via); + if (AuthenticationScheme == AuthenticationSchemes.IntegratedWindowsAuthentication) + { + credentials.Add(credentialCacheUriPrefix, AuthenticationSchemesHelper.ToString(AuthenticationSchemes.Negotiate), + credential); + credentials.Add(credentialCacheUriPrefix, AuthenticationSchemesHelper.ToString(AuthenticationSchemes.Ntlm), + credential); + } + else + { + credentials.Add(credentialCacheUriPrefix, AuthenticationSchemesHelper.ToString(AuthenticationScheme), + credential); + } + clientHandler.Credentials = credentials; } } @@ -413,7 +425,7 @@ internal ICredentials GetCredentials() } break; case AuthenticationSchemes.Ntlm: - goto case AuthenticationSchemes.Negotiate; + case AuthenticationSchemes.IntegratedWindowsAuthentication: case AuthenticationSchemes.Negotiate: if (credentials.Windows.ClientCredential.UserName != string.Empty) { @@ -451,6 +463,7 @@ private SecurityTokenProviderContainer CreateAndOpenTokenProvider(TimeSpan timeo break; case AuthenticationSchemes.Negotiate: case AuthenticationSchemes.Ntlm: + case AuthenticationSchemes.IntegratedWindowsAuthentication: tokenProvider = TransportSecurityHelpers.GetSspiTokenProvider(SecurityTokenManager, target, via, Scheme, authenticationScheme, channelParameters); break; case AuthenticationSchemes.Digest: @@ -617,10 +630,11 @@ protected virtual string OnGetConnectionGroupPrefix(SecurityTokenContainer clien internal static bool IsWindowsAuth(AuthenticationSchemes authScheme) { - Contract.Assert(authScheme.IsSingleton(), "authenticationScheme used in an Http(s)ChannelFactory must be a singleton value."); + Fx.Assert(authScheme.IsSingleton() || authScheme == AuthenticationSchemes.IntegratedWindowsAuthentication, "authenticationScheme used in an Http(s)ChannelFactory must be a singleton value."); return authScheme == AuthenticationSchemes.Negotiate || - authScheme == AuthenticationSchemes.Ntlm; + authScheme == AuthenticationSchemes.Ntlm || + authScheme == AuthenticationSchemes.IntegratedWindowsAuthentication; } private string GetConnectionGroupName(NetworkCredential credential, AuthenticationLevel authenticationLevel, @@ -1336,7 +1350,7 @@ public WebProxyFactory(Uri address, bool bypassOnLocal, AuthenticationSchemes au _address = address; _bypassOnLocal = bypassOnLocal; - if (!authenticationScheme.IsSingleton()) + if (!authenticationScheme.IsSingleton() && authenticationScheme != AuthenticationSchemes.IntegratedWindowsAuthentication) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgument(nameof(authenticationScheme), SR.Format(SR.HttpRequiresSingleAuthScheme, authenticationScheme)); @@ -1376,8 +1390,18 @@ public async Task CreateWebProxyAsync(AuthenticationLevel requestAuth } CredentialCache credentials = new CredentialCache(); - credentials.Add(_address, AuthenticationSchemesHelper.ToString(AuthenticationScheme), - credential); + if (AuthenticationScheme == AuthenticationSchemes.IntegratedWindowsAuthentication) + { + credentials.Add(_address, AuthenticationSchemesHelper.ToString(AuthenticationSchemes.Negotiate), + credential); + credentials.Add(_address, AuthenticationSchemesHelper.ToString(AuthenticationSchemes.Ntlm), + credential); + } + else + { + credentials.Add(_address, AuthenticationSchemesHelper.ToString(AuthenticationScheme), + credential); + } result.Credentials = credentials; } diff --git a/src/System.Private.ServiceModel/src/System/ServiceModel/Channels/HttpChannelHelpers.cs b/src/System.Private.ServiceModel/src/System/ServiceModel/Channels/HttpChannelHelpers.cs index 3d55b1febcd..5f1f0fa6265 100644 --- a/src/System.Private.ServiceModel/src/System/ServiceModel/Channels/HttpChannelHelpers.cs +++ b/src/System.Private.ServiceModel/src/System/ServiceModel/Channels/HttpChannelHelpers.cs @@ -96,6 +96,7 @@ private static async Task GetCredentialCoreAsync(Authenticati break; case AuthenticationSchemes.Ntlm: + case AuthenticationSchemes.IntegratedWindowsAuthentication: // IWA could use NTLM result = await TransportSecurityHelpers.GetSspiCredentialAsync(credentialProvider, impersonationLevelWrapper, authenticationLevelWrapper, timeout); if (authenticationLevelWrapper.Value == AuthenticationLevel.MutualAuthRequired) diff --git a/src/System.Private.ServiceModel/src/System/ServiceModel/Channels/HttpTransportBindingElement.cs b/src/System.Private.ServiceModel/src/System/ServiceModel/Channels/HttpTransportBindingElement.cs index 4b4e9354f16..44e603ae538 100644 --- a/src/System.Private.ServiceModel/src/System/ServiceModel/Channels/HttpTransportBindingElement.cs +++ b/src/System.Private.ServiceModel/src/System/ServiceModel/Channels/HttpTransportBindingElement.cs @@ -442,7 +442,7 @@ public override IChannelFactory BuildChannelFactory(BindingC throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgument("value", SR.Format(SR.HttpAuthSchemeCannotBeNone, AuthenticationScheme)); } - else if (!AuthenticationScheme.IsSingleton()) + else if (!AuthenticationScheme.IsSingleton() && AuthenticationScheme != AuthenticationSchemes.IntegratedWindowsAuthentication) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgument("value", SR.Format(SR.HttpRequiresSingleAuthScheme, AuthenticationScheme)); diff --git a/src/System.Private.ServiceModel/src/System/ServiceModel/HttpClientCredentialType.cs b/src/System.Private.ServiceModel/src/System/ServiceModel/HttpClientCredentialType.cs index 708c140d2c3..a15330732d5 100644 --- a/src/System.Private.ServiceModel/src/System/ServiceModel/HttpClientCredentialType.cs +++ b/src/System.Private.ServiceModel/src/System/ServiceModel/HttpClientCredentialType.cs @@ -82,6 +82,7 @@ internal static HttpClientCredentialType MapToClientCredentialType(Authenticatio result = HttpClientCredentialType.Ntlm; break; case AuthenticationSchemes.Negotiate: + case AuthenticationSchemes.IntegratedWindowsAuthentication: result = HttpClientCredentialType.Windows; break; default: diff --git a/src/System.Private.ServiceModel/src/System/ServiceModel/Security/ClientCredentialsSecurityTokenManager.cs b/src/System.Private.ServiceModel/src/System/ServiceModel/Security/ClientCredentialsSecurityTokenManager.cs index 34fa7e1de12..8c8a7df3fff 100644 --- a/src/System.Private.ServiceModel/src/System/ServiceModel/Security/ClientCredentialsSecurityTokenManager.cs +++ b/src/System.Private.ServiceModel/src/System/ServiceModel/Security/ClientCredentialsSecurityTokenManager.cs @@ -52,7 +52,7 @@ private bool IsDigestAuthenticationScheme(SecurityTokenRequirement requirement) { AuthenticationSchemes authScheme = (AuthenticationSchemes)requirement.Properties[ServiceModelSecurityTokenRequirement.HttpAuthenticationSchemeProperty]; - if (!authScheme.IsSingleton()) + if (!authScheme.IsSingleton() && authScheme != AuthenticationSchemes.IntegratedWindowsAuthentication) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgument("authScheme", string.Format(SR.HttpRequiresSingleAuthScheme, authScheme)); } diff --git a/src/System.Private.ServiceModel/tests/Scenarios/Security/TransportSecurity/Http/ClientCredentialTypeTests.4.1.0.cs b/src/System.Private.ServiceModel/tests/Scenarios/Security/TransportSecurity/Http/ClientCredentialTypeTests.4.1.0.cs index 24a6f9d7503..5f8939f5732 100644 --- a/src/System.Private.ServiceModel/tests/Scenarios/Security/TransportSecurity/Http/ClientCredentialTypeTests.4.1.0.cs +++ b/src/System.Private.ServiceModel/tests/Scenarios/Security/TransportSecurity/Http/ClientCredentialTypeTests.4.1.0.cs @@ -2,18 +2,15 @@ // 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.Collections.Generic; using System.Net; using System.ServiceModel; using System.ServiceModel.Channels; using Infrastructure.Common; - using Xunit; -public static class Http_ClientCredentialTypeTests +public class Http_ClientCredentialTypeTests : ConditionalWcfTest { [WcfFact] [OuterLoop] @@ -139,4 +136,29 @@ public static void HttpExpect100Continue_DigestAuthentication_True() ScenarioTestHelpers.CloseCommunicationObjects((ICommunicationObject)serviceProxy, factory); } } + + [WcfFact] + [Condition(nameof(Windows_Authentication_Available), nameof(Is_Windows))] + [OuterLoop] + public static void WindowsAuthentication_RoundTrips_Echo() + { + BasicHttpBinding basicHttpBinding = new BasicHttpBinding(BasicHttpSecurityMode.TransportCredentialOnly); + basicHttpBinding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Windows; + + ScenarioTestHelpers.RunBasicEchoTest(basicHttpBinding, Endpoints.Http_WindowsAuth_Address, "BasicHttpBinding with Windows authentication", null); + } + + [WcfFact] + [Condition(nameof(Windows_Authentication_Available), nameof(Is_Windows))] + [OuterLoop] + public static void IntegratedWindowsAuthentication_Negotiate_RoundTrips_Echo() + { + BasicHttpBinding basicHttpBinding = new BasicHttpBinding(BasicHttpSecurityMode.TransportCredentialOnly); + basicHttpBinding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Windows; + var binding = new CustomBinding(basicHttpBinding); + var htbe = binding.Elements.Find(); + htbe.AuthenticationScheme = System.Net.AuthenticationSchemes.IntegratedWindowsAuthentication; + + ScenarioTestHelpers.RunBasicEchoTest(binding, Endpoints.Http_WindowsAuth_Address, "BasicHttpBinding with IntegratedWindowsAuthentication authentication and Negotiate endpoint", null); + } } diff --git a/src/System.Private.ServiceModel/tests/Scenarios/Security/TransportSecurity/Https/ClientCredentialTypeTests.4.1.0.cs b/src/System.Private.ServiceModel/tests/Scenarios/Security/TransportSecurity/Https/ClientCredentialTypeTests.4.1.0.cs index 4e5bf687f94..5a671f7aa52 100644 --- a/src/System.Private.ServiceModel/tests/Scenarios/Security/TransportSecurity/Https/ClientCredentialTypeTests.4.1.0.cs +++ b/src/System.Private.ServiceModel/tests/Scenarios/Security/TransportSecurity/Https/ClientCredentialTypeTests.4.1.0.cs @@ -238,13 +238,16 @@ public static void NtlmAuthentication_RoundTrips_Echo() } [WcfFact] - [Condition(nameof(Windows_Authentication_Available), nameof(Is_Windows))] + [Condition(nameof(NTLM_Available), nameof(Root_Certificate_Installed))] [OuterLoop] - public static void WindowsAuthentication_RoundTrips_Echo() + public static void IntegratedWindowsAuthentication_Ntlm_RoundTrips_Echo() { - BasicHttpBinding basicHttpBinding = new BasicHttpBinding(BasicHttpSecurityMode.TransportCredentialOnly); + BasicHttpBinding basicHttpBinding = new BasicHttpBinding(BasicHttpSecurityMode.Transport); basicHttpBinding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Windows; + var binding = new CustomBinding(basicHttpBinding); + var htbe = binding.Elements.Find(); + htbe.AuthenticationScheme = System.Net.AuthenticationSchemes.IntegratedWindowsAuthentication; - ScenarioTestHelpers.RunBasicEchoTest(basicHttpBinding, Endpoints.Http_WindowsAuth_Address, "BasicHttpBinding with Windows authentication", null); + ScenarioTestHelpers.RunBasicEchoTest(binding, Endpoints.Https_NtlmAuth_Address, "BasicHttpBinding with IntegratedWindowsAuthentication authentication and Ntlm endpoint", null); } }