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
;
40 using testing::Invoke
;
41 using testing::IsEmpty
;
42 using testing::SizeIs
;
43 using testing::WithArg
;
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
{
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
{
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
{
79 BadLoginDatabase() : password_manager::LoginDatabase(base::FilePath()) {}
80 ~BadLoginDatabase() override
{}
83 bool Init() override
{ return false; }
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
{
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
{}
100 bool Init() override
{
102 return password_manager::LoginDatabase::Init();
106 base::WaitableEvent
* event_
;
108 DISALLOW_COPY_AND_ASSIGN(SlowToInitLoginDatabase
);
111 class TestPasswordStoreMac
: public PasswordStoreMac
{
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
,
123 using PasswordStoreMac::GetBackgroundTaskRunner
;
126 ~TestPasswordStoreMac() override
{}
128 DISALLOW_COPY_AND_ASSIGN(TestPasswordStoreMac
);
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 "
148 if (expectations
.size() != forms
.size())
151 for (unsigned int i
= 0; i
< expectations
.size(); ++i
) {
152 SCOPED_TRACE(testing::Message() << forms_label
<< " in test " << test_number
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
);
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
);
179 created
+ base::TimeDelta::FromDays(
180 password_manager::kTestingDaysAfterPasswordsAreSynced
),
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
));
197 class PasswordStoreMacInternalsTest
: public testing::Test
{
199 void SetUp() override
{
200 MockAppleKeychain::KeychainTestData test_data
[] = {
202 {kSecAuthenticationTypeHTMLForm
,
204 kSecProtocolTypeHTTP
,
212 // HTML form with path.
213 {kSecAuthenticationTypeHTMLForm
,
215 kSecProtocolTypeHTTP
,
223 // Secure HTML form with path.
224 {kSecAuthenticationTypeHTMLForm
,
226 kSecProtocolTypeHTTPS
,
234 // True negative item.
235 {kSecAuthenticationTypeHTMLForm
,
237 kSecProtocolTypeHTTP
,
245 // De-facto negative item, type one.
246 {kSecAuthenticationTypeHTMLForm
,
248 kSecProtocolTypeHTTP
,
253 "Password Not Stored",
256 // De-facto negative item, type two.
257 {kSecAuthenticationTypeHTMLForm
,
259 kSecProtocolTypeHTTPS
,
264 "Password Not Stored",
267 // HTTP auth basic, with port and path.
268 {kSecAuthenticationTypeHTTPBasic
,
270 kSecProtocolTypeHTTP
,
278 // HTTP auth digest, secure.
279 {kSecAuthenticationTypeHTTPDigest
,
281 kSecProtocolTypeHTTPS
,
289 // An FTP password with an invalid date, for edge-case testing.
290 {kSecAuthenticationTypeDefault
,
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();
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_
;
336 TEST_F(PasswordStoreMacInternalsTest
, TestKeychainToFormTranslation
) {
338 const PasswordForm::Scheme scheme
;
339 const char* signon_realm
;
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
;
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
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);
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
;
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);
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 },
446 { { PasswordForm::SCHEME_HTML
, "http://some.domain.com/",
447 NULL
, NULL
, NULL
, NULL
, NULL
, L
"wrong_user", NULL
, false, false, 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 },
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,
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,
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,
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 },
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,
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
,
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
,
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
) {
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(
599 EXPECT_FALSE(match
) << "In modified version " << j
600 << " of base form " << i
;
605 TEST_F(PasswordStoreMacInternalsTest
, TestKeychainAdd
) {
606 struct TestDataAndExpectation
{
607 PasswordFormData data
;
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
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
);
645 EXPECT_TRUE(owned_keychain_adapter
.HasPasswordsMergeableWithForm(
647 EXPECT_TRUE(owned_keychain_adapter
.HasPasswordExactlyMatchingForm(
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_
,
669 EXPECT_EQ(update_form
->password_value
, stored_form
.password_value
);
673 TEST_F(PasswordStoreMacInternalsTest
, TestKeychainRemove
) {
674 struct TestDataAndExpectation
{
675 PasswordFormData data
;
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();
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");
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
;
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/");
746 FormsMatchForMerge(base_form
, different_form
, STRICT_FORM_MATCH
));
749 PasswordForm
different_form(base_form
);
750 different_form
.username_value
= ASCIIToUTF16("john.doe");
752 FormsMatchForMerge(base_form
, different_form
, STRICT_FORM_MATCH
));
755 PasswordForm
different_form(base_form
);
756 different_form
.blacklisted_by_user
= true;
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
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
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.
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.
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
,
916 CHECK_FORMS(keychain_forms
.get(), test_data
[KEYCHAIN_OUTPUT
][test_case
],
918 CHECK_FORMS(database_forms
.get(), test_data
[DATABASE_OUTPUT
][test_case
],
920 CHECK_FORMS(merged_forms
.get(), test_data
[MERGE_OUTPUT
][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
,
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
,
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(
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(
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(
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(
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(
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());
1099 class PasswordStoreMacTest
: public testing::Test
{
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() {
1129 EXPECT_FALSE(store_
->GetBackgroundTaskRunner());
1130 base::MessageLoop::current()->RunUntilIdle();
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
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(); }
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
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
);
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",
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",
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",
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
;
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)))
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
,
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());
1327 class PasswordsChangeObserver
:
1328 public password_manager::PasswordStore::Observer
{
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
));
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
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
;
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());
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
));
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());
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
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.
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
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();
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
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());
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
) {
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)))
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)))
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
);