Cast: Stop logging kVideoFrameSentToEncoder and rename a couple events.
[chromium-blink-merge.git] / chrome / browser / password_manager / password_store_mac_unittest.cc
blob82e60db9a1f1a6bbc4887d724f94ff4c9daadefa
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;
31 using testing::_;
32 using testing::DoAll;
33 using testing::Invoke;
34 using testing::WithArg;
36 namespace {
38 class MockPasswordStoreConsumer : public PasswordStoreConsumer {
39 public:
40 MOCK_METHOD1(OnGetPasswordStoreResults,
41 void(const std::vector<autofill::PasswordForm*>&));
43 void CopyElements(const std::vector<autofill::PasswordForm*>& forms) {
44 last_result.clear();
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 {
63 public:
64 TestPasswordStoreMac(
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,
70 db_thread_runner,
71 keychain,
72 login_db) {
75 using PasswordStoreMac::GetBackgroundTaskRunner;
77 private:
78 virtual ~TestPasswordStoreMac() {}
80 DISALLOW_COPY_AND_ASSIGN(TestPasswordStoreMac);
83 } // namespace
85 #pragma mark -
87 class PasswordStoreMacInternalsTest : public testing::Test {
88 public:
89 virtual void SetUp() {
90 MockAppleKeychain::KeychainTestData test_data[] = {
91 // Basic HTML form.
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",
106 "", "", true },
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",
118 "19980330100000Z",
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();
140 delete keychain_;
143 protected:
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_;
162 #pragma mark -
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;
168 const char* origin;
169 const char* action;
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 if (form_data.signon_realm)
190 form->signon_realm = std::string(form_data.signon_realm);
191 if (form_data.origin)
192 form->origin = GURL(form_data.origin);
193 if (form_data.action)
194 form->action = GURL(form_data.action);
195 if (form_data.submit_element)
196 form->submit_element = WideToUTF16(form_data.submit_element);
197 if (form_data.username_element)
198 form->username_element = WideToUTF16(form_data.username_element);
199 if (form_data.password_element)
200 form->password_element = WideToUTF16(form_data.password_element);
201 if (form_data.username_value) {
202 form->username_value = WideToUTF16(form_data.username_value);
203 if (form_data.password_value)
204 form->password_value = WideToUTF16(form_data.password_value);
205 } else {
206 form->blacklisted_by_user = true;
208 return form;
211 // Macro to simplify calling CheckFormsAgainstExpectations with a useful label.
212 #define CHECK_FORMS(forms, expectations, i) \
213 CheckFormsAgainstExpectations(forms, expectations, #forms, i)
215 // Ensures that the data in |forms| match |expectations|, causing test failures
216 // for any discrepencies.
217 // TODO(stuartmorgan): This is current order-dependent; ideally it shouldn't
218 // matter if |forms| and |expectations| are scrambled.
219 static void CheckFormsAgainstExpectations(
220 const std::vector<PasswordForm*>& forms,
221 const std::vector<PasswordFormData*>& expectations,
222 const char* forms_label, unsigned int test_number) {
223 const unsigned int kBufferSize = 128;
224 char test_label[kBufferSize];
225 snprintf(test_label, kBufferSize, "%s in test %u", forms_label, test_number);
227 EXPECT_EQ(expectations.size(), forms.size()) << test_label;
228 if (expectations.size() != forms.size())
229 return;
231 for (unsigned int i = 0; i < expectations.size(); ++i) {
232 snprintf(test_label, kBufferSize, "%s in test %u, item %u",
233 forms_label, test_number, i);
234 PasswordForm* form = forms[i];
235 PasswordFormData* expectation = expectations[i];
236 EXPECT_EQ(expectation->scheme, form->scheme) << test_label;
237 EXPECT_EQ(std::string(expectation->signon_realm), form->signon_realm)
238 << test_label;
239 EXPECT_EQ(GURL(expectation->origin), form->origin) << test_label;
240 EXPECT_EQ(GURL(expectation->action), form->action) << test_label;
241 EXPECT_EQ(WideToUTF16(expectation->submit_element), form->submit_element)
242 << test_label;
243 EXPECT_EQ(WideToUTF16(expectation->username_element),
244 form->username_element) << test_label;
245 EXPECT_EQ(WideToUTF16(expectation->password_element),
246 form->password_element) << test_label;
247 if (expectation->username_value) {
248 EXPECT_EQ(WideToUTF16(expectation->username_value),
249 form->username_value) << test_label;
250 EXPECT_EQ(WideToUTF16(expectation->password_value),
251 form->password_value) << test_label;
252 } else {
253 EXPECT_TRUE(form->blacklisted_by_user) << test_label;
255 EXPECT_EQ(expectation->preferred, form->preferred) << test_label;
256 EXPECT_EQ(expectation->ssl_valid, form->ssl_valid) << test_label;
257 EXPECT_DOUBLE_EQ(expectation->creation_time,
258 form->date_created.ToDoubleT()) << test_label;
262 #pragma mark -
264 TEST_F(PasswordStoreMacInternalsTest, TestKeychainToFormTranslation) {
265 typedef struct {
266 const PasswordForm::Scheme scheme;
267 const char* signon_realm;
268 const char* origin;
269 const wchar_t* username; // Set to NULL to check for a blacklist entry.
270 const wchar_t* password;
271 const bool ssl_valid;
272 const int creation_year;
273 const int creation_month;
274 const int creation_day;
275 const int creation_hour;
276 const int creation_minute;
277 const int creation_second;
278 } TestExpectations;
280 TestExpectations expected[] = {
281 { PasswordForm::SCHEME_HTML, "http://some.domain.com/",
282 "http://some.domain.com/", L"joe_user", L"sekrit", false,
283 2002, 6, 1, 17, 15, 0 },
284 { PasswordForm::SCHEME_HTML, "http://some.domain.com/",
285 "http://some.domain.com/insecure.html", L"joe_user", L"sekrit", false,
286 1999, 12, 31, 23, 59, 59 },
287 { PasswordForm::SCHEME_HTML, "https://some.domain.com/",
288 "https://some.domain.com/secure.html", L"secure_user", L"password", true,
289 2010, 9, 8, 7, 6, 5 },
290 { PasswordForm::SCHEME_HTML, "http://dont.remember.com/",
291 "http://dont.remember.com/", NULL, NULL, false,
292 2000, 1, 1, 0, 0, 0 },
293 { PasswordForm::SCHEME_HTML, "http://dont.remember.com/",
294 "http://dont.remember.com/", NULL, NULL, false,
295 2000, 1, 1, 0, 0, 0 },
296 { PasswordForm::SCHEME_HTML, "https://dont.remember.com/",
297 "https://dont.remember.com/", NULL, NULL, true,
298 2000, 1, 1, 0, 0, 0 },
299 { PasswordForm::SCHEME_BASIC, "http://some.domain.com:4567/low_security",
300 "http://some.domain.com:4567/insecure.html", L"basic_auth_user", L"basic",
301 false, 1998, 03, 30, 10, 00, 00 },
302 { PasswordForm::SCHEME_DIGEST, "https://some.domain.com/high_security",
303 "https://some.domain.com/", L"digest_auth_user", L"digest", true,
304 1998, 3, 30, 10, 0, 0 },
305 // This one gives us an invalid date, which we will treat as a "NULL" date
306 // which is 1601.
307 { PasswordForm::SCHEME_OTHER, "http://a.server.com/",
308 "http://a.server.com/", L"abc", L"123", false,
309 1601, 1, 1, 0, 0, 0 },
312 for (unsigned int i = 0; i < ARRAYSIZE_UNSAFE(expected); ++i) {
313 // Create our fake KeychainItemRef; see MockAppleKeychain docs.
314 SecKeychainItemRef keychain_item =
315 reinterpret_cast<SecKeychainItemRef>(i + 1);
316 PasswordForm form;
317 bool parsed = internal_keychain_helpers::FillPasswordFormFromKeychainItem(
318 *keychain_, keychain_item, &form, true);
320 EXPECT_TRUE(parsed) << "In iteration " << i;
322 EXPECT_EQ(expected[i].scheme, form.scheme) << "In iteration " << i;
323 EXPECT_EQ(GURL(expected[i].origin), form.origin) << "In iteration " << i;
324 EXPECT_EQ(expected[i].ssl_valid, form.ssl_valid) << "In iteration " << i;
325 EXPECT_EQ(std::string(expected[i].signon_realm), form.signon_realm)
326 << "In iteration " << i;
327 if (expected[i].username) {
328 EXPECT_EQ(WideToUTF16(expected[i].username), form.username_value)
329 << "In iteration " << i;
330 EXPECT_EQ(WideToUTF16(expected[i].password), form.password_value)
331 << "In iteration " << i;
332 EXPECT_FALSE(form.blacklisted_by_user) << "In iteration " << i;
333 } else {
334 EXPECT_TRUE(form.blacklisted_by_user) << "In iteration " << i;
336 base::Time::Exploded exploded_time;
337 form.date_created.UTCExplode(&exploded_time);
338 EXPECT_EQ(expected[i].creation_year, exploded_time.year)
339 << "In iteration " << i;
340 EXPECT_EQ(expected[i].creation_month, exploded_time.month)
341 << "In iteration " << i;
342 EXPECT_EQ(expected[i].creation_day, exploded_time.day_of_month)
343 << "In iteration " << i;
344 EXPECT_EQ(expected[i].creation_hour, exploded_time.hour)
345 << "In iteration " << i;
346 EXPECT_EQ(expected[i].creation_minute, exploded_time.minute)
347 << "In iteration " << i;
348 EXPECT_EQ(expected[i].creation_second, exploded_time.second)
349 << "In iteration " << i;
353 // Use an invalid ref, to make sure errors are reported.
354 SecKeychainItemRef keychain_item = reinterpret_cast<SecKeychainItemRef>(99);
355 PasswordForm form;
356 bool parsed = internal_keychain_helpers::FillPasswordFormFromKeychainItem(
357 *keychain_, keychain_item, &form, true);
358 EXPECT_FALSE(parsed);
362 TEST_F(PasswordStoreMacInternalsTest, TestKeychainSearch) {
363 struct TestDataAndExpectation {
364 const PasswordFormData data;
365 const size_t expected_fill_matches;
366 const size_t expected_merge_matches;
368 // Most fields are left blank because we don't care about them for searching.
369 TestDataAndExpectation test_data[] = {
370 // An HTML form we've seen.
371 { { PasswordForm::SCHEME_HTML, "http://some.domain.com/",
372 NULL, NULL, NULL, NULL, NULL, L"joe_user", NULL, false, false, 0 },
373 2, 2 },
374 { { PasswordForm::SCHEME_HTML, "http://some.domain.com/",
375 NULL, NULL, NULL, NULL, NULL, L"wrong_user", NULL, false, false, 0 },
376 2, 0 },
377 // An HTML form we haven't seen
378 { { PasswordForm::SCHEME_HTML, "http://www.unseendomain.com/",
379 NULL, NULL, NULL, NULL, NULL, L"joe_user", NULL, false, false, 0 },
380 0, 0 },
381 // Basic auth that should match.
382 { { PasswordForm::SCHEME_BASIC, "http://some.domain.com:4567/low_security",
383 NULL, NULL, NULL, NULL, NULL, L"basic_auth_user", NULL, false, false,
384 0 },
385 1, 1 },
386 // Basic auth with the wrong port.
387 { { PasswordForm::SCHEME_BASIC, "http://some.domain.com:1111/low_security",
388 NULL, NULL, NULL, NULL, NULL, L"basic_auth_user", NULL, false, false,
389 0 },
390 0, 0 },
391 // Digest auth we've saved under https, visited with http.
392 { { PasswordForm::SCHEME_DIGEST, "http://some.domain.com/high_security",
393 NULL, NULL, NULL, NULL, NULL, L"digest_auth_user", NULL, false, false,
394 0 },
395 0, 0 },
396 // Digest auth that should match.
397 { { PasswordForm::SCHEME_DIGEST, "https://some.domain.com/high_security",
398 NULL, NULL, NULL, NULL, NULL, L"wrong_user", NULL, false, true, 0 },
399 1, 0 },
400 // Digest auth with the wrong domain.
401 { { PasswordForm::SCHEME_DIGEST, "https://some.domain.com/other_domain",
402 NULL, NULL, NULL, NULL, NULL, L"digest_auth_user", NULL, false, true,
403 0 },
404 0, 0 },
405 // Garbage forms should have no matches.
406 { { PasswordForm::SCHEME_HTML, "foo/bar/baz",
407 NULL, NULL, NULL, NULL, NULL, NULL, NULL, false, false, 0 }, 0, 0 },
410 MacKeychainPasswordFormAdapter keychain_adapter(keychain_);
411 MacKeychainPasswordFormAdapter owned_keychain_adapter(keychain_);
412 owned_keychain_adapter.SetFindsOnlyOwnedItems(true);
413 for (unsigned int i = 0; i < ARRAYSIZE_UNSAFE(test_data); ++i) {
414 scoped_ptr<PasswordForm> query_form(
415 CreatePasswordFormFromData(test_data[i].data));
417 // Check matches treating the form as a fill target.
418 std::vector<PasswordForm*> matching_items =
419 keychain_adapter.PasswordsFillingForm(query_form->signon_realm,
420 query_form->scheme);
421 EXPECT_EQ(test_data[i].expected_fill_matches, matching_items.size());
422 STLDeleteElements(&matching_items);
424 // Check matches treating the form as a merging target.
425 EXPECT_EQ(test_data[i].expected_merge_matches > 0,
426 keychain_adapter.HasPasswordsMergeableWithForm(*query_form));
427 std::vector<SecKeychainItemRef> keychain_items;
428 std::vector<internal_keychain_helpers::ItemFormPair> item_form_pairs =
429 internal_keychain_helpers::
430 ExtractAllKeychainItemAttributesIntoPasswordForms(&keychain_items,
431 *keychain_);
432 matching_items =
433 internal_keychain_helpers::ExtractPasswordsMergeableWithForm(
434 *keychain_, item_form_pairs, *query_form);
435 EXPECT_EQ(test_data[i].expected_merge_matches, matching_items.size());
436 STLDeleteContainerPairSecondPointers(item_form_pairs.begin(),
437 item_form_pairs.end());
438 for (std::vector<SecKeychainItemRef>::iterator i = keychain_items.begin();
439 i != keychain_items.end(); ++i) {
440 keychain_->Free(*i);
442 STLDeleteElements(&matching_items);
444 // None of the pre-seeded items are owned by us, so none should match an
445 // owned-passwords-only search.
446 matching_items = owned_keychain_adapter.PasswordsFillingForm(
447 query_form->signon_realm, query_form->scheme);
448 EXPECT_EQ(0U, matching_items.size());
449 STLDeleteElements(&matching_items);
453 // Changes just the origin path of |form|.
454 static void SetPasswordFormPath(PasswordForm* form, const char* path) {
455 GURL::Replacements replacement;
456 std::string new_value(path);
457 replacement.SetPathStr(new_value);
458 form->origin = form->origin.ReplaceComponents(replacement);
461 // Changes just the signon_realm port of |form|.
462 static void SetPasswordFormPort(PasswordForm* form, const char* port) {
463 GURL::Replacements replacement;
464 std::string new_value(port);
465 replacement.SetPortStr(new_value);
466 GURL signon_gurl = GURL(form->signon_realm);
467 form->signon_realm = signon_gurl.ReplaceComponents(replacement).spec();
470 // Changes just the signon_ream auth realm of |form|.
471 static void SetPasswordFormRealm(PasswordForm* form, const char* realm) {
472 GURL::Replacements replacement;
473 std::string new_value(realm);
474 replacement.SetPathStr(new_value);
475 GURL signon_gurl = GURL(form->signon_realm);
476 form->signon_realm = signon_gurl.ReplaceComponents(replacement).spec();
479 TEST_F(PasswordStoreMacInternalsTest, TestKeychainExactSearch) {
480 MacKeychainPasswordFormAdapter keychain_adapter(keychain_);
482 PasswordFormData base_form_data[] = {
483 { PasswordForm::SCHEME_HTML, "http://some.domain.com/",
484 "http://some.domain.com/insecure.html",
485 NULL, NULL, NULL, NULL, L"joe_user", NULL, true, false, 0 },
486 { PasswordForm::SCHEME_BASIC, "http://some.domain.com:4567/low_security",
487 "http://some.domain.com:4567/insecure.html",
488 NULL, NULL, NULL, NULL, L"basic_auth_user", NULL, true, false, 0 },
489 { PasswordForm::SCHEME_DIGEST, "https://some.domain.com/high_security",
490 "https://some.domain.com",
491 NULL, NULL, NULL, NULL, L"digest_auth_user", NULL, true, true, 0 },
494 for (unsigned int i = 0; i < arraysize(base_form_data); ++i) {
495 // Create a base form and make sure we find a match.
496 scoped_ptr<PasswordForm> base_form(CreatePasswordFormFromData(
497 base_form_data[i]));
498 EXPECT_TRUE(keychain_adapter.HasPasswordsMergeableWithForm(*base_form));
499 PasswordForm* match =
500 keychain_adapter.PasswordExactlyMatchingForm(*base_form);
501 EXPECT_TRUE(match != NULL);
502 if (match) {
503 EXPECT_EQ(base_form->scheme, match->scheme);
504 EXPECT_EQ(base_form->origin, match->origin);
505 EXPECT_EQ(base_form->username_value, match->username_value);
506 delete match;
509 // Make sure that the matching isn't looser than it should be by checking
510 // that slightly altered forms don't match.
511 std::vector<PasswordForm*> modified_forms;
513 modified_forms.push_back(new PasswordForm(*base_form));
514 modified_forms.back()->username_value = ASCIIToUTF16("wrong_user");
516 modified_forms.push_back(new PasswordForm(*base_form));
517 SetPasswordFormPath(modified_forms.back(), "elsewhere.html");
519 modified_forms.push_back(new PasswordForm(*base_form));
520 modified_forms.back()->scheme = PasswordForm::SCHEME_OTHER;
522 modified_forms.push_back(new PasswordForm(*base_form));
523 SetPasswordFormPort(modified_forms.back(), "1234");
525 modified_forms.push_back(new PasswordForm(*base_form));
526 modified_forms.back()->blacklisted_by_user = true;
528 if (base_form->scheme == PasswordForm::SCHEME_BASIC ||
529 base_form->scheme == PasswordForm::SCHEME_DIGEST) {
530 modified_forms.push_back(new PasswordForm(*base_form));
531 SetPasswordFormRealm(modified_forms.back(), "incorrect");
534 for (unsigned int j = 0; j < modified_forms.size(); ++j) {
535 PasswordForm* match =
536 keychain_adapter.PasswordExactlyMatchingForm(*modified_forms[j]);
537 EXPECT_EQ(NULL, match) << "In modified version " << j << " of base form "
538 << i;
540 STLDeleteElements(&modified_forms);
544 TEST_F(PasswordStoreMacInternalsTest, TestKeychainAdd) {
545 struct TestDataAndExpectation {
546 PasswordFormData data;
547 bool should_succeed;
549 TestDataAndExpectation test_data[] = {
550 // Test a variety of scheme/port/protocol/path variations.
551 { { PasswordForm::SCHEME_HTML, "http://web.site.com/",
552 "http://web.site.com/path/to/page.html", NULL, NULL, NULL, NULL,
553 L"anonymous", L"knock-knock", false, false, 0 }, true },
554 { { PasswordForm::SCHEME_HTML, "https://web.site.com/",
555 "https://web.site.com/", NULL, NULL, NULL, NULL,
556 L"admin", L"p4ssw0rd", false, false, 0 }, true },
557 { { PasswordForm::SCHEME_BASIC, "http://a.site.com:2222/therealm",
558 "http://a.site.com:2222/", NULL, NULL, NULL, NULL,
559 L"username", L"password", false, false, 0 }, true },
560 { { PasswordForm::SCHEME_DIGEST, "https://digest.site.com/differentrealm",
561 "https://digest.site.com/secure.html", NULL, NULL, NULL, NULL,
562 L"testname", L"testpass", false, false, 0 }, true },
563 // Make sure that garbage forms are rejected.
564 { { PasswordForm::SCHEME_HTML, "gobbledygook",
565 "gobbledygook", NULL, NULL, NULL, NULL,
566 L"anonymous", L"knock-knock", false, false, 0 }, false },
567 // Test that failing to update a duplicate (forced using the magic failure
568 // password; see MockAppleKeychain::ItemModifyAttributesAndData) is
569 // reported.
570 { { PasswordForm::SCHEME_HTML, "http://some.domain.com",
571 "http://some.domain.com/insecure.html", NULL, NULL, NULL, NULL,
572 L"joe_user", L"fail_me", false, false, 0 }, false },
575 MacKeychainPasswordFormAdapter owned_keychain_adapter(keychain_);
576 owned_keychain_adapter.SetFindsOnlyOwnedItems(true);
578 for (unsigned int i = 0; i < ARRAYSIZE_UNSAFE(test_data); ++i) {
579 scoped_ptr<PasswordForm> in_form(
580 CreatePasswordFormFromData(test_data[i].data));
581 bool add_succeeded = owned_keychain_adapter.AddPassword(*in_form);
582 EXPECT_EQ(test_data[i].should_succeed, add_succeeded);
583 if (add_succeeded) {
584 EXPECT_TRUE(owned_keychain_adapter.HasPasswordsMergeableWithForm(
585 *in_form));
586 scoped_ptr<PasswordForm> out_form(
587 owned_keychain_adapter.PasswordExactlyMatchingForm(*in_form));
588 EXPECT_TRUE(out_form.get() != NULL);
589 EXPECT_EQ(out_form->scheme, in_form->scheme);
590 EXPECT_EQ(out_form->signon_realm, in_form->signon_realm);
591 EXPECT_EQ(out_form->origin, in_form->origin);
592 EXPECT_EQ(out_form->username_value, in_form->username_value);
593 EXPECT_EQ(out_form->password_value, in_form->password_value);
597 // Test that adding duplicate item updates the existing item.
599 PasswordFormData data = {
600 PasswordForm::SCHEME_HTML, "http://some.domain.com",
601 "http://some.domain.com/insecure.html", NULL,
602 NULL, NULL, NULL, L"joe_user", L"updated_password", false, false, 0
604 scoped_ptr<PasswordForm> update_form(CreatePasswordFormFromData(data));
605 MacKeychainPasswordFormAdapter keychain_adapter(keychain_);
606 EXPECT_TRUE(keychain_adapter.AddPassword(*update_form));
607 SecKeychainItemRef keychain_item = reinterpret_cast<SecKeychainItemRef>(2);
608 PasswordForm stored_form;
609 internal_keychain_helpers::FillPasswordFormFromKeychainItem(*keychain_,
610 keychain_item,
611 &stored_form,
612 true);
613 EXPECT_EQ(update_form->password_value, stored_form.password_value);
617 TEST_F(PasswordStoreMacInternalsTest, TestKeychainRemove) {
618 struct TestDataAndExpectation {
619 PasswordFormData data;
620 bool should_succeed;
622 TestDataAndExpectation test_data[] = {
623 // Test deletion of an item that we add.
624 { { PasswordForm::SCHEME_HTML, "http://web.site.com/",
625 "http://web.site.com/path/to/page.html", NULL, NULL, NULL, NULL,
626 L"anonymous", L"knock-knock", false, false, 0 }, true },
627 // Make sure we don't delete items we don't own.
628 { { PasswordForm::SCHEME_HTML, "http://some.domain.com/",
629 "http://some.domain.com/insecure.html", NULL, NULL, NULL, NULL,
630 L"joe_user", NULL, true, false, 0 }, false },
633 MacKeychainPasswordFormAdapter owned_keychain_adapter(keychain_);
634 owned_keychain_adapter.SetFindsOnlyOwnedItems(true);
636 // Add our test item so that we can delete it.
637 PasswordForm* add_form = CreatePasswordFormFromData(test_data[0].data);
638 EXPECT_TRUE(owned_keychain_adapter.AddPassword(*add_form));
639 delete add_form;
641 for (unsigned int i = 0; i < ARRAYSIZE_UNSAFE(test_data); ++i) {
642 scoped_ptr<PasswordForm> form(CreatePasswordFormFromData(
643 test_data[i].data));
644 EXPECT_EQ(test_data[i].should_succeed,
645 owned_keychain_adapter.RemovePassword(*form));
647 MacKeychainPasswordFormAdapter keychain_adapter(keychain_);
648 PasswordForm* match = keychain_adapter.PasswordExactlyMatchingForm(*form);
649 EXPECT_EQ(test_data[i].should_succeed, match == NULL);
650 if (match) {
651 delete match;
656 TEST_F(PasswordStoreMacInternalsTest, TestFormMatch) {
657 PasswordForm base_form;
658 base_form.signon_realm = std::string("http://some.domain.com/");
659 base_form.origin = GURL("http://some.domain.com/page.html");
660 base_form.username_value = ASCIIToUTF16("joe_user");
663 // Check that everything unimportant can be changed.
664 PasswordForm different_form(base_form);
665 different_form.username_element = ASCIIToUTF16("username");
666 different_form.submit_element = ASCIIToUTF16("submit");
667 different_form.username_element = ASCIIToUTF16("password");
668 different_form.password_value = ASCIIToUTF16("sekrit");
669 different_form.action = GURL("http://some.domain.com/action.cgi");
670 different_form.ssl_valid = true;
671 different_form.preferred = true;
672 different_form.date_created = base::Time::Now();
673 EXPECT_TRUE(
674 FormsMatchForMerge(base_form, different_form, STRICT_FORM_MATCH));
676 // Check that path differences don't prevent a match.
677 base_form.origin = GURL("http://some.domain.com/other_page.html");
678 EXPECT_TRUE(
679 FormsMatchForMerge(base_form, different_form, STRICT_FORM_MATCH));
682 // Check that any one primary key changing is enough to prevent matching.
684 PasswordForm different_form(base_form);
685 different_form.scheme = PasswordForm::SCHEME_DIGEST;
686 EXPECT_FALSE(
687 FormsMatchForMerge(base_form, different_form, STRICT_FORM_MATCH));
690 PasswordForm different_form(base_form);
691 different_form.signon_realm = std::string("http://some.domain.com:8080/");
692 EXPECT_FALSE(
693 FormsMatchForMerge(base_form, different_form, STRICT_FORM_MATCH));
696 PasswordForm different_form(base_form);
697 different_form.username_value = ASCIIToUTF16("john.doe");
698 EXPECT_FALSE(
699 FormsMatchForMerge(base_form, different_form, STRICT_FORM_MATCH));
702 PasswordForm different_form(base_form);
703 different_form.blacklisted_by_user = true;
704 EXPECT_FALSE(
705 FormsMatchForMerge(base_form, different_form, STRICT_FORM_MATCH));
708 // Blacklist forms should *never* match for merging, even when identical
709 // (and certainly not when only one is a blacklist entry).
711 PasswordForm form_a(base_form);
712 form_a.blacklisted_by_user = true;
713 PasswordForm form_b(form_a);
714 EXPECT_FALSE(FormsMatchForMerge(form_a, form_b, STRICT_FORM_MATCH));
718 TEST_F(PasswordStoreMacInternalsTest, TestFormMerge) {
719 // Set up a bunch of test data to use in varying combinations.
720 PasswordFormData keychain_user_1 =
721 { PasswordForm::SCHEME_HTML, "http://some.domain.com/",
722 "http://some.domain.com/", "", L"", L"", L"", L"joe_user", L"sekrit",
723 false, false, 1010101010 };
724 PasswordFormData keychain_user_1_with_path =
725 { PasswordForm::SCHEME_HTML, "http://some.domain.com/",
726 "http://some.domain.com/page.html",
727 "", L"", L"", L"", L"joe_user", L"otherpassword",
728 false, false, 1010101010 };
729 PasswordFormData keychain_user_2 =
730 { PasswordForm::SCHEME_HTML, "http://some.domain.com/",
731 "http://some.domain.com/", "", L"", L"", L"", L"john.doe", L"sesame",
732 false, false, 958739876 };
733 PasswordFormData keychain_blacklist =
734 { PasswordForm::SCHEME_HTML, "http://some.domain.com/",
735 "http://some.domain.com/", "", L"", L"", L"", NULL, NULL,
736 false, false, 1010101010 };
738 PasswordFormData db_user_1 =
739 { PasswordForm::SCHEME_HTML, "http://some.domain.com/",
740 "http://some.domain.com/", "http://some.domain.com/action.cgi",
741 L"submit", L"username", L"password", L"joe_user", L"",
742 true, false, 1212121212 };
743 PasswordFormData db_user_1_with_path =
744 { PasswordForm::SCHEME_HTML, "http://some.domain.com/",
745 "http://some.domain.com/page.html",
746 "http://some.domain.com/handlepage.cgi",
747 L"submit", L"username", L"password", L"joe_user", L"",
748 true, false, 1234567890 };
749 PasswordFormData db_user_3_with_path =
750 { PasswordForm::SCHEME_HTML, "http://some.domain.com/",
751 "http://some.domain.com/page.html",
752 "http://some.domain.com/handlepage.cgi",
753 L"submit", L"username", L"password", L"second-account", L"",
754 true, false, 1240000000 };
755 PasswordFormData database_blacklist_with_path =
756 { PasswordForm::SCHEME_HTML, "http://some.domain.com/",
757 "http://some.domain.com/path.html", "http://some.domain.com/action.cgi",
758 L"submit", L"username", L"password", NULL, NULL,
759 true, false, 1212121212 };
761 PasswordFormData merged_user_1 =
762 { PasswordForm::SCHEME_HTML, "http://some.domain.com/",
763 "http://some.domain.com/", "http://some.domain.com/action.cgi",
764 L"submit", L"username", L"password", L"joe_user", L"sekrit",
765 true, false, 1212121212 };
766 PasswordFormData merged_user_1_with_db_path =
767 { PasswordForm::SCHEME_HTML, "http://some.domain.com/",
768 "http://some.domain.com/page.html",
769 "http://some.domain.com/handlepage.cgi",
770 L"submit", L"username", L"password", L"joe_user", L"sekrit",
771 true, false, 1234567890 };
772 PasswordFormData merged_user_1_with_both_paths =
773 { PasswordForm::SCHEME_HTML, "http://some.domain.com/",
774 "http://some.domain.com/page.html",
775 "http://some.domain.com/handlepage.cgi",
776 L"submit", L"username", L"password", L"joe_user", L"otherpassword",
777 true, false, 1234567890 };
779 // Build up the big multi-dimensional array of data sets that will actually
780 // drive the test. Use vectors rather than arrays so that initialization is
781 // simple.
782 enum {
783 KEYCHAIN_INPUT = 0,
784 DATABASE_INPUT,
785 MERGE_OUTPUT,
786 KEYCHAIN_OUTPUT,
787 DATABASE_OUTPUT,
788 MERGE_IO_ARRAY_COUNT // termination marker
790 const unsigned int kTestCount = 4;
791 std::vector< std::vector< std::vector<PasswordFormData*> > > test_data(
792 MERGE_IO_ARRAY_COUNT, std::vector< std::vector<PasswordFormData*> >(
793 kTestCount, std::vector<PasswordFormData*>()));
794 unsigned int current_test = 0;
796 // Test a merge with a few accounts in both systems, with partial overlap.
797 CHECK(current_test < kTestCount);
798 test_data[KEYCHAIN_INPUT][current_test].push_back(&keychain_user_1);
799 test_data[KEYCHAIN_INPUT][current_test].push_back(&keychain_user_2);
800 test_data[DATABASE_INPUT][current_test].push_back(&db_user_1);
801 test_data[DATABASE_INPUT][current_test].push_back(&db_user_1_with_path);
802 test_data[DATABASE_INPUT][current_test].push_back(&db_user_3_with_path);
803 test_data[MERGE_OUTPUT][current_test].push_back(&merged_user_1);
804 test_data[MERGE_OUTPUT][current_test].push_back(&merged_user_1_with_db_path);
805 test_data[KEYCHAIN_OUTPUT][current_test].push_back(&keychain_user_2);
806 test_data[DATABASE_OUTPUT][current_test].push_back(&db_user_3_with_path);
808 // Test a merge where Chrome has a blacklist entry, and the keychain has
809 // a stored account.
810 ++current_test;
811 CHECK(current_test < kTestCount);
812 test_data[KEYCHAIN_INPUT][current_test].push_back(&keychain_user_1);
813 test_data[DATABASE_INPUT][current_test].push_back(
814 &database_blacklist_with_path);
815 // We expect both to be present because a blacklist could be specific to a
816 // subpath, and we want access to the password on other paths.
817 test_data[MERGE_OUTPUT][current_test].push_back(
818 &database_blacklist_with_path);
819 test_data[KEYCHAIN_OUTPUT][current_test].push_back(&keychain_user_1);
821 // Test a merge where Chrome has an account, and Keychain has a blacklist
822 // (from another browser) and the Chrome password data.
823 ++current_test;
824 CHECK(current_test < kTestCount);
825 test_data[KEYCHAIN_INPUT][current_test].push_back(&keychain_blacklist);
826 test_data[KEYCHAIN_INPUT][current_test].push_back(&keychain_user_1);
827 test_data[DATABASE_INPUT][current_test].push_back(&db_user_1);
828 test_data[MERGE_OUTPUT][current_test].push_back(&merged_user_1);
829 test_data[KEYCHAIN_OUTPUT][current_test].push_back(&keychain_blacklist);
831 // Test that matches are done using exact path when possible.
832 ++current_test;
833 CHECK(current_test < kTestCount);
834 test_data[KEYCHAIN_INPUT][current_test].push_back(&keychain_user_1);
835 test_data[KEYCHAIN_INPUT][current_test].push_back(&keychain_user_1_with_path);
836 test_data[DATABASE_INPUT][current_test].push_back(&db_user_1);
837 test_data[DATABASE_INPUT][current_test].push_back(&db_user_1_with_path);
838 test_data[MERGE_OUTPUT][current_test].push_back(&merged_user_1);
839 test_data[MERGE_OUTPUT][current_test].push_back(
840 &merged_user_1_with_both_paths);
842 for (unsigned int test_case = 0; test_case <= current_test; ++test_case) {
843 std::vector<PasswordForm*> keychain_forms;
844 for (std::vector<PasswordFormData*>::iterator i =
845 test_data[KEYCHAIN_INPUT][test_case].begin();
846 i != test_data[KEYCHAIN_INPUT][test_case].end(); ++i) {
847 keychain_forms.push_back(CreatePasswordFormFromData(*(*i)));
849 std::vector<PasswordForm*> database_forms;
850 for (std::vector<PasswordFormData*>::iterator i =
851 test_data[DATABASE_INPUT][test_case].begin();
852 i != test_data[DATABASE_INPUT][test_case].end(); ++i) {
853 database_forms.push_back(CreatePasswordFormFromData(*(*i)));
856 std::vector<PasswordForm*> merged_forms;
857 internal_keychain_helpers::MergePasswordForms(&keychain_forms,
858 &database_forms,
859 &merged_forms);
861 CHECK_FORMS(keychain_forms, test_data[KEYCHAIN_OUTPUT][test_case],
862 test_case);
863 CHECK_FORMS(database_forms, test_data[DATABASE_OUTPUT][test_case],
864 test_case);
865 CHECK_FORMS(merged_forms, test_data[MERGE_OUTPUT][test_case], test_case);
867 STLDeleteElements(&keychain_forms);
868 STLDeleteElements(&database_forms);
869 STLDeleteElements(&merged_forms);
873 TEST_F(PasswordStoreMacInternalsTest, TestPasswordBulkLookup) {
874 PasswordFormData db_data[] = {
875 { PasswordForm::SCHEME_HTML, "http://some.domain.com/",
876 "http://some.domain.com/", "http://some.domain.com/action.cgi",
877 L"submit", L"username", L"password", L"joe_user", L"",
878 true, false, 1212121212 },
879 { PasswordForm::SCHEME_HTML, "http://some.domain.com/",
880 "http://some.domain.com/page.html",
881 "http://some.domain.com/handlepage.cgi",
882 L"submit", L"username", L"password", L"joe_user", L"",
883 true, false, 1234567890 },
884 { PasswordForm::SCHEME_HTML, "http://some.domain.com/",
885 "http://some.domain.com/page.html",
886 "http://some.domain.com/handlepage.cgi",
887 L"submit", L"username", L"password", L"second-account", L"",
888 true, false, 1240000000 },
889 { PasswordForm::SCHEME_HTML, "http://dont.remember.com/",
890 "http://dont.remember.com/",
891 "http://dont.remember.com/handlepage.cgi",
892 L"submit", L"username", L"password", L"joe_user", L"",
893 true, false, 1240000000 },
894 { PasswordForm::SCHEME_HTML, "http://some.domain.com/",
895 "http://some.domain.com/path.html", "http://some.domain.com/action.cgi",
896 L"submit", L"username", L"password", NULL, NULL,
897 true, false, 1212121212 },
899 std::vector<PasswordForm*> database_forms;
900 for (unsigned int i = 0; i < ARRAYSIZE_UNSAFE(db_data); ++i) {
901 database_forms.push_back(CreatePasswordFormFromData(db_data[i]));
903 std::vector<PasswordForm*> merged_forms =
904 internal_keychain_helpers::GetPasswordsForForms(*keychain_,
905 &database_forms);
906 EXPECT_EQ(2U, database_forms.size());
907 ASSERT_EQ(3U, merged_forms.size());
908 EXPECT_EQ(ASCIIToUTF16("sekrit"), merged_forms[0]->password_value);
909 EXPECT_EQ(ASCIIToUTF16("sekrit"), merged_forms[1]->password_value);
910 EXPECT_TRUE(merged_forms[2]->blacklisted_by_user);
912 STLDeleteElements(&database_forms);
913 STLDeleteElements(&merged_forms);
916 TEST_F(PasswordStoreMacInternalsTest, TestBlacklistedFiltering) {
917 PasswordFormData db_data[] = {
918 { PasswordForm::SCHEME_HTML, "http://dont.remember.com/",
919 "http://dont.remember.com/",
920 "http://dont.remember.com/handlepage.cgi",
921 L"submit", L"username", L"password", L"joe_user", L"non_empty_password",
922 true, false, 1240000000 },
923 { PasswordForm::SCHEME_HTML, "https://dont.remember.com/",
924 "https://dont.remember.com/",
925 "https://dont.remember.com/handlepage_secure.cgi",
926 L"submit", L"username", L"password", L"joe_user", L"non_empty_password",
927 true, false, 1240000000 },
929 std::vector<PasswordForm*> database_forms;
930 for (unsigned int i = 0; i < ARRAYSIZE_UNSAFE(db_data); ++i) {
931 database_forms.push_back(CreatePasswordFormFromData(db_data[i]));
933 std::vector<PasswordForm*> merged_forms =
934 internal_keychain_helpers::GetPasswordsForForms(*keychain_,
935 &database_forms);
936 EXPECT_EQ(2U, database_forms.size());
937 ASSERT_EQ(0U, merged_forms.size());
939 STLDeleteElements(&database_forms);
940 STLDeleteElements(&merged_forms);
943 TEST_F(PasswordStoreMacInternalsTest, TestFillPasswordFormFromKeychainItem) {
944 // When |extract_password_data| is false, the password field must be empty,
945 // and |blacklisted_by_user| must be false.
946 SecKeychainItemRef keychain_item = reinterpret_cast<SecKeychainItemRef>(1);
947 PasswordForm form_without_extracted_password;
948 bool parsed = internal_keychain_helpers::FillPasswordFormFromKeychainItem(
949 *keychain_,
950 keychain_item,
951 &form_without_extracted_password,
952 false); // Do not extract password.
953 EXPECT_TRUE(parsed);
954 ASSERT_TRUE(form_without_extracted_password.password_value.empty());
955 ASSERT_FALSE(form_without_extracted_password.blacklisted_by_user);
957 // When |extract_password_data| is true and the keychain entry has a non-empty
958 // password, the password field must be non-empty, and the value of
959 // |blacklisted_by_user| must be false.
960 keychain_item = reinterpret_cast<SecKeychainItemRef>(1);
961 PasswordForm form_with_extracted_password;
962 parsed = internal_keychain_helpers::FillPasswordFormFromKeychainItem(
963 *keychain_,
964 keychain_item,
965 &form_with_extracted_password,
966 true); // Extract password.
967 EXPECT_TRUE(parsed);
968 ASSERT_EQ(ASCIIToUTF16("sekrit"),
969 form_with_extracted_password.password_value);
970 ASSERT_FALSE(form_with_extracted_password.blacklisted_by_user);
972 // When |extract_password_data| is true and the keychain entry has an empty
973 // username and password (""), the password field must be empty, and the value
974 // of |blacklisted_by_user| must be true.
975 keychain_item = reinterpret_cast<SecKeychainItemRef>(4);
976 PasswordForm negative_form;
977 parsed = internal_keychain_helpers::FillPasswordFormFromKeychainItem(
978 *keychain_,
979 keychain_item,
980 &negative_form,
981 true); // Extract password.
982 EXPECT_TRUE(parsed);
983 ASSERT_TRUE(negative_form.username_value.empty());
984 ASSERT_TRUE(negative_form.password_value.empty());
985 ASSERT_TRUE(negative_form.blacklisted_by_user);
987 // When |extract_password_data| is true and the keychain entry has an empty
988 // password (""), the password field must be empty (""), and the value of
989 // |blacklisted_by_user| must be true.
990 keychain_item = reinterpret_cast<SecKeychainItemRef>(5);
991 PasswordForm form_with_empty_password_a;
992 parsed = internal_keychain_helpers::FillPasswordFormFromKeychainItem(
993 *keychain_,
994 keychain_item,
995 &form_with_empty_password_a,
996 true); // Extract password.
997 EXPECT_TRUE(parsed);
998 ASSERT_TRUE(form_with_empty_password_a.password_value.empty());
999 ASSERT_TRUE(form_with_empty_password_a.blacklisted_by_user);
1001 // When |extract_password_data| is true and the keychain entry has a single
1002 // space password (" "), the password field must be a single space (" "), and
1003 // the value of |blacklisted_by_user| must be true.
1004 keychain_item = reinterpret_cast<SecKeychainItemRef>(6);
1005 PasswordForm form_with_empty_password_b;
1006 parsed = internal_keychain_helpers::FillPasswordFormFromKeychainItem(
1007 *keychain_,
1008 keychain_item,
1009 &form_with_empty_password_b,
1010 true); // Extract password.
1011 EXPECT_TRUE(parsed);
1012 ASSERT_EQ(ASCIIToUTF16(" "),
1013 form_with_empty_password_b.password_value);
1014 ASSERT_TRUE(form_with_empty_password_b.blacklisted_by_user);
1017 TEST_F(PasswordStoreMacInternalsTest, TestPasswordGetAll) {
1018 MacKeychainPasswordFormAdapter keychain_adapter(keychain_);
1019 MacKeychainPasswordFormAdapter owned_keychain_adapter(keychain_);
1020 owned_keychain_adapter.SetFindsOnlyOwnedItems(true);
1022 // Add a few passwords of various types so that we own some.
1023 PasswordFormData owned_password_data[] = {
1024 { PasswordForm::SCHEME_HTML, "http://web.site.com/",
1025 "http://web.site.com/path/to/page.html", NULL, NULL, NULL, NULL,
1026 L"anonymous", L"knock-knock", false, false, 0 },
1027 { PasswordForm::SCHEME_BASIC, "http://a.site.com:2222/therealm",
1028 "http://a.site.com:2222/", NULL, NULL, NULL, NULL,
1029 L"username", L"password", false, false, 0 },
1030 { PasswordForm::SCHEME_DIGEST, "https://digest.site.com/differentrealm",
1031 "https://digest.site.com/secure.html", NULL, NULL, NULL, NULL,
1032 L"testname", L"testpass", false, false, 0 },
1034 for (unsigned int i = 0; i < arraysize(owned_password_data); ++i) {
1035 scoped_ptr<PasswordForm> form(CreatePasswordFormFromData(
1036 owned_password_data[i]));
1037 owned_keychain_adapter.AddPassword(*form);
1040 std::vector<PasswordForm*> all_passwords =
1041 keychain_adapter.GetAllPasswordFormPasswords();
1042 EXPECT_EQ(8 + arraysize(owned_password_data), all_passwords.size());
1043 STLDeleteElements(&all_passwords);
1045 std::vector<PasswordForm*> owned_passwords =
1046 owned_keychain_adapter.GetAllPasswordFormPasswords();
1047 EXPECT_EQ(arraysize(owned_password_data), owned_passwords.size());
1048 STLDeleteElements(&owned_passwords);
1051 #pragma mark -
1053 class PasswordStoreMacTest : public testing::Test {
1054 public:
1055 PasswordStoreMacTest() : ui_thread_(BrowserThread::UI, &message_loop_) {}
1057 virtual void SetUp() {
1058 login_db_ = new LoginDatabase();
1059 ASSERT_TRUE(db_dir_.CreateUniqueTempDir());
1060 base::FilePath db_file = db_dir_.path().AppendASCII("login.db");
1061 ASSERT_TRUE(login_db_->Init(db_file));
1063 keychain_ = new MockAppleKeychain();
1065 store_ = new TestPasswordStoreMac(
1066 base::MessageLoopProxy::current(),
1067 base::MessageLoopProxy::current(),
1068 keychain_,
1069 login_db_);
1070 ASSERT_TRUE(store_->Init(syncer::SyncableService::StartSyncFlare()));
1073 virtual void TearDown() {
1074 store_->Shutdown();
1075 EXPECT_FALSE(store_->GetBackgroundTaskRunner());
1078 void WaitForStoreUpdate() {
1079 // Do a store-level query to wait for all the operations above to be done.
1080 MockPasswordStoreConsumer consumer;
1081 EXPECT_CALL(consumer, OnGetPasswordStoreResults(_))
1082 .WillOnce(DoAll(WithArg<0>(STLDeleteElements0()), QuitUIMessageLoop()));
1083 store_->GetLogins(PasswordForm(), PasswordStore::ALLOW_PROMPT, &consumer);
1084 base::MessageLoop::current()->Run();
1087 protected:
1088 base::MessageLoopForUI message_loop_;
1089 content::TestBrowserThread ui_thread_;
1091 MockAppleKeychain* keychain_; // Owned by store_.
1092 LoginDatabase* login_db_; // Owned by store_.
1093 scoped_refptr<TestPasswordStoreMac> store_;
1094 base::ScopedTempDir db_dir_;
1097 TEST_F(PasswordStoreMacTest, TestStoreUpdate) {
1098 // Insert a password into both the database and the keychain.
1099 // This is done manually, rather than through store_->AddLogin, because the
1100 // Mock Keychain isn't smart enough to be able to support update generically,
1101 // so some.domain.com triggers special handling to test it that make inserting
1102 // fail.
1103 PasswordFormData joint_data = {
1104 PasswordForm::SCHEME_HTML, "http://some.domain.com/",
1105 "http://some.domain.com/insecure.html", "login.cgi",
1106 L"username", L"password", L"submit", L"joe_user", L"sekrit", true, false, 1
1108 scoped_ptr<PasswordForm> joint_form(CreatePasswordFormFromData(joint_data));
1109 login_db_->AddLogin(*joint_form);
1110 MockAppleKeychain::KeychainTestData joint_keychain_data = {
1111 kSecAuthenticationTypeHTMLForm, "some.domain.com",
1112 kSecProtocolTypeHTTP, "/insecure.html", 0, NULL, "20020601171500Z",
1113 "joe_user", "sekrit", false };
1114 keychain_->AddTestItem(joint_keychain_data);
1116 // Insert a password into the keychain only.
1117 MockAppleKeychain::KeychainTestData keychain_only_data = {
1118 kSecAuthenticationTypeHTMLForm, "keychain.only.com",
1119 kSecProtocolTypeHTTP, NULL, 0, NULL, "20020601171500Z",
1120 "keychain", "only", false
1122 keychain_->AddTestItem(keychain_only_data);
1124 struct UpdateData {
1125 PasswordFormData form_data;
1126 const char* password; // NULL indicates no entry should be present.
1129 // Make a series of update calls.
1130 UpdateData updates[] = {
1131 // Update the keychain+db passwords (the normal password update case).
1132 { { PasswordForm::SCHEME_HTML, "http://some.domain.com/",
1133 "http://some.domain.com/insecure.html", "login.cgi",
1134 L"username", L"password", L"submit", L"joe_user", L"53krit",
1135 true, false, 2 },
1136 "53krit",
1138 // Update the keychain-only password; this simulates the initial use of a
1139 // password stored by another browsers.
1140 { { PasswordForm::SCHEME_HTML, "http://keychain.only.com/",
1141 "http://keychain.only.com/login.html", "login.cgi",
1142 L"username", L"password", L"submit", L"keychain", L"only",
1143 true, false, 2 },
1144 "only",
1146 // Update a password that doesn't exist in either location. This tests the
1147 // case where a form is filled, then the stored login is removed, then the
1148 // form is submitted.
1149 { { PasswordForm::SCHEME_HTML, "http://different.com/",
1150 "http://different.com/index.html", "login.cgi",
1151 L"username", L"password", L"submit", L"abc", L"123",
1152 true, false, 2 },
1153 NULL,
1156 for (unsigned int i = 0; i < ARRAYSIZE_UNSAFE(updates); ++i) {
1157 scoped_ptr<PasswordForm> form(CreatePasswordFormFromData(
1158 updates[i].form_data));
1159 store_->UpdateLogin(*form);
1162 WaitForStoreUpdate();
1164 MacKeychainPasswordFormAdapter keychain_adapter(keychain_);
1165 for (unsigned int i = 0; i < ARRAYSIZE_UNSAFE(updates); ++i) {
1166 scoped_ptr<PasswordForm> query_form(
1167 CreatePasswordFormFromData(updates[i].form_data));
1169 std::vector<PasswordForm*> matching_items =
1170 keychain_adapter.PasswordsFillingForm(query_form->signon_realm,
1171 query_form->scheme);
1172 if (updates[i].password) {
1173 EXPECT_GT(matching_items.size(), 0U) << "iteration " << i;
1174 if (matching_items.size() >= 1)
1175 EXPECT_EQ(ASCIIToUTF16(updates[i].password),
1176 matching_items[0]->password_value) << "iteration " << i;
1177 } else {
1178 EXPECT_EQ(0U, matching_items.size()) << "iteration " << i;
1180 STLDeleteElements(&matching_items);
1182 login_db_->GetLogins(*query_form, &matching_items);
1183 EXPECT_EQ(updates[i].password ? 1U : 0U, matching_items.size())
1184 << "iteration " << i;
1185 STLDeleteElements(&matching_items);
1189 TEST_F(PasswordStoreMacTest, TestDBKeychainAssociation) {
1190 // Tests that association between the keychain and login database parts of a
1191 // password added by fuzzy (PSL) matching works.
1192 // 1. Add a password for www.facebook.com
1193 // 2. Get a password for m.facebook.com. This fuzzy matches and returns the
1194 // www.facebook.com password.
1195 // 3. Add the returned password for m.facebook.com.
1196 // 4. Remove both passwords.
1197 // -> check: that both are gone from the login DB and the keychain
1198 // This test should in particular ensure that we don't keep passwords in the
1199 // keychain just before we think we still have other (fuzzy-)matching entries
1200 // for them in the login database. (For example, here if we deleted the
1201 // www.facebook.com password from the login database, we should not be blocked
1202 // from deleting it from the keystore just becaus the m.facebook.com password
1203 // fuzzy-matches the www.facebook.com one.)
1205 // 1. Add a password for www.facebook.com
1206 PasswordFormData www_form_data = {
1207 PasswordForm::SCHEME_HTML, "http://www.facebook.com/",
1208 "http://www.facebook.com/index.html", "login",
1209 L"username", L"password", L"submit", L"joe_user", L"sekrit", true, false, 1
1211 scoped_ptr<PasswordForm> www_form(CreatePasswordFormFromData(www_form_data));
1212 login_db_->AddLogin(*www_form);
1213 MacKeychainPasswordFormAdapter owned_keychain_adapter(keychain_);
1214 owned_keychain_adapter.SetFindsOnlyOwnedItems(true);
1215 owned_keychain_adapter.AddPassword(*www_form);
1217 // 2. Get a password for m.facebook.com.
1218 PasswordForm m_form(*www_form);
1219 m_form.signon_realm = "http://m.facebook.com";
1220 m_form.origin = GURL("http://m.facebook.com/index.html");
1221 MockPasswordStoreConsumer consumer;
1222 EXPECT_CALL(consumer, OnGetPasswordStoreResults(_)).WillOnce(DoAll(
1223 WithArg<0>(Invoke(&consumer, &MockPasswordStoreConsumer::CopyElements)),
1224 WithArg<0>(STLDeleteElements0()),
1225 QuitUIMessageLoop()));
1226 store_->GetLogins(m_form, PasswordStore::ALLOW_PROMPT, &consumer);
1227 base::MessageLoop::current()->Run();
1228 EXPECT_EQ(1u, consumer.last_result.size());
1230 // 3. Add the returned password for m.facebook.com.
1231 login_db_->AddLogin(consumer.last_result[0]);
1232 owned_keychain_adapter.AddPassword(m_form);
1234 // 4. Remove both passwords.
1235 store_->RemoveLogin(*www_form);
1236 store_->RemoveLogin(m_form);
1237 WaitForStoreUpdate();
1239 std::vector<PasswordForm*> matching_items;
1240 // No trace of www.facebook.com.
1241 matching_items = owned_keychain_adapter.PasswordsFillingForm(
1242 www_form->signon_realm, www_form->scheme);
1243 EXPECT_EQ(0u, matching_items.size());
1244 login_db_->GetLogins(*www_form, &matching_items);
1245 EXPECT_EQ(0u, matching_items.size());
1246 // No trace of m.facebook.com.
1247 matching_items = owned_keychain_adapter.PasswordsFillingForm(
1248 m_form.signon_realm, m_form.scheme);
1249 EXPECT_EQ(0u, matching_items.size());
1250 login_db_->GetLogins(m_form, &matching_items);
1251 EXPECT_EQ(0u, matching_items.size());