Skip to content
This repository was archived by the owner on Jan 23, 2023. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
254 changes: 236 additions & 18 deletions src/System.Net.HttpListener/tests/AuthenticationTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,12 @@
// 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.Collections.Generic;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Security.Authentication.ExtendedProtection;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Xunit;

Expand All @@ -27,16 +30,81 @@ public AuthenticationTests()

public void Dispose() => _factory.Dispose();

[ConditionalTheory(nameof(Helpers) + "." + nameof(Helpers.IsWindowsImplementation))] // Managed implementation connects successfully.
[InlineData("Basic")]
[InlineData("NTLM")]
[InlineData("Negotiate")]
[InlineData("Unknown")]
public async Task NoAuthentication_AuthenticationProvided_ReturnsForbiddenStatusCode(string headerType)
{
_listener.AuthenticationSchemes = AuthenticationSchemes.None;

using (HttpClient client = new HttpClient())
{
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(headerType, "body");
await AuthenticationFailure(client, HttpStatusCode.Forbidden);
}
}

[ConditionalTheory(nameof(PlatformDetection) + "." + nameof(PlatformDetection.IsNotOneCoreUAP))]
[InlineData(AuthenticationSchemes.Basic)]
[InlineData(AuthenticationSchemes.Basic | AuthenticationSchemes.None)]
[InlineData(AuthenticationSchemes.Basic | AuthenticationSchemes.Anonymous)]
public async Task TestBasicAuthentication(AuthenticationSchemes authScheme)
public async Task BasicAuthentication_ValidUsernameAndPassword_Success(AuthenticationSchemes authScheme)
{
_listener.AuthenticationSchemes = authScheme;
await ValidateValidUser();
}

[ConditionalTheory(nameof(Helpers) + "." + nameof(Helpers.IsWindowsImplementation))] // [ActiveIssue(20099, TestPlatforms.Unix)]
Copy link
Member

@stephentoub stephentoub May 22, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this ConditionalTheory construct being used like this with IsWindowsImplementation? It doesn't work to just add the active issue for unix?

[ActiveIssue(20099, TestPlatforms.AnyUnix)]
[ConditionalTheory(nameof(PlatformDetection) + "." + nameof(PlatformDetection.IsNotOneCoreUAP))]

?

[MemberData(nameof(BasicAuthenticationHeader_TestData))]
public async Task BasicAuthentication_InvalidRequest_SendsStatusCodeClient(string header, HttpStatusCode statusCode)
{
_listener.AuthenticationSchemes = AuthenticationSchemes.Basic;

using (HttpClient client = new HttpClient())
{
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(Basic, header);

HttpResponseMessage response = await AuthenticationFailure(client, statusCode);

if (statusCode == HttpStatusCode.Unauthorized)
{
Assert.Equal("Basic realm =\"\"", response.Headers.WwwAuthenticate.ToString());
}
else
{
Assert.Empty(response.Headers.WwwAuthenticate);
}
}
}

public static IEnumerable<object[]> BasicAuthenticationHeader_TestData()
{
yield return new object[] { string.Empty, HttpStatusCode.Unauthorized };
yield return new object[] { null, HttpStatusCode.Unauthorized };
yield return new object[] { Convert.ToBase64String(Encoding.ASCII.GetBytes("username")), HttpStatusCode.BadRequest };
yield return new object[] { "abc", HttpStatusCode.InternalServerError };
}

[ConditionalTheory(nameof(Helpers) + "." + nameof(Helpers.IsWindowsImplementation))] // [ActiveIssue(20098, TestPlatforms.Unix)]
[InlineData("ExampleRealm")]
[InlineData(" ExampleRealm ")]
[InlineData("")]
[InlineData(null)]
public async Task BasicAuthentication_RealmSet_SendsChallengeToClient(string realm)
{
_listener.Realm = realm;
_listener.AuthenticationSchemes = AuthenticationSchemes.Basic;
Assert.Equal(realm, _listener.Realm);

using (var client = new HttpClient())
{
HttpResponseMessage response = await AuthenticationFailure(client, HttpStatusCode.Unauthorized);
Assert.Equal($"Basic realm =\"{realm}\"", response.Headers.WwwAuthenticate.ToString());
}
}

[ConditionalFact(nameof(PlatformDetection) + "." + nameof(PlatformDetection.IsNotOneCoreUAP))]
public async Task TestAnonymousAuthentication()
{
Expand Down Expand Up @@ -64,6 +132,126 @@ public async Task TestAnonymousAuthenticationWithDelegate()
await ValidateNullUser();
}

[ConditionalFact(nameof(Helpers) + "." + nameof(Helpers.IsWindowsImplementation))] // [ActiveIssue(20101, TestPlatforms.AnyUnix)]
[ActiveIssue(20096)]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similarly, why can't this be:

[ActiveIssue(20096, TestPlatforms.Windows)]
[ActiveIssue(20101, TestPlatforms.AnyUnix)]
[ConditionalFact(nameof(PlatformDetection) + "." + nameof(PlatformDetection.IsNotOneCoreUAP))] 

? Does that not work?

public async Task NtlmAuthentication_Conversation_ReturnsExpectedType2Message()
{
_listener.AuthenticationSchemes = AuthenticationSchemes.Ntlm;

using (var client = new HttpClient())
{
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("NTLM", "TlRMTVNTUAABAAAABzIAAAYABgArAAAACwALACAAAABXT1JLU1RBVElPTkRPTUFJTg==");

HttpResponseMessage message = await AuthenticationFailure(client, HttpStatusCode.Unauthorized);
Assert.StartsWith("NTLM", message.Headers.WwwAuthenticate.ToString());
}
}

public static IEnumerable<object[]> InvalidNtlmNegotiateAuthentication_TestData()
{
yield return new object[] { null, HttpStatusCode.Unauthorized };
yield return new object[] { string.Empty, HttpStatusCode.Unauthorized };
yield return new object[] { "abc", HttpStatusCode.BadRequest };
yield return new object[] { "abcd", HttpStatusCode.BadRequest };
}

[ConditionalTheory(nameof(Helpers) + "." + nameof(Helpers.IsWindowsImplementation))] // [ActiveIssue(20101, TestPlatforms.AnyUnix)]
[ActiveIssue(20096)]
[MemberData(nameof(InvalidNtlmNegotiateAuthentication_TestData))]
public async Task NtlmAuthentication_InvalidRequestHeaders_ReturnsExpectedStatusCode(string header, HttpStatusCode statusCode)
{
_listener.AuthenticationSchemes = AuthenticationSchemes.Ntlm;

using (var client = new HttpClient())
{
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("NTLM", header);

HttpResponseMessage message = await AuthenticationFailure(client, statusCode);
if (statusCode == HttpStatusCode.Unauthorized)
{
Assert.Equal("NTLM", message.Headers.WwwAuthenticate.ToString());
}
else
{
Assert.Empty(message.Headers.WwwAuthenticate);
}
}
}

[ConditionalFact(nameof(Helpers) + "." + nameof(Helpers.IsWindowsImplementation))] // [ActiveIssue(20101, TestPlatforms.AnyUnix)]
[ActiveIssue(20096)]
public async Task NegotiateAuthentication_Conversation_ReturnsExpectedType2Message()
{
_listener.AuthenticationSchemes = AuthenticationSchemes.Negotiate;

using (var client = new HttpClient())
{
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Negotiate", "TlRMTVNTUAABAAAABzIAAAYABgArAAAACwALACAAAABXT1JLU1RBVElPTkRPTUFJTg==");

HttpResponseMessage message = await AuthenticationFailure(client, HttpStatusCode.Unauthorized);
Assert.StartsWith("Negotiate", message.Headers.WwwAuthenticate.ToString());
}
}

[ConditionalTheory(nameof(Helpers) + "." + nameof(Helpers.IsWindowsImplementation))] // [ActiveIssue(20101, TestPlatforms.AnyUnix)]
[ActiveIssue(20096)]
[MemberData(nameof(InvalidNtlmNegotiateAuthentication_TestData))]
public async Task NegotiateAuthentication_InvalidRequestHeaders_ReturnsExpectedStatusCode(string header, HttpStatusCode statusCode)
{
_listener.AuthenticationSchemes = AuthenticationSchemes.Negotiate;

using (var client = new HttpClient())
{
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Negotiate", header);

HttpResponseMessage message = await AuthenticationFailure(client, statusCode);
Assert.Empty(message.Headers.WwwAuthenticate);
}
}

[ConditionalFact(nameof(PlatformDetection) + "." + nameof(PlatformDetection.IsNotOneCoreUAP))]
public async Task AuthenticationSchemeSelectorDelegate_ReturnsInvalidAuthenticationScheme_PerformsNoAuthentication()
{
_listener.AuthenticationSchemes = AuthenticationSchemes.Basic;
_listener.AuthenticationSchemeSelectorDelegate = (request) => (AuthenticationSchemes)(-1);

using (var client = new HttpClient())
{
Task<HttpResponseMessage> clientTask = client.GetAsync(_factory.ListeningUrl);
HttpListenerContext context = await _listener.GetContextAsync();

Assert.False(context.Request.IsAuthenticated);
context.Response.Close();

await clientTask;
}
}

[ConditionalFact(nameof(Helpers) + "." + nameof(Helpers.IsWindowsImplementation))] // [ActiveIssue(20100, TestPlatforms.AnyUnix)]
public async Task AuthenticationSchemeSelectorDelegate_ThrowsException_SendsInternalServerErrorToClient()
{
_listener.AuthenticationSchemes = AuthenticationSchemes.Basic;
_listener.AuthenticationSchemeSelectorDelegate = (request) => { throw new InvalidOperationException(); };

using (var client = new HttpClient())
{
HttpResponseMessage response = await AuthenticationFailure(client, HttpStatusCode.InternalServerError);
}
}

[ConditionalFact(nameof(Helpers) + "." + nameof(Helpers.IsWindowsImplementation))] // [ActiveIssue(20100, TestPlatforms.AnyUnix)]
public void AuthenticationSchemeSelectorDelegate_ThrowsOutOfMemoryException_RethrowsException()
{
_listener.AuthenticationSchemes = AuthenticationSchemes.Basic;
_listener.AuthenticationSchemeSelectorDelegate = (request) => { throw new OutOfMemoryException(); };

using (var client = new HttpClient())
{
Task<string> clientTask = client.GetStringAsync(_factory.ListeningUrl);
Assert.Throws<OutOfMemoryException>(() => _listener.GetContext());
}
}

[ConditionalFact(nameof(PlatformDetection) + "." + nameof(PlatformDetection.IsNotOneCoreUAP))]
public void AuthenticationSchemeSelectorDelegate_SetDisposed_ThrowsObjectDisposedException()
{
Expand Down Expand Up @@ -121,8 +309,7 @@ public void UnsafeConnectionNtlmAuthentication_Unix_ThrowsPlatformNotSupportedEx
}
}

[ConditionalFact(nameof(PlatformDetection) + "." + nameof(PlatformDetection.IsNotOneCoreUAP))]
[PlatformSpecific(TestPlatforms.Windows)]
[ConditionalFact(nameof(Helpers) + "." + nameof(Helpers.IsWindowsImplementation))]
public void UnsafeConnectionNtlmAuthentication_SetGet_ReturnsExpected()
{
using (var listener = new HttpListener())
Expand All @@ -140,8 +327,7 @@ public void UnsafeConnectionNtlmAuthentication_SetGet_ReturnsExpected()
}
}

[ConditionalFact(nameof(PlatformDetection) + "." + nameof(PlatformDetection.IsNotOneCoreUAP))]
[PlatformSpecific(TestPlatforms.Windows)]
[ConditionalFact(nameof(Helpers) + "." + nameof(Helpers.IsWindowsImplementation))]
public void UnsafeConnectionNtlmAuthentication_SetDisposed_ThrowsObjectDisposedException()
{
var listener = new HttpListener();
Expand All @@ -168,6 +354,22 @@ public void ExtendedProtectionSelectorDelegate_SetDisposed_ThrowsObjectDisposedE
Assert.Throws<ObjectDisposedException>(() => listener.ExtendedProtectionSelectorDelegate = null);
}

[ConditionalFact(nameof(PlatformDetection) + "." + nameof(PlatformDetection.IsNotOneCoreUAP))]
public async Task Realm_SetWithoutBasicAuthenticationScheme_SendsNoChallengeToClient()
{
_listener.Realm = "ExampleRealm";

using (HttpClient client = new HttpClient())
{
Task<HttpResponseMessage> clientTask = client.GetAsync(_factory.ListeningUrl);
HttpListenerContext context = await _listener.GetContextAsync();
context.Response.Close();

HttpResponseMessage response = await clientTask;
Assert.Empty(response.Headers.WwwAuthenticate);
}
}

[ConditionalFact(nameof(PlatformDetection) + "." + nameof(PlatformDetection.IsNotOneCoreUAP))]
public void Realm_SetDisposed_ThrowsObjectDisposedException()
{
Expand All @@ -177,17 +379,39 @@ public void Realm_SetDisposed_ThrowsObjectDisposedException()
Assert.Throws<ObjectDisposedException>(() => listener.Realm = null);
}

public async Task<HttpResponseMessage> AuthenticationFailure(HttpClient client, HttpStatusCode errorCode)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: we should make this private instead of public so that it's clear it's not a mistake that it doesn't have a Theory attribute on it.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(we don't have to restart CI for this though)

{
Task<HttpResponseMessage> clientTask = client.GetAsync(_factory.ListeningUrl);

// The server task will hang forever if it is not cancelled.
var tokenSource = new CancellationTokenSource();
Task<HttpListenerContext> serverTask = Task.Run(() => _listener.GetContext(), tokenSource.Token);

// The client task should complete first - the server should send a 401 response.
Task resultTask = await Task.WhenAny(clientTask, serverTask);
tokenSource.Cancel();
if (resultTask == serverTask)
{
await serverTask;
}

Assert.Same(clientTask, resultTask);

Assert.Equal(errorCode, clientTask.Result.StatusCode);
return clientTask.Result;
}

private async Task ValidateNullUser()
{
var serverContextTask = _listener.GetContextAsync();
Task<HttpListenerContext> serverContextTask = _listener.GetContextAsync();

using (HttpClient client = new HttpClient())
{
client.DefaultRequestHeaders.Authorization = new Http.Headers.AuthenticationHeaderValue(
Basic,
Convert.ToBase64String(Encoding.ASCII.GetBytes(string.Format("{0}:{1}", TestUser, TestPassword))));

var clientTask = client.GetStringAsync(_factory.ListeningUrl);
Task<string> clientTask = client.GetStringAsync(_factory.ListeningUrl);
HttpListenerContext listenerContext = await serverContextTask;

Assert.Null(listenerContext.User);
Expand All @@ -196,14 +420,14 @@ private async Task ValidateNullUser()

private async Task ValidateValidUser()
{
var serverContextTask = _listener.GetContextAsync();
Task<HttpListenerContext> serverContextTask = _listener.GetContextAsync();
using (HttpClient client = new HttpClient())
{
client.DefaultRequestHeaders.Authorization = new Http.Headers.AuthenticationHeaderValue(
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(
Basic,
Convert.ToBase64String(Encoding.ASCII.GetBytes(string.Format("{0}:{1}", TestUser, TestPassword))));

var clientTask = client.GetStringAsync(_factory.ListeningUrl);
Task<string> clientTask = client.GetStringAsync(_factory.ListeningUrl);
HttpListenerContext listenerContext = await serverContextTask;

Assert.Equal(TestUser, listenerContext.User.Identity.Name);
Expand All @@ -212,15 +436,9 @@ private async Task ValidateValidUser()
}
}

private AuthenticationSchemes SelectAnonymousAndBasicSchemes(HttpListenerRequest request)
{
return AuthenticationSchemes.Anonymous | AuthenticationSchemes.Basic;
}
private AuthenticationSchemes SelectAnonymousAndBasicSchemes(HttpListenerRequest request) => AuthenticationSchemes.Anonymous | AuthenticationSchemes.Basic;

private AuthenticationSchemes SelectAnonymousScheme(HttpListenerRequest request)
{
return AuthenticationSchemes.Anonymous;
}
private AuthenticationSchemes SelectAnonymousScheme(HttpListenerRequest request) => AuthenticationSchemes.Anonymous;

private class CustomChannelBinding : ChannelBinding
{
Expand Down
Loading