1 // Copyright (c) 2011 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
8 #include "base/memory/ref_counted.h"
9 #include "base/memory/scoped_ptr.h"
10 #include "base/strings/string_util.h"
11 #include "net/base/net_errors.h"
12 #include "net/dns/mock_host_resolver.h"
13 #include "net/http/http_auth.h"
14 #include "net/http/http_auth_filter.h"
15 #include "net/http/http_auth_handler.h"
16 #include "net/http/http_auth_handler_factory.h"
17 #include "net/http/http_auth_handler_mock.h"
18 #include "net/http/http_response_headers.h"
19 #include "net/http/http_util.h"
20 #include "net/http/mock_allow_url_security_manager.h"
21 #include "testing/gtest/include/gtest/gtest.h"
27 HttpAuthHandlerMock
* CreateMockHandler(bool connection_based
) {
28 HttpAuthHandlerMock
* auth_handler
= new HttpAuthHandlerMock();
29 auth_handler
->set_connection_based(connection_based
);
30 std::string challenge_text
= "Basic";
31 HttpAuth::ChallengeTokenizer
challenge(challenge_text
.begin(),
32 challenge_text
.end());
33 GURL
origin("www.example.com");
34 EXPECT_TRUE(auth_handler
->InitFromChallenge(&challenge
,
35 HttpAuth::AUTH_SERVER
,
41 HttpResponseHeaders
* HeadersFromResponseText(const std::string
& response
) {
42 return new HttpResponseHeaders(
43 HttpUtil::AssembleRawHeaders(response
.c_str(), response
.length()));
46 HttpAuth::AuthorizationResult
HandleChallengeResponse(
47 bool connection_based
,
48 const std::string
& headers_text
,
49 std::string
* challenge_used
) {
50 scoped_ptr
<HttpAuthHandlerMock
> mock_handler(
51 CreateMockHandler(connection_based
));
52 std::set
<HttpAuth::Scheme
> disabled_schemes
;
53 scoped_refptr
<HttpResponseHeaders
> headers(
54 HeadersFromResponseText(headers_text
));
55 return HttpAuth::HandleChallengeResponse(
58 HttpAuth::AUTH_SERVER
,
65 TEST(HttpAuthTest
, ChooseBestChallenge
) {
68 HttpAuth::Scheme challenge_scheme
;
69 const char* challenge_realm
;
72 // Basic is the only challenge type, pick it.
73 "Y: Digest realm=\"X\", nonce=\"aaaaaaaaaa\"\n"
74 "www-authenticate: Basic realm=\"BasicRealm\"\n",
76 HttpAuth::AUTH_SCHEME_BASIC
,
80 // Fake is the only challenge type, but it is unsupported.
81 "Y: Digest realm=\"FooBar\", nonce=\"aaaaaaaaaa\"\n"
82 "www-authenticate: Fake realm=\"FooBar\"\n",
84 HttpAuth::AUTH_SCHEME_MAX
,
88 // Pick Digest over Basic.
89 "www-authenticate: Basic realm=\"FooBar\"\n"
90 "www-authenticate: Fake realm=\"FooBar\"\n"
91 "www-authenticate: nonce=\"aaaaaaaaaa\"\n"
92 "www-authenticate: Digest realm=\"DigestRealm\", nonce=\"aaaaaaaaaa\"\n",
94 HttpAuth::AUTH_SCHEME_DIGEST
,
98 // Handle an empty header correctly.
99 "Y: Digest realm=\"X\", nonce=\"aaaaaaaaaa\"\n"
100 "www-authenticate:\n",
102 HttpAuth::AUTH_SCHEME_MAX
,
106 "WWW-Authenticate: Negotiate\n"
107 "WWW-Authenticate: NTLM\n",
109 #if defined(USE_KERBEROS)
110 // Choose Negotiate over NTLM on all platforms.
111 // TODO(ahendrickson): This may be flaky on Linux and OSX as it
112 // relies on being able to load one of the known .so files
114 HttpAuth::AUTH_SCHEME_NEGOTIATE
,
116 // On systems that don't use Kerberos fall back to NTLM.
117 HttpAuth::AUTH_SCHEME_NTLM
,
118 #endif // defined(USE_KERBEROS)
122 GURL
origin("http://www.example.com");
123 std::set
<HttpAuth::Scheme
> disabled_schemes
;
124 MockAllowURLSecurityManager url_security_manager
;
125 scoped_ptr
<HostResolver
> host_resolver(new MockHostResolver());
126 scoped_ptr
<HttpAuthHandlerRegistryFactory
> http_auth_handler_factory(
127 HttpAuthHandlerFactory::CreateDefault(host_resolver
.get()));
128 http_auth_handler_factory
->SetURLSecurityManager(
129 "negotiate", &url_security_manager
);
131 for (size_t i
= 0; i
< ARRAYSIZE_UNSAFE(tests
); ++i
) {
132 // Make a HttpResponseHeaders object.
133 std::string
headers_with_status_line("HTTP/1.1 401 Unauthorized\n");
134 headers_with_status_line
+= tests
[i
].headers
;
135 scoped_refptr
<HttpResponseHeaders
> headers(
136 HeadersFromResponseText(headers_with_status_line
));
138 scoped_ptr
<HttpAuthHandler
> handler
;
139 HttpAuth::ChooseBestChallenge(http_auth_handler_factory
.get(),
141 HttpAuth::AUTH_SERVER
,
148 EXPECT_EQ(tests
[i
].challenge_scheme
, handler
->auth_scheme());
149 EXPECT_STREQ(tests
[i
].challenge_realm
, handler
->realm().c_str());
151 EXPECT_EQ(HttpAuth::AUTH_SCHEME_MAX
, tests
[i
].challenge_scheme
);
152 EXPECT_STREQ("", tests
[i
].challenge_realm
);
157 TEST(HttpAuthTest
, HandleChallengeResponse
) {
158 std::string challenge_used
;
159 const char* const kMockChallenge
=
160 "HTTP/1.1 401 Unauthorized\n"
161 "WWW-Authenticate: Mock token_here\n";
162 const char* const kBasicChallenge
=
163 "HTTP/1.1 401 Unauthorized\n"
164 "WWW-Authenticate: Basic realm=\"happy\"\n";
165 const char* const kMissingChallenge
=
166 "HTTP/1.1 401 Unauthorized\n";
167 const char* const kEmptyChallenge
=
168 "HTTP/1.1 401 Unauthorized\n"
169 "WWW-Authenticate: \n";
170 const char* const kBasicAndMockChallenges
=
171 "HTTP/1.1 401 Unauthorized\n"
172 "WWW-Authenticate: Basic realm=\"happy\"\n"
173 "WWW-Authenticate: Mock token_here\n";
174 const char* const kTwoMockChallenges
=
175 "HTTP/1.1 401 Unauthorized\n"
176 "WWW-Authenticate: Mock token_a\n"
177 "WWW-Authenticate: Mock token_b\n";
179 // Request based schemes should treat any new challenges as rejections of the
180 // previous authentication attempt. (There is a slight exception for digest
181 // authentication and the stale parameter, but that is covered in the
182 // http_auth_handler_digest_unittests).
184 HttpAuth::AUTHORIZATION_RESULT_REJECT
,
185 HandleChallengeResponse(false, kMockChallenge
, &challenge_used
));
186 EXPECT_EQ("Mock token_here", challenge_used
);
189 HttpAuth::AUTHORIZATION_RESULT_REJECT
,
190 HandleChallengeResponse(false, kBasicChallenge
, &challenge_used
));
191 EXPECT_EQ("", challenge_used
);
194 HttpAuth::AUTHORIZATION_RESULT_REJECT
,
195 HandleChallengeResponse(false, kMissingChallenge
, &challenge_used
));
196 EXPECT_EQ("", challenge_used
);
199 HttpAuth::AUTHORIZATION_RESULT_REJECT
,
200 HandleChallengeResponse(false, kEmptyChallenge
, &challenge_used
));
201 EXPECT_EQ("", challenge_used
);
204 HttpAuth::AUTHORIZATION_RESULT_REJECT
,
205 HandleChallengeResponse(false, kBasicAndMockChallenges
, &challenge_used
));
206 EXPECT_EQ("Mock token_here", challenge_used
);
209 HttpAuth::AUTHORIZATION_RESULT_REJECT
,
210 HandleChallengeResponse(false, kTwoMockChallenges
, &challenge_used
));
211 EXPECT_EQ("Mock token_a", challenge_used
);
213 // Connection based schemes will treat new auth challenges for the same scheme
214 // as acceptance (and continuance) of the current approach. If there are
215 // no auth challenges for the same scheme, the response will be treated as
218 HttpAuth::AUTHORIZATION_RESULT_ACCEPT
,
219 HandleChallengeResponse(true, kMockChallenge
, &challenge_used
));
220 EXPECT_EQ("Mock token_here", challenge_used
);
223 HttpAuth::AUTHORIZATION_RESULT_REJECT
,
224 HandleChallengeResponse(true, kBasicChallenge
, &challenge_used
));
225 EXPECT_EQ("", challenge_used
);
228 HttpAuth::AUTHORIZATION_RESULT_REJECT
,
229 HandleChallengeResponse(true, kMissingChallenge
, &challenge_used
));
230 EXPECT_EQ("", challenge_used
);
233 HttpAuth::AUTHORIZATION_RESULT_REJECT
,
234 HandleChallengeResponse(true, kEmptyChallenge
, &challenge_used
));
235 EXPECT_EQ("", challenge_used
);
238 HttpAuth::AUTHORIZATION_RESULT_ACCEPT
,
239 HandleChallengeResponse(true, kBasicAndMockChallenges
, &challenge_used
));
240 EXPECT_EQ("Mock token_here", challenge_used
);
243 HttpAuth::AUTHORIZATION_RESULT_ACCEPT
,
244 HandleChallengeResponse(true, kTwoMockChallenges
, &challenge_used
));
245 EXPECT_EQ("Mock token_a", challenge_used
);
248 TEST(HttpAuthTest
, ChallengeTokenizer
) {
249 std::string challenge_str
= "Basic realm=\"foobar\"";
250 HttpAuth::ChallengeTokenizer
challenge(challenge_str
.begin(),
251 challenge_str
.end());
252 HttpUtil::NameValuePairsIterator parameters
= challenge
.param_pairs();
254 EXPECT_TRUE(parameters
.valid());
255 EXPECT_EQ(std::string("Basic"), challenge
.scheme());
256 EXPECT_TRUE(parameters
.GetNext());
257 EXPECT_TRUE(parameters
.valid());
258 EXPECT_EQ(std::string("realm"), parameters
.name());
259 EXPECT_EQ(std::string("foobar"), parameters
.value());
260 EXPECT_FALSE(parameters
.GetNext());
263 // Use a name=value property with no quote marks.
264 TEST(HttpAuthTest
, ChallengeTokenizerNoQuotes
) {
265 std::string challenge_str
= "Basic realm=foobar@baz.com";
266 HttpAuth::ChallengeTokenizer
challenge(challenge_str
.begin(),
267 challenge_str
.end());
268 HttpUtil::NameValuePairsIterator parameters
= challenge
.param_pairs();
270 EXPECT_TRUE(parameters
.valid());
271 EXPECT_EQ(std::string("Basic"), challenge
.scheme());
272 EXPECT_TRUE(parameters
.GetNext());
273 EXPECT_TRUE(parameters
.valid());
274 EXPECT_EQ(std::string("realm"), parameters
.name());
275 EXPECT_EQ(std::string("foobar@baz.com"), parameters
.value());
276 EXPECT_FALSE(parameters
.GetNext());
279 // Use a name=value property with mismatching quote marks.
280 TEST(HttpAuthTest
, ChallengeTokenizerMismatchedQuotes
) {
281 std::string challenge_str
= "Basic realm=\"foobar@baz.com";
282 HttpAuth::ChallengeTokenizer
challenge(challenge_str
.begin(),
283 challenge_str
.end());
284 HttpUtil::NameValuePairsIterator parameters
= challenge
.param_pairs();
286 EXPECT_TRUE(parameters
.valid());
287 EXPECT_EQ(std::string("Basic"), challenge
.scheme());
288 EXPECT_TRUE(parameters
.GetNext());
289 EXPECT_TRUE(parameters
.valid());
290 EXPECT_EQ(std::string("realm"), parameters
.name());
291 EXPECT_EQ(std::string("foobar@baz.com"), parameters
.value());
292 EXPECT_FALSE(parameters
.GetNext());
295 // Use a name= property without a value and with mismatching quote marks.
296 TEST(HttpAuthTest
, ChallengeTokenizerMismatchedQuotesNoValue
) {
297 std::string challenge_str
= "Basic realm=\"";
298 HttpAuth::ChallengeTokenizer
challenge(challenge_str
.begin(),
299 challenge_str
.end());
300 HttpUtil::NameValuePairsIterator parameters
= challenge
.param_pairs();
302 EXPECT_TRUE(parameters
.valid());
303 EXPECT_EQ(std::string("Basic"), challenge
.scheme());
304 EXPECT_TRUE(parameters
.GetNext());
305 EXPECT_TRUE(parameters
.valid());
306 EXPECT_EQ(std::string("realm"), parameters
.name());
307 EXPECT_EQ(std::string(), parameters
.value());
308 EXPECT_FALSE(parameters
.GetNext());
311 // Use a name=value property with mismatching quote marks and spaces in the
313 TEST(HttpAuthTest
, ChallengeTokenizerMismatchedQuotesSpaces
) {
314 std::string challenge_str
= "Basic realm=\"foo bar";
315 HttpAuth::ChallengeTokenizer
challenge(challenge_str
.begin(),
316 challenge_str
.end());
317 HttpUtil::NameValuePairsIterator parameters
= challenge
.param_pairs();
319 EXPECT_TRUE(parameters
.valid());
320 EXPECT_EQ(std::string("Basic"), challenge
.scheme());
321 EXPECT_TRUE(parameters
.GetNext());
322 EXPECT_TRUE(parameters
.valid());
323 EXPECT_EQ(std::string("realm"), parameters
.name());
324 EXPECT_EQ(std::string("foo bar"), parameters
.value());
325 EXPECT_FALSE(parameters
.GetNext());
328 // Use multiple name=value properties with mismatching quote marks in the last
330 TEST(HttpAuthTest
, ChallengeTokenizerMismatchedQuotesMultiple
) {
331 std::string challenge_str
= "Digest qop=auth-int, algorithm=md5, realm=\"foo";
332 HttpAuth::ChallengeTokenizer
challenge(challenge_str
.begin(),
333 challenge_str
.end());
334 HttpUtil::NameValuePairsIterator parameters
= challenge
.param_pairs();
336 EXPECT_TRUE(parameters
.valid());
337 EXPECT_EQ(std::string("Digest"), challenge
.scheme());
338 EXPECT_TRUE(parameters
.GetNext());
339 EXPECT_TRUE(parameters
.valid());
340 EXPECT_EQ(std::string("qop"), parameters
.name());
341 EXPECT_EQ(std::string("auth-int"), parameters
.value());
342 EXPECT_TRUE(parameters
.GetNext());
343 EXPECT_TRUE(parameters
.valid());
344 EXPECT_EQ(std::string("algorithm"), parameters
.name());
345 EXPECT_EQ(std::string("md5"), parameters
.value());
346 EXPECT_TRUE(parameters
.GetNext());
347 EXPECT_TRUE(parameters
.valid());
348 EXPECT_EQ(std::string("realm"), parameters
.name());
349 EXPECT_EQ(std::string("foo"), parameters
.value());
350 EXPECT_FALSE(parameters
.GetNext());
353 // Use a name= property which has no value.
354 TEST(HttpAuthTest
, ChallengeTokenizerNoValue
) {
355 std::string challenge_str
= "Digest qop=";
356 HttpAuth::ChallengeTokenizer
challenge(
357 challenge_str
.begin(), challenge_str
.end());
358 HttpUtil::NameValuePairsIterator parameters
= challenge
.param_pairs();
360 EXPECT_TRUE(parameters
.valid());
361 EXPECT_EQ(std::string("Digest"), challenge
.scheme());
362 EXPECT_FALSE(parameters
.GetNext());
363 EXPECT_FALSE(parameters
.valid());
366 // Specify multiple properties, comma separated.
367 TEST(HttpAuthTest
, ChallengeTokenizerMultiple
) {
368 std::string challenge_str
=
369 "Digest algorithm=md5, realm=\"Oblivion\", qop=auth-int";
370 HttpAuth::ChallengeTokenizer
challenge(challenge_str
.begin(),
371 challenge_str
.end());
372 HttpUtil::NameValuePairsIterator parameters
= challenge
.param_pairs();
374 EXPECT_TRUE(parameters
.valid());
375 EXPECT_EQ(std::string("Digest"), challenge
.scheme());
376 EXPECT_TRUE(parameters
.GetNext());
377 EXPECT_TRUE(parameters
.valid());
378 EXPECT_EQ(std::string("algorithm"), parameters
.name());
379 EXPECT_EQ(std::string("md5"), parameters
.value());
380 EXPECT_TRUE(parameters
.GetNext());
381 EXPECT_TRUE(parameters
.valid());
382 EXPECT_EQ(std::string("realm"), parameters
.name());
383 EXPECT_EQ(std::string("Oblivion"), parameters
.value());
384 EXPECT_TRUE(parameters
.GetNext());
385 EXPECT_TRUE(parameters
.valid());
386 EXPECT_EQ(std::string("qop"), parameters
.name());
387 EXPECT_EQ(std::string("auth-int"), parameters
.value());
388 EXPECT_FALSE(parameters
.GetNext());
389 EXPECT_TRUE(parameters
.valid());
392 // Use a challenge which has no property.
393 TEST(HttpAuthTest
, ChallengeTokenizerNoProperty
) {
394 std::string challenge_str
= "NTLM";
395 HttpAuth::ChallengeTokenizer
challenge(
396 challenge_str
.begin(), challenge_str
.end());
397 HttpUtil::NameValuePairsIterator parameters
= challenge
.param_pairs();
399 EXPECT_TRUE(parameters
.valid());
400 EXPECT_EQ(std::string("NTLM"), challenge
.scheme());
401 EXPECT_FALSE(parameters
.GetNext());
404 // Use a challenge with Base64 encoded token.
405 TEST(HttpAuthTest
, ChallengeTokenizerBase64
) {
406 std::string challenge_str
= "NTLM SGVsbG8sIFdvcmxkCg===";
407 HttpAuth::ChallengeTokenizer
challenge(challenge_str
.begin(),
408 challenge_str
.end());
410 EXPECT_EQ(std::string("NTLM"), challenge
.scheme());
411 // Notice the two equal statements below due to padding removal.
412 EXPECT_EQ(std::string("SGVsbG8sIFdvcmxkCg=="), challenge
.base64_param());
415 TEST(HttpAuthTest
, GetChallengeHeaderName
) {
418 name
= HttpAuth::GetChallengeHeaderName(HttpAuth::AUTH_SERVER
);
419 EXPECT_STREQ("WWW-Authenticate", name
.c_str());
421 name
= HttpAuth::GetChallengeHeaderName(HttpAuth::AUTH_PROXY
);
422 EXPECT_STREQ("Proxy-Authenticate", name
.c_str());
425 TEST(HttpAuthTest
, GetAuthorizationHeaderName
) {
428 name
= HttpAuth::GetAuthorizationHeaderName(HttpAuth::AUTH_SERVER
);
429 EXPECT_STREQ("Authorization", name
.c_str());
431 name
= HttpAuth::GetAuthorizationHeaderName(HttpAuth::AUTH_PROXY
);
432 EXPECT_STREQ("Proxy-Authorization", name
.c_str());