NaCl: Update revision in DEPS, r12770 -> r12773
[chromium-blink-merge.git] / chrome / browser / password_manager / password_store_mac_unittest.cc
blob7c75055e50f9035141f912dda3757ef4de215301
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 testing::_;
29 using testing::DoAll;
30 using testing::Invoke;
31 using testing::WithArg;
33 namespace {
35 class MockPasswordStoreConsumer : public PasswordStoreConsumer {
36 public:
37 MOCK_METHOD1(OnGetPasswordStoreResults,
38 void(const std::vector<autofill::PasswordForm*>&));
40 void CopyElements(const std::vector<autofill::PasswordForm*>& forms) {
41 last_result.clear();
42 for (size_t i = 0; i < forms.size(); ++i) {
43 last_result.push_back(*forms[i]);
47 std::vector<PasswordForm> last_result;
50 ACTION(STLDeleteElements0) {
51 STLDeleteContainerPointers(arg0.begin(), arg0.end());
54 ACTION(QuitUIMessageLoop) {
55 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
56 base::MessageLoop::current()->Quit();
59 } // namespace
61 #pragma mark -
63 class PasswordStoreMacInternalsTest : public testing::Test {
64 public:
65 virtual void SetUp() {
66 MockAppleKeychain::KeychainTestData test_data[] = {
67 // Basic HTML form.
68 { kSecAuthenticationTypeHTMLForm, "some.domain.com",
69 kSecProtocolTypeHTTP, NULL, 0, NULL, "20020601171500Z",
70 "joe_user", "sekrit", false },
71 // HTML form with path.
72 { kSecAuthenticationTypeHTMLForm, "some.domain.com",
73 kSecProtocolTypeHTTP, "/insecure.html", 0, NULL, "19991231235959Z",
74 "joe_user", "sekrit", false },
75 // Secure HTML form with path.
76 { kSecAuthenticationTypeHTMLForm, "some.domain.com",
77 kSecProtocolTypeHTTPS, "/secure.html", 0, NULL, "20100908070605Z",
78 "secure_user", "password", false },
79 // True negative item.
80 { kSecAuthenticationTypeHTMLForm, "dont.remember.com",
81 kSecProtocolTypeHTTP, NULL, 0, NULL, "20000101000000Z",
82 "", "", true },
83 // De-facto negative item, type one.
84 { kSecAuthenticationTypeHTMLForm, "dont.remember.com",
85 kSecProtocolTypeHTTP, NULL, 0, NULL, "20000101000000Z",
86 "Password Not Stored", "", false },
87 // De-facto negative item, type two.
88 { kSecAuthenticationTypeHTMLForm, "dont.remember.com",
89 kSecProtocolTypeHTTPS, NULL, 0, NULL, "20000101000000Z",
90 "Password Not Stored", " ", false },
91 // HTTP auth basic, with port and path.
92 { kSecAuthenticationTypeHTTPBasic, "some.domain.com",
93 kSecProtocolTypeHTTP, "/insecure.html", 4567, "low_security",
94 "19980330100000Z",
95 "basic_auth_user", "basic", false },
96 // HTTP auth digest, secure.
97 { kSecAuthenticationTypeHTTPDigest, "some.domain.com",
98 kSecProtocolTypeHTTPS, NULL, 0, "high_security", "19980330100000Z",
99 "digest_auth_user", "digest", false },
100 // An FTP password with an invalid date, for edge-case testing.
101 { kSecAuthenticationTypeDefault, "a.server.com",
102 kSecProtocolTypeFTP, NULL, 0, NULL, "20010203040",
103 "abc", "123", false },
106 keychain_ = new MockAppleKeychain();
108 for (unsigned int i = 0; i < arraysize(test_data); ++i) {
109 keychain_->AddTestItem(test_data[i]);
113 virtual void TearDown() {
114 ExpectCreatesAndFreesBalanced();
115 ExpectCreatorCodesSet();
116 delete keychain_;
119 protected:
120 // Causes a test failure unless everything returned from keychain_'s
121 // ItemCopyAttributesAndData, SearchCreateFromAttributes, and SearchCopyNext
122 // was correctly freed.
123 void ExpectCreatesAndFreesBalanced() {
124 EXPECT_EQ(0, keychain_->UnfreedSearchCount());
125 EXPECT_EQ(0, keychain_->UnfreedKeychainItemCount());
126 EXPECT_EQ(0, keychain_->UnfreedAttributeDataCount());
129 // Causes a test failure unless any Keychain items added during the test have
130 // their creator code set.
131 void ExpectCreatorCodesSet() {
132 EXPECT_TRUE(keychain_->CreatorCodesSetForAddedItems());
135 MockAppleKeychain* keychain_;
138 #pragma mark -
140 // Struct used for creation of PasswordForms from static arrays of data.
141 struct PasswordFormData {
142 const PasswordForm::Scheme scheme;
143 const char* signon_realm;
144 const char* origin;
145 const char* action;
146 const wchar_t* submit_element;
147 const wchar_t* username_element;
148 const wchar_t* password_element;
149 const wchar_t* username_value; // Set to NULL for a blacklist entry.
150 const wchar_t* password_value;
151 const bool preferred;
152 const bool ssl_valid;
153 const double creation_time;
156 // Creates and returns a new PasswordForm built from form_data. Caller is
157 // responsible for deleting the object when finished with it.
158 static PasswordForm* CreatePasswordFormFromData(
159 const PasswordFormData& form_data) {
160 PasswordForm* form = new PasswordForm();
161 form->scheme = form_data.scheme;
162 form->preferred = form_data.preferred;
163 form->ssl_valid = form_data.ssl_valid;
164 form->date_created = base::Time::FromDoubleT(form_data.creation_time);
165 if (form_data.signon_realm)
166 form->signon_realm = std::string(form_data.signon_realm);
167 if (form_data.origin)
168 form->origin = GURL(form_data.origin);
169 if (form_data.action)
170 form->action = GURL(form_data.action);
171 if (form_data.submit_element)
172 form->submit_element = WideToUTF16(form_data.submit_element);
173 if (form_data.username_element)
174 form->username_element = WideToUTF16(form_data.username_element);
175 if (form_data.password_element)
176 form->password_element = WideToUTF16(form_data.password_element);
177 if (form_data.username_value) {
178 form->username_value = WideToUTF16(form_data.username_value);
179 if (form_data.password_value)
180 form->password_value = WideToUTF16(form_data.password_value);
181 } else {
182 form->blacklisted_by_user = true;
184 return form;
187 // Macro to simplify calling CheckFormsAgainstExpectations with a useful label.
188 #define CHECK_FORMS(forms, expectations, i) \
189 CheckFormsAgainstExpectations(forms, expectations, #forms, i)
191 // Ensures that the data in |forms| match |expectations|, causing test failures
192 // for any discrepencies.
193 // TODO(stuartmorgan): This is current order-dependent; ideally it shouldn't
194 // matter if |forms| and |expectations| are scrambled.
195 static void CheckFormsAgainstExpectations(
196 const std::vector<PasswordForm*>& forms,
197 const std::vector<PasswordFormData*>& expectations,
198 const char* forms_label, unsigned int test_number) {
199 const unsigned int kBufferSize = 128;
200 char test_label[kBufferSize];
201 snprintf(test_label, kBufferSize, "%s in test %u", forms_label, test_number);
203 EXPECT_EQ(expectations.size(), forms.size()) << test_label;
204 if (expectations.size() != forms.size())
205 return;
207 for (unsigned int i = 0; i < expectations.size(); ++i) {
208 snprintf(test_label, kBufferSize, "%s in test %u, item %u",
209 forms_label, test_number, i);
210 PasswordForm* form = forms[i];
211 PasswordFormData* expectation = expectations[i];
212 EXPECT_EQ(expectation->scheme, form->scheme) << test_label;
213 EXPECT_EQ(std::string(expectation->signon_realm), form->signon_realm)
214 << test_label;
215 EXPECT_EQ(GURL(expectation->origin), form->origin) << test_label;
216 EXPECT_EQ(GURL(expectation->action), form->action) << test_label;
217 EXPECT_EQ(WideToUTF16(expectation->submit_element), form->submit_element)
218 << test_label;
219 EXPECT_EQ(WideToUTF16(expectation->username_element),
220 form->username_element) << test_label;
221 EXPECT_EQ(WideToUTF16(expectation->password_element),
222 form->password_element) << test_label;
223 if (expectation->username_value) {
224 EXPECT_EQ(WideToUTF16(expectation->username_value),
225 form->username_value) << test_label;
226 EXPECT_EQ(WideToUTF16(expectation->password_value),
227 form->password_value) << test_label;
228 } else {
229 EXPECT_TRUE(form->blacklisted_by_user) << test_label;
231 EXPECT_EQ(expectation->preferred, form->preferred) << test_label;
232 EXPECT_EQ(expectation->ssl_valid, form->ssl_valid) << test_label;
233 EXPECT_DOUBLE_EQ(expectation->creation_time,
234 form->date_created.ToDoubleT()) << test_label;
238 #pragma mark -
240 TEST_F(PasswordStoreMacInternalsTest, TestKeychainToFormTranslation) {
241 typedef struct {
242 const PasswordForm::Scheme scheme;
243 const char* signon_realm;
244 const char* origin;
245 const wchar_t* username; // Set to NULL to check for a blacklist entry.
246 const wchar_t* password;
247 const bool ssl_valid;
248 const int creation_year;
249 const int creation_month;
250 const int creation_day;
251 const int creation_hour;
252 const int creation_minute;
253 const int creation_second;
254 } TestExpectations;
256 TestExpectations expected[] = {
257 { PasswordForm::SCHEME_HTML, "http://some.domain.com/",
258 "http://some.domain.com/", L"joe_user", L"sekrit", false,
259 2002, 6, 1, 17, 15, 0 },
260 { PasswordForm::SCHEME_HTML, "http://some.domain.com/",
261 "http://some.domain.com/insecure.html", L"joe_user", L"sekrit", false,
262 1999, 12, 31, 23, 59, 59 },
263 { PasswordForm::SCHEME_HTML, "https://some.domain.com/",
264 "https://some.domain.com/secure.html", L"secure_user", L"password", true,
265 2010, 9, 8, 7, 6, 5 },
266 { PasswordForm::SCHEME_HTML, "http://dont.remember.com/",
267 "http://dont.remember.com/", NULL, NULL, false,
268 2000, 1, 1, 0, 0, 0 },
269 { PasswordForm::SCHEME_HTML, "http://dont.remember.com/",
270 "http://dont.remember.com/", NULL, NULL, false,
271 2000, 1, 1, 0, 0, 0 },
272 { PasswordForm::SCHEME_HTML, "https://dont.remember.com/",
273 "https://dont.remember.com/", NULL, NULL, true,
274 2000, 1, 1, 0, 0, 0 },
275 { PasswordForm::SCHEME_BASIC, "http://some.domain.com:4567/low_security",
276 "http://some.domain.com:4567/insecure.html", L"basic_auth_user", L"basic",
277 false, 1998, 03, 30, 10, 00, 00 },
278 { PasswordForm::SCHEME_DIGEST, "https://some.domain.com/high_security",
279 "https://some.domain.com/", L"digest_auth_user", L"digest", true,
280 1998, 3, 30, 10, 0, 0 },
281 // This one gives us an invalid date, which we will treat as a "NULL" date
282 // which is 1601.
283 { PasswordForm::SCHEME_OTHER, "http://a.server.com/",
284 "http://a.server.com/", L"abc", L"123", false,
285 1601, 1, 1, 0, 0, 0 },
288 for (unsigned int i = 0; i < ARRAYSIZE_UNSAFE(expected); ++i) {
289 // Create our fake KeychainItemRef; see MockAppleKeychain docs.
290 SecKeychainItemRef keychain_item =
291 reinterpret_cast<SecKeychainItemRef>(i + 1);
292 PasswordForm form;
293 bool parsed = internal_keychain_helpers::FillPasswordFormFromKeychainItem(
294 *keychain_, keychain_item, &form, true);
296 EXPECT_TRUE(parsed) << "In iteration " << i;
298 EXPECT_EQ(expected[i].scheme, form.scheme) << "In iteration " << i;
299 EXPECT_EQ(GURL(expected[i].origin), form.origin) << "In iteration " << i;
300 EXPECT_EQ(expected[i].ssl_valid, form.ssl_valid) << "In iteration " << i;
301 EXPECT_EQ(std::string(expected[i].signon_realm), form.signon_realm)
302 << "In iteration " << i;
303 if (expected[i].username) {
304 EXPECT_EQ(WideToUTF16(expected[i].username), form.username_value)
305 << "In iteration " << i;
306 EXPECT_EQ(WideToUTF16(expected[i].password), form.password_value)
307 << "In iteration " << i;
308 EXPECT_FALSE(form.blacklisted_by_user) << "In iteration " << i;
309 } else {
310 EXPECT_TRUE(form.blacklisted_by_user) << "In iteration " << i;
312 base::Time::Exploded exploded_time;
313 form.date_created.UTCExplode(&exploded_time);
314 EXPECT_EQ(expected[i].creation_year, exploded_time.year)
315 << "In iteration " << i;
316 EXPECT_EQ(expected[i].creation_month, exploded_time.month)
317 << "In iteration " << i;
318 EXPECT_EQ(expected[i].creation_day, exploded_time.day_of_month)
319 << "In iteration " << i;
320 EXPECT_EQ(expected[i].creation_hour, exploded_time.hour)
321 << "In iteration " << i;
322 EXPECT_EQ(expected[i].creation_minute, exploded_time.minute)
323 << "In iteration " << i;
324 EXPECT_EQ(expected[i].creation_second, exploded_time.second)
325 << "In iteration " << i;
329 // Use an invalid ref, to make sure errors are reported.
330 SecKeychainItemRef keychain_item = reinterpret_cast<SecKeychainItemRef>(99);
331 PasswordForm form;
332 bool parsed = internal_keychain_helpers::FillPasswordFormFromKeychainItem(
333 *keychain_, keychain_item, &form, true);
334 EXPECT_FALSE(parsed);
338 TEST_F(PasswordStoreMacInternalsTest, TestKeychainSearch) {
339 struct TestDataAndExpectation {
340 const PasswordFormData data;
341 const size_t expected_fill_matches;
342 const size_t expected_merge_matches;
344 // Most fields are left blank because we don't care about them for searching.
345 TestDataAndExpectation test_data[] = {
346 // An HTML form we've seen.
347 { { PasswordForm::SCHEME_HTML, "http://some.domain.com/",
348 NULL, NULL, NULL, NULL, NULL, L"joe_user", NULL, false, false, 0 },
349 2, 2 },
350 { { PasswordForm::SCHEME_HTML, "http://some.domain.com/",
351 NULL, NULL, NULL, NULL, NULL, L"wrong_user", NULL, false, false, 0 },
352 2, 0 },
353 // An HTML form we haven't seen
354 { { PasswordForm::SCHEME_HTML, "http://www.unseendomain.com/",
355 NULL, NULL, NULL, NULL, NULL, L"joe_user", NULL, false, false, 0 },
356 0, 0 },
357 // Basic auth that should match.
358 { { PasswordForm::SCHEME_BASIC, "http://some.domain.com:4567/low_security",
359 NULL, NULL, NULL, NULL, NULL, L"basic_auth_user", NULL, false, false,
360 0 },
361 1, 1 },
362 // Basic auth with the wrong port.
363 { { PasswordForm::SCHEME_BASIC, "http://some.domain.com:1111/low_security",
364 NULL, NULL, NULL, NULL, NULL, L"basic_auth_user", NULL, false, false,
365 0 },
366 0, 0 },
367 // Digest auth we've saved under https, visited with http.
368 { { PasswordForm::SCHEME_DIGEST, "http://some.domain.com/high_security",
369 NULL, NULL, NULL, NULL, NULL, L"digest_auth_user", NULL, false, false,
370 0 },
371 0, 0 },
372 // Digest auth that should match.
373 { { PasswordForm::SCHEME_DIGEST, "https://some.domain.com/high_security",
374 NULL, NULL, NULL, NULL, NULL, L"wrong_user", NULL, false, true, 0 },
375 1, 0 },
376 // Digest auth with the wrong domain.
377 { { PasswordForm::SCHEME_DIGEST, "https://some.domain.com/other_domain",
378 NULL, NULL, NULL, NULL, NULL, L"digest_auth_user", NULL, false, true,
379 0 },
380 0, 0 },
381 // Garbage forms should have no matches.
382 { { PasswordForm::SCHEME_HTML, "foo/bar/baz",
383 NULL, NULL, NULL, NULL, NULL, NULL, NULL, false, false, 0 }, 0, 0 },
386 MacKeychainPasswordFormAdapter keychain_adapter(keychain_);
387 MacKeychainPasswordFormAdapter owned_keychain_adapter(keychain_);
388 owned_keychain_adapter.SetFindsOnlyOwnedItems(true);
389 for (unsigned int i = 0; i < ARRAYSIZE_UNSAFE(test_data); ++i) {
390 scoped_ptr<PasswordForm> query_form(
391 CreatePasswordFormFromData(test_data[i].data));
393 // Check matches treating the form as a fill target.
394 std::vector<PasswordForm*> matching_items =
395 keychain_adapter.PasswordsFillingForm(query_form->signon_realm,
396 query_form->scheme);
397 EXPECT_EQ(test_data[i].expected_fill_matches, matching_items.size());
398 STLDeleteElements(&matching_items);
400 // Check matches treating the form as a merging target.
401 EXPECT_EQ(test_data[i].expected_merge_matches > 0,
402 keychain_adapter.HasPasswordsMergeableWithForm(*query_form));
403 std::vector<SecKeychainItemRef> keychain_items;
404 std::vector<internal_keychain_helpers::ItemFormPair> item_form_pairs =
405 internal_keychain_helpers::
406 ExtractAllKeychainItemAttributesIntoPasswordForms(&keychain_items,
407 *keychain_);
408 matching_items =
409 internal_keychain_helpers::ExtractPasswordsMergeableWithForm(
410 *keychain_, item_form_pairs, *query_form);
411 EXPECT_EQ(test_data[i].expected_merge_matches, matching_items.size());
412 STLDeleteContainerPairSecondPointers(item_form_pairs.begin(),
413 item_form_pairs.end());
414 for (std::vector<SecKeychainItemRef>::iterator i = keychain_items.begin();
415 i != keychain_items.end(); ++i) {
416 keychain_->Free(*i);
418 STLDeleteElements(&matching_items);
420 // None of the pre-seeded items are owned by us, so none should match an
421 // owned-passwords-only search.
422 matching_items = owned_keychain_adapter.PasswordsFillingForm(
423 query_form->signon_realm, query_form->scheme);
424 EXPECT_EQ(0U, matching_items.size());
425 STLDeleteElements(&matching_items);
429 // Changes just the origin path of |form|.
430 static void SetPasswordFormPath(PasswordForm* form, const char* path) {
431 GURL::Replacements replacement;
432 std::string new_value(path);
433 replacement.SetPathStr(new_value);
434 form->origin = form->origin.ReplaceComponents(replacement);
437 // Changes just the signon_realm port of |form|.
438 static void SetPasswordFormPort(PasswordForm* form, const char* port) {
439 GURL::Replacements replacement;
440 std::string new_value(port);
441 replacement.SetPortStr(new_value);
442 GURL signon_gurl = GURL(form->signon_realm);
443 form->signon_realm = signon_gurl.ReplaceComponents(replacement).spec();
446 // Changes just the signon_ream auth realm of |form|.
447 static void SetPasswordFormRealm(PasswordForm* form, const char* realm) {
448 GURL::Replacements replacement;
449 std::string new_value(realm);
450 replacement.SetPathStr(new_value);
451 GURL signon_gurl = GURL(form->signon_realm);
452 form->signon_realm = signon_gurl.ReplaceComponents(replacement).spec();
455 TEST_F(PasswordStoreMacInternalsTest, TestKeychainExactSearch) {
456 MacKeychainPasswordFormAdapter keychain_adapter(keychain_);
458 PasswordFormData base_form_data[] = {
459 { PasswordForm::SCHEME_HTML, "http://some.domain.com/",
460 "http://some.domain.com/insecure.html",
461 NULL, NULL, NULL, NULL, L"joe_user", NULL, true, false, 0 },
462 { PasswordForm::SCHEME_BASIC, "http://some.domain.com:4567/low_security",
463 "http://some.domain.com:4567/insecure.html",
464 NULL, NULL, NULL, NULL, L"basic_auth_user", NULL, true, false, 0 },
465 { PasswordForm::SCHEME_DIGEST, "https://some.domain.com/high_security",
466 "https://some.domain.com",
467 NULL, NULL, NULL, NULL, L"digest_auth_user", NULL, true, true, 0 },
470 for (unsigned int i = 0; i < arraysize(base_form_data); ++i) {
471 // Create a base form and make sure we find a match.
472 scoped_ptr<PasswordForm> base_form(CreatePasswordFormFromData(
473 base_form_data[i]));
474 EXPECT_TRUE(keychain_adapter.HasPasswordsMergeableWithForm(*base_form));
475 PasswordForm* match =
476 keychain_adapter.PasswordExactlyMatchingForm(*base_form);
477 EXPECT_TRUE(match != NULL);
478 if (match) {
479 EXPECT_EQ(base_form->scheme, match->scheme);
480 EXPECT_EQ(base_form->origin, match->origin);
481 EXPECT_EQ(base_form->username_value, match->username_value);
482 delete match;
485 // Make sure that the matching isn't looser than it should be by checking
486 // that slightly altered forms don't match.
487 std::vector<PasswordForm*> modified_forms;
489 modified_forms.push_back(new PasswordForm(*base_form));
490 modified_forms.back()->username_value = ASCIIToUTF16("wrong_user");
492 modified_forms.push_back(new PasswordForm(*base_form));
493 SetPasswordFormPath(modified_forms.back(), "elsewhere.html");
495 modified_forms.push_back(new PasswordForm(*base_form));
496 modified_forms.back()->scheme = PasswordForm::SCHEME_OTHER;
498 modified_forms.push_back(new PasswordForm(*base_form));
499 SetPasswordFormPort(modified_forms.back(), "1234");
501 modified_forms.push_back(new PasswordForm(*base_form));
502 modified_forms.back()->blacklisted_by_user = true;
504 if (base_form->scheme == PasswordForm::SCHEME_BASIC ||
505 base_form->scheme == PasswordForm::SCHEME_DIGEST) {
506 modified_forms.push_back(new PasswordForm(*base_form));
507 SetPasswordFormRealm(modified_forms.back(), "incorrect");
510 for (unsigned int j = 0; j < modified_forms.size(); ++j) {
511 PasswordForm* match =
512 keychain_adapter.PasswordExactlyMatchingForm(*modified_forms[j]);
513 EXPECT_EQ(NULL, match) << "In modified version " << j << " of base form "
514 << i;
516 STLDeleteElements(&modified_forms);
520 TEST_F(PasswordStoreMacInternalsTest, TestKeychainAdd) {
521 struct TestDataAndExpectation {
522 PasswordFormData data;
523 bool should_succeed;
525 TestDataAndExpectation test_data[] = {
526 // Test a variety of scheme/port/protocol/path variations.
527 { { PasswordForm::SCHEME_HTML, "http://web.site.com/",
528 "http://web.site.com/path/to/page.html", NULL, NULL, NULL, NULL,
529 L"anonymous", L"knock-knock", false, false, 0 }, true },
530 { { PasswordForm::SCHEME_HTML, "https://web.site.com/",
531 "https://web.site.com/", NULL, NULL, NULL, NULL,
532 L"admin", L"p4ssw0rd", false, false, 0 }, true },
533 { { PasswordForm::SCHEME_BASIC, "http://a.site.com:2222/therealm",
534 "http://a.site.com:2222/", NULL, NULL, NULL, NULL,
535 L"username", L"password", false, false, 0 }, true },
536 { { PasswordForm::SCHEME_DIGEST, "https://digest.site.com/differentrealm",
537 "https://digest.site.com/secure.html", NULL, NULL, NULL, NULL,
538 L"testname", L"testpass", false, false, 0 }, true },
539 // Make sure that garbage forms are rejected.
540 { { PasswordForm::SCHEME_HTML, "gobbledygook",
541 "gobbledygook", NULL, NULL, NULL, NULL,
542 L"anonymous", L"knock-knock", false, false, 0 }, false },
543 // Test that failing to update a duplicate (forced using the magic failure
544 // password; see MockAppleKeychain::ItemModifyAttributesAndData) is
545 // reported.
546 { { PasswordForm::SCHEME_HTML, "http://some.domain.com",
547 "http://some.domain.com/insecure.html", NULL, NULL, NULL, NULL,
548 L"joe_user", L"fail_me", false, false, 0 }, false },
551 MacKeychainPasswordFormAdapter owned_keychain_adapter(keychain_);
552 owned_keychain_adapter.SetFindsOnlyOwnedItems(true);
554 for (unsigned int i = 0; i < ARRAYSIZE_UNSAFE(test_data); ++i) {
555 scoped_ptr<PasswordForm> in_form(
556 CreatePasswordFormFromData(test_data[i].data));
557 bool add_succeeded = owned_keychain_adapter.AddPassword(*in_form);
558 EXPECT_EQ(test_data[i].should_succeed, add_succeeded);
559 if (add_succeeded) {
560 EXPECT_TRUE(owned_keychain_adapter.HasPasswordsMergeableWithForm(
561 *in_form));
562 scoped_ptr<PasswordForm> out_form(
563 owned_keychain_adapter.PasswordExactlyMatchingForm(*in_form));
564 EXPECT_TRUE(out_form.get() != NULL);
565 EXPECT_EQ(out_form->scheme, in_form->scheme);
566 EXPECT_EQ(out_form->signon_realm, in_form->signon_realm);
567 EXPECT_EQ(out_form->origin, in_form->origin);
568 EXPECT_EQ(out_form->username_value, in_form->username_value);
569 EXPECT_EQ(out_form->password_value, in_form->password_value);
573 // Test that adding duplicate item updates the existing item.
575 PasswordFormData data = {
576 PasswordForm::SCHEME_HTML, "http://some.domain.com",
577 "http://some.domain.com/insecure.html", NULL,
578 NULL, NULL, NULL, L"joe_user", L"updated_password", false, false, 0
580 scoped_ptr<PasswordForm> update_form(CreatePasswordFormFromData(data));
581 MacKeychainPasswordFormAdapter keychain_adapter(keychain_);
582 EXPECT_TRUE(keychain_adapter.AddPassword(*update_form));
583 SecKeychainItemRef keychain_item = reinterpret_cast<SecKeychainItemRef>(2);
584 PasswordForm stored_form;
585 internal_keychain_helpers::FillPasswordFormFromKeychainItem(*keychain_,
586 keychain_item,
587 &stored_form,
588 true);
589 EXPECT_EQ(update_form->password_value, stored_form.password_value);
593 TEST_F(PasswordStoreMacInternalsTest, TestKeychainRemove) {
594 struct TestDataAndExpectation {
595 PasswordFormData data;
596 bool should_succeed;
598 TestDataAndExpectation test_data[] = {
599 // Test deletion of an item that we add.
600 { { PasswordForm::SCHEME_HTML, "http://web.site.com/",
601 "http://web.site.com/path/to/page.html", NULL, NULL, NULL, NULL,
602 L"anonymous", L"knock-knock", false, false, 0 }, true },
603 // Make sure we don't delete items we don't own.
604 { { PasswordForm::SCHEME_HTML, "http://some.domain.com/",
605 "http://some.domain.com/insecure.html", NULL, NULL, NULL, NULL,
606 L"joe_user", NULL, true, false, 0 }, false },
609 MacKeychainPasswordFormAdapter owned_keychain_adapter(keychain_);
610 owned_keychain_adapter.SetFindsOnlyOwnedItems(true);
612 // Add our test item so that we can delete it.
613 PasswordForm* add_form = CreatePasswordFormFromData(test_data[0].data);
614 EXPECT_TRUE(owned_keychain_adapter.AddPassword(*add_form));
615 delete add_form;
617 for (unsigned int i = 0; i < ARRAYSIZE_UNSAFE(test_data); ++i) {
618 scoped_ptr<PasswordForm> form(CreatePasswordFormFromData(
619 test_data[i].data));
620 EXPECT_EQ(test_data[i].should_succeed,
621 owned_keychain_adapter.RemovePassword(*form));
623 MacKeychainPasswordFormAdapter keychain_adapter(keychain_);
624 PasswordForm* match = keychain_adapter.PasswordExactlyMatchingForm(*form);
625 EXPECT_EQ(test_data[i].should_succeed, match == NULL);
626 if (match) {
627 delete match;
632 TEST_F(PasswordStoreMacInternalsTest, TestFormMatch) {
633 PasswordForm base_form;
634 base_form.signon_realm = std::string("http://some.domain.com/");
635 base_form.origin = GURL("http://some.domain.com/page.html");
636 base_form.username_value = ASCIIToUTF16("joe_user");
639 // Check that everything unimportant can be changed.
640 PasswordForm different_form(base_form);
641 different_form.username_element = ASCIIToUTF16("username");
642 different_form.submit_element = ASCIIToUTF16("submit");
643 different_form.username_element = ASCIIToUTF16("password");
644 different_form.password_value = ASCIIToUTF16("sekrit");
645 different_form.action = GURL("http://some.domain.com/action.cgi");
646 different_form.ssl_valid = true;
647 different_form.preferred = true;
648 different_form.date_created = base::Time::Now();
649 EXPECT_TRUE(
650 FormsMatchForMerge(base_form, different_form, STRICT_FORM_MATCH));
652 // Check that path differences don't prevent a match.
653 base_form.origin = GURL("http://some.domain.com/other_page.html");
654 EXPECT_TRUE(
655 FormsMatchForMerge(base_form, different_form, STRICT_FORM_MATCH));
658 // Check that any one primary key changing is enough to prevent matching.
660 PasswordForm different_form(base_form);
661 different_form.scheme = PasswordForm::SCHEME_DIGEST;
662 EXPECT_FALSE(
663 FormsMatchForMerge(base_form, different_form, STRICT_FORM_MATCH));
666 PasswordForm different_form(base_form);
667 different_form.signon_realm = std::string("http://some.domain.com:8080/");
668 EXPECT_FALSE(
669 FormsMatchForMerge(base_form, different_form, STRICT_FORM_MATCH));
672 PasswordForm different_form(base_form);
673 different_form.username_value = ASCIIToUTF16("john.doe");
674 EXPECT_FALSE(
675 FormsMatchForMerge(base_form, different_form, STRICT_FORM_MATCH));
678 PasswordForm different_form(base_form);
679 different_form.blacklisted_by_user = true;
680 EXPECT_FALSE(
681 FormsMatchForMerge(base_form, different_form, STRICT_FORM_MATCH));
684 // Blacklist forms should *never* match for merging, even when identical
685 // (and certainly not when only one is a blacklist entry).
687 PasswordForm form_a(base_form);
688 form_a.blacklisted_by_user = true;
689 PasswordForm form_b(form_a);
690 EXPECT_FALSE(FormsMatchForMerge(form_a, form_b, STRICT_FORM_MATCH));
694 TEST_F(PasswordStoreMacInternalsTest, TestFormMerge) {
695 // Set up a bunch of test data to use in varying combinations.
696 PasswordFormData keychain_user_1 =
697 { PasswordForm::SCHEME_HTML, "http://some.domain.com/",
698 "http://some.domain.com/", "", L"", L"", L"", L"joe_user", L"sekrit",
699 false, false, 1010101010 };
700 PasswordFormData keychain_user_1_with_path =
701 { PasswordForm::SCHEME_HTML, "http://some.domain.com/",
702 "http://some.domain.com/page.html",
703 "", L"", L"", L"", L"joe_user", L"otherpassword",
704 false, false, 1010101010 };
705 PasswordFormData keychain_user_2 =
706 { PasswordForm::SCHEME_HTML, "http://some.domain.com/",
707 "http://some.domain.com/", "", L"", L"", L"", L"john.doe", L"sesame",
708 false, false, 958739876 };
709 PasswordFormData keychain_blacklist =
710 { PasswordForm::SCHEME_HTML, "http://some.domain.com/",
711 "http://some.domain.com/", "", L"", L"", L"", NULL, NULL,
712 false, false, 1010101010 };
714 PasswordFormData db_user_1 =
715 { PasswordForm::SCHEME_HTML, "http://some.domain.com/",
716 "http://some.domain.com/", "http://some.domain.com/action.cgi",
717 L"submit", L"username", L"password", L"joe_user", L"",
718 true, false, 1212121212 };
719 PasswordFormData db_user_1_with_path =
720 { PasswordForm::SCHEME_HTML, "http://some.domain.com/",
721 "http://some.domain.com/page.html",
722 "http://some.domain.com/handlepage.cgi",
723 L"submit", L"username", L"password", L"joe_user", L"",
724 true, false, 1234567890 };
725 PasswordFormData db_user_3_with_path =
726 { PasswordForm::SCHEME_HTML, "http://some.domain.com/",
727 "http://some.domain.com/page.html",
728 "http://some.domain.com/handlepage.cgi",
729 L"submit", L"username", L"password", L"second-account", L"",
730 true, false, 1240000000 };
731 PasswordFormData database_blacklist_with_path =
732 { PasswordForm::SCHEME_HTML, "http://some.domain.com/",
733 "http://some.domain.com/path.html", "http://some.domain.com/action.cgi",
734 L"submit", L"username", L"password", NULL, NULL,
735 true, false, 1212121212 };
737 PasswordFormData merged_user_1 =
738 { PasswordForm::SCHEME_HTML, "http://some.domain.com/",
739 "http://some.domain.com/", "http://some.domain.com/action.cgi",
740 L"submit", L"username", L"password", L"joe_user", L"sekrit",
741 true, false, 1212121212 };
742 PasswordFormData merged_user_1_with_db_path =
743 { PasswordForm::SCHEME_HTML, "http://some.domain.com/",
744 "http://some.domain.com/page.html",
745 "http://some.domain.com/handlepage.cgi",
746 L"submit", L"username", L"password", L"joe_user", L"sekrit",
747 true, false, 1234567890 };
748 PasswordFormData merged_user_1_with_both_paths =
749 { PasswordForm::SCHEME_HTML, "http://some.domain.com/",
750 "http://some.domain.com/page.html",
751 "http://some.domain.com/handlepage.cgi",
752 L"submit", L"username", L"password", L"joe_user", L"otherpassword",
753 true, false, 1234567890 };
755 // Build up the big multi-dimensional array of data sets that will actually
756 // drive the test. Use vectors rather than arrays so that initialization is
757 // simple.
758 enum {
759 KEYCHAIN_INPUT = 0,
760 DATABASE_INPUT,
761 MERGE_OUTPUT,
762 KEYCHAIN_OUTPUT,
763 DATABASE_OUTPUT,
764 MERGE_IO_ARRAY_COUNT // termination marker
766 const unsigned int kTestCount = 4;
767 std::vector< std::vector< std::vector<PasswordFormData*> > > test_data(
768 MERGE_IO_ARRAY_COUNT, std::vector< std::vector<PasswordFormData*> >(
769 kTestCount, std::vector<PasswordFormData*>()));
770 unsigned int current_test = 0;
772 // Test a merge with a few accounts in both systems, with partial overlap.
773 CHECK(current_test < kTestCount);
774 test_data[KEYCHAIN_INPUT][current_test].push_back(&keychain_user_1);
775 test_data[KEYCHAIN_INPUT][current_test].push_back(&keychain_user_2);
776 test_data[DATABASE_INPUT][current_test].push_back(&db_user_1);
777 test_data[DATABASE_INPUT][current_test].push_back(&db_user_1_with_path);
778 test_data[DATABASE_INPUT][current_test].push_back(&db_user_3_with_path);
779 test_data[MERGE_OUTPUT][current_test].push_back(&merged_user_1);
780 test_data[MERGE_OUTPUT][current_test].push_back(&merged_user_1_with_db_path);
781 test_data[KEYCHAIN_OUTPUT][current_test].push_back(&keychain_user_2);
782 test_data[DATABASE_OUTPUT][current_test].push_back(&db_user_3_with_path);
784 // Test a merge where Chrome has a blacklist entry, and the keychain has
785 // a stored account.
786 ++current_test;
787 CHECK(current_test < kTestCount);
788 test_data[KEYCHAIN_INPUT][current_test].push_back(&keychain_user_1);
789 test_data[DATABASE_INPUT][current_test].push_back(
790 &database_blacklist_with_path);
791 // We expect both to be present because a blacklist could be specific to a
792 // subpath, and we want access to the password on other paths.
793 test_data[MERGE_OUTPUT][current_test].push_back(
794 &database_blacklist_with_path);
795 test_data[KEYCHAIN_OUTPUT][current_test].push_back(&keychain_user_1);
797 // Test a merge where Chrome has an account, and Keychain has a blacklist
798 // (from another browser) and the Chrome password data.
799 ++current_test;
800 CHECK(current_test < kTestCount);
801 test_data[KEYCHAIN_INPUT][current_test].push_back(&keychain_blacklist);
802 test_data[KEYCHAIN_INPUT][current_test].push_back(&keychain_user_1);
803 test_data[DATABASE_INPUT][current_test].push_back(&db_user_1);
804 test_data[MERGE_OUTPUT][current_test].push_back(&merged_user_1);
805 test_data[KEYCHAIN_OUTPUT][current_test].push_back(&keychain_blacklist);
807 // Test that matches are done using exact path when possible.
808 ++current_test;
809 CHECK(current_test < kTestCount);
810 test_data[KEYCHAIN_INPUT][current_test].push_back(&keychain_user_1);
811 test_data[KEYCHAIN_INPUT][current_test].push_back(&keychain_user_1_with_path);
812 test_data[DATABASE_INPUT][current_test].push_back(&db_user_1);
813 test_data[DATABASE_INPUT][current_test].push_back(&db_user_1_with_path);
814 test_data[MERGE_OUTPUT][current_test].push_back(&merged_user_1);
815 test_data[MERGE_OUTPUT][current_test].push_back(
816 &merged_user_1_with_both_paths);
818 for (unsigned int test_case = 0; test_case <= current_test; ++test_case) {
819 std::vector<PasswordForm*> keychain_forms;
820 for (std::vector<PasswordFormData*>::iterator i =
821 test_data[KEYCHAIN_INPUT][test_case].begin();
822 i != test_data[KEYCHAIN_INPUT][test_case].end(); ++i) {
823 keychain_forms.push_back(CreatePasswordFormFromData(*(*i)));
825 std::vector<PasswordForm*> database_forms;
826 for (std::vector<PasswordFormData*>::iterator i =
827 test_data[DATABASE_INPUT][test_case].begin();
828 i != test_data[DATABASE_INPUT][test_case].end(); ++i) {
829 database_forms.push_back(CreatePasswordFormFromData(*(*i)));
832 std::vector<PasswordForm*> merged_forms;
833 internal_keychain_helpers::MergePasswordForms(&keychain_forms,
834 &database_forms,
835 &merged_forms);
837 CHECK_FORMS(keychain_forms, test_data[KEYCHAIN_OUTPUT][test_case],
838 test_case);
839 CHECK_FORMS(database_forms, test_data[DATABASE_OUTPUT][test_case],
840 test_case);
841 CHECK_FORMS(merged_forms, test_data[MERGE_OUTPUT][test_case], test_case);
843 STLDeleteElements(&keychain_forms);
844 STLDeleteElements(&database_forms);
845 STLDeleteElements(&merged_forms);
849 TEST_F(PasswordStoreMacInternalsTest, TestPasswordBulkLookup) {
850 PasswordFormData db_data[] = {
851 { PasswordForm::SCHEME_HTML, "http://some.domain.com/",
852 "http://some.domain.com/", "http://some.domain.com/action.cgi",
853 L"submit", L"username", L"password", L"joe_user", L"",
854 true, false, 1212121212 },
855 { PasswordForm::SCHEME_HTML, "http://some.domain.com/",
856 "http://some.domain.com/page.html",
857 "http://some.domain.com/handlepage.cgi",
858 L"submit", L"username", L"password", L"joe_user", L"",
859 true, false, 1234567890 },
860 { PasswordForm::SCHEME_HTML, "http://some.domain.com/",
861 "http://some.domain.com/page.html",
862 "http://some.domain.com/handlepage.cgi",
863 L"submit", L"username", L"password", L"second-account", L"",
864 true, false, 1240000000 },
865 { PasswordForm::SCHEME_HTML, "http://dont.remember.com/",
866 "http://dont.remember.com/",
867 "http://dont.remember.com/handlepage.cgi",
868 L"submit", L"username", L"password", L"joe_user", L"",
869 true, false, 1240000000 },
870 { PasswordForm::SCHEME_HTML, "http://some.domain.com/",
871 "http://some.domain.com/path.html", "http://some.domain.com/action.cgi",
872 L"submit", L"username", L"password", NULL, NULL,
873 true, false, 1212121212 },
875 std::vector<PasswordForm*> database_forms;
876 for (unsigned int i = 0; i < ARRAYSIZE_UNSAFE(db_data); ++i) {
877 database_forms.push_back(CreatePasswordFormFromData(db_data[i]));
879 std::vector<PasswordForm*> merged_forms =
880 internal_keychain_helpers::GetPasswordsForForms(*keychain_,
881 &database_forms);
882 EXPECT_EQ(2U, database_forms.size());
883 ASSERT_EQ(3U, merged_forms.size());
884 EXPECT_EQ(ASCIIToUTF16("sekrit"), merged_forms[0]->password_value);
885 EXPECT_EQ(ASCIIToUTF16("sekrit"), merged_forms[1]->password_value);
886 EXPECT_TRUE(merged_forms[2]->blacklisted_by_user);
888 STLDeleteElements(&database_forms);
889 STLDeleteElements(&merged_forms);
892 TEST_F(PasswordStoreMacInternalsTest, TestBlacklistedFiltering) {
893 PasswordFormData db_data[] = {
894 { PasswordForm::SCHEME_HTML, "http://dont.remember.com/",
895 "http://dont.remember.com/",
896 "http://dont.remember.com/handlepage.cgi",
897 L"submit", L"username", L"password", L"joe_user", L"non_empty_password",
898 true, false, 1240000000 },
899 { PasswordForm::SCHEME_HTML, "https://dont.remember.com/",
900 "https://dont.remember.com/",
901 "https://dont.remember.com/handlepage_secure.cgi",
902 L"submit", L"username", L"password", L"joe_user", L"non_empty_password",
903 true, false, 1240000000 },
905 std::vector<PasswordForm*> database_forms;
906 for (unsigned int i = 0; i < ARRAYSIZE_UNSAFE(db_data); ++i) {
907 database_forms.push_back(CreatePasswordFormFromData(db_data[i]));
909 std::vector<PasswordForm*> merged_forms =
910 internal_keychain_helpers::GetPasswordsForForms(*keychain_,
911 &database_forms);
912 EXPECT_EQ(2U, database_forms.size());
913 ASSERT_EQ(0U, merged_forms.size());
915 STLDeleteElements(&database_forms);
916 STLDeleteElements(&merged_forms);
919 TEST_F(PasswordStoreMacInternalsTest, TestFillPasswordFormFromKeychainItem) {
920 // When |extract_password_data| is false, the password field must be empty,
921 // and |blacklisted_by_user| must be false.
922 SecKeychainItemRef keychain_item = reinterpret_cast<SecKeychainItemRef>(1);
923 PasswordForm form_without_extracted_password;
924 bool parsed = internal_keychain_helpers::FillPasswordFormFromKeychainItem(
925 *keychain_,
926 keychain_item,
927 &form_without_extracted_password,
928 false); // Do not extract password.
929 EXPECT_TRUE(parsed);
930 ASSERT_TRUE(form_without_extracted_password.password_value.empty());
931 ASSERT_FALSE(form_without_extracted_password.blacklisted_by_user);
933 // When |extract_password_data| is true and the keychain entry has a non-empty
934 // password, the password field must be non-empty, and the value of
935 // |blacklisted_by_user| must be false.
936 keychain_item = reinterpret_cast<SecKeychainItemRef>(1);
937 PasswordForm form_with_extracted_password;
938 parsed = internal_keychain_helpers::FillPasswordFormFromKeychainItem(
939 *keychain_,
940 keychain_item,
941 &form_with_extracted_password,
942 true); // Extract password.
943 EXPECT_TRUE(parsed);
944 ASSERT_EQ(ASCIIToUTF16("sekrit"),
945 form_with_extracted_password.password_value);
946 ASSERT_FALSE(form_with_extracted_password.blacklisted_by_user);
948 // When |extract_password_data| is true and the keychain entry has an empty
949 // username and password (""), the password field must be empty, and the value
950 // of |blacklisted_by_user| must be true.
951 keychain_item = reinterpret_cast<SecKeychainItemRef>(4);
952 PasswordForm negative_form;
953 parsed = internal_keychain_helpers::FillPasswordFormFromKeychainItem(
954 *keychain_,
955 keychain_item,
956 &negative_form,
957 true); // Extract password.
958 EXPECT_TRUE(parsed);
959 ASSERT_TRUE(negative_form.username_value.empty());
960 ASSERT_TRUE(negative_form.password_value.empty());
961 ASSERT_TRUE(negative_form.blacklisted_by_user);
963 // When |extract_password_data| is true and the keychain entry has an empty
964 // password (""), the password field must be empty (""), and the value of
965 // |blacklisted_by_user| must be true.
966 keychain_item = reinterpret_cast<SecKeychainItemRef>(5);
967 PasswordForm form_with_empty_password_a;
968 parsed = internal_keychain_helpers::FillPasswordFormFromKeychainItem(
969 *keychain_,
970 keychain_item,
971 &form_with_empty_password_a,
972 true); // Extract password.
973 EXPECT_TRUE(parsed);
974 ASSERT_TRUE(form_with_empty_password_a.password_value.empty());
975 ASSERT_TRUE(form_with_empty_password_a.blacklisted_by_user);
977 // When |extract_password_data| is true and the keychain entry has a single
978 // space password (" "), the password field must be a single space (" "), and
979 // the value of |blacklisted_by_user| must be true.
980 keychain_item = reinterpret_cast<SecKeychainItemRef>(6);
981 PasswordForm form_with_empty_password_b;
982 parsed = internal_keychain_helpers::FillPasswordFormFromKeychainItem(
983 *keychain_,
984 keychain_item,
985 &form_with_empty_password_b,
986 true); // Extract password.
987 EXPECT_TRUE(parsed);
988 ASSERT_EQ(ASCIIToUTF16(" "),
989 form_with_empty_password_b.password_value);
990 ASSERT_TRUE(form_with_empty_password_b.blacklisted_by_user);
993 TEST_F(PasswordStoreMacInternalsTest, TestPasswordGetAll) {
994 MacKeychainPasswordFormAdapter keychain_adapter(keychain_);
995 MacKeychainPasswordFormAdapter owned_keychain_adapter(keychain_);
996 owned_keychain_adapter.SetFindsOnlyOwnedItems(true);
998 // Add a few passwords of various types so that we own some.
999 PasswordFormData owned_password_data[] = {
1000 { PasswordForm::SCHEME_HTML, "http://web.site.com/",
1001 "http://web.site.com/path/to/page.html", NULL, NULL, NULL, NULL,
1002 L"anonymous", L"knock-knock", false, false, 0 },
1003 { PasswordForm::SCHEME_BASIC, "http://a.site.com:2222/therealm",
1004 "http://a.site.com:2222/", NULL, NULL, NULL, NULL,
1005 L"username", L"password", false, false, 0 },
1006 { PasswordForm::SCHEME_DIGEST, "https://digest.site.com/differentrealm",
1007 "https://digest.site.com/secure.html", NULL, NULL, NULL, NULL,
1008 L"testname", L"testpass", false, false, 0 },
1010 for (unsigned int i = 0; i < arraysize(owned_password_data); ++i) {
1011 scoped_ptr<PasswordForm> form(CreatePasswordFormFromData(
1012 owned_password_data[i]));
1013 owned_keychain_adapter.AddPassword(*form);
1016 std::vector<PasswordForm*> all_passwords =
1017 keychain_adapter.GetAllPasswordFormPasswords();
1018 EXPECT_EQ(8 + arraysize(owned_password_data), all_passwords.size());
1019 STLDeleteElements(&all_passwords);
1021 std::vector<PasswordForm*> owned_passwords =
1022 owned_keychain_adapter.GetAllPasswordFormPasswords();
1023 EXPECT_EQ(arraysize(owned_password_data), owned_passwords.size());
1024 STLDeleteElements(&owned_passwords);
1027 #pragma mark -
1029 class PasswordStoreMacTest : public testing::Test {
1030 public:
1031 PasswordStoreMacTest() : ui_thread_(BrowserThread::UI, &message_loop_) {}
1033 virtual void SetUp() {
1034 login_db_ = new LoginDatabase();
1035 ASSERT_TRUE(db_dir_.CreateUniqueTempDir());
1036 base::FilePath db_file = db_dir_.path().AppendASCII("login.db");
1037 ASSERT_TRUE(login_db_->Init(db_file));
1039 keychain_ = new MockAppleKeychain();
1041 store_ = new PasswordStoreMac(
1042 base::MessageLoopProxy::current(),
1043 base::MessageLoopProxy::current(),
1044 keychain_,
1045 login_db_);
1046 ASSERT_TRUE(store_->Init());
1049 virtual void TearDown() {
1050 store_->Shutdown();
1051 base::MessageLoop::current()->PostTask(FROM_HERE,
1052 base::MessageLoop::QuitClosure());
1053 base::MessageLoop::current()->Run();
1056 void WaitForStoreUpdate() {
1057 // Do a store-level query to wait for all the operations above to be done.
1058 MockPasswordStoreConsumer consumer;
1059 ON_CALL(consumer, OnGetPasswordStoreResults(_))
1060 .WillByDefault(QuitUIMessageLoop());
1061 EXPECT_CALL(consumer, OnGetPasswordStoreResults(_))
1062 .WillOnce(DoAll(WithArg<0>(STLDeleteElements0()), QuitUIMessageLoop()));
1063 store_->GetLogins(PasswordForm(), PasswordStore::ALLOW_PROMPT, &consumer);
1064 base::MessageLoop::current()->Run();
1067 protected:
1068 base::MessageLoopForUI message_loop_;
1069 content::TestBrowserThread ui_thread_;
1071 MockAppleKeychain* keychain_; // Owned by store_.
1072 LoginDatabase* login_db_; // Owned by store_.
1073 scoped_refptr<PasswordStoreMac> store_;
1074 base::ScopedTempDir db_dir_;
1077 TEST_F(PasswordStoreMacTest, TestStoreUpdate) {
1078 // Insert a password into both the database and the keychain.
1079 // This is done manually, rather than through store_->AddLogin, because the
1080 // Mock Keychain isn't smart enough to be able to support update generically,
1081 // so some.domain.com triggers special handling to test it that make inserting
1082 // fail.
1083 PasswordFormData joint_data = {
1084 PasswordForm::SCHEME_HTML, "http://some.domain.com/",
1085 "http://some.domain.com/insecure.html", "login.cgi",
1086 L"username", L"password", L"submit", L"joe_user", L"sekrit", true, false, 1
1088 scoped_ptr<PasswordForm> joint_form(CreatePasswordFormFromData(joint_data));
1089 login_db_->AddLogin(*joint_form);
1090 MockAppleKeychain::KeychainTestData joint_keychain_data = {
1091 kSecAuthenticationTypeHTMLForm, "some.domain.com",
1092 kSecProtocolTypeHTTP, "/insecure.html", 0, NULL, "20020601171500Z",
1093 "joe_user", "sekrit", false };
1094 keychain_->AddTestItem(joint_keychain_data);
1096 // Insert a password into the keychain only.
1097 MockAppleKeychain::KeychainTestData keychain_only_data = {
1098 kSecAuthenticationTypeHTMLForm, "keychain.only.com",
1099 kSecProtocolTypeHTTP, NULL, 0, NULL, "20020601171500Z",
1100 "keychain", "only", false
1102 keychain_->AddTestItem(keychain_only_data);
1104 struct UpdateData {
1105 PasswordFormData form_data;
1106 const char* password; // NULL indicates no entry should be present.
1109 // Make a series of update calls.
1110 UpdateData updates[] = {
1111 // Update the keychain+db passwords (the normal password update case).
1112 { { PasswordForm::SCHEME_HTML, "http://some.domain.com/",
1113 "http://some.domain.com/insecure.html", "login.cgi",
1114 L"username", L"password", L"submit", L"joe_user", L"53krit",
1115 true, false, 2 },
1116 "53krit",
1118 // Update the keychain-only password; this simulates the initial use of a
1119 // password stored by another browsers.
1120 { { PasswordForm::SCHEME_HTML, "http://keychain.only.com/",
1121 "http://keychain.only.com/login.html", "login.cgi",
1122 L"username", L"password", L"submit", L"keychain", L"only",
1123 true, false, 2 },
1124 "only",
1126 // Update a password that doesn't exist in either location. This tests the
1127 // case where a form is filled, then the stored login is removed, then the
1128 // form is submitted.
1129 { { PasswordForm::SCHEME_HTML, "http://different.com/",
1130 "http://different.com/index.html", "login.cgi",
1131 L"username", L"password", L"submit", L"abc", L"123",
1132 true, false, 2 },
1133 NULL,
1136 for (unsigned int i = 0; i < ARRAYSIZE_UNSAFE(updates); ++i) {
1137 scoped_ptr<PasswordForm> form(CreatePasswordFormFromData(
1138 updates[i].form_data));
1139 store_->UpdateLogin(*form);
1142 WaitForStoreUpdate();
1144 MacKeychainPasswordFormAdapter keychain_adapter(keychain_);
1145 for (unsigned int i = 0; i < ARRAYSIZE_UNSAFE(updates); ++i) {
1146 scoped_ptr<PasswordForm> query_form(
1147 CreatePasswordFormFromData(updates[i].form_data));
1149 std::vector<PasswordForm*> matching_items =
1150 keychain_adapter.PasswordsFillingForm(query_form->signon_realm,
1151 query_form->scheme);
1152 if (updates[i].password) {
1153 EXPECT_GT(matching_items.size(), 0U) << "iteration " << i;
1154 if (matching_items.size() >= 1)
1155 EXPECT_EQ(ASCIIToUTF16(updates[i].password),
1156 matching_items[0]->password_value) << "iteration " << i;
1157 } else {
1158 EXPECT_EQ(0U, matching_items.size()) << "iteration " << i;
1160 STLDeleteElements(&matching_items);
1162 login_db_->GetLogins(*query_form, &matching_items);
1163 EXPECT_EQ(updates[i].password ? 1U : 0U, matching_items.size())
1164 << "iteration " << i;
1165 STLDeleteElements(&matching_items);
1169 TEST_F(PasswordStoreMacTest, TestDBKeychainAssociation) {
1170 // Tests that association between the keychain and login database parts of a
1171 // password added by fuzzy (PSL) matching works.
1172 // 1. Add a password for www.facebook.com
1173 // 2. Get a password for m.facebook.com. This fuzzy matches and returns the
1174 // www.facebook.com password.
1175 // 3. Add the returned password for m.facebook.com.
1176 // 4. Remove both passwords.
1177 // -> check: that both are gone from the login DB and the keychain
1178 // This test should in particular ensure that we don't keep passwords in the
1179 // keychain just before we think we still have other (fuzzy-)matching entries
1180 // for them in the login database. (For example, here if we deleted the
1181 // www.facebook.com password from the login database, we should not be blocked
1182 // from deleting it from the keystore just becaus the m.facebook.com password
1183 // fuzzy-matches the www.facebook.com one.)
1185 // 1. Add a password for www.facebook.com
1186 PasswordFormData www_form_data = {
1187 PasswordForm::SCHEME_HTML, "http://www.facebook.com/",
1188 "http://www.facebook.com/index.html", "login",
1189 L"username", L"password", L"submit", L"joe_user", L"sekrit", true, false, 1
1191 scoped_ptr<PasswordForm> www_form(CreatePasswordFormFromData(www_form_data));
1192 login_db_->AddLogin(*www_form);
1193 MacKeychainPasswordFormAdapter owned_keychain_adapter(keychain_);
1194 owned_keychain_adapter.SetFindsOnlyOwnedItems(true);
1195 owned_keychain_adapter.AddPassword(*www_form);
1197 // 2. Get a password for m.facebook.com.
1198 PasswordForm m_form(*www_form);
1199 m_form.signon_realm = "http://m.facebook.com";
1200 m_form.origin = GURL("http://m.facebook.com/index.html");
1201 MockPasswordStoreConsumer consumer;
1202 ON_CALL(consumer, OnGetPasswordStoreResults(_))
1203 .WillByDefault(QuitUIMessageLoop());
1204 EXPECT_CALL(consumer, OnGetPasswordStoreResults(_)).WillOnce(DoAll(
1205 WithArg<0>(Invoke(&consumer, &MockPasswordStoreConsumer::CopyElements)),
1206 WithArg<0>(STLDeleteElements0()),
1207 QuitUIMessageLoop()));
1208 store_->GetLogins(m_form, PasswordStore::ALLOW_PROMPT, &consumer);
1209 base::MessageLoop::current()->Run();
1210 EXPECT_EQ(1u, consumer.last_result.size());
1212 // 3. Add the returned password for m.facebook.com.
1213 login_db_->AddLogin(consumer.last_result[0]);
1214 owned_keychain_adapter.AddPassword(m_form);
1216 // 4. Remove both passwords.
1217 store_->RemoveLogin(*www_form);
1218 store_->RemoveLogin(m_form);
1219 WaitForStoreUpdate();
1221 std::vector<PasswordForm*> matching_items;
1222 // No trace of www.facebook.com.
1223 matching_items = owned_keychain_adapter.PasswordsFillingForm(
1224 www_form->signon_realm, www_form->scheme);
1225 EXPECT_EQ(0u, matching_items.size());
1226 login_db_->GetLogins(*www_form, &matching_items);
1227 EXPECT_EQ(0u, matching_items.size());
1228 // No trace of m.facebook.com.
1229 matching_items = owned_keychain_adapter.PasswordsFillingForm(
1230 m_form.signon_realm, m_form.scheme);
1231 EXPECT_EQ(0u, matching_items.size());
1232 login_db_->GetLogins(m_form, &matching_items);
1233 EXPECT_EQ(0u, matching_items.size());