@@ -17,9 +17,9 @@ public class HttpClientHandler_Authentication_Test : HttpClientTestBase
17
17
private const string Password = "testpassword" ;
18
18
private const string Domain = "testdomain" ;
19
19
20
- private NetworkCredential _credentials = new NetworkCredential ( Username , Password , Domain ) ;
20
+ private static readonly NetworkCredential s_credentials = new NetworkCredential ( Username , Password , Domain ) ;
21
21
22
- private Func < HttpClientHandler , Uri , HttpStatusCode , NetworkCredential , Task > _createAndValidateRequest = async ( handler , url , expectedStatusCode , credentials ) =>
22
+ private static readonly Func < HttpClientHandler , Uri , HttpStatusCode , NetworkCredential , Task > s_createAndValidateRequest = async ( handler , url , expectedStatusCode , credentials ) =>
23
23
{
24
24
handler . Credentials = credentials ;
25
25
@@ -50,7 +50,7 @@ await LoopbackServer.CreateServerAsync(async (server, url) =>
50
50
server . AcceptConnectionSendResponseAndCloseAsync ( HttpStatusCode . Unauthorized , serverAuthenticateHeader ) ;
51
51
52
52
await TestHelper . WhenAllCompletedOrAnyFailedWithTimeout ( TestHelper . PassingTestTimeoutMilliseconds ,
53
- _createAndValidateRequest ( handler , url , result ? HttpStatusCode . OK : HttpStatusCode . Unauthorized , _credentials ) , serverTask ) ;
53
+ s_createAndValidateRequest ( handler , url , result ? HttpStatusCode . OK : HttpStatusCode . Unauthorized , s_credentials ) , serverTask ) ;
54
54
} , options ) ;
55
55
}
56
56
@@ -86,7 +86,7 @@ await LoopbackServer.CreateServerAsync(async (server, url) =>
86
86
{
87
87
HttpClientHandler handler = CreateHttpClientHandler ( ) ;
88
88
Task serverTask = server . AcceptConnectionPerformAuthenticationAndCloseAsync ( authenticateHeader ) ;
89
- await TestHelper . WhenAllCompletedOrAnyFailed ( _createAndValidateRequest ( handler , url , HttpStatusCode . OK , _credentials ) , serverTask ) ;
89
+ await TestHelper . WhenAllCompletedOrAnyFailed ( s_createAndValidateRequest ( handler , url , HttpStatusCode . OK , s_credentials ) , serverTask ) ;
90
90
} , options ) ;
91
91
}
92
92
@@ -100,7 +100,7 @@ await LoopbackServer.CreateServerAsync(async (server, url) =>
100
100
{
101
101
HttpClientHandler handler = CreateHttpClientHandler ( ) ;
102
102
Task serverTask = server . AcceptConnectionPerformAuthenticationAndCloseAsync ( authenticateHeader ) ;
103
- await TestHelper . WhenAllCompletedOrAnyFailed ( _createAndValidateRequest ( handler , url , HttpStatusCode . Unauthorized , new NetworkCredential ( "wronguser" , "wrongpassword" ) ) , serverTask ) ;
103
+ await TestHelper . WhenAllCompletedOrAnyFailed ( s_createAndValidateRequest ( handler , url , HttpStatusCode . Unauthorized , new NetworkCredential ( "wronguser" , "wrongpassword" ) ) , serverTask ) ;
104
104
} , options ) ;
105
105
}
106
106
@@ -134,5 +134,180 @@ public static IEnumerable<object[]> Authentication_TestData()
134
134
$ "Basic realm=\" testrealm\" ", false } ;
135
135
}
136
136
}
137
+
138
+ [ Theory ]
139
+ [ InlineData ( null ) ]
140
+ [ InlineData ( "Basic" ) ]
141
+ [ InlineData ( "Digest" ) ]
142
+ [ InlineData ( "NTLM" ) ]
143
+ [ InlineData ( "Kerberos" ) ]
144
+ [ InlineData ( "Negotiate" ) ]
145
+ public async Task PreAuthenticate_NoPreviousAuthenticatedRequests_NoCredentialsSent ( string credCacheScheme )
146
+ {
147
+ const int NumRequests = 3 ;
148
+ await LoopbackServer . CreateClientAndServerAsync ( async uri =>
149
+ {
150
+ using ( HttpClientHandler handler = CreateHttpClientHandler ( ) )
151
+ using ( var client = new HttpClient ( handler ) )
152
+ {
153
+ client . DefaultRequestHeaders . ConnectionClose = true ; // for simplicity of not needing to know every handler's pooling policy
154
+ handler . PreAuthenticate = true ;
155
+ switch ( credCacheScheme )
156
+ {
157
+ case null :
158
+ handler . Credentials = s_credentials ;
159
+ break ;
160
+
161
+ default :
162
+ var cc = new CredentialCache ( ) ;
163
+ cc . Add ( uri , credCacheScheme , s_credentials ) ;
164
+ handler . Credentials = cc ;
165
+ break ;
166
+ }
167
+
168
+ for ( int i = 0 ; i < NumRequests ; i ++ )
169
+ {
170
+ Assert . Equal ( "hello world" , await client . GetStringAsync ( uri ) ) ;
171
+ }
172
+ }
173
+ } ,
174
+ async server =>
175
+ {
176
+ for ( int i = 0 ; i < NumRequests ; i ++ )
177
+ {
178
+ List < string > headers = await server . AcceptConnectionSendResponseAndCloseAsync ( content : "hello world" ) ;
179
+ Assert . All ( headers , header => Assert . DoesNotContain ( "Authorization" , header ) ) ;
180
+ }
181
+ } ) ;
182
+ }
183
+
184
+ [ Theory ]
185
+ [ InlineData ( null , "WWW-Authenticate: Basic realm=\" hello\" \r \n " ) ]
186
+ [ InlineData ( "Basic" , "WWW-Authenticate: Basic realm=\" hello\" \r \n " ) ]
187
+ public async Task PreAuthenticate_FirstRequestNoHeaderAndAuthenticates_SecondRequestPreauthenticates ( string credCacheScheme , string authResponse )
188
+ {
189
+ await LoopbackServer . CreateClientAndServerAsync ( async uri =>
190
+ {
191
+ using ( HttpClientHandler handler = CreateHttpClientHandler ( ) )
192
+ using ( var client = new HttpClient ( handler ) )
193
+ {
194
+ client . DefaultRequestHeaders . ConnectionClose = true ; // for simplicity of not needing to know every handler's pooling policy
195
+ handler . PreAuthenticate = true ;
196
+ switch ( credCacheScheme )
197
+ {
198
+ case null :
199
+ handler . Credentials = s_credentials ;
200
+ break ;
201
+
202
+ default :
203
+ var cc = new CredentialCache ( ) ;
204
+ cc . Add ( uri , credCacheScheme , s_credentials ) ;
205
+ handler . Credentials = cc ;
206
+ break ;
207
+ }
208
+
209
+ Assert . Equal ( "hello world" , await client . GetStringAsync ( uri ) ) ;
210
+ Assert . Equal ( "hello world" , await client . GetStringAsync ( uri ) ) ;
211
+ }
212
+ } ,
213
+ async server =>
214
+ {
215
+ List < string > headers = headers = await server . AcceptConnectionSendResponseAndCloseAsync ( HttpStatusCode . Unauthorized , authResponse ) ;
216
+ Assert . All ( headers , header => Assert . DoesNotContain ( "Authorization" , header ) ) ;
217
+
218
+ for ( int i = 0 ; i < 2 ; i ++ )
219
+ {
220
+ headers = await server . AcceptConnectionSendResponseAndCloseAsync ( content : "hello world" ) ;
221
+ Assert . Contains ( headers , header => header . Contains ( "Authorization" ) ) ;
222
+ }
223
+ } ) ;
224
+ }
225
+
226
+ [ Fact ]
227
+ public async Task PreAuthenticate_SuccessfulBasicButThenFails_DoesntLoopInfinitely ( )
228
+ {
229
+ await LoopbackServer . CreateClientAndServerAsync ( async uri =>
230
+ {
231
+ using ( HttpClientHandler handler = CreateHttpClientHandler ( ) )
232
+ using ( var client = new HttpClient ( handler ) )
233
+ {
234
+ client . DefaultRequestHeaders . ConnectionClose = true ; // for simplicity of not needing to know every handler's pooling policy
235
+ handler . PreAuthenticate = true ;
236
+ handler . Credentials = s_credentials ;
237
+
238
+ // First two requests: initially without auth header, then with
239
+ Assert . Equal ( "hello world" , await client . GetStringAsync ( uri ) ) ;
240
+
241
+ // Attempt preauth, and when that fails, give up.
242
+ using ( HttpResponseMessage resp = await client . GetAsync ( uri ) )
243
+ {
244
+ Assert . Equal ( HttpStatusCode . Unauthorized , resp . StatusCode ) ;
245
+ }
246
+ }
247
+ } ,
248
+ async server =>
249
+ {
250
+ // First request, no auth header, challenge Basic
251
+ List < string > headers = headers = await server . AcceptConnectionSendResponseAndCloseAsync ( HttpStatusCode . Unauthorized , "WWW-Authenticate: Basic realm=\" hello\" \r \n " ) ;
252
+ Assert . All ( headers , header => Assert . DoesNotContain ( "Authorization" , header ) ) ;
253
+
254
+ // Second request, contains Basic auth header
255
+ headers = await server . AcceptConnectionSendResponseAndCloseAsync ( content : "hello world" ) ;
256
+ Assert . Contains ( headers , header => header . Contains ( "Authorization" ) ) ;
257
+
258
+ // Third request, contains Basic auth header but challenges anyway
259
+ headers = headers = await server . AcceptConnectionSendResponseAndCloseAsync ( HttpStatusCode . Unauthorized , "WWW-Authenticate: Basic realm=\" hello\" \r \n " ) ;
260
+ Assert . Contains ( headers , header => header . Contains ( "Authorization" ) ) ;
261
+
262
+ if ( IsNetfxHandler )
263
+ {
264
+ // For some reason, netfx tries one more time.
265
+ headers = headers = await server . AcceptConnectionSendResponseAndCloseAsync ( HttpStatusCode . Unauthorized , "WWW-Authenticate: Basic realm=\" hello\" \r \n " ) ;
266
+ Assert . Contains ( headers , header => header . Contains ( "Authorization" ) ) ;
267
+ }
268
+ } ) ;
269
+ }
270
+
271
+ [ Fact ]
272
+ public async Task PreAuthenticate_SuccessfulBasic_ThenDigestChallenged ( )
273
+ {
274
+ if ( IsWinHttpHandler )
275
+ {
276
+ // WinHttpHandler fails with Unauthorized after the basic preauth fails.
277
+ return ;
278
+ }
279
+
280
+ await LoopbackServer . CreateClientAndServerAsync ( async uri =>
281
+ {
282
+ using ( HttpClientHandler handler = CreateHttpClientHandler ( ) )
283
+ using ( var client = new HttpClient ( handler ) )
284
+ {
285
+ client . DefaultRequestHeaders . ConnectionClose = true ; // for simplicity of not needing to know every handler's pooling policy
286
+ handler . PreAuthenticate = true ;
287
+ handler . Credentials = s_credentials ;
288
+
289
+ Assert . Equal ( "hello world" , await client . GetStringAsync ( uri ) ) ;
290
+ Assert . Equal ( "hello world" , await client . GetStringAsync ( uri ) ) ;
291
+ }
292
+ } ,
293
+ async server =>
294
+ {
295
+ // First request, no auth header, challenge Basic
296
+ List < string > headers = headers = await server . AcceptConnectionSendResponseAndCloseAsync ( HttpStatusCode . Unauthorized , "WWW-Authenticate: Basic realm=\" hello\" \r \n " ) ;
297
+ Assert . All ( headers , header => Assert . DoesNotContain ( "Authorization" , header ) ) ;
298
+
299
+ // Second request, contains Basic auth header, success
300
+ headers = await server . AcceptConnectionSendResponseAndCloseAsync ( content : "hello world" ) ;
301
+ Assert . Contains ( headers , header => header . Contains ( "Authorization" ) ) ;
302
+
303
+ // Third request, contains Basic auth header, challenge digest
304
+ headers = await server . AcceptConnectionSendResponseAndCloseAsync ( HttpStatusCode . Unauthorized , "WWW-Authenticate: Digest realm=\" hello\" , nonce=\" testnonce\" \r \n " ) ;
305
+ Assert . Contains ( headers , header => header . Contains ( "Authorization: Basic" ) ) ;
306
+
307
+ // Fourth request, contains Digest auth header, success
308
+ headers = await server . AcceptConnectionSendResponseAndCloseAsync ( content : "hello world" ) ;
309
+ Assert . Contains ( headers , header => header . Contains ( "Authorization: Digest" ) ) ;
310
+ } ) ;
311
+ }
137
312
}
138
313
}
0 commit comments