http_auth_cache_unittest.cc revision 72a454cd3513ac24fbdd0e0cb9ad70b86a99b801
1// Copyright (c) 2010 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 <string> 6 7#include "base/string16.h" 8#include "base/string_util.h" 9#include "base/stringprintf.h" 10#include "base/utf_string_conversions.h" 11#include "net/base/net_errors.h" 12#include "net/http/http_auth_cache.h" 13#include "net/http/http_auth_handler.h" 14#include "testing/gtest/include/gtest/gtest.h" 15 16namespace net { 17 18namespace { 19 20class MockAuthHandler : public HttpAuthHandler { 21 public: 22 MockAuthHandler(HttpAuth::Scheme scheme, 23 const std::string& realm, 24 HttpAuth::Target target) { 25 // Can't use initializer list since these are members of the base class. 26 auth_scheme_ = scheme; 27 realm_ = realm; 28 score_ = 1; 29 target_ = target; 30 properties_ = 0; 31 } 32 33 virtual HttpAuth::AuthorizationResult HandleAnotherChallenge( 34 HttpAuth::ChallengeTokenizer* challenge) { 35 return HttpAuth::AUTHORIZATION_RESULT_REJECT; 36 } 37 38 protected: 39 virtual bool Init(HttpAuth::ChallengeTokenizer* challenge) { 40 return false; // Unused. 41 } 42 43 virtual int GenerateAuthTokenImpl(const string16*, 44 const string16*, 45 const HttpRequestInfo*, 46 CompletionCallback* callback, 47 std::string* auth_token) { 48 *auth_token = "mock-credentials"; 49 return OK; 50 } 51 52 53 private: 54 ~MockAuthHandler() {} 55}; 56 57const char* kRealm1 = "Realm1"; 58const char* kRealm2 = "Realm2"; 59const char* kRealm3 = "Realm3"; 60const char* kRealm4 = "Realm4"; 61const string16 k123(ASCIIToUTF16("123")); 62const string16 k1234(ASCIIToUTF16("1234")); 63const string16 kAdmin(ASCIIToUTF16("admin")); 64const string16 kAlice(ASCIIToUTF16("alice")); 65const string16 kAlice2(ASCIIToUTF16("alice2")); 66const string16 kPassword(ASCIIToUTF16("password")); 67const string16 kRoot(ASCIIToUTF16("root")); 68const string16 kUsername(ASCIIToUTF16("username")); 69const string16 kWileCoyote(ASCIIToUTF16("wilecoyote")); 70 71} // namespace 72 73// Test adding and looking-up cache entries (both by realm and by path). 74TEST(HttpAuthCacheTest, Basic) { 75 GURL origin("http://www.google.com"); 76 HttpAuthCache cache; 77 HttpAuthCache::Entry* entry; 78 79 // Add cache entries for 3 realms: "Realm1", "Realm2", "Realm3" 80 81 scoped_ptr<HttpAuthHandler> realm1_handler( 82 new MockAuthHandler(HttpAuth::AUTH_SCHEME_BASIC, 83 kRealm1, 84 HttpAuth::AUTH_SERVER)); 85 cache.Add(origin, realm1_handler->realm(), realm1_handler->auth_scheme(), 86 "Basic realm=Realm1", ASCIIToUTF16("realm1-user"), 87 ASCIIToUTF16("realm1-password"), "/foo/bar/index.html"); 88 89 scoped_ptr<HttpAuthHandler> realm2_handler( 90 new MockAuthHandler(HttpAuth::AUTH_SCHEME_BASIC, 91 kRealm2, 92 HttpAuth::AUTH_SERVER)); 93 cache.Add(origin, realm2_handler->realm(), realm2_handler->auth_scheme(), 94 "Basic realm=Realm2", ASCIIToUTF16("realm2-user"), 95 ASCIIToUTF16("realm2-password"), "/foo2/index.html"); 96 97 scoped_ptr<HttpAuthHandler> realm3_basic_handler( 98 new MockAuthHandler(HttpAuth::AUTH_SCHEME_BASIC, 99 kRealm3, 100 HttpAuth::AUTH_PROXY)); 101 cache.Add(origin, realm3_basic_handler->realm(), 102 realm3_basic_handler->auth_scheme(), "Basic realm=Realm3", 103 ASCIIToUTF16("realm3-basic-user"), 104 ASCIIToUTF16("realm3-basic-password"), ""); 105 106 scoped_ptr<HttpAuthHandler> realm3_digest_handler( 107 new MockAuthHandler(HttpAuth::AUTH_SCHEME_DIGEST, 108 kRealm3, 109 HttpAuth::AUTH_PROXY)); 110 cache.Add(origin, realm3_digest_handler->realm(), 111 realm3_digest_handler->auth_scheme(), "Digest realm=Realm3", 112 ASCIIToUTF16("realm3-digest-user"), 113 ASCIIToUTF16("realm3-digest-password"), "/baz/index.html"); 114 115 // There is no Realm4 116 entry = cache.Lookup(origin, kRealm4, HttpAuth::AUTH_SCHEME_BASIC); 117 EXPECT_TRUE(NULL == entry); 118 119 // While Realm3 does exist, the origin scheme is wrong. 120 entry = cache.Lookup(GURL("https://www.google.com"), kRealm3, 121 HttpAuth::AUTH_SCHEME_BASIC); 122 EXPECT_TRUE(NULL == entry); 123 124 // Realm, origin scheme ok, authentication scheme wrong 125 entry = cache.Lookup 126 (GURL("http://www.google.com"), kRealm1, HttpAuth::AUTH_SCHEME_DIGEST); 127 EXPECT_TRUE(NULL == entry); 128 129 // Valid lookup by origin, realm, scheme. 130 entry = cache.Lookup( 131 GURL("http://www.google.com:80"), kRealm3, HttpAuth::AUTH_SCHEME_BASIC); 132 ASSERT_FALSE(NULL == entry); 133 EXPECT_EQ(HttpAuth::AUTH_SCHEME_BASIC, entry->scheme()); 134 EXPECT_EQ(kRealm3, entry->realm()); 135 EXPECT_EQ("Basic realm=Realm3", entry->auth_challenge()); 136 EXPECT_EQ(ASCIIToUTF16("realm3-basic-user"), entry->username()); 137 EXPECT_EQ(ASCIIToUTF16("realm3-basic-password"), entry->password()); 138 139 // Valid lookup by origin, realm, scheme when there's a duplicate 140 // origin, realm in the cache 141 entry = cache.Lookup( 142 GURL("http://www.google.com:80"), kRealm3, HttpAuth::AUTH_SCHEME_DIGEST); 143 ASSERT_FALSE(NULL == entry); 144 EXPECT_EQ(HttpAuth::AUTH_SCHEME_DIGEST, entry->scheme()); 145 EXPECT_EQ(kRealm3, entry->realm()); 146 EXPECT_EQ("Digest realm=Realm3", entry->auth_challenge()); 147 EXPECT_EQ(ASCIIToUTF16("realm3-digest-user"), entry->username()); 148 EXPECT_EQ(ASCIIToUTF16("realm3-digest-password"), entry->password()); 149 150 // Valid lookup by realm. 151 entry = cache.Lookup(origin, kRealm2, HttpAuth::AUTH_SCHEME_BASIC); 152 ASSERT_FALSE(NULL == entry); 153 EXPECT_EQ(HttpAuth::AUTH_SCHEME_BASIC, entry->scheme()); 154 EXPECT_EQ(kRealm2, entry->realm()); 155 EXPECT_EQ("Basic realm=Realm2", entry->auth_challenge()); 156 EXPECT_EQ(ASCIIToUTF16("realm2-user"), entry->username()); 157 EXPECT_EQ(ASCIIToUTF16("realm2-password"), entry->password()); 158 159 // Check that subpaths are recognized. 160 HttpAuthCache::Entry* realm2_entry = cache.Lookup( 161 origin, kRealm2, HttpAuth::AUTH_SCHEME_BASIC); 162 EXPECT_FALSE(NULL == realm2_entry); 163 // Positive tests: 164 entry = cache.LookupByPath(origin, "/foo2/index.html"); 165 EXPECT_TRUE(realm2_entry == entry); 166 entry = cache.LookupByPath(origin, "/foo2/foobar.html"); 167 EXPECT_TRUE(realm2_entry == entry); 168 entry = cache.LookupByPath(origin, "/foo2/bar/index.html"); 169 EXPECT_TRUE(realm2_entry == entry); 170 entry = cache.LookupByPath(origin, "/foo2/"); 171 EXPECT_TRUE(realm2_entry == entry); 172 173 // Negative tests: 174 entry = cache.LookupByPath(origin, "/foo2"); 175 EXPECT_FALSE(realm2_entry == entry); 176 entry = cache.LookupByPath(origin, "/foo3/index.html"); 177 EXPECT_FALSE(realm2_entry == entry); 178 entry = cache.LookupByPath(origin, ""); 179 EXPECT_FALSE(realm2_entry == entry); 180 entry = cache.LookupByPath(origin, "/"); 181 EXPECT_FALSE(realm2_entry == entry); 182 183 // Confirm we find the same realm, different auth scheme by path lookup 184 HttpAuthCache::Entry* realm3_digest_entry = 185 cache.Lookup(origin, kRealm3, HttpAuth::AUTH_SCHEME_DIGEST); 186 EXPECT_FALSE(NULL == realm3_digest_entry); 187 entry = cache.LookupByPath(origin, "/baz/index.html"); 188 EXPECT_TRUE(realm3_digest_entry == entry); 189 entry = cache.LookupByPath(origin, "/baz/"); 190 EXPECT_TRUE(realm3_digest_entry == entry); 191 entry = cache.LookupByPath(origin, "/baz"); 192 EXPECT_FALSE(realm3_digest_entry == entry); 193 194 // Confirm we find the same realm, different auth scheme by path lookup 195 HttpAuthCache::Entry* realm3DigestEntry = 196 cache.Lookup(origin, kRealm3, HttpAuth::AUTH_SCHEME_DIGEST); 197 EXPECT_FALSE(NULL == realm3DigestEntry); 198 entry = cache.LookupByPath(origin, "/baz/index.html"); 199 EXPECT_TRUE(realm3DigestEntry == entry); 200 entry = cache.LookupByPath(origin, "/baz/"); 201 EXPECT_TRUE(realm3DigestEntry == entry); 202 entry = cache.LookupByPath(origin, "/baz"); 203 EXPECT_FALSE(realm3DigestEntry == entry); 204 205 // Lookup using empty path (may be used for proxy). 206 entry = cache.LookupByPath(origin, ""); 207 EXPECT_FALSE(NULL == entry); 208 EXPECT_EQ(HttpAuth::AUTH_SCHEME_BASIC, entry->scheme()); 209 EXPECT_EQ(kRealm3, entry->realm()); 210} 211 212TEST(HttpAuthCacheTest, AddPath) { 213 HttpAuthCache::Entry entry; 214 215 // All of these paths have a common root /1/2/2/4/5/ 216 entry.AddPath("/1/2/3/4/5/x.txt"); 217 entry.AddPath("/1/2/3/4/5/y.txt"); 218 entry.AddPath("/1/2/3/4/5/z.txt"); 219 220 EXPECT_EQ(1U, entry.paths_.size()); 221 EXPECT_EQ("/1/2/3/4/5/", entry.paths_.front()); 222 223 // Add a new entry (not a subpath). 224 entry.AddPath("/1/XXX/q"); 225 EXPECT_EQ(2U, entry.paths_.size()); 226 EXPECT_EQ("/1/XXX/", entry.paths_.front()); 227 EXPECT_EQ("/1/2/3/4/5/", entry.paths_.back()); 228 229 // Add containing paths of /1/2/3/4/5/ -- should swallow up the deeper paths. 230 entry.AddPath("/1/2/3/4/x.txt"); 231 EXPECT_EQ(2U, entry.paths_.size()); 232 EXPECT_EQ("/1/2/3/4/", entry.paths_.front()); 233 EXPECT_EQ("/1/XXX/", entry.paths_.back()); 234 entry.AddPath("/1/2/3/x"); 235 EXPECT_EQ(2U, entry.paths_.size()); 236 EXPECT_EQ("/1/2/3/", entry.paths_.front()); 237 EXPECT_EQ("/1/XXX/", entry.paths_.back()); 238 239 entry.AddPath("/index.html"); 240 EXPECT_EQ(1U, entry.paths_.size()); 241 EXPECT_EQ("/", entry.paths_.front()); 242} 243 244// Calling Add when the realm entry already exists, should append that 245// path. 246TEST(HttpAuthCacheTest, AddToExistingEntry) { 247 HttpAuthCache cache; 248 GURL origin("http://www.foobar.com:70"); 249 const std::string auth_challenge = "Basic realm=MyRealm"; 250 251 scoped_ptr<HttpAuthHandler> handler( 252 new MockAuthHandler( 253 HttpAuth::AUTH_SCHEME_BASIC, "MyRealm", HttpAuth::AUTH_SERVER)); 254 HttpAuthCache::Entry* orig_entry = cache.Add( 255 origin, handler->realm(), handler->auth_scheme(), auth_challenge, 256 ASCIIToUTF16("user1"), ASCIIToUTF16("password1"), "/x/y/z/"); 257 cache.Add(origin, handler->realm(), handler->auth_scheme(), auth_challenge, 258 ASCIIToUTF16("user2"), ASCIIToUTF16("password2"), "/z/y/x/"); 259 cache.Add(origin, handler->realm(), handler->auth_scheme(), auth_challenge, 260 ASCIIToUTF16("user3"), ASCIIToUTF16("password3"), "/z/y"); 261 262 HttpAuthCache::Entry* entry = cache.Lookup( 263 origin, "MyRealm", HttpAuth::AUTH_SCHEME_BASIC); 264 265 EXPECT_TRUE(entry == orig_entry); 266 EXPECT_EQ(ASCIIToUTF16("user3"), entry->username()); 267 EXPECT_EQ(ASCIIToUTF16("password3"), entry->password()); 268 269 EXPECT_EQ(2U, entry->paths_.size()); 270 EXPECT_EQ("/z/", entry->paths_.front()); 271 EXPECT_EQ("/x/y/z/", entry->paths_.back()); 272} 273 274TEST(HttpAuthCacheTest, Remove) { 275 GURL origin("http://foobar2.com"); 276 277 scoped_ptr<HttpAuthHandler> realm1_handler( 278 new MockAuthHandler( 279 HttpAuth::AUTH_SCHEME_BASIC, kRealm1, HttpAuth::AUTH_SERVER)); 280 281 scoped_ptr<HttpAuthHandler> realm2_handler( 282 new MockAuthHandler( 283 HttpAuth::AUTH_SCHEME_BASIC, kRealm2, HttpAuth::AUTH_SERVER)); 284 285 scoped_ptr<HttpAuthHandler> realm3_basic_handler( 286 new MockAuthHandler( 287 HttpAuth::AUTH_SCHEME_BASIC, kRealm3, HttpAuth::AUTH_SERVER)); 288 289 scoped_ptr<HttpAuthHandler> realm3_digest_handler( 290 new MockAuthHandler( 291 HttpAuth::AUTH_SCHEME_DIGEST, kRealm3, HttpAuth::AUTH_SERVER)); 292 293 HttpAuthCache cache; 294 cache.Add(origin, realm1_handler->realm(), realm1_handler->auth_scheme(), 295 "basic realm=Realm1", kAlice, k123, "/"); 296 cache.Add(origin, realm2_handler->realm(), realm2_handler->auth_scheme(), 297 "basic realm=Realm2", ASCIIToUTF16("bob"), ASCIIToUTF16("princess"), 298 "/"); 299 cache.Add(origin, realm3_basic_handler->realm(), 300 realm3_basic_handler->auth_scheme(), "basic realm=Realm3", 301 kAdmin, kPassword, "/"); 302 cache.Add(origin, realm3_digest_handler->realm(), 303 realm3_digest_handler->auth_scheme(), "digest realm=Realm3", 304 kRoot, kWileCoyote, "/"); 305 306 // Fails, because there is no realm "Realm4". 307 EXPECT_FALSE(cache.Remove( 308 origin, kRealm4, HttpAuth::AUTH_SCHEME_BASIC, kAlice, k123)); 309 310 // Fails because the origin is wrong. 311 EXPECT_FALSE(cache.Remove(GURL("http://foobar2.com:100"), 312 kRealm1, 313 HttpAuth::AUTH_SCHEME_BASIC, 314 kAlice, 315 k123)); 316 317 // Fails because the username is wrong. 318 EXPECT_FALSE(cache.Remove( 319 origin, kRealm1, HttpAuth::AUTH_SCHEME_BASIC, kAlice2, k123)); 320 321 // Fails because the password is wrong. 322 EXPECT_FALSE(cache.Remove( 323 origin, kRealm1, HttpAuth::AUTH_SCHEME_BASIC, kAlice, k1234)); 324 325 // Fails because the authentication type is wrong. 326 EXPECT_FALSE(cache.Remove( 327 origin, kRealm1, HttpAuth::AUTH_SCHEME_DIGEST, kAlice, k123)); 328 329 // Succeeds. 330 EXPECT_TRUE(cache.Remove( 331 origin, kRealm1, HttpAuth::AUTH_SCHEME_BASIC, kAlice, k123)); 332 333 // Fails because we just deleted the entry! 334 EXPECT_FALSE(cache.Remove( 335 origin, kRealm1, HttpAuth::AUTH_SCHEME_BASIC, kAlice, k123)); 336 337 // Succeed when there are two authentication types for the same origin,realm. 338 EXPECT_TRUE(cache.Remove( 339 origin, kRealm3, HttpAuth::AUTH_SCHEME_DIGEST, kRoot, kWileCoyote)); 340 341 // Succeed as above, but when entries were added in opposite order 342 cache.Add(origin, realm3_digest_handler->realm(), 343 realm3_digest_handler->auth_scheme(), "digest realm=Realm3", 344 kRoot, kWileCoyote, "/"); 345 EXPECT_TRUE(cache.Remove( 346 origin, kRealm3, HttpAuth::AUTH_SCHEME_BASIC, kAdmin, kPassword)); 347 348 // Make sure that removing one entry still leaves the other available for 349 // lookup. 350 HttpAuthCache::Entry* entry = cache.Lookup( 351 origin, kRealm3, HttpAuth::AUTH_SCHEME_DIGEST); 352 EXPECT_FALSE(NULL == entry); 353} 354 355TEST(HttpAuthCacheTest, UpdateStaleChallenge) { 356 HttpAuthCache cache; 357 GURL origin("http://foobar2.com"); 358 scoped_ptr<HttpAuthHandler> digest_handler( 359 new MockAuthHandler( 360 HttpAuth::AUTH_SCHEME_DIGEST, kRealm1, HttpAuth::AUTH_PROXY)); 361 HttpAuthCache::Entry* entry_pre = cache.Add( 362 origin, 363 digest_handler->realm(), 364 digest_handler->auth_scheme(), 365 "Digest realm=Realm1," 366 "nonce=\"s3MzvFhaBAA=4c520af5acd9d8d7ae26947529d18c8eae1e98f4\"", 367 ASCIIToUTF16("realm-digest-user"), 368 ASCIIToUTF16("realm-digest-password"), 369 "/baz/index.html"); 370 ASSERT_TRUE(entry_pre != NULL); 371 372 EXPECT_EQ(2, entry_pre->IncrementNonceCount()); 373 EXPECT_EQ(3, entry_pre->IncrementNonceCount()); 374 EXPECT_EQ(4, entry_pre->IncrementNonceCount()); 375 376 bool update_success = cache.UpdateStaleChallenge( 377 origin, 378 digest_handler->realm(), 379 digest_handler->auth_scheme(), 380 "Digest realm=Realm1," 381 "nonce=\"claGgoRXBAA=7583377687842fdb7b56ba0555d175baa0b800e3\"," 382 "stale=\"true\""); 383 EXPECT_TRUE(update_success); 384 385 // After the stale update, the entry should still exist in the cache and 386 // the nonce count should be reset to 0. 387 HttpAuthCache::Entry* entry_post = cache.Lookup( 388 origin, 389 digest_handler->realm(), 390 digest_handler->auth_scheme()); 391 ASSERT_TRUE(entry_post != NULL); 392 EXPECT_EQ(2, entry_post->IncrementNonceCount()); 393 394 // UpdateStaleChallenge will fail if an entry doesn't exist in the cache. 395 bool update_failure = cache.UpdateStaleChallenge( 396 origin, 397 kRealm2, 398 digest_handler->auth_scheme(), 399 "Digest realm=Realm2," 400 "nonce=\"claGgoRXBAA=7583377687842fdb7b56ba0555d175baa0b800e3\"," 401 "stale=\"true\""); 402 EXPECT_FALSE(update_failure); 403} 404 405// Test fixture class for eviction tests (contains helpers for bulk 406// insertion and existence testing). 407class HttpAuthCacheEvictionTest : public testing::Test { 408 protected: 409 HttpAuthCacheEvictionTest() : origin_("http://www.google.com") { } 410 411 std::string GenerateRealm(int realm_i) { 412 return base::StringPrintf("Realm %d", realm_i); 413 } 414 415 std::string GeneratePath(int realm_i, int path_i) { 416 return base::StringPrintf("/%d/%d/x/y", realm_i, path_i); 417 } 418 419 void AddRealm(int realm_i) { 420 AddPathToRealm(realm_i, 0); 421 } 422 423 void AddPathToRealm(int realm_i, int path_i) { 424 cache_.Add(origin_, GenerateRealm(realm_i), HttpAuth::AUTH_SCHEME_BASIC, "", 425 kUsername, kPassword, GeneratePath(realm_i, path_i)); 426 } 427 428 void CheckRealmExistence(int realm_i, bool exists) { 429 const HttpAuthCache::Entry* entry = 430 cache_.Lookup( 431 origin_, GenerateRealm(realm_i), HttpAuth::AUTH_SCHEME_BASIC); 432 if (exists) { 433 EXPECT_FALSE(entry == NULL); 434 EXPECT_EQ(GenerateRealm(realm_i), entry->realm()); 435 } else { 436 EXPECT_TRUE(entry == NULL); 437 } 438 } 439 440 void CheckPathExistence(int realm_i, int path_i, bool exists) { 441 const HttpAuthCache::Entry* entry = 442 cache_.LookupByPath(origin_, GeneratePath(realm_i, path_i)); 443 if (exists) { 444 EXPECT_FALSE(entry == NULL); 445 EXPECT_EQ(GenerateRealm(realm_i), entry->realm()); 446 } else { 447 EXPECT_TRUE(entry == NULL); 448 } 449 } 450 451 GURL origin_; 452 HttpAuthCache cache_; 453 454 static const int kMaxPaths = HttpAuthCache::kMaxNumPathsPerRealmEntry; 455 static const int kMaxRealms = HttpAuthCache::kMaxNumRealmEntries; 456}; 457 458// Add the maxinim number of realm entries to the cache. Each of these entries 459// must still be retrievable. Next add three more entries -- since the cache is 460// full this causes FIFO eviction of the first three entries. 461TEST_F(HttpAuthCacheEvictionTest, RealmEntryEviction) { 462 for (int i = 0; i < kMaxRealms; ++i) 463 AddRealm(i); 464 465 for (int i = 0; i < kMaxRealms; ++i) 466 CheckRealmExistence(i, true); 467 468 for (int i = 0; i < 3; ++i) 469 AddRealm(i + kMaxRealms); 470 471 for (int i = 0; i < 3; ++i) 472 CheckRealmExistence(i, false); 473 474 for (int i = 0; i < kMaxRealms; ++i) 475 CheckRealmExistence(i + 3, true); 476} 477 478// Add the maximum number of paths to a single realm entry. Each of these 479// paths should be retrievable. Next add 3 more paths -- since the cache is 480// full this causes FIFO eviction of the first three paths. 481TEST_F(HttpAuthCacheEvictionTest, RealmPathEviction) { 482 for (int i = 0; i < kMaxPaths; ++i) 483 AddPathToRealm(0, i); 484 485 for (int i = 1; i < kMaxRealms; ++i) 486 AddRealm(i); 487 488 for (int i = 0; i < 3; ++i) 489 AddPathToRealm(0, i + kMaxPaths); 490 491 for (int i = 0; i < 3; ++i) 492 CheckPathExistence(0, i, false); 493 494 for (int i = 0; i < kMaxPaths; ++i) 495 CheckPathExistence(0, i + 3, true); 496 497 for (int i = 0; i < kMaxRealms; ++i) 498 CheckRealmExistence(i, true); 499} 500 501} // namespace net 502