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