Skip to content
This repository was archived by the owner on Jan 23, 2023. It is now read-only.

Commit eedddac

Browse files
geoffkizerdavidsh
authored andcommitted
Handle nt auth with Connection: close on initial challenge (#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 #30327
1 parent 408fe4f commit eedddac

File tree

5 files changed

+105
-58
lines changed

5 files changed

+105
-58
lines changed

src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/AuthenticationHelper.NtAuth.cs

Lines changed: 53 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
// See the LICENSE file in the project root for more information.
44

55
using System.Collections.Generic;
6+
using System.Diagnostics;
67
using System.Net.Http.Headers;
78
using System.Threading;
89
using System.Threading.Tasks;
@@ -12,11 +13,11 @@ namespace System.Net.Http
1213
{
1314
internal partial class AuthenticationHelper
1415
{
15-
private static Task<HttpResponseMessage> InnerSendAsync(HttpRequestMessage request, bool isProxyAuth, HttpConnection connection, CancellationToken cancellationToken)
16+
private static Task<HttpResponseMessage> InnerSendAsync(HttpRequestMessage request, bool isProxyAuth, HttpConnectionPool pool, HttpConnection connection, CancellationToken cancellationToken)
1617
{
1718
return isProxyAuth ?
1819
connection.SendAsyncCore(request, cancellationToken) :
19-
connection.SendWithNtProxyAuthAsync(request, cancellationToken);
20+
pool.SendWithNtProxyAuthAsync(connection, request, cancellationToken);
2021
}
2122

2223
private static bool ProxySupportsConnectionAuth(HttpResponseMessage response)
@@ -37,9 +38,9 @@ private static bool ProxySupportsConnectionAuth(HttpResponseMessage response)
3738
return false;
3839
}
3940

40-
private static async Task<HttpResponseMessage> SendWithNtAuthAsync(HttpRequestMessage request, Uri authUri, ICredentials credentials, bool isProxyAuth, HttpConnection connection, CancellationToken cancellationToken)
41+
private static async Task<HttpResponseMessage> SendWithNtAuthAsync(HttpRequestMessage request, Uri authUri, ICredentials credentials, bool isProxyAuth, HttpConnection connection, HttpConnectionPool connectionPool, CancellationToken cancellationToken)
4142
{
42-
HttpResponseMessage response = await InnerSendAsync(request, isProxyAuth, connection, cancellationToken).ConfigureAwait(false);
43+
HttpResponseMessage response = await InnerSendAsync(request, isProxyAuth, connectionPool, connection, cancellationToken).ConfigureAwait(false);
4344
if (!isProxyAuth && connection.Kind == HttpConnectionKind.Proxy && !ProxySupportsConnectionAuth(response))
4445
{
4546
// Proxy didn't indicate that it supports connection-based auth, so we can't proceed.
@@ -55,51 +56,80 @@ private static async Task<HttpResponseMessage> SendWithNtAuthAsync(HttpRequestMe
5556
if (challenge.AuthenticationType == AuthenticationType.Negotiate ||
5657
challenge.AuthenticationType == AuthenticationType.Ntlm)
5758
{
58-
string challengeData = challenge.ChallengeData;
59-
60-
string spn = "HTTP/" + authUri.IdnHost;
61-
ChannelBinding channelBinding = connection.TransportContext?.GetChannelBinding(ChannelBindingKind.Endpoint);
62-
NTAuthentication authContext = new NTAuthentication(isServer:false, challenge.SchemeName, challenge.Credential, spn, ContextFlagsPal.Connection, channelBinding);
59+
bool isNewConnection = false;
6360
try
6461
{
65-
while (true)
62+
if (response.Headers.ConnectionClose.GetValueOrDefault())
6663
{
67-
string challengeResponse = authContext.GetOutgoingBlob(challengeData);
68-
if (challengeResponse == null)
64+
// Server is closing the connection and asking us to authenticate on a new connection.
65+
(connection, response) = await connectionPool.CreateHttp11ConnectionAsync(request, cancellationToken).ConfigureAwait(false);
66+
if (response != null)
6967
{
70-
// Response indicated denial even after login, so stop processing and return current response.
71-
break;
68+
return response;
7269
}
7370

71+
connectionPool.IncrementConnectionCount();
72+
connection.Acquire();
73+
isNewConnection = true;
74+
}
75+
else
76+
{
7477
await connection.DrainResponseAsync(response).ConfigureAwait(false);
78+
}
7579

76-
SetRequestAuthenticationHeaderValue(request, new AuthenticationHeaderValue(challenge.SchemeName, challengeResponse), isProxyAuth);
80+
string challengeData = challenge.ChallengeData;
7781

78-
response = await InnerSendAsync(request, isProxyAuth, connection, cancellationToken).ConfigureAwait(false);
79-
if (authContext.IsCompleted || !TryGetRepeatedChallenge(response, challenge.SchemeName, isProxyAuth, out challengeData))
82+
string spn = "HTTP/" + authUri.IdnHost;
83+
ChannelBinding channelBinding = connection.TransportContext?.GetChannelBinding(ChannelBindingKind.Endpoint);
84+
NTAuthentication authContext = new NTAuthentication(isServer:false, challenge.SchemeName, challenge.Credential, spn, ContextFlagsPal.Connection, channelBinding);
85+
try
86+
{
87+
while (true)
8088
{
81-
break;
89+
string challengeResponse = authContext.GetOutgoingBlob(challengeData);
90+
if (challengeResponse == null)
91+
{
92+
// Response indicated denial even after login, so stop processing and return current response.
93+
break;
94+
}
95+
96+
SetRequestAuthenticationHeaderValue(request, new AuthenticationHeaderValue(challenge.SchemeName, challengeResponse), isProxyAuth);
97+
98+
response = await InnerSendAsync(request, isProxyAuth, connectionPool, connection, cancellationToken).ConfigureAwait(false);
99+
if (authContext.IsCompleted || !TryGetRepeatedChallenge(response, challenge.SchemeName, isProxyAuth, out challengeData))
100+
{
101+
break;
102+
}
103+
104+
await connection.DrainResponseAsync(response).ConfigureAwait(false);
82105
}
83106
}
107+
finally
108+
{
109+
authContext.CloseContext();
110+
}
84111
}
85112
finally
86113
{
87-
authContext.CloseContext();
114+
if (isNewConnection)
115+
{
116+
connection.Release();
117+
}
88118
}
89119
}
90120
}
91121

92122
return response;
93123
}
94124

95-
public static Task<HttpResponseMessage> SendWithNtProxyAuthAsync(HttpRequestMessage request, Uri proxyUri, ICredentials proxyCredentials, HttpConnection connection, CancellationToken cancellationToken)
125+
public static Task<HttpResponseMessage> SendWithNtProxyAuthAsync(HttpRequestMessage request, Uri proxyUri, ICredentials proxyCredentials, HttpConnection connection, HttpConnectionPool connectionPool, CancellationToken cancellationToken)
96126
{
97-
return SendWithNtAuthAsync(request, proxyUri, proxyCredentials, isProxyAuth:true, connection, cancellationToken);
127+
return SendWithNtAuthAsync(request, proxyUri, proxyCredentials, isProxyAuth:true, connection, connectionPool, cancellationToken);
98128
}
99129

100-
public static Task<HttpResponseMessage> SendWithNtConnectionAuthAsync(HttpRequestMessage request, ICredentials credentials, HttpConnection connection, CancellationToken cancellationToken)
130+
public static Task<HttpResponseMessage> SendWithNtConnectionAuthAsync(HttpRequestMessage request, ICredentials credentials, HttpConnection connection, HttpConnectionPool connectionPool, CancellationToken cancellationToken)
101131
{
102-
return SendWithNtAuthAsync(request, request.RequestUri, credentials, isProxyAuth:false, connection, cancellationToken);
132+
return SendWithNtAuthAsync(request, request.RequestUri, credentials, isProxyAuth:false, connection, connectionPool, cancellationToken);
103133
}
104134
}
105135
}

src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Connection.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -673,7 +673,7 @@ private async Task SendFramesAsync(Memory<byte> frame)
673673

674674
// Note that this is safe to be called concurrently by multiple threads.
675675

676-
public sealed override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, bool doRequestAuth, CancellationToken cancellationToken)
676+
public sealed override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
677677
{
678678
Http2Stream http2Stream = new Http2Stream(this);
679679
try

src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnection.cs

Lines changed: 3 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -682,39 +682,11 @@ public async Task<HttpResponseMessage> SendAsyncCore(HttpRequestMessage request,
682682
}
683683
}
684684

685-
public Task<HttpResponseMessage> SendWithNtProxyAuthAsync(HttpRequestMessage request, CancellationToken cancellationToken)
685+
public sealed override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
686686
{
687-
if (_pool.AnyProxyKind && _pool.ProxyCredentials != null)
688-
{
689-
return AuthenticationHelper.SendWithNtProxyAuthAsync(request, _pool.ProxyUri, _pool.ProxyCredentials, this, cancellationToken);
690-
}
691-
692687
return SendAsyncCore(request, cancellationToken);
693688
}
694689

695-
private Task<HttpResponseMessage> SendAsyncInternal(HttpRequestMessage request, bool doRequestAuth, CancellationToken cancellationToken)
696-
{
697-
if (doRequestAuth && _pool.Settings._credentials != null)
698-
{
699-
return AuthenticationHelper.SendWithNtConnectionAuthAsync(request, _pool.Settings._credentials, this, cancellationToken);
700-
}
701-
702-
return SendWithNtProxyAuthAsync(request, cancellationToken);
703-
}
704-
705-
public sealed override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, bool doRequestAuth, CancellationToken cancellationToken)
706-
{
707-
Acquire();
708-
try
709-
{
710-
return await SendAsyncInternal(request, doRequestAuth, cancellationToken).ConfigureAwait(false);
711-
}
712-
finally
713-
{
714-
Release();
715-
}
716-
}
717-
718690
private HttpContentWriteStream CreateRequestContentStream(HttpRequestMessage request)
719691
{
720692
bool requestTransferEncodingChunked = request.HasHeaders && request.Headers.TransferEncodingChunked == true;
@@ -1471,15 +1443,15 @@ private async Task CopyToExactLengthAsync(Stream destination, ulong length, Canc
14711443
}
14721444
}
14731445

1474-
private void Acquire()
1446+
internal void Acquire()
14751447
{
14761448
Debug.Assert(_currentRequest == null);
14771449
Debug.Assert(!_inUse);
14781450

14791451
_inUse = true;
14801452
}
14811453

1482-
private void Release()
1454+
internal void Release()
14831455
{
14841456
Debug.Assert(_inUse);
14851457

src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnectionBase.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,6 @@ namespace System.Net.Http
99
{
1010
internal abstract class HttpConnectionBase
1111
{
12-
public abstract Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, bool doRequestAuth, CancellationToken cancellationToken);
12+
public abstract Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken);
1313
}
1414
}

src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnectionPool.cs

Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -464,7 +464,14 @@ public async Task<HttpResponseMessage> SendWithRetryAsync(HttpRequestMessage req
464464

465465
try
466466
{
467-
return await connection.SendAsync(request, doRequestAuth, cancellationToken).ConfigureAwait(false);
467+
if (connection is HttpConnection)
468+
{
469+
return await SendWithNtConnectionAuthAsync((HttpConnection)connection, request, doRequestAuth, cancellationToken).ConfigureAwait(false);
470+
}
471+
else
472+
{
473+
return await connection.SendAsync(request, cancellationToken).ConfigureAwait(false);
474+
}
468475
}
469476
catch (HttpRequestException e) when (!isNewConnection && e.AllowRetry)
470477
{
@@ -478,6 +485,35 @@ public async Task<HttpResponseMessage> SendWithRetryAsync(HttpRequestMessage req
478485
}
479486
}
480487

488+
public async Task<HttpResponseMessage> SendWithNtConnectionAuthAsync(HttpConnection connection, HttpRequestMessage request, bool doRequestAuth, CancellationToken cancellationToken)
489+
{
490+
connection.Acquire();
491+
try
492+
{
493+
if (doRequestAuth && Settings._credentials != null)
494+
{
495+
return await AuthenticationHelper.SendWithNtConnectionAuthAsync(request, Settings._credentials, connection, this, cancellationToken).ConfigureAwait(false);
496+
}
497+
498+
return await SendWithNtProxyAuthAsync(connection, request, cancellationToken).ConfigureAwait(false);
499+
}
500+
finally
501+
{
502+
connection.Release();
503+
}
504+
}
505+
506+
public Task<HttpResponseMessage> SendWithNtProxyAuthAsync(HttpConnection connection, HttpRequestMessage request, CancellationToken cancellationToken)
507+
{
508+
if (AnyProxyKind && ProxyCredentials != null)
509+
{
510+
return AuthenticationHelper.SendWithNtProxyAuthAsync(request, ProxyUri, ProxyCredentials, connection, this, cancellationToken);
511+
}
512+
513+
return connection.SendAsync(request, cancellationToken);
514+
}
515+
516+
481517
public Task<HttpResponseMessage> SendWithProxyAuthAsync(HttpRequestMessage request, bool doRequestAuth, CancellationToken cancellationToken)
482518
{
483519
if ((_kind == HttpConnectionKind.Proxy || _kind == HttpConnectionKind.ProxyConnect) &&
@@ -556,7 +592,7 @@ public Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, bool doRe
556592
}
557593
}
558594

559-
private async ValueTask<(HttpConnection, HttpResponseMessage)> CreateHttp11ConnectionAsync(HttpRequestMessage request, CancellationToken cancellationToken)
595+
internal async ValueTask<(HttpConnection, HttpResponseMessage)> CreateHttp11ConnectionAsync(HttpRequestMessage request, CancellationToken cancellationToken)
560596
{
561597
(Socket socket, Stream stream, TransportContext transportContext, HttpResponseMessage failureResponse) =
562598
await ConnectAsync(request, false, cancellationToken).ConfigureAwait(false);
@@ -706,6 +742,15 @@ private void IncrementConnectionCountNoLock()
706742
_associatedConnectionCount++;
707743
}
708744

745+
internal void IncrementConnectionCount()
746+
{
747+
lock (SyncObj)
748+
{
749+
IncrementConnectionCountNoLock();
750+
}
751+
}
752+
753+
709754
/// <summary>
710755
/// Decrements the number of connections associated with the pool.
711756
/// If there are waiters on the pool due to having reached the maximum,

0 commit comments

Comments
 (0)