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/string_util.h" 11 #include "net/base/mock_host_resolver.h" 12 #include "net/base/net_errors.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 // Choose Negotiate over NTLM on all platforms. 107 // TODO(ahendrickson): This may be flaky on Linux and OSX as it 108 // relies on being able to load one of the known .so files 109 // for gssapi. 110 "WWW-Authenticate: Negotiate\n" 111 "WWW-Authenticate: NTLM\n", 112 113 HttpAuth::AUTH_SCHEME_NEGOTIATE, 114 "", 115 } 116 }; 117 GURL origin("http://www.example.com"); 118 std::set<HttpAuth::Scheme> disabled_schemes; 119 MockAllowURLSecurityManager url_security_manager; 120 scoped_ptr<HostResolver> host_resolver(new MockHostResolver()); 121 scoped_ptr<HttpAuthHandlerRegistryFactory> http_auth_handler_factory( 122 HttpAuthHandlerFactory::CreateDefault(host_resolver.get())); 123 http_auth_handler_factory->SetURLSecurityManager( 124 "negotiate", &url_security_manager); 125 126 for (size_t i = 0; i < ARRAYSIZE_UNSAFE(tests); ++i) { 127 // Make a HttpResponseHeaders object. 128 std::string headers_with_status_line("HTTP/1.1 401 Unauthorized\n"); 129 headers_with_status_line += tests[i].headers; 130 scoped_refptr<HttpResponseHeaders> headers( 131 HeadersFromResponseText(headers_with_status_line)); 132 133 scoped_ptr<HttpAuthHandler> handler; 134 HttpAuth::ChooseBestChallenge(http_auth_handler_factory.get(), 135 headers.get(), 136 HttpAuth::AUTH_SERVER, 137 origin, 138 disabled_schemes, 139 BoundNetLog(), 140 &handler); 141 142 if (handler.get()) { 143 EXPECT_EQ(tests[i].challenge_scheme, handler->auth_scheme()); 144 EXPECT_STREQ(tests[i].challenge_realm, handler->realm().c_str()); 145 } else { 146 EXPECT_EQ(HttpAuth::AUTH_SCHEME_MAX, tests[i].challenge_scheme); 147 EXPECT_STREQ("", tests[i].challenge_realm); 148 } 149 } 150 } 151 152 TEST(HttpAuthTest, HandleChallengeResponse) { 153 std::string challenge_used; 154 const char* const kMockChallenge = 155 "HTTP/1.1 401 Unauthorized\n" 156 "WWW-Authenticate: Mock token_here\n"; 157 const char* const kBasicChallenge = 158 "HTTP/1.1 401 Unauthorized\n" 159 "WWW-Authenticate: Basic realm=\"happy\"\n"; 160 const char* const kMissingChallenge = 161 "HTTP/1.1 401 Unauthorized\n"; 162 const char* const kEmptyChallenge = 163 "HTTP/1.1 401 Unauthorized\n" 164 "WWW-Authenticate: \n"; 165 const char* const kBasicAndMockChallenges = 166 "HTTP/1.1 401 Unauthorized\n" 167 "WWW-Authenticate: Basic realm=\"happy\"\n" 168 "WWW-Authenticate: Mock token_here\n"; 169 const char* const kTwoMockChallenges = 170 "HTTP/1.1 401 Unauthorized\n" 171 "WWW-Authenticate: Mock token_a\n" 172 "WWW-Authenticate: Mock token_b\n"; 173 174 // Request based schemes should treat any new challenges as rejections of the 175 // previous authentication attempt. (There is a slight exception for digest 176 // authentication and the stale parameter, but that is covered in the 177 // http_auth_handler_digest_unittests). 178 EXPECT_EQ( 179 HttpAuth::AUTHORIZATION_RESULT_REJECT, 180 HandleChallengeResponse(false, kMockChallenge, &challenge_used)); 181 EXPECT_EQ("Mock token_here", challenge_used); 182 183 EXPECT_EQ( 184 HttpAuth::AUTHORIZATION_RESULT_REJECT, 185 HandleChallengeResponse(false, kBasicChallenge, &challenge_used)); 186 EXPECT_EQ("", challenge_used); 187 188 EXPECT_EQ( 189 HttpAuth::AUTHORIZATION_RESULT_REJECT, 190 HandleChallengeResponse(false, kMissingChallenge, &challenge_used)); 191 EXPECT_EQ("", challenge_used); 192 193 EXPECT_EQ( 194 HttpAuth::AUTHORIZATION_RESULT_REJECT, 195 HandleChallengeResponse(false, kEmptyChallenge, &challenge_used)); 196 EXPECT_EQ("", challenge_used); 197 198 EXPECT_EQ( 199 HttpAuth::AUTHORIZATION_RESULT_REJECT, 200 HandleChallengeResponse(false, kBasicAndMockChallenges, &challenge_used)); 201 EXPECT_EQ("Mock token_here", challenge_used); 202 203 EXPECT_EQ( 204 HttpAuth::AUTHORIZATION_RESULT_REJECT, 205 HandleChallengeResponse(false, kTwoMockChallenges, &challenge_used)); 206 EXPECT_EQ("Mock token_a", challenge_used); 207 208 // Connection based schemes will treat new auth challenges for the same scheme 209 // as acceptance (and continuance) of the current approach. If there are 210 // no auth challenges for the same scheme, the response will be treated as 211 // a rejection. 212 EXPECT_EQ( 213 HttpAuth::AUTHORIZATION_RESULT_ACCEPT, 214 HandleChallengeResponse(true, kMockChallenge, &challenge_used)); 215 EXPECT_EQ("Mock token_here", challenge_used); 216 217 EXPECT_EQ( 218 HttpAuth::AUTHORIZATION_RESULT_REJECT, 219 HandleChallengeResponse(true, kBasicChallenge, &challenge_used)); 220 EXPECT_EQ("", challenge_used); 221 222 EXPECT_EQ( 223 HttpAuth::AUTHORIZATION_RESULT_REJECT, 224 HandleChallengeResponse(true, kMissingChallenge, &challenge_used)); 225 EXPECT_EQ("", challenge_used); 226 227 EXPECT_EQ( 228 HttpAuth::AUTHORIZATION_RESULT_REJECT, 229 HandleChallengeResponse(true, kEmptyChallenge, &challenge_used)); 230 EXPECT_EQ("", challenge_used); 231 232 EXPECT_EQ( 233 HttpAuth::AUTHORIZATION_RESULT_ACCEPT, 234 HandleChallengeResponse(true, kBasicAndMockChallenges, &challenge_used)); 235 EXPECT_EQ("Mock token_here", challenge_used); 236 237 EXPECT_EQ( 238 HttpAuth::AUTHORIZATION_RESULT_ACCEPT, 239 HandleChallengeResponse(true, kTwoMockChallenges, &challenge_used)); 240 EXPECT_EQ("Mock token_a", challenge_used); 241 } 242 243 TEST(HttpAuthTest, ChallengeTokenizer) { 244 std::string challenge_str = "Basic realm=\"foobar\""; 245 HttpAuth::ChallengeTokenizer challenge(challenge_str.begin(), 246 challenge_str.end()); 247 HttpUtil::NameValuePairsIterator parameters = challenge.param_pairs(); 248 249 EXPECT_TRUE(parameters.valid()); 250 EXPECT_EQ(std::string("Basic"), challenge.scheme()); 251 EXPECT_TRUE(parameters.GetNext()); 252 EXPECT_TRUE(parameters.valid()); 253 EXPECT_EQ(std::string("realm"), parameters.name()); 254 EXPECT_EQ(std::string("foobar"), parameters.value()); 255 EXPECT_FALSE(parameters.GetNext()); 256 } 257 258 // Use a name=value property with no quote marks. 259 TEST(HttpAuthTest, ChallengeTokenizerNoQuotes) { 260 std::string challenge_str = "Basic realm=foobar (at) baz.com"; 261 HttpAuth::ChallengeTokenizer challenge(challenge_str.begin(), 262 challenge_str.end()); 263 HttpUtil::NameValuePairsIterator parameters = challenge.param_pairs(); 264 265 EXPECT_TRUE(parameters.valid()); 266 EXPECT_EQ(std::string("Basic"), challenge.scheme()); 267 EXPECT_TRUE(parameters.GetNext()); 268 EXPECT_TRUE(parameters.valid()); 269 EXPECT_EQ(std::string("realm"), parameters.name()); 270 EXPECT_EQ(std::string("foobar (at) baz.com"), parameters.value()); 271 EXPECT_FALSE(parameters.GetNext()); 272 } 273 274 // Use a name=value property with mismatching quote marks. 275 TEST(HttpAuthTest, ChallengeTokenizerMismatchedQuotes) { 276 std::string challenge_str = "Basic realm=\"foobar (at) baz.com"; 277 HttpAuth::ChallengeTokenizer challenge(challenge_str.begin(), 278 challenge_str.end()); 279 HttpUtil::NameValuePairsIterator parameters = challenge.param_pairs(); 280 281 EXPECT_TRUE(parameters.valid()); 282 EXPECT_EQ(std::string("Basic"), challenge.scheme()); 283 EXPECT_TRUE(parameters.GetNext()); 284 EXPECT_TRUE(parameters.valid()); 285 EXPECT_EQ(std::string("realm"), parameters.name()); 286 EXPECT_EQ(std::string("foobar (at) baz.com"), parameters.value()); 287 EXPECT_FALSE(parameters.GetNext()); 288 } 289 290 // Use a name= property without a value and with mismatching quote marks. 291 TEST(HttpAuthTest, ChallengeTokenizerMismatchedQuotesNoValue) { 292 std::string challenge_str = "Basic realm=\""; 293 HttpAuth::ChallengeTokenizer challenge(challenge_str.begin(), 294 challenge_str.end()); 295 HttpUtil::NameValuePairsIterator parameters = challenge.param_pairs(); 296 297 EXPECT_TRUE(parameters.valid()); 298 EXPECT_EQ(std::string("Basic"), challenge.scheme()); 299 EXPECT_TRUE(parameters.GetNext()); 300 EXPECT_TRUE(parameters.valid()); 301 EXPECT_EQ(std::string("realm"), parameters.name()); 302 EXPECT_EQ(std::string(""), parameters.value()); 303 EXPECT_FALSE(parameters.GetNext()); 304 } 305 306 // Use a name=value property with mismatching quote marks and spaces in the 307 // value. 308 TEST(HttpAuthTest, ChallengeTokenizerMismatchedQuotesSpaces) { 309 std::string challenge_str = "Basic realm=\"foo bar"; 310 HttpAuth::ChallengeTokenizer challenge(challenge_str.begin(), 311 challenge_str.end()); 312 HttpUtil::NameValuePairsIterator parameters = challenge.param_pairs(); 313 314 EXPECT_TRUE(parameters.valid()); 315 EXPECT_EQ(std::string("Basic"), challenge.scheme()); 316 EXPECT_TRUE(parameters.GetNext()); 317 EXPECT_TRUE(parameters.valid()); 318 EXPECT_EQ(std::string("realm"), parameters.name()); 319 EXPECT_EQ(std::string("foo bar"), parameters.value()); 320 EXPECT_FALSE(parameters.GetNext()); 321 } 322 323 // Use multiple name=value properties with mismatching quote marks in the last 324 // value. 325 TEST(HttpAuthTest, ChallengeTokenizerMismatchedQuotesMultiple) { 326 std::string challenge_str = "Digest qop=auth-int, algorithm=md5, realm=\"foo"; 327 HttpAuth::ChallengeTokenizer challenge(challenge_str.begin(), 328 challenge_str.end()); 329 HttpUtil::NameValuePairsIterator parameters = challenge.param_pairs(); 330 331 EXPECT_TRUE(parameters.valid()); 332 EXPECT_EQ(std::string("Digest"), challenge.scheme()); 333 EXPECT_TRUE(parameters.GetNext()); 334 EXPECT_TRUE(parameters.valid()); 335 EXPECT_EQ(std::string("qop"), parameters.name()); 336 EXPECT_EQ(std::string("auth-int"), parameters.value()); 337 EXPECT_TRUE(parameters.GetNext()); 338 EXPECT_TRUE(parameters.valid()); 339 EXPECT_EQ(std::string("algorithm"), parameters.name()); 340 EXPECT_EQ(std::string("md5"), parameters.value()); 341 EXPECT_TRUE(parameters.GetNext()); 342 EXPECT_TRUE(parameters.valid()); 343 EXPECT_EQ(std::string("realm"), parameters.name()); 344 EXPECT_EQ(std::string("foo"), parameters.value()); 345 EXPECT_FALSE(parameters.GetNext()); 346 } 347 348 // Use a name= property which has no value. 349 TEST(HttpAuthTest, ChallengeTokenizerNoValue) { 350 std::string challenge_str = "Digest qop="; 351 HttpAuth::ChallengeTokenizer challenge( 352 challenge_str.begin(), challenge_str.end()); 353 HttpUtil::NameValuePairsIterator parameters = challenge.param_pairs(); 354 355 EXPECT_TRUE(parameters.valid()); 356 EXPECT_EQ(std::string("Digest"), challenge.scheme()); 357 EXPECT_FALSE(parameters.GetNext()); 358 EXPECT_FALSE(parameters.valid()); 359 } 360 361 // Specify multiple properties, comma separated. 362 TEST(HttpAuthTest, ChallengeTokenizerMultiple) { 363 std::string challenge_str = 364 "Digest algorithm=md5, realm=\"Oblivion\", qop=auth-int"; 365 HttpAuth::ChallengeTokenizer challenge(challenge_str.begin(), 366 challenge_str.end()); 367 HttpUtil::NameValuePairsIterator parameters = challenge.param_pairs(); 368 369 EXPECT_TRUE(parameters.valid()); 370 EXPECT_EQ(std::string("Digest"), challenge.scheme()); 371 EXPECT_TRUE(parameters.GetNext()); 372 EXPECT_TRUE(parameters.valid()); 373 EXPECT_EQ(std::string("algorithm"), parameters.name()); 374 EXPECT_EQ(std::string("md5"), parameters.value()); 375 EXPECT_TRUE(parameters.GetNext()); 376 EXPECT_TRUE(parameters.valid()); 377 EXPECT_EQ(std::string("realm"), parameters.name()); 378 EXPECT_EQ(std::string("Oblivion"), parameters.value()); 379 EXPECT_TRUE(parameters.GetNext()); 380 EXPECT_TRUE(parameters.valid()); 381 EXPECT_EQ(std::string("qop"), parameters.name()); 382 EXPECT_EQ(std::string("auth-int"), parameters.value()); 383 EXPECT_FALSE(parameters.GetNext()); 384 EXPECT_TRUE(parameters.valid()); 385 } 386 387 // Use a challenge which has no property. 388 TEST(HttpAuthTest, ChallengeTokenizerNoProperty) { 389 std::string challenge_str = "NTLM"; 390 HttpAuth::ChallengeTokenizer challenge( 391 challenge_str.begin(), challenge_str.end()); 392 HttpUtil::NameValuePairsIterator parameters = challenge.param_pairs(); 393 394 EXPECT_TRUE(parameters.valid()); 395 EXPECT_EQ(std::string("NTLM"), challenge.scheme()); 396 EXPECT_FALSE(parameters.GetNext()); 397 } 398 399 // Use a challenge with Base64 encoded token. 400 TEST(HttpAuthTest, ChallengeTokenizerBase64) { 401 std::string challenge_str = "NTLM SGVsbG8sIFdvcmxkCg==="; 402 HttpAuth::ChallengeTokenizer challenge(challenge_str.begin(), 403 challenge_str.end()); 404 405 EXPECT_EQ(std::string("NTLM"), challenge.scheme()); 406 // Notice the two equal statements below due to padding removal. 407 EXPECT_EQ(std::string("SGVsbG8sIFdvcmxkCg=="), challenge.base64_param()); 408 } 409 410 TEST(HttpAuthTest, GetChallengeHeaderName) { 411 std::string name; 412 413 name = HttpAuth::GetChallengeHeaderName(HttpAuth::AUTH_SERVER); 414 EXPECT_STREQ("WWW-Authenticate", name.c_str()); 415 416 name = HttpAuth::GetChallengeHeaderName(HttpAuth::AUTH_PROXY); 417 EXPECT_STREQ("Proxy-Authenticate", name.c_str()); 418 } 419 420 TEST(HttpAuthTest, GetAuthorizationHeaderName) { 421 std::string name; 422 423 name = HttpAuth::GetAuthorizationHeaderName(HttpAuth::AUTH_SERVER); 424 EXPECT_STREQ("Authorization", name.c_str()); 425 426 name = HttpAuth::GetAuthorizationHeaderName(HttpAuth::AUTH_PROXY); 427 EXPECT_STREQ("Proxy-Authorization", name.c_str()); 428 } 429 430 } // namespace net 431