Handle nt auth with Connection: close on initial challenge (dotnet/corefx#31527)
authorGeoff Kizer <geoffrek@microsoft.com>
Fri, 3 Aug 2018 17:55:11 +0000 (10:55 -0700)
committerDavid Shulman <david.shulman@microsoft.com>
Fri, 3 Aug 2018 17:55:11 +0000 (10:55 -0700)
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

src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/AuthenticationHelper.NtAuth.cs
src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Connection.cs
src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnection.cs
src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnectionBase.cs
src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnectionPool.cs

index 2ce48e0..2307cfc 100644 (file)
@@ -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<HttpResponseMessage> InnerSendAsync(HttpRequestMessage request, bool isProxyAuth, HttpConnection connection, CancellationToken cancellationToken)
+        private static Task<HttpResponseMessage> 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<HttpResponseMessage> SendWithNtAuthAsync(HttpRequestMessage request, Uri authUri, ICredentials credentials, bool isProxyAuth, HttpConnection connection, CancellationToken cancellationToken)
+        private static async Task<HttpResponseMessage> 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<HttpResponseMessage> SendWithNtProxyAuthAsync(HttpRequestMessage request, Uri proxyUri, ICredentials proxyCredentials, HttpConnection connection, CancellationToken cancellationToken)
+        public static Task<HttpResponseMessage> 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<HttpResponseMessage> SendWithNtConnectionAuthAsync(HttpRequestMessage request, ICredentials credentials, HttpConnection connection, CancellationToken cancellationToken)
+        public static Task<HttpResponseMessage> 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);
         }
     }
 }
index 2d38e48..728cba9 100644 (file)
@@ -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<HttpResponseMessage> SendAsync(HttpRequestMessage request, bool doRequestAuth, CancellationToken cancellationToken)
+        public sealed override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
         {
             Http2Stream http2Stream = new Http2Stream(this);
             try
index a319ea1..c184f53 100644 (file)
@@ -682,39 +682,11 @@ namespace System.Net.Http
             }
         }
 
-        public Task<HttpResponseMessage> SendWithNtProxyAuthAsync(HttpRequestMessage request, CancellationToken cancellationToken)
+        public sealed override Task<HttpResponseMessage> 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<HttpResponseMessage> 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<HttpResponseMessage> 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);
 
index 08cb510..7840bbf 100644 (file)
@@ -9,6 +9,6 @@ namespace System.Net.Http
 {
     internal abstract class HttpConnectionBase
     {
-        public abstract Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, bool doRequestAuth, CancellationToken cancellationToken);
+        public abstract Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken);
     }
 }
index 8e10f9e..ee2e9bc 100644 (file)
@@ -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<HttpResponseMessage> 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<HttpResponseMessage> 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<HttpResponseMessage> 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();
+            }
+        }
+
+
         /// <summary>
         /// Decrements the number of connections associated with the pool.
         /// If there are waiters on the pool due to having reached the maximum,