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

Commit 036608d

Browse files
geoffkizerstephentoub
authored andcommitted
fix NTLM auth and add some manually enabled tests (#27958)
1 parent cf8e9e8 commit 036608d

File tree

2 files changed

+109
-66
lines changed

2 files changed

+109
-66
lines changed

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

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -57,17 +57,13 @@ private static async Task<HttpResponseMessage> SendWithNtAuthAsync(HttpRequestMe
5757
while (true)
5858
{
5959
string challengeResponse = authContext.GetOutgoingBlob(challengeData);
60-
if (authContext.IsCompleted)
61-
{
62-
break;
63-
}
6460

6561
await connection.DrainResponseAsync(response).ConfigureAwait(false);
6662

6763
SetRequestAuthenticationHeaderValue(request, new AuthenticationHeaderValue(challenge.SchemeName, challengeResponse), isProxyAuth);
6864

6965
response = await InnerSendAsync(request, isProxyAuth, connection, cancellationToken).ConfigureAwait(false);
70-
if (!TryGetRepeatedChallenge(response, challenge.SchemeName, isProxyAuth, out challengeData))
66+
if (authContext.IsCompleted || !TryGetRepeatedChallenge(response, challenge.SchemeName, isProxyAuth, out challengeData))
7167
{
7268
break;
7369
}

src/System.Net.Http/tests/FunctionalTests/DefaultCredentialsTest.cs

Lines changed: 108 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// The .NET Foundation licenses this file to you under the MIT license.
33
// See the LICENSE file in the project root for more information.
44

5+
using System.Collections.Generic;
56
using System.Security.Principal;
67
using System.Threading.Tasks;
78

@@ -17,80 +18,74 @@ namespace System.Net.Http.Functional.Tests
1718
[PlatformSpecific(TestPlatforms.Windows)]
1819
public class DefaultCredentialsTest : HttpClientTestBase
1920
{
20-
private static string DomainJoinedTestServer => Configuration.Http.DomainJoinedHttpHost;
21-
private static bool DomainJoinedTestsEnabled => !string.IsNullOrEmpty(DomainJoinedTestServer);
22-
private static bool DomainProxyTestsEnabled => (!string.IsNullOrEmpty(Configuration.Http.DomainJoinedProxyHost)) && DomainJoinedTestsEnabled;
21+
private static bool DomainJoinedTestsEnabled => !string.IsNullOrEmpty(Configuration.Http.DomainJoinedHttpHost);
22+
23+
private static bool DomainProxyTestsEnabled => !string.IsNullOrEmpty(Configuration.Http.DomainJoinedProxyHost);
24+
25+
// Enable this to test against local HttpListener over loopback
26+
// Note this doesn't work as expected with WinHttpHandler, because WinHttpHandler will always authenticate the
27+
// current user against a loopback server using NTLM or Negotiate.
28+
private static bool LocalHttpListenerTestsEnabled = false;
29+
30+
public static bool ServerAuthenticationTestsEnabled => (LocalHttpListenerTestsEnabled || DomainJoinedTestsEnabled);
2331

2432
private static string s_specificUserName = Configuration.Security.ActiveDirectoryUserName;
2533
private static string s_specificPassword = Configuration.Security.ActiveDirectoryUserPassword;
2634
private static string s_specificDomain = Configuration.Security.ActiveDirectoryName;
27-
private static Uri s_authenticatedServer =
28-
new Uri($"http://{DomainJoinedTestServer}/test/auth/negotiate/showidentity.ashx");
29-
30-
// This test endpoint offers multiple schemes, Basic and NTLM, in that specific order. This endpoint
31-
// helps test that the client will use the stronger of the server proposed auth schemes and
32-
// not the first auth scheme.
33-
private static Uri s_multipleSchemesAuthenticatedServer =
34-
new Uri($"http://{DomainJoinedTestServer}/test/auth/multipleschemes/showidentity.ashx");
35-
36-
private readonly ITestOutputHelper _output;
3735
private readonly NetworkCredential _specificCredential =
3836
new NetworkCredential(s_specificUserName, s_specificPassword, s_specificDomain);
37+
private static Uri s_authenticatedServer = DomainJoinedTestsEnabled ?
38+
new Uri($"http://{Configuration.Http.DomainJoinedHttpHost}/test/auth/negotiate/showidentity.ashx") : null;
39+
40+
private readonly ITestOutputHelper _output;
3941

4042
public DefaultCredentialsTest(ITestOutputHelper output)
4143
{
4244
_output = output;
43-
_output.WriteLine(s_authenticatedServer.ToString());
4445
}
4546

4647
[OuterLoop] // TODO: Issue #11345
47-
[ActiveIssue(10041)]
48-
[ConditionalTheory(nameof(DomainJoinedTestsEnabled))]
49-
[InlineData(false)]
50-
[InlineData(true)]
51-
public async Task UseDefaultCredentials_DefaultValue_Unauthorized(bool useProxy)
48+
[ConditionalTheory(nameof(ServerAuthenticationTestsEnabled))]
49+
[MemberData(nameof(AuthenticatedServers))]
50+
public async Task UseDefaultCredentials_DefaultValue_Unauthorized(string uri, bool useProxy)
5251
{
5352
HttpClientHandler handler = CreateHttpClientHandler();
5453
handler.UseProxy = useProxy;
5554

5655
using (var client = new HttpClient(handler))
57-
using (HttpResponseMessage response = await client.GetAsync(s_authenticatedServer))
56+
using (HttpResponseMessage response = await client.GetAsync(uri))
5857
{
5958
Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode);
6059
}
6160
}
6261

6362
[OuterLoop] // TODO: Issue #11345
64-
[ActiveIssue(10041)]
65-
[ConditionalTheory(nameof(DomainJoinedTestsEnabled))]
66-
[InlineData(false)]
67-
[InlineData(true)]
68-
public async Task UseDefaultCredentials_SetFalse_Unauthorized(bool useProxy)
63+
[ConditionalTheory(nameof(ServerAuthenticationTestsEnabled))]
64+
[MemberData(nameof(AuthenticatedServers))]
65+
public async Task UseDefaultCredentials_SetFalse_Unauthorized(string uri, bool useProxy)
6966
{
7067
HttpClientHandler handler = CreateHttpClientHandler();
7168
handler.UseProxy = useProxy;
7269
handler.UseDefaultCredentials = false;
7370

7471
using (var client = new HttpClient(handler))
75-
using (HttpResponseMessage response = await client.GetAsync(s_authenticatedServer))
72+
using (HttpResponseMessage response = await client.GetAsync(uri))
7673
{
7774
Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode);
7875
}
7976
}
8077

8178
[OuterLoop] // TODO: Issue #11345
82-
[ActiveIssue(10041)]
83-
[ConditionalTheory(nameof(DomainJoinedTestsEnabled))]
84-
[InlineData(false)]
85-
[InlineData(true)]
86-
public async Task UseDefaultCredentials_SetTrue_ConnectAsCurrentIdentity(bool useProxy)
79+
[ConditionalTheory(nameof(ServerAuthenticationTestsEnabled))]
80+
[MemberData(nameof(AuthenticatedServers))]
81+
public async Task UseDefaultCredentials_SetTrue_ConnectAsCurrentIdentity(string uri, bool useProxy)
8782
{
8883
HttpClientHandler handler = CreateHttpClientHandler();
8984
handler.UseProxy = useProxy;
9085
handler.UseDefaultCredentials = true;
9186

9287
using (var client = new HttpClient(handler))
93-
using (HttpResponseMessage response = await client.GetAsync(s_authenticatedServer))
88+
using (HttpResponseMessage response = await client.GetAsync(uri))
9489
{
9590
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
9691

@@ -102,18 +97,19 @@ public async Task UseDefaultCredentials_SetTrue_ConnectAsCurrentIdentity(bool us
10297
}
10398

10499
[OuterLoop] // TODO: Issue #11345
105-
[ActiveIssue(10041)]
106-
[ConditionalTheory(nameof(DomainJoinedTestsEnabled))]
107-
[InlineData(false)]
108-
[InlineData(true)]
109-
public async Task UseDefaultCredentials_SetTrueAndServerOffersMultipleSchemes_Ok(bool useProxy)
100+
[ConditionalTheory(nameof(ServerAuthenticationTestsEnabled))]
101+
[MemberData(nameof(AuthenticatedServers))]
102+
public async Task Credentials_SetToWrappedDefaultCredential_ConnectAsCurrentIdentity(string uri, bool useProxy)
110103
{
111104
HttpClientHandler handler = CreateHttpClientHandler();
112105
handler.UseProxy = useProxy;
113-
handler.UseDefaultCredentials = true;
106+
handler.Credentials = new CredentialWrapper
107+
{
108+
InnerCredentials = CredentialCache.DefaultCredentials
109+
};
114110

115111
using (var client = new HttpClient(handler))
116-
using (HttpResponseMessage response = await client.GetAsync(s_multipleSchemesAuthenticatedServer))
112+
using (HttpResponseMessage response = await client.GetAsync(uri))
117113
{
118114
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
119115

@@ -125,24 +121,18 @@ public async Task UseDefaultCredentials_SetTrueAndServerOffersMultipleSchemes_Ok
125121
}
126122

127123
[OuterLoop] // TODO: Issue #11345
128-
[ActiveIssue(10041)]
129-
[ConditionalTheory(nameof(DomainJoinedTestsEnabled))]
130-
[InlineData(false)]
131-
[InlineData(true)]
132-
public async Task Credentials_SetToSpecificCredential_ConnectAsSpecificIdentity(bool useProxy)
124+
[ConditionalTheory(nameof(ServerAuthenticationTestsEnabled))]
125+
[MemberData(nameof(AuthenticatedServers))]
126+
public async Task Credentials_SetToBadCredential_Unauthorized(string uri, bool useProxy)
133127
{
134128
HttpClientHandler handler = CreateHttpClientHandler();
135129
handler.UseProxy = useProxy;
136-
handler.UseDefaultCredentials = false;
137-
handler.Credentials = _specificCredential;
130+
handler.Credentials = new NetworkCredential("notarealuser", "123456");
138131

139132
using (var client = new HttpClient(handler))
140-
using (HttpResponseMessage response = await client.GetAsync(s_authenticatedServer))
133+
using (HttpResponseMessage response = await client.GetAsync(uri))
141134
{
142-
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
143-
144-
string responseBody = await response.Content.ReadAsStringAsync();
145-
VerifyAuthentication(responseBody, true, s_specificDomain + "\\" + s_specificUserName);
135+
Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode);
146136
}
147137
}
148138

@@ -151,24 +141,20 @@ public async Task Credentials_SetToSpecificCredential_ConnectAsSpecificIdentity(
151141
[ConditionalTheory(nameof(DomainJoinedTestsEnabled))]
152142
[InlineData(false)]
153143
[InlineData(true)]
154-
public async Task Credentials_SetToWrappedDefaultCredential_ConnectAsCurrentIdentity(bool useProxy)
144+
public async Task Credentials_SetToSpecificCredential_ConnectAsSpecificIdentity(bool useProxy)
155145
{
156146
HttpClientHandler handler = CreateHttpClientHandler();
157147
handler.UseProxy = useProxy;
158-
handler.Credentials = new CredentialWrapper
159-
{
160-
InnerCredentials = CredentialCache.DefaultCredentials
161-
};
148+
handler.UseDefaultCredentials = false;
149+
handler.Credentials = _specificCredential;
162150

163151
using (var client = new HttpClient(handler))
164152
using (HttpResponseMessage response = await client.GetAsync(s_authenticatedServer))
165153
{
166154
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
167-
155+
168156
string responseBody = await response.Content.ReadAsStringAsync();
169-
WindowsIdentity currentIdentity = WindowsIdentity.GetCurrent();
170-
_output.WriteLine("currentIdentity={0}", currentIdentity.Name);
171-
VerifyAuthentication(responseBody, true, currentIdentity.Name);
157+
VerifyAuthentication(responseBody, true, s_specificDomain + "\\" + s_specificUserName);
172158
}
173159
}
174160

@@ -221,6 +207,27 @@ public async Task Proxy_UseAuthenticatedProxyWithWrappedDefaultCredentials_OK()
221207
}
222208
}
223209

210+
public static IEnumerable<object[]> AuthenticatedServers()
211+
{
212+
// Note that localhost will not actually use the proxy, but there's no harm in testing it.
213+
foreach (bool b in new bool[] { true, false })
214+
{
215+
if (LocalHttpListenerTestsEnabled)
216+
{
217+
yield return new object[] { HttpListenerAuthenticatedLoopbackServer.NtlmOnly.Uri, b };
218+
yield return new object[] { HttpListenerAuthenticatedLoopbackServer.NegotiateOnly.Uri, b };
219+
yield return new object[] { HttpListenerAuthenticatedLoopbackServer.NegotiateAndNtlm.Uri, b };
220+
yield return new object[] { HttpListenerAuthenticatedLoopbackServer.BasicAndNtlm.Uri, b };
221+
}
222+
223+
if (!string.IsNullOrEmpty(Configuration.Http.DomainJoinedHttpHost))
224+
{
225+
yield return new object[] { $"http://{Configuration.Http.DomainJoinedHttpHost}/test/auth/negotiate/showidentity.ashx", b };
226+
yield return new object[] { $"http://{Configuration.Http.DomainJoinedHttpHost}/test/auth/multipleschemes/showidentity.ashx", b };
227+
}
228+
}
229+
}
230+
224231
private void VerifyAuthentication(string response, bool authenticated, string user)
225232
{
226233
// Convert all strings to lowercase to compare. Windows treats domain and username as case-insensitive.
@@ -296,6 +303,46 @@ public bool IsBypassed(Uri host)
296303
{
297304
return false;
298305
}
299-
}
306+
}
307+
308+
private sealed class HttpListenerAuthenticatedLoopbackServer
309+
{
310+
private readonly HttpListener _listener;
311+
private readonly string _uri;
312+
313+
public static readonly HttpListenerAuthenticatedLoopbackServer NtlmOnly = new HttpListenerAuthenticatedLoopbackServer("http://localhost:8080/", AuthenticationSchemes.Ntlm);
314+
public static readonly HttpListenerAuthenticatedLoopbackServer NegotiateOnly = new HttpListenerAuthenticatedLoopbackServer("http://localhost:8081/", AuthenticationSchemes.Negotiate);
315+
public static readonly HttpListenerAuthenticatedLoopbackServer NegotiateAndNtlm = new HttpListenerAuthenticatedLoopbackServer("http://localhost:8082/", AuthenticationSchemes.Negotiate | AuthenticationSchemes.Ntlm);
316+
public static readonly HttpListenerAuthenticatedLoopbackServer BasicAndNtlm = new HttpListenerAuthenticatedLoopbackServer("http://localhost:8083/", AuthenticationSchemes.Basic | AuthenticationSchemes.Ntlm);
317+
318+
// Don't construct directly, use instances above
319+
private HttpListenerAuthenticatedLoopbackServer(string uri, AuthenticationSchemes authenticationSchemes)
320+
{
321+
_uri = uri;
322+
323+
_listener = new HttpListener();
324+
_listener.Prefixes.Add(uri);
325+
_listener.AuthenticationSchemes = authenticationSchemes;
326+
_listener.Start();
327+
328+
Task.Run(() => ProcessRequests());
329+
}
330+
331+
public string Uri => _uri;
332+
333+
private async void ProcessRequests()
334+
{
335+
while (true)
336+
{
337+
var context = await _listener.GetContextAsync();
338+
339+
// Send a response in the JSON format that the client expects
340+
string username = context.User.Identity.Name;
341+
await context.Response.OutputStream.WriteAsync(System.Text.Encoding.UTF8.GetBytes($"{{\"authenticated\": \"true\", \"user\": \"{username}\" }}"));
342+
343+
context.Response.Close();
344+
}
345+
}
346+
}
300347
}
301348
}

0 commit comments

Comments
 (0)