From 1b53449ad265d31e5c8fb88f7f572c94ec5ddaa4 Mon Sep 17 00:00:00 2001 From: Geoff Kizer Date: Fri, 3 Aug 2018 10:55:11 -0700 Subject: [PATCH] Handle nt auth with Connection: close on initial challenge (dotnet/corefx#31527) When we receive an initial NT auth challenge that has Connection: close set on the response, we need to proceed with authentication on a new connection. Fixes dotnet/corefx#30327 Commit migrated from https://github.com/dotnet/corefx/commit/eedddac1bd10a7c8629a9db7f46fc8afc7385d63 --- .../AuthenticationHelper.NtAuth.cs | 76 +++++++++++++++------- .../Net/Http/SocketsHttpHandler/Http2Connection.cs | 2 +- .../Net/Http/SocketsHttpHandler/HttpConnection.cs | 34 +--------- .../Http/SocketsHttpHandler/HttpConnectionBase.cs | 2 +- .../Http/SocketsHttpHandler/HttpConnectionPool.cs | 49 +++++++++++++- 5 files changed, 105 insertions(+), 58 deletions(-) diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/AuthenticationHelper.NtAuth.cs b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/AuthenticationHelper.NtAuth.cs index 2ce48e0..2307cfc 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/AuthenticationHelper.NtAuth.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/AuthenticationHelper.NtAuth.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System.Collections.Generic; +using System.Diagnostics; using System.Net.Http.Headers; using System.Threading; using System.Threading.Tasks; @@ -12,11 +13,11 @@ namespace System.Net.Http { internal partial class AuthenticationHelper { - private static Task InnerSendAsync(HttpRequestMessage request, bool isProxyAuth, HttpConnection connection, CancellationToken cancellationToken) + private static Task InnerSendAsync(HttpRequestMessage request, bool isProxyAuth, HttpConnectionPool pool, HttpConnection connection, CancellationToken cancellationToken) { return isProxyAuth ? connection.SendAsyncCore(request, cancellationToken) : - connection.SendWithNtProxyAuthAsync(request, cancellationToken); + pool.SendWithNtProxyAuthAsync(connection, request, cancellationToken); } private static bool ProxySupportsConnectionAuth(HttpResponseMessage response) @@ -37,9 +38,9 @@ namespace System.Net.Http return false; } - private static async Task SendWithNtAuthAsync(HttpRequestMessage request, Uri authUri, ICredentials credentials, bool isProxyAuth, HttpConnection connection, CancellationToken cancellationToken) + private static async Task SendWithNtAuthAsync(HttpRequestMessage request, Uri authUri, ICredentials credentials, bool isProxyAuth, HttpConnection connection, HttpConnectionPool connectionPool, CancellationToken cancellationToken) { - HttpResponseMessage response = await InnerSendAsync(request, isProxyAuth, connection, cancellationToken).ConfigureAwait(false); + HttpResponseMessage response = await InnerSendAsync(request, isProxyAuth, connectionPool, connection, cancellationToken).ConfigureAwait(false); if (!isProxyAuth && connection.Kind == HttpConnectionKind.Proxy && !ProxySupportsConnectionAuth(response)) { // Proxy didn't indicate that it supports connection-based auth, so we can't proceed. @@ -55,36 +56,65 @@ namespace System.Net.Http if (challenge.AuthenticationType == AuthenticationType.Negotiate || challenge.AuthenticationType == AuthenticationType.Ntlm) { - string challengeData = challenge.ChallengeData; - - string spn = "HTTP/" + authUri.IdnHost; - ChannelBinding channelBinding = connection.TransportContext?.GetChannelBinding(ChannelBindingKind.Endpoint); - NTAuthentication authContext = new NTAuthentication(isServer:false, challenge.SchemeName, challenge.Credential, spn, ContextFlagsPal.Connection, channelBinding); + bool isNewConnection = false; try { - while (true) + if (response.Headers.ConnectionClose.GetValueOrDefault()) { - string challengeResponse = authContext.GetOutgoingBlob(challengeData); - if (challengeResponse == null) + // Server is closing the connection and asking us to authenticate on a new connection. + (connection, response) = await connectionPool.CreateHttp11ConnectionAsync(request, cancellationToken).ConfigureAwait(false); + if (response != null) { - // Response indicated denial even after login, so stop processing and return current response. - break; + return response; } + connectionPool.IncrementConnectionCount(); + connection.Acquire(); + isNewConnection = true; + } + else + { await connection.DrainResponseAsync(response).ConfigureAwait(false); + } - SetRequestAuthenticationHeaderValue(request, new AuthenticationHeaderValue(challenge.SchemeName, challengeResponse), isProxyAuth); + string challengeData = challenge.ChallengeData; - response = await InnerSendAsync(request, isProxyAuth, connection, cancellationToken).ConfigureAwait(false); - if (authContext.IsCompleted || !TryGetRepeatedChallenge(response, challenge.SchemeName, isProxyAuth, out challengeData)) + string spn = "HTTP/" + authUri.IdnHost; + ChannelBinding channelBinding = connection.TransportContext?.GetChannelBinding(ChannelBindingKind.Endpoint); + NTAuthentication authContext = new NTAuthentication(isServer:false, challenge.SchemeName, challenge.Credential, spn, ContextFlagsPal.Connection, channelBinding); + try + { + while (true) { - break; + string challengeResponse = authContext.GetOutgoingBlob(challengeData); + if (challengeResponse == null) + { + // Response indicated denial even after login, so stop processing and return current response. + break; + } + + SetRequestAuthenticationHeaderValue(request, new AuthenticationHeaderValue(challenge.SchemeName, challengeResponse), isProxyAuth); + + response = await InnerSendAsync(request, isProxyAuth, connectionPool, connection, cancellationToken).ConfigureAwait(false); + if (authContext.IsCompleted || !TryGetRepeatedChallenge(response, challenge.SchemeName, isProxyAuth, out challengeData)) + { + break; + } + + await connection.DrainResponseAsync(response).ConfigureAwait(false); } } + finally + { + authContext.CloseContext(); + } } finally { - authContext.CloseContext(); + if (isNewConnection) + { + connection.Release(); + } } } } @@ -92,14 +122,14 @@ namespace System.Net.Http return response; } - public static Task SendWithNtProxyAuthAsync(HttpRequestMessage request, Uri proxyUri, ICredentials proxyCredentials, HttpConnection connection, CancellationToken cancellationToken) + public static Task SendWithNtProxyAuthAsync(HttpRequestMessage request, Uri proxyUri, ICredentials proxyCredentials, HttpConnection connection, HttpConnectionPool connectionPool, CancellationToken cancellationToken) { - return SendWithNtAuthAsync(request, proxyUri, proxyCredentials, isProxyAuth:true, connection, cancellationToken); + return SendWithNtAuthAsync(request, proxyUri, proxyCredentials, isProxyAuth:true, connection, connectionPool, cancellationToken); } - public static Task SendWithNtConnectionAuthAsync(HttpRequestMessage request, ICredentials credentials, HttpConnection connection, CancellationToken cancellationToken) + public static Task SendWithNtConnectionAuthAsync(HttpRequestMessage request, ICredentials credentials, HttpConnection connection, HttpConnectionPool connectionPool, CancellationToken cancellationToken) { - return SendWithNtAuthAsync(request, request.RequestUri, credentials, isProxyAuth:false, connection, cancellationToken); + return SendWithNtAuthAsync(request, request.RequestUri, credentials, isProxyAuth:false, connection, connectionPool, cancellationToken); } } } diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Connection.cs b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Connection.cs index 2d38e48..728cba9 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Connection.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Connection.cs @@ -673,7 +673,7 @@ namespace System.Net.Http // Note that this is safe to be called concurrently by multiple threads. - public sealed override async Task SendAsync(HttpRequestMessage request, bool doRequestAuth, CancellationToken cancellationToken) + public sealed override async Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { Http2Stream http2Stream = new Http2Stream(this); try diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnection.cs b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnection.cs index a319ea1..c184f53 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnection.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnection.cs @@ -682,39 +682,11 @@ namespace System.Net.Http } } - public Task SendWithNtProxyAuthAsync(HttpRequestMessage request, CancellationToken cancellationToken) + public sealed override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { - if (_pool.AnyProxyKind && _pool.ProxyCredentials != null) - { - return AuthenticationHelper.SendWithNtProxyAuthAsync(request, _pool.ProxyUri, _pool.ProxyCredentials, this, cancellationToken); - } - return SendAsyncCore(request, cancellationToken); } - private Task SendAsyncInternal(HttpRequestMessage request, bool doRequestAuth, CancellationToken cancellationToken) - { - if (doRequestAuth && _pool.Settings._credentials != null) - { - return AuthenticationHelper.SendWithNtConnectionAuthAsync(request, _pool.Settings._credentials, this, cancellationToken); - } - - return SendWithNtProxyAuthAsync(request, cancellationToken); - } - - public sealed override async Task SendAsync(HttpRequestMessage request, bool doRequestAuth, CancellationToken cancellationToken) - { - Acquire(); - try - { - return await SendAsyncInternal(request, doRequestAuth, cancellationToken).ConfigureAwait(false); - } - finally - { - Release(); - } - } - private HttpContentWriteStream CreateRequestContentStream(HttpRequestMessage request) { bool requestTransferEncodingChunked = request.HasHeaders && request.Headers.TransferEncodingChunked == true; @@ -1471,7 +1443,7 @@ namespace System.Net.Http } } - private void Acquire() + internal void Acquire() { Debug.Assert(_currentRequest == null); Debug.Assert(!_inUse); @@ -1479,7 +1451,7 @@ namespace System.Net.Http _inUse = true; } - private void Release() + internal void Release() { Debug.Assert(_inUse); diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnectionBase.cs b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnectionBase.cs index 08cb510..7840bbf 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnectionBase.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnectionBase.cs @@ -9,6 +9,6 @@ namespace System.Net.Http { internal abstract class HttpConnectionBase { - public abstract Task SendAsync(HttpRequestMessage request, bool doRequestAuth, CancellationToken cancellationToken); + public abstract Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken); } } diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnectionPool.cs b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnectionPool.cs index 8e10f9e..ee2e9bc 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnectionPool.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnectionPool.cs @@ -464,7 +464,14 @@ namespace System.Net.Http try { - return await connection.SendAsync(request, doRequestAuth, cancellationToken).ConfigureAwait(false); + if (connection is HttpConnection) + { + return await SendWithNtConnectionAuthAsync((HttpConnection)connection, request, doRequestAuth, cancellationToken).ConfigureAwait(false); + } + else + { + return await connection.SendAsync(request, cancellationToken).ConfigureAwait(false); + } } catch (HttpRequestException e) when (!isNewConnection && e.AllowRetry) { @@ -478,6 +485,35 @@ namespace System.Net.Http } } + public async Task SendWithNtConnectionAuthAsync(HttpConnection connection, HttpRequestMessage request, bool doRequestAuth, CancellationToken cancellationToken) + { + connection.Acquire(); + try + { + if (doRequestAuth && Settings._credentials != null) + { + return await AuthenticationHelper.SendWithNtConnectionAuthAsync(request, Settings._credentials, connection, this, cancellationToken).ConfigureAwait(false); + } + + return await SendWithNtProxyAuthAsync(connection, request, cancellationToken).ConfigureAwait(false); + } + finally + { + connection.Release(); + } + } + + public Task SendWithNtProxyAuthAsync(HttpConnection connection, HttpRequestMessage request, CancellationToken cancellationToken) + { + if (AnyProxyKind && ProxyCredentials != null) + { + return AuthenticationHelper.SendWithNtProxyAuthAsync(request, ProxyUri, ProxyCredentials, connection, this, cancellationToken); + } + + return connection.SendAsync(request, cancellationToken); + } + + public Task SendWithProxyAuthAsync(HttpRequestMessage request, bool doRequestAuth, CancellationToken cancellationToken) { if ((_kind == HttpConnectionKind.Proxy || _kind == HttpConnectionKind.ProxyConnect) && @@ -556,7 +592,7 @@ namespace System.Net.Http } } - private async ValueTask<(HttpConnection, HttpResponseMessage)> CreateHttp11ConnectionAsync(HttpRequestMessage request, CancellationToken cancellationToken) + internal async ValueTask<(HttpConnection, HttpResponseMessage)> CreateHttp11ConnectionAsync(HttpRequestMessage request, CancellationToken cancellationToken) { (Socket socket, Stream stream, TransportContext transportContext, HttpResponseMessage failureResponse) = await ConnectAsync(request, false, cancellationToken).ConfigureAwait(false); @@ -706,6 +742,15 @@ namespace System.Net.Http _associatedConnectionCount++; } + internal void IncrementConnectionCount() + { + lock (SyncObj) + { + IncrementConnectionCountNoLock(); + } + } + + /// /// Decrements the number of connections associated with the pool. /// If there are waiters on the pool due to having reached the maximum, -- 2.7.4