ozone: evdev: Sync caps lock LED state to evdev
[chromium-blink-merge.git] / chrome / browser / password_manager / password_store_mac_unittest.cc
blob71ac708072e6375fd6ebb07d1a996d10da87e112
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.
5 #include "chrome/browser/password_manager/password_store_mac.h"
7 #include "base/basictypes.h"
8 #include "base/files/scoped_temp_dir.h"
9 #include "base/scoped_observer.h"
10 #include "base/stl_util.h"
11 #include "base/strings/string_util.h"
12 #include "base/strings/utf_string_conversions.h"
13 #include "base/synchronization/waitable_event.h"
14 #include "chrome/browser/password_manager/password_store_mac_internal.h"
15 #include "chrome/common/chrome_paths.h"
16 #include "components/password_manager/core/browser/login_database.h"
17 #include "components/password_manager/core/browser/password_manager_test_utils.h"
18 #include "components/password_manager/core/browser/password_store_consumer.h"
19 #include "content/public/test/test_browser_thread.h"
20 #include "crypto/mock_apple_keychain.h"
21 #include "testing/gmock/include/gmock/gmock.h"
22 #include "testing/gtest/include/gtest/gtest.h"
24 using autofill::PasswordForm;
25 using base::ASCIIToUTF16;
26 using base::WideToUTF16;
27 using content::BrowserThread;
28 using crypto::MockAppleKeychain;
29 using internal_keychain_helpers::FormsMatchForMerge;
30 using internal_keychain_helpers::STRICT_FORM_MATCH;
31 using password_manager::CreatePasswordFormFromDataForTesting;
32 using password_manager::LoginDatabase;
33 using password_manager::PasswordFormData;
34 using password_manager::PasswordStore;
35 using password_manager::PasswordStoreChange;
36 using password_manager::PasswordStoreChangeList;
37 using password_manager::PasswordStoreConsumer;
38 using testing::_;
39 using testing::DoAll;
40 using testing::Invoke;
41 using testing::IsEmpty;
42 using testing::SizeIs;
43 using testing::WithArg;
45 namespace {
47 ACTION(QuitUIMessageLoop) {
48 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
49 base::MessageLoop::current()->Quit();
52 // From the mock's argument #0 of type const std::vector<PasswordForm*>& takes
53 // the first form and copies it to the form pointed to by |target_form_ptr|.
54 ACTION_P(SaveACopyOfFirstForm, target_form_ptr) {
55 ASSERT_FALSE(arg0.empty());
56 *target_form_ptr = *arg0[0];
59 class MockPasswordStoreConsumer : public PasswordStoreConsumer {
60 public:
61 MOCK_METHOD1(OnGetPasswordStoreResultsConstRef,
62 void(const std::vector<PasswordForm*>&));
64 // GMock cannot mock methods with move-only args.
65 void OnGetPasswordStoreResults(ScopedVector<PasswordForm> results) override {
66 OnGetPasswordStoreResultsConstRef(results.get());
70 class MockPasswordStoreObserver : public PasswordStore::Observer {
71 public:
72 MOCK_METHOD1(OnLoginsChanged,
73 void(const password_manager::PasswordStoreChangeList& changes));
76 // A mock LoginDatabase that simulates a failing Init() method.
77 class BadLoginDatabase : public password_manager::LoginDatabase {
78 public:
79 BadLoginDatabase() : password_manager::LoginDatabase(base::FilePath()) {}
80 ~BadLoginDatabase() override {}
82 // LoginDatabase:
83 bool Init() override { return false; }
85 private:
86 DISALLOW_COPY_AND_ASSIGN(BadLoginDatabase);
89 // A LoginDatabase that simulates an Init() method that takes a long time.
90 class SlowToInitLoginDatabase : public password_manager::LoginDatabase {
91 public:
92 // Creates an instance whose Init() method will block until |event| is
93 // signaled. |event| must outlive |this|.
94 SlowToInitLoginDatabase(const base::FilePath& db_path,
95 base::WaitableEvent* event)
96 : password_manager::LoginDatabase(db_path), event_(event) {}
97 ~SlowToInitLoginDatabase() override {}
99 // LoginDatabase:
100 bool Init() override {
101 event_->Wait();
102 return password_manager::LoginDatabase::Init();
105 private:
106 base::WaitableEvent* event_;
108 DISALLOW_COPY_AND_ASSIGN(SlowToInitLoginDatabase);
111 class TestPasswordStoreMac : public PasswordStoreMac {
112 public:
113 TestPasswordStoreMac(
114 scoped_refptr<base::SingleThreadTaskRunner> main_thread_runner,
115 scoped_refptr<base::SingleThreadTaskRunner> db_thread_runner,
116 scoped_ptr<crypto::AppleKeychain> keychain,
117 scoped_ptr<password_manager::LoginDatabase> login_db)
118 : PasswordStoreMac(main_thread_runner,
119 db_thread_runner,
120 keychain.Pass(),
121 login_db.Pass()) {}
123 using PasswordStoreMac::GetBackgroundTaskRunner;
125 private:
126 ~TestPasswordStoreMac() override {}
128 DISALLOW_COPY_AND_ASSIGN(TestPasswordStoreMac);
131 #pragma mark -
133 // Macro to simplify calling CheckFormsAgainstExpectations with a useful label.
134 #define CHECK_FORMS(forms, expectations, i) \
135 CheckFormsAgainstExpectations(forms, expectations, #forms, i)
137 // Ensures that the data in |forms| match |expectations|, causing test failures
138 // for any discrepencies.
139 // TODO(stuartmorgan): This is current order-dependent; ideally it shouldn't
140 // matter if |forms| and |expectations| are scrambled.
141 void CheckFormsAgainstExpectations(
142 const std::vector<PasswordForm*>& forms,
143 const std::vector<PasswordFormData*>& expectations,
145 const char* forms_label, unsigned int test_number) {
146 EXPECT_EQ(expectations.size(), forms.size()) << forms_label << " in test "
147 << test_number;
148 if (expectations.size() != forms.size())
149 return;
151 for (unsigned int i = 0; i < expectations.size(); ++i) {
152 SCOPED_TRACE(testing::Message() << forms_label << " in test " << test_number
153 << ", item " << i);
154 PasswordForm* form = forms[i];
155 PasswordFormData* expectation = expectations[i];
156 EXPECT_EQ(expectation->scheme, form->scheme);
157 EXPECT_EQ(std::string(expectation->signon_realm), form->signon_realm);
158 EXPECT_EQ(GURL(expectation->origin), form->origin);
159 EXPECT_EQ(GURL(expectation->action), form->action);
160 EXPECT_EQ(WideToUTF16(expectation->submit_element), form->submit_element);
161 EXPECT_EQ(WideToUTF16(expectation->username_element),
162 form->username_element);
163 EXPECT_EQ(WideToUTF16(expectation->password_element),
164 form->password_element);
165 if (expectation->username_value) {
166 EXPECT_EQ(WideToUTF16(expectation->username_value), form->username_value);
167 EXPECT_EQ(WideToUTF16(expectation->username_value), form->display_name);
168 EXPECT_TRUE(form->skip_zero_click);
169 EXPECT_EQ(WideToUTF16(expectation->password_value), form->password_value);
170 } else {
171 EXPECT_TRUE(form->blacklisted_by_user);
173 EXPECT_EQ(expectation->preferred, form->preferred);
174 EXPECT_EQ(expectation->ssl_valid, form->ssl_valid);
175 EXPECT_DOUBLE_EQ(expectation->creation_time,
176 form->date_created.ToDoubleT());
177 base::Time created = base::Time::FromDoubleT(expectation->creation_time);
178 EXPECT_EQ(
179 created + base::TimeDelta::FromDays(
180 password_manager::kTestingDaysAfterPasswordsAreSynced),
181 form->date_synced);
182 EXPECT_EQ(GURL(password_manager::kTestingAvatarUrlSpec), form->avatar_url);
183 EXPECT_EQ(GURL(password_manager::kTestingFederationUrlSpec),
184 form->federation_url);
188 PasswordStoreChangeList AddChangeForForm(const PasswordForm& form) {
189 return PasswordStoreChangeList(
190 1, PasswordStoreChange(PasswordStoreChange::ADD, form));
193 } // namespace
195 #pragma mark -
197 class PasswordStoreMacInternalsTest : public testing::Test {
198 public:
199 void SetUp() override {
200 MockAppleKeychain::KeychainTestData test_data[] = {
201 // Basic HTML form.
202 {kSecAuthenticationTypeHTMLForm,
203 "some.domain.com",
204 kSecProtocolTypeHTTP,
205 NULL,
207 NULL,
208 "20020601171500Z",
209 "joe_user",
210 "sekrit",
211 false},
212 // HTML form with path.
213 {kSecAuthenticationTypeHTMLForm,
214 "some.domain.com",
215 kSecProtocolTypeHTTP,
216 "/insecure.html",
218 NULL,
219 "19991231235959Z",
220 "joe_user",
221 "sekrit",
222 false},
223 // Secure HTML form with path.
224 {kSecAuthenticationTypeHTMLForm,
225 "some.domain.com",
226 kSecProtocolTypeHTTPS,
227 "/secure.html",
229 NULL,
230 "20100908070605Z",
231 "secure_user",
232 "password",
233 false},
234 // True negative item.
235 {kSecAuthenticationTypeHTMLForm,
236 "dont.remember.com",
237 kSecProtocolTypeHTTP,
238 NULL,
240 NULL,
241 "20000101000000Z",
244 true},
245 // De-facto negative item, type one.
246 {kSecAuthenticationTypeHTMLForm,
247 "dont.remember.com",
248 kSecProtocolTypeHTTP,
249 NULL,
251 NULL,
252 "20000101000000Z",
253 "Password Not Stored",
255 false},
256 // De-facto negative item, type two.
257 {kSecAuthenticationTypeHTMLForm,
258 "dont.remember.com",
259 kSecProtocolTypeHTTPS,
260 NULL,
262 NULL,
263 "20000101000000Z",
264 "Password Not Stored",
265 " ",
266 false},
267 // HTTP auth basic, with port and path.
268 {kSecAuthenticationTypeHTTPBasic,
269 "some.domain.com",
270 kSecProtocolTypeHTTP,
271 "/insecure.html",
272 4567,
273 "low_security",
274 "19980330100000Z",
275 "basic_auth_user",
276 "basic",
277 false},
278 // HTTP auth digest, secure.
279 {kSecAuthenticationTypeHTTPDigest,
280 "some.domain.com",
281 kSecProtocolTypeHTTPS,
282 NULL,
284 "high_security",
285 "19980330100000Z",
286 "digest_auth_user",
287 "digest",
288 false},
289 // An FTP password with an invalid date, for edge-case testing.
290 {kSecAuthenticationTypeDefault,
291 "a.server.com",
292 kSecProtocolTypeFTP,
293 NULL,
295 NULL,
296 "20010203040",
297 "abc",
298 "123",
299 false},
302 keychain_ = new MockAppleKeychain();
304 for (unsigned int i = 0; i < arraysize(test_data); ++i) {
305 keychain_->AddTestItem(test_data[i]);
309 void TearDown() override {
310 ExpectCreatesAndFreesBalanced();
311 ExpectCreatorCodesSet();
312 delete keychain_;
315 protected:
316 // Causes a test failure unless everything returned from keychain_'s
317 // ItemCopyAttributesAndData, SearchCreateFromAttributes, and SearchCopyNext
318 // was correctly freed.
319 void ExpectCreatesAndFreesBalanced() {
320 EXPECT_EQ(0, keychain_->UnfreedSearchCount());
321 EXPECT_EQ(0, keychain_->UnfreedKeychainItemCount());
322 EXPECT_EQ(0, keychain_->UnfreedAttributeDataCount());
325 // Causes a test failure unless any Keychain items added during the test have
326 // their creator code set.
327 void ExpectCreatorCodesSet() {
328 EXPECT_TRUE(keychain_->CreatorCodesSetForAddedItems());
331 MockAppleKeychain* keychain_;
334 #pragma mark -
336 TEST_F(PasswordStoreMacInternalsTest, TestKeychainToFormTranslation) {
337 typedef struct {
338 const PasswordForm::Scheme scheme;
339 const char* signon_realm;
340 const char* origin;
341 const wchar_t* username; // Set to NULL to check for a blacklist entry.
342 const wchar_t* password;
343 const bool ssl_valid;
344 const int creation_year;
345 const int creation_month;
346 const int creation_day;
347 const int creation_hour;
348 const int creation_minute;
349 const int creation_second;
350 } TestExpectations;
352 TestExpectations expected[] = {
353 { PasswordForm::SCHEME_HTML, "http://some.domain.com/",
354 "http://some.domain.com/", L"joe_user", L"sekrit", false,
355 2002, 6, 1, 17, 15, 0 },
356 { PasswordForm::SCHEME_HTML, "http://some.domain.com/",
357 "http://some.domain.com/insecure.html", L"joe_user", L"sekrit", false,
358 1999, 12, 31, 23, 59, 59 },
359 { PasswordForm::SCHEME_HTML, "https://some.domain.com/",
360 "https://some.domain.com/secure.html", L"secure_user", L"password", true,
361 2010, 9, 8, 7, 6, 5 },
362 { PasswordForm::SCHEME_HTML, "http://dont.remember.com/",
363 "http://dont.remember.com/", NULL, NULL, false,
364 2000, 1, 1, 0, 0, 0 },
365 { PasswordForm::SCHEME_HTML, "http://dont.remember.com/",
366 "http://dont.remember.com/", NULL, NULL, false,
367 2000, 1, 1, 0, 0, 0 },
368 { PasswordForm::SCHEME_HTML, "https://dont.remember.com/",
369 "https://dont.remember.com/", NULL, NULL, true,
370 2000, 1, 1, 0, 0, 0 },
371 { PasswordForm::SCHEME_BASIC, "http://some.domain.com:4567/low_security",
372 "http://some.domain.com:4567/insecure.html", L"basic_auth_user", L"basic",
373 false, 1998, 03, 30, 10, 00, 00 },
374 { PasswordForm::SCHEME_DIGEST, "https://some.domain.com/high_security",
375 "https://some.domain.com/", L"digest_auth_user", L"digest", true,
376 1998, 3, 30, 10, 0, 0 },
377 // This one gives us an invalid date, which we will treat as a "NULL" date
378 // which is 1601.
379 { PasswordForm::SCHEME_OTHER, "http://a.server.com/",
380 "http://a.server.com/", L"abc", L"123", false,
381 1601, 1, 1, 0, 0, 0 },
384 for (unsigned int i = 0; i < arraysize(expected); ++i) {
385 // Create our fake KeychainItemRef; see MockAppleKeychain docs.
386 SecKeychainItemRef keychain_item =
387 reinterpret_cast<SecKeychainItemRef>(i + 1);
388 PasswordForm form;
389 bool parsed = internal_keychain_helpers::FillPasswordFormFromKeychainItem(
390 *keychain_, keychain_item, &form, true);
392 EXPECT_TRUE(parsed) << "In iteration " << i;
394 EXPECT_EQ(expected[i].scheme, form.scheme) << "In iteration " << i;
395 EXPECT_EQ(GURL(expected[i].origin), form.origin) << "In iteration " << i;
396 EXPECT_EQ(expected[i].ssl_valid, form.ssl_valid) << "In iteration " << i;
397 EXPECT_EQ(std::string(expected[i].signon_realm), form.signon_realm)
398 << "In iteration " << i;
399 if (expected[i].username) {
400 EXPECT_EQ(WideToUTF16(expected[i].username), form.username_value)
401 << "In iteration " << i;
402 EXPECT_EQ(WideToUTF16(expected[i].password), form.password_value)
403 << "In iteration " << i;
404 EXPECT_FALSE(form.blacklisted_by_user) << "In iteration " << i;
405 } else {
406 EXPECT_TRUE(form.blacklisted_by_user) << "In iteration " << i;
408 base::Time::Exploded exploded_time;
409 form.date_created.UTCExplode(&exploded_time);
410 EXPECT_EQ(expected[i].creation_year, exploded_time.year)
411 << "In iteration " << i;
412 EXPECT_EQ(expected[i].creation_month, exploded_time.month)
413 << "In iteration " << i;
414 EXPECT_EQ(expected[i].creation_day, exploded_time.day_of_month)
415 << "In iteration " << i;
416 EXPECT_EQ(expected[i].creation_hour, exploded_time.hour)
417 << "In iteration " << i;
418 EXPECT_EQ(expected[i].creation_minute, exploded_time.minute)
419 << "In iteration " << i;
420 EXPECT_EQ(expected[i].creation_second, exploded_time.second)
421 << "In iteration " << i;
425 // Use an invalid ref, to make sure errors are reported.
426 SecKeychainItemRef keychain_item = reinterpret_cast<SecKeychainItemRef>(99);
427 PasswordForm form;
428 bool parsed = internal_keychain_helpers::FillPasswordFormFromKeychainItem(
429 *keychain_, keychain_item, &form, true);
430 EXPECT_FALSE(parsed);
434 TEST_F(PasswordStoreMacInternalsTest, TestKeychainSearch) {
435 struct TestDataAndExpectation {
436 const PasswordFormData data;
437 const size_t expected_fill_matches;
438 const size_t expected_merge_matches;
440 // Most fields are left blank because we don't care about them for searching.
441 TestDataAndExpectation test_data[] = {
442 // An HTML form we've seen.
443 { { PasswordForm::SCHEME_HTML, "http://some.domain.com/",
444 NULL, NULL, NULL, NULL, NULL, L"joe_user", NULL, false, false, 0 },
445 2, 2 },
446 { { PasswordForm::SCHEME_HTML, "http://some.domain.com/",
447 NULL, NULL, NULL, NULL, NULL, L"wrong_user", NULL, false, false, 0 },
448 2, 0 },
449 // An HTML form we haven't seen
450 { { PasswordForm::SCHEME_HTML, "http://www.unseendomain.com/",
451 NULL, NULL, NULL, NULL, NULL, L"joe_user", NULL, false, false, 0 },
452 0, 0 },
453 // Basic auth that should match.
454 { { PasswordForm::SCHEME_BASIC, "http://some.domain.com:4567/low_security",
455 NULL, NULL, NULL, NULL, NULL, L"basic_auth_user", NULL, false, false,
456 0 },
457 1, 1 },
458 // Basic auth with the wrong port.
459 { { PasswordForm::SCHEME_BASIC, "http://some.domain.com:1111/low_security",
460 NULL, NULL, NULL, NULL, NULL, L"basic_auth_user", NULL, false, false,
461 0 },
462 0, 0 },
463 // Digest auth we've saved under https, visited with http.
464 { { PasswordForm::SCHEME_DIGEST, "http://some.domain.com/high_security",
465 NULL, NULL, NULL, NULL, NULL, L"digest_auth_user", NULL, false, false,
466 0 },
467 0, 0 },
468 // Digest auth that should match.
469 { { PasswordForm::SCHEME_DIGEST, "https://some.domain.com/high_security",
470 NULL, NULL, NULL, NULL, NULL, L"wrong_user", NULL, false, true, 0 },
471 1, 0 },
472 // Digest auth with the wrong domain.
473 { { PasswordForm::SCHEME_DIGEST, "https://some.domain.com/other_domain",
474 NULL, NULL, NULL, NULL, NULL, L"digest_auth_user", NULL, false, true,
475 0 },
476 0, 0 },
477 // Garbage forms should have no matches.
478 { { PasswordForm::SCHEME_HTML, "foo/bar/baz",
479 NULL, NULL, NULL, NULL, NULL, NULL, NULL, false, false, 0 }, 0, 0 },
482 MacKeychainPasswordFormAdapter keychain_adapter(keychain_);
483 MacKeychainPasswordFormAdapter owned_keychain_adapter(keychain_);
484 owned_keychain_adapter.SetFindsOnlyOwnedItems(true);
485 for (unsigned int i = 0; i < arraysize(test_data); ++i) {
486 scoped_ptr<PasswordForm> query_form =
487 CreatePasswordFormFromDataForTesting(test_data[i].data);
489 // Check matches treating the form as a fill target.
490 ScopedVector<autofill::PasswordForm> matching_items =
491 keychain_adapter.PasswordsFillingForm(query_form->signon_realm,
492 query_form->scheme);
493 EXPECT_EQ(test_data[i].expected_fill_matches, matching_items.size());
494 matching_items.clear();
496 // Check matches treating the form as a merging target.
497 EXPECT_EQ(test_data[i].expected_merge_matches > 0,
498 keychain_adapter.HasPasswordsMergeableWithForm(*query_form));
499 std::vector<SecKeychainItemRef> keychain_items;
500 std::vector<internal_keychain_helpers::ItemFormPair> item_form_pairs =
501 internal_keychain_helpers::
502 ExtractAllKeychainItemAttributesIntoPasswordForms(&keychain_items,
503 *keychain_);
504 matching_items =
505 internal_keychain_helpers::ExtractPasswordsMergeableWithForm(
506 *keychain_, item_form_pairs, *query_form);
507 EXPECT_EQ(test_data[i].expected_merge_matches, matching_items.size());
508 STLDeleteContainerPairSecondPointers(item_form_pairs.begin(),
509 item_form_pairs.end());
510 for (std::vector<SecKeychainItemRef>::iterator i = keychain_items.begin();
511 i != keychain_items.end(); ++i) {
512 keychain_->Free(*i);
515 // None of the pre-seeded items are owned by us, so none should match an
516 // owned-passwords-only search.
517 matching_items = owned_keychain_adapter.PasswordsFillingForm(
518 query_form->signon_realm, query_form->scheme);
519 EXPECT_EQ(0U, matching_items.size());
523 // Changes just the origin path of |form|.
524 static void SetPasswordFormPath(PasswordForm* form, const char* path) {
525 GURL::Replacements replacement;
526 std::string new_value(path);
527 replacement.SetPathStr(new_value);
528 form->origin = form->origin.ReplaceComponents(replacement);
531 // Changes just the signon_realm port of |form|.
532 static void SetPasswordFormPort(PasswordForm* form, const char* port) {
533 GURL::Replacements replacement;
534 std::string new_value(port);
535 replacement.SetPortStr(new_value);
536 GURL signon_gurl = GURL(form->signon_realm);
537 form->signon_realm = signon_gurl.ReplaceComponents(replacement).spec();
540 // Changes just the signon_ream auth realm of |form|.
541 static void SetPasswordFormRealm(PasswordForm* form, const char* realm) {
542 GURL::Replacements replacement;
543 std::string new_value(realm);
544 replacement.SetPathStr(new_value);
545 GURL signon_gurl = GURL(form->signon_realm);
546 form->signon_realm = signon_gurl.ReplaceComponents(replacement).spec();
549 TEST_F(PasswordStoreMacInternalsTest, TestKeychainExactSearch) {
550 MacKeychainPasswordFormAdapter keychain_adapter(keychain_);
552 PasswordFormData base_form_data[] = {
553 { PasswordForm::SCHEME_HTML, "http://some.domain.com/",
554 "http://some.domain.com/insecure.html",
555 NULL, NULL, NULL, NULL, L"joe_user", NULL, true, false, 0 },
556 { PasswordForm::SCHEME_BASIC, "http://some.domain.com:4567/low_security",
557 "http://some.domain.com:4567/insecure.html",
558 NULL, NULL, NULL, NULL, L"basic_auth_user", NULL, true, false, 0 },
559 { PasswordForm::SCHEME_DIGEST, "https://some.domain.com/high_security",
560 "https://some.domain.com",
561 NULL, NULL, NULL, NULL, L"digest_auth_user", NULL, true, true, 0 },
564 for (unsigned int i = 0; i < arraysize(base_form_data); ++i) {
565 // Create a base form and make sure we find a match.
566 scoped_ptr<PasswordForm> base_form =
567 CreatePasswordFormFromDataForTesting(base_form_data[i]);
568 EXPECT_TRUE(keychain_adapter.HasPasswordsMergeableWithForm(*base_form));
569 EXPECT_TRUE(keychain_adapter.HasPasswordExactlyMatchingForm(*base_form));
571 // Make sure that the matching isn't looser than it should be by checking
572 // that slightly altered forms don't match.
573 ScopedVector<autofill::PasswordForm> modified_forms;
575 modified_forms.push_back(new PasswordForm(*base_form));
576 modified_forms.back()->username_value = ASCIIToUTF16("wrong_user");
578 modified_forms.push_back(new PasswordForm(*base_form));
579 SetPasswordFormPath(modified_forms.back(), "elsewhere.html");
581 modified_forms.push_back(new PasswordForm(*base_form));
582 modified_forms.back()->scheme = PasswordForm::SCHEME_OTHER;
584 modified_forms.push_back(new PasswordForm(*base_form));
585 SetPasswordFormPort(modified_forms.back(), "1234");
587 modified_forms.push_back(new PasswordForm(*base_form));
588 modified_forms.back()->blacklisted_by_user = true;
590 if (base_form->scheme == PasswordForm::SCHEME_BASIC ||
591 base_form->scheme == PasswordForm::SCHEME_DIGEST) {
592 modified_forms.push_back(new PasswordForm(*base_form));
593 SetPasswordFormRealm(modified_forms.back(), "incorrect");
596 for (unsigned int j = 0; j < modified_forms.size(); ++j) {
597 bool match = keychain_adapter.HasPasswordExactlyMatchingForm(
598 *modified_forms[j]);
599 EXPECT_FALSE(match) << "In modified version " << j
600 << " of base form " << i;
605 TEST_F(PasswordStoreMacInternalsTest, TestKeychainAdd) {
606 struct TestDataAndExpectation {
607 PasswordFormData data;
608 bool should_succeed;
610 TestDataAndExpectation test_data[] = {
611 // Test a variety of scheme/port/protocol/path variations.
612 { { PasswordForm::SCHEME_HTML, "http://web.site.com/",
613 "http://web.site.com/path/to/page.html", NULL, NULL, NULL, NULL,
614 L"anonymous", L"knock-knock", false, false, 0 }, true },
615 { { PasswordForm::SCHEME_HTML, "https://web.site.com/",
616 "https://web.site.com/", NULL, NULL, NULL, NULL,
617 L"admin", L"p4ssw0rd", false, false, 0 }, true },
618 { { PasswordForm::SCHEME_BASIC, "http://a.site.com:2222/therealm",
619 "http://a.site.com:2222/", NULL, NULL, NULL, NULL,
620 L"username", L"password", false, false, 0 }, true },
621 { { PasswordForm::SCHEME_DIGEST, "https://digest.site.com/differentrealm",
622 "https://digest.site.com/secure.html", NULL, NULL, NULL, NULL,
623 L"testname", L"testpass", false, false, 0 }, true },
624 // Make sure that garbage forms are rejected.
625 { { PasswordForm::SCHEME_HTML, "gobbledygook",
626 "gobbledygook", NULL, NULL, NULL, NULL,
627 L"anonymous", L"knock-knock", false, false, 0 }, false },
628 // Test that failing to update a duplicate (forced using the magic failure
629 // password; see MockAppleKeychain::ItemModifyAttributesAndData) is
630 // reported.
631 { { PasswordForm::SCHEME_HTML, "http://some.domain.com",
632 "http://some.domain.com/insecure.html", NULL, NULL, NULL, NULL,
633 L"joe_user", L"fail_me", false, false, 0 }, false },
636 MacKeychainPasswordFormAdapter owned_keychain_adapter(keychain_);
637 owned_keychain_adapter.SetFindsOnlyOwnedItems(true);
639 for (unsigned int i = 0; i < arraysize(test_data); ++i) {
640 scoped_ptr<PasswordForm> in_form =
641 CreatePasswordFormFromDataForTesting(test_data[i].data);
642 bool add_succeeded = owned_keychain_adapter.AddPassword(*in_form);
643 EXPECT_EQ(test_data[i].should_succeed, add_succeeded);
644 if (add_succeeded) {
645 EXPECT_TRUE(owned_keychain_adapter.HasPasswordsMergeableWithForm(
646 *in_form));
647 EXPECT_TRUE(owned_keychain_adapter.HasPasswordExactlyMatchingForm(
648 *in_form));
652 // Test that adding duplicate item updates the existing item.
654 PasswordFormData data = {
655 PasswordForm::SCHEME_HTML, "http://some.domain.com",
656 "http://some.domain.com/insecure.html", NULL,
657 NULL, NULL, NULL, L"joe_user", L"updated_password", false, false, 0
659 scoped_ptr<PasswordForm> update_form =
660 CreatePasswordFormFromDataForTesting(data);
661 MacKeychainPasswordFormAdapter keychain_adapter(keychain_);
662 EXPECT_TRUE(keychain_adapter.AddPassword(*update_form));
663 SecKeychainItemRef keychain_item = reinterpret_cast<SecKeychainItemRef>(2);
664 PasswordForm stored_form;
665 internal_keychain_helpers::FillPasswordFormFromKeychainItem(*keychain_,
666 keychain_item,
667 &stored_form,
668 true);
669 EXPECT_EQ(update_form->password_value, stored_form.password_value);
673 TEST_F(PasswordStoreMacInternalsTest, TestKeychainRemove) {
674 struct TestDataAndExpectation {
675 PasswordFormData data;
676 bool should_succeed;
678 TestDataAndExpectation test_data[] = {
679 // Test deletion of an item that we add.
680 { { PasswordForm::SCHEME_HTML, "http://web.site.com/",
681 "http://web.site.com/path/to/page.html", NULL, NULL, NULL, NULL,
682 L"anonymous", L"knock-knock", false, false, 0 }, true },
683 // Make sure we don't delete items we don't own.
684 { { PasswordForm::SCHEME_HTML, "http://some.domain.com/",
685 "http://some.domain.com/insecure.html", NULL, NULL, NULL, NULL,
686 L"joe_user", NULL, true, false, 0 }, false },
689 MacKeychainPasswordFormAdapter owned_keychain_adapter(keychain_);
690 owned_keychain_adapter.SetFindsOnlyOwnedItems(true);
692 // Add our test item so that we can delete it.
693 scoped_ptr<PasswordForm> add_form =
694 CreatePasswordFormFromDataForTesting(test_data[0].data);
695 EXPECT_TRUE(owned_keychain_adapter.AddPassword(*add_form));
697 for (unsigned int i = 0; i < arraysize(test_data); ++i) {
698 scoped_ptr<PasswordForm> form =
699 CreatePasswordFormFromDataForTesting(test_data[i].data);
700 EXPECT_EQ(test_data[i].should_succeed,
701 owned_keychain_adapter.RemovePassword(*form));
703 MacKeychainPasswordFormAdapter keychain_adapter(keychain_);
704 bool match = keychain_adapter.HasPasswordExactlyMatchingForm(*form);
705 EXPECT_EQ(test_data[i].should_succeed, !match);
709 TEST_F(PasswordStoreMacInternalsTest, TestFormMatch) {
710 PasswordForm base_form;
711 base_form.signon_realm = std::string("http://some.domain.com/");
712 base_form.origin = GURL("http://some.domain.com/page.html");
713 base_form.username_value = ASCIIToUTF16("joe_user");
716 // Check that everything unimportant can be changed.
717 PasswordForm different_form(base_form);
718 different_form.username_element = ASCIIToUTF16("username");
719 different_form.submit_element = ASCIIToUTF16("submit");
720 different_form.username_element = ASCIIToUTF16("password");
721 different_form.password_value = ASCIIToUTF16("sekrit");
722 different_form.action = GURL("http://some.domain.com/action.cgi");
723 different_form.ssl_valid = true;
724 different_form.preferred = true;
725 different_form.date_created = base::Time::Now();
726 EXPECT_TRUE(
727 FormsMatchForMerge(base_form, different_form, STRICT_FORM_MATCH));
729 // Check that path differences don't prevent a match.
730 base_form.origin = GURL("http://some.domain.com/other_page.html");
731 EXPECT_TRUE(
732 FormsMatchForMerge(base_form, different_form, STRICT_FORM_MATCH));
735 // Check that any one primary key changing is enough to prevent matching.
737 PasswordForm different_form(base_form);
738 different_form.scheme = PasswordForm::SCHEME_DIGEST;
739 EXPECT_FALSE(
740 FormsMatchForMerge(base_form, different_form, STRICT_FORM_MATCH));
743 PasswordForm different_form(base_form);
744 different_form.signon_realm = std::string("http://some.domain.com:8080/");
745 EXPECT_FALSE(
746 FormsMatchForMerge(base_form, different_form, STRICT_FORM_MATCH));
749 PasswordForm different_form(base_form);
750 different_form.username_value = ASCIIToUTF16("john.doe");
751 EXPECT_FALSE(
752 FormsMatchForMerge(base_form, different_form, STRICT_FORM_MATCH));
755 PasswordForm different_form(base_form);
756 different_form.blacklisted_by_user = true;
757 EXPECT_FALSE(
758 FormsMatchForMerge(base_form, different_form, STRICT_FORM_MATCH));
761 // Blacklist forms should *never* match for merging, even when identical
762 // (and certainly not when only one is a blacklist entry).
764 PasswordForm form_a(base_form);
765 form_a.blacklisted_by_user = true;
766 PasswordForm form_b(form_a);
767 EXPECT_FALSE(FormsMatchForMerge(form_a, form_b, STRICT_FORM_MATCH));
771 TEST_F(PasswordStoreMacInternalsTest, TestFormMerge) {
772 // Set up a bunch of test data to use in varying combinations.
773 PasswordFormData keychain_user_1 =
774 { PasswordForm::SCHEME_HTML, "http://some.domain.com/",
775 "http://some.domain.com/", "", L"", L"", L"", L"joe_user", L"sekrit",
776 false, false, 1010101010 };
777 PasswordFormData keychain_user_1_with_path =
778 { PasswordForm::SCHEME_HTML, "http://some.domain.com/",
779 "http://some.domain.com/page.html",
780 "", L"", L"", L"", L"joe_user", L"otherpassword",
781 false, false, 1010101010 };
782 PasswordFormData keychain_user_2 =
783 { PasswordForm::SCHEME_HTML, "http://some.domain.com/",
784 "http://some.domain.com/", "", L"", L"", L"", L"john.doe", L"sesame",
785 false, false, 958739876 };
786 PasswordFormData keychain_blacklist =
787 { PasswordForm::SCHEME_HTML, "http://some.domain.com/",
788 "http://some.domain.com/", "", L"", L"", L"", NULL, NULL,
789 false, false, 1010101010 };
791 PasswordFormData db_user_1 =
792 { PasswordForm::SCHEME_HTML, "http://some.domain.com/",
793 "http://some.domain.com/", "http://some.domain.com/action.cgi",
794 L"submit", L"username", L"password", L"joe_user", L"",
795 true, false, 1212121212 };
796 PasswordFormData db_user_1_with_path =
797 { PasswordForm::SCHEME_HTML, "http://some.domain.com/",
798 "http://some.domain.com/page.html",
799 "http://some.domain.com/handlepage.cgi",
800 L"submit", L"username", L"password", L"joe_user", L"",
801 true, false, 1234567890 };
802 PasswordFormData db_user_3_with_path =
803 { PasswordForm::SCHEME_HTML, "http://some.domain.com/",
804 "http://some.domain.com/page.html",
805 "http://some.domain.com/handlepage.cgi",
806 L"submit", L"username", L"password", L"second-account", L"",
807 true, false, 1240000000 };
808 PasswordFormData database_blacklist_with_path =
809 { PasswordForm::SCHEME_HTML, "http://some.domain.com/",
810 "http://some.domain.com/path.html", "http://some.domain.com/action.cgi",
811 L"submit", L"username", L"password", NULL, NULL,
812 true, false, 1212121212 };
814 PasswordFormData merged_user_1 =
815 { PasswordForm::SCHEME_HTML, "http://some.domain.com/",
816 "http://some.domain.com/", "http://some.domain.com/action.cgi",
817 L"submit", L"username", L"password", L"joe_user", L"sekrit",
818 true, false, 1212121212 };
819 PasswordFormData merged_user_1_with_db_path =
820 { PasswordForm::SCHEME_HTML, "http://some.domain.com/",
821 "http://some.domain.com/page.html",
822 "http://some.domain.com/handlepage.cgi",
823 L"submit", L"username", L"password", L"joe_user", L"sekrit",
824 true, false, 1234567890 };
825 PasswordFormData merged_user_1_with_both_paths =
826 { PasswordForm::SCHEME_HTML, "http://some.domain.com/",
827 "http://some.domain.com/page.html",
828 "http://some.domain.com/handlepage.cgi",
829 L"submit", L"username", L"password", L"joe_user", L"otherpassword",
830 true, false, 1234567890 };
832 // Build up the big multi-dimensional array of data sets that will actually
833 // drive the test. Use vectors rather than arrays so that initialization is
834 // simple.
835 enum {
836 KEYCHAIN_INPUT = 0,
837 DATABASE_INPUT,
838 MERGE_OUTPUT,
839 KEYCHAIN_OUTPUT,
840 DATABASE_OUTPUT,
841 MERGE_IO_ARRAY_COUNT // termination marker
843 const unsigned int kTestCount = 4;
844 std::vector< std::vector< std::vector<PasswordFormData*> > > test_data(
845 MERGE_IO_ARRAY_COUNT, std::vector< std::vector<PasswordFormData*> >(
846 kTestCount, std::vector<PasswordFormData*>()));
847 unsigned int current_test = 0;
849 // Test a merge with a few accounts in both systems, with partial overlap.
850 CHECK(current_test < kTestCount);
851 test_data[KEYCHAIN_INPUT][current_test].push_back(&keychain_user_1);
852 test_data[KEYCHAIN_INPUT][current_test].push_back(&keychain_user_2);
853 test_data[DATABASE_INPUT][current_test].push_back(&db_user_1);
854 test_data[DATABASE_INPUT][current_test].push_back(&db_user_1_with_path);
855 test_data[DATABASE_INPUT][current_test].push_back(&db_user_3_with_path);
856 test_data[MERGE_OUTPUT][current_test].push_back(&merged_user_1);
857 test_data[MERGE_OUTPUT][current_test].push_back(&merged_user_1_with_db_path);
858 test_data[KEYCHAIN_OUTPUT][current_test].push_back(&keychain_user_2);
859 test_data[DATABASE_OUTPUT][current_test].push_back(&db_user_3_with_path);
861 // Test a merge where Chrome has a blacklist entry, and the keychain has
862 // a stored account.
863 ++current_test;
864 CHECK(current_test < kTestCount);
865 test_data[KEYCHAIN_INPUT][current_test].push_back(&keychain_user_1);
866 test_data[DATABASE_INPUT][current_test].push_back(
867 &database_blacklist_with_path);
868 // We expect both to be present because a blacklist could be specific to a
869 // subpath, and we want access to the password on other paths.
870 test_data[MERGE_OUTPUT][current_test].push_back(
871 &database_blacklist_with_path);
872 test_data[KEYCHAIN_OUTPUT][current_test].push_back(&keychain_user_1);
874 // Test a merge where Chrome has an account, and Keychain has a blacklist
875 // (from another browser) and the Chrome password data.
876 ++current_test;
877 CHECK(current_test < kTestCount);
878 test_data[KEYCHAIN_INPUT][current_test].push_back(&keychain_blacklist);
879 test_data[KEYCHAIN_INPUT][current_test].push_back(&keychain_user_1);
880 test_data[DATABASE_INPUT][current_test].push_back(&db_user_1);
881 test_data[MERGE_OUTPUT][current_test].push_back(&merged_user_1);
882 test_data[KEYCHAIN_OUTPUT][current_test].push_back(&keychain_blacklist);
884 // Test that matches are done using exact path when possible.
885 ++current_test;
886 CHECK(current_test < kTestCount);
887 test_data[KEYCHAIN_INPUT][current_test].push_back(&keychain_user_1);
888 test_data[KEYCHAIN_INPUT][current_test].push_back(&keychain_user_1_with_path);
889 test_data[DATABASE_INPUT][current_test].push_back(&db_user_1);
890 test_data[DATABASE_INPUT][current_test].push_back(&db_user_1_with_path);
891 test_data[MERGE_OUTPUT][current_test].push_back(&merged_user_1);
892 test_data[MERGE_OUTPUT][current_test].push_back(
893 &merged_user_1_with_both_paths);
895 for (unsigned int test_case = 0; test_case <= current_test; ++test_case) {
896 ScopedVector<autofill::PasswordForm> keychain_forms;
897 for (std::vector<PasswordFormData*>::iterator i =
898 test_data[KEYCHAIN_INPUT][test_case].begin();
899 i != test_data[KEYCHAIN_INPUT][test_case].end(); ++i) {
900 keychain_forms.push_back(
901 CreatePasswordFormFromDataForTesting(*(*i)).release());
903 ScopedVector<autofill::PasswordForm> database_forms;
904 for (std::vector<PasswordFormData*>::iterator i =
905 test_data[DATABASE_INPUT][test_case].begin();
906 i != test_data[DATABASE_INPUT][test_case].end(); ++i) {
907 database_forms.push_back(
908 CreatePasswordFormFromDataForTesting(*(*i)).release());
911 ScopedVector<autofill::PasswordForm> merged_forms;
912 internal_keychain_helpers::MergePasswordForms(&keychain_forms,
913 &database_forms,
914 &merged_forms);
916 CHECK_FORMS(keychain_forms.get(), test_data[KEYCHAIN_OUTPUT][test_case],
917 test_case);
918 CHECK_FORMS(database_forms.get(), test_data[DATABASE_OUTPUT][test_case],
919 test_case);
920 CHECK_FORMS(merged_forms.get(), test_data[MERGE_OUTPUT][test_case],
921 test_case);
925 TEST_F(PasswordStoreMacInternalsTest, TestPasswordBulkLookup) {
926 PasswordFormData db_data[] = {
927 { PasswordForm::SCHEME_HTML, "http://some.domain.com/",
928 "http://some.domain.com/", "http://some.domain.com/action.cgi",
929 L"submit", L"username", L"password", L"joe_user", L"",
930 true, false, 1212121212 },
931 { PasswordForm::SCHEME_HTML, "http://some.domain.com/",
932 "http://some.domain.com/page.html",
933 "http://some.domain.com/handlepage.cgi",
934 L"submit", L"username", L"password", L"joe_user", L"",
935 true, false, 1234567890 },
936 { PasswordForm::SCHEME_HTML, "http://some.domain.com/",
937 "http://some.domain.com/page.html",
938 "http://some.domain.com/handlepage.cgi",
939 L"submit", L"username", L"password", L"second-account", L"",
940 true, false, 1240000000 },
941 { PasswordForm::SCHEME_HTML, "http://dont.remember.com/",
942 "http://dont.remember.com/",
943 "http://dont.remember.com/handlepage.cgi",
944 L"submit", L"username", L"password", L"joe_user", L"",
945 true, false, 1240000000 },
946 { PasswordForm::SCHEME_HTML, "http://some.domain.com/",
947 "http://some.domain.com/path.html", "http://some.domain.com/action.cgi",
948 L"submit", L"username", L"password", NULL, NULL,
949 true, false, 1212121212 },
951 ScopedVector<autofill::PasswordForm> database_forms;
952 for (unsigned int i = 0; i < arraysize(db_data); ++i) {
953 database_forms.push_back(
954 CreatePasswordFormFromDataForTesting(db_data[i]).release());
956 ScopedVector<autofill::PasswordForm> merged_forms;
957 internal_keychain_helpers::GetPasswordsForForms(*keychain_, &database_forms,
958 &merged_forms);
959 EXPECT_EQ(2U, database_forms.size());
960 ASSERT_EQ(3U, merged_forms.size());
961 EXPECT_EQ(ASCIIToUTF16("sekrit"), merged_forms[0]->password_value);
962 EXPECT_EQ(ASCIIToUTF16("sekrit"), merged_forms[1]->password_value);
963 EXPECT_TRUE(merged_forms[2]->blacklisted_by_user);
966 TEST_F(PasswordStoreMacInternalsTest, TestBlacklistedFiltering) {
967 PasswordFormData db_data[] = {
968 { PasswordForm::SCHEME_HTML, "http://dont.remember.com/",
969 "http://dont.remember.com/",
970 "http://dont.remember.com/handlepage.cgi",
971 L"submit", L"username", L"password", L"joe_user", L"non_empty_password",
972 true, false, 1240000000 },
973 { PasswordForm::SCHEME_HTML, "https://dont.remember.com/",
974 "https://dont.remember.com/",
975 "https://dont.remember.com/handlepage_secure.cgi",
976 L"submit", L"username", L"password", L"joe_user", L"non_empty_password",
977 true, false, 1240000000 },
979 ScopedVector<autofill::PasswordForm> database_forms;
980 for (unsigned int i = 0; i < arraysize(db_data); ++i) {
981 database_forms.push_back(
982 CreatePasswordFormFromDataForTesting(db_data[i]).release());
984 ScopedVector<autofill::PasswordForm> merged_forms;
985 internal_keychain_helpers::GetPasswordsForForms(*keychain_, &database_forms,
986 &merged_forms);
987 EXPECT_EQ(2U, database_forms.size());
988 ASSERT_EQ(0U, merged_forms.size());
991 TEST_F(PasswordStoreMacInternalsTest, TestFillPasswordFormFromKeychainItem) {
992 // When |extract_password_data| is false, the password field must be empty,
993 // and |blacklisted_by_user| must be false.
994 SecKeychainItemRef keychain_item = reinterpret_cast<SecKeychainItemRef>(1);
995 PasswordForm form_without_extracted_password;
996 bool parsed = internal_keychain_helpers::FillPasswordFormFromKeychainItem(
997 *keychain_,
998 keychain_item,
999 &form_without_extracted_password,
1000 false); // Do not extract password.
1001 EXPECT_TRUE(parsed);
1002 ASSERT_TRUE(form_without_extracted_password.password_value.empty());
1003 ASSERT_FALSE(form_without_extracted_password.blacklisted_by_user);
1005 // When |extract_password_data| is true and the keychain entry has a non-empty
1006 // password, the password field must be non-empty, and the value of
1007 // |blacklisted_by_user| must be false.
1008 keychain_item = reinterpret_cast<SecKeychainItemRef>(1);
1009 PasswordForm form_with_extracted_password;
1010 parsed = internal_keychain_helpers::FillPasswordFormFromKeychainItem(
1011 *keychain_,
1012 keychain_item,
1013 &form_with_extracted_password,
1014 true); // Extract password.
1015 EXPECT_TRUE(parsed);
1016 ASSERT_EQ(ASCIIToUTF16("sekrit"),
1017 form_with_extracted_password.password_value);
1018 ASSERT_FALSE(form_with_extracted_password.blacklisted_by_user);
1020 // When |extract_password_data| is true and the keychain entry has an empty
1021 // username and password (""), the password field must be empty, and the value
1022 // of |blacklisted_by_user| must be true.
1023 keychain_item = reinterpret_cast<SecKeychainItemRef>(4);
1024 PasswordForm negative_form;
1025 parsed = internal_keychain_helpers::FillPasswordFormFromKeychainItem(
1026 *keychain_,
1027 keychain_item,
1028 &negative_form,
1029 true); // Extract password.
1030 EXPECT_TRUE(parsed);
1031 ASSERT_TRUE(negative_form.username_value.empty());
1032 ASSERT_TRUE(negative_form.password_value.empty());
1033 ASSERT_TRUE(negative_form.blacklisted_by_user);
1035 // When |extract_password_data| is true and the keychain entry has an empty
1036 // password (""), the password field must be empty (""), and the value of
1037 // |blacklisted_by_user| must be true.
1038 keychain_item = reinterpret_cast<SecKeychainItemRef>(5);
1039 PasswordForm form_with_empty_password_a;
1040 parsed = internal_keychain_helpers::FillPasswordFormFromKeychainItem(
1041 *keychain_,
1042 keychain_item,
1043 &form_with_empty_password_a,
1044 true); // Extract password.
1045 EXPECT_TRUE(parsed);
1046 ASSERT_TRUE(form_with_empty_password_a.password_value.empty());
1047 ASSERT_TRUE(form_with_empty_password_a.blacklisted_by_user);
1049 // When |extract_password_data| is true and the keychain entry has a single
1050 // space password (" "), the password field must be a single space (" "), and
1051 // the value of |blacklisted_by_user| must be true.
1052 keychain_item = reinterpret_cast<SecKeychainItemRef>(6);
1053 PasswordForm form_with_empty_password_b;
1054 parsed = internal_keychain_helpers::FillPasswordFormFromKeychainItem(
1055 *keychain_,
1056 keychain_item,
1057 &form_with_empty_password_b,
1058 true); // Extract password.
1059 EXPECT_TRUE(parsed);
1060 ASSERT_EQ(ASCIIToUTF16(" "),
1061 form_with_empty_password_b.password_value);
1062 ASSERT_TRUE(form_with_empty_password_b.blacklisted_by_user);
1065 TEST_F(PasswordStoreMacInternalsTest, TestPasswordGetAll) {
1066 MacKeychainPasswordFormAdapter keychain_adapter(keychain_);
1067 MacKeychainPasswordFormAdapter owned_keychain_adapter(keychain_);
1068 owned_keychain_adapter.SetFindsOnlyOwnedItems(true);
1070 // Add a few passwords of various types so that we own some.
1071 PasswordFormData owned_password_data[] = {
1072 { PasswordForm::SCHEME_HTML, "http://web.site.com/",
1073 "http://web.site.com/path/to/page.html", NULL, NULL, NULL, NULL,
1074 L"anonymous", L"knock-knock", false, false, 0 },
1075 { PasswordForm::SCHEME_BASIC, "http://a.site.com:2222/therealm",
1076 "http://a.site.com:2222/", NULL, NULL, NULL, NULL,
1077 L"username", L"password", false, false, 0 },
1078 { PasswordForm::SCHEME_DIGEST, "https://digest.site.com/differentrealm",
1079 "https://digest.site.com/secure.html", NULL, NULL, NULL, NULL,
1080 L"testname", L"testpass", false, false, 0 },
1082 for (unsigned int i = 0; i < arraysize(owned_password_data); ++i) {
1083 scoped_ptr<PasswordForm> form =
1084 CreatePasswordFormFromDataForTesting(owned_password_data[i]);
1085 owned_keychain_adapter.AddPassword(*form);
1088 ScopedVector<autofill::PasswordForm> all_passwords =
1089 keychain_adapter.GetAllPasswordFormPasswords();
1090 EXPECT_EQ(8 + arraysize(owned_password_data), all_passwords.size());
1092 ScopedVector<autofill::PasswordForm> owned_passwords =
1093 owned_keychain_adapter.GetAllPasswordFormPasswords();
1094 EXPECT_EQ(arraysize(owned_password_data), owned_passwords.size());
1097 #pragma mark -
1099 class PasswordStoreMacTest : public testing::Test {
1100 public:
1101 PasswordStoreMacTest() : ui_thread_(BrowserThread::UI, &message_loop_) {}
1103 void SetUp() override {
1104 ASSERT_TRUE(db_dir_.CreateUniqueTempDir());
1106 scoped_ptr<password_manager::LoginDatabase> login_db(
1107 new password_manager::LoginDatabase(test_login_db_file_path()));
1108 CreateAndInitPasswordStore(login_db.Pass());
1109 // Make sure deferred initialization is performed before some tests start
1110 // accessing the |login_db| directly.
1111 FinishAsyncProcessing();
1114 void TearDown() override { ClosePasswordStore(); }
1116 void CreateAndInitPasswordStore(
1117 scoped_ptr<password_manager::LoginDatabase> login_db) {
1118 store_ = new TestPasswordStoreMac(
1119 base::MessageLoopProxy::current(), base::MessageLoopProxy::current(),
1120 make_scoped_ptr<AppleKeychain>(new MockAppleKeychain), login_db.Pass());
1121 ASSERT_TRUE(store_->Init(syncer::SyncableService::StartSyncFlare()));
1124 void ClosePasswordStore() {
1125 if (!store_)
1126 return;
1128 store_->Shutdown();
1129 EXPECT_FALSE(store_->GetBackgroundTaskRunner());
1130 base::MessageLoop::current()->RunUntilIdle();
1131 store_ = nullptr;
1134 base::FilePath test_login_db_file_path() const {
1135 return db_dir_.path().Append(FILE_PATH_LITERAL("login.db"));
1138 password_manager::LoginDatabase* login_db() const {
1139 return store_->login_metadata_db();
1142 MockAppleKeychain* keychain() {
1143 return static_cast<MockAppleKeychain*>(store_->keychain());
1146 void FinishAsyncProcessing() {
1147 // Do a store-level query to wait for all the previously enqueued operations
1148 // to finish.
1149 MockPasswordStoreConsumer consumer;
1150 store_->GetLogins(PasswordForm(), PasswordStore::ALLOW_PROMPT, &consumer);
1151 EXPECT_CALL(consumer, OnGetPasswordStoreResultsConstRef(_))
1152 .WillOnce(QuitUIMessageLoop());
1153 base::MessageLoop::current()->Run();
1156 TestPasswordStoreMac* store() { return store_.get(); }
1158 protected:
1159 base::MessageLoopForUI message_loop_;
1160 content::TestBrowserThread ui_thread_;
1162 base::ScopedTempDir db_dir_;
1163 scoped_refptr<TestPasswordStoreMac> store_;
1166 TEST_F(PasswordStoreMacTest, TestStoreUpdate) {
1167 // Insert a password into both the database and the keychain.
1168 // This is done manually, rather than through store_->AddLogin, because the
1169 // Mock Keychain isn't smart enough to be able to support update generically,
1170 // so some.domain.com triggers special handling to test it that make inserting
1171 // fail.
1172 PasswordFormData joint_data = {
1173 PasswordForm::SCHEME_HTML, "http://some.domain.com/",
1174 "http://some.domain.com/insecure.html", "login.cgi",
1175 L"username", L"password", L"submit", L"joe_user", L"sekrit", true, false, 1
1177 scoped_ptr<PasswordForm> joint_form =
1178 CreatePasswordFormFromDataForTesting(joint_data);
1179 EXPECT_EQ(AddChangeForForm(*joint_form), login_db()->AddLogin(*joint_form));
1180 MockAppleKeychain::KeychainTestData joint_keychain_data = {
1181 kSecAuthenticationTypeHTMLForm, "some.domain.com",
1182 kSecProtocolTypeHTTP, "/insecure.html", 0, NULL, "20020601171500Z",
1183 "joe_user", "sekrit", false };
1184 keychain()->AddTestItem(joint_keychain_data);
1186 // Insert a password into the keychain only.
1187 MockAppleKeychain::KeychainTestData keychain_only_data = {
1188 kSecAuthenticationTypeHTMLForm, "keychain.only.com",
1189 kSecProtocolTypeHTTP, NULL, 0, NULL, "20020601171500Z",
1190 "keychain", "only", false
1192 keychain()->AddTestItem(keychain_only_data);
1194 struct UpdateData {
1195 PasswordFormData form_data;
1196 const char* password; // NULL indicates no entry should be present.
1199 // Make a series of update calls.
1200 UpdateData updates[] = {
1201 // Update the keychain+db passwords (the normal password update case).
1202 { { PasswordForm::SCHEME_HTML, "http://some.domain.com/",
1203 "http://some.domain.com/insecure.html", "login.cgi",
1204 L"username", L"password", L"submit", L"joe_user", L"53krit",
1205 true, false, 2 },
1206 "53krit",
1208 // Update the keychain-only password; this simulates the initial use of a
1209 // password stored by another browsers.
1210 { { PasswordForm::SCHEME_HTML, "http://keychain.only.com/",
1211 "http://keychain.only.com/login.html", "login.cgi",
1212 L"username", L"password", L"submit", L"keychain", L"only",
1213 true, false, 2 },
1214 "only",
1216 // Update a password that doesn't exist in either location. This tests the
1217 // case where a form is filled, then the stored login is removed, then the
1218 // form is submitted.
1219 { { PasswordForm::SCHEME_HTML, "http://different.com/",
1220 "http://different.com/index.html", "login.cgi",
1221 L"username", L"password", L"submit", L"abc", L"123",
1222 true, false, 2 },
1223 NULL,
1226 for (unsigned int i = 0; i < arraysize(updates); ++i) {
1227 scoped_ptr<PasswordForm> form =
1228 CreatePasswordFormFromDataForTesting(updates[i].form_data);
1229 store_->UpdateLogin(*form);
1232 FinishAsyncProcessing();
1234 MacKeychainPasswordFormAdapter keychain_adapter(keychain());
1235 for (unsigned int i = 0; i < arraysize(updates); ++i) {
1236 scoped_ptr<PasswordForm> query_form =
1237 CreatePasswordFormFromDataForTesting(updates[i].form_data);
1239 ScopedVector<autofill::PasswordForm> matching_items =
1240 keychain_adapter.PasswordsFillingForm(query_form->signon_realm,
1241 query_form->scheme);
1242 if (updates[i].password) {
1243 EXPECT_GT(matching_items.size(), 0U) << "iteration " << i;
1244 if (matching_items.size() >= 1)
1245 EXPECT_EQ(ASCIIToUTF16(updates[i].password),
1246 matching_items[0]->password_value) << "iteration " << i;
1247 } else {
1248 EXPECT_EQ(0U, matching_items.size()) << "iteration " << i;
1250 matching_items.clear();
1252 login_db()->GetLogins(*query_form, &matching_items);
1253 EXPECT_EQ(updates[i].password ? 1U : 0U, matching_items.size())
1254 << "iteration " << i;
1258 TEST_F(PasswordStoreMacTest, TestDBKeychainAssociation) {
1259 // Tests that association between the keychain and login database parts of a
1260 // password added by fuzzy (PSL) matching works.
1261 // 1. Add a password for www.facebook.com
1262 // 2. Get a password for m.facebook.com. This fuzzy matches and returns the
1263 // www.facebook.com password.
1264 // 3. Add the returned password for m.facebook.com.
1265 // 4. Remove both passwords.
1266 // -> check: that both are gone from the login DB and the keychain
1267 // This test should in particular ensure that we don't keep passwords in the
1268 // keychain just before we think we still have other (fuzzy-)matching entries
1269 // for them in the login database. (For example, here if we deleted the
1270 // www.facebook.com password from the login database, we should not be blocked
1271 // from deleting it from the keystore just becaus the m.facebook.com password
1272 // fuzzy-matches the www.facebook.com one.)
1274 // 1. Add a password for www.facebook.com
1275 PasswordFormData www_form_data = {
1276 PasswordForm::SCHEME_HTML, "http://www.facebook.com/",
1277 "http://www.facebook.com/index.html", "login",
1278 L"username", L"password", L"submit", L"joe_user", L"sekrit", true, false, 1
1280 scoped_ptr<PasswordForm> www_form =
1281 CreatePasswordFormFromDataForTesting(www_form_data);
1282 EXPECT_EQ(AddChangeForForm(*www_form), login_db()->AddLogin(*www_form));
1283 MacKeychainPasswordFormAdapter owned_keychain_adapter(keychain());
1284 owned_keychain_adapter.SetFindsOnlyOwnedItems(true);
1285 owned_keychain_adapter.AddPassword(*www_form);
1287 // 2. Get a password for m.facebook.com.
1288 PasswordForm m_form(*www_form);
1289 m_form.signon_realm = "http://m.facebook.com";
1290 m_form.origin = GURL("http://m.facebook.com/index.html");
1292 MockPasswordStoreConsumer consumer;
1293 store_->GetLogins(m_form, PasswordStore::ALLOW_PROMPT, &consumer);
1294 PasswordForm returned_form;
1295 EXPECT_CALL(consumer, OnGetPasswordStoreResultsConstRef(SizeIs(1u)))
1296 .WillOnce(
1297 DoAll(SaveACopyOfFirstForm(&returned_form), QuitUIMessageLoop()));
1298 base::MessageLoop::current()->Run();
1300 // 3. Add the returned password for m.facebook.com.
1301 EXPECT_EQ(AddChangeForForm(returned_form),
1302 login_db()->AddLogin(returned_form));
1303 owned_keychain_adapter.AddPassword(m_form);
1305 // 4. Remove both passwords.
1306 store_->RemoveLogin(*www_form);
1307 store_->RemoveLogin(m_form);
1308 FinishAsyncProcessing();
1310 // No trace of www.facebook.com.
1311 ScopedVector<autofill::PasswordForm> matching_items =
1312 owned_keychain_adapter.PasswordsFillingForm(www_form->signon_realm,
1313 www_form->scheme);
1314 EXPECT_EQ(0u, matching_items.size());
1315 login_db()->GetLogins(*www_form, &matching_items);
1316 EXPECT_EQ(0u, matching_items.size());
1317 // No trace of m.facebook.com.
1318 matching_items = owned_keychain_adapter.PasswordsFillingForm(
1319 m_form.signon_realm, m_form.scheme);
1320 EXPECT_EQ(0u, matching_items.size());
1321 login_db()->GetLogins(m_form, &matching_items);
1322 EXPECT_EQ(0u, matching_items.size());
1325 namespace {
1327 class PasswordsChangeObserver :
1328 public password_manager::PasswordStore::Observer {
1329 public:
1330 PasswordsChangeObserver(TestPasswordStoreMac* store) : observer_(this) {
1331 observer_.Add(store);
1334 void WaitAndVerify(PasswordStoreMacTest* test) {
1335 test->FinishAsyncProcessing();
1336 ::testing::Mock::VerifyAndClearExpectations(this);
1339 // password_manager::PasswordStore::Observer:
1340 MOCK_METHOD1(OnLoginsChanged,
1341 void(const password_manager::PasswordStoreChangeList& changes));
1343 private:
1344 ScopedObserver<password_manager::PasswordStore,
1345 PasswordsChangeObserver> observer_;
1348 password_manager::PasswordStoreChangeList GetAddChangeList(
1349 const PasswordForm& form) {
1350 password_manager::PasswordStoreChange change(
1351 password_manager::PasswordStoreChange::ADD, form);
1352 return password_manager::PasswordStoreChangeList(1, change);
1355 // Tests RemoveLoginsCreatedBetween or RemoveLoginsSyncedBetween depending on
1356 // |check_created|.
1357 void CheckRemoveLoginsBetween(PasswordStoreMacTest* test, bool check_created) {
1358 PasswordFormData www_form_data_facebook = {
1359 PasswordForm::SCHEME_HTML, "http://www.facebook.com/",
1360 "http://www.facebook.com/index.html", "login", L"submit", L"username",
1361 L"password", L"joe_user", L"sekrit", true, false, 0 };
1362 // The old form doesn't have elements names.
1363 PasswordFormData www_form_data_facebook_old = {
1364 PasswordForm::SCHEME_HTML, "http://www.facebook.com/",
1365 "http://www.facebook.com/index.html", "login", L"", L"",
1366 L"", L"joe_user", L"oldsekrit", true, false, 0 };
1367 PasswordFormData www_form_data_other = {
1368 PasswordForm::SCHEME_HTML, "http://different.com/",
1369 "http://different.com/index.html", "login", L"submit", L"username",
1370 L"password", L"different_joe_user", L"sekrit", true, false, 0 };
1371 scoped_ptr<PasswordForm> form_facebook =
1372 CreatePasswordFormFromDataForTesting(www_form_data_facebook);
1373 scoped_ptr<PasswordForm> form_facebook_old =
1374 CreatePasswordFormFromDataForTesting(www_form_data_facebook_old);
1375 scoped_ptr<PasswordForm> form_other =
1376 CreatePasswordFormFromDataForTesting(www_form_data_other);
1377 base::Time now = base::Time::Now();
1378 // TODO(vasilii): remove the next line once crbug/374132 is fixed.
1379 now = base::Time::FromTimeT(now.ToTimeT());
1380 base::Time next_day = now + base::TimeDelta::FromDays(1);
1381 if (check_created) {
1382 form_facebook_old->date_created = now;
1383 form_facebook->date_created = next_day;
1384 form_other->date_created = next_day;
1385 } else {
1386 form_facebook_old->date_synced = now;
1387 form_facebook->date_synced = next_day;
1388 form_other->date_synced = next_day;
1391 PasswordsChangeObserver observer(test->store());
1392 test->store()->AddLogin(*form_facebook_old);
1393 test->store()->AddLogin(*form_facebook);
1394 test->store()->AddLogin(*form_other);
1395 EXPECT_CALL(observer, OnLoginsChanged(GetAddChangeList(*form_facebook_old)));
1396 EXPECT_CALL(observer, OnLoginsChanged(GetAddChangeList(*form_facebook)));
1397 EXPECT_CALL(observer, OnLoginsChanged(GetAddChangeList(*form_other)));
1398 observer.WaitAndVerify(test);
1400 // Check the keychain content.
1401 MacKeychainPasswordFormAdapter owned_keychain_adapter(test->keychain());
1402 owned_keychain_adapter.SetFindsOnlyOwnedItems(false);
1403 ScopedVector<PasswordForm> matching_items(
1404 owned_keychain_adapter.PasswordsFillingForm(form_facebook->signon_realm,
1405 form_facebook->scheme));
1406 EXPECT_EQ(1u, matching_items.size());
1407 matching_items = owned_keychain_adapter.PasswordsFillingForm(
1408 form_other->signon_realm, form_other->scheme);
1409 EXPECT_EQ(1u, matching_items.size());
1411 // Remove facebook.
1412 void (PasswordStore::*method)(base::Time, base::Time) =
1413 check_created ? &PasswordStore::RemoveLoginsCreatedBetween
1414 : &PasswordStore::RemoveLoginsSyncedBetween;
1415 (test->store()->*method)(base::Time(), next_day);
1416 password_manager::PasswordStoreChangeList list;
1417 form_facebook_old->password_value.clear();
1418 form_facebook->password_value.clear();
1419 list.push_back(password_manager::PasswordStoreChange(
1420 password_manager::PasswordStoreChange::REMOVE, *form_facebook_old));
1421 list.push_back(password_manager::PasswordStoreChange(
1422 password_manager::PasswordStoreChange::REMOVE, *form_facebook));
1423 EXPECT_CALL(observer, OnLoginsChanged(list));
1424 list.clear();
1425 observer.WaitAndVerify(test);
1427 matching_items = owned_keychain_adapter.PasswordsFillingForm(
1428 form_facebook->signon_realm, form_facebook->scheme);
1429 EXPECT_EQ(0u, matching_items.size());
1430 matching_items = owned_keychain_adapter.PasswordsFillingForm(
1431 form_other->signon_realm, form_other->scheme);
1432 EXPECT_EQ(1u, matching_items.size());
1434 // Remove form_other.
1435 (test->store()->*method)(next_day, base::Time());
1436 form_other->password_value.clear();
1437 list.push_back(password_manager::PasswordStoreChange(
1438 password_manager::PasswordStoreChange::REMOVE, *form_other));
1439 EXPECT_CALL(observer, OnLoginsChanged(list));
1440 observer.WaitAndVerify(test);
1441 matching_items = owned_keychain_adapter.PasswordsFillingForm(
1442 form_other->signon_realm, form_other->scheme);
1443 EXPECT_EQ(0u, matching_items.size());
1446 } // namespace
1448 TEST_F(PasswordStoreMacTest, TestRemoveLoginsCreatedBetween) {
1449 CheckRemoveLoginsBetween(this, true);
1452 TEST_F(PasswordStoreMacTest, TestRemoveLoginsSyncedBetween) {
1453 CheckRemoveLoginsBetween(this, false);
1456 TEST_F(PasswordStoreMacTest, TestRemoveLoginsMultiProfile) {
1457 // Make sure that RemoveLoginsCreatedBetween does affect only the correct
1458 // profile.
1460 // Add a third-party password.
1461 MockAppleKeychain::KeychainTestData keychain_data = {
1462 kSecAuthenticationTypeHTMLForm, "some.domain.com",
1463 kSecProtocolTypeHTTP, "/insecure.html", 0, NULL, "20020601171500Z",
1464 "joe_user", "sekrit", false };
1465 keychain()->AddTestItem(keychain_data);
1467 // Add a password through the adapter. It has the "Chrome" creator tag.
1468 // However, it's not referenced by the password database.
1469 MacKeychainPasswordFormAdapter owned_keychain_adapter(keychain());
1470 owned_keychain_adapter.SetFindsOnlyOwnedItems(true);
1471 PasswordFormData www_form_data1 = {
1472 PasswordForm::SCHEME_HTML, "http://www.facebook.com/",
1473 "http://www.facebook.com/index.html", "login", L"username", L"password",
1474 L"submit", L"joe_user", L"sekrit", true, false, 1 };
1475 scoped_ptr<PasswordForm> www_form =
1476 CreatePasswordFormFromDataForTesting(www_form_data1);
1477 EXPECT_TRUE(owned_keychain_adapter.AddPassword(*www_form));
1479 // Add a password from the current profile.
1480 PasswordFormData www_form_data2 = {
1481 PasswordForm::SCHEME_HTML, "http://www.facebook.com/",
1482 "http://www.facebook.com/index.html", "login", L"username", L"password",
1483 L"submit", L"not_joe_user", L"12345", true, false, 1 };
1484 www_form = CreatePasswordFormFromDataForTesting(www_form_data2);
1485 store_->AddLogin(*www_form);
1486 FinishAsyncProcessing();
1488 ScopedVector<PasswordForm> matching_items;
1489 login_db()->GetLogins(*www_form, &matching_items);
1490 EXPECT_EQ(1u, matching_items.size());
1491 matching_items.clear();
1493 store_->RemoveLoginsCreatedBetween(base::Time(), base::Time());
1494 FinishAsyncProcessing();
1496 // Check the second facebook form is gone.
1497 login_db()->GetLogins(*www_form, &matching_items);
1498 EXPECT_EQ(0u, matching_items.size());
1500 // Check the first facebook form is still there.
1501 matching_items = owned_keychain_adapter.PasswordsFillingForm(
1502 www_form->signon_realm, www_form->scheme);
1503 ASSERT_EQ(1u, matching_items.size());
1504 EXPECT_EQ(ASCIIToUTF16("joe_user"), matching_items[0]->username_value);
1505 matching_items.clear();
1507 // Check the third-party password is still there.
1508 owned_keychain_adapter.SetFindsOnlyOwnedItems(false);
1509 matching_items = owned_keychain_adapter.PasswordsFillingForm(
1510 "http://some.domain.com/insecure.html", PasswordForm::SCHEME_HTML);
1511 ASSERT_EQ(1u, matching_items.size());
1514 // Open the store and immediately write to it and try to read it back, without
1515 // first waiting for the initialization to finish. If tasks are processed in
1516 // order, read/write operations will correctly be performed only after the
1517 // initialization has finished.
1518 TEST_F(PasswordStoreMacTest, StoreIsUsableImmediatelyAfterConstruction) {
1519 ClosePasswordStore();
1521 base::WaitableEvent event(false, false);
1522 CreateAndInitPasswordStore(make_scoped_ptr<password_manager::LoginDatabase>(
1523 new SlowToInitLoginDatabase(test_login_db_file_path(), &event)));
1525 PasswordFormData www_form_data = {
1526 PasswordForm::SCHEME_HTML, "http://www.facebook.com/",
1527 "http://www.facebook.com/index.html", "login", L"username", L"password",
1528 L"submit", L"not_joe_user", L"12345", true, false, 1};
1529 scoped_ptr<PasswordForm> form =
1530 CreatePasswordFormFromDataForTesting(www_form_data);
1531 store()->AddLogin(*form);
1533 MockPasswordStoreConsumer mock_consumer;
1534 store()->GetLogins(*form, PasswordStore::ALLOW_PROMPT, &mock_consumer);
1536 // Now the read/write tasks are scheduled, let the DB initialization proceed.
1537 event.Signal();
1539 EXPECT_CALL(mock_consumer, OnGetPasswordStoreResultsConstRef(SizeIs(1u)))
1540 .WillOnce(QuitUIMessageLoop());
1541 base::MessageLoop::current()->Run();
1542 EXPECT_TRUE(login_db());
1545 // Verify that operations on a PasswordStore with a bad database cause no
1546 // explosions, but fail without side effect, return no data and trigger no
1547 // notifications.
1548 TEST_F(PasswordStoreMacTest, OperationsOnABadDatabaseSilentlyFail) {
1549 ClosePasswordStore();
1550 CreateAndInitPasswordStore(
1551 make_scoped_ptr<password_manager::LoginDatabase>(new BadLoginDatabase));
1552 FinishAsyncProcessing();
1553 EXPECT_FALSE(login_db());
1555 testing::StrictMock<MockPasswordStoreObserver> mock_observer;
1556 store()->AddObserver(&mock_observer);
1558 // Add a new autofillable login + a blacklisted login.
1559 PasswordFormData www_form_data = {
1560 PasswordForm::SCHEME_HTML, "http://www.facebook.com/",
1561 "http://www.facebook.com/index.html", "login", L"username", L"password",
1562 L"submit", L"not_joe_user", L"12345", true, false, 1};
1563 scoped_ptr<PasswordForm> form =
1564 CreatePasswordFormFromDataForTesting(www_form_data);
1565 scoped_ptr<PasswordForm> blacklisted_form(new PasswordForm(*form));
1566 blacklisted_form->signon_realm = "http://foo.example.com";
1567 blacklisted_form->origin = GURL("http://foo.example.com/origin");
1568 blacklisted_form->action = GURL("http://foo.example.com/action");
1569 blacklisted_form->blacklisted_by_user = true;
1570 store()->AddLogin(*form);
1571 store()->AddLogin(*blacklisted_form);
1572 FinishAsyncProcessing();
1574 // Get all logins; autofillable logins; blacklisted logins.
1575 MockPasswordStoreConsumer mock_consumer;
1576 store()->GetLogins(*form, PasswordStore::DISALLOW_PROMPT, &mock_consumer);
1577 ON_CALL(mock_consumer, OnGetPasswordStoreResultsConstRef(_))
1578 .WillByDefault(QuitUIMessageLoop());
1579 EXPECT_CALL(mock_consumer, OnGetPasswordStoreResultsConstRef(IsEmpty()));
1580 base::MessageLoop::current()->Run();
1582 store()->GetAutofillableLogins(&mock_consumer);
1583 EXPECT_CALL(mock_consumer, OnGetPasswordStoreResultsConstRef(IsEmpty()));
1584 base::MessageLoop::current()->Run();
1586 store()->GetBlacklistLogins(&mock_consumer);
1587 EXPECT_CALL(mock_consumer, OnGetPasswordStoreResultsConstRef(IsEmpty()));
1588 base::MessageLoop::current()->Run();
1590 // Report metrics.
1591 store()->ReportMetrics("Test Username", true);
1592 FinishAsyncProcessing();
1594 // Change the login.
1595 form->password_value = base::ASCIIToUTF16("a different password");
1596 store()->UpdateLogin(*form);
1597 FinishAsyncProcessing();
1599 // Delete one login; a range of logins.
1600 store()->RemoveLogin(*form);
1601 store()->RemoveLoginsCreatedBetween(base::Time(), base::Time::Max());
1602 store()->RemoveLoginsSyncedBetween(base::Time(), base::Time::Max());
1603 FinishAsyncProcessing();
1605 // Verify no notifications are fired during shutdown either.
1606 ClosePasswordStore();
1609 // Add a facebook form to the store but not to the keychain. The form is to be
1610 // implicitly deleted. However, the observers shouldn't get notified about
1611 // deletion of non-existent forms like m.facebook.com.
1612 TEST_F(PasswordStoreMacTest, SilentlyRemoveOrphanedForm) {
1613 testing::StrictMock<MockPasswordStoreObserver> mock_observer;
1614 store()->AddObserver(&mock_observer);
1616 // 1. Add a password for www.facebook.com to the LoginDatabase.
1617 PasswordFormData www_form_data = {
1618 PasswordForm::SCHEME_HTML, "http://www.facebook.com/",
1619 "http://www.facebook.com/index.html", "login",
1620 L"username", L"password", L"submit", L"joe_user", L"", true, false, 1
1622 scoped_ptr<PasswordForm> www_form(
1623 CreatePasswordFormFromDataForTesting(www_form_data));
1624 EXPECT_EQ(AddChangeForForm(*www_form), login_db()->AddLogin(*www_form));
1626 // 2. Get a PSL-matched password for m.facebook.com. The observer isn't
1627 // notified because the form isn't in the database.
1628 PasswordForm m_form(*www_form);
1629 m_form.signon_realm = "http://m.facebook.com";
1630 m_form.origin = GURL("http://m.facebook.com/index.html");
1632 MockPasswordStoreConsumer consumer;
1633 ON_CALL(consumer, OnGetPasswordStoreResultsConstRef(_))
1634 .WillByDefault(QuitUIMessageLoop());
1635 EXPECT_CALL(mock_observer, OnLoginsChanged(_)).Times(0);
1636 // The PSL-matched form isn't returned because there is no actual password in
1637 // the keychain.
1638 EXPECT_CALL(consumer, OnGetPasswordStoreResultsConstRef(IsEmpty()));
1639 store_->GetLogins(m_form, PasswordStore::ALLOW_PROMPT, &consumer);
1640 base::MessageLoop::current()->Run();
1641 ScopedVector<autofill::PasswordForm> all_forms;
1642 EXPECT_TRUE(login_db()->GetAutofillableLogins(&all_forms));
1643 EXPECT_EQ(1u, all_forms.size());
1644 all_forms.clear();
1645 ::testing::Mock::VerifyAndClearExpectations(&mock_observer);
1647 // 3. Get a password for www.facebook.com. The form is implicitly removed and
1648 // the observer is notified.
1649 password_manager::PasswordStoreChangeList list;
1650 list.push_back(password_manager::PasswordStoreChange(
1651 password_manager::PasswordStoreChange::REMOVE, *www_form));
1652 EXPECT_CALL(mock_observer, OnLoginsChanged(list));
1653 EXPECT_CALL(consumer, OnGetPasswordStoreResultsConstRef(IsEmpty()));
1654 store_->GetLogins(*www_form, PasswordStore::ALLOW_PROMPT, &consumer);
1655 base::MessageLoop::current()->Run();
1656 EXPECT_TRUE(login_db()->GetAutofillableLogins(&all_forms));
1657 EXPECT_EQ(0u, all_forms.size());
1660 // Verify that Android app passwords are retrievable.
1661 // Regression test for http://crbug.com/455551
1662 TEST_F(PasswordStoreMacTest, AndroidCredentialsMatchAfterInsertion) {
1663 PasswordForm form;
1664 form.signon_realm = "android://7x7IDboo8u9YKraUsbmVkuf1@net.rateflix.app/";
1665 form.username_value = base::UTF8ToUTF16("randomusername");
1666 form.password_value = base::UTF8ToUTF16("password");
1667 store()->AddLogin(form);
1668 FinishAsyncProcessing();
1670 PasswordForm returned_form;
1671 MockPasswordStoreConsumer mock_consumer;
1672 EXPECT_CALL(mock_consumer, OnGetPasswordStoreResultsConstRef(SizeIs(1u)))
1673 .WillOnce(
1674 DoAll(SaveACopyOfFirstForm(&returned_form), QuitUIMessageLoop()));
1676 store()->GetAutofillableLogins(&mock_consumer);
1677 base::MessageLoop::current()->Run();
1678 ::testing::Mock::VerifyAndClearExpectations(&mock_consumer);
1679 EXPECT_EQ(form, returned_form);
1681 PasswordForm query_form = form;
1682 query_form.password_value.clear();
1683 query_form.username_value.clear();
1684 EXPECT_CALL(mock_consumer, OnGetPasswordStoreResultsConstRef(SizeIs(1u)))
1685 .WillOnce(
1686 DoAll(SaveACopyOfFirstForm(&returned_form), QuitUIMessageLoop()));
1687 store()->GetLogins(query_form, PasswordStore::ALLOW_PROMPT, &mock_consumer);
1688 base::MessageLoop::current()->Run();
1689 ::testing::Mock::VerifyAndClearExpectations(&mock_consumer);
1690 EXPECT_EQ(form, returned_form);