Upstream version 5.34.104.0
[platform/framework/web/crosswalk.git] / src / chrome / browser / password_manager / password_store_mac_unittest.cc
1 // Copyright (c) 2012 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 "testing/gmock/include/gmock/gmock.h"
6 #include "testing/gtest/include/gtest/gtest.h"
7
8 #include "base/basictypes.h"
9 #include "base/files/scoped_temp_dir.h"
10 #include "base/path_service.h"
11 #include "base/stl_util.h"
12 #include "base/strings/string_util.h"
13 #include "base/strings/utf_string_conversions.h"
14 #include "chrome/browser/password_manager/password_store_mac.h"
15 #include "chrome/browser/password_manager/password_store_mac_internal.h"
16 #include "chrome/common/chrome_paths.h"
17 #include "components/password_manager/core/browser/password_store_consumer.h"
18 #include "content/public/test/test_browser_thread.h"
19 #include "crypto/mock_apple_keychain.h"
20
21 using autofill::PasswordForm;
22 using base::ASCIIToUTF16;
23 using base::WideToUTF16;
24 using content::BrowserThread;
25 using crypto::MockAppleKeychain;
26 using internal_keychain_helpers::FormsMatchForMerge;
27 using internal_keychain_helpers::STRICT_FORM_MATCH;
28 using testing::_;
29 using testing::DoAll;
30 using testing::Invoke;
31 using testing::WithArg;
32
33 namespace {
34
35 class MockPasswordStoreConsumer : public PasswordStoreConsumer {
36  public:
37   MOCK_METHOD1(OnGetPasswordStoreResults,
38                void(const std::vector<autofill::PasswordForm*>&));
39
40   void CopyElements(const std::vector<autofill::PasswordForm*>& forms) {
41     last_result.clear();
42     for (size_t i = 0; i < forms.size(); ++i) {
43       last_result.push_back(*forms[i]);
44     }
45   }
46
47   std::vector<PasswordForm> last_result;
48 };
49
50 ACTION(STLDeleteElements0) {
51   STLDeleteContainerPointers(arg0.begin(), arg0.end());
52 }
53
54 ACTION(QuitUIMessageLoop) {
55   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
56   base::MessageLoop::current()->Quit();
57 }
58
59 }  // namespace
60
61 #pragma mark -
62
63 class PasswordStoreMacInternalsTest : public testing::Test {
64  public:
65   virtual void SetUp() {
66     MockAppleKeychain::KeychainTestData test_data[] = {
67       // Basic HTML form.
68       { kSecAuthenticationTypeHTMLForm, "some.domain.com",
69         kSecProtocolTypeHTTP, NULL, 0, NULL, "20020601171500Z",
70         "joe_user", "sekrit", false },
71       // HTML form with path.
72       { kSecAuthenticationTypeHTMLForm, "some.domain.com",
73         kSecProtocolTypeHTTP, "/insecure.html", 0, NULL, "19991231235959Z",
74         "joe_user", "sekrit", false },
75       // Secure HTML form with path.
76       { kSecAuthenticationTypeHTMLForm, "some.domain.com",
77         kSecProtocolTypeHTTPS, "/secure.html", 0, NULL, "20100908070605Z",
78         "secure_user", "password", false },
79       // True negative item.
80       { kSecAuthenticationTypeHTMLForm, "dont.remember.com",
81         kSecProtocolTypeHTTP, NULL, 0, NULL, "20000101000000Z",
82         "", "", true },
83       // De-facto negative item, type one.
84       { kSecAuthenticationTypeHTMLForm, "dont.remember.com",
85         kSecProtocolTypeHTTP, NULL, 0, NULL, "20000101000000Z",
86         "Password Not Stored", "", false },
87       // De-facto negative item, type two.
88       { kSecAuthenticationTypeHTMLForm, "dont.remember.com",
89         kSecProtocolTypeHTTPS, NULL, 0, NULL, "20000101000000Z",
90         "Password Not Stored", " ", false },
91       // HTTP auth basic, with port and path.
92       { kSecAuthenticationTypeHTTPBasic, "some.domain.com",
93         kSecProtocolTypeHTTP, "/insecure.html", 4567, "low_security",
94         "19980330100000Z",
95         "basic_auth_user", "basic", false },
96       // HTTP auth digest, secure.
97       { kSecAuthenticationTypeHTTPDigest, "some.domain.com",
98         kSecProtocolTypeHTTPS, NULL, 0, "high_security", "19980330100000Z",
99         "digest_auth_user", "digest", false },
100       // An FTP password with an invalid date, for edge-case testing.
101       { kSecAuthenticationTypeDefault, "a.server.com",
102         kSecProtocolTypeFTP, NULL, 0, NULL, "20010203040",
103         "abc", "123", false },
104     };
105
106     keychain_ = new MockAppleKeychain();
107
108     for (unsigned int i = 0; i < arraysize(test_data); ++i) {
109       keychain_->AddTestItem(test_data[i]);
110     }
111   }
112
113   virtual void TearDown() {
114     ExpectCreatesAndFreesBalanced();
115     ExpectCreatorCodesSet();
116     delete keychain_;
117   }
118
119  protected:
120   // Causes a test failure unless everything returned from keychain_'s
121   // ItemCopyAttributesAndData, SearchCreateFromAttributes, and SearchCopyNext
122   // was correctly freed.
123   void ExpectCreatesAndFreesBalanced() {
124     EXPECT_EQ(0, keychain_->UnfreedSearchCount());
125     EXPECT_EQ(0, keychain_->UnfreedKeychainItemCount());
126     EXPECT_EQ(0, keychain_->UnfreedAttributeDataCount());
127   }
128
129   // Causes a test failure unless any Keychain items added during the test have
130   // their creator code set.
131   void ExpectCreatorCodesSet() {
132     EXPECT_TRUE(keychain_->CreatorCodesSetForAddedItems());
133   }
134
135   MockAppleKeychain* keychain_;
136 };
137
138 #pragma mark -
139
140 // Struct used for creation of PasswordForms from static arrays of data.
141 struct PasswordFormData {
142   const PasswordForm::Scheme scheme;
143   const char* signon_realm;
144   const char* origin;
145   const char* action;
146   const wchar_t* submit_element;
147   const wchar_t* username_element;
148   const wchar_t* password_element;
149   const wchar_t* username_value;  // Set to NULL for a blacklist entry.
150   const wchar_t* password_value;
151   const bool preferred;
152   const bool ssl_valid;
153   const double creation_time;
154 };
155
156 // Creates and returns a new PasswordForm built from form_data. Caller is
157 // responsible for deleting the object when finished with it.
158 static PasswordForm* CreatePasswordFormFromData(
159     const PasswordFormData& form_data) {
160   PasswordForm* form = new PasswordForm();
161   form->scheme = form_data.scheme;
162   form->preferred = form_data.preferred;
163   form->ssl_valid = form_data.ssl_valid;
164   form->date_created = base::Time::FromDoubleT(form_data.creation_time);
165   if (form_data.signon_realm)
166     form->signon_realm = std::string(form_data.signon_realm);
167   if (form_data.origin)
168     form->origin = GURL(form_data.origin);
169   if (form_data.action)
170     form->action = GURL(form_data.action);
171   if (form_data.submit_element)
172     form->submit_element = WideToUTF16(form_data.submit_element);
173   if (form_data.username_element)
174     form->username_element = WideToUTF16(form_data.username_element);
175   if (form_data.password_element)
176     form->password_element = WideToUTF16(form_data.password_element);
177   if (form_data.username_value) {
178     form->username_value = WideToUTF16(form_data.username_value);
179     if (form_data.password_value)
180       form->password_value = WideToUTF16(form_data.password_value);
181   } else {
182     form->blacklisted_by_user = true;
183   }
184   return form;
185 }
186
187 // Macro to simplify calling CheckFormsAgainstExpectations with a useful label.
188 #define CHECK_FORMS(forms, expectations, i) \
189     CheckFormsAgainstExpectations(forms, expectations, #forms, i)
190
191 // Ensures that the data in |forms| match |expectations|, causing test failures
192 // for any discrepencies.
193 // TODO(stuartmorgan): This is current order-dependent; ideally it shouldn't
194 // matter if |forms| and |expectations| are scrambled.
195 static void CheckFormsAgainstExpectations(
196     const std::vector<PasswordForm*>& forms,
197     const std::vector<PasswordFormData*>& expectations,
198     const char* forms_label, unsigned int test_number) {
199   const unsigned int kBufferSize = 128;
200   char test_label[kBufferSize];
201   snprintf(test_label, kBufferSize, "%s in test %u", forms_label, test_number);
202
203   EXPECT_EQ(expectations.size(), forms.size()) << test_label;
204   if (expectations.size() != forms.size())
205     return;
206
207   for (unsigned int i = 0; i < expectations.size(); ++i) {
208     snprintf(test_label, kBufferSize, "%s in test %u, item %u",
209              forms_label, test_number, i);
210     PasswordForm* form = forms[i];
211     PasswordFormData* expectation = expectations[i];
212     EXPECT_EQ(expectation->scheme, form->scheme) << test_label;
213     EXPECT_EQ(std::string(expectation->signon_realm), form->signon_realm)
214         << test_label;
215     EXPECT_EQ(GURL(expectation->origin), form->origin) << test_label;
216     EXPECT_EQ(GURL(expectation->action), form->action) << test_label;
217     EXPECT_EQ(WideToUTF16(expectation->submit_element), form->submit_element)
218         << test_label;
219     EXPECT_EQ(WideToUTF16(expectation->username_element),
220               form->username_element) << test_label;
221     EXPECT_EQ(WideToUTF16(expectation->password_element),
222               form->password_element) << test_label;
223     if (expectation->username_value) {
224       EXPECT_EQ(WideToUTF16(expectation->username_value),
225                 form->username_value) << test_label;
226       EXPECT_EQ(WideToUTF16(expectation->password_value),
227                 form->password_value) << test_label;
228     } else {
229       EXPECT_TRUE(form->blacklisted_by_user) << test_label;
230     }
231     EXPECT_EQ(expectation->preferred, form->preferred)  << test_label;
232     EXPECT_EQ(expectation->ssl_valid, form->ssl_valid) << test_label;
233     EXPECT_DOUBLE_EQ(expectation->creation_time,
234                      form->date_created.ToDoubleT()) << test_label;
235   }
236 }
237
238 #pragma mark -
239
240 TEST_F(PasswordStoreMacInternalsTest, TestKeychainToFormTranslation) {
241   typedef struct {
242     const PasswordForm::Scheme scheme;
243     const char* signon_realm;
244     const char* origin;
245     const wchar_t* username;  // Set to NULL to check for a blacklist entry.
246     const wchar_t* password;
247     const bool ssl_valid;
248     const int creation_year;
249     const int creation_month;
250     const int creation_day;
251     const int creation_hour;
252     const int creation_minute;
253     const int creation_second;
254   } TestExpectations;
255
256   TestExpectations expected[] = {
257     { PasswordForm::SCHEME_HTML, "http://some.domain.com/",
258       "http://some.domain.com/", L"joe_user", L"sekrit", false,
259       2002,  6,  1, 17, 15,  0 },
260     { PasswordForm::SCHEME_HTML, "http://some.domain.com/",
261       "http://some.domain.com/insecure.html", L"joe_user", L"sekrit", false,
262       1999, 12, 31, 23, 59, 59 },
263     { PasswordForm::SCHEME_HTML, "https://some.domain.com/",
264       "https://some.domain.com/secure.html", L"secure_user", L"password", true,
265       2010,  9,  8,  7,  6,  5 },
266     { PasswordForm::SCHEME_HTML, "http://dont.remember.com/",
267       "http://dont.remember.com/", NULL, NULL, false,
268       2000,  1,  1,  0,  0,  0 },
269     { PasswordForm::SCHEME_HTML, "http://dont.remember.com/",
270       "http://dont.remember.com/", NULL, NULL, false,
271       2000,  1,  1,  0,  0,  0 },
272     { PasswordForm::SCHEME_HTML, "https://dont.remember.com/",
273       "https://dont.remember.com/", NULL, NULL, true,
274       2000,  1,  1,  0,  0,  0 },
275     { PasswordForm::SCHEME_BASIC, "http://some.domain.com:4567/low_security",
276       "http://some.domain.com:4567/insecure.html", L"basic_auth_user", L"basic",
277       false, 1998, 03, 30, 10, 00, 00 },
278     { PasswordForm::SCHEME_DIGEST, "https://some.domain.com/high_security",
279       "https://some.domain.com/", L"digest_auth_user", L"digest", true,
280       1998,  3, 30, 10,  0,  0 },
281     // This one gives us an invalid date, which we will treat as a "NULL" date
282     // which is 1601.
283     { PasswordForm::SCHEME_OTHER, "http://a.server.com/",
284       "http://a.server.com/", L"abc", L"123", false,
285       1601,  1,  1,  0,  0,  0 },
286   };
287
288   for (unsigned int i = 0; i < ARRAYSIZE_UNSAFE(expected); ++i) {
289     // Create our fake KeychainItemRef; see MockAppleKeychain docs.
290     SecKeychainItemRef keychain_item =
291         reinterpret_cast<SecKeychainItemRef>(i + 1);
292     PasswordForm form;
293     bool parsed = internal_keychain_helpers::FillPasswordFormFromKeychainItem(
294         *keychain_, keychain_item, &form, true);
295
296     EXPECT_TRUE(parsed) << "In iteration " << i;
297
298     EXPECT_EQ(expected[i].scheme, form.scheme) << "In iteration " << i;
299     EXPECT_EQ(GURL(expected[i].origin), form.origin) << "In iteration " << i;
300     EXPECT_EQ(expected[i].ssl_valid, form.ssl_valid) << "In iteration " << i;
301     EXPECT_EQ(std::string(expected[i].signon_realm), form.signon_realm)
302         << "In iteration " << i;
303     if (expected[i].username) {
304       EXPECT_EQ(WideToUTF16(expected[i].username), form.username_value)
305           << "In iteration " << i;
306       EXPECT_EQ(WideToUTF16(expected[i].password), form.password_value)
307           << "In iteration " << i;
308       EXPECT_FALSE(form.blacklisted_by_user) << "In iteration " << i;
309     } else {
310       EXPECT_TRUE(form.blacklisted_by_user) << "In iteration " << i;
311     }
312     base::Time::Exploded exploded_time;
313     form.date_created.UTCExplode(&exploded_time);
314     EXPECT_EQ(expected[i].creation_year, exploded_time.year)
315          << "In iteration " << i;
316     EXPECT_EQ(expected[i].creation_month, exploded_time.month)
317         << "In iteration " << i;
318     EXPECT_EQ(expected[i].creation_day, exploded_time.day_of_month)
319         << "In iteration " << i;
320     EXPECT_EQ(expected[i].creation_hour, exploded_time.hour)
321         << "In iteration " << i;
322     EXPECT_EQ(expected[i].creation_minute, exploded_time.minute)
323         << "In iteration " << i;
324     EXPECT_EQ(expected[i].creation_second, exploded_time.second)
325         << "In iteration " << i;
326   }
327
328   {
329     // Use an invalid ref, to make sure errors are reported.
330     SecKeychainItemRef keychain_item = reinterpret_cast<SecKeychainItemRef>(99);
331     PasswordForm form;
332     bool parsed = internal_keychain_helpers::FillPasswordFormFromKeychainItem(
333         *keychain_, keychain_item, &form, true);
334     EXPECT_FALSE(parsed);
335   }
336 }
337
338 TEST_F(PasswordStoreMacInternalsTest, TestKeychainSearch) {
339   struct TestDataAndExpectation {
340     const PasswordFormData data;
341     const size_t expected_fill_matches;
342     const size_t expected_merge_matches;
343   };
344   // Most fields are left blank because we don't care about them for searching.
345   TestDataAndExpectation test_data[] = {
346     // An HTML form we've seen.
347     { { PasswordForm::SCHEME_HTML, "http://some.domain.com/",
348         NULL, NULL, NULL, NULL, NULL, L"joe_user", NULL, false, false, 0 },
349       2, 2 },
350     { { PasswordForm::SCHEME_HTML, "http://some.domain.com/",
351         NULL, NULL, NULL, NULL, NULL, L"wrong_user", NULL, false, false, 0 },
352       2, 0 },
353     // An HTML form we haven't seen
354     { { PasswordForm::SCHEME_HTML, "http://www.unseendomain.com/",
355         NULL, NULL, NULL, NULL, NULL, L"joe_user", NULL, false, false, 0 },
356       0, 0 },
357     // Basic auth that should match.
358     { { PasswordForm::SCHEME_BASIC, "http://some.domain.com:4567/low_security",
359         NULL, NULL, NULL, NULL, NULL, L"basic_auth_user", NULL, false, false,
360         0 },
361       1, 1 },
362     // Basic auth with the wrong port.
363     { { PasswordForm::SCHEME_BASIC, "http://some.domain.com:1111/low_security",
364         NULL, NULL, NULL, NULL, NULL, L"basic_auth_user", NULL, false, false,
365         0 },
366       0, 0 },
367     // Digest auth we've saved under https, visited with http.
368     { { PasswordForm::SCHEME_DIGEST, "http://some.domain.com/high_security",
369         NULL, NULL, NULL, NULL, NULL, L"digest_auth_user", NULL, false, false,
370         0 },
371       0, 0 },
372     // Digest auth that should match.
373     { { PasswordForm::SCHEME_DIGEST, "https://some.domain.com/high_security",
374         NULL, NULL, NULL, NULL, NULL, L"wrong_user", NULL, false, true, 0 },
375       1, 0 },
376     // Digest auth with the wrong domain.
377     { { PasswordForm::SCHEME_DIGEST, "https://some.domain.com/other_domain",
378         NULL, NULL, NULL, NULL, NULL, L"digest_auth_user", NULL, false, true,
379         0 },
380       0, 0 },
381     // Garbage forms should have no matches.
382     { { PasswordForm::SCHEME_HTML, "foo/bar/baz",
383         NULL, NULL, NULL, NULL, NULL, NULL, NULL, false, false, 0 }, 0, 0 },
384   };
385
386   MacKeychainPasswordFormAdapter keychain_adapter(keychain_);
387   MacKeychainPasswordFormAdapter owned_keychain_adapter(keychain_);
388   owned_keychain_adapter.SetFindsOnlyOwnedItems(true);
389   for (unsigned int i = 0; i < ARRAYSIZE_UNSAFE(test_data); ++i) {
390     scoped_ptr<PasswordForm> query_form(
391         CreatePasswordFormFromData(test_data[i].data));
392
393     // Check matches treating the form as a fill target.
394     std::vector<PasswordForm*> matching_items =
395         keychain_adapter.PasswordsFillingForm(query_form->signon_realm,
396                                               query_form->scheme);
397     EXPECT_EQ(test_data[i].expected_fill_matches, matching_items.size());
398     STLDeleteElements(&matching_items);
399
400     // Check matches treating the form as a merging target.
401     EXPECT_EQ(test_data[i].expected_merge_matches > 0,
402               keychain_adapter.HasPasswordsMergeableWithForm(*query_form));
403     std::vector<SecKeychainItemRef> keychain_items;
404     std::vector<internal_keychain_helpers::ItemFormPair> item_form_pairs =
405         internal_keychain_helpers::
406             ExtractAllKeychainItemAttributesIntoPasswordForms(&keychain_items,
407                                                               *keychain_);
408     matching_items =
409         internal_keychain_helpers::ExtractPasswordsMergeableWithForm(
410             *keychain_, item_form_pairs, *query_form);
411     EXPECT_EQ(test_data[i].expected_merge_matches, matching_items.size());
412     STLDeleteContainerPairSecondPointers(item_form_pairs.begin(),
413                                          item_form_pairs.end());
414     for (std::vector<SecKeychainItemRef>::iterator i = keychain_items.begin();
415          i != keychain_items.end(); ++i) {
416       keychain_->Free(*i);
417     }
418     STLDeleteElements(&matching_items);
419
420     // None of the pre-seeded items are owned by us, so none should match an
421     // owned-passwords-only search.
422     matching_items = owned_keychain_adapter.PasswordsFillingForm(
423         query_form->signon_realm, query_form->scheme);
424     EXPECT_EQ(0U, matching_items.size());
425     STLDeleteElements(&matching_items);
426   }
427 }
428
429 // Changes just the origin path of |form|.
430 static void SetPasswordFormPath(PasswordForm* form, const char* path) {
431   GURL::Replacements replacement;
432   std::string new_value(path);
433   replacement.SetPathStr(new_value);
434   form->origin = form->origin.ReplaceComponents(replacement);
435 }
436
437 // Changes just the signon_realm port of |form|.
438 static void SetPasswordFormPort(PasswordForm* form, const char* port) {
439   GURL::Replacements replacement;
440   std::string new_value(port);
441   replacement.SetPortStr(new_value);
442   GURL signon_gurl = GURL(form->signon_realm);
443   form->signon_realm = signon_gurl.ReplaceComponents(replacement).spec();
444 }
445
446 // Changes just the signon_ream auth realm of |form|.
447 static void SetPasswordFormRealm(PasswordForm* form, const char* realm) {
448   GURL::Replacements replacement;
449   std::string new_value(realm);
450   replacement.SetPathStr(new_value);
451   GURL signon_gurl = GURL(form->signon_realm);
452   form->signon_realm = signon_gurl.ReplaceComponents(replacement).spec();
453 }
454
455 TEST_F(PasswordStoreMacInternalsTest, TestKeychainExactSearch) {
456   MacKeychainPasswordFormAdapter keychain_adapter(keychain_);
457
458   PasswordFormData base_form_data[] = {
459     { PasswordForm::SCHEME_HTML, "http://some.domain.com/",
460       "http://some.domain.com/insecure.html",
461       NULL, NULL, NULL, NULL, L"joe_user", NULL, true, false, 0 },
462     { PasswordForm::SCHEME_BASIC, "http://some.domain.com:4567/low_security",
463       "http://some.domain.com:4567/insecure.html",
464       NULL, NULL, NULL, NULL, L"basic_auth_user", NULL, true, false, 0 },
465     { PasswordForm::SCHEME_DIGEST, "https://some.domain.com/high_security",
466       "https://some.domain.com",
467       NULL, NULL, NULL, NULL, L"digest_auth_user", NULL, true, true, 0 },
468   };
469
470   for (unsigned int i = 0; i < arraysize(base_form_data); ++i) {
471     // Create a base form and make sure we find a match.
472     scoped_ptr<PasswordForm> base_form(CreatePasswordFormFromData(
473         base_form_data[i]));
474     EXPECT_TRUE(keychain_adapter.HasPasswordsMergeableWithForm(*base_form));
475     PasswordForm* match =
476         keychain_adapter.PasswordExactlyMatchingForm(*base_form);
477     EXPECT_TRUE(match != NULL);
478     if (match) {
479       EXPECT_EQ(base_form->scheme, match->scheme);
480       EXPECT_EQ(base_form->origin, match->origin);
481       EXPECT_EQ(base_form->username_value, match->username_value);
482       delete match;
483     }
484
485     // Make sure that the matching isn't looser than it should be by checking
486     // that slightly altered forms don't match.
487     std::vector<PasswordForm*> modified_forms;
488
489     modified_forms.push_back(new PasswordForm(*base_form));
490     modified_forms.back()->username_value = ASCIIToUTF16("wrong_user");
491
492     modified_forms.push_back(new PasswordForm(*base_form));
493     SetPasswordFormPath(modified_forms.back(), "elsewhere.html");
494
495     modified_forms.push_back(new PasswordForm(*base_form));
496     modified_forms.back()->scheme = PasswordForm::SCHEME_OTHER;
497
498     modified_forms.push_back(new PasswordForm(*base_form));
499     SetPasswordFormPort(modified_forms.back(), "1234");
500
501     modified_forms.push_back(new PasswordForm(*base_form));
502     modified_forms.back()->blacklisted_by_user = true;
503
504     if (base_form->scheme == PasswordForm::SCHEME_BASIC ||
505         base_form->scheme == PasswordForm::SCHEME_DIGEST) {
506       modified_forms.push_back(new PasswordForm(*base_form));
507       SetPasswordFormRealm(modified_forms.back(), "incorrect");
508     }
509
510     for (unsigned int j = 0; j < modified_forms.size(); ++j) {
511       PasswordForm* match =
512           keychain_adapter.PasswordExactlyMatchingForm(*modified_forms[j]);
513       EXPECT_EQ(NULL, match) << "In modified version " << j << " of base form "
514                              << i;
515     }
516     STLDeleteElements(&modified_forms);
517   }
518 }
519
520 TEST_F(PasswordStoreMacInternalsTest, TestKeychainAdd) {
521   struct TestDataAndExpectation {
522     PasswordFormData data;
523     bool should_succeed;
524   };
525   TestDataAndExpectation test_data[] = {
526     // Test a variety of scheme/port/protocol/path variations.
527     { { PasswordForm::SCHEME_HTML, "http://web.site.com/",
528         "http://web.site.com/path/to/page.html", NULL, NULL, NULL, NULL,
529         L"anonymous", L"knock-knock", false, false, 0 }, true },
530     { { PasswordForm::SCHEME_HTML, "https://web.site.com/",
531         "https://web.site.com/", NULL, NULL, NULL, NULL,
532         L"admin", L"p4ssw0rd", false, false, 0 }, true },
533     { { PasswordForm::SCHEME_BASIC, "http://a.site.com:2222/therealm",
534         "http://a.site.com:2222/", NULL, NULL, NULL, NULL,
535         L"username", L"password", false, false, 0 }, true },
536     { { PasswordForm::SCHEME_DIGEST, "https://digest.site.com/differentrealm",
537         "https://digest.site.com/secure.html", NULL, NULL, NULL, NULL,
538         L"testname", L"testpass", false, false, 0 }, true },
539     // Make sure that garbage forms are rejected.
540     { { PasswordForm::SCHEME_HTML, "gobbledygook",
541         "gobbledygook", NULL, NULL, NULL, NULL,
542         L"anonymous", L"knock-knock", false, false, 0 }, false },
543     // Test that failing to update a duplicate (forced using the magic failure
544     // password; see MockAppleKeychain::ItemModifyAttributesAndData) is
545     // reported.
546     { { PasswordForm::SCHEME_HTML, "http://some.domain.com",
547         "http://some.domain.com/insecure.html", NULL, NULL, NULL, NULL,
548         L"joe_user", L"fail_me", false, false, 0 }, false },
549   };
550
551   MacKeychainPasswordFormAdapter owned_keychain_adapter(keychain_);
552   owned_keychain_adapter.SetFindsOnlyOwnedItems(true);
553
554   for (unsigned int i = 0; i < ARRAYSIZE_UNSAFE(test_data); ++i) {
555     scoped_ptr<PasswordForm> in_form(
556         CreatePasswordFormFromData(test_data[i].data));
557     bool add_succeeded = owned_keychain_adapter.AddPassword(*in_form);
558     EXPECT_EQ(test_data[i].should_succeed, add_succeeded);
559     if (add_succeeded) {
560       EXPECT_TRUE(owned_keychain_adapter.HasPasswordsMergeableWithForm(
561           *in_form));
562       scoped_ptr<PasswordForm> out_form(
563           owned_keychain_adapter.PasswordExactlyMatchingForm(*in_form));
564       EXPECT_TRUE(out_form.get() != NULL);
565       EXPECT_EQ(out_form->scheme, in_form->scheme);
566       EXPECT_EQ(out_form->signon_realm, in_form->signon_realm);
567       EXPECT_EQ(out_form->origin, in_form->origin);
568       EXPECT_EQ(out_form->username_value, in_form->username_value);
569       EXPECT_EQ(out_form->password_value, in_form->password_value);
570     }
571   }
572
573   // Test that adding duplicate item updates the existing item.
574   {
575     PasswordFormData data = {
576       PasswordForm::SCHEME_HTML, "http://some.domain.com",
577       "http://some.domain.com/insecure.html", NULL,
578       NULL, NULL, NULL, L"joe_user", L"updated_password", false, false, 0
579     };
580     scoped_ptr<PasswordForm> update_form(CreatePasswordFormFromData(data));
581     MacKeychainPasswordFormAdapter keychain_adapter(keychain_);
582     EXPECT_TRUE(keychain_adapter.AddPassword(*update_form));
583     SecKeychainItemRef keychain_item = reinterpret_cast<SecKeychainItemRef>(2);
584     PasswordForm stored_form;
585     internal_keychain_helpers::FillPasswordFormFromKeychainItem(*keychain_,
586                                                                 keychain_item,
587                                                                 &stored_form,
588                                                                 true);
589     EXPECT_EQ(update_form->password_value, stored_form.password_value);
590   }
591 }
592
593 TEST_F(PasswordStoreMacInternalsTest, TestKeychainRemove) {
594   struct TestDataAndExpectation {
595     PasswordFormData data;
596     bool should_succeed;
597   };
598   TestDataAndExpectation test_data[] = {
599     // Test deletion of an item that we add.
600     { { PasswordForm::SCHEME_HTML, "http://web.site.com/",
601         "http://web.site.com/path/to/page.html", NULL, NULL, NULL, NULL,
602         L"anonymous", L"knock-knock", false, false, 0 }, true },
603     // Make sure we don't delete items we don't own.
604     { { PasswordForm::SCHEME_HTML, "http://some.domain.com/",
605         "http://some.domain.com/insecure.html", NULL, NULL, NULL, NULL,
606         L"joe_user", NULL, true, false, 0 }, false },
607   };
608
609   MacKeychainPasswordFormAdapter owned_keychain_adapter(keychain_);
610   owned_keychain_adapter.SetFindsOnlyOwnedItems(true);
611
612   // Add our test item so that we can delete it.
613   PasswordForm* add_form = CreatePasswordFormFromData(test_data[0].data);
614   EXPECT_TRUE(owned_keychain_adapter.AddPassword(*add_form));
615   delete add_form;
616
617   for (unsigned int i = 0; i < ARRAYSIZE_UNSAFE(test_data); ++i) {
618     scoped_ptr<PasswordForm> form(CreatePasswordFormFromData(
619         test_data[i].data));
620     EXPECT_EQ(test_data[i].should_succeed,
621               owned_keychain_adapter.RemovePassword(*form));
622
623     MacKeychainPasswordFormAdapter keychain_adapter(keychain_);
624     PasswordForm* match = keychain_adapter.PasswordExactlyMatchingForm(*form);
625     EXPECT_EQ(test_data[i].should_succeed, match == NULL);
626     if (match) {
627       delete match;
628     }
629   }
630 }
631
632 TEST_F(PasswordStoreMacInternalsTest, TestFormMatch) {
633   PasswordForm base_form;
634   base_form.signon_realm = std::string("http://some.domain.com/");
635   base_form.origin = GURL("http://some.domain.com/page.html");
636   base_form.username_value = ASCIIToUTF16("joe_user");
637
638   {
639     // Check that everything unimportant can be changed.
640     PasswordForm different_form(base_form);
641     different_form.username_element = ASCIIToUTF16("username");
642     different_form.submit_element = ASCIIToUTF16("submit");
643     different_form.username_element = ASCIIToUTF16("password");
644     different_form.password_value = ASCIIToUTF16("sekrit");
645     different_form.action = GURL("http://some.domain.com/action.cgi");
646     different_form.ssl_valid = true;
647     different_form.preferred = true;
648     different_form.date_created = base::Time::Now();
649     EXPECT_TRUE(
650         FormsMatchForMerge(base_form, different_form, STRICT_FORM_MATCH));
651
652     // Check that path differences don't prevent a match.
653     base_form.origin = GURL("http://some.domain.com/other_page.html");
654     EXPECT_TRUE(
655         FormsMatchForMerge(base_form, different_form, STRICT_FORM_MATCH));
656   }
657
658   // Check that any one primary key changing is enough to prevent matching.
659   {
660     PasswordForm different_form(base_form);
661     different_form.scheme = PasswordForm::SCHEME_DIGEST;
662     EXPECT_FALSE(
663         FormsMatchForMerge(base_form, different_form, STRICT_FORM_MATCH));
664   }
665   {
666     PasswordForm different_form(base_form);
667     different_form.signon_realm = std::string("http://some.domain.com:8080/");
668     EXPECT_FALSE(
669         FormsMatchForMerge(base_form, different_form, STRICT_FORM_MATCH));
670   }
671   {
672     PasswordForm different_form(base_form);
673     different_form.username_value = ASCIIToUTF16("john.doe");
674     EXPECT_FALSE(
675         FormsMatchForMerge(base_form, different_form, STRICT_FORM_MATCH));
676   }
677   {
678     PasswordForm different_form(base_form);
679     different_form.blacklisted_by_user = true;
680     EXPECT_FALSE(
681         FormsMatchForMerge(base_form, different_form, STRICT_FORM_MATCH));
682   }
683
684   // Blacklist forms should *never* match for merging, even when identical
685   // (and certainly not when only one is a blacklist entry).
686   {
687     PasswordForm form_a(base_form);
688     form_a.blacklisted_by_user = true;
689     PasswordForm form_b(form_a);
690     EXPECT_FALSE(FormsMatchForMerge(form_a, form_b, STRICT_FORM_MATCH));
691   }
692 }
693
694 TEST_F(PasswordStoreMacInternalsTest, TestFormMerge) {
695   // Set up a bunch of test data to use in varying combinations.
696   PasswordFormData keychain_user_1 =
697       { PasswordForm::SCHEME_HTML, "http://some.domain.com/",
698         "http://some.domain.com/", "", L"", L"", L"", L"joe_user", L"sekrit",
699         false, false, 1010101010 };
700   PasswordFormData keychain_user_1_with_path =
701       { PasswordForm::SCHEME_HTML, "http://some.domain.com/",
702         "http://some.domain.com/page.html",
703         "", L"", L"", L"", L"joe_user", L"otherpassword",
704         false, false, 1010101010 };
705   PasswordFormData keychain_user_2 =
706       { PasswordForm::SCHEME_HTML, "http://some.domain.com/",
707         "http://some.domain.com/", "", L"", L"", L"", L"john.doe", L"sesame",
708         false, false, 958739876 };
709   PasswordFormData keychain_blacklist =
710       { PasswordForm::SCHEME_HTML, "http://some.domain.com/",
711         "http://some.domain.com/", "", L"", L"", L"", NULL, NULL,
712         false, false, 1010101010 };
713
714   PasswordFormData db_user_1 =
715       { PasswordForm::SCHEME_HTML, "http://some.domain.com/",
716         "http://some.domain.com/", "http://some.domain.com/action.cgi",
717         L"submit", L"username", L"password", L"joe_user", L"",
718         true, false, 1212121212 };
719   PasswordFormData db_user_1_with_path =
720       { PasswordForm::SCHEME_HTML, "http://some.domain.com/",
721         "http://some.domain.com/page.html",
722         "http://some.domain.com/handlepage.cgi",
723         L"submit", L"username", L"password", L"joe_user", L"",
724         true, false, 1234567890 };
725   PasswordFormData db_user_3_with_path =
726       { PasswordForm::SCHEME_HTML, "http://some.domain.com/",
727         "http://some.domain.com/page.html",
728         "http://some.domain.com/handlepage.cgi",
729         L"submit", L"username", L"password", L"second-account", L"",
730         true, false, 1240000000 };
731   PasswordFormData database_blacklist_with_path =
732       { PasswordForm::SCHEME_HTML, "http://some.domain.com/",
733         "http://some.domain.com/path.html", "http://some.domain.com/action.cgi",
734         L"submit", L"username", L"password", NULL, NULL,
735         true, false, 1212121212 };
736
737   PasswordFormData merged_user_1 =
738       { PasswordForm::SCHEME_HTML, "http://some.domain.com/",
739         "http://some.domain.com/", "http://some.domain.com/action.cgi",
740         L"submit", L"username", L"password", L"joe_user", L"sekrit",
741         true, false, 1212121212 };
742   PasswordFormData merged_user_1_with_db_path =
743       { PasswordForm::SCHEME_HTML, "http://some.domain.com/",
744         "http://some.domain.com/page.html",
745         "http://some.domain.com/handlepage.cgi",
746         L"submit", L"username", L"password", L"joe_user", L"sekrit",
747         true, false, 1234567890 };
748   PasswordFormData merged_user_1_with_both_paths =
749       { PasswordForm::SCHEME_HTML, "http://some.domain.com/",
750         "http://some.domain.com/page.html",
751         "http://some.domain.com/handlepage.cgi",
752         L"submit", L"username", L"password", L"joe_user", L"otherpassword",
753         true, false, 1234567890 };
754
755   // Build up the big multi-dimensional array of data sets that will actually
756   // drive the test. Use vectors rather than arrays so that initialization is
757   // simple.
758   enum {
759     KEYCHAIN_INPUT = 0,
760     DATABASE_INPUT,
761     MERGE_OUTPUT,
762     KEYCHAIN_OUTPUT,
763     DATABASE_OUTPUT,
764     MERGE_IO_ARRAY_COUNT  // termination marker
765   };
766   const unsigned int kTestCount = 4;
767   std::vector< std::vector< std::vector<PasswordFormData*> > > test_data(
768       MERGE_IO_ARRAY_COUNT, std::vector< std::vector<PasswordFormData*> >(
769           kTestCount, std::vector<PasswordFormData*>()));
770   unsigned int current_test = 0;
771
772   // Test a merge with a few accounts in both systems, with partial overlap.
773   CHECK(current_test < kTestCount);
774   test_data[KEYCHAIN_INPUT][current_test].push_back(&keychain_user_1);
775   test_data[KEYCHAIN_INPUT][current_test].push_back(&keychain_user_2);
776   test_data[DATABASE_INPUT][current_test].push_back(&db_user_1);
777   test_data[DATABASE_INPUT][current_test].push_back(&db_user_1_with_path);
778   test_data[DATABASE_INPUT][current_test].push_back(&db_user_3_with_path);
779   test_data[MERGE_OUTPUT][current_test].push_back(&merged_user_1);
780   test_data[MERGE_OUTPUT][current_test].push_back(&merged_user_1_with_db_path);
781   test_data[KEYCHAIN_OUTPUT][current_test].push_back(&keychain_user_2);
782   test_data[DATABASE_OUTPUT][current_test].push_back(&db_user_3_with_path);
783
784   // Test a merge where Chrome has a blacklist entry, and the keychain has
785   // a stored account.
786   ++current_test;
787   CHECK(current_test < kTestCount);
788   test_data[KEYCHAIN_INPUT][current_test].push_back(&keychain_user_1);
789   test_data[DATABASE_INPUT][current_test].push_back(
790       &database_blacklist_with_path);
791   // We expect both to be present because a blacklist could be specific to a
792   // subpath, and we want access to the password on other paths.
793   test_data[MERGE_OUTPUT][current_test].push_back(
794       &database_blacklist_with_path);
795   test_data[KEYCHAIN_OUTPUT][current_test].push_back(&keychain_user_1);
796
797   // Test a merge where Chrome has an account, and Keychain has a blacklist
798   // (from another browser) and the Chrome password data.
799   ++current_test;
800   CHECK(current_test < kTestCount);
801   test_data[KEYCHAIN_INPUT][current_test].push_back(&keychain_blacklist);
802   test_data[KEYCHAIN_INPUT][current_test].push_back(&keychain_user_1);
803   test_data[DATABASE_INPUT][current_test].push_back(&db_user_1);
804   test_data[MERGE_OUTPUT][current_test].push_back(&merged_user_1);
805   test_data[KEYCHAIN_OUTPUT][current_test].push_back(&keychain_blacklist);
806
807   // Test that matches are done using exact path when possible.
808   ++current_test;
809   CHECK(current_test < kTestCount);
810   test_data[KEYCHAIN_INPUT][current_test].push_back(&keychain_user_1);
811   test_data[KEYCHAIN_INPUT][current_test].push_back(&keychain_user_1_with_path);
812   test_data[DATABASE_INPUT][current_test].push_back(&db_user_1);
813   test_data[DATABASE_INPUT][current_test].push_back(&db_user_1_with_path);
814   test_data[MERGE_OUTPUT][current_test].push_back(&merged_user_1);
815   test_data[MERGE_OUTPUT][current_test].push_back(
816       &merged_user_1_with_both_paths);
817
818   for (unsigned int test_case = 0; test_case <= current_test; ++test_case) {
819     std::vector<PasswordForm*> keychain_forms;
820     for (std::vector<PasswordFormData*>::iterator i =
821              test_data[KEYCHAIN_INPUT][test_case].begin();
822          i != test_data[KEYCHAIN_INPUT][test_case].end(); ++i) {
823       keychain_forms.push_back(CreatePasswordFormFromData(*(*i)));
824     }
825     std::vector<PasswordForm*> database_forms;
826     for (std::vector<PasswordFormData*>::iterator i =
827              test_data[DATABASE_INPUT][test_case].begin();
828          i != test_data[DATABASE_INPUT][test_case].end(); ++i) {
829       database_forms.push_back(CreatePasswordFormFromData(*(*i)));
830     }
831
832     std::vector<PasswordForm*> merged_forms;
833     internal_keychain_helpers::MergePasswordForms(&keychain_forms,
834                                                   &database_forms,
835                                                   &merged_forms);
836
837     CHECK_FORMS(keychain_forms, test_data[KEYCHAIN_OUTPUT][test_case],
838                 test_case);
839     CHECK_FORMS(database_forms, test_data[DATABASE_OUTPUT][test_case],
840                 test_case);
841     CHECK_FORMS(merged_forms, test_data[MERGE_OUTPUT][test_case], test_case);
842
843     STLDeleteElements(&keychain_forms);
844     STLDeleteElements(&database_forms);
845     STLDeleteElements(&merged_forms);
846   }
847 }
848
849 TEST_F(PasswordStoreMacInternalsTest, TestPasswordBulkLookup) {
850   PasswordFormData db_data[] = {
851     { PasswordForm::SCHEME_HTML, "http://some.domain.com/",
852       "http://some.domain.com/", "http://some.domain.com/action.cgi",
853       L"submit", L"username", L"password", L"joe_user", L"",
854       true, false, 1212121212 },
855     { PasswordForm::SCHEME_HTML, "http://some.domain.com/",
856       "http://some.domain.com/page.html",
857       "http://some.domain.com/handlepage.cgi",
858       L"submit", L"username", L"password", L"joe_user", L"",
859       true, false, 1234567890 },
860     { PasswordForm::SCHEME_HTML, "http://some.domain.com/",
861       "http://some.domain.com/page.html",
862       "http://some.domain.com/handlepage.cgi",
863       L"submit", L"username", L"password", L"second-account", L"",
864       true, false, 1240000000 },
865     { PasswordForm::SCHEME_HTML, "http://dont.remember.com/",
866       "http://dont.remember.com/",
867       "http://dont.remember.com/handlepage.cgi",
868       L"submit", L"username", L"password", L"joe_user", L"",
869       true, false, 1240000000 },
870     { PasswordForm::SCHEME_HTML, "http://some.domain.com/",
871       "http://some.domain.com/path.html", "http://some.domain.com/action.cgi",
872       L"submit", L"username", L"password", NULL, NULL,
873       true, false, 1212121212 },
874   };
875   std::vector<PasswordForm*> database_forms;
876   for (unsigned int i = 0; i < ARRAYSIZE_UNSAFE(db_data); ++i) {
877     database_forms.push_back(CreatePasswordFormFromData(db_data[i]));
878   }
879   std::vector<PasswordForm*> merged_forms =
880       internal_keychain_helpers::GetPasswordsForForms(*keychain_,
881                                                       &database_forms);
882   EXPECT_EQ(2U, database_forms.size());
883   ASSERT_EQ(3U, merged_forms.size());
884   EXPECT_EQ(ASCIIToUTF16("sekrit"), merged_forms[0]->password_value);
885   EXPECT_EQ(ASCIIToUTF16("sekrit"), merged_forms[1]->password_value);
886   EXPECT_TRUE(merged_forms[2]->blacklisted_by_user);
887
888   STLDeleteElements(&database_forms);
889   STLDeleteElements(&merged_forms);
890 }
891
892 TEST_F(PasswordStoreMacInternalsTest, TestBlacklistedFiltering) {
893   PasswordFormData db_data[] = {
894     { PasswordForm::SCHEME_HTML, "http://dont.remember.com/",
895       "http://dont.remember.com/",
896       "http://dont.remember.com/handlepage.cgi",
897       L"submit", L"username", L"password", L"joe_user", L"non_empty_password",
898       true, false, 1240000000 },
899     { PasswordForm::SCHEME_HTML, "https://dont.remember.com/",
900       "https://dont.remember.com/",
901       "https://dont.remember.com/handlepage_secure.cgi",
902       L"submit", L"username", L"password", L"joe_user", L"non_empty_password",
903       true, false, 1240000000 },
904   };
905   std::vector<PasswordForm*> database_forms;
906   for (unsigned int i = 0; i < ARRAYSIZE_UNSAFE(db_data); ++i) {
907     database_forms.push_back(CreatePasswordFormFromData(db_data[i]));
908   }
909   std::vector<PasswordForm*> merged_forms =
910       internal_keychain_helpers::GetPasswordsForForms(*keychain_,
911                                                       &database_forms);
912   EXPECT_EQ(2U, database_forms.size());
913   ASSERT_EQ(0U, merged_forms.size());
914
915   STLDeleteElements(&database_forms);
916   STLDeleteElements(&merged_forms);
917 }
918
919 TEST_F(PasswordStoreMacInternalsTest, TestFillPasswordFormFromKeychainItem) {
920   // When |extract_password_data| is false, the password field must be empty,
921   // and |blacklisted_by_user| must be false.
922   SecKeychainItemRef keychain_item = reinterpret_cast<SecKeychainItemRef>(1);
923   PasswordForm form_without_extracted_password;
924   bool parsed = internal_keychain_helpers::FillPasswordFormFromKeychainItem(
925       *keychain_,
926       keychain_item,
927       &form_without_extracted_password,
928       false);  // Do not extract password.
929   EXPECT_TRUE(parsed);
930   ASSERT_TRUE(form_without_extracted_password.password_value.empty());
931   ASSERT_FALSE(form_without_extracted_password.blacklisted_by_user);
932
933   // When |extract_password_data| is true and the keychain entry has a non-empty
934   // password, the password field must be non-empty, and the value of
935   // |blacklisted_by_user| must be false.
936   keychain_item = reinterpret_cast<SecKeychainItemRef>(1);
937   PasswordForm form_with_extracted_password;
938   parsed = internal_keychain_helpers::FillPasswordFormFromKeychainItem(
939       *keychain_,
940       keychain_item,
941       &form_with_extracted_password,
942       true);  // Extract password.
943   EXPECT_TRUE(parsed);
944   ASSERT_EQ(ASCIIToUTF16("sekrit"),
945             form_with_extracted_password.password_value);
946   ASSERT_FALSE(form_with_extracted_password.blacklisted_by_user);
947
948   // When |extract_password_data| is true and the keychain entry has an empty
949   // username and password (""), the password field must be empty, and the value
950   // of |blacklisted_by_user| must be true.
951   keychain_item = reinterpret_cast<SecKeychainItemRef>(4);
952   PasswordForm negative_form;
953   parsed = internal_keychain_helpers::FillPasswordFormFromKeychainItem(
954       *keychain_,
955       keychain_item,
956       &negative_form,
957       true);  // Extract password.
958   EXPECT_TRUE(parsed);
959   ASSERT_TRUE(negative_form.username_value.empty());
960   ASSERT_TRUE(negative_form.password_value.empty());
961   ASSERT_TRUE(negative_form.blacklisted_by_user);
962
963   // When |extract_password_data| is true and the keychain entry has an empty
964   // password (""), the password field must be empty (""), and the value of
965   // |blacklisted_by_user| must be true.
966   keychain_item = reinterpret_cast<SecKeychainItemRef>(5);
967   PasswordForm form_with_empty_password_a;
968   parsed = internal_keychain_helpers::FillPasswordFormFromKeychainItem(
969       *keychain_,
970       keychain_item,
971       &form_with_empty_password_a,
972       true);  // Extract password.
973   EXPECT_TRUE(parsed);
974   ASSERT_TRUE(form_with_empty_password_a.password_value.empty());
975   ASSERT_TRUE(form_with_empty_password_a.blacklisted_by_user);
976
977   // When |extract_password_data| is true and the keychain entry has a single
978   // space password (" "), the password field must be a single space (" "), and
979   // the value of |blacklisted_by_user| must be true.
980   keychain_item = reinterpret_cast<SecKeychainItemRef>(6);
981   PasswordForm form_with_empty_password_b;
982   parsed = internal_keychain_helpers::FillPasswordFormFromKeychainItem(
983       *keychain_,
984       keychain_item,
985       &form_with_empty_password_b,
986       true);  // Extract password.
987   EXPECT_TRUE(parsed);
988   ASSERT_EQ(ASCIIToUTF16(" "),
989             form_with_empty_password_b.password_value);
990   ASSERT_TRUE(form_with_empty_password_b.blacklisted_by_user);
991 }
992
993 TEST_F(PasswordStoreMacInternalsTest, TestPasswordGetAll) {
994   MacKeychainPasswordFormAdapter keychain_adapter(keychain_);
995   MacKeychainPasswordFormAdapter owned_keychain_adapter(keychain_);
996   owned_keychain_adapter.SetFindsOnlyOwnedItems(true);
997
998   // Add a few passwords of various types so that we own some.
999   PasswordFormData owned_password_data[] = {
1000     { PasswordForm::SCHEME_HTML, "http://web.site.com/",
1001       "http://web.site.com/path/to/page.html", NULL, NULL, NULL, NULL,
1002       L"anonymous", L"knock-knock", false, false, 0 },
1003     { PasswordForm::SCHEME_BASIC, "http://a.site.com:2222/therealm",
1004       "http://a.site.com:2222/", NULL, NULL, NULL, NULL,
1005       L"username", L"password", false, false, 0 },
1006     { PasswordForm::SCHEME_DIGEST, "https://digest.site.com/differentrealm",
1007       "https://digest.site.com/secure.html", NULL, NULL, NULL, NULL,
1008       L"testname", L"testpass", false, false, 0 },
1009   };
1010   for (unsigned int i = 0; i < arraysize(owned_password_data); ++i) {
1011     scoped_ptr<PasswordForm> form(CreatePasswordFormFromData(
1012         owned_password_data[i]));
1013     owned_keychain_adapter.AddPassword(*form);
1014   }
1015
1016   std::vector<PasswordForm*> all_passwords =
1017       keychain_adapter.GetAllPasswordFormPasswords();
1018   EXPECT_EQ(8 + arraysize(owned_password_data), all_passwords.size());
1019   STLDeleteElements(&all_passwords);
1020
1021   std::vector<PasswordForm*> owned_passwords =
1022       owned_keychain_adapter.GetAllPasswordFormPasswords();
1023   EXPECT_EQ(arraysize(owned_password_data), owned_passwords.size());
1024   STLDeleteElements(&owned_passwords);
1025 }
1026
1027 #pragma mark -
1028
1029 class PasswordStoreMacTest : public testing::Test {
1030  public:
1031   PasswordStoreMacTest() : ui_thread_(BrowserThread::UI, &message_loop_) {}
1032
1033   virtual void SetUp() {
1034     login_db_ = new LoginDatabase();
1035     ASSERT_TRUE(db_dir_.CreateUniqueTempDir());
1036     base::FilePath db_file = db_dir_.path().AppendASCII("login.db");
1037     ASSERT_TRUE(login_db_->Init(db_file));
1038
1039     keychain_ = new MockAppleKeychain();
1040
1041     store_ = new PasswordStoreMac(
1042         base::MessageLoopProxy::current(),
1043         base::MessageLoopProxy::current(),
1044         keychain_,
1045         login_db_);
1046     ASSERT_TRUE(store_->Init());
1047   }
1048
1049   virtual void TearDown() {
1050     store_->Shutdown();
1051     base::MessageLoop::current()->PostTask(FROM_HERE,
1052                                            base::MessageLoop::QuitClosure());
1053     base::MessageLoop::current()->Run();
1054   }
1055
1056   void WaitForStoreUpdate() {
1057     // Do a store-level query to wait for all the operations above to be done.
1058     MockPasswordStoreConsumer consumer;
1059     ON_CALL(consumer, OnGetPasswordStoreResults(_))
1060         .WillByDefault(QuitUIMessageLoop());
1061     EXPECT_CALL(consumer, OnGetPasswordStoreResults(_))
1062         .WillOnce(DoAll(WithArg<0>(STLDeleteElements0()), QuitUIMessageLoop()));
1063     store_->GetLogins(PasswordForm(), PasswordStore::ALLOW_PROMPT, &consumer);
1064     base::MessageLoop::current()->Run();
1065   }
1066
1067  protected:
1068   base::MessageLoopForUI message_loop_;
1069   content::TestBrowserThread ui_thread_;
1070
1071   MockAppleKeychain* keychain_;  // Owned by store_.
1072   LoginDatabase* login_db_;  // Owned by store_.
1073   scoped_refptr<PasswordStoreMac> store_;
1074   base::ScopedTempDir db_dir_;
1075 };
1076
1077 TEST_F(PasswordStoreMacTest, TestStoreUpdate) {
1078   // Insert a password into both the database and the keychain.
1079   // This is done manually, rather than through store_->AddLogin, because the
1080   // Mock Keychain isn't smart enough to be able to support update generically,
1081   // so some.domain.com triggers special handling to test it that make inserting
1082   // fail.
1083   PasswordFormData joint_data = {
1084     PasswordForm::SCHEME_HTML, "http://some.domain.com/",
1085     "http://some.domain.com/insecure.html", "login.cgi",
1086     L"username", L"password", L"submit", L"joe_user", L"sekrit", true, false, 1
1087   };
1088   scoped_ptr<PasswordForm> joint_form(CreatePasswordFormFromData(joint_data));
1089   login_db_->AddLogin(*joint_form);
1090   MockAppleKeychain::KeychainTestData joint_keychain_data = {
1091     kSecAuthenticationTypeHTMLForm, "some.domain.com",
1092     kSecProtocolTypeHTTP, "/insecure.html", 0, NULL, "20020601171500Z",
1093     "joe_user", "sekrit", false };
1094   keychain_->AddTestItem(joint_keychain_data);
1095
1096   // Insert a password into the keychain only.
1097   MockAppleKeychain::KeychainTestData keychain_only_data = {
1098     kSecAuthenticationTypeHTMLForm, "keychain.only.com",
1099     kSecProtocolTypeHTTP, NULL, 0, NULL, "20020601171500Z",
1100     "keychain", "only", false
1101   };
1102   keychain_->AddTestItem(keychain_only_data);
1103
1104   struct UpdateData {
1105     PasswordFormData form_data;
1106     const char* password;  // NULL indicates no entry should be present.
1107   };
1108
1109   // Make a series of update calls.
1110   UpdateData updates[] = {
1111     // Update the keychain+db passwords (the normal password update case).
1112     { { PasswordForm::SCHEME_HTML, "http://some.domain.com/",
1113         "http://some.domain.com/insecure.html", "login.cgi",
1114         L"username", L"password", L"submit", L"joe_user", L"53krit",
1115         true, false, 2 },
1116       "53krit",
1117     },
1118     // Update the keychain-only password; this simulates the initial use of a
1119     // password stored by another browsers.
1120     { { PasswordForm::SCHEME_HTML, "http://keychain.only.com/",
1121         "http://keychain.only.com/login.html", "login.cgi",
1122         L"username", L"password", L"submit", L"keychain", L"only",
1123         true, false, 2 },
1124       "only",
1125     },
1126     // Update a password that doesn't exist in either location. This tests the
1127     // case where a form is filled, then the stored login is removed, then the
1128     // form is submitted.
1129     { { PasswordForm::SCHEME_HTML, "http://different.com/",
1130         "http://different.com/index.html", "login.cgi",
1131         L"username", L"password", L"submit", L"abc", L"123",
1132         true, false, 2 },
1133       NULL,
1134     },
1135   };
1136   for (unsigned int i = 0; i < ARRAYSIZE_UNSAFE(updates); ++i) {
1137     scoped_ptr<PasswordForm> form(CreatePasswordFormFromData(
1138         updates[i].form_data));
1139     store_->UpdateLogin(*form);
1140   }
1141
1142   WaitForStoreUpdate();
1143
1144   MacKeychainPasswordFormAdapter keychain_adapter(keychain_);
1145   for (unsigned int i = 0; i < ARRAYSIZE_UNSAFE(updates); ++i) {
1146     scoped_ptr<PasswordForm> query_form(
1147         CreatePasswordFormFromData(updates[i].form_data));
1148
1149     std::vector<PasswordForm*> matching_items =
1150         keychain_adapter.PasswordsFillingForm(query_form->signon_realm,
1151                                               query_form->scheme);
1152     if (updates[i].password) {
1153       EXPECT_GT(matching_items.size(), 0U) << "iteration " << i;
1154       if (matching_items.size() >= 1)
1155         EXPECT_EQ(ASCIIToUTF16(updates[i].password),
1156                   matching_items[0]->password_value) << "iteration " << i;
1157     } else {
1158       EXPECT_EQ(0U, matching_items.size()) << "iteration " << i;
1159     }
1160     STLDeleteElements(&matching_items);
1161
1162     login_db_->GetLogins(*query_form, &matching_items);
1163     EXPECT_EQ(updates[i].password ? 1U : 0U, matching_items.size())
1164         << "iteration " << i;
1165     STLDeleteElements(&matching_items);
1166   }
1167 }
1168
1169 TEST_F(PasswordStoreMacTest, TestDBKeychainAssociation) {
1170   // Tests that association between the keychain and login database parts of a
1171   // password added by fuzzy (PSL) matching works.
1172   // 1. Add a password for www.facebook.com
1173   // 2. Get a password for m.facebook.com. This fuzzy matches and returns the
1174   //    www.facebook.com password.
1175   // 3. Add the returned password for m.facebook.com.
1176   // 4. Remove both passwords.
1177   //    -> check: that both are gone from the login DB and the keychain
1178   // This test should in particular ensure that we don't keep passwords in the
1179   // keychain just before we think we still have other (fuzzy-)matching entries
1180   // for them in the login database. (For example, here if we deleted the
1181   // www.facebook.com password from the login database, we should not be blocked
1182   // from deleting it from the keystore just becaus the m.facebook.com password
1183   // fuzzy-matches the www.facebook.com one.)
1184
1185   // 1. Add a password for www.facebook.com
1186   PasswordFormData www_form_data = {
1187     PasswordForm::SCHEME_HTML, "http://www.facebook.com/",
1188     "http://www.facebook.com/index.html", "login",
1189     L"username", L"password", L"submit", L"joe_user", L"sekrit", true, false, 1
1190   };
1191   scoped_ptr<PasswordForm> www_form(CreatePasswordFormFromData(www_form_data));
1192   login_db_->AddLogin(*www_form);
1193   MacKeychainPasswordFormAdapter owned_keychain_adapter(keychain_);
1194   owned_keychain_adapter.SetFindsOnlyOwnedItems(true);
1195   owned_keychain_adapter.AddPassword(*www_form);
1196
1197   // 2. Get a password for m.facebook.com.
1198   PasswordForm m_form(*www_form);
1199   m_form.signon_realm = "http://m.facebook.com";
1200   m_form.origin = GURL("http://m.facebook.com/index.html");
1201   MockPasswordStoreConsumer consumer;
1202   ON_CALL(consumer, OnGetPasswordStoreResults(_))
1203       .WillByDefault(QuitUIMessageLoop());
1204   EXPECT_CALL(consumer, OnGetPasswordStoreResults(_)).WillOnce(DoAll(
1205       WithArg<0>(Invoke(&consumer, &MockPasswordStoreConsumer::CopyElements)),
1206       WithArg<0>(STLDeleteElements0()),
1207       QuitUIMessageLoop()));
1208   store_->GetLogins(m_form, PasswordStore::ALLOW_PROMPT, &consumer);
1209   base::MessageLoop::current()->Run();
1210   EXPECT_EQ(1u, consumer.last_result.size());
1211
1212   // 3. Add the returned password for m.facebook.com.
1213   login_db_->AddLogin(consumer.last_result[0]);
1214   owned_keychain_adapter.AddPassword(m_form);
1215
1216   // 4. Remove both passwords.
1217   store_->RemoveLogin(*www_form);
1218   store_->RemoveLogin(m_form);
1219   WaitForStoreUpdate();
1220
1221   std::vector<PasswordForm*> matching_items;
1222   // No trace of www.facebook.com.
1223   matching_items = owned_keychain_adapter.PasswordsFillingForm(
1224       www_form->signon_realm, www_form->scheme);
1225   EXPECT_EQ(0u, matching_items.size());
1226   login_db_->GetLogins(*www_form, &matching_items);
1227   EXPECT_EQ(0u, matching_items.size());
1228   // No trace of m.facebook.com.
1229   matching_items = owned_keychain_adapter.PasswordsFillingForm(
1230       m_form.signon_realm, m_form.scheme);
1231   EXPECT_EQ(0u, matching_items.size());
1232   login_db_->GetLogins(m_form, &matching_items);
1233   EXPECT_EQ(0u, matching_items.size());
1234 }