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