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
22using webkit_glue::PasswordForm;
23using testing::_;
24using testing::DoAll;
25using testing::WithArg;
26
27namespace {
28
29class MockPasswordStoreConsumer : public PasswordStoreConsumer {
30public:
31  MOCK_METHOD2(OnPasswordStoreRequestDone,
32               void(CancelableRequestProvider::Handle,
33                    const std::vector<webkit_glue::PasswordForm*>&));
34};
35
36ACTION(STLDeleteElements0) {
37  STLDeleteContainerPointers(arg0.begin(), arg0.end());
38}
39
40ACTION(QuitUIMessageLoop) {
41  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
42  MessageLoop::current()->Quit();
43}
44
45}  // namespace
46
47#pragma mark -
48
49class 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.
129struct 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.
146static 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.
183static 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
228TEST_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
326TEST_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|.
403static 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|.
411static 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|.
420static 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
428TEST_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
493TEST_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
564TEST_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
603TEST_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
665TEST_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
820TEST_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
863TEST_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
899class 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
931TEST_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