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 "base/test/histogram_tester.h"
15 #include "base/thread_task_runner_handle.h"
16 #include "chrome/browser/password_manager/password_store_mac_internal.h"
17 #include "chrome/common/chrome_paths.h"
18 #include "components/os_crypt/os_crypt.h"
19 #include "components/password_manager/core/browser/login_database.h"
20 #include "components/password_manager/core/browser/password_manager_test_utils.h"
21 #include "components/password_manager/core/browser/password_store_consumer.h"
22 #include "content/public/test/test_browser_thread.h"
23 #include "content/public/test/test_utils.h"
24 #include "crypto/mock_apple_keychain.h"
25 #include "testing/gmock/include/gmock/gmock.h"
26 #include "testing/gtest/include/gtest/gtest.h"
28 using autofill::PasswordForm
;
29 using base::ASCIIToUTF16
;
30 using base::WideToUTF16
;
31 using content::BrowserThread
;
32 using crypto::MockAppleKeychain
;
33 using internal_keychain_helpers::FormsMatchForMerge
;
34 using internal_keychain_helpers::STRICT_FORM_MATCH
;
35 using password_manager::CreatePasswordFormFromDataForTesting
;
36 using password_manager::LoginDatabase
;
37 using password_manager::PasswordFormData
;
38 using password_manager::PasswordStore
;
39 using password_manager::PasswordStoreChange
;
40 using password_manager::PasswordStoreChangeList
;
41 using password_manager::PasswordStoreConsumer
;
44 using testing::Invoke
;
45 using testing::IsEmpty
;
46 using testing::SizeIs
;
47 using testing::WithArg
;
51 ACTION(QuitUIMessageLoop
) {
52 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
53 base::MessageLoop::current()->Quit();
56 // From the mock's argument #0 of type const std::vector<PasswordForm*>& takes
57 // the first form and copies it to the form pointed to by |target_form_ptr|.
58 ACTION_P(SaveACopyOfFirstForm
, target_form_ptr
) {
59 ASSERT_FALSE(arg0
.empty());
60 *target_form_ptr
= *arg0
[0];
66 class MockPasswordStoreConsumer
: public PasswordStoreConsumer
{
68 MOCK_METHOD1(OnGetPasswordStoreResultsConstRef
,
69 void(const std::vector
<PasswordForm
*>&));
71 // GMock cannot mock methods with move-only args.
72 void OnGetPasswordStoreResults(ScopedVector
<PasswordForm
> results
) override
{
73 OnGetPasswordStoreResultsConstRef(results
.get());
77 class MockPasswordStoreObserver
: public PasswordStore::Observer
{
79 MOCK_METHOD1(OnLoginsChanged
,
80 void(const password_manager::PasswordStoreChangeList
& changes
));
83 // A LoginDatabase that simulates an Init() method that takes a long time.
84 class SlowToInitLoginDatabase
: public password_manager::LoginDatabase
{
86 // Creates an instance whose Init() method will block until |event| is
87 // signaled. |event| must outlive |this|.
88 SlowToInitLoginDatabase(const base::FilePath
& db_path
,
89 base::WaitableEvent
* event
)
90 : password_manager::LoginDatabase(db_path
), event_(event
) {}
91 ~SlowToInitLoginDatabase() override
{}
94 bool Init() override
{
96 return password_manager::LoginDatabase::Init();
100 base::WaitableEvent
* event_
;
102 DISALLOW_COPY_AND_ASSIGN(SlowToInitLoginDatabase
);
107 // Macro to simplify calling CheckFormsAgainstExpectations with a useful label.
108 #define CHECK_FORMS(forms, expectations, i) \
109 CheckFormsAgainstExpectations(forms, expectations, #forms, i)
111 // Ensures that the data in |forms| match |expectations|, causing test failures
112 // for any discrepencies.
113 // TODO(stuartmorgan): This is current order-dependent; ideally it shouldn't
114 // matter if |forms| and |expectations| are scrambled.
115 void CheckFormsAgainstExpectations(
116 const std::vector
<PasswordForm
*>& forms
,
117 const std::vector
<PasswordFormData
*>& expectations
,
119 const char* forms_label
, unsigned int test_number
) {
120 EXPECT_EQ(expectations
.size(), forms
.size()) << forms_label
<< " in test "
122 if (expectations
.size() != forms
.size())
125 for (unsigned int i
= 0; i
< expectations
.size(); ++i
) {
126 SCOPED_TRACE(testing::Message() << forms_label
<< " in test " << test_number
128 PasswordForm
* form
= forms
[i
];
129 PasswordFormData
* expectation
= expectations
[i
];
130 EXPECT_EQ(expectation
->scheme
, form
->scheme
);
131 EXPECT_EQ(std::string(expectation
->signon_realm
), form
->signon_realm
);
132 EXPECT_EQ(GURL(expectation
->origin
), form
->origin
);
133 EXPECT_EQ(GURL(expectation
->action
), form
->action
);
134 EXPECT_EQ(WideToUTF16(expectation
->submit_element
), form
->submit_element
);
135 EXPECT_EQ(WideToUTF16(expectation
->username_element
),
136 form
->username_element
);
137 EXPECT_EQ(WideToUTF16(expectation
->password_element
),
138 form
->password_element
);
139 if (expectation
->username_value
) {
140 EXPECT_EQ(WideToUTF16(expectation
->username_value
), form
->username_value
);
141 EXPECT_EQ(WideToUTF16(expectation
->username_value
), form
->display_name
);
142 EXPECT_TRUE(form
->skip_zero_click
);
143 if (expectation
->password_value
&&
144 wcscmp(expectation
->password_value
,
145 password_manager::kTestingFederatedLoginMarker
) == 0) {
146 EXPECT_TRUE(form
->password_value
.empty());
147 EXPECT_EQ(GURL(password_manager::kTestingFederationUrlSpec
),
148 form
->federation_url
);
150 EXPECT_EQ(WideToUTF16(expectation
->password_value
),
151 form
->password_value
);
152 EXPECT_TRUE(form
->federation_url
.is_empty());
155 EXPECT_TRUE(form
->blacklisted_by_user
);
157 EXPECT_EQ(expectation
->preferred
, form
->preferred
);
158 EXPECT_EQ(expectation
->ssl_valid
, form
->ssl_valid
);
159 EXPECT_DOUBLE_EQ(expectation
->creation_time
,
160 form
->date_created
.ToDoubleT());
161 base::Time created
= base::Time::FromDoubleT(expectation
->creation_time
);
163 created
+ base::TimeDelta::FromDays(
164 password_manager::kTestingDaysAfterPasswordsAreSynced
),
166 EXPECT_EQ(GURL(password_manager::kTestingIconUrlSpec
), form
->icon_url
);
170 PasswordStoreChangeList
AddChangeForForm(const PasswordForm
& form
) {
171 return PasswordStoreChangeList(
172 1, PasswordStoreChange(PasswordStoreChange::ADD
, form
));
179 class PasswordStoreMacInternalsTest
: public testing::Test
{
181 void SetUp() override
{
182 MockAppleKeychain::KeychainTestData test_data
[] = {
184 {kSecAuthenticationTypeHTMLForm
,
186 kSecProtocolTypeHTTP
,
194 // HTML form with path.
195 {kSecAuthenticationTypeHTMLForm
,
197 kSecProtocolTypeHTTP
,
205 // Secure HTML form with path.
206 {kSecAuthenticationTypeHTMLForm
,
208 kSecProtocolTypeHTTPS
,
216 // True negative item.
217 {kSecAuthenticationTypeHTMLForm
,
219 kSecProtocolTypeHTTP
,
227 // De-facto negative item, type one.
228 {kSecAuthenticationTypeHTMLForm
,
230 kSecProtocolTypeHTTP
,
235 "Password Not Stored",
238 // De-facto negative item, type two.
239 {kSecAuthenticationTypeHTMLForm
,
241 kSecProtocolTypeHTTPS
,
246 "Password Not Stored",
249 // HTTP auth basic, with port and path.
250 {kSecAuthenticationTypeHTTPBasic
,
252 kSecProtocolTypeHTTP
,
260 // HTTP auth digest, secure.
261 {kSecAuthenticationTypeHTTPDigest
,
263 kSecProtocolTypeHTTPS
,
271 // An FTP password with an invalid date, for edge-case testing.
272 {kSecAuthenticationTypeDefault
,
282 // Password for an Android application.
283 {kSecAuthenticationTypeHTMLForm
,
284 "android://hash@com.domain.some/",
285 kSecProtocolTypeHTTPS
,
295 keychain_
= new MockAppleKeychain();
297 for (unsigned int i
= 0; i
< arraysize(test_data
); ++i
) {
298 keychain_
->AddTestItem(test_data
[i
]);
302 void TearDown() override
{
303 ExpectCreatesAndFreesBalanced();
304 ExpectCreatorCodesSet();
309 // Causes a test failure unless everything returned from keychain_'s
310 // ItemCopyAttributesAndData, SearchCreateFromAttributes, and SearchCopyNext
311 // was correctly freed.
312 void ExpectCreatesAndFreesBalanced() {
313 EXPECT_EQ(0, keychain_
->UnfreedSearchCount());
314 EXPECT_EQ(0, keychain_
->UnfreedKeychainItemCount());
315 EXPECT_EQ(0, keychain_
->UnfreedAttributeDataCount());
318 // Causes a test failure unless any Keychain items added during the test have
319 // their creator code set.
320 void ExpectCreatorCodesSet() {
321 EXPECT_TRUE(keychain_
->CreatorCodesSetForAddedItems());
324 MockAppleKeychain
* keychain_
;
329 TEST_F(PasswordStoreMacInternalsTest
, TestKeychainToFormTranslation
) {
331 const PasswordForm::Scheme scheme
;
332 const char* signon_realm
;
334 const wchar_t* username
; // Set to NULL to check for a blacklist entry.
335 const wchar_t* password
;
336 const bool ssl_valid
;
337 const int creation_year
;
338 const int creation_month
;
339 const int creation_day
;
340 const int creation_hour
;
341 const int creation_minute
;
342 const int creation_second
;
345 TestExpectations expected
[] = {
346 { PasswordForm::SCHEME_HTML
, "http://some.domain.com/",
347 "http://some.domain.com/", L
"joe_user", L
"sekrit", false,
348 2002, 6, 1, 17, 15, 0 },
349 { PasswordForm::SCHEME_HTML
, "http://some.domain.com/",
350 "http://some.domain.com/insecure.html", L
"joe_user", L
"sekrit", false,
351 1999, 12, 31, 23, 59, 59 },
352 { PasswordForm::SCHEME_HTML
, "https://some.domain.com/",
353 "https://some.domain.com/secure.html", L
"secure_user", L
"password", true,
354 2010, 9, 8, 7, 6, 5 },
355 { PasswordForm::SCHEME_HTML
, "http://dont.remember.com/",
356 "http://dont.remember.com/", NULL
, NULL
, false,
357 2000, 1, 1, 0, 0, 0 },
358 { PasswordForm::SCHEME_HTML
, "http://dont.remember.com/",
359 "http://dont.remember.com/", NULL
, NULL
, false,
360 2000, 1, 1, 0, 0, 0 },
361 { PasswordForm::SCHEME_HTML
, "https://dont.remember.com/",
362 "https://dont.remember.com/", NULL
, NULL
, true,
363 2000, 1, 1, 0, 0, 0 },
364 { PasswordForm::SCHEME_BASIC
, "http://some.domain.com:4567/low_security",
365 "http://some.domain.com:4567/insecure.html", L
"basic_auth_user", L
"basic",
366 false, 1998, 03, 30, 10, 00, 00 },
367 { PasswordForm::SCHEME_DIGEST
, "https://some.domain.com/high_security",
368 "https://some.domain.com/", L
"digest_auth_user", L
"digest", true,
369 1998, 3, 30, 10, 0, 0 },
370 // This one gives us an invalid date, which we will treat as a "NULL" date
372 { PasswordForm::SCHEME_OTHER
, "http://a.server.com/",
373 "http://a.server.com/", L
"abc", L
"123", false,
374 1601, 1, 1, 0, 0, 0 },
375 { PasswordForm::SCHEME_HTML
, "android://hash@com.domain.some/",
376 "", L
"joe_user", L
"secret", true,
377 2015, 5, 15, 14, 13, 12 },
380 for (unsigned int i
= 0; i
< arraysize(expected
); ++i
) {
381 // Create our fake KeychainItemRef; see MockAppleKeychain docs.
382 SecKeychainItemRef keychain_item
=
383 reinterpret_cast<SecKeychainItemRef
>(i
+ 1);
385 bool parsed
= internal_keychain_helpers::FillPasswordFormFromKeychainItem(
386 *keychain_
, keychain_item
, &form
, true);
388 EXPECT_TRUE(parsed
) << "In iteration " << i
;
390 EXPECT_EQ(expected
[i
].scheme
, form
.scheme
) << "In iteration " << i
;
391 EXPECT_EQ(GURL(expected
[i
].origin
), form
.origin
) << "In iteration " << i
;
392 EXPECT_EQ(expected
[i
].ssl_valid
, form
.ssl_valid
) << "In iteration " << i
;
393 EXPECT_EQ(std::string(expected
[i
].signon_realm
), form
.signon_realm
)
394 << "In iteration " << i
;
395 if (expected
[i
].username
) {
396 EXPECT_EQ(WideToUTF16(expected
[i
].username
), form
.username_value
)
397 << "In iteration " << i
;
398 EXPECT_EQ(WideToUTF16(expected
[i
].password
), form
.password_value
)
399 << "In iteration " << i
;
400 EXPECT_FALSE(form
.blacklisted_by_user
) << "In iteration " << i
;
402 EXPECT_TRUE(form
.blacklisted_by_user
) << "In iteration " << i
;
404 base::Time::Exploded exploded_time
;
405 form
.date_created
.UTCExplode(&exploded_time
);
406 EXPECT_EQ(expected
[i
].creation_year
, exploded_time
.year
)
407 << "In iteration " << i
;
408 EXPECT_EQ(expected
[i
].creation_month
, exploded_time
.month
)
409 << "In iteration " << i
;
410 EXPECT_EQ(expected
[i
].creation_day
, exploded_time
.day_of_month
)
411 << "In iteration " << i
;
412 EXPECT_EQ(expected
[i
].creation_hour
, exploded_time
.hour
)
413 << "In iteration " << i
;
414 EXPECT_EQ(expected
[i
].creation_minute
, exploded_time
.minute
)
415 << "In iteration " << i
;
416 EXPECT_EQ(expected
[i
].creation_second
, exploded_time
.second
)
417 << "In iteration " << i
;
421 // Use an invalid ref, to make sure errors are reported.
422 SecKeychainItemRef keychain_item
= reinterpret_cast<SecKeychainItemRef
>(99);
424 bool parsed
= internal_keychain_helpers::FillPasswordFormFromKeychainItem(
425 *keychain_
, keychain_item
, &form
, true);
426 EXPECT_FALSE(parsed
);
430 TEST_F(PasswordStoreMacInternalsTest
, TestKeychainSearch
) {
431 struct TestDataAndExpectation
{
432 const PasswordFormData data
;
433 const size_t expected_fill_matches
;
434 const size_t expected_merge_matches
;
436 // Most fields are left blank because we don't care about them for searching.
437 /* clang-format off */
438 TestDataAndExpectation test_data
[] = {
439 // An HTML form we've seen.
440 { { PasswordForm::SCHEME_HTML
, "http://some.domain.com/",
441 NULL
, NULL
, NULL
, NULL
, NULL
, L
"joe_user", NULL
, false, false, 0 },
443 { { PasswordForm::SCHEME_HTML
, "http://some.domain.com/",
444 NULL
, NULL
, NULL
, NULL
, NULL
, L
"wrong_user", NULL
, false, false, 0 },
446 // An HTML form we haven't seen
447 { { PasswordForm::SCHEME_HTML
, "http://www.unseendomain.com/",
448 NULL
, NULL
, NULL
, NULL
, NULL
, L
"joe_user", NULL
, false, false, 0 },
450 // Basic auth that should match.
451 { { PasswordForm::SCHEME_BASIC
, "http://some.domain.com:4567/low_security",
452 NULL
, NULL
, NULL
, NULL
, NULL
, L
"basic_auth_user", NULL
, false, false,
455 // Basic auth with the wrong port.
456 { { PasswordForm::SCHEME_BASIC
, "http://some.domain.com:1111/low_security",
457 NULL
, NULL
, NULL
, NULL
, NULL
, L
"basic_auth_user", NULL
, false, false,
460 // Digest auth we've saved under https, visited with http.
461 { { PasswordForm::SCHEME_DIGEST
, "http://some.domain.com/high_security",
462 NULL
, NULL
, NULL
, NULL
, NULL
, L
"digest_auth_user", NULL
, false, false,
465 // Digest auth that should match.
466 { { PasswordForm::SCHEME_DIGEST
, "https://some.domain.com/high_security",
467 NULL
, NULL
, NULL
, NULL
, NULL
, L
"wrong_user", NULL
, false, true, 0 },
469 // Digest auth with the wrong domain.
470 { { PasswordForm::SCHEME_DIGEST
, "https://some.domain.com/other_domain",
471 NULL
, NULL
, NULL
, NULL
, NULL
, L
"digest_auth_user", NULL
, false, true,
474 // Android credentials (both legacy ones with origin, and without).
475 { { PasswordForm::SCHEME_HTML
, "android://hash@com.domain.some/",
476 "android://hash@com.domain.some/", NULL
, NULL
, NULL
, NULL
, L
"joe_user",
477 NULL
, false, true, 0 },
479 { { PasswordForm::SCHEME_HTML
, "android://hash@com.domain.some/",
480 NULL
, NULL
, NULL
, NULL
, NULL
, L
"joe_user", NULL
, false, true, 0 },
482 // Federated logins do not have a corresponding Keychain entry, and should
483 // not match the username/password stored for the same application. Note
484 // that it will match for filling, however, because that part does not know
485 // that it is a federated login.
486 { { PasswordForm::SCHEME_HTML
, "android://hash@com.domain.some/",
487 NULL
, NULL
, NULL
, NULL
, NULL
, L
"joe_user",
488 password_manager::kTestingFederatedLoginMarker
, false, true, 0 },
490 /// Garbage forms should have no matches.
491 { { PasswordForm::SCHEME_HTML
, "foo/bar/baz",
492 NULL
, NULL
, NULL
, NULL
, NULL
, NULL
, NULL
, false, false, 0 }, 0, 0 },
494 /* clang-format on */
496 MacKeychainPasswordFormAdapter
keychain_adapter(keychain_
);
497 MacKeychainPasswordFormAdapter
owned_keychain_adapter(keychain_
);
498 owned_keychain_adapter
.SetFindsOnlyOwnedItems(true);
499 for (unsigned int i
= 0; i
< arraysize(test_data
); ++i
) {
500 scoped_ptr
<PasswordForm
> query_form
=
501 CreatePasswordFormFromDataForTesting(test_data
[i
].data
);
503 // Check matches treating the form as a fill target.
504 ScopedVector
<autofill::PasswordForm
> matching_items
=
505 keychain_adapter
.PasswordsFillingForm(query_form
->signon_realm
,
507 EXPECT_EQ(test_data
[i
].expected_fill_matches
, matching_items
.size());
509 // Check matches treating the form as a merging target.
510 EXPECT_EQ(test_data
[i
].expected_merge_matches
> 0,
511 keychain_adapter
.HasPasswordsMergeableWithForm(*query_form
));
512 std::vector
<SecKeychainItemRef
> keychain_items
;
513 std::vector
<internal_keychain_helpers::ItemFormPair
> item_form_pairs
=
514 internal_keychain_helpers::
515 ExtractAllKeychainItemAttributesIntoPasswordForms(&keychain_items
,
518 internal_keychain_helpers::ExtractPasswordsMergeableWithForm(
519 *keychain_
, item_form_pairs
, *query_form
);
520 EXPECT_EQ(test_data
[i
].expected_merge_matches
, matching_items
.size());
521 STLDeleteContainerPairSecondPointers(item_form_pairs
.begin(),
522 item_form_pairs
.end());
523 for (std::vector
<SecKeychainItemRef
>::iterator i
= keychain_items
.begin();
524 i
!= keychain_items
.end(); ++i
) {
528 // None of the pre-seeded items are owned by us, so none should match an
529 // owned-passwords-only search.
530 matching_items
= owned_keychain_adapter
.PasswordsFillingForm(
531 query_form
->signon_realm
, query_form
->scheme
);
532 EXPECT_EQ(0U, matching_items
.size());
536 // Changes just the origin path of |form|.
537 static void SetPasswordFormPath(PasswordForm
* form
, const char* path
) {
538 GURL::Replacements replacement
;
539 std::string
new_value(path
);
540 replacement
.SetPathStr(new_value
);
541 form
->origin
= form
->origin
.ReplaceComponents(replacement
);
544 // Changes just the signon_realm port of |form|.
545 static void SetPasswordFormPort(PasswordForm
* form
, const char* port
) {
546 GURL::Replacements replacement
;
547 std::string
new_value(port
);
548 replacement
.SetPortStr(new_value
);
549 GURL signon_gurl
= GURL(form
->signon_realm
);
550 form
->signon_realm
= signon_gurl
.ReplaceComponents(replacement
).spec();
553 // Changes just the signon_ream auth realm of |form|.
554 static void SetPasswordFormRealm(PasswordForm
* form
, const char* realm
) {
555 GURL::Replacements replacement
;
556 std::string
new_value(realm
);
557 replacement
.SetPathStr(new_value
);
558 GURL signon_gurl
= GURL(form
->signon_realm
);
559 form
->signon_realm
= signon_gurl
.ReplaceComponents(replacement
).spec();
562 TEST_F(PasswordStoreMacInternalsTest
, TestKeychainExactSearch
) {
563 MacKeychainPasswordFormAdapter
keychain_adapter(keychain_
);
565 PasswordFormData base_form_data
[] = {
566 { PasswordForm::SCHEME_HTML
, "http://some.domain.com/",
567 "http://some.domain.com/insecure.html",
568 NULL
, NULL
, NULL
, NULL
, L
"joe_user", NULL
, true, false, 0 },
569 { PasswordForm::SCHEME_BASIC
, "http://some.domain.com:4567/low_security",
570 "http://some.domain.com:4567/insecure.html",
571 NULL
, NULL
, NULL
, NULL
, L
"basic_auth_user", NULL
, true, false, 0 },
572 { PasswordForm::SCHEME_DIGEST
, "https://some.domain.com/high_security",
573 "https://some.domain.com",
574 NULL
, NULL
, NULL
, NULL
, L
"digest_auth_user", NULL
, true, true, 0 },
577 for (unsigned int i
= 0; i
< arraysize(base_form_data
); ++i
) {
578 // Create a base form and make sure we find a match.
579 scoped_ptr
<PasswordForm
> base_form
=
580 CreatePasswordFormFromDataForTesting(base_form_data
[i
]);
581 EXPECT_TRUE(keychain_adapter
.HasPasswordsMergeableWithForm(*base_form
));
582 EXPECT_TRUE(keychain_adapter
.HasPasswordExactlyMatchingForm(*base_form
));
584 // Make sure that the matching isn't looser than it should be by checking
585 // that slightly altered forms don't match.
586 ScopedVector
<autofill::PasswordForm
> modified_forms
;
588 modified_forms
.push_back(new PasswordForm(*base_form
));
589 modified_forms
.back()->username_value
= ASCIIToUTF16("wrong_user");
591 modified_forms
.push_back(new PasswordForm(*base_form
));
592 SetPasswordFormPath(modified_forms
.back(), "elsewhere.html");
594 modified_forms
.push_back(new PasswordForm(*base_form
));
595 modified_forms
.back()->scheme
= PasswordForm::SCHEME_OTHER
;
597 modified_forms
.push_back(new PasswordForm(*base_form
));
598 SetPasswordFormPort(modified_forms
.back(), "1234");
600 modified_forms
.push_back(new PasswordForm(*base_form
));
601 modified_forms
.back()->blacklisted_by_user
= true;
603 if (base_form
->scheme
== PasswordForm::SCHEME_BASIC
||
604 base_form
->scheme
== PasswordForm::SCHEME_DIGEST
) {
605 modified_forms
.push_back(new PasswordForm(*base_form
));
606 SetPasswordFormRealm(modified_forms
.back(), "incorrect");
609 for (unsigned int j
= 0; j
< modified_forms
.size(); ++j
) {
610 bool match
= keychain_adapter
.HasPasswordExactlyMatchingForm(
612 EXPECT_FALSE(match
) << "In modified version " << j
613 << " of base form " << i
;
618 TEST_F(PasswordStoreMacInternalsTest
, TestKeychainAdd
) {
619 struct TestDataAndExpectation
{
620 PasswordFormData data
;
623 /* clang-format off */
624 TestDataAndExpectation test_data
[] = {
625 // Test a variety of scheme/port/protocol/path variations.
626 { { PasswordForm::SCHEME_HTML
, "http://web.site.com/",
627 "http://web.site.com/path/to/page.html", NULL
, NULL
, NULL
, NULL
,
628 L
"anonymous", L
"knock-knock", false, false, 0 }, true },
629 { { PasswordForm::SCHEME_HTML
, "https://web.site.com/",
630 "https://web.site.com/", NULL
, NULL
, NULL
, NULL
,
631 L
"admin", L
"p4ssw0rd", false, false, 0 }, true },
632 { { PasswordForm::SCHEME_BASIC
, "http://a.site.com:2222/therealm",
633 "http://a.site.com:2222/", NULL
, NULL
, NULL
, NULL
,
634 L
"username", L
"password", false, false, 0 }, true },
635 { { PasswordForm::SCHEME_DIGEST
, "https://digest.site.com/differentrealm",
636 "https://digest.site.com/secure.html", NULL
, NULL
, NULL
, NULL
,
637 L
"testname", L
"testpass", false, false, 0 }, true },
638 // Test that Android credentials can be stored. Also check the legacy form
639 // when |origin| was still filled with the Android URI (and not left empty).
640 { { PasswordForm::SCHEME_HTML
, "android://hash@com.example.alpha/",
641 "", NULL
, NULL
, NULL
, NULL
,
642 L
"joe_user", L
"password", false, true, 0 }, true },
643 { { PasswordForm::SCHEME_HTML
, "android://hash@com.example.beta/",
644 "android://hash@com.example.beta/", NULL
, NULL
, NULL
, NULL
,
645 L
"jane_user", L
"password2", false, true, 0 }, true },
646 // Make sure that garbage forms are rejected.
647 { { PasswordForm::SCHEME_HTML
, "gobbledygook",
648 "gobbledygook", NULL
, NULL
, NULL
, NULL
,
649 L
"anonymous", L
"knock-knock", false, false, 0 }, false },
650 // Test that failing to update a duplicate (forced using the magic failure
651 // password; see MockAppleKeychain::ItemModifyAttributesAndData) is
653 { { PasswordForm::SCHEME_HTML
, "http://some.domain.com",
654 "http://some.domain.com/insecure.html", NULL
, NULL
, NULL
, NULL
,
655 L
"joe_user", L
"fail_me", false, false, 0 }, false },
657 /* clang-format on */
659 MacKeychainPasswordFormAdapter
owned_keychain_adapter(keychain_
);
660 owned_keychain_adapter
.SetFindsOnlyOwnedItems(true);
662 for (unsigned int i
= 0; i
< arraysize(test_data
); ++i
) {
663 scoped_ptr
<PasswordForm
> in_form
=
664 CreatePasswordFormFromDataForTesting(test_data
[i
].data
);
665 bool add_succeeded
= owned_keychain_adapter
.AddPassword(*in_form
);
666 EXPECT_EQ(test_data
[i
].should_succeed
, add_succeeded
);
668 EXPECT_TRUE(owned_keychain_adapter
.HasPasswordsMergeableWithForm(
670 EXPECT_TRUE(owned_keychain_adapter
.HasPasswordExactlyMatchingForm(
675 // Test that adding duplicate item updates the existing item.
676 // TODO(engedy): Add a test to verify that updating Android credentials work.
677 // See: https://crbug.com/476851.
679 PasswordFormData data
= {
680 PasswordForm::SCHEME_HTML
, "http://some.domain.com",
681 "http://some.domain.com/insecure.html", NULL
,
682 NULL
, NULL
, NULL
, L
"joe_user", L
"updated_password", false, false, 0
684 scoped_ptr
<PasswordForm
> update_form
=
685 CreatePasswordFormFromDataForTesting(data
);
686 MacKeychainPasswordFormAdapter
keychain_adapter(keychain_
);
687 EXPECT_TRUE(keychain_adapter
.AddPassword(*update_form
));
688 SecKeychainItemRef keychain_item
= reinterpret_cast<SecKeychainItemRef
>(2);
689 PasswordForm stored_form
;
690 internal_keychain_helpers::FillPasswordFormFromKeychainItem(*keychain_
,
694 EXPECT_EQ(update_form
->password_value
, stored_form
.password_value
);
698 TEST_F(PasswordStoreMacInternalsTest
, TestKeychainRemove
) {
699 struct TestDataAndExpectation
{
700 PasswordFormData data
;
703 /* clang-format off */
704 TestDataAndExpectation test_data
[] = {
705 // Test deletion of an item that we add.
706 { { PasswordForm::SCHEME_HTML
, "http://web.site.com/",
707 "http://web.site.com/path/to/page.html", NULL
, NULL
, NULL
, NULL
,
708 L
"anonymous", L
"knock-knock", false, false, 0 }, true },
709 // Test that Android credentials can be removed. Also check the legacy case
710 // when |origin| was still filled with the Android URI (and not left empty).
711 { { PasswordForm::SCHEME_HTML
, "android://hash@com.example.alpha/",
712 "", NULL
, NULL
, NULL
, NULL
,
713 L
"joe_user", L
"secret", false, true, 0 }, true },
714 { { PasswordForm::SCHEME_HTML
, "android://hash@com.example.beta/",
715 "android://hash@com.example.beta/", NULL
, NULL
, NULL
, NULL
,
716 L
"jane_user", L
"secret", false, true, 0 }, true },
717 // Make sure we don't delete items we don't own.
718 { { PasswordForm::SCHEME_HTML
, "http://some.domain.com/",
719 "http://some.domain.com/insecure.html", NULL
, NULL
, NULL
, NULL
,
720 L
"joe_user", NULL
, true, false, 0 }, false },
722 /* clang-format on */
724 MacKeychainPasswordFormAdapter
owned_keychain_adapter(keychain_
);
725 owned_keychain_adapter
.SetFindsOnlyOwnedItems(true);
727 // Add our test items (except the last one) so that we can delete them.
728 for (unsigned int i
= 0; i
+ 1 < arraysize(test_data
); ++i
) {
729 scoped_ptr
<PasswordForm
> add_form
=
730 CreatePasswordFormFromDataForTesting(test_data
[i
].data
);
731 EXPECT_TRUE(owned_keychain_adapter
.AddPassword(*add_form
));
734 for (unsigned int i
= 0; i
< arraysize(test_data
); ++i
) {
735 scoped_ptr
<PasswordForm
> form
=
736 CreatePasswordFormFromDataForTesting(test_data
[i
].data
);
737 EXPECT_EQ(test_data
[i
].should_succeed
,
738 owned_keychain_adapter
.RemovePassword(*form
));
740 MacKeychainPasswordFormAdapter
keychain_adapter(keychain_
);
741 bool match
= keychain_adapter
.HasPasswordExactlyMatchingForm(*form
);
742 EXPECT_EQ(test_data
[i
].should_succeed
, !match
);
746 TEST_F(PasswordStoreMacInternalsTest
, TestFormMatch
) {
747 PasswordForm base_form
;
748 base_form
.signon_realm
= std::string("http://some.domain.com/");
749 base_form
.origin
= GURL("http://some.domain.com/page.html");
750 base_form
.username_value
= ASCIIToUTF16("joe_user");
753 // Check that everything unimportant can be changed.
754 PasswordForm
different_form(base_form
);
755 different_form
.username_element
= ASCIIToUTF16("username");
756 different_form
.submit_element
= ASCIIToUTF16("submit");
757 different_form
.username_element
= ASCIIToUTF16("password");
758 different_form
.password_value
= ASCIIToUTF16("sekrit");
759 different_form
.action
= GURL("http://some.domain.com/action.cgi");
760 different_form
.ssl_valid
= true;
761 different_form
.preferred
= true;
762 different_form
.date_created
= base::Time::Now();
764 FormsMatchForMerge(base_form
, different_form
, STRICT_FORM_MATCH
));
766 // Check that path differences don't prevent a match.
767 base_form
.origin
= GURL("http://some.domain.com/other_page.html");
769 FormsMatchForMerge(base_form
, different_form
, STRICT_FORM_MATCH
));
772 // Check that any one primary key changing is enough to prevent matching.
774 PasswordForm
different_form(base_form
);
775 different_form
.scheme
= PasswordForm::SCHEME_DIGEST
;
777 FormsMatchForMerge(base_form
, different_form
, STRICT_FORM_MATCH
));
780 PasswordForm
different_form(base_form
);
781 different_form
.signon_realm
= std::string("http://some.domain.com:8080/");
783 FormsMatchForMerge(base_form
, different_form
, STRICT_FORM_MATCH
));
786 PasswordForm
different_form(base_form
);
787 different_form
.username_value
= ASCIIToUTF16("john.doe");
789 FormsMatchForMerge(base_form
, different_form
, STRICT_FORM_MATCH
));
792 PasswordForm
different_form(base_form
);
793 different_form
.blacklisted_by_user
= true;
795 FormsMatchForMerge(base_form
, different_form
, STRICT_FORM_MATCH
));
798 // Blacklist forms should *never* match for merging, even when identical
799 // (and certainly not when only one is a blacklist entry).
801 PasswordForm
form_a(base_form
);
802 form_a
.blacklisted_by_user
= true;
803 PasswordForm
form_b(form_a
);
804 EXPECT_FALSE(FormsMatchForMerge(form_a
, form_b
, STRICT_FORM_MATCH
));
807 // Federated login forms should never match for merging either.
809 PasswordForm
form_b(base_form
);
810 form_b
.federation_url
= GURL(password_manager::kTestingFederationUrlSpec
);
811 EXPECT_FALSE(FormsMatchForMerge(base_form
, form_b
, STRICT_FORM_MATCH
));
812 EXPECT_FALSE(FormsMatchForMerge(form_b
, base_form
, STRICT_FORM_MATCH
));
813 EXPECT_FALSE(FormsMatchForMerge(form_b
, form_b
, STRICT_FORM_MATCH
));
817 TEST_F(PasswordStoreMacInternalsTest
, TestFormMerge
) {
818 // Set up a bunch of test data to use in varying combinations.
819 /* clang-format off */
820 PasswordFormData keychain_user_1
=
821 { PasswordForm::SCHEME_HTML
, "http://some.domain.com/",
822 "http://some.domain.com/", "", L
"", L
"", L
"", L
"joe_user", L
"sekrit",
823 false, false, 1010101010 };
824 PasswordFormData keychain_user_1_with_path
=
825 { PasswordForm::SCHEME_HTML
, "http://some.domain.com/",
826 "http://some.domain.com/page.html",
827 "", L
"", L
"", L
"", L
"joe_user", L
"otherpassword",
828 false, false, 1010101010 };
829 PasswordFormData keychain_user_2
=
830 { PasswordForm::SCHEME_HTML
, "http://some.domain.com/",
831 "http://some.domain.com/", "", L
"", L
"", L
"", L
"john.doe", L
"sesame",
832 false, false, 958739876 };
833 PasswordFormData keychain_blacklist
=
834 { PasswordForm::SCHEME_HTML
, "http://some.domain.com/",
835 "http://some.domain.com/", "", L
"", L
"", L
"", NULL
, NULL
,
836 false, false, 1010101010 };
837 PasswordFormData keychain_android
=
838 { PasswordForm::SCHEME_HTML
, "android://hash@com.domain.some/",
839 "", "", L
"", L
"", L
"", L
"joe_user", L
"secret",
840 false, true, 1234567890 };
842 PasswordFormData db_user_1
=
843 { PasswordForm::SCHEME_HTML
, "http://some.domain.com/",
844 "http://some.domain.com/", "http://some.domain.com/action.cgi",
845 L
"submit", L
"username", L
"password", L
"joe_user", L
"",
846 true, false, 1212121212 };
847 PasswordFormData db_user_1_with_path
=
848 { PasswordForm::SCHEME_HTML
, "http://some.domain.com/",
849 "http://some.domain.com/page.html",
850 "http://some.domain.com/handlepage.cgi",
851 L
"submit", L
"username", L
"password", L
"joe_user", L
"",
852 true, false, 1234567890 };
853 PasswordFormData db_user_3_with_path
=
854 { PasswordForm::SCHEME_HTML
, "http://some.domain.com/",
855 "http://some.domain.com/page.html",
856 "http://some.domain.com/handlepage.cgi",
857 L
"submit", L
"username", L
"password", L
"second-account", L
"",
858 true, false, 1240000000 };
859 PasswordFormData database_blacklist_with_path
=
860 { PasswordForm::SCHEME_HTML
, "http://some.domain.com/",
861 "http://some.domain.com/path.html", "http://some.domain.com/action.cgi",
862 L
"submit", L
"username", L
"password", NULL
, NULL
,
863 true, false, 1212121212 };
864 PasswordFormData db_android
=
865 { PasswordForm::SCHEME_HTML
, "android://hash@com.domain.some/",
866 "android://hash@com.domain.some/", "", L
"", L
"", L
"", L
"joe_user", L
"",
867 false, true, 1234567890 };
868 PasswordFormData db_federated
=
869 { PasswordForm::SCHEME_HTML
, "android://hash@com.domain.some/",
870 "android://hash@com.domain.some/", "", L
"", L
"", L
"", L
"joe_user",
871 password_manager::kTestingFederatedLoginMarker
,
872 false, true, 3434343434 };
874 PasswordFormData merged_user_1
=
875 { PasswordForm::SCHEME_HTML
, "http://some.domain.com/",
876 "http://some.domain.com/", "http://some.domain.com/action.cgi",
877 L
"submit", L
"username", L
"password", L
"joe_user", L
"sekrit",
878 true, false, 1212121212 };
879 PasswordFormData merged_user_1_with_db_path
=
880 { PasswordForm::SCHEME_HTML
, "http://some.domain.com/",
881 "http://some.domain.com/page.html",
882 "http://some.domain.com/handlepage.cgi",
883 L
"submit", L
"username", L
"password", L
"joe_user", L
"sekrit",
884 true, false, 1234567890 };
885 PasswordFormData merged_user_1_with_both_paths
=
886 { PasswordForm::SCHEME_HTML
, "http://some.domain.com/",
887 "http://some.domain.com/page.html",
888 "http://some.domain.com/handlepage.cgi",
889 L
"submit", L
"username", L
"password", L
"joe_user", L
"otherpassword",
890 true, false, 1234567890 };
891 PasswordFormData merged_android
=
892 { PasswordForm::SCHEME_HTML
, "android://hash@com.domain.some/",
893 "android://hash@com.domain.some/", "", L
"", L
"", L
"", L
"joe_user",
894 L
"secret", false, true, 1234567890 };
895 /* clang-format on */
897 // Build up the big multi-dimensional array of data sets that will actually
898 // drive the test. Use vectors rather than arrays so that initialization is
906 MERGE_IO_ARRAY_COUNT
// termination marker
908 const unsigned int kTestCount
= 5;
909 std::vector
< std::vector
< std::vector
<PasswordFormData
*> > > test_data(
910 MERGE_IO_ARRAY_COUNT
, std::vector
< std::vector
<PasswordFormData
*> >(
911 kTestCount
, std::vector
<PasswordFormData
*>()));
912 unsigned int current_test
= 0;
914 // Test a merge with a few accounts in both systems, with partial overlap.
915 CHECK(current_test
< kTestCount
);
916 test_data
[KEYCHAIN_INPUT
][current_test
].push_back(&keychain_user_1
);
917 test_data
[KEYCHAIN_INPUT
][current_test
].push_back(&keychain_user_2
);
918 test_data
[DATABASE_INPUT
][current_test
].push_back(&db_user_1
);
919 test_data
[DATABASE_INPUT
][current_test
].push_back(&db_user_1_with_path
);
920 test_data
[DATABASE_INPUT
][current_test
].push_back(&db_user_3_with_path
);
921 test_data
[MERGE_OUTPUT
][current_test
].push_back(&merged_user_1
);
922 test_data
[MERGE_OUTPUT
][current_test
].push_back(&merged_user_1_with_db_path
);
923 test_data
[KEYCHAIN_OUTPUT
][current_test
].push_back(&keychain_user_2
);
924 test_data
[DATABASE_OUTPUT
][current_test
].push_back(&db_user_3_with_path
);
926 // Test a merge where Chrome has a blacklist entry, and the keychain has
929 CHECK(current_test
< kTestCount
);
930 test_data
[KEYCHAIN_INPUT
][current_test
].push_back(&keychain_user_1
);
931 test_data
[DATABASE_INPUT
][current_test
].push_back(
932 &database_blacklist_with_path
);
933 // We expect both to be present because a blacklist could be specific to a
934 // subpath, and we want access to the password on other paths.
935 test_data
[MERGE_OUTPUT
][current_test
].push_back(
936 &database_blacklist_with_path
);
937 test_data
[KEYCHAIN_OUTPUT
][current_test
].push_back(&keychain_user_1
);
939 // Test a merge where Chrome has an account, and Keychain has a blacklist
940 // (from another browser) and the Chrome password data.
942 CHECK(current_test
< kTestCount
);
943 test_data
[KEYCHAIN_INPUT
][current_test
].push_back(&keychain_blacklist
);
944 test_data
[KEYCHAIN_INPUT
][current_test
].push_back(&keychain_user_1
);
945 test_data
[DATABASE_INPUT
][current_test
].push_back(&db_user_1
);
946 test_data
[MERGE_OUTPUT
][current_test
].push_back(&merged_user_1
);
947 test_data
[KEYCHAIN_OUTPUT
][current_test
].push_back(&keychain_blacklist
);
949 // Test that matches are done using exact path when possible.
951 CHECK(current_test
< kTestCount
);
952 test_data
[KEYCHAIN_INPUT
][current_test
].push_back(&keychain_user_1
);
953 test_data
[KEYCHAIN_INPUT
][current_test
].push_back(&keychain_user_1_with_path
);
954 test_data
[DATABASE_INPUT
][current_test
].push_back(&db_user_1
);
955 test_data
[DATABASE_INPUT
][current_test
].push_back(&db_user_1_with_path
);
956 test_data
[MERGE_OUTPUT
][current_test
].push_back(&merged_user_1
);
957 test_data
[MERGE_OUTPUT
][current_test
].push_back(
958 &merged_user_1_with_both_paths
);
960 // Test that Android credentails are matched correctly and that federated
961 // credentials are not tried to be matched with a Keychain item.
963 CHECK(current_test
< kTestCount
);
964 test_data
[KEYCHAIN_INPUT
][current_test
].push_back(&keychain_android
);
965 test_data
[DATABASE_INPUT
][current_test
].push_back(&db_federated
);
966 test_data
[DATABASE_INPUT
][current_test
].push_back(&db_android
);
967 test_data
[MERGE_OUTPUT
][current_test
].push_back(&db_federated
);
968 test_data
[MERGE_OUTPUT
][current_test
].push_back(&merged_android
);
970 for (unsigned int test_case
= 0; test_case
<= current_test
; ++test_case
) {
971 ScopedVector
<autofill::PasswordForm
> keychain_forms
;
972 for (std::vector
<PasswordFormData
*>::iterator i
=
973 test_data
[KEYCHAIN_INPUT
][test_case
].begin();
974 i
!= test_data
[KEYCHAIN_INPUT
][test_case
].end(); ++i
) {
975 keychain_forms
.push_back(
976 CreatePasswordFormFromDataForTesting(*(*i
)).release());
978 ScopedVector
<autofill::PasswordForm
> database_forms
;
979 for (std::vector
<PasswordFormData
*>::iterator i
=
980 test_data
[DATABASE_INPUT
][test_case
].begin();
981 i
!= test_data
[DATABASE_INPUT
][test_case
].end(); ++i
) {
982 database_forms
.push_back(
983 CreatePasswordFormFromDataForTesting(*(*i
)).release());
986 ScopedVector
<autofill::PasswordForm
> merged_forms
;
987 internal_keychain_helpers::MergePasswordForms(&keychain_forms
,
991 CHECK_FORMS(keychain_forms
.get(), test_data
[KEYCHAIN_OUTPUT
][test_case
],
993 CHECK_FORMS(database_forms
.get(), test_data
[DATABASE_OUTPUT
][test_case
],
995 CHECK_FORMS(merged_forms
.get(), test_data
[MERGE_OUTPUT
][test_case
],
1000 TEST_F(PasswordStoreMacInternalsTest
, TestPasswordBulkLookup
) {
1001 PasswordFormData db_data
[] = {
1002 { PasswordForm::SCHEME_HTML
, "http://some.domain.com/",
1003 "http://some.domain.com/", "http://some.domain.com/action.cgi",
1004 L
"submit", L
"username", L
"password", L
"joe_user", L
"",
1005 true, false, 1212121212 },
1006 { PasswordForm::SCHEME_HTML
, "http://some.domain.com/",
1007 "http://some.domain.com/page.html",
1008 "http://some.domain.com/handlepage.cgi",
1009 L
"submit", L
"username", L
"password", L
"joe_user", L
"",
1010 true, false, 1234567890 },
1011 { PasswordForm::SCHEME_HTML
, "http://some.domain.com/",
1012 "http://some.domain.com/page.html",
1013 "http://some.domain.com/handlepage.cgi",
1014 L
"submit", L
"username", L
"password", L
"second-account", L
"",
1015 true, false, 1240000000 },
1016 { PasswordForm::SCHEME_HTML
, "http://dont.remember.com/",
1017 "http://dont.remember.com/",
1018 "http://dont.remember.com/handlepage.cgi",
1019 L
"submit", L
"username", L
"password", L
"joe_user", L
"",
1020 true, false, 1240000000 },
1021 { PasswordForm::SCHEME_HTML
, "http://some.domain.com/",
1022 "http://some.domain.com/path.html", "http://some.domain.com/action.cgi",
1023 L
"submit", L
"username", L
"password", NULL
, NULL
,
1024 true, false, 1212121212 },
1026 ScopedVector
<autofill::PasswordForm
> database_forms
;
1027 for (unsigned int i
= 0; i
< arraysize(db_data
); ++i
) {
1028 database_forms
.push_back(
1029 CreatePasswordFormFromDataForTesting(db_data
[i
]).release());
1031 ScopedVector
<autofill::PasswordForm
> merged_forms
;
1032 internal_keychain_helpers::GetPasswordsForForms(*keychain_
, &database_forms
,
1034 EXPECT_EQ(2U, database_forms
.size());
1035 ASSERT_EQ(3U, merged_forms
.size());
1036 EXPECT_EQ(ASCIIToUTF16("sekrit"), merged_forms
[0]->password_value
);
1037 EXPECT_EQ(ASCIIToUTF16("sekrit"), merged_forms
[1]->password_value
);
1038 EXPECT_TRUE(merged_forms
[2]->blacklisted_by_user
);
1041 TEST_F(PasswordStoreMacInternalsTest
, TestBlacklistedFiltering
) {
1042 PasswordFormData db_data
[] = {
1043 { PasswordForm::SCHEME_HTML
, "http://dont.remember.com/",
1044 "http://dont.remember.com/",
1045 "http://dont.remember.com/handlepage.cgi",
1046 L
"submit", L
"username", L
"password", L
"joe_user", L
"non_empty_password",
1047 true, false, 1240000000 },
1048 { PasswordForm::SCHEME_HTML
, "https://dont.remember.com/",
1049 "https://dont.remember.com/",
1050 "https://dont.remember.com/handlepage_secure.cgi",
1051 L
"submit", L
"username", L
"password", L
"joe_user", L
"non_empty_password",
1052 true, false, 1240000000 },
1054 ScopedVector
<autofill::PasswordForm
> database_forms
;
1055 for (unsigned int i
= 0; i
< arraysize(db_data
); ++i
) {
1056 database_forms
.push_back(
1057 CreatePasswordFormFromDataForTesting(db_data
[i
]).release());
1059 ScopedVector
<autofill::PasswordForm
> merged_forms
;
1060 internal_keychain_helpers::GetPasswordsForForms(*keychain_
, &database_forms
,
1062 EXPECT_EQ(2U, database_forms
.size());
1063 ASSERT_EQ(0U, merged_forms
.size());
1066 TEST_F(PasswordStoreMacInternalsTest
, TestFillPasswordFormFromKeychainItem
) {
1067 // When |extract_password_data| is false, the password field must be empty,
1068 // and |blacklisted_by_user| must be false.
1069 SecKeychainItemRef keychain_item
= reinterpret_cast<SecKeychainItemRef
>(1);
1070 PasswordForm form_without_extracted_password
;
1071 bool parsed
= internal_keychain_helpers::FillPasswordFormFromKeychainItem(
1074 &form_without_extracted_password
,
1075 false); // Do not extract password.
1076 EXPECT_TRUE(parsed
);
1077 ASSERT_TRUE(form_without_extracted_password
.password_value
.empty());
1078 ASSERT_FALSE(form_without_extracted_password
.blacklisted_by_user
);
1080 // When |extract_password_data| is true and the keychain entry has a non-empty
1081 // password, the password field must be non-empty, and the value of
1082 // |blacklisted_by_user| must be false.
1083 keychain_item
= reinterpret_cast<SecKeychainItemRef
>(1);
1084 PasswordForm form_with_extracted_password
;
1085 parsed
= internal_keychain_helpers::FillPasswordFormFromKeychainItem(
1088 &form_with_extracted_password
,
1089 true); // Extract password.
1090 EXPECT_TRUE(parsed
);
1091 ASSERT_EQ(ASCIIToUTF16("sekrit"),
1092 form_with_extracted_password
.password_value
);
1093 ASSERT_FALSE(form_with_extracted_password
.blacklisted_by_user
);
1095 // When |extract_password_data| is true and the keychain entry has an empty
1096 // username and password (""), the password field must be empty, and the value
1097 // of |blacklisted_by_user| must be true.
1098 keychain_item
= reinterpret_cast<SecKeychainItemRef
>(4);
1099 PasswordForm negative_form
;
1100 parsed
= internal_keychain_helpers::FillPasswordFormFromKeychainItem(
1104 true); // Extract password.
1105 EXPECT_TRUE(parsed
);
1106 ASSERT_TRUE(negative_form
.username_value
.empty());
1107 ASSERT_TRUE(negative_form
.password_value
.empty());
1108 ASSERT_TRUE(negative_form
.blacklisted_by_user
);
1110 // When |extract_password_data| is true and the keychain entry has an empty
1111 // password (""), the password field must be empty (""), and the value of
1112 // |blacklisted_by_user| must be true.
1113 keychain_item
= reinterpret_cast<SecKeychainItemRef
>(5);
1114 PasswordForm form_with_empty_password_a
;
1115 parsed
= internal_keychain_helpers::FillPasswordFormFromKeychainItem(
1118 &form_with_empty_password_a
,
1119 true); // Extract password.
1120 EXPECT_TRUE(parsed
);
1121 ASSERT_TRUE(form_with_empty_password_a
.password_value
.empty());
1122 ASSERT_TRUE(form_with_empty_password_a
.blacklisted_by_user
);
1124 // When |extract_password_data| is true and the keychain entry has a single
1125 // space password (" "), the password field must be a single space (" "), and
1126 // the value of |blacklisted_by_user| must be true.
1127 keychain_item
= reinterpret_cast<SecKeychainItemRef
>(6);
1128 PasswordForm form_with_empty_password_b
;
1129 parsed
= internal_keychain_helpers::FillPasswordFormFromKeychainItem(
1132 &form_with_empty_password_b
,
1133 true); // Extract password.
1134 EXPECT_TRUE(parsed
);
1135 ASSERT_EQ(ASCIIToUTF16(" "),
1136 form_with_empty_password_b
.password_value
);
1137 ASSERT_TRUE(form_with_empty_password_b
.blacklisted_by_user
);
1140 TEST_F(PasswordStoreMacInternalsTest
, TestPasswordGetAll
) {
1141 MacKeychainPasswordFormAdapter
keychain_adapter(keychain_
);
1142 MacKeychainPasswordFormAdapter
owned_keychain_adapter(keychain_
);
1143 owned_keychain_adapter
.SetFindsOnlyOwnedItems(true);
1145 // Add a few passwords of various types so that we own some.
1146 PasswordFormData owned_password_data
[] = {
1147 { PasswordForm::SCHEME_HTML
, "http://web.site.com/",
1148 "http://web.site.com/path/to/page.html", NULL
, NULL
, NULL
, NULL
,
1149 L
"anonymous", L
"knock-knock", false, false, 0 },
1150 { PasswordForm::SCHEME_BASIC
, "http://a.site.com:2222/therealm",
1151 "http://a.site.com:2222/", NULL
, NULL
, NULL
, NULL
,
1152 L
"username", L
"password", false, false, 0 },
1153 { PasswordForm::SCHEME_DIGEST
, "https://digest.site.com/differentrealm",
1154 "https://digest.site.com/secure.html", NULL
, NULL
, NULL
, NULL
,
1155 L
"testname", L
"testpass", false, false, 0 },
1157 for (unsigned int i
= 0; i
< arraysize(owned_password_data
); ++i
) {
1158 scoped_ptr
<PasswordForm
> form
=
1159 CreatePasswordFormFromDataForTesting(owned_password_data
[i
]);
1160 owned_keychain_adapter
.AddPassword(*form
);
1163 ScopedVector
<autofill::PasswordForm
> all_passwords
=
1164 keychain_adapter
.GetAllPasswordFormPasswords();
1165 EXPECT_EQ(9 + arraysize(owned_password_data
), all_passwords
.size());
1167 ScopedVector
<autofill::PasswordForm
> owned_passwords
=
1168 owned_keychain_adapter
.GetAllPasswordFormPasswords();
1169 EXPECT_EQ(arraysize(owned_password_data
), owned_passwords
.size());
1174 class PasswordStoreMacTest
: public testing::Test
{
1176 PasswordStoreMacTest() : ui_thread_(BrowserThread::UI
, &message_loop_
) {}
1178 void SetUp() override
{
1179 ASSERT_TRUE(db_dir_
.CreateUniqueTempDir());
1180 histogram_tester_
.reset(new base::HistogramTester
);
1182 // Ensure that LoginDatabase will use the mock keychain if it needs to
1183 // encrypt/decrypt a password.
1184 OSCrypt::UseMockKeychain(true);
1186 new password_manager::LoginDatabase(test_login_db_file_path()));
1187 thread_
.reset(new base::Thread("Chrome_PasswordStore_Thread"));
1188 ASSERT_TRUE(thread_
->Start());
1189 ASSERT_TRUE(thread_
->task_runner()->PostTask(
1190 FROM_HERE
, base::Bind(&PasswordStoreMacTest::InitLoginDatabase
,
1191 base::Unretained(login_db_
.get()))));
1192 CreateAndInitPasswordStore(login_db_
.get());
1193 // Make sure deferred initialization is performed before some tests start
1194 // accessing the |login_db| directly.
1195 FinishAsyncProcessing();
1198 void TearDown() override
{
1199 ClosePasswordStore();
1202 // Whatever a test did, PasswordStoreMac stores only empty password values
1203 // in LoginDatabase. The empty valus do not require encryption and therefore
1204 // OSCrypt shouldn't call the Keychain. The histogram doesn't cover the
1205 // internet passwords.
1206 if (histogram_tester_
) {
1207 scoped_ptr
<base::HistogramSamples
> samples
=
1208 histogram_tester_
->GetHistogramSamplesSinceCreation(
1209 "OSX.Keychain.Access");
1210 EXPECT_TRUE(!samples
|| samples
->TotalCount() == 0);
1214 static void InitLoginDatabase(password_manager::LoginDatabase
* login_db
) {
1215 ASSERT_TRUE(login_db
->Init());
1218 void CreateAndInitPasswordStore(password_manager::LoginDatabase
* login_db
) {
1219 store_
= new PasswordStoreMac(
1220 base::ThreadTaskRunnerHandle::Get(), nullptr,
1221 make_scoped_ptr
<AppleKeychain
>(new MockAppleKeychain
));
1222 ASSERT_TRUE(thread_
->task_runner()->PostTask(
1223 FROM_HERE
, base::Bind(&PasswordStoreMac::InitWithTaskRunner
, store_
,
1224 thread_
->task_runner())));
1226 ASSERT_TRUE(thread_
->task_runner()->PostTask(
1227 FROM_HERE
, base::Bind(&PasswordStoreMac::set_login_metadata_db
, store_
,
1228 base::Unretained(login_db
))));
1231 void ClosePasswordStore() {
1239 // Verifies that the given |form| can be properly stored so that it can be
1240 // retrieved by FillMatchingLogins() and GetAutofillableLogins(), and then it
1241 // can be properly removed.
1242 void VerifyCredentialLifecycle(const PasswordForm
& form
) {
1243 // Run everything twice to make sure no garbage is left behind that would
1244 // prevent storing the form a second time.
1245 for (size_t iteration
= 0; iteration
< 2; ++iteration
) {
1246 SCOPED_TRACE(testing::Message("Iteration: ") << iteration
);
1248 MockPasswordStoreConsumer mock_consumer
;
1249 EXPECT_CALL(mock_consumer
, OnGetPasswordStoreResultsConstRef(IsEmpty()))
1250 .WillOnce(QuitUIMessageLoop());
1251 store()->GetAutofillableLogins(&mock_consumer
);
1252 base::MessageLoop::current()->Run();
1253 ::testing::Mock::VerifyAndClearExpectations(&mock_consumer
);
1255 store()->AddLogin(form
);
1256 FinishAsyncProcessing();
1258 PasswordForm returned_form
;
1259 EXPECT_CALL(mock_consumer
, OnGetPasswordStoreResultsConstRef(SizeIs(1u)))
1261 DoAll(SaveACopyOfFirstForm(&returned_form
), QuitUIMessageLoop()));
1263 // The query operations will also do some housekeeping: they will remove
1264 // dangling credentials in the LoginDatabase without a matching Keychain
1265 // item when one is expected. If the logic that stores the Keychain item
1266 // is incorrect, this will wipe the newly added form before the second
1268 store()->GetAutofillableLogins(&mock_consumer
);
1269 base::MessageLoop::current()->Run();
1270 ::testing::Mock::VerifyAndClearExpectations(&mock_consumer
);
1271 EXPECT_EQ(form
, returned_form
);
1273 PasswordForm query_form
= form
;
1274 query_form
.password_value
.clear();
1275 query_form
.username_value
.clear();
1276 EXPECT_CALL(mock_consumer
, OnGetPasswordStoreResultsConstRef(SizeIs(1u)))
1278 DoAll(SaveACopyOfFirstForm(&returned_form
), QuitUIMessageLoop()));
1279 store()->GetLogins(query_form
, PasswordStore::ALLOW_PROMPT
,
1281 base::MessageLoop::current()->Run();
1282 ::testing::Mock::VerifyAndClearExpectations(&mock_consumer
);
1283 EXPECT_EQ(form
, returned_form
);
1285 store()->RemoveLogin(form
);
1289 base::FilePath
test_login_db_file_path() const {
1290 return db_dir_
.path().Append(FILE_PATH_LITERAL("login.db"));
1293 password_manager::LoginDatabase
* login_db() const {
1294 return store_
->login_metadata_db();
1297 MockAppleKeychain
* keychain() {
1298 return static_cast<MockAppleKeychain
*>(store_
->keychain());
1301 void FinishAsyncProcessing() {
1302 scoped_refptr
<content::MessageLoopRunner
> runner
=
1303 new content::MessageLoopRunner
;
1304 ASSERT_TRUE(thread_
->task_runner()->PostTaskAndReply(
1305 FROM_HERE
, base::Bind(&Noop
), runner
->QuitClosure()));
1309 PasswordStoreMac
* store() { return store_
.get(); }
1312 base::MessageLoopForUI message_loop_
;
1313 content::TestBrowserThread ui_thread_
;
1314 // Thread that the synchronous methods are run on.
1315 scoped_ptr
<base::Thread
> thread_
;
1317 base::ScopedTempDir db_dir_
;
1318 scoped_ptr
<password_manager::LoginDatabase
> login_db_
;
1319 scoped_refptr
<PasswordStoreMac
> store_
;
1320 scoped_ptr
<base::HistogramTester
> histogram_tester_
;
1323 TEST_F(PasswordStoreMacTest
, TestStoreUpdate
) {
1324 // Insert a password into both the database and the keychain.
1325 // This is done manually, rather than through store_->AddLogin, because the
1326 // Mock Keychain isn't smart enough to be able to support update generically,
1327 // so some.domain.com triggers special handling to test it that make inserting
1329 PasswordFormData joint_data
= {
1330 PasswordForm::SCHEME_HTML
, "http://some.domain.com/",
1331 "http://some.domain.com/insecure.html", "login.cgi",
1332 L
"username", L
"password", L
"submit", L
"joe_user", L
"sekrit", true, false, 1
1334 scoped_ptr
<PasswordForm
> joint_form
=
1335 CreatePasswordFormFromDataForTesting(joint_data
);
1336 EXPECT_EQ(AddChangeForForm(*joint_form
), login_db()->AddLogin(*joint_form
));
1337 MockAppleKeychain::KeychainTestData joint_keychain_data
= {
1338 kSecAuthenticationTypeHTMLForm
, "some.domain.com",
1339 kSecProtocolTypeHTTP
, "/insecure.html", 0, NULL
, "20020601171500Z",
1340 "joe_user", "sekrit", false };
1341 keychain()->AddTestItem(joint_keychain_data
);
1343 // Insert a password into the keychain only.
1344 MockAppleKeychain::KeychainTestData keychain_only_data
= {
1345 kSecAuthenticationTypeHTMLForm
, "keychain.only.com",
1346 kSecProtocolTypeHTTP
, NULL
, 0, NULL
, "20020601171500Z",
1347 "keychain", "only", false
1349 keychain()->AddTestItem(keychain_only_data
);
1352 PasswordFormData form_data
;
1353 const char* password
; // NULL indicates no entry should be present.
1356 // Make a series of update calls.
1357 UpdateData updates
[] = {
1358 // Update the keychain+db passwords (the normal password update case).
1359 { { PasswordForm::SCHEME_HTML
, "http://some.domain.com/",
1360 "http://some.domain.com/insecure.html", "login.cgi",
1361 L
"username", L
"password", L
"submit", L
"joe_user", L
"53krit",
1365 // Update the keychain-only password; this simulates the initial use of a
1366 // password stored by another browsers.
1367 { { PasswordForm::SCHEME_HTML
, "http://keychain.only.com/",
1368 "http://keychain.only.com/login.html", "login.cgi",
1369 L
"username", L
"password", L
"submit", L
"keychain", L
"only",
1373 // Update a password that doesn't exist in either location. This tests the
1374 // case where a form is filled, then the stored login is removed, then the
1375 // form is submitted.
1376 { { PasswordForm::SCHEME_HTML
, "http://different.com/",
1377 "http://different.com/index.html", "login.cgi",
1378 L
"username", L
"password", L
"submit", L
"abc", L
"123",
1383 for (unsigned int i
= 0; i
< arraysize(updates
); ++i
) {
1384 scoped_ptr
<PasswordForm
> form
=
1385 CreatePasswordFormFromDataForTesting(updates
[i
].form_data
);
1386 store_
->UpdateLogin(*form
);
1389 FinishAsyncProcessing();
1391 MacKeychainPasswordFormAdapter
keychain_adapter(keychain());
1392 for (unsigned int i
= 0; i
< arraysize(updates
); ++i
) {
1393 scoped_ptr
<PasswordForm
> query_form
=
1394 CreatePasswordFormFromDataForTesting(updates
[i
].form_data
);
1396 ScopedVector
<autofill::PasswordForm
> matching_items
=
1397 keychain_adapter
.PasswordsFillingForm(query_form
->signon_realm
,
1398 query_form
->scheme
);
1399 if (updates
[i
].password
) {
1400 EXPECT_GT(matching_items
.size(), 0U) << "iteration " << i
;
1401 if (matching_items
.size() >= 1)
1402 EXPECT_EQ(ASCIIToUTF16(updates
[i
].password
),
1403 matching_items
[0]->password_value
) << "iteration " << i
;
1405 EXPECT_EQ(0U, matching_items
.size()) << "iteration " << i
;
1408 EXPECT_TRUE(login_db()->GetLogins(*query_form
, &matching_items
));
1409 EXPECT_EQ(updates
[i
].password
? 1U : 0U, matching_items
.size())
1410 << "iteration " << i
;
1414 TEST_F(PasswordStoreMacTest
, TestDBKeychainAssociation
) {
1415 // Tests that association between the keychain and login database parts of a
1416 // password added by fuzzy (PSL) matching works.
1417 // 1. Add a password for www.facebook.com
1418 // 2. Get a password for m.facebook.com. This fuzzy matches and returns the
1419 // www.facebook.com password.
1420 // 3. Add the returned password for m.facebook.com.
1421 // 4. Remove both passwords.
1422 // -> check: that both are gone from the login DB and the keychain
1423 // This test should in particular ensure that we don't keep passwords in the
1424 // keychain just before we think we still have other (fuzzy-)matching entries
1425 // for them in the login database. (For example, here if we deleted the
1426 // www.facebook.com password from the login database, we should not be blocked
1427 // from deleting it from the keystore just becaus the m.facebook.com password
1428 // fuzzy-matches the www.facebook.com one.)
1430 // 1. Add a password for www.facebook.com
1431 PasswordFormData www_form_data
= {
1432 PasswordForm::SCHEME_HTML
, "http://www.facebook.com/",
1433 "http://www.facebook.com/index.html", "login",
1434 L
"username", L
"password", L
"submit", L
"joe_user", L
"sekrit", true, false, 1
1436 scoped_ptr
<PasswordForm
> www_form
=
1437 CreatePasswordFormFromDataForTesting(www_form_data
);
1438 EXPECT_EQ(AddChangeForForm(*www_form
), login_db()->AddLogin(*www_form
));
1439 MacKeychainPasswordFormAdapter
owned_keychain_adapter(keychain());
1440 owned_keychain_adapter
.SetFindsOnlyOwnedItems(true);
1441 owned_keychain_adapter
.AddPassword(*www_form
);
1443 // 2. Get a password for m.facebook.com.
1444 PasswordForm
m_form(*www_form
);
1445 m_form
.signon_realm
= "http://m.facebook.com";
1446 m_form
.origin
= GURL("http://m.facebook.com/index.html");
1448 MockPasswordStoreConsumer consumer
;
1449 store_
->GetLogins(m_form
, PasswordStore::ALLOW_PROMPT
, &consumer
);
1450 PasswordForm returned_form
;
1451 EXPECT_CALL(consumer
, OnGetPasswordStoreResultsConstRef(SizeIs(1u)))
1453 DoAll(SaveACopyOfFirstForm(&returned_form
), QuitUIMessageLoop()));
1454 base::MessageLoop::current()->Run();
1456 // 3. Add the returned password for m.facebook.com.
1457 EXPECT_EQ(AddChangeForForm(returned_form
),
1458 login_db()->AddLogin(returned_form
));
1459 owned_keychain_adapter
.AddPassword(m_form
);
1461 // 4. Remove both passwords.
1462 store_
->RemoveLogin(*www_form
);
1463 store_
->RemoveLogin(m_form
);
1464 FinishAsyncProcessing();
1466 // No trace of www.facebook.com.
1467 ScopedVector
<autofill::PasswordForm
> matching_items
=
1468 owned_keychain_adapter
.PasswordsFillingForm(www_form
->signon_realm
,
1470 EXPECT_EQ(0u, matching_items
.size());
1471 EXPECT_TRUE(login_db()->GetLogins(*www_form
, &matching_items
));
1472 EXPECT_EQ(0u, matching_items
.size());
1473 // No trace of m.facebook.com.
1474 matching_items
= owned_keychain_adapter
.PasswordsFillingForm(
1475 m_form
.signon_realm
, m_form
.scheme
);
1476 EXPECT_EQ(0u, matching_items
.size());
1477 EXPECT_TRUE(login_db()->GetLogins(m_form
, &matching_items
));
1478 EXPECT_EQ(0u, matching_items
.size());
1483 class PasswordsChangeObserver
:
1484 public password_manager::PasswordStore::Observer
{
1486 PasswordsChangeObserver(PasswordStoreMac
* store
) : observer_(this) {
1487 observer_
.Add(store
);
1490 void WaitAndVerify(PasswordStoreMacTest
* test
) {
1491 test
->FinishAsyncProcessing();
1492 ::testing::Mock::VerifyAndClearExpectations(this);
1495 // password_manager::PasswordStore::Observer:
1496 MOCK_METHOD1(OnLoginsChanged
,
1497 void(const password_manager::PasswordStoreChangeList
& changes
));
1500 ScopedObserver
<password_manager::PasswordStore
,
1501 PasswordsChangeObserver
> observer_
;
1504 password_manager::PasswordStoreChangeList
GetAddChangeList(
1505 const PasswordForm
& form
) {
1506 password_manager::PasswordStoreChange
change(
1507 password_manager::PasswordStoreChange::ADD
, form
);
1508 return password_manager::PasswordStoreChangeList(1, change
);
1511 // Tests RemoveLoginsCreatedBetween or RemoveLoginsSyncedBetween depending on
1513 void CheckRemoveLoginsBetween(PasswordStoreMacTest
* test
, bool check_created
) {
1514 PasswordFormData www_form_data_facebook
= {
1515 PasswordForm::SCHEME_HTML
, "http://www.facebook.com/",
1516 "http://www.facebook.com/index.html", "login", L
"submit", L
"username",
1517 L
"password", L
"joe_user", L
"sekrit", true, false, 0 };
1518 // The old form doesn't have elements names.
1519 PasswordFormData www_form_data_facebook_old
= {
1520 PasswordForm::SCHEME_HTML
, "http://www.facebook.com/",
1521 "http://www.facebook.com/index.html", "login", L
"", L
"",
1522 L
"", L
"joe_user", L
"oldsekrit", true, false, 0 };
1523 PasswordFormData www_form_data_other
= {
1524 PasswordForm::SCHEME_HTML
, "http://different.com/",
1525 "http://different.com/index.html", "login", L
"submit", L
"username",
1526 L
"password", L
"different_joe_user", L
"sekrit", true, false, 0 };
1527 scoped_ptr
<PasswordForm
> form_facebook
=
1528 CreatePasswordFormFromDataForTesting(www_form_data_facebook
);
1529 scoped_ptr
<PasswordForm
> form_facebook_old
=
1530 CreatePasswordFormFromDataForTesting(www_form_data_facebook_old
);
1531 scoped_ptr
<PasswordForm
> form_other
=
1532 CreatePasswordFormFromDataForTesting(www_form_data_other
);
1533 base::Time now
= base::Time::Now();
1534 // TODO(vasilii): remove the next line once crbug/374132 is fixed.
1535 now
= base::Time::FromTimeT(now
.ToTimeT());
1536 base::Time next_day
= now
+ base::TimeDelta::FromDays(1);
1537 if (check_created
) {
1538 form_facebook_old
->date_created
= now
;
1539 form_facebook
->date_created
= next_day
;
1540 form_other
->date_created
= next_day
;
1542 form_facebook_old
->date_synced
= now
;
1543 form_facebook
->date_synced
= next_day
;
1544 form_other
->date_synced
= next_day
;
1547 PasswordsChangeObserver
observer(test
->store());
1548 test
->store()->AddLogin(*form_facebook_old
);
1549 test
->store()->AddLogin(*form_facebook
);
1550 test
->store()->AddLogin(*form_other
);
1551 EXPECT_CALL(observer
, OnLoginsChanged(GetAddChangeList(*form_facebook_old
)));
1552 EXPECT_CALL(observer
, OnLoginsChanged(GetAddChangeList(*form_facebook
)));
1553 EXPECT_CALL(observer
, OnLoginsChanged(GetAddChangeList(*form_other
)));
1554 observer
.WaitAndVerify(test
);
1556 // Check the keychain content.
1557 MacKeychainPasswordFormAdapter
owned_keychain_adapter(test
->keychain());
1558 owned_keychain_adapter
.SetFindsOnlyOwnedItems(false);
1559 ScopedVector
<PasswordForm
> matching_items(
1560 owned_keychain_adapter
.PasswordsFillingForm(form_facebook
->signon_realm
,
1561 form_facebook
->scheme
));
1562 EXPECT_EQ(1u, matching_items
.size());
1563 matching_items
= owned_keychain_adapter
.PasswordsFillingForm(
1564 form_other
->signon_realm
, form_other
->scheme
);
1565 EXPECT_EQ(1u, matching_items
.size());
1568 if (check_created
) {
1569 test
->store()->RemoveLoginsCreatedBetween(base::Time(), next_day
,
1572 test
->store()->RemoveLoginsSyncedBetween(base::Time(), next_day
);
1574 password_manager::PasswordStoreChangeList list
;
1575 form_facebook_old
->password_value
.clear();
1576 form_facebook
->password_value
.clear();
1577 list
.push_back(password_manager::PasswordStoreChange(
1578 password_manager::PasswordStoreChange::REMOVE
, *form_facebook_old
));
1579 list
.push_back(password_manager::PasswordStoreChange(
1580 password_manager::PasswordStoreChange::REMOVE
, *form_facebook
));
1581 EXPECT_CALL(observer
, OnLoginsChanged(list
));
1583 observer
.WaitAndVerify(test
);
1585 matching_items
= owned_keychain_adapter
.PasswordsFillingForm(
1586 form_facebook
->signon_realm
, form_facebook
->scheme
);
1587 EXPECT_EQ(0u, matching_items
.size());
1588 matching_items
= owned_keychain_adapter
.PasswordsFillingForm(
1589 form_other
->signon_realm
, form_other
->scheme
);
1590 EXPECT_EQ(1u, matching_items
.size());
1592 // Remove form_other.
1593 if (check_created
) {
1594 test
->store()->RemoveLoginsCreatedBetween(next_day
, base::Time(),
1597 test
->store()->RemoveLoginsSyncedBetween(next_day
, base::Time());
1599 form_other
->password_value
.clear();
1600 list
.push_back(password_manager::PasswordStoreChange(
1601 password_manager::PasswordStoreChange::REMOVE
, *form_other
));
1602 EXPECT_CALL(observer
, OnLoginsChanged(list
));
1603 observer
.WaitAndVerify(test
);
1604 matching_items
= owned_keychain_adapter
.PasswordsFillingForm(
1605 form_other
->signon_realm
, form_other
->scheme
);
1606 EXPECT_EQ(0u, matching_items
.size());
1611 TEST_F(PasswordStoreMacTest
, TestRemoveLoginsCreatedBetween
) {
1612 CheckRemoveLoginsBetween(this, true);
1615 TEST_F(PasswordStoreMacTest
, TestRemoveLoginsSyncedBetween
) {
1616 CheckRemoveLoginsBetween(this, false);
1619 TEST_F(PasswordStoreMacTest
, TestRemoveLoginsMultiProfile
) {
1620 // Make sure that RemoveLoginsCreatedBetween does affect only the correct
1623 // Add a third-party password.
1624 MockAppleKeychain::KeychainTestData keychain_data
= {
1625 kSecAuthenticationTypeHTMLForm
, "some.domain.com",
1626 kSecProtocolTypeHTTP
, "/insecure.html", 0, NULL
, "20020601171500Z",
1627 "joe_user", "sekrit", false };
1628 keychain()->AddTestItem(keychain_data
);
1630 // Add a password through the adapter. It has the "Chrome" creator tag.
1631 // However, it's not referenced by the password database.
1632 MacKeychainPasswordFormAdapter
owned_keychain_adapter(keychain());
1633 owned_keychain_adapter
.SetFindsOnlyOwnedItems(true);
1634 PasswordFormData www_form_data1
= {
1635 PasswordForm::SCHEME_HTML
, "http://www.facebook.com/",
1636 "http://www.facebook.com/index.html", "login", L
"username", L
"password",
1637 L
"submit", L
"joe_user", L
"sekrit", true, false, 1 };
1638 scoped_ptr
<PasswordForm
> www_form
=
1639 CreatePasswordFormFromDataForTesting(www_form_data1
);
1640 EXPECT_TRUE(owned_keychain_adapter
.AddPassword(*www_form
));
1642 // Add a password from the current profile.
1643 PasswordFormData www_form_data2
= {
1644 PasswordForm::SCHEME_HTML
, "http://www.facebook.com/",
1645 "http://www.facebook.com/index.html", "login", L
"username", L
"password",
1646 L
"submit", L
"not_joe_user", L
"12345", true, false, 1 };
1647 www_form
= CreatePasswordFormFromDataForTesting(www_form_data2
);
1648 store_
->AddLogin(*www_form
);
1649 FinishAsyncProcessing();
1651 ScopedVector
<PasswordForm
> matching_items
;
1652 EXPECT_TRUE(login_db()->GetLogins(*www_form
, &matching_items
));
1653 EXPECT_EQ(1u, matching_items
.size());
1655 store_
->RemoveLoginsCreatedBetween(base::Time(), base::Time(),
1657 FinishAsyncProcessing();
1659 // Check the second facebook form is gone.
1660 EXPECT_TRUE(login_db()->GetLogins(*www_form
, &matching_items
));
1661 EXPECT_EQ(0u, matching_items
.size());
1663 // Check the first facebook form is still there.
1664 matching_items
= owned_keychain_adapter
.PasswordsFillingForm(
1665 www_form
->signon_realm
, www_form
->scheme
);
1666 ASSERT_EQ(1u, matching_items
.size());
1667 EXPECT_EQ(ASCIIToUTF16("joe_user"), matching_items
[0]->username_value
);
1669 // Check the third-party password is still there.
1670 owned_keychain_adapter
.SetFindsOnlyOwnedItems(false);
1671 matching_items
= owned_keychain_adapter
.PasswordsFillingForm(
1672 "http://some.domain.com/insecure.html", PasswordForm::SCHEME_HTML
);
1673 ASSERT_EQ(1u, matching_items
.size());
1676 // Add a facebook form to the store but not to the keychain. The form is to be
1677 // implicitly deleted. However, the observers shouldn't get notified about
1678 // deletion of non-existent forms like m.facebook.com.
1679 TEST_F(PasswordStoreMacTest
, SilentlyRemoveOrphanedForm
) {
1680 testing::StrictMock
<MockPasswordStoreObserver
> mock_observer
;
1681 store()->AddObserver(&mock_observer
);
1683 // 1. Add a password for www.facebook.com to the LoginDatabase.
1684 PasswordFormData www_form_data
= {
1685 PasswordForm::SCHEME_HTML
, "http://www.facebook.com/",
1686 "http://www.facebook.com/index.html", "login",
1687 L
"username", L
"password", L
"submit", L
"joe_user", L
"", true, false, 1
1689 scoped_ptr
<PasswordForm
> www_form(
1690 CreatePasswordFormFromDataForTesting(www_form_data
));
1691 EXPECT_EQ(AddChangeForForm(*www_form
), login_db()->AddLogin(*www_form
));
1693 // 2. Get a PSL-matched password for m.facebook.com. The observer isn't
1694 // notified because the form isn't in the database.
1695 PasswordForm
m_form(*www_form
);
1696 m_form
.signon_realm
= "http://m.facebook.com";
1697 m_form
.origin
= GURL("http://m.facebook.com/index.html");
1699 MockPasswordStoreConsumer consumer
;
1700 ON_CALL(consumer
, OnGetPasswordStoreResultsConstRef(_
))
1701 .WillByDefault(QuitUIMessageLoop());
1702 EXPECT_CALL(mock_observer
, OnLoginsChanged(_
)).Times(0);
1703 // The PSL-matched form isn't returned because there is no actual password in
1705 EXPECT_CALL(consumer
, OnGetPasswordStoreResultsConstRef(IsEmpty()));
1706 store_
->GetLogins(m_form
, PasswordStore::ALLOW_PROMPT
, &consumer
);
1707 base::MessageLoop::current()->Run();
1708 ScopedVector
<autofill::PasswordForm
> all_forms
;
1709 EXPECT_TRUE(login_db()->GetAutofillableLogins(&all_forms
));
1710 EXPECT_EQ(1u, all_forms
.size());
1711 ::testing::Mock::VerifyAndClearExpectations(&mock_observer
);
1713 // 3. Get a password for www.facebook.com. The form is implicitly removed and
1714 // the observer is notified.
1715 password_manager::PasswordStoreChangeList list
;
1716 list
.push_back(password_manager::PasswordStoreChange(
1717 password_manager::PasswordStoreChange::REMOVE
, *www_form
));
1718 EXPECT_CALL(mock_observer
, OnLoginsChanged(list
));
1719 EXPECT_CALL(consumer
, OnGetPasswordStoreResultsConstRef(IsEmpty()));
1720 store_
->GetLogins(*www_form
, PasswordStore::ALLOW_PROMPT
, &consumer
);
1721 base::MessageLoop::current()->Run();
1722 EXPECT_TRUE(login_db()->GetAutofillableLogins(&all_forms
));
1723 EXPECT_EQ(0u, all_forms
.size());
1726 // Verify that Android app passwords can be stored, retrieved, and deleted.
1727 // Regression test for http://crbug.com/455551
1728 TEST_F(PasswordStoreMacTest
, StoringAndRetrievingAndroidCredentials
) {
1730 form
.signon_realm
= "android://7x7IDboo8u9YKraUsbmVkuf1@net.rateflix.app/";
1731 form
.username_value
= base::UTF8ToUTF16("randomusername");
1732 form
.password_value
= base::UTF8ToUTF16("password");
1734 VerifyCredentialLifecycle(form
);
1737 // Verify that federated credentials can be stored, retrieved and deleted.
1738 TEST_F(PasswordStoreMacTest
, StoringAndRetrievingFederatedCredentials
) {
1740 form
.signon_realm
= "android://7x7IDboo8u9YKraUsbmVkuf1@net.rateflix.app/";
1741 form
.federation_url
= GURL(password_manager::kTestingFederationUrlSpec
);
1742 form
.username_value
= base::UTF8ToUTF16("randomusername");
1743 form
.password_value
= base::UTF8ToUTF16(""); // No password.
1745 VerifyCredentialLifecycle(form
);
1748 void CheckMigrationResult(PasswordStoreMac::MigrationResult expected_result
,
1749 PasswordStoreMac::MigrationResult result
) {
1750 EXPECT_EQ(expected_result
, result
);
1751 QuitUIMessageLoop();
1754 // Import the passwords from the Keychain to LoginDatabase.
1755 TEST_F(PasswordStoreMacTest
, ImportFromKeychain
) {
1757 form1
.origin
= GURL("http://accounts.google.com/LoginAuth");
1758 form1
.signon_realm
= "http://accounts.google.com/";
1759 form1
.username_value
= ASCIIToUTF16("my_username");
1760 form1
.password_value
= ASCIIToUTF16("my_password");
1763 form2
.origin
= GURL("http://facebook.com/Login");
1764 form2
.signon_realm
= "http://facebook.com/";
1765 form2
.username_value
= ASCIIToUTF16("my_username");
1766 form2
.password_value
= ASCIIToUTF16("my_password");
1768 PasswordForm blacklisted_form
;
1769 blacklisted_form
.origin
= GURL("http://badsite.com/Login");
1770 blacklisted_form
.signon_realm
= "http://badsite.com/";
1771 blacklisted_form
.blacklisted_by_user
= true;
1773 store()->AddLogin(form1
);
1774 store()->AddLogin(form2
);
1775 store()->AddLogin(blacklisted_form
);
1776 FinishAsyncProcessing();
1778 ASSERT_TRUE(base::PostTaskAndReplyWithResult(
1779 thread_
->task_runner().get(), FROM_HERE
,
1780 base::Bind(&PasswordStoreMac::ImportFromKeychain
, store()),
1781 base::Bind(&CheckMigrationResult
, PasswordStoreMac::MIGRATION_OK
)));
1782 FinishAsyncProcessing();
1784 // The password should be stored in the database by now.
1785 ScopedVector
<PasswordForm
> matching_items
;
1786 EXPECT_TRUE(login_db()->GetLogins(form1
, &matching_items
));
1787 ASSERT_EQ(1u, matching_items
.size());
1788 EXPECT_EQ(form1
, *matching_items
[0]);
1790 EXPECT_TRUE(login_db()->GetLogins(form2
, &matching_items
));
1791 ASSERT_EQ(1u, matching_items
.size());
1792 EXPECT_EQ(form2
, *matching_items
[0]);
1794 EXPECT_TRUE(login_db()->GetLogins(blacklisted_form
, &matching_items
));
1795 ASSERT_EQ(1u, matching_items
.size());
1796 EXPECT_EQ(blacklisted_form
, *matching_items
[0]);
1798 // The passwords are encrypted using a key from the Keychain.
1799 EXPECT_TRUE(histogram_tester_
->GetHistogramSamplesSinceCreation(
1800 "OSX.Keychain.Access")->TotalCount());
1801 histogram_tester_
.reset();
1804 // Import a federated credential while the Keychain is locked.
1805 TEST_F(PasswordStoreMacTest
, ImportFederatedFromLockedKeychain
) {
1806 keychain()->set_locked(true);
1808 form1
.origin
= GURL("http://example.com/Login");
1809 form1
.signon_realm
= "http://example.com/";
1810 form1
.username_value
= ASCIIToUTF16("my_username");
1811 form1
.federation_url
= GURL("https://accounts.google.com/");
1813 store()->AddLogin(form1
);
1814 FinishAsyncProcessing();
1815 ASSERT_TRUE(base::PostTaskAndReplyWithResult(
1816 thread_
->task_runner().get(), FROM_HERE
,
1817 base::Bind(&PasswordStoreMac::ImportFromKeychain
, store()),
1818 base::Bind(&CheckMigrationResult
, PasswordStoreMac::MIGRATION_OK
)));
1819 FinishAsyncProcessing();
1821 ScopedVector
<PasswordForm
> matching_items
;
1822 EXPECT_TRUE(login_db()->GetLogins(form1
, &matching_items
));
1823 ASSERT_EQ(1u, matching_items
.size());
1824 EXPECT_EQ(form1
, *matching_items
[0]);
1827 // Try to import while the Keychain is locked but the encryption key had been
1829 TEST_F(PasswordStoreMacTest
, ImportFromLockedKeychainError
) {
1831 form1
.origin
= GURL("http://accounts.google.com/LoginAuth");
1832 form1
.signon_realm
= "http://accounts.google.com/";
1833 form1
.username_value
= ASCIIToUTF16("my_username");
1834 form1
.password_value
= ASCIIToUTF16("my_password");
1835 store()->AddLogin(form1
);
1836 FinishAsyncProcessing();
1838 // Add a second keychain item matching the Database entry.
1839 PasswordForm form2
= form1
;
1840 form2
.origin
= GURL("http://accounts.google.com/Login");
1841 form2
.password_value
= ASCIIToUTF16("1234");
1842 MacKeychainPasswordFormAdapter
adapter(keychain());
1843 EXPECT_TRUE(adapter
.AddPassword(form2
));
1845 keychain()->set_locked(true);
1846 ASSERT_TRUE(base::PostTaskAndReplyWithResult(
1847 thread_
->task_runner().get(), FROM_HERE
,
1848 base::Bind(&PasswordStoreMac::ImportFromKeychain
, store()),
1849 base::Bind(&CheckMigrationResult
, PasswordStoreMac::KEYCHAIN_BLOCKED
)));
1850 FinishAsyncProcessing();
1852 ScopedVector
<PasswordForm
> matching_items
;
1853 EXPECT_TRUE(login_db()->GetLogins(form1
, &matching_items
));
1854 ASSERT_EQ(1u, matching_items
.size());
1855 EXPECT_EQ(base::string16(), matching_items
[0]->password_value
);
1857 histogram_tester_
->ExpectUniqueSample(
1858 "PasswordManager.KeychainMigration.NumPasswordsOnFailure", 1, 1);
1859 histogram_tester_
->ExpectUniqueSample(
1860 "PasswordManager.KeychainMigration.NumFailedPasswords", 1, 1);
1861 histogram_tester_
->ExpectUniqueSample(
1862 "PasswordManager.KeychainMigration.NumChromeOwnedInaccessiblePasswords",
1864 // Don't test the encryption key access.
1865 histogram_tester_
.reset();