Upstream version 10.39.225.0
[platform/framework/web/crosswalk.git] / src / chrome / browser / password_manager / password_store_mac_unittest.cc
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
23 using autofill::PasswordForm;
24 using base::ASCIIToUTF16;
25 using base::WideToUTF16;
26 using content::BrowserThread;
27 using crypto::MockAppleKeychain;
28 using internal_keychain_helpers::FormsMatchForMerge;
29 using internal_keychain_helpers::STRICT_FORM_MATCH;
30 using password_manager::LoginDatabase;
31 using password_manager::PasswordStore;
32 using password_manager::PasswordStoreConsumer;
33 using testing::_;
34 using testing::DoAll;
35 using testing::Invoke;
36 using testing::WithArg;
37
38 namespace {
39
40 class 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
55 ACTION(STLDeleteElements0) {
56   STLDeleteContainerPointers(arg0.begin(), arg0.end());
57 }
58
59 ACTION(QuitUIMessageLoop) {
60   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
61   base::MessageLoop::current()->Quit();
62 }
63
64 class 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
89 class 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.
167 struct 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.
184 static 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.
226 static 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
279 TEST_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
377 TEST_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|.
469 static 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|.
477 static 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|.
486 static 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
494 TEST_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
551 TEST_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
618 TEST_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
654 TEST_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
716 TEST_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
871 TEST_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
914 TEST_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
941 TEST_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
1015 TEST_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
1051 class 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
1099 TEST_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
1191 TEST_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
1256 namespace {
1257
1258 class PasswordsChangeObserver :
1259     public password_manager::PasswordStore::Observer {
1260 public:
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
1274 private:
1275   ScopedObserver<password_manager::PasswordStore,
1276                 PasswordsChangeObserver> observer_;
1277 };
1278
1279 password_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|.
1288 void 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
1382 TEST_F(PasswordStoreMacTest, TestRemoveLoginsCreatedBetween) {
1383   CheckRemoveLoginsBetween(this, true);
1384 }
1385
1386 TEST_F(PasswordStoreMacTest, TestRemoveLoginsSyncedBetween) {
1387   CheckRemoveLoginsBetween(this, false);
1388 }
1389
1390 TEST_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 }