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