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 ;
56using System . Net . Http ;
7+ using System . Net . Http . Headers ;
68using System . Security . Authentication . ExtendedProtection ;
79using System . Text ;
10+ using System . Threading ;
811using System . Threading . Tasks ;
912using Xunit ;
1013
@@ -27,16 +30,81 @@ public AuthenticationTests()
2730
2831 public void Dispose ( ) => _factory . Dispose ( ) ;
2932
33+ [ ConditionalTheory ( nameof ( Helpers ) + "." + nameof ( Helpers . IsWindowsImplementation ) ) ] // Managed implementation connects successfully.
34+ [ InlineData ( "Basic" ) ]
35+ [ InlineData ( "NTLM" ) ]
36+ [ InlineData ( "Negotiate" ) ]
37+ [ InlineData ( "Unknown" ) ]
38+ public async Task NoAuthentication_AuthenticationProvided_ReturnsForbiddenStatusCode ( string headerType )
39+ {
40+ _listener . AuthenticationSchemes = AuthenticationSchemes . None ;
41+
42+ using ( HttpClient client = new HttpClient ( ) )
43+ {
44+ client . DefaultRequestHeaders . Authorization = new AuthenticationHeaderValue ( headerType , "body" ) ;
45+ await AuthenticationFailure ( client , HttpStatusCode . Forbidden ) ;
46+ }
47+ }
48+
3049 [ ConditionalTheory ( nameof ( PlatformDetection ) + "." + nameof ( PlatformDetection . IsNotOneCoreUAP ) ) ]
3150 [ InlineData ( AuthenticationSchemes . Basic ) ]
3251 [ InlineData ( AuthenticationSchemes . Basic | AuthenticationSchemes . None ) ]
3352 [ InlineData ( AuthenticationSchemes . Basic | AuthenticationSchemes . Anonymous ) ]
34- public async Task TestBasicAuthentication ( AuthenticationSchemes authScheme )
53+ public async Task BasicAuthentication_ValidUsernameAndPassword_Success ( AuthenticationSchemes authScheme )
3554 {
3655 _listener . AuthenticationSchemes = authScheme ;
3756 await ValidateValidUser ( ) ;
3857 }
3958
59+ [ ConditionalTheory ( nameof ( Helpers ) + "." + nameof ( Helpers . IsWindowsImplementation ) ) ] // [ActiveIssue(20099, TestPlatforms.Unix)]
60+ [ MemberData ( nameof ( BasicAuthenticationHeader_TestData ) ) ]
61+ public async Task BasicAuthentication_InvalidRequest_SendsStatusCodeClient ( string header , HttpStatusCode statusCode )
62+ {
63+ _listener . AuthenticationSchemes = AuthenticationSchemes . Basic ;
64+
65+ using ( HttpClient client = new HttpClient ( ) )
66+ {
67+ client . DefaultRequestHeaders . Authorization = new AuthenticationHeaderValue ( Basic , header ) ;
68+
69+ HttpResponseMessage response = await AuthenticationFailure ( client , statusCode ) ;
70+
71+ if ( statusCode == HttpStatusCode . Unauthorized )
72+ {
73+ Assert . Equal ( "Basic realm =\" \" " , response . Headers . WwwAuthenticate . ToString ( ) ) ;
74+ }
75+ else
76+ {
77+ Assert . Empty ( response . Headers . WwwAuthenticate ) ;
78+ }
79+ }
80+ }
81+
82+ public static IEnumerable < object [ ] > BasicAuthenticationHeader_TestData ( )
83+ {
84+ yield return new object [ ] { string . Empty , HttpStatusCode . Unauthorized } ;
85+ yield return new object [ ] { null , HttpStatusCode . Unauthorized } ;
86+ yield return new object [ ] { Convert . ToBase64String ( Encoding . ASCII . GetBytes ( "username" ) ) , HttpStatusCode . BadRequest } ;
87+ yield return new object [ ] { "abc" , HttpStatusCode . InternalServerError } ;
88+ }
89+
90+ [ ConditionalTheory ( nameof ( Helpers ) + "." + nameof ( Helpers . IsWindowsImplementation ) ) ] // [ActiveIssue(20098, TestPlatforms.Unix)]
91+ [ InlineData ( "ExampleRealm" ) ]
92+ [ InlineData ( " ExampleRealm " ) ]
93+ [ InlineData ( "" ) ]
94+ [ InlineData ( null ) ]
95+ public async Task BasicAuthentication_RealmSet_SendsChallengeToClient ( string realm )
96+ {
97+ _listener . Realm = realm ;
98+ _listener . AuthenticationSchemes = AuthenticationSchemes . Basic ;
99+ Assert . Equal ( realm , _listener . Realm ) ;
100+
101+ using ( var client = new HttpClient ( ) )
102+ {
103+ HttpResponseMessage response = await AuthenticationFailure ( client , HttpStatusCode . Unauthorized ) ;
104+ Assert . Equal ( $ "Basic realm =\" { realm } \" ", response . Headers . WwwAuthenticate . ToString ( ) ) ;
105+ }
106+ }
107+
40108 [ ConditionalFact ( nameof ( PlatformDetection ) + "." + nameof ( PlatformDetection . IsNotOneCoreUAP ) ) ]
41109 public async Task TestAnonymousAuthentication ( )
42110 {
@@ -64,6 +132,126 @@ public async Task TestAnonymousAuthenticationWithDelegate()
64132 await ValidateNullUser ( ) ;
65133 }
66134
135+ [ ConditionalFact ( nameof ( Helpers ) + "." + nameof ( Helpers . IsWindowsImplementation ) ) ] // [ActiveIssue(20101, TestPlatforms.AnyUnix)]
136+ [ ActiveIssue ( 20096 ) ]
137+ public async Task NtlmAuthentication_Conversation_ReturnsExpectedType2Message ( )
138+ {
139+ _listener . AuthenticationSchemes = AuthenticationSchemes . Ntlm ;
140+
141+ using ( var client = new HttpClient ( ) )
142+ {
143+ client . DefaultRequestHeaders . Authorization = new AuthenticationHeaderValue ( "NTLM" , "TlRMTVNTUAABAAAABzIAAAYABgArAAAACwALACAAAABXT1JLU1RBVElPTkRPTUFJTg==" ) ;
144+
145+ HttpResponseMessage message = await AuthenticationFailure ( client , HttpStatusCode . Unauthorized ) ;
146+ Assert . StartsWith ( "NTLM" , message . Headers . WwwAuthenticate . ToString ( ) ) ;
147+ }
148+ }
149+
150+ public static IEnumerable < object [ ] > InvalidNtlmNegotiateAuthentication_TestData ( )
151+ {
152+ yield return new object [ ] { null , HttpStatusCode . Unauthorized } ;
153+ yield return new object [ ] { string . Empty , HttpStatusCode . Unauthorized } ;
154+ yield return new object [ ] { "abc" , HttpStatusCode . BadRequest } ;
155+ yield return new object [ ] { "abcd" , HttpStatusCode . BadRequest } ;
156+ }
157+
158+ [ ConditionalTheory ( nameof ( Helpers ) + "." + nameof ( Helpers . IsWindowsImplementation ) ) ] // [ActiveIssue(20101, TestPlatforms.AnyUnix)]
159+ [ ActiveIssue ( 20096 ) ]
160+ [ MemberData ( nameof ( InvalidNtlmNegotiateAuthentication_TestData ) ) ]
161+ public async Task NtlmAuthentication_InvalidRequestHeaders_ReturnsExpectedStatusCode ( string header , HttpStatusCode statusCode )
162+ {
163+ _listener . AuthenticationSchemes = AuthenticationSchemes . Ntlm ;
164+
165+ using ( var client = new HttpClient ( ) )
166+ {
167+ client . DefaultRequestHeaders . Authorization = new AuthenticationHeaderValue ( "NTLM" , header ) ;
168+
169+ HttpResponseMessage message = await AuthenticationFailure ( client , statusCode ) ;
170+ if ( statusCode == HttpStatusCode . Unauthorized )
171+ {
172+ Assert . Equal ( "NTLM" , message . Headers . WwwAuthenticate . ToString ( ) ) ;
173+ }
174+ else
175+ {
176+ Assert . Empty ( message . Headers . WwwAuthenticate ) ;
177+ }
178+ }
179+ }
180+
181+ [ ConditionalFact ( nameof ( Helpers ) + "." + nameof ( Helpers . IsWindowsImplementation ) ) ] // [ActiveIssue(20101, TestPlatforms.AnyUnix)]
182+ [ ActiveIssue ( 20096 ) ]
183+ public async Task NegotiateAuthentication_Conversation_ReturnsExpectedType2Message ( )
184+ {
185+ _listener . AuthenticationSchemes = AuthenticationSchemes . Negotiate ;
186+
187+ using ( var client = new HttpClient ( ) )
188+ {
189+ client . DefaultRequestHeaders . Authorization = new AuthenticationHeaderValue ( "Negotiate" , "TlRMTVNTUAABAAAABzIAAAYABgArAAAACwALACAAAABXT1JLU1RBVElPTkRPTUFJTg==" ) ;
190+
191+ HttpResponseMessage message = await AuthenticationFailure ( client , HttpStatusCode . Unauthorized ) ;
192+ Assert . StartsWith ( "Negotiate" , message . Headers . WwwAuthenticate . ToString ( ) ) ;
193+ }
194+ }
195+
196+ [ ConditionalTheory ( nameof ( Helpers ) + "." + nameof ( Helpers . IsWindowsImplementation ) ) ] // [ActiveIssue(20101, TestPlatforms.AnyUnix)]
197+ [ ActiveIssue ( 20096 ) ]
198+ [ MemberData ( nameof ( InvalidNtlmNegotiateAuthentication_TestData ) ) ]
199+ public async Task NegotiateAuthentication_InvalidRequestHeaders_ReturnsExpectedStatusCode ( string header , HttpStatusCode statusCode )
200+ {
201+ _listener . AuthenticationSchemes = AuthenticationSchemes . Negotiate ;
202+
203+ using ( var client = new HttpClient ( ) )
204+ {
205+ client . DefaultRequestHeaders . Authorization = new AuthenticationHeaderValue ( "Negotiate" , header ) ;
206+
207+ HttpResponseMessage message = await AuthenticationFailure ( client , statusCode ) ;
208+ Assert . Empty ( message . Headers . WwwAuthenticate ) ;
209+ }
210+ }
211+
212+ [ ConditionalFact ( nameof ( PlatformDetection ) + "." + nameof ( PlatformDetection . IsNotOneCoreUAP ) ) ]
213+ public async Task AuthenticationSchemeSelectorDelegate_ReturnsInvalidAuthenticationScheme_PerformsNoAuthentication ( )
214+ {
215+ _listener . AuthenticationSchemes = AuthenticationSchemes . Basic ;
216+ _listener . AuthenticationSchemeSelectorDelegate = ( request ) => ( AuthenticationSchemes ) ( - 1 ) ;
217+
218+ using ( var client = new HttpClient ( ) )
219+ {
220+ Task < HttpResponseMessage > clientTask = client . GetAsync ( _factory . ListeningUrl ) ;
221+ HttpListenerContext context = await _listener . GetContextAsync ( ) ;
222+
223+ Assert . False ( context . Request . IsAuthenticated ) ;
224+ context . Response . Close ( ) ;
225+
226+ await clientTask ;
227+ }
228+ }
229+
230+ [ ConditionalFact ( nameof ( Helpers ) + "." + nameof ( Helpers . IsWindowsImplementation ) ) ] // [ActiveIssue(20100, TestPlatforms.AnyUnix)]
231+ public async Task AuthenticationSchemeSelectorDelegate_ThrowsException_SendsInternalServerErrorToClient ( )
232+ {
233+ _listener . AuthenticationSchemes = AuthenticationSchemes . Basic ;
234+ _listener . AuthenticationSchemeSelectorDelegate = ( request ) => { throw new InvalidOperationException ( ) ; } ;
235+
236+ using ( var client = new HttpClient ( ) )
237+ {
238+ HttpResponseMessage response = await AuthenticationFailure ( client , HttpStatusCode . InternalServerError ) ;
239+ }
240+ }
241+
242+ [ ConditionalFact ( nameof ( Helpers ) + "." + nameof ( Helpers . IsWindowsImplementation ) ) ] // [ActiveIssue(20100, TestPlatforms.AnyUnix)]
243+ public void AuthenticationSchemeSelectorDelegate_ThrowsOutOfMemoryException_RethrowsException ( )
244+ {
245+ _listener . AuthenticationSchemes = AuthenticationSchemes . Basic ;
246+ _listener . AuthenticationSchemeSelectorDelegate = ( request ) => { throw new OutOfMemoryException ( ) ; } ;
247+
248+ using ( var client = new HttpClient ( ) )
249+ {
250+ Task < string > clientTask = client . GetStringAsync ( _factory . ListeningUrl ) ;
251+ Assert . Throws < OutOfMemoryException > ( ( ) => _listener . GetContext ( ) ) ;
252+ }
253+ }
254+
67255 [ ConditionalFact ( nameof ( PlatformDetection ) + "." + nameof ( PlatformDetection . IsNotOneCoreUAP ) ) ]
68256 public void AuthenticationSchemeSelectorDelegate_SetDisposed_ThrowsObjectDisposedException ( )
69257 {
@@ -121,8 +309,7 @@ public void UnsafeConnectionNtlmAuthentication_Unix_ThrowsPlatformNotSupportedEx
121309 }
122310 }
123311
124- [ ConditionalFact ( nameof ( PlatformDetection ) + "." + nameof ( PlatformDetection . IsNotOneCoreUAP ) ) ]
125- [ PlatformSpecific ( TestPlatforms . Windows ) ]
312+ [ ConditionalFact ( nameof ( Helpers ) + "." + nameof ( Helpers . IsWindowsImplementation ) ) ]
126313 public void UnsafeConnectionNtlmAuthentication_SetGet_ReturnsExpected ( )
127314 {
128315 using ( var listener = new HttpListener ( ) )
@@ -140,8 +327,7 @@ public void UnsafeConnectionNtlmAuthentication_SetGet_ReturnsExpected()
140327 }
141328 }
142329
143- [ ConditionalFact ( nameof ( PlatformDetection ) + "." + nameof ( PlatformDetection . IsNotOneCoreUAP ) ) ]
144- [ PlatformSpecific ( TestPlatforms . Windows ) ]
330+ [ ConditionalFact ( nameof ( Helpers ) + "." + nameof ( Helpers . IsWindowsImplementation ) ) ]
145331 public void UnsafeConnectionNtlmAuthentication_SetDisposed_ThrowsObjectDisposedException ( )
146332 {
147333 var listener = new HttpListener ( ) ;
@@ -168,6 +354,22 @@ public void ExtendedProtectionSelectorDelegate_SetDisposed_ThrowsObjectDisposedE
168354 Assert . Throws < ObjectDisposedException > ( ( ) => listener . ExtendedProtectionSelectorDelegate = null ) ;
169355 }
170356
357+ [ ConditionalFact ( nameof ( PlatformDetection ) + "." + nameof ( PlatformDetection . IsNotOneCoreUAP ) ) ]
358+ public async Task Realm_SetWithoutBasicAuthenticationScheme_SendsNoChallengeToClient ( )
359+ {
360+ _listener . Realm = "ExampleRealm" ;
361+
362+ using ( HttpClient client = new HttpClient ( ) )
363+ {
364+ Task < HttpResponseMessage > clientTask = client . GetAsync ( _factory . ListeningUrl ) ;
365+ HttpListenerContext context = await _listener . GetContextAsync ( ) ;
366+ context . Response . Close ( ) ;
367+
368+ HttpResponseMessage response = await clientTask ;
369+ Assert . Empty ( response . Headers . WwwAuthenticate ) ;
370+ }
371+ }
372+
171373 [ ConditionalFact ( nameof ( PlatformDetection ) + "." + nameof ( PlatformDetection . IsNotOneCoreUAP ) ) ]
172374 public void Realm_SetDisposed_ThrowsObjectDisposedException ( )
173375 {
@@ -177,17 +379,39 @@ public void Realm_SetDisposed_ThrowsObjectDisposedException()
177379 Assert . Throws < ObjectDisposedException > ( ( ) => listener . Realm = null ) ;
178380 }
179381
382+ public async Task < HttpResponseMessage > AuthenticationFailure ( HttpClient client , HttpStatusCode errorCode )
383+ {
384+ Task < HttpResponseMessage > clientTask = client . GetAsync ( _factory . ListeningUrl ) ;
385+
386+ // The server task will hang forever if it is not cancelled.
387+ var tokenSource = new CancellationTokenSource ( ) ;
388+ Task < HttpListenerContext > serverTask = Task . Run ( ( ) => _listener . GetContext ( ) , tokenSource . Token ) ;
389+
390+ // The client task should complete first - the server should send a 401 response.
391+ Task resultTask = await Task . WhenAny ( clientTask , serverTask ) ;
392+ tokenSource . Cancel ( ) ;
393+ if ( resultTask == serverTask )
394+ {
395+ await serverTask ;
396+ }
397+
398+ Assert . Same ( clientTask , resultTask ) ;
399+
400+ Assert . Equal ( errorCode , clientTask . Result . StatusCode ) ;
401+ return clientTask . Result ;
402+ }
403+
180404 private async Task ValidateNullUser ( )
181405 {
182- var serverContextTask = _listener . GetContextAsync ( ) ;
406+ Task < HttpListenerContext > serverContextTask = _listener . GetContextAsync ( ) ;
183407
184408 using ( HttpClient client = new HttpClient ( ) )
185409 {
186410 client . DefaultRequestHeaders . Authorization = new Http . Headers . AuthenticationHeaderValue (
187411 Basic ,
188412 Convert . ToBase64String ( Encoding . ASCII . GetBytes ( string . Format ( "{0}:{1}" , TestUser , TestPassword ) ) ) ) ;
189413
190- var clientTask = client . GetStringAsync ( _factory . ListeningUrl ) ;
414+ Task < string > clientTask = client . GetStringAsync ( _factory . ListeningUrl ) ;
191415 HttpListenerContext listenerContext = await serverContextTask ;
192416
193417 Assert . Null ( listenerContext . User ) ;
@@ -196,14 +420,14 @@ private async Task ValidateNullUser()
196420
197421 private async Task ValidateValidUser ( )
198422 {
199- var serverContextTask = _listener . GetContextAsync ( ) ;
423+ Task < HttpListenerContext > serverContextTask = _listener . GetContextAsync ( ) ;
200424 using ( HttpClient client = new HttpClient ( ) )
201425 {
202- client . DefaultRequestHeaders . Authorization = new Http . Headers . AuthenticationHeaderValue (
426+ client . DefaultRequestHeaders . Authorization = new AuthenticationHeaderValue (
203427 Basic ,
204428 Convert . ToBase64String ( Encoding . ASCII . GetBytes ( string . Format ( "{0}:{1}" , TestUser , TestPassword ) ) ) ) ;
205429
206- var clientTask = client . GetStringAsync ( _factory . ListeningUrl ) ;
430+ Task < string > clientTask = client . GetStringAsync ( _factory . ListeningUrl ) ;
207431 HttpListenerContext listenerContext = await serverContextTask ;
208432
209433 Assert . Equal ( TestUser , listenerContext . User . Identity . Name ) ;
@@ -212,15 +436,9 @@ private async Task ValidateValidUser()
212436 }
213437 }
214438
215- private AuthenticationSchemes SelectAnonymousAndBasicSchemes ( HttpListenerRequest request )
216- {
217- return AuthenticationSchemes . Anonymous | AuthenticationSchemes . Basic ;
218- }
439+ private AuthenticationSchemes SelectAnonymousAndBasicSchemes ( HttpListenerRequest request ) => AuthenticationSchemes . Anonymous | AuthenticationSchemes . Basic ;
219440
220- private AuthenticationSchemes SelectAnonymousScheme ( HttpListenerRequest request )
221- {
222- return AuthenticationSchemes . Anonymous ;
223- }
441+ private AuthenticationSchemes SelectAnonymousScheme ( HttpListenerRequest request ) => AuthenticationSchemes . Anonymous ;
224442
225443 private class CustomChannelBinding : ChannelBinding
226444 {
0 commit comments