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