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