Update broken references to image assets
[chromium-blink-merge.git] / chrome / browser / password_manager / password_store_mac_unittest.cc
bloba201fcbed714eda43f518aeba2a0faae9ae689d2
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;
42 using testing::_;
43 using testing::DoAll;
44 using testing::Invoke;
45 using testing::IsEmpty;
46 using testing::SizeIs;
47 using testing::WithArg;
49 namespace {
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];
63 void Noop() {
66 class MockPasswordStoreConsumer : public PasswordStoreConsumer {
67 public:
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 {
78 public:
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 {
85 public:
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 {}
93 // LoginDatabase:
94 bool Init() override {
95 event_->Wait();
96 return password_manager::LoginDatabase::Init();
99 private:
100 base::WaitableEvent* event_;
102 DISALLOW_COPY_AND_ASSIGN(SlowToInitLoginDatabase);
105 #pragma mark -
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 "
121 << test_number;
122 if (expectations.size() != forms.size())
123 return;
125 for (unsigned int i = 0; i < expectations.size(); ++i) {
126 SCOPED_TRACE(testing::Message() << forms_label << " in test " << test_number
127 << ", item " << i);
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);
149 } else {
150 EXPECT_EQ(WideToUTF16(expectation->password_value),
151 form->password_value);
152 EXPECT_TRUE(form->federation_url.is_empty());
154 } else {
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);
162 EXPECT_EQ(
163 created + base::TimeDelta::FromDays(
164 password_manager::kTestingDaysAfterPasswordsAreSynced),
165 form->date_synced);
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));
175 } // namespace
177 #pragma mark -
179 class PasswordStoreMacInternalsTest : public testing::Test {
180 public:
181 void SetUp() override {
182 MockAppleKeychain::KeychainTestData test_data[] = {
183 // Basic HTML form.
184 {kSecAuthenticationTypeHTMLForm,
185 "some.domain.com",
186 kSecProtocolTypeHTTP,
187 NULL,
189 NULL,
190 "20020601171500Z",
191 "joe_user",
192 "sekrit",
193 false},
194 // HTML form with path.
195 {kSecAuthenticationTypeHTMLForm,
196 "some.domain.com",
197 kSecProtocolTypeHTTP,
198 "/insecure.html",
200 NULL,
201 "19991231235959Z",
202 "joe_user",
203 "sekrit",
204 false},
205 // Secure HTML form with path.
206 {kSecAuthenticationTypeHTMLForm,
207 "some.domain.com",
208 kSecProtocolTypeHTTPS,
209 "/secure.html",
211 NULL,
212 "20100908070605Z",
213 "secure_user",
214 "password",
215 false},
216 // True negative item.
217 {kSecAuthenticationTypeHTMLForm,
218 "dont.remember.com",
219 kSecProtocolTypeHTTP,
220 NULL,
222 NULL,
223 "20000101000000Z",
226 true},
227 // De-facto negative item, type one.
228 {kSecAuthenticationTypeHTMLForm,
229 "dont.remember.com",
230 kSecProtocolTypeHTTP,
231 NULL,
233 NULL,
234 "20000101000000Z",
235 "Password Not Stored",
237 false},
238 // De-facto negative item, type two.
239 {kSecAuthenticationTypeHTMLForm,
240 "dont.remember.com",
241 kSecProtocolTypeHTTPS,
242 NULL,
244 NULL,
245 "20000101000000Z",
246 "Password Not Stored",
247 " ",
248 false},
249 // HTTP auth basic, with port and path.
250 {kSecAuthenticationTypeHTTPBasic,
251 "some.domain.com",
252 kSecProtocolTypeHTTP,
253 "/insecure.html",
254 4567,
255 "low_security",
256 "19980330100000Z",
257 "basic_auth_user",
258 "basic",
259 false},
260 // HTTP auth digest, secure.
261 {kSecAuthenticationTypeHTTPDigest,
262 "some.domain.com",
263 kSecProtocolTypeHTTPS,
264 NULL,
266 "high_security",
267 "19980330100000Z",
268 "digest_auth_user",
269 "digest",
270 false},
271 // An FTP password with an invalid date, for edge-case testing.
272 {kSecAuthenticationTypeDefault,
273 "a.server.com",
274 kSecProtocolTypeFTP,
275 NULL,
277 NULL,
278 "20010203040",
279 "abc",
280 "123",
281 false},
282 // Password for an Android application.
283 {kSecAuthenticationTypeHTMLForm,
284 "android://hash@com.domain.some/",
285 kSecProtocolTypeHTTPS,
288 NULL,
289 "20150515141312Z",
290 "joe_user",
291 "secret",
292 false},
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();
305 delete keychain_;
308 protected:
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_;
327 #pragma mark -
329 TEST_F(PasswordStoreMacInternalsTest, TestKeychainToFormTranslation) {
330 typedef struct {
331 const PasswordForm::Scheme scheme;
332 const char* signon_realm;
333 const char* origin;
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;
343 } TestExpectations;
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
371 // which is 1601.
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);
384 PasswordForm form;
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;
401 } else {
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);
423 PasswordForm form;
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 },
442 2, 2 },
443 { { PasswordForm::SCHEME_HTML, "http://some.domain.com/",
444 NULL, NULL, NULL, NULL, NULL, L"wrong_user", NULL, false, false, 0 },
445 2, 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 },
449 0, 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,
453 0 },
454 1, 1 },
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,
458 0 },
459 0, 0 },
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,
463 0 },
464 0, 0 },
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 },
468 1, 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,
472 0 },
473 0, 0 },
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 },
478 1, 1 },
479 { { PasswordForm::SCHEME_HTML, "android://hash@com.domain.some/",
480 NULL, NULL, NULL, NULL, NULL, L"joe_user", NULL, false, true, 0 },
481 1, 1 },
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 },
489 1, 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,
506 query_form->scheme);
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,
516 *keychain_);
517 matching_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) {
525 keychain_->Free(*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(
611 *modified_forms[j]);
612 EXPECT_FALSE(match) << "In modified version " << j
613 << " of base form " << i;
618 TEST_F(PasswordStoreMacInternalsTest, TestKeychainAdd) {
619 struct TestDataAndExpectation {
620 PasswordFormData data;
621 bool should_succeed;
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
652 // reported.
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);
667 if (add_succeeded) {
668 EXPECT_TRUE(owned_keychain_adapter.HasPasswordsMergeableWithForm(
669 *in_form));
670 EXPECT_TRUE(owned_keychain_adapter.HasPasswordExactlyMatchingForm(
671 *in_form));
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_,
691 keychain_item,
692 &stored_form,
693 true);
694 EXPECT_EQ(update_form->password_value, stored_form.password_value);
698 TEST_F(PasswordStoreMacInternalsTest, TestKeychainRemove) {
699 struct TestDataAndExpectation {
700 PasswordFormData data;
701 bool should_succeed;
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();
763 EXPECT_TRUE(
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");
768 EXPECT_TRUE(
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;
776 EXPECT_FALSE(
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/");
782 EXPECT_FALSE(
783 FormsMatchForMerge(base_form, different_form, STRICT_FORM_MATCH));
786 PasswordForm different_form(base_form);
787 different_form.username_value = ASCIIToUTF16("john.doe");
788 EXPECT_FALSE(
789 FormsMatchForMerge(base_form, different_form, STRICT_FORM_MATCH));
792 PasswordForm different_form(base_form);
793 different_form.blacklisted_by_user = true;
794 EXPECT_FALSE(
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
899 // simple.
900 enum {
901 KEYCHAIN_INPUT = 0,
902 DATABASE_INPUT,
903 MERGE_OUTPUT,
904 KEYCHAIN_OUTPUT,
905 DATABASE_OUTPUT,
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
927 // a stored account.
928 ++current_test;
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.
941 ++current_test;
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.
950 ++current_test;
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.
962 ++current_test;
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,
988 &database_forms,
989 &merged_forms);
991 CHECK_FORMS(keychain_forms.get(), test_data[KEYCHAIN_OUTPUT][test_case],
992 test_case);
993 CHECK_FORMS(database_forms.get(), test_data[DATABASE_OUTPUT][test_case],
994 test_case);
995 CHECK_FORMS(merged_forms.get(), test_data[MERGE_OUTPUT][test_case],
996 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,
1033 &merged_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,
1061 &merged_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(
1072 *keychain_,
1073 keychain_item,
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(
1086 *keychain_,
1087 keychain_item,
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(
1101 *keychain_,
1102 keychain_item,
1103 &negative_form,
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(
1116 *keychain_,
1117 keychain_item,
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(
1130 *keychain_,
1131 keychain_item,
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());
1172 #pragma mark -
1174 class PasswordStoreMacTest : public testing::Test {
1175 public:
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);
1185 login_db_.reset(
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();
1200 thread_.reset();
1201 login_db_.reset();
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 histogram_tester_->ExpectTotalCount("OSX.Keychain.Access", 0);
1211 static void InitLoginDatabase(password_manager::LoginDatabase* login_db) {
1212 ASSERT_TRUE(login_db->Init());
1215 void CreateAndInitPasswordStore(password_manager::LoginDatabase* login_db) {
1216 store_ = new PasswordStoreMac(
1217 base::ThreadTaskRunnerHandle::Get(), nullptr,
1218 make_scoped_ptr<AppleKeychain>(new MockAppleKeychain));
1219 ASSERT_TRUE(thread_->task_runner()->PostTask(
1220 FROM_HERE, base::Bind(&PasswordStoreMac::InitWithTaskRunner, store_,
1221 thread_->task_runner())));
1223 ASSERT_TRUE(thread_->task_runner()->PostTask(
1224 FROM_HERE, base::Bind(&PasswordStoreMac::set_login_metadata_db, store_,
1225 base::Unretained(login_db))));
1228 void ClosePasswordStore() {
1229 if (!store_)
1230 return;
1232 store_->Shutdown();
1233 store_ = nullptr;
1236 // Verifies that the given |form| can be properly stored so that it can be
1237 // retrieved by FillMatchingLogins() and GetAutofillableLogins(), and then it
1238 // can be properly removed.
1239 void VerifyCredentialLifecycle(const PasswordForm& form) {
1240 // Run everything twice to make sure no garbage is left behind that would
1241 // prevent storing the form a second time.
1242 for (size_t iteration = 0; iteration < 2; ++iteration) {
1243 SCOPED_TRACE(testing::Message("Iteration: ") << iteration);
1245 MockPasswordStoreConsumer mock_consumer;
1246 EXPECT_CALL(mock_consumer, OnGetPasswordStoreResultsConstRef(IsEmpty()))
1247 .WillOnce(QuitUIMessageLoop());
1248 store()->GetAutofillableLogins(&mock_consumer);
1249 base::MessageLoop::current()->Run();
1250 ::testing::Mock::VerifyAndClearExpectations(&mock_consumer);
1252 store()->AddLogin(form);
1253 FinishAsyncProcessing();
1255 PasswordForm returned_form;
1256 EXPECT_CALL(mock_consumer, OnGetPasswordStoreResultsConstRef(SizeIs(1u)))
1257 .WillOnce(
1258 DoAll(SaveACopyOfFirstForm(&returned_form), QuitUIMessageLoop()));
1260 // The query operations will also do some housekeeping: they will remove
1261 // dangling credentials in the LoginDatabase without a matching Keychain
1262 // item when one is expected. If the logic that stores the Keychain item
1263 // is incorrect, this will wipe the newly added form before the second
1264 // query.
1265 store()->GetAutofillableLogins(&mock_consumer);
1266 base::MessageLoop::current()->Run();
1267 ::testing::Mock::VerifyAndClearExpectations(&mock_consumer);
1268 EXPECT_EQ(form, returned_form);
1270 PasswordForm query_form = form;
1271 query_form.password_value.clear();
1272 query_form.username_value.clear();
1273 EXPECT_CALL(mock_consumer, OnGetPasswordStoreResultsConstRef(SizeIs(1u)))
1274 .WillOnce(
1275 DoAll(SaveACopyOfFirstForm(&returned_form), QuitUIMessageLoop()));
1276 store()->GetLogins(query_form, PasswordStore::ALLOW_PROMPT,
1277 &mock_consumer);
1278 base::MessageLoop::current()->Run();
1279 ::testing::Mock::VerifyAndClearExpectations(&mock_consumer);
1280 EXPECT_EQ(form, returned_form);
1282 store()->RemoveLogin(form);
1286 base::FilePath test_login_db_file_path() const {
1287 return db_dir_.path().Append(FILE_PATH_LITERAL("login.db"));
1290 password_manager::LoginDatabase* login_db() const {
1291 return store_->login_metadata_db();
1294 MockAppleKeychain* keychain() {
1295 return static_cast<MockAppleKeychain*>(store_->keychain());
1298 void FinishAsyncProcessing() {
1299 scoped_refptr<content::MessageLoopRunner> runner =
1300 new content::MessageLoopRunner;
1301 ASSERT_TRUE(thread_->task_runner()->PostTaskAndReply(
1302 FROM_HERE, base::Bind(&Noop), runner->QuitClosure()));
1303 runner->Run();
1306 PasswordStoreMac* store() { return store_.get(); }
1308 protected:
1309 base::MessageLoopForUI message_loop_;
1310 content::TestBrowserThread ui_thread_;
1311 // Thread that the synchronous methods are run on.
1312 scoped_ptr<base::Thread> thread_;
1314 base::ScopedTempDir db_dir_;
1315 scoped_ptr<password_manager::LoginDatabase> login_db_;
1316 scoped_refptr<PasswordStoreMac> store_;
1317 scoped_ptr<base::HistogramTester> histogram_tester_;
1320 TEST_F(PasswordStoreMacTest, TestStoreUpdate) {
1321 // Insert a password into both the database and the keychain.
1322 // This is done manually, rather than through store_->AddLogin, because the
1323 // Mock Keychain isn't smart enough to be able to support update generically,
1324 // so some.domain.com triggers special handling to test it that make inserting
1325 // fail.
1326 PasswordFormData joint_data = {
1327 PasswordForm::SCHEME_HTML, "http://some.domain.com/",
1328 "http://some.domain.com/insecure.html", "login.cgi",
1329 L"username", L"password", L"submit", L"joe_user", L"sekrit", true, false, 1
1331 scoped_ptr<PasswordForm> joint_form =
1332 CreatePasswordFormFromDataForTesting(joint_data);
1333 EXPECT_EQ(AddChangeForForm(*joint_form), login_db()->AddLogin(*joint_form));
1334 MockAppleKeychain::KeychainTestData joint_keychain_data = {
1335 kSecAuthenticationTypeHTMLForm, "some.domain.com",
1336 kSecProtocolTypeHTTP, "/insecure.html", 0, NULL, "20020601171500Z",
1337 "joe_user", "sekrit", false };
1338 keychain()->AddTestItem(joint_keychain_data);
1340 // Insert a password into the keychain only.
1341 MockAppleKeychain::KeychainTestData keychain_only_data = {
1342 kSecAuthenticationTypeHTMLForm, "keychain.only.com",
1343 kSecProtocolTypeHTTP, NULL, 0, NULL, "20020601171500Z",
1344 "keychain", "only", false
1346 keychain()->AddTestItem(keychain_only_data);
1348 struct UpdateData {
1349 PasswordFormData form_data;
1350 const char* password; // NULL indicates no entry should be present.
1353 // Make a series of update calls.
1354 UpdateData updates[] = {
1355 // Update the keychain+db passwords (the normal password update case).
1356 { { PasswordForm::SCHEME_HTML, "http://some.domain.com/",
1357 "http://some.domain.com/insecure.html", "login.cgi",
1358 L"username", L"password", L"submit", L"joe_user", L"53krit",
1359 true, false, 2 },
1360 "53krit",
1362 // Update the keychain-only password; this simulates the initial use of a
1363 // password stored by another browsers.
1364 { { PasswordForm::SCHEME_HTML, "http://keychain.only.com/",
1365 "http://keychain.only.com/login.html", "login.cgi",
1366 L"username", L"password", L"submit", L"keychain", L"only",
1367 true, false, 2 },
1368 "only",
1370 // Update a password that doesn't exist in either location. This tests the
1371 // case where a form is filled, then the stored login is removed, then the
1372 // form is submitted.
1373 { { PasswordForm::SCHEME_HTML, "http://different.com/",
1374 "http://different.com/index.html", "login.cgi",
1375 L"username", L"password", L"submit", L"abc", L"123",
1376 true, false, 2 },
1377 NULL,
1380 for (unsigned int i = 0; i < arraysize(updates); ++i) {
1381 scoped_ptr<PasswordForm> form =
1382 CreatePasswordFormFromDataForTesting(updates[i].form_data);
1383 store_->UpdateLogin(*form);
1386 FinishAsyncProcessing();
1388 MacKeychainPasswordFormAdapter keychain_adapter(keychain());
1389 for (unsigned int i = 0; i < arraysize(updates); ++i) {
1390 scoped_ptr<PasswordForm> query_form =
1391 CreatePasswordFormFromDataForTesting(updates[i].form_data);
1393 ScopedVector<autofill::PasswordForm> matching_items =
1394 keychain_adapter.PasswordsFillingForm(query_form->signon_realm,
1395 query_form->scheme);
1396 if (updates[i].password) {
1397 EXPECT_GT(matching_items.size(), 0U) << "iteration " << i;
1398 if (matching_items.size() >= 1)
1399 EXPECT_EQ(ASCIIToUTF16(updates[i].password),
1400 matching_items[0]->password_value) << "iteration " << i;
1401 } else {
1402 EXPECT_EQ(0U, matching_items.size()) << "iteration " << i;
1405 EXPECT_TRUE(login_db()->GetLogins(*query_form, &matching_items));
1406 EXPECT_EQ(updates[i].password ? 1U : 0U, matching_items.size())
1407 << "iteration " << i;
1411 TEST_F(PasswordStoreMacTest, TestDBKeychainAssociation) {
1412 // Tests that association between the keychain and login database parts of a
1413 // password added by fuzzy (PSL) matching works.
1414 // 1. Add a password for www.facebook.com
1415 // 2. Get a password for m.facebook.com. This fuzzy matches and returns the
1416 // www.facebook.com password.
1417 // 3. Add the returned password for m.facebook.com.
1418 // 4. Remove both passwords.
1419 // -> check: that both are gone from the login DB and the keychain
1420 // This test should in particular ensure that we don't keep passwords in the
1421 // keychain just before we think we still have other (fuzzy-)matching entries
1422 // for them in the login database. (For example, here if we deleted the
1423 // www.facebook.com password from the login database, we should not be blocked
1424 // from deleting it from the keystore just becaus the m.facebook.com password
1425 // fuzzy-matches the www.facebook.com one.)
1427 // 1. Add a password for www.facebook.com
1428 PasswordFormData www_form_data = {
1429 PasswordForm::SCHEME_HTML, "http://www.facebook.com/",
1430 "http://www.facebook.com/index.html", "login",
1431 L"username", L"password", L"submit", L"joe_user", L"sekrit", true, false, 1
1433 scoped_ptr<PasswordForm> www_form =
1434 CreatePasswordFormFromDataForTesting(www_form_data);
1435 EXPECT_EQ(AddChangeForForm(*www_form), login_db()->AddLogin(*www_form));
1436 MacKeychainPasswordFormAdapter owned_keychain_adapter(keychain());
1437 owned_keychain_adapter.SetFindsOnlyOwnedItems(true);
1438 owned_keychain_adapter.AddPassword(*www_form);
1440 // 2. Get a password for m.facebook.com.
1441 PasswordForm m_form(*www_form);
1442 m_form.signon_realm = "http://m.facebook.com";
1443 m_form.origin = GURL("http://m.facebook.com/index.html");
1445 MockPasswordStoreConsumer consumer;
1446 store_->GetLogins(m_form, PasswordStore::ALLOW_PROMPT, &consumer);
1447 PasswordForm returned_form;
1448 EXPECT_CALL(consumer, OnGetPasswordStoreResultsConstRef(SizeIs(1u)))
1449 .WillOnce(
1450 DoAll(SaveACopyOfFirstForm(&returned_form), QuitUIMessageLoop()));
1451 base::MessageLoop::current()->Run();
1453 // 3. Add the returned password for m.facebook.com.
1454 EXPECT_EQ(AddChangeForForm(returned_form),
1455 login_db()->AddLogin(returned_form));
1456 owned_keychain_adapter.AddPassword(m_form);
1458 // 4. Remove both passwords.
1459 store_->RemoveLogin(*www_form);
1460 store_->RemoveLogin(m_form);
1461 FinishAsyncProcessing();
1463 // No trace of www.facebook.com.
1464 ScopedVector<autofill::PasswordForm> matching_items =
1465 owned_keychain_adapter.PasswordsFillingForm(www_form->signon_realm,
1466 www_form->scheme);
1467 EXPECT_EQ(0u, matching_items.size());
1468 EXPECT_TRUE(login_db()->GetLogins(*www_form, &matching_items));
1469 EXPECT_EQ(0u, matching_items.size());
1470 // No trace of m.facebook.com.
1471 matching_items = owned_keychain_adapter.PasswordsFillingForm(
1472 m_form.signon_realm, m_form.scheme);
1473 EXPECT_EQ(0u, matching_items.size());
1474 EXPECT_TRUE(login_db()->GetLogins(m_form, &matching_items));
1475 EXPECT_EQ(0u, matching_items.size());
1478 namespace {
1480 class PasswordsChangeObserver :
1481 public password_manager::PasswordStore::Observer {
1482 public:
1483 PasswordsChangeObserver(PasswordStoreMac* store) : observer_(this) {
1484 observer_.Add(store);
1487 void WaitAndVerify(PasswordStoreMacTest* test) {
1488 test->FinishAsyncProcessing();
1489 ::testing::Mock::VerifyAndClearExpectations(this);
1492 // password_manager::PasswordStore::Observer:
1493 MOCK_METHOD1(OnLoginsChanged,
1494 void(const password_manager::PasswordStoreChangeList& changes));
1496 private:
1497 ScopedObserver<password_manager::PasswordStore,
1498 PasswordsChangeObserver> observer_;
1501 password_manager::PasswordStoreChangeList GetAddChangeList(
1502 const PasswordForm& form) {
1503 password_manager::PasswordStoreChange change(
1504 password_manager::PasswordStoreChange::ADD, form);
1505 return password_manager::PasswordStoreChangeList(1, change);
1508 // Tests RemoveLoginsCreatedBetween or RemoveLoginsSyncedBetween depending on
1509 // |check_created|.
1510 void CheckRemoveLoginsBetween(PasswordStoreMacTest* test, bool check_created) {
1511 PasswordFormData www_form_data_facebook = {
1512 PasswordForm::SCHEME_HTML, "http://www.facebook.com/",
1513 "http://www.facebook.com/index.html", "login", L"submit", L"username",
1514 L"password", L"joe_user", L"sekrit", true, false, 0 };
1515 // The old form doesn't have elements names.
1516 PasswordFormData www_form_data_facebook_old = {
1517 PasswordForm::SCHEME_HTML, "http://www.facebook.com/",
1518 "http://www.facebook.com/index.html", "login", L"", L"",
1519 L"", L"joe_user", L"oldsekrit", true, false, 0 };
1520 PasswordFormData www_form_data_other = {
1521 PasswordForm::SCHEME_HTML, "http://different.com/",
1522 "http://different.com/index.html", "login", L"submit", L"username",
1523 L"password", L"different_joe_user", L"sekrit", true, false, 0 };
1524 scoped_ptr<PasswordForm> form_facebook =
1525 CreatePasswordFormFromDataForTesting(www_form_data_facebook);
1526 scoped_ptr<PasswordForm> form_facebook_old =
1527 CreatePasswordFormFromDataForTesting(www_form_data_facebook_old);
1528 scoped_ptr<PasswordForm> form_other =
1529 CreatePasswordFormFromDataForTesting(www_form_data_other);
1530 base::Time now = base::Time::Now();
1531 // TODO(vasilii): remove the next line once crbug/374132 is fixed.
1532 now = base::Time::FromTimeT(now.ToTimeT());
1533 base::Time next_day = now + base::TimeDelta::FromDays(1);
1534 if (check_created) {
1535 form_facebook_old->date_created = now;
1536 form_facebook->date_created = next_day;
1537 form_other->date_created = next_day;
1538 } else {
1539 form_facebook_old->date_synced = now;
1540 form_facebook->date_synced = next_day;
1541 form_other->date_synced = next_day;
1544 PasswordsChangeObserver observer(test->store());
1545 test->store()->AddLogin(*form_facebook_old);
1546 test->store()->AddLogin(*form_facebook);
1547 test->store()->AddLogin(*form_other);
1548 EXPECT_CALL(observer, OnLoginsChanged(GetAddChangeList(*form_facebook_old)));
1549 EXPECT_CALL(observer, OnLoginsChanged(GetAddChangeList(*form_facebook)));
1550 EXPECT_CALL(observer, OnLoginsChanged(GetAddChangeList(*form_other)));
1551 observer.WaitAndVerify(test);
1553 // Check the keychain content.
1554 MacKeychainPasswordFormAdapter owned_keychain_adapter(test->keychain());
1555 owned_keychain_adapter.SetFindsOnlyOwnedItems(false);
1556 ScopedVector<PasswordForm> matching_items(
1557 owned_keychain_adapter.PasswordsFillingForm(form_facebook->signon_realm,
1558 form_facebook->scheme));
1559 EXPECT_EQ(1u, matching_items.size());
1560 matching_items = owned_keychain_adapter.PasswordsFillingForm(
1561 form_other->signon_realm, form_other->scheme);
1562 EXPECT_EQ(1u, matching_items.size());
1564 // Remove facebook.
1565 if (check_created) {
1566 test->store()->RemoveLoginsCreatedBetween(base::Time(), next_day,
1567 base::Closure());
1568 } else {
1569 test->store()->RemoveLoginsSyncedBetween(base::Time(), next_day);
1571 password_manager::PasswordStoreChangeList list;
1572 form_facebook_old->password_value.clear();
1573 form_facebook->password_value.clear();
1574 list.push_back(password_manager::PasswordStoreChange(
1575 password_manager::PasswordStoreChange::REMOVE, *form_facebook_old));
1576 list.push_back(password_manager::PasswordStoreChange(
1577 password_manager::PasswordStoreChange::REMOVE, *form_facebook));
1578 EXPECT_CALL(observer, OnLoginsChanged(list));
1579 list.clear();
1580 observer.WaitAndVerify(test);
1582 matching_items = owned_keychain_adapter.PasswordsFillingForm(
1583 form_facebook->signon_realm, form_facebook->scheme);
1584 EXPECT_EQ(0u, matching_items.size());
1585 matching_items = owned_keychain_adapter.PasswordsFillingForm(
1586 form_other->signon_realm, form_other->scheme);
1587 EXPECT_EQ(1u, matching_items.size());
1589 // Remove form_other.
1590 if (check_created) {
1591 test->store()->RemoveLoginsCreatedBetween(next_day, base::Time(),
1592 base::Closure());
1593 } else {
1594 test->store()->RemoveLoginsSyncedBetween(next_day, base::Time());
1596 form_other->password_value.clear();
1597 list.push_back(password_manager::PasswordStoreChange(
1598 password_manager::PasswordStoreChange::REMOVE, *form_other));
1599 EXPECT_CALL(observer, OnLoginsChanged(list));
1600 observer.WaitAndVerify(test);
1601 matching_items = owned_keychain_adapter.PasswordsFillingForm(
1602 form_other->signon_realm, form_other->scheme);
1603 EXPECT_EQ(0u, matching_items.size());
1606 } // namespace
1608 TEST_F(PasswordStoreMacTest, TestRemoveLoginsCreatedBetween) {
1609 CheckRemoveLoginsBetween(this, true);
1612 TEST_F(PasswordStoreMacTest, TestRemoveLoginsSyncedBetween) {
1613 CheckRemoveLoginsBetween(this, false);
1616 TEST_F(PasswordStoreMacTest, TestRemoveLoginsMultiProfile) {
1617 // Make sure that RemoveLoginsCreatedBetween does affect only the correct
1618 // profile.
1620 // Add a third-party password.
1621 MockAppleKeychain::KeychainTestData keychain_data = {
1622 kSecAuthenticationTypeHTMLForm, "some.domain.com",
1623 kSecProtocolTypeHTTP, "/insecure.html", 0, NULL, "20020601171500Z",
1624 "joe_user", "sekrit", false };
1625 keychain()->AddTestItem(keychain_data);
1627 // Add a password through the adapter. It has the "Chrome" creator tag.
1628 // However, it's not referenced by the password database.
1629 MacKeychainPasswordFormAdapter owned_keychain_adapter(keychain());
1630 owned_keychain_adapter.SetFindsOnlyOwnedItems(true);
1631 PasswordFormData www_form_data1 = {
1632 PasswordForm::SCHEME_HTML, "http://www.facebook.com/",
1633 "http://www.facebook.com/index.html", "login", L"username", L"password",
1634 L"submit", L"joe_user", L"sekrit", true, false, 1 };
1635 scoped_ptr<PasswordForm> www_form =
1636 CreatePasswordFormFromDataForTesting(www_form_data1);
1637 EXPECT_TRUE(owned_keychain_adapter.AddPassword(*www_form));
1639 // Add a password from the current profile.
1640 PasswordFormData www_form_data2 = {
1641 PasswordForm::SCHEME_HTML, "http://www.facebook.com/",
1642 "http://www.facebook.com/index.html", "login", L"username", L"password",
1643 L"submit", L"not_joe_user", L"12345", true, false, 1 };
1644 www_form = CreatePasswordFormFromDataForTesting(www_form_data2);
1645 store_->AddLogin(*www_form);
1646 FinishAsyncProcessing();
1648 ScopedVector<PasswordForm> matching_items;
1649 EXPECT_TRUE(login_db()->GetLogins(*www_form, &matching_items));
1650 EXPECT_EQ(1u, matching_items.size());
1652 store_->RemoveLoginsCreatedBetween(base::Time(), base::Time(),
1653 base::Closure());
1654 FinishAsyncProcessing();
1656 // Check the second facebook form is gone.
1657 EXPECT_TRUE(login_db()->GetLogins(*www_form, &matching_items));
1658 EXPECT_EQ(0u, matching_items.size());
1660 // Check the first facebook form is still there.
1661 matching_items = owned_keychain_adapter.PasswordsFillingForm(
1662 www_form->signon_realm, www_form->scheme);
1663 ASSERT_EQ(1u, matching_items.size());
1664 EXPECT_EQ(ASCIIToUTF16("joe_user"), matching_items[0]->username_value);
1666 // Check the third-party password is still there.
1667 owned_keychain_adapter.SetFindsOnlyOwnedItems(false);
1668 matching_items = owned_keychain_adapter.PasswordsFillingForm(
1669 "http://some.domain.com/insecure.html", PasswordForm::SCHEME_HTML);
1670 ASSERT_EQ(1u, matching_items.size());
1673 // Add a facebook form to the store but not to the keychain. The form is to be
1674 // implicitly deleted. However, the observers shouldn't get notified about
1675 // deletion of non-existent forms like m.facebook.com.
1676 TEST_F(PasswordStoreMacTest, SilentlyRemoveOrphanedForm) {
1677 testing::StrictMock<MockPasswordStoreObserver> mock_observer;
1678 store()->AddObserver(&mock_observer);
1680 // 1. Add a password for www.facebook.com to the LoginDatabase.
1681 PasswordFormData www_form_data = {
1682 PasswordForm::SCHEME_HTML, "http://www.facebook.com/",
1683 "http://www.facebook.com/index.html", "login",
1684 L"username", L"password", L"submit", L"joe_user", L"", true, false, 1
1686 scoped_ptr<PasswordForm> www_form(
1687 CreatePasswordFormFromDataForTesting(www_form_data));
1688 EXPECT_EQ(AddChangeForForm(*www_form), login_db()->AddLogin(*www_form));
1690 // 2. Get a PSL-matched password for m.facebook.com. The observer isn't
1691 // notified because the form isn't in the database.
1692 PasswordForm m_form(*www_form);
1693 m_form.signon_realm = "http://m.facebook.com";
1694 m_form.origin = GURL("http://m.facebook.com/index.html");
1696 MockPasswordStoreConsumer consumer;
1697 ON_CALL(consumer, OnGetPasswordStoreResultsConstRef(_))
1698 .WillByDefault(QuitUIMessageLoop());
1699 EXPECT_CALL(mock_observer, OnLoginsChanged(_)).Times(0);
1700 // The PSL-matched form isn't returned because there is no actual password in
1701 // the keychain.
1702 EXPECT_CALL(consumer, OnGetPasswordStoreResultsConstRef(IsEmpty()));
1703 store_->GetLogins(m_form, PasswordStore::ALLOW_PROMPT, &consumer);
1704 base::MessageLoop::current()->Run();
1705 ScopedVector<autofill::PasswordForm> all_forms;
1706 EXPECT_TRUE(login_db()->GetAutofillableLogins(&all_forms));
1707 EXPECT_EQ(1u, all_forms.size());
1708 ::testing::Mock::VerifyAndClearExpectations(&mock_observer);
1710 // 3. Get a password for www.facebook.com. The form is implicitly removed and
1711 // the observer is notified.
1712 password_manager::PasswordStoreChangeList list;
1713 list.push_back(password_manager::PasswordStoreChange(
1714 password_manager::PasswordStoreChange::REMOVE, *www_form));
1715 EXPECT_CALL(mock_observer, OnLoginsChanged(list));
1716 EXPECT_CALL(consumer, OnGetPasswordStoreResultsConstRef(IsEmpty()));
1717 store_->GetLogins(*www_form, PasswordStore::ALLOW_PROMPT, &consumer);
1718 base::MessageLoop::current()->Run();
1719 EXPECT_TRUE(login_db()->GetAutofillableLogins(&all_forms));
1720 EXPECT_EQ(0u, all_forms.size());
1723 // Verify that Android app passwords can be stored, retrieved, and deleted.
1724 // Regression test for http://crbug.com/455551
1725 TEST_F(PasswordStoreMacTest, StoringAndRetrievingAndroidCredentials) {
1726 PasswordForm form;
1727 form.signon_realm = "android://7x7IDboo8u9YKraUsbmVkuf1@net.rateflix.app/";
1728 form.username_value = base::UTF8ToUTF16("randomusername");
1729 form.password_value = base::UTF8ToUTF16("password");
1731 VerifyCredentialLifecycle(form);
1734 // Verify that federated credentials can be stored, retrieved and deleted.
1735 TEST_F(PasswordStoreMacTest, StoringAndRetrievingFederatedCredentials) {
1736 PasswordForm form;
1737 form.signon_realm = "android://7x7IDboo8u9YKraUsbmVkuf1@net.rateflix.app/";
1738 form.federation_url = GURL(password_manager::kTestingFederationUrlSpec);
1739 form.username_value = base::UTF8ToUTF16("randomusername");
1740 form.password_value = base::UTF8ToUTF16(""); // No password.
1742 VerifyCredentialLifecycle(form);
1745 void CheckMigrationResult(PasswordStoreMac::MigrationResult expected_result,
1746 PasswordStoreMac::MigrationResult result) {
1747 EXPECT_EQ(expected_result, result);
1748 QuitUIMessageLoop();
1751 // Import the passwords from the Keychain to LoginDatabase.
1752 TEST_F(PasswordStoreMacTest, ImportFromKeychain) {
1753 PasswordForm form1;
1754 form1.origin = GURL("http://accounts.google.com/LoginAuth");
1755 form1.signon_realm = "http://accounts.google.com/";
1756 form1.username_value = ASCIIToUTF16("my_username");
1757 form1.password_value = ASCIIToUTF16("my_password");
1759 PasswordForm form2;
1760 form2.origin = GURL("http://facebook.com/Login");
1761 form2.signon_realm = "http://facebook.com/";
1762 form2.username_value = ASCIIToUTF16("my_username");
1763 form2.password_value = ASCIIToUTF16("my_password");
1765 PasswordForm blacklisted_form;
1766 blacklisted_form.origin = GURL("http://badsite.com/Login");
1767 blacklisted_form.signon_realm = "http://badsite.com/";
1768 blacklisted_form.blacklisted_by_user = true;
1770 store()->AddLogin(form1);
1771 store()->AddLogin(form2);
1772 store()->AddLogin(blacklisted_form);
1773 FinishAsyncProcessing();
1775 ASSERT_TRUE(base::PostTaskAndReplyWithResult(
1776 thread_->task_runner().get(), FROM_HERE,
1777 base::Bind(&PasswordStoreMac::ImportFromKeychain, store()),
1778 base::Bind(&CheckMigrationResult, PasswordStoreMac::MIGRATION_OK)));
1779 FinishAsyncProcessing();
1781 // The password should be stored in the database by now.
1782 ScopedVector<PasswordForm> matching_items;
1783 EXPECT_TRUE(login_db()->GetLogins(form1, &matching_items));
1784 ASSERT_EQ(1u, matching_items.size());
1785 EXPECT_EQ(form1, *matching_items[0]);
1787 EXPECT_TRUE(login_db()->GetLogins(form2, &matching_items));
1788 ASSERT_EQ(1u, matching_items.size());
1789 EXPECT_EQ(form2, *matching_items[0]);
1791 EXPECT_TRUE(login_db()->GetLogins(blacklisted_form, &matching_items));
1792 ASSERT_EQ(1u, matching_items.size());
1793 EXPECT_EQ(blacklisted_form, *matching_items[0]);
1795 // The passwords are encrypted using a key from the Keychain.
1796 EXPECT_TRUE(histogram_tester_->GetHistogramSamplesSinceCreation(
1797 "OSX.Keychain.Access")->TotalCount());
1798 histogram_tester_.reset();
1801 // Import a federated credential while the Keychain is locked.
1802 TEST_F(PasswordStoreMacTest, ImportFederatedFromLockedKeychain) {
1803 keychain()->set_locked(true);
1804 PasswordForm form1;
1805 form1.origin = GURL("http://example.com/Login");
1806 form1.signon_realm = "http://example.com/";
1807 form1.username_value = ASCIIToUTF16("my_username");
1808 form1.federation_url = GURL("https://accounts.google.com/");
1810 store()->AddLogin(form1);
1811 FinishAsyncProcessing();
1812 ASSERT_TRUE(base::PostTaskAndReplyWithResult(
1813 thread_->task_runner().get(), FROM_HERE,
1814 base::Bind(&PasswordStoreMac::ImportFromKeychain, store()),
1815 base::Bind(&CheckMigrationResult, PasswordStoreMac::MIGRATION_OK)));
1816 FinishAsyncProcessing();
1818 ScopedVector<PasswordForm> matching_items;
1819 EXPECT_TRUE(login_db()->GetLogins(form1, &matching_items));
1820 ASSERT_EQ(1u, matching_items.size());
1821 EXPECT_EQ(form1, *matching_items[0]);
1824 // Try to import while the Keychain is locked but the encryption key had been
1825 // read earlier.
1826 TEST_F(PasswordStoreMacTest, ImportFromLockedKeychainError) {
1827 PasswordForm form1;
1828 form1.origin = GURL("http://accounts.google.com/LoginAuth");
1829 form1.signon_realm = "http://accounts.google.com/";
1830 form1.username_value = ASCIIToUTF16("my_username");
1831 form1.password_value = ASCIIToUTF16("my_password");
1832 store()->AddLogin(form1);
1833 FinishAsyncProcessing();
1835 // Add a second keychain item matching the Database entry.
1836 PasswordForm form2 = form1;
1837 form2.origin = GURL("http://accounts.google.com/Login");
1838 form2.password_value = ASCIIToUTF16("1234");
1839 MacKeychainPasswordFormAdapter adapter(keychain());
1840 EXPECT_TRUE(adapter.AddPassword(form2));
1842 keychain()->set_locked(true);
1843 ASSERT_TRUE(base::PostTaskAndReplyWithResult(
1844 thread_->task_runner().get(), FROM_HERE,
1845 base::Bind(&PasswordStoreMac::ImportFromKeychain, store()),
1846 base::Bind(&CheckMigrationResult, PasswordStoreMac::KEYCHAIN_BLOCKED)));
1847 FinishAsyncProcessing();
1849 ScopedVector<PasswordForm> matching_items;
1850 EXPECT_TRUE(login_db()->GetLogins(form1, &matching_items));
1851 ASSERT_EQ(1u, matching_items.size());
1852 EXPECT_EQ(base::string16(), matching_items[0]->password_value);
1854 histogram_tester_->ExpectUniqueSample(
1855 "PasswordManager.KeychainMigration.NumPasswordsOnFailure", 1, 1);
1856 histogram_tester_->ExpectUniqueSample(
1857 "PasswordManager.KeychainMigration.NumFailedPasswords", 1, 1);
1858 histogram_tester_->ExpectUniqueSample(
1859 "PasswordManager.KeychainMigration.NumChromeOwnedInaccessiblePasswords",
1860 2, 1);
1861 // Don't test the encryption key access.
1862 histogram_tester_.reset();