This repository was archived by the owner on Jan 23, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 4.9k
Add some HttpListener authentication tests #20102
Merged
Merged
Changes from all commits
Commits
Show all changes
3 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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; | ||
|
|
||
|
|
@@ -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)] | ||
| [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() | ||
| { | ||
|
|
@@ -64,6 +132,126 @@ public async Task TestAnonymousAuthenticationWithDelegate() | |
| await ValidateNullUser(); | ||
| } | ||
|
|
||
| [ConditionalFact(nameof(Helpers) + "." + nameof(Helpers.IsWindowsImplementation))] // [ActiveIssue(20101, TestPlatforms.AnyUnix)] | ||
| [ActiveIssue(20096)] | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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() | ||
| { | ||
|
|
@@ -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()) | ||
|
|
@@ -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(); | ||
|
|
@@ -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() | ||
| { | ||
|
|
@@ -177,17 +379,39 @@ public void Realm_SetDisposed_ThrowsObjectDisposedException() | |
| Assert.Throws<ObjectDisposedException>(() => listener.Realm = null); | ||
| } | ||
|
|
||
| public async Task<HttpResponseMessage> AuthenticationFailure(HttpClient client, HttpStatusCode errorCode) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. There was a problem hiding this comment. Choose a reason for hiding this commentThe 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); | ||
|
|
@@ -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); | ||
|
|
@@ -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 | ||
| { | ||
|
|
||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
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?
?