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. 4 5 #include <set> 6 #include <string> 7 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" 22 23 namespace net { 24 25 namespace { 26 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, 36 origin, 37 BoundNetLog())); 38 return auth_handler; 39 } 40 41 HttpResponseHeaders* HeadersFromResponseText(const std::string& response) { 42 return new HttpResponseHeaders( 43 HttpUtil::AssembleRawHeaders(response.c_str(), response.length())); 44 } 45 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( 56 mock_handler.get(), 57 headers.get(), 58 HttpAuth::AUTH_SERVER, 59 disabled_schemes, 60 challenge_used); 61 } 62 63 } // namespace 64 65 TEST(HttpAuthTest, ChooseBestChallenge) { 66 static const struct { 67 const char* headers; 68 HttpAuth::Scheme challenge_scheme; 69 const char* challenge_realm; 70 } tests[] = { 71 { 72 // Basic is the only challenge type, pick it. 73 "Y: Digest realm=\"X\", nonce=\"aaaaaaaaaa\"\n" 74 "www-authenticate: Basic realm=\"BasicRealm\"\n", 75 76 HttpAuth::AUTH_SCHEME_BASIC, 77 "BasicRealm", 78 }, 79 { 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", 83 84 HttpAuth::AUTH_SCHEME_MAX, 85 "", 86 }, 87 { 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", 93 94 HttpAuth::AUTH_SCHEME_DIGEST, 95 "DigestRealm", 96 }, 97 { 98 // Handle an empty header correctly. 99 "Y: Digest realm=\"X\", nonce=\"aaaaaaaaaa\"\n" 100 "www-authenticate:\n", 101 102 HttpAuth::AUTH_SCHEME_MAX, 103 "", 104 }, 105 { 106 "WWW-Authenticate: Negotiate\n" 107 "WWW-Authenticate: NTLM\n", 108 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 113 // for gssapi. 114 HttpAuth::AUTH_SCHEME_NEGOTIATE, 115 #else 116 // On systems that don't use Kerberos fall back to NTLM. 117 HttpAuth::AUTH_SCHEME_NTLM, 118 #endif // defined(USE_KERBEROS) 119 "", 120 } 121 }; 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); 130 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)); 137 138 scoped_ptr<HttpAuthHandler> handler; 139 HttpAuth::ChooseBestChallenge(http_auth_handler_factory.get(), 140 headers.get(), 141 HttpAuth::AUTH_SERVER, 142 origin, 143 disabled_schemes, 144 BoundNetLog(), 145 &handler); 146 147 if (handler.get()) { 148 EXPECT_EQ(tests[i].challenge_scheme, handler->auth_scheme()); 149 EXPECT_STREQ(tests[i].challenge_realm, handler->realm().c_str()); 150 } else { 151 EXPECT_EQ(HttpAuth::AUTH_SCHEME_MAX, tests[i].challenge_scheme); 152 EXPECT_STREQ("", tests[i].challenge_realm); 153 } 154 } 155 } 156 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"; 178 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). 183 EXPECT_EQ( 184 HttpAuth::AUTHORIZATION_RESULT_REJECT, 185 HandleChallengeResponse(false, kMockChallenge, &challenge_used)); 186 EXPECT_EQ("Mock token_here", challenge_used); 187 188 EXPECT_EQ( 189 HttpAuth::AUTHORIZATION_RESULT_REJECT, 190 HandleChallengeResponse(false, kBasicChallenge, &challenge_used)); 191 EXPECT_EQ("", challenge_used); 192 193 EXPECT_EQ( 194 HttpAuth::AUTHORIZATION_RESULT_REJECT, 195 HandleChallengeResponse(false, kMissingChallenge, &challenge_used)); 196 EXPECT_EQ("", challenge_used); 197 198 EXPECT_EQ( 199 HttpAuth::AUTHORIZATION_RESULT_REJECT, 200 HandleChallengeResponse(false, kEmptyChallenge, &challenge_used)); 201 EXPECT_EQ("", challenge_used); 202 203 EXPECT_EQ( 204 HttpAuth::AUTHORIZATION_RESULT_REJECT, 205 HandleChallengeResponse(false, kBasicAndMockChallenges, &challenge_used)); 206 EXPECT_EQ("Mock token_here", challenge_used); 207 208 EXPECT_EQ( 209 HttpAuth::AUTHORIZATION_RESULT_REJECT, 210 HandleChallengeResponse(false, kTwoMockChallenges, &challenge_used)); 211 EXPECT_EQ("Mock token_a", challenge_used); 212 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 216 // a rejection. 217 EXPECT_EQ( 218 HttpAuth::AUTHORIZATION_RESULT_ACCEPT, 219 HandleChallengeResponse(true, kMockChallenge, &challenge_used)); 220 EXPECT_EQ("Mock token_here", challenge_used); 221 222 EXPECT_EQ( 223 HttpAuth::AUTHORIZATION_RESULT_REJECT, 224 HandleChallengeResponse(true, kBasicChallenge, &challenge_used)); 225 EXPECT_EQ("", challenge_used); 226 227 EXPECT_EQ( 228 HttpAuth::AUTHORIZATION_RESULT_REJECT, 229 HandleChallengeResponse(true, kMissingChallenge, &challenge_used)); 230 EXPECT_EQ("", challenge_used); 231 232 EXPECT_EQ( 233 HttpAuth::AUTHORIZATION_RESULT_REJECT, 234 HandleChallengeResponse(true, kEmptyChallenge, &challenge_used)); 235 EXPECT_EQ("", challenge_used); 236 237 EXPECT_EQ( 238 HttpAuth::AUTHORIZATION_RESULT_ACCEPT, 239 HandleChallengeResponse(true, kBasicAndMockChallenges, &challenge_used)); 240 EXPECT_EQ("Mock token_here", challenge_used); 241 242 EXPECT_EQ( 243 HttpAuth::AUTHORIZATION_RESULT_ACCEPT, 244 HandleChallengeResponse(true, kTwoMockChallenges, &challenge_used)); 245 EXPECT_EQ("Mock token_a", challenge_used); 246 } 247 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(); 253 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()); 261 } 262 263 // Use a name=value property with no quote marks. 264 TEST(HttpAuthTest, ChallengeTokenizerNoQuotes) { 265 std::string challenge_str = "Basic realm=foobar (at) baz.com"; 266 HttpAuth::ChallengeTokenizer challenge(challenge_str.begin(), 267 challenge_str.end()); 268 HttpUtil::NameValuePairsIterator parameters = challenge.param_pairs(); 269 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 (at) baz.com"), parameters.value()); 276 EXPECT_FALSE(parameters.GetNext()); 277 } 278 279 // Use a name=value property with mismatching quote marks. 280 TEST(HttpAuthTest, ChallengeTokenizerMismatchedQuotes) { 281 std::string challenge_str = "Basic realm=\"foobar (at) baz.com"; 282 HttpAuth::ChallengeTokenizer challenge(challenge_str.begin(), 283 challenge_str.end()); 284 HttpUtil::NameValuePairsIterator parameters = challenge.param_pairs(); 285 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 (at) baz.com"), parameters.value()); 292 EXPECT_FALSE(parameters.GetNext()); 293 } 294 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(); 301 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()); 309 } 310 311 // Use a name=value property with mismatching quote marks and spaces in the 312 // value. 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(); 318 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()); 326 } 327 328 // Use multiple name=value properties with mismatching quote marks in the last 329 // value. 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(); 335 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()); 351 } 352 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(); 359 360 EXPECT_TRUE(parameters.valid()); 361 EXPECT_EQ(std::string("Digest"), challenge.scheme()); 362 EXPECT_FALSE(parameters.GetNext()); 363 EXPECT_FALSE(parameters.valid()); 364 } 365 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(); 373 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()); 390 } 391 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(); 398 399 EXPECT_TRUE(parameters.valid()); 400 EXPECT_EQ(std::string("NTLM"), challenge.scheme()); 401 EXPECT_FALSE(parameters.GetNext()); 402 } 403 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()); 409 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()); 413 } 414 415 TEST(HttpAuthTest, GetChallengeHeaderName) { 416 std::string name; 417 418 name = HttpAuth::GetChallengeHeaderName(HttpAuth::AUTH_SERVER); 419 EXPECT_STREQ("WWW-Authenticate", name.c_str()); 420 421 name = HttpAuth::GetChallengeHeaderName(HttpAuth::AUTH_PROXY); 422 EXPECT_STREQ("Proxy-Authenticate", name.c_str()); 423 } 424 425 TEST(HttpAuthTest, GetAuthorizationHeaderName) { 426 std::string name; 427 428 name = HttpAuth::GetAuthorizationHeaderName(HttpAuth::AUTH_SERVER); 429 EXPECT_STREQ("Authorization", name.c_str()); 430 431 name = HttpAuth::GetAuthorizationHeaderName(HttpAuth::AUTH_PROXY); 432 EXPECT_STREQ("Proxy-Authorization", name.c_str()); 433 } 434 435 } // namespace net 436