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