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 "testing/gmock/include/gmock/gmock.h"
6 #include "testing/gtest/include/gtest/gtest.h"
8 #include "base/basictypes.h"
9 #include "base/files/scoped_temp_dir.h"
10 #include "base/path_service.h"
11 #include "base/stl_util.h"
12 #include "base/strings/string_util.h"
13 #include "base/strings/utf_string_conversions.h"
14 #include "chrome/browser/password_manager/password_store_mac.h"
15 #include "chrome/browser/password_manager/password_store_mac_internal.h"
16 #include "chrome/common/chrome_paths.h"
17 #include "components/password_manager/core/browser/password_store_consumer.h"
18 #include "content/public/test/test_browser_thread.h"
19 #include "crypto/mock_apple_keychain.h"
21 using autofill::PasswordForm;
22 using base::ASCIIToUTF16;
23 using base::WideToUTF16;
24 using content::BrowserThread;
25 using crypto::MockAppleKeychain;
26 using internal_keychain_helpers::FormsMatchForMerge;
27 using internal_keychain_helpers::STRICT_FORM_MATCH;
28 using password_manager::LoginDatabase;
29 using password_manager::PasswordStore;
30 using password_manager::PasswordStoreConsumer;
33 using testing::Invoke;
34 using testing::WithArg;
38 class MockPasswordStoreConsumer : public PasswordStoreConsumer {
40 MOCK_METHOD1(OnGetPasswordStoreResults,
41 void(const std::vector<autofill::PasswordForm*>&));
43 void CopyElements(const std::vector<autofill::PasswordForm*>& forms) {
45 for (size_t i = 0; i < forms.size(); ++i) {
46 last_result.push_back(*forms[i]);
50 std::vector<PasswordForm> last_result;
53 ACTION(STLDeleteElements0) {
54 STLDeleteContainerPointers(arg0.begin(), arg0.end());
57 ACTION(QuitUIMessageLoop) {
58 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
59 base::MessageLoop::current()->Quit();
62 class TestPasswordStoreMac : public PasswordStoreMac {
65 scoped_refptr<base::SingleThreadTaskRunner> main_thread_runner,
66 scoped_refptr<base::SingleThreadTaskRunner> db_thread_runner,
67 crypto::AppleKeychain* keychain,
68 LoginDatabase* login_db)
69 : PasswordStoreMac(main_thread_runner,
75 using PasswordStoreMac::GetBackgroundTaskRunner;
78 virtual ~TestPasswordStoreMac() {}
80 DISALLOW_COPY_AND_ASSIGN(TestPasswordStoreMac);
87 class PasswordStoreMacInternalsTest : public testing::Test {
89 virtual void SetUp() {
90 MockAppleKeychain::KeychainTestData test_data[] = {
92 { kSecAuthenticationTypeHTMLForm, "some.domain.com",
93 kSecProtocolTypeHTTP, NULL, 0, NULL, "20020601171500Z",
94 "joe_user", "sekrit", false },
95 // HTML form with path.
96 { kSecAuthenticationTypeHTMLForm, "some.domain.com",
97 kSecProtocolTypeHTTP, "/insecure.html", 0, NULL, "19991231235959Z",
98 "joe_user", "sekrit", false },
99 // Secure HTML form with path.
100 { kSecAuthenticationTypeHTMLForm, "some.domain.com",
101 kSecProtocolTypeHTTPS, "/secure.html", 0, NULL, "20100908070605Z",
102 "secure_user", "password", false },
103 // True negative item.
104 { kSecAuthenticationTypeHTMLForm, "dont.remember.com",
105 kSecProtocolTypeHTTP, NULL, 0, NULL, "20000101000000Z",
107 // De-facto negative item, type one.
108 { kSecAuthenticationTypeHTMLForm, "dont.remember.com",
109 kSecProtocolTypeHTTP, NULL, 0, NULL, "20000101000000Z",
110 "Password Not Stored", "", false },
111 // De-facto negative item, type two.
112 { kSecAuthenticationTypeHTMLForm, "dont.remember.com",
113 kSecProtocolTypeHTTPS, NULL, 0, NULL, "20000101000000Z",
114 "Password Not Stored", " ", false },
115 // HTTP auth basic, with port and path.
116 { kSecAuthenticationTypeHTTPBasic, "some.domain.com",
117 kSecProtocolTypeHTTP, "/insecure.html", 4567, "low_security",
119 "basic_auth_user", "basic", false },
120 // HTTP auth digest, secure.
121 { kSecAuthenticationTypeHTTPDigest, "some.domain.com",
122 kSecProtocolTypeHTTPS, NULL, 0, "high_security", "19980330100000Z",
123 "digest_auth_user", "digest", false },
124 // An FTP password with an invalid date, for edge-case testing.
125 { kSecAuthenticationTypeDefault, "a.server.com",
126 kSecProtocolTypeFTP, NULL, 0, NULL, "20010203040",
127 "abc", "123", false },
130 keychain_ = new MockAppleKeychain();
132 for (unsigned int i = 0; i < arraysize(test_data); ++i) {
133 keychain_->AddTestItem(test_data[i]);
137 virtual void TearDown() {
138 ExpectCreatesAndFreesBalanced();
139 ExpectCreatorCodesSet();
144 // Causes a test failure unless everything returned from keychain_'s
145 // ItemCopyAttributesAndData, SearchCreateFromAttributes, and SearchCopyNext
146 // was correctly freed.
147 void ExpectCreatesAndFreesBalanced() {
148 EXPECT_EQ(0, keychain_->UnfreedSearchCount());
149 EXPECT_EQ(0, keychain_->UnfreedKeychainItemCount());
150 EXPECT_EQ(0, keychain_->UnfreedAttributeDataCount());
153 // Causes a test failure unless any Keychain items added during the test have
154 // their creator code set.
155 void ExpectCreatorCodesSet() {
156 EXPECT_TRUE(keychain_->CreatorCodesSetForAddedItems());
159 MockAppleKeychain* keychain_;
164 // Struct used for creation of PasswordForms from static arrays of data.
165 struct PasswordFormData {
166 const PasswordForm::Scheme scheme;
167 const char* signon_realm;
170 const wchar_t* submit_element;
171 const wchar_t* username_element;
172 const wchar_t* password_element;
173 const wchar_t* username_value; // Set to NULL for a blacklist entry.
174 const wchar_t* password_value;
175 const bool preferred;
176 const bool ssl_valid;
177 const double creation_time;
180 // Creates and returns a new PasswordForm built from form_data. Caller is
181 // responsible for deleting the object when finished with it.
182 static PasswordForm* CreatePasswordFormFromData(
183 const PasswordFormData& form_data) {
184 PasswordForm* form = new PasswordForm();
185 form->scheme = form_data.scheme;
186 form->preferred = form_data.preferred;
187 form->ssl_valid = form_data.ssl_valid;
188 form->date_created = base::Time::FromDoubleT(form_data.creation_time);
189 form->date_synced = form->date_created + base::TimeDelta::FromDays(1);
190 if (form_data.signon_realm)
191 form->signon_realm = std::string(form_data.signon_realm);
192 if (form_data.origin)
193 form->origin = GURL(form_data.origin);
194 if (form_data.action)
195 form->action = GURL(form_data.action);
196 if (form_data.submit_element)
197 form->submit_element = WideToUTF16(form_data.submit_element);
198 if (form_data.username_element)
199 form->username_element = WideToUTF16(form_data.username_element);
200 if (form_data.password_element)
201 form->password_element = WideToUTF16(form_data.password_element);
202 if (form_data.username_value) {
203 form->username_value = WideToUTF16(form_data.username_value);
204 if (form_data.password_value)
205 form->password_value = WideToUTF16(form_data.password_value);
207 form->blacklisted_by_user = true;
212 // Macro to simplify calling CheckFormsAgainstExpectations with a useful label.
213 #define CHECK_FORMS(forms, expectations, i) \
214 CheckFormsAgainstExpectations(forms, expectations, #forms, i)
216 // Ensures that the data in |forms| match |expectations|, causing test failures
217 // for any discrepencies.
218 // TODO(stuartmorgan): This is current order-dependent; ideally it shouldn't
219 // matter if |forms| and |expectations| are scrambled.
220 static void CheckFormsAgainstExpectations(
221 const std::vector<PasswordForm*>& forms,
222 const std::vector<PasswordFormData*>& expectations,
223 const char* forms_label, unsigned int test_number) {
224 const unsigned int kBufferSize = 128;
225 char test_label[kBufferSize];
226 snprintf(test_label, kBufferSize, "%s in test %u", forms_label, test_number);
228 EXPECT_EQ(expectations.size(), forms.size()) << test_label;
229 if (expectations.size() != forms.size())
232 for (unsigned int i = 0; i < expectations.size(); ++i) {
233 snprintf(test_label, kBufferSize, "%s in test %u, item %u",
234 forms_label, test_number, i);
235 PasswordForm* form = forms[i];
236 PasswordFormData* expectation = expectations[i];
237 EXPECT_EQ(expectation->scheme, form->scheme) << test_label;
238 EXPECT_EQ(std::string(expectation->signon_realm), form->signon_realm)
240 EXPECT_EQ(GURL(expectation->origin), form->origin) << test_label;
241 EXPECT_EQ(GURL(expectation->action), form->action) << test_label;
242 EXPECT_EQ(WideToUTF16(expectation->submit_element), form->submit_element)
244 EXPECT_EQ(WideToUTF16(expectation->username_element),
245 form->username_element) << test_label;
246 EXPECT_EQ(WideToUTF16(expectation->password_element),
247 form->password_element) << test_label;
248 if (expectation->username_value) {
249 EXPECT_EQ(WideToUTF16(expectation->username_value),
250 form->username_value) << test_label;
251 EXPECT_EQ(WideToUTF16(expectation->password_value),
252 form->password_value) << test_label;
254 EXPECT_TRUE(form->blacklisted_by_user) << test_label;
256 EXPECT_EQ(expectation->preferred, form->preferred) << test_label;
257 EXPECT_EQ(expectation->ssl_valid, form->ssl_valid) << test_label;
258 EXPECT_DOUBLE_EQ(expectation->creation_time,
259 form->date_created.ToDoubleT()) << test_label;
260 base::Time created = base::Time::FromDoubleT(expectation->creation_time);
261 EXPECT_EQ(created + base::TimeDelta::FromDays(1),
262 form->date_synced) << test_label;
268 TEST_F(PasswordStoreMacInternalsTest, TestKeychainToFormTranslation) {
270 const PasswordForm::Scheme scheme;
271 const char* signon_realm;
273 const wchar_t* username; // Set to NULL to check for a blacklist entry.
274 const wchar_t* password;
275 const bool ssl_valid;
276 const int creation_year;
277 const int creation_month;
278 const int creation_day;
279 const int creation_hour;
280 const int creation_minute;
281 const int creation_second;
284 TestExpectations expected[] = {
285 { PasswordForm::SCHEME_HTML, "http://some.domain.com/",
286 "http://some.domain.com/", L"joe_user", L"sekrit", false,
287 2002, 6, 1, 17, 15, 0 },
288 { PasswordForm::SCHEME_HTML, "http://some.domain.com/",
289 "http://some.domain.com/insecure.html", L"joe_user", L"sekrit", false,
290 1999, 12, 31, 23, 59, 59 },
291 { PasswordForm::SCHEME_HTML, "https://some.domain.com/",
292 "https://some.domain.com/secure.html", L"secure_user", L"password", true,
293 2010, 9, 8, 7, 6, 5 },
294 { PasswordForm::SCHEME_HTML, "http://dont.remember.com/",
295 "http://dont.remember.com/", NULL, NULL, false,
296 2000, 1, 1, 0, 0, 0 },
297 { PasswordForm::SCHEME_HTML, "http://dont.remember.com/",
298 "http://dont.remember.com/", NULL, NULL, false,
299 2000, 1, 1, 0, 0, 0 },
300 { PasswordForm::SCHEME_HTML, "https://dont.remember.com/",
301 "https://dont.remember.com/", NULL, NULL, true,
302 2000, 1, 1, 0, 0, 0 },
303 { PasswordForm::SCHEME_BASIC, "http://some.domain.com:4567/low_security",
304 "http://some.domain.com:4567/insecure.html", L"basic_auth_user", L"basic",
305 false, 1998, 03, 30, 10, 00, 00 },
306 { PasswordForm::SCHEME_DIGEST, "https://some.domain.com/high_security",
307 "https://some.domain.com/", L"digest_auth_user", L"digest", true,
308 1998, 3, 30, 10, 0, 0 },
309 // This one gives us an invalid date, which we will treat as a "NULL" date
311 { PasswordForm::SCHEME_OTHER, "http://a.server.com/",
312 "http://a.server.com/", L"abc", L"123", false,
313 1601, 1, 1, 0, 0, 0 },
316 for (unsigned int i = 0; i < ARRAYSIZE_UNSAFE(expected); ++i) {
317 // Create our fake KeychainItemRef; see MockAppleKeychain docs.
318 SecKeychainItemRef keychain_item =
319 reinterpret_cast<SecKeychainItemRef>(i + 1);
321 bool parsed = internal_keychain_helpers::FillPasswordFormFromKeychainItem(
322 *keychain_, keychain_item, &form, true);
324 EXPECT_TRUE(parsed) << "In iteration " << i;
326 EXPECT_EQ(expected[i].scheme, form.scheme) << "In iteration " << i;
327 EXPECT_EQ(GURL(expected[i].origin), form.origin) << "In iteration " << i;
328 EXPECT_EQ(expected[i].ssl_valid, form.ssl_valid) << "In iteration " << i;
329 EXPECT_EQ(std::string(expected[i].signon_realm), form.signon_realm)
330 << "In iteration " << i;
331 if (expected[i].username) {
332 EXPECT_EQ(WideToUTF16(expected[i].username), form.username_value)
333 << "In iteration " << i;
334 EXPECT_EQ(WideToUTF16(expected[i].password), form.password_value)
335 << "In iteration " << i;
336 EXPECT_FALSE(form.blacklisted_by_user) << "In iteration " << i;
338 EXPECT_TRUE(form.blacklisted_by_user) << "In iteration " << i;
340 base::Time::Exploded exploded_time;
341 form.date_created.UTCExplode(&exploded_time);
342 EXPECT_EQ(expected[i].creation_year, exploded_time.year)
343 << "In iteration " << i;
344 EXPECT_EQ(expected[i].creation_month, exploded_time.month)
345 << "In iteration " << i;
346 EXPECT_EQ(expected[i].creation_day, exploded_time.day_of_month)
347 << "In iteration " << i;
348 EXPECT_EQ(expected[i].creation_hour, exploded_time.hour)
349 << "In iteration " << i;
350 EXPECT_EQ(expected[i].creation_minute, exploded_time.minute)
351 << "In iteration " << i;
352 EXPECT_EQ(expected[i].creation_second, exploded_time.second)
353 << "In iteration " << i;
357 // Use an invalid ref, to make sure errors are reported.
358 SecKeychainItemRef keychain_item = reinterpret_cast<SecKeychainItemRef>(99);
360 bool parsed = internal_keychain_helpers::FillPasswordFormFromKeychainItem(
361 *keychain_, keychain_item, &form, true);
362 EXPECT_FALSE(parsed);
366 TEST_F(PasswordStoreMacInternalsTest, TestKeychainSearch) {
367 struct TestDataAndExpectation {
368 const PasswordFormData data;
369 const size_t expected_fill_matches;
370 const size_t expected_merge_matches;
372 // Most fields are left blank because we don't care about them for searching.
373 TestDataAndExpectation test_data[] = {
374 // An HTML form we've seen.
375 { { PasswordForm::SCHEME_HTML, "http://some.domain.com/",
376 NULL, NULL, NULL, NULL, NULL, L"joe_user", NULL, false, false, 0 },
378 { { PasswordForm::SCHEME_HTML, "http://some.domain.com/",
379 NULL, NULL, NULL, NULL, NULL, L"wrong_user", NULL, false, false, 0 },
381 // An HTML form we haven't seen
382 { { PasswordForm::SCHEME_HTML, "http://www.unseendomain.com/",
383 NULL, NULL, NULL, NULL, NULL, L"joe_user", NULL, false, false, 0 },
385 // Basic auth that should match.
386 { { PasswordForm::SCHEME_BASIC, "http://some.domain.com:4567/low_security",
387 NULL, NULL, NULL, NULL, NULL, L"basic_auth_user", NULL, false, false,
390 // Basic auth with the wrong port.
391 { { PasswordForm::SCHEME_BASIC, "http://some.domain.com:1111/low_security",
392 NULL, NULL, NULL, NULL, NULL, L"basic_auth_user", NULL, false, false,
395 // Digest auth we've saved under https, visited with http.
396 { { PasswordForm::SCHEME_DIGEST, "http://some.domain.com/high_security",
397 NULL, NULL, NULL, NULL, NULL, L"digest_auth_user", NULL, false, false,
400 // Digest auth that should match.
401 { { PasswordForm::SCHEME_DIGEST, "https://some.domain.com/high_security",
402 NULL, NULL, NULL, NULL, NULL, L"wrong_user", NULL, false, true, 0 },
404 // Digest auth with the wrong domain.
405 { { PasswordForm::SCHEME_DIGEST, "https://some.domain.com/other_domain",
406 NULL, NULL, NULL, NULL, NULL, L"digest_auth_user", NULL, false, true,
409 // Garbage forms should have no matches.
410 { { PasswordForm::SCHEME_HTML, "foo/bar/baz",
411 NULL, NULL, NULL, NULL, NULL, NULL, NULL, false, false, 0 }, 0, 0 },
414 MacKeychainPasswordFormAdapter keychain_adapter(keychain_);
415 MacKeychainPasswordFormAdapter owned_keychain_adapter(keychain_);
416 owned_keychain_adapter.SetFindsOnlyOwnedItems(true);
417 for (unsigned int i = 0; i < ARRAYSIZE_UNSAFE(test_data); ++i) {
418 scoped_ptr<PasswordForm> query_form(
419 CreatePasswordFormFromData(test_data[i].data));
421 // Check matches treating the form as a fill target.
422 std::vector<PasswordForm*> matching_items =
423 keychain_adapter.PasswordsFillingForm(query_form->signon_realm,
425 EXPECT_EQ(test_data[i].expected_fill_matches, matching_items.size());
426 STLDeleteElements(&matching_items);
428 // Check matches treating the form as a merging target.
429 EXPECT_EQ(test_data[i].expected_merge_matches > 0,
430 keychain_adapter.HasPasswordsMergeableWithForm(*query_form));
431 std::vector<SecKeychainItemRef> keychain_items;
432 std::vector<internal_keychain_helpers::ItemFormPair> item_form_pairs =
433 internal_keychain_helpers::
434 ExtractAllKeychainItemAttributesIntoPasswordForms(&keychain_items,
437 internal_keychain_helpers::ExtractPasswordsMergeableWithForm(
438 *keychain_, item_form_pairs, *query_form);
439 EXPECT_EQ(test_data[i].expected_merge_matches, matching_items.size());
440 STLDeleteContainerPairSecondPointers(item_form_pairs.begin(),
441 item_form_pairs.end());
442 for (std::vector<SecKeychainItemRef>::iterator i = keychain_items.begin();
443 i != keychain_items.end(); ++i) {
446 STLDeleteElements(&matching_items);
448 // None of the pre-seeded items are owned by us, so none should match an
449 // owned-passwords-only search.
450 matching_items = owned_keychain_adapter.PasswordsFillingForm(
451 query_form->signon_realm, query_form->scheme);
452 EXPECT_EQ(0U, matching_items.size());
453 STLDeleteElements(&matching_items);
457 // Changes just the origin path of |form|.
458 static void SetPasswordFormPath(PasswordForm* form, const char* path) {
459 GURL::Replacements replacement;
460 std::string new_value(path);
461 replacement.SetPathStr(new_value);
462 form->origin = form->origin.ReplaceComponents(replacement);
465 // Changes just the signon_realm port of |form|.
466 static void SetPasswordFormPort(PasswordForm* form, const char* port) {
467 GURL::Replacements replacement;
468 std::string new_value(port);
469 replacement.SetPortStr(new_value);
470 GURL signon_gurl = GURL(form->signon_realm);
471 form->signon_realm = signon_gurl.ReplaceComponents(replacement).spec();
474 // Changes just the signon_ream auth realm of |form|.
475 static void SetPasswordFormRealm(PasswordForm* form, const char* realm) {
476 GURL::Replacements replacement;
477 std::string new_value(realm);
478 replacement.SetPathStr(new_value);
479 GURL signon_gurl = GURL(form->signon_realm);
480 form->signon_realm = signon_gurl.ReplaceComponents(replacement).spec();
483 TEST_F(PasswordStoreMacInternalsTest, TestKeychainExactSearch) {
484 MacKeychainPasswordFormAdapter keychain_adapter(keychain_);
486 PasswordFormData base_form_data[] = {
487 { PasswordForm::SCHEME_HTML, "http://some.domain.com/",
488 "http://some.domain.com/insecure.html",
489 NULL, NULL, NULL, NULL, L"joe_user", NULL, true, false, 0 },
490 { PasswordForm::SCHEME_BASIC, "http://some.domain.com:4567/low_security",
491 "http://some.domain.com:4567/insecure.html",
492 NULL, NULL, NULL, NULL, L"basic_auth_user", NULL, true, false, 0 },
493 { PasswordForm::SCHEME_DIGEST, "https://some.domain.com/high_security",
494 "https://some.domain.com",
495 NULL, NULL, NULL, NULL, L"digest_auth_user", NULL, true, true, 0 },
498 for (unsigned int i = 0; i < arraysize(base_form_data); ++i) {
499 // Create a base form and make sure we find a match.
500 scoped_ptr<PasswordForm> base_form(CreatePasswordFormFromData(
502 EXPECT_TRUE(keychain_adapter.HasPasswordsMergeableWithForm(*base_form));
503 PasswordForm* match =
504 keychain_adapter.PasswordExactlyMatchingForm(*base_form);
505 EXPECT_TRUE(match != NULL);
507 EXPECT_EQ(base_form->scheme, match->scheme);
508 EXPECT_EQ(base_form->origin, match->origin);
509 EXPECT_EQ(base_form->username_value, match->username_value);
513 // Make sure that the matching isn't looser than it should be by checking
514 // that slightly altered forms don't match.
515 std::vector<PasswordForm*> modified_forms;
517 modified_forms.push_back(new PasswordForm(*base_form));
518 modified_forms.back()->username_value = ASCIIToUTF16("wrong_user");
520 modified_forms.push_back(new PasswordForm(*base_form));
521 SetPasswordFormPath(modified_forms.back(), "elsewhere.html");
523 modified_forms.push_back(new PasswordForm(*base_form));
524 modified_forms.back()->scheme = PasswordForm::SCHEME_OTHER;
526 modified_forms.push_back(new PasswordForm(*base_form));
527 SetPasswordFormPort(modified_forms.back(), "1234");
529 modified_forms.push_back(new PasswordForm(*base_form));
530 modified_forms.back()->blacklisted_by_user = true;
532 if (base_form->scheme == PasswordForm::SCHEME_BASIC ||
533 base_form->scheme == PasswordForm::SCHEME_DIGEST) {
534 modified_forms.push_back(new PasswordForm(*base_form));
535 SetPasswordFormRealm(modified_forms.back(), "incorrect");
538 for (unsigned int j = 0; j < modified_forms.size(); ++j) {
539 PasswordForm* match =
540 keychain_adapter.PasswordExactlyMatchingForm(*modified_forms[j]);
541 EXPECT_EQ(NULL, match) << "In modified version " << j << " of base form "
544 STLDeleteElements(&modified_forms);
548 TEST_F(PasswordStoreMacInternalsTest, TestKeychainAdd) {
549 struct TestDataAndExpectation {
550 PasswordFormData data;
553 TestDataAndExpectation test_data[] = {
554 // Test a variety of scheme/port/protocol/path variations.
555 { { PasswordForm::SCHEME_HTML, "http://web.site.com/",
556 "http://web.site.com/path/to/page.html", NULL, NULL, NULL, NULL,
557 L"anonymous", L"knock-knock", false, false, 0 }, true },
558 { { PasswordForm::SCHEME_HTML, "https://web.site.com/",
559 "https://web.site.com/", NULL, NULL, NULL, NULL,
560 L"admin", L"p4ssw0rd", false, false, 0 }, true },
561 { { PasswordForm::SCHEME_BASIC, "http://a.site.com:2222/therealm",
562 "http://a.site.com:2222/", NULL, NULL, NULL, NULL,
563 L"username", L"password", false, false, 0 }, true },
564 { { PasswordForm::SCHEME_DIGEST, "https://digest.site.com/differentrealm",
565 "https://digest.site.com/secure.html", NULL, NULL, NULL, NULL,
566 L"testname", L"testpass", false, false, 0 }, true },
567 // Make sure that garbage forms are rejected.
568 { { PasswordForm::SCHEME_HTML, "gobbledygook",
569 "gobbledygook", NULL, NULL, NULL, NULL,
570 L"anonymous", L"knock-knock", false, false, 0 }, false },
571 // Test that failing to update a duplicate (forced using the magic failure
572 // password; see MockAppleKeychain::ItemModifyAttributesAndData) is
574 { { PasswordForm::SCHEME_HTML, "http://some.domain.com",
575 "http://some.domain.com/insecure.html", NULL, NULL, NULL, NULL,
576 L"joe_user", L"fail_me", false, false, 0 }, false },
579 MacKeychainPasswordFormAdapter owned_keychain_adapter(keychain_);
580 owned_keychain_adapter.SetFindsOnlyOwnedItems(true);
582 for (unsigned int i = 0; i < ARRAYSIZE_UNSAFE(test_data); ++i) {
583 scoped_ptr<PasswordForm> in_form(
584 CreatePasswordFormFromData(test_data[i].data));
585 bool add_succeeded = owned_keychain_adapter.AddPassword(*in_form);
586 EXPECT_EQ(test_data[i].should_succeed, add_succeeded);
588 EXPECT_TRUE(owned_keychain_adapter.HasPasswordsMergeableWithForm(
590 scoped_ptr<PasswordForm> out_form(
591 owned_keychain_adapter.PasswordExactlyMatchingForm(*in_form));
592 EXPECT_TRUE(out_form.get() != NULL);
593 EXPECT_EQ(out_form->scheme, in_form->scheme);
594 EXPECT_EQ(out_form->signon_realm, in_form->signon_realm);
595 EXPECT_EQ(out_form->origin, in_form->origin);
596 EXPECT_EQ(out_form->username_value, in_form->username_value);
597 EXPECT_EQ(out_form->password_value, in_form->password_value);
601 // Test that adding duplicate item updates the existing item.
603 PasswordFormData data = {
604 PasswordForm::SCHEME_HTML, "http://some.domain.com",
605 "http://some.domain.com/insecure.html", NULL,
606 NULL, NULL, NULL, L"joe_user", L"updated_password", false, false, 0
608 scoped_ptr<PasswordForm> update_form(CreatePasswordFormFromData(data));
609 MacKeychainPasswordFormAdapter keychain_adapter(keychain_);
610 EXPECT_TRUE(keychain_adapter.AddPassword(*update_form));
611 SecKeychainItemRef keychain_item = reinterpret_cast<SecKeychainItemRef>(2);
612 PasswordForm stored_form;
613 internal_keychain_helpers::FillPasswordFormFromKeychainItem(*keychain_,
617 EXPECT_EQ(update_form->password_value, stored_form.password_value);
621 TEST_F(PasswordStoreMacInternalsTest, TestKeychainRemove) {
622 struct TestDataAndExpectation {
623 PasswordFormData data;
626 TestDataAndExpectation test_data[] = {
627 // Test deletion of an item that we add.
628 { { PasswordForm::SCHEME_HTML, "http://web.site.com/",
629 "http://web.site.com/path/to/page.html", NULL, NULL, NULL, NULL,
630 L"anonymous", L"knock-knock", false, false, 0 }, true },
631 // Make sure we don't delete items we don't own.
632 { { PasswordForm::SCHEME_HTML, "http://some.domain.com/",
633 "http://some.domain.com/insecure.html", NULL, NULL, NULL, NULL,
634 L"joe_user", NULL, true, false, 0 }, false },
637 MacKeychainPasswordFormAdapter owned_keychain_adapter(keychain_);
638 owned_keychain_adapter.SetFindsOnlyOwnedItems(true);
640 // Add our test item so that we can delete it.
641 PasswordForm* add_form = CreatePasswordFormFromData(test_data[0].data);
642 EXPECT_TRUE(owned_keychain_adapter.AddPassword(*add_form));
645 for (unsigned int i = 0; i < ARRAYSIZE_UNSAFE(test_data); ++i) {
646 scoped_ptr<PasswordForm> form(CreatePasswordFormFromData(
648 EXPECT_EQ(test_data[i].should_succeed,
649 owned_keychain_adapter.RemovePassword(*form));
651 MacKeychainPasswordFormAdapter keychain_adapter(keychain_);
652 PasswordForm* match = keychain_adapter.PasswordExactlyMatchingForm(*form);
653 EXPECT_EQ(test_data[i].should_succeed, match == NULL);
660 TEST_F(PasswordStoreMacInternalsTest, TestFormMatch) {
661 PasswordForm base_form;
662 base_form.signon_realm = std::string("http://some.domain.com/");
663 base_form.origin = GURL("http://some.domain.com/page.html");
664 base_form.username_value = ASCIIToUTF16("joe_user");
667 // Check that everything unimportant can be changed.
668 PasswordForm different_form(base_form);
669 different_form.username_element = ASCIIToUTF16("username");
670 different_form.submit_element = ASCIIToUTF16("submit");
671 different_form.username_element = ASCIIToUTF16("password");
672 different_form.password_value = ASCIIToUTF16("sekrit");
673 different_form.action = GURL("http://some.domain.com/action.cgi");
674 different_form.ssl_valid = true;
675 different_form.preferred = true;
676 different_form.date_created = base::Time::Now();
678 FormsMatchForMerge(base_form, different_form, STRICT_FORM_MATCH));
680 // Check that path differences don't prevent a match.
681 base_form.origin = GURL("http://some.domain.com/other_page.html");
683 FormsMatchForMerge(base_form, different_form, STRICT_FORM_MATCH));
686 // Check that any one primary key changing is enough to prevent matching.
688 PasswordForm different_form(base_form);
689 different_form.scheme = PasswordForm::SCHEME_DIGEST;
691 FormsMatchForMerge(base_form, different_form, STRICT_FORM_MATCH));
694 PasswordForm different_form(base_form);
695 different_form.signon_realm = std::string("http://some.domain.com:8080/");
697 FormsMatchForMerge(base_form, different_form, STRICT_FORM_MATCH));
700 PasswordForm different_form(base_form);
701 different_form.username_value = ASCIIToUTF16("john.doe");
703 FormsMatchForMerge(base_form, different_form, STRICT_FORM_MATCH));
706 PasswordForm different_form(base_form);
707 different_form.blacklisted_by_user = true;
709 FormsMatchForMerge(base_form, different_form, STRICT_FORM_MATCH));
712 // Blacklist forms should *never* match for merging, even when identical
713 // (and certainly not when only one is a blacklist entry).
715 PasswordForm form_a(base_form);
716 form_a.blacklisted_by_user = true;
717 PasswordForm form_b(form_a);
718 EXPECT_FALSE(FormsMatchForMerge(form_a, form_b, STRICT_FORM_MATCH));
722 TEST_F(PasswordStoreMacInternalsTest, TestFormMerge) {
723 // Set up a bunch of test data to use in varying combinations.
724 PasswordFormData keychain_user_1 =
725 { PasswordForm::SCHEME_HTML, "http://some.domain.com/",
726 "http://some.domain.com/", "", L"", L"", L"", L"joe_user", L"sekrit",
727 false, false, 1010101010 };
728 PasswordFormData keychain_user_1_with_path =
729 { PasswordForm::SCHEME_HTML, "http://some.domain.com/",
730 "http://some.domain.com/page.html",
731 "", L"", L"", L"", L"joe_user", L"otherpassword",
732 false, false, 1010101010 };
733 PasswordFormData keychain_user_2 =
734 { PasswordForm::SCHEME_HTML, "http://some.domain.com/",
735 "http://some.domain.com/", "", L"", L"", L"", L"john.doe", L"sesame",
736 false, false, 958739876 };
737 PasswordFormData keychain_blacklist =
738 { PasswordForm::SCHEME_HTML, "http://some.domain.com/",
739 "http://some.domain.com/", "", L"", L"", L"", NULL, NULL,
740 false, false, 1010101010 };
742 PasswordFormData db_user_1 =
743 { PasswordForm::SCHEME_HTML, "http://some.domain.com/",
744 "http://some.domain.com/", "http://some.domain.com/action.cgi",
745 L"submit", L"username", L"password", L"joe_user", L"",
746 true, false, 1212121212 };
747 PasswordFormData db_user_1_with_path =
748 { PasswordForm::SCHEME_HTML, "http://some.domain.com/",
749 "http://some.domain.com/page.html",
750 "http://some.domain.com/handlepage.cgi",
751 L"submit", L"username", L"password", L"joe_user", L"",
752 true, false, 1234567890 };
753 PasswordFormData db_user_3_with_path =
754 { PasswordForm::SCHEME_HTML, "http://some.domain.com/",
755 "http://some.domain.com/page.html",
756 "http://some.domain.com/handlepage.cgi",
757 L"submit", L"username", L"password", L"second-account", L"",
758 true, false, 1240000000 };
759 PasswordFormData database_blacklist_with_path =
760 { PasswordForm::SCHEME_HTML, "http://some.domain.com/",
761 "http://some.domain.com/path.html", "http://some.domain.com/action.cgi",
762 L"submit", L"username", L"password", NULL, NULL,
763 true, false, 1212121212 };
765 PasswordFormData merged_user_1 =
766 { PasswordForm::SCHEME_HTML, "http://some.domain.com/",
767 "http://some.domain.com/", "http://some.domain.com/action.cgi",
768 L"submit", L"username", L"password", L"joe_user", L"sekrit",
769 true, false, 1212121212 };
770 PasswordFormData merged_user_1_with_db_path =
771 { PasswordForm::SCHEME_HTML, "http://some.domain.com/",
772 "http://some.domain.com/page.html",
773 "http://some.domain.com/handlepage.cgi",
774 L"submit", L"username", L"password", L"joe_user", L"sekrit",
775 true, false, 1234567890 };
776 PasswordFormData merged_user_1_with_both_paths =
777 { PasswordForm::SCHEME_HTML, "http://some.domain.com/",
778 "http://some.domain.com/page.html",
779 "http://some.domain.com/handlepage.cgi",
780 L"submit", L"username", L"password", L"joe_user", L"otherpassword",
781 true, false, 1234567890 };
783 // Build up the big multi-dimensional array of data sets that will actually
784 // drive the test. Use vectors rather than arrays so that initialization is
792 MERGE_IO_ARRAY_COUNT // termination marker
794 const unsigned int kTestCount = 4;
795 std::vector< std::vector< std::vector<PasswordFormData*> > > test_data(
796 MERGE_IO_ARRAY_COUNT, std::vector< std::vector<PasswordFormData*> >(
797 kTestCount, std::vector<PasswordFormData*>()));
798 unsigned int current_test = 0;
800 // Test a merge with a few accounts in both systems, with partial overlap.
801 CHECK(current_test < kTestCount);
802 test_data[KEYCHAIN_INPUT][current_test].push_back(&keychain_user_1);
803 test_data[KEYCHAIN_INPUT][current_test].push_back(&keychain_user_2);
804 test_data[DATABASE_INPUT][current_test].push_back(&db_user_1);
805 test_data[DATABASE_INPUT][current_test].push_back(&db_user_1_with_path);
806 test_data[DATABASE_INPUT][current_test].push_back(&db_user_3_with_path);
807 test_data[MERGE_OUTPUT][current_test].push_back(&merged_user_1);
808 test_data[MERGE_OUTPUT][current_test].push_back(&merged_user_1_with_db_path);
809 test_data[KEYCHAIN_OUTPUT][current_test].push_back(&keychain_user_2);
810 test_data[DATABASE_OUTPUT][current_test].push_back(&db_user_3_with_path);
812 // Test a merge where Chrome has a blacklist entry, and the keychain has
815 CHECK(current_test < kTestCount);
816 test_data[KEYCHAIN_INPUT][current_test].push_back(&keychain_user_1);
817 test_data[DATABASE_INPUT][current_test].push_back(
818 &database_blacklist_with_path);
819 // We expect both to be present because a blacklist could be specific to a
820 // subpath, and we want access to the password on other paths.
821 test_data[MERGE_OUTPUT][current_test].push_back(
822 &database_blacklist_with_path);
823 test_data[KEYCHAIN_OUTPUT][current_test].push_back(&keychain_user_1);
825 // Test a merge where Chrome has an account, and Keychain has a blacklist
826 // (from another browser) and the Chrome password data.
828 CHECK(current_test < kTestCount);
829 test_data[KEYCHAIN_INPUT][current_test].push_back(&keychain_blacklist);
830 test_data[KEYCHAIN_INPUT][current_test].push_back(&keychain_user_1);
831 test_data[DATABASE_INPUT][current_test].push_back(&db_user_1);
832 test_data[MERGE_OUTPUT][current_test].push_back(&merged_user_1);
833 test_data[KEYCHAIN_OUTPUT][current_test].push_back(&keychain_blacklist);
835 // Test that matches are done using exact path when possible.
837 CHECK(current_test < kTestCount);
838 test_data[KEYCHAIN_INPUT][current_test].push_back(&keychain_user_1);
839 test_data[KEYCHAIN_INPUT][current_test].push_back(&keychain_user_1_with_path);
840 test_data[DATABASE_INPUT][current_test].push_back(&db_user_1);
841 test_data[DATABASE_INPUT][current_test].push_back(&db_user_1_with_path);
842 test_data[MERGE_OUTPUT][current_test].push_back(&merged_user_1);
843 test_data[MERGE_OUTPUT][current_test].push_back(
844 &merged_user_1_with_both_paths);
846 for (unsigned int test_case = 0; test_case <= current_test; ++test_case) {
847 std::vector<PasswordForm*> keychain_forms;
848 for (std::vector<PasswordFormData*>::iterator i =
849 test_data[KEYCHAIN_INPUT][test_case].begin();
850 i != test_data[KEYCHAIN_INPUT][test_case].end(); ++i) {
851 keychain_forms.push_back(CreatePasswordFormFromData(*(*i)));
853 std::vector<PasswordForm*> database_forms;
854 for (std::vector<PasswordFormData*>::iterator i =
855 test_data[DATABASE_INPUT][test_case].begin();
856 i != test_data[DATABASE_INPUT][test_case].end(); ++i) {
857 database_forms.push_back(CreatePasswordFormFromData(*(*i)));
860 std::vector<PasswordForm*> merged_forms;
861 internal_keychain_helpers::MergePasswordForms(&keychain_forms,
865 CHECK_FORMS(keychain_forms, test_data[KEYCHAIN_OUTPUT][test_case],
867 CHECK_FORMS(database_forms, test_data[DATABASE_OUTPUT][test_case],
869 CHECK_FORMS(merged_forms, test_data[MERGE_OUTPUT][test_case], test_case);
871 STLDeleteElements(&keychain_forms);
872 STLDeleteElements(&database_forms);
873 STLDeleteElements(&merged_forms);
877 TEST_F(PasswordStoreMacInternalsTest, TestPasswordBulkLookup) {
878 PasswordFormData db_data[] = {
879 { PasswordForm::SCHEME_HTML, "http://some.domain.com/",
880 "http://some.domain.com/", "http://some.domain.com/action.cgi",
881 L"submit", L"username", L"password", L"joe_user", L"",
882 true, false, 1212121212 },
883 { PasswordForm::SCHEME_HTML, "http://some.domain.com/",
884 "http://some.domain.com/page.html",
885 "http://some.domain.com/handlepage.cgi",
886 L"submit", L"username", L"password", L"joe_user", L"",
887 true, false, 1234567890 },
888 { PasswordForm::SCHEME_HTML, "http://some.domain.com/",
889 "http://some.domain.com/page.html",
890 "http://some.domain.com/handlepage.cgi",
891 L"submit", L"username", L"password", L"second-account", L"",
892 true, false, 1240000000 },
893 { PasswordForm::SCHEME_HTML, "http://dont.remember.com/",
894 "http://dont.remember.com/",
895 "http://dont.remember.com/handlepage.cgi",
896 L"submit", L"username", L"password", L"joe_user", L"",
897 true, false, 1240000000 },
898 { PasswordForm::SCHEME_HTML, "http://some.domain.com/",
899 "http://some.domain.com/path.html", "http://some.domain.com/action.cgi",
900 L"submit", L"username", L"password", NULL, NULL,
901 true, false, 1212121212 },
903 std::vector<PasswordForm*> database_forms;
904 for (unsigned int i = 0; i < ARRAYSIZE_UNSAFE(db_data); ++i) {
905 database_forms.push_back(CreatePasswordFormFromData(db_data[i]));
907 std::vector<PasswordForm*> merged_forms =
908 internal_keychain_helpers::GetPasswordsForForms(*keychain_,
910 EXPECT_EQ(2U, database_forms.size());
911 ASSERT_EQ(3U, merged_forms.size());
912 EXPECT_EQ(ASCIIToUTF16("sekrit"), merged_forms[0]->password_value);
913 EXPECT_EQ(ASCIIToUTF16("sekrit"), merged_forms[1]->password_value);
914 EXPECT_TRUE(merged_forms[2]->blacklisted_by_user);
916 STLDeleteElements(&database_forms);
917 STLDeleteElements(&merged_forms);
920 TEST_F(PasswordStoreMacInternalsTest, TestBlacklistedFiltering) {
921 PasswordFormData db_data[] = {
922 { PasswordForm::SCHEME_HTML, "http://dont.remember.com/",
923 "http://dont.remember.com/",
924 "http://dont.remember.com/handlepage.cgi",
925 L"submit", L"username", L"password", L"joe_user", L"non_empty_password",
926 true, false, 1240000000 },
927 { PasswordForm::SCHEME_HTML, "https://dont.remember.com/",
928 "https://dont.remember.com/",
929 "https://dont.remember.com/handlepage_secure.cgi",
930 L"submit", L"username", L"password", L"joe_user", L"non_empty_password",
931 true, false, 1240000000 },
933 std::vector<PasswordForm*> database_forms;
934 for (unsigned int i = 0; i < ARRAYSIZE_UNSAFE(db_data); ++i) {
935 database_forms.push_back(CreatePasswordFormFromData(db_data[i]));
937 std::vector<PasswordForm*> merged_forms =
938 internal_keychain_helpers::GetPasswordsForForms(*keychain_,
940 EXPECT_EQ(2U, database_forms.size());
941 ASSERT_EQ(0U, merged_forms.size());
943 STLDeleteElements(&database_forms);
944 STLDeleteElements(&merged_forms);
947 TEST_F(PasswordStoreMacInternalsTest, TestFillPasswordFormFromKeychainItem) {
948 // When |extract_password_data| is false, the password field must be empty,
949 // and |blacklisted_by_user| must be false.
950 SecKeychainItemRef keychain_item = reinterpret_cast<SecKeychainItemRef>(1);
951 PasswordForm form_without_extracted_password;
952 bool parsed = internal_keychain_helpers::FillPasswordFormFromKeychainItem(
955 &form_without_extracted_password,
956 false); // Do not extract password.
958 ASSERT_TRUE(form_without_extracted_password.password_value.empty());
959 ASSERT_FALSE(form_without_extracted_password.blacklisted_by_user);
961 // When |extract_password_data| is true and the keychain entry has a non-empty
962 // password, the password field must be non-empty, and the value of
963 // |blacklisted_by_user| must be false.
964 keychain_item = reinterpret_cast<SecKeychainItemRef>(1);
965 PasswordForm form_with_extracted_password;
966 parsed = internal_keychain_helpers::FillPasswordFormFromKeychainItem(
969 &form_with_extracted_password,
970 true); // Extract password.
972 ASSERT_EQ(ASCIIToUTF16("sekrit"),
973 form_with_extracted_password.password_value);
974 ASSERT_FALSE(form_with_extracted_password.blacklisted_by_user);
976 // When |extract_password_data| is true and the keychain entry has an empty
977 // username and password (""), the password field must be empty, and the value
978 // of |blacklisted_by_user| must be true.
979 keychain_item = reinterpret_cast<SecKeychainItemRef>(4);
980 PasswordForm negative_form;
981 parsed = internal_keychain_helpers::FillPasswordFormFromKeychainItem(
985 true); // Extract password.
987 ASSERT_TRUE(negative_form.username_value.empty());
988 ASSERT_TRUE(negative_form.password_value.empty());
989 ASSERT_TRUE(negative_form.blacklisted_by_user);
991 // When |extract_password_data| is true and the keychain entry has an empty
992 // password (""), the password field must be empty (""), and the value of
993 // |blacklisted_by_user| must be true.
994 keychain_item = reinterpret_cast<SecKeychainItemRef>(5);
995 PasswordForm form_with_empty_password_a;
996 parsed = internal_keychain_helpers::FillPasswordFormFromKeychainItem(
999 &form_with_empty_password_a,
1000 true); // Extract password.
1001 EXPECT_TRUE(parsed);
1002 ASSERT_TRUE(form_with_empty_password_a.password_value.empty());
1003 ASSERT_TRUE(form_with_empty_password_a.blacklisted_by_user);
1005 // When |extract_password_data| is true and the keychain entry has a single
1006 // space password (" "), the password field must be a single space (" "), and
1007 // the value of |blacklisted_by_user| must be true.
1008 keychain_item = reinterpret_cast<SecKeychainItemRef>(6);
1009 PasswordForm form_with_empty_password_b;
1010 parsed = internal_keychain_helpers::FillPasswordFormFromKeychainItem(
1013 &form_with_empty_password_b,
1014 true); // Extract password.
1015 EXPECT_TRUE(parsed);
1016 ASSERT_EQ(ASCIIToUTF16(" "),
1017 form_with_empty_password_b.password_value);
1018 ASSERT_TRUE(form_with_empty_password_b.blacklisted_by_user);
1021 TEST_F(PasswordStoreMacInternalsTest, TestPasswordGetAll) {
1022 MacKeychainPasswordFormAdapter keychain_adapter(keychain_);
1023 MacKeychainPasswordFormAdapter owned_keychain_adapter(keychain_);
1024 owned_keychain_adapter.SetFindsOnlyOwnedItems(true);
1026 // Add a few passwords of various types so that we own some.
1027 PasswordFormData owned_password_data[] = {
1028 { PasswordForm::SCHEME_HTML, "http://web.site.com/",
1029 "http://web.site.com/path/to/page.html", NULL, NULL, NULL, NULL,
1030 L"anonymous", L"knock-knock", false, false, 0 },
1031 { PasswordForm::SCHEME_BASIC, "http://a.site.com:2222/therealm",
1032 "http://a.site.com:2222/", NULL, NULL, NULL, NULL,
1033 L"username", L"password", false, false, 0 },
1034 { PasswordForm::SCHEME_DIGEST, "https://digest.site.com/differentrealm",
1035 "https://digest.site.com/secure.html", NULL, NULL, NULL, NULL,
1036 L"testname", L"testpass", false, false, 0 },
1038 for (unsigned int i = 0; i < arraysize(owned_password_data); ++i) {
1039 scoped_ptr<PasswordForm> form(CreatePasswordFormFromData(
1040 owned_password_data[i]));
1041 owned_keychain_adapter.AddPassword(*form);
1044 std::vector<PasswordForm*> all_passwords =
1045 keychain_adapter.GetAllPasswordFormPasswords();
1046 EXPECT_EQ(8 + arraysize(owned_password_data), all_passwords.size());
1047 STLDeleteElements(&all_passwords);
1049 std::vector<PasswordForm*> owned_passwords =
1050 owned_keychain_adapter.GetAllPasswordFormPasswords();
1051 EXPECT_EQ(arraysize(owned_password_data), owned_passwords.size());
1052 STLDeleteElements(&owned_passwords);
1057 class PasswordStoreMacTest : public testing::Test {
1059 PasswordStoreMacTest() : ui_thread_(BrowserThread::UI, &message_loop_) {}
1061 virtual void SetUp() {
1062 login_db_ = new LoginDatabase();
1063 ASSERT_TRUE(db_dir_.CreateUniqueTempDir());
1064 base::FilePath db_file = db_dir_.path().AppendASCII("login.db");
1065 ASSERT_TRUE(login_db_->Init(db_file));
1067 keychain_ = new MockAppleKeychain();
1069 store_ = new TestPasswordStoreMac(
1070 base::MessageLoopProxy::current(),
1071 base::MessageLoopProxy::current(),
1074 ASSERT_TRUE(store_->Init(syncer::SyncableService::StartSyncFlare()));
1077 virtual void TearDown() {
1079 EXPECT_FALSE(store_->GetBackgroundTaskRunner());
1082 void WaitForStoreUpdate() {
1083 // Do a store-level query to wait for all the operations above to be done.
1084 MockPasswordStoreConsumer consumer;
1085 EXPECT_CALL(consumer, OnGetPasswordStoreResults(_))
1086 .WillOnce(DoAll(WithArg<0>(STLDeleteElements0()), QuitUIMessageLoop()));
1087 store_->GetLogins(PasswordForm(), PasswordStore::ALLOW_PROMPT, &consumer);
1088 base::MessageLoop::current()->Run();
1092 base::MessageLoopForUI message_loop_;
1093 content::TestBrowserThread ui_thread_;
1095 MockAppleKeychain* keychain_; // Owned by store_.
1096 LoginDatabase* login_db_; // Owned by store_.
1097 scoped_refptr<TestPasswordStoreMac> store_;
1098 base::ScopedTempDir db_dir_;
1101 TEST_F(PasswordStoreMacTest, TestStoreUpdate) {
1102 // Insert a password into both the database and the keychain.
1103 // This is done manually, rather than through store_->AddLogin, because the
1104 // Mock Keychain isn't smart enough to be able to support update generically,
1105 // so some.domain.com triggers special handling to test it that make inserting
1107 PasswordFormData joint_data = {
1108 PasswordForm::SCHEME_HTML, "http://some.domain.com/",
1109 "http://some.domain.com/insecure.html", "login.cgi",
1110 L"username", L"password", L"submit", L"joe_user", L"sekrit", true, false, 1
1112 scoped_ptr<PasswordForm> joint_form(CreatePasswordFormFromData(joint_data));
1113 login_db_->AddLogin(*joint_form);
1114 MockAppleKeychain::KeychainTestData joint_keychain_data = {
1115 kSecAuthenticationTypeHTMLForm, "some.domain.com",
1116 kSecProtocolTypeHTTP, "/insecure.html", 0, NULL, "20020601171500Z",
1117 "joe_user", "sekrit", false };
1118 keychain_->AddTestItem(joint_keychain_data);
1120 // Insert a password into the keychain only.
1121 MockAppleKeychain::KeychainTestData keychain_only_data = {
1122 kSecAuthenticationTypeHTMLForm, "keychain.only.com",
1123 kSecProtocolTypeHTTP, NULL, 0, NULL, "20020601171500Z",
1124 "keychain", "only", false
1126 keychain_->AddTestItem(keychain_only_data);
1129 PasswordFormData form_data;
1130 const char* password; // NULL indicates no entry should be present.
1133 // Make a series of update calls.
1134 UpdateData updates[] = {
1135 // Update the keychain+db passwords (the normal password update case).
1136 { { PasswordForm::SCHEME_HTML, "http://some.domain.com/",
1137 "http://some.domain.com/insecure.html", "login.cgi",
1138 L"username", L"password", L"submit", L"joe_user", L"53krit",
1142 // Update the keychain-only password; this simulates the initial use of a
1143 // password stored by another browsers.
1144 { { PasswordForm::SCHEME_HTML, "http://keychain.only.com/",
1145 "http://keychain.only.com/login.html", "login.cgi",
1146 L"username", L"password", L"submit", L"keychain", L"only",
1150 // Update a password that doesn't exist in either location. This tests the
1151 // case where a form is filled, then the stored login is removed, then the
1152 // form is submitted.
1153 { { PasswordForm::SCHEME_HTML, "http://different.com/",
1154 "http://different.com/index.html", "login.cgi",
1155 L"username", L"password", L"submit", L"abc", L"123",
1160 for (unsigned int i = 0; i < ARRAYSIZE_UNSAFE(updates); ++i) {
1161 scoped_ptr<PasswordForm> form(CreatePasswordFormFromData(
1162 updates[i].form_data));
1163 store_->UpdateLogin(*form);
1166 WaitForStoreUpdate();
1168 MacKeychainPasswordFormAdapter keychain_adapter(keychain_);
1169 for (unsigned int i = 0; i < ARRAYSIZE_UNSAFE(updates); ++i) {
1170 scoped_ptr<PasswordForm> query_form(
1171 CreatePasswordFormFromData(updates[i].form_data));
1173 std::vector<PasswordForm*> matching_items =
1174 keychain_adapter.PasswordsFillingForm(query_form->signon_realm,
1175 query_form->scheme);
1176 if (updates[i].password) {
1177 EXPECT_GT(matching_items.size(), 0U) << "iteration " << i;
1178 if (matching_items.size() >= 1)
1179 EXPECT_EQ(ASCIIToUTF16(updates[i].password),
1180 matching_items[0]->password_value) << "iteration " << i;
1182 EXPECT_EQ(0U, matching_items.size()) << "iteration " << i;
1184 STLDeleteElements(&matching_items);
1186 login_db_->GetLogins(*query_form, &matching_items);
1187 EXPECT_EQ(updates[i].password ? 1U : 0U, matching_items.size())
1188 << "iteration " << i;
1189 STLDeleteElements(&matching_items);
1193 TEST_F(PasswordStoreMacTest, TestDBKeychainAssociation) {
1194 // Tests that association between the keychain and login database parts of a
1195 // password added by fuzzy (PSL) matching works.
1196 // 1. Add a password for www.facebook.com
1197 // 2. Get a password for m.facebook.com. This fuzzy matches and returns the
1198 // www.facebook.com password.
1199 // 3. Add the returned password for m.facebook.com.
1200 // 4. Remove both passwords.
1201 // -> check: that both are gone from the login DB and the keychain
1202 // This test should in particular ensure that we don't keep passwords in the
1203 // keychain just before we think we still have other (fuzzy-)matching entries
1204 // for them in the login database. (For example, here if we deleted the
1205 // www.facebook.com password from the login database, we should not be blocked
1206 // from deleting it from the keystore just becaus the m.facebook.com password
1207 // fuzzy-matches the www.facebook.com one.)
1209 // 1. Add a password for www.facebook.com
1210 PasswordFormData www_form_data = {
1211 PasswordForm::SCHEME_HTML, "http://www.facebook.com/",
1212 "http://www.facebook.com/index.html", "login",
1213 L"username", L"password", L"submit", L"joe_user", L"sekrit", true, false, 1
1215 scoped_ptr<PasswordForm> www_form(CreatePasswordFormFromData(www_form_data));
1216 login_db_->AddLogin(*www_form);
1217 MacKeychainPasswordFormAdapter owned_keychain_adapter(keychain_);
1218 owned_keychain_adapter.SetFindsOnlyOwnedItems(true);
1219 owned_keychain_adapter.AddPassword(*www_form);
1221 // 2. Get a password for m.facebook.com.
1222 PasswordForm m_form(*www_form);
1223 m_form.signon_realm = "http://m.facebook.com";
1224 m_form.origin = GURL("http://m.facebook.com/index.html");
1225 MockPasswordStoreConsumer consumer;
1226 EXPECT_CALL(consumer, OnGetPasswordStoreResults(_)).WillOnce(DoAll(
1227 WithArg<0>(Invoke(&consumer, &MockPasswordStoreConsumer::CopyElements)),
1228 WithArg<0>(STLDeleteElements0()),
1229 QuitUIMessageLoop()));
1230 store_->GetLogins(m_form, PasswordStore::ALLOW_PROMPT, &consumer);
1231 base::MessageLoop::current()->Run();
1232 EXPECT_EQ(1u, consumer.last_result.size());
1234 // 3. Add the returned password for m.facebook.com.
1235 login_db_->AddLogin(consumer.last_result[0]);
1236 owned_keychain_adapter.AddPassword(m_form);
1238 // 4. Remove both passwords.
1239 store_->RemoveLogin(*www_form);
1240 store_->RemoveLogin(m_form);
1241 WaitForStoreUpdate();
1243 std::vector<PasswordForm*> matching_items;
1244 // No trace of www.facebook.com.
1245 matching_items = owned_keychain_adapter.PasswordsFillingForm(
1246 www_form->signon_realm, www_form->scheme);
1247 EXPECT_EQ(0u, matching_items.size());
1248 login_db_->GetLogins(*www_form, &matching_items);
1249 EXPECT_EQ(0u, matching_items.size());
1250 // No trace of m.facebook.com.
1251 matching_items = owned_keychain_adapter.PasswordsFillingForm(
1252 m_form.signon_realm, m_form.scheme);
1253 EXPECT_EQ(0u, matching_items.size());
1254 login_db_->GetLogins(m_form, &matching_items);
1255 EXPECT_EQ(0u, matching_items.size());