1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "testing/gmock/include/gmock/gmock.h"
6 #include "testing/gtest/include/gtest/gtest.h"
8 #include "base/basictypes.h"
9 #include "base/files/scoped_temp_dir.h"
10 #include "base/path_service.h"
11 #include "base/stl_util.h"
12 #include "base/strings/string_util.h"
13 #include "base/strings/utf_string_conversions.h"
14 #include "chrome/browser/password_manager/password_store_mac.h"
15 #include "chrome/browser/password_manager/password_store_mac_internal.h"
16 #include "chrome/common/chrome_paths.h"
17 #include "components/password_manager/core/browser/password_store_consumer.h"
18 #include "content/public/test/test_browser_thread.h"
19 #include "crypto/mock_apple_keychain.h"
21 using autofill::PasswordForm
;
22 using base::ASCIIToUTF16
;
23 using base::WideToUTF16
;
24 using content::BrowserThread
;
25 using crypto::MockAppleKeychain
;
26 using internal_keychain_helpers::FormsMatchForMerge
;
27 using internal_keychain_helpers::STRICT_FORM_MATCH
;
28 using password_manager::LoginDatabase
;
29 using password_manager::PasswordStore
;
30 using password_manager::PasswordStoreConsumer
;
33 using testing::Invoke
;
34 using testing::WithArg
;
38 class MockPasswordStoreConsumer
: public PasswordStoreConsumer
{
40 MOCK_METHOD1(OnGetPasswordStoreResults
,
41 void(const std::vector
<autofill::PasswordForm
*>&));
43 void CopyElements(const std::vector
<autofill::PasswordForm
*>& forms
) {
45 for (size_t i
= 0; i
< forms
.size(); ++i
) {
46 last_result
.push_back(*forms
[i
]);
50 std::vector
<PasswordForm
> last_result
;
53 ACTION(STLDeleteElements0
) {
54 STLDeleteContainerPointers(arg0
.begin(), arg0
.end());
57 ACTION(QuitUIMessageLoop
) {
58 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
59 base::MessageLoop::current()->Quit();
62 class TestPasswordStoreMac
: public PasswordStoreMac
{
65 scoped_refptr
<base::SingleThreadTaskRunner
> main_thread_runner
,
66 scoped_refptr
<base::SingleThreadTaskRunner
> db_thread_runner
,
67 crypto::AppleKeychain
* keychain
,
68 LoginDatabase
* login_db
)
69 : PasswordStoreMac(main_thread_runner
,
75 using PasswordStoreMac::GetBackgroundTaskRunner
;
78 virtual ~TestPasswordStoreMac() {}
80 DISALLOW_COPY_AND_ASSIGN(TestPasswordStoreMac
);
87 class PasswordStoreMacInternalsTest
: public testing::Test
{
89 virtual void SetUp() {
90 MockAppleKeychain::KeychainTestData test_data
[] = {
92 { kSecAuthenticationTypeHTMLForm
, "some.domain.com",
93 kSecProtocolTypeHTTP
, NULL
, 0, NULL
, "20020601171500Z",
94 "joe_user", "sekrit", false },
95 // HTML form with path.
96 { kSecAuthenticationTypeHTMLForm
, "some.domain.com",
97 kSecProtocolTypeHTTP
, "/insecure.html", 0, NULL
, "19991231235959Z",
98 "joe_user", "sekrit", false },
99 // Secure HTML form with path.
100 { kSecAuthenticationTypeHTMLForm
, "some.domain.com",
101 kSecProtocolTypeHTTPS
, "/secure.html", 0, NULL
, "20100908070605Z",
102 "secure_user", "password", false },
103 // True negative item.
104 { kSecAuthenticationTypeHTMLForm
, "dont.remember.com",
105 kSecProtocolTypeHTTP
, NULL
, 0, NULL
, "20000101000000Z",
107 // De-facto negative item, type one.
108 { kSecAuthenticationTypeHTMLForm
, "dont.remember.com",
109 kSecProtocolTypeHTTP
, NULL
, 0, NULL
, "20000101000000Z",
110 "Password Not Stored", "", false },
111 // De-facto negative item, type two.
112 { kSecAuthenticationTypeHTMLForm
, "dont.remember.com",
113 kSecProtocolTypeHTTPS
, NULL
, 0, NULL
, "20000101000000Z",
114 "Password Not Stored", " ", false },
115 // HTTP auth basic, with port and path.
116 { kSecAuthenticationTypeHTTPBasic
, "some.domain.com",
117 kSecProtocolTypeHTTP
, "/insecure.html", 4567, "low_security",
119 "basic_auth_user", "basic", false },
120 // HTTP auth digest, secure.
121 { kSecAuthenticationTypeHTTPDigest
, "some.domain.com",
122 kSecProtocolTypeHTTPS
, NULL
, 0, "high_security", "19980330100000Z",
123 "digest_auth_user", "digest", false },
124 // An FTP password with an invalid date, for edge-case testing.
125 { kSecAuthenticationTypeDefault
, "a.server.com",
126 kSecProtocolTypeFTP
, NULL
, 0, NULL
, "20010203040",
127 "abc", "123", false },
130 keychain_
= new MockAppleKeychain();
132 for (unsigned int i
= 0; i
< arraysize(test_data
); ++i
) {
133 keychain_
->AddTestItem(test_data
[i
]);
137 virtual void TearDown() {
138 ExpectCreatesAndFreesBalanced();
139 ExpectCreatorCodesSet();
144 // Causes a test failure unless everything returned from keychain_'s
145 // ItemCopyAttributesAndData, SearchCreateFromAttributes, and SearchCopyNext
146 // was correctly freed.
147 void ExpectCreatesAndFreesBalanced() {
148 EXPECT_EQ(0, keychain_
->UnfreedSearchCount());
149 EXPECT_EQ(0, keychain_
->UnfreedKeychainItemCount());
150 EXPECT_EQ(0, keychain_
->UnfreedAttributeDataCount());
153 // Causes a test failure unless any Keychain items added during the test have
154 // their creator code set.
155 void ExpectCreatorCodesSet() {
156 EXPECT_TRUE(keychain_
->CreatorCodesSetForAddedItems());
159 MockAppleKeychain
* keychain_
;
164 // Struct used for creation of PasswordForms from static arrays of data.
165 struct PasswordFormData
{
166 const PasswordForm::Scheme scheme
;
167 const char* signon_realm
;
170 const wchar_t* submit_element
;
171 const wchar_t* username_element
;
172 const wchar_t* password_element
;
173 const wchar_t* username_value
; // Set to NULL for a blacklist entry.
174 const wchar_t* password_value
;
175 const bool preferred
;
176 const bool ssl_valid
;
177 const double creation_time
;
180 // Creates and returns a new PasswordForm built from form_data. Caller is
181 // responsible for deleting the object when finished with it.
182 static PasswordForm
* CreatePasswordFormFromData(
183 const PasswordFormData
& form_data
) {
184 PasswordForm
* form
= new PasswordForm();
185 form
->scheme
= form_data
.scheme
;
186 form
->preferred
= form_data
.preferred
;
187 form
->ssl_valid
= form_data
.ssl_valid
;
188 form
->date_created
= base::Time::FromDoubleT(form_data
.creation_time
);
189 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
);
206 form
->blacklisted_by_user
= true;
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())
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
)
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
)
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
;
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
;
264 TEST_F(PasswordStoreMacInternalsTest
, TestKeychainToFormTranslation
) {
266 const PasswordForm::Scheme scheme
;
267 const char* signon_realm
;
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
;
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
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);
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
;
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);
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 },
374 { { PasswordForm::SCHEME_HTML
, "http://some.domain.com/",
375 NULL
, NULL
, NULL
, NULL
, NULL
, L
"wrong_user", NULL
, false, false, 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 },
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,
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,
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,
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 },
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,
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
,
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
,
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
) {
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(
498 EXPECT_TRUE(keychain_adapter
.HasPasswordsMergeableWithForm(*base_form
));
499 PasswordForm
* match
=
500 keychain_adapter
.PasswordExactlyMatchingForm(*base_form
);
501 EXPECT_TRUE(match
!= NULL
);
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
);
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 "
540 STLDeleteElements(&modified_forms
);
544 TEST_F(PasswordStoreMacInternalsTest
, TestKeychainAdd
) {
545 struct TestDataAndExpectation
{
546 PasswordFormData data
;
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
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
);
584 EXPECT_TRUE(owned_keychain_adapter
.HasPasswordsMergeableWithForm(
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_
,
613 EXPECT_EQ(update_form
->password_value
, stored_form
.password_value
);
617 TEST_F(PasswordStoreMacInternalsTest
, TestKeychainRemove
) {
618 struct TestDataAndExpectation
{
619 PasswordFormData data
;
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
));
641 for (unsigned int i
= 0; i
< ARRAYSIZE_UNSAFE(test_data
); ++i
) {
642 scoped_ptr
<PasswordForm
> form(CreatePasswordFormFromData(
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
);
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();
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");
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
;
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/");
693 FormsMatchForMerge(base_form
, different_form
, STRICT_FORM_MATCH
));
696 PasswordForm
different_form(base_form
);
697 different_form
.username_value
= ASCIIToUTF16("john.doe");
699 FormsMatchForMerge(base_form
, different_form
, STRICT_FORM_MATCH
));
702 PasswordForm
different_form(base_form
);
703 different_form
.blacklisted_by_user
= true;
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
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
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.
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.
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
,
861 CHECK_FORMS(keychain_forms
, test_data
[KEYCHAIN_OUTPUT
][test_case
],
863 CHECK_FORMS(database_forms
, test_data
[DATABASE_OUTPUT
][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_
,
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_
,
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(
951 &form_without_extracted_password
,
952 false); // Do not extract password.
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(
965 &form_with_extracted_password
,
966 true); // Extract password.
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(
981 true); // Extract password.
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(
995 &form_with_empty_password_a
,
996 true); // Extract password.
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(
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
);
1053 class PasswordStoreMacTest
: public testing::Test
{
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(),
1070 ASSERT_TRUE(store_
->Init(syncer::SyncableService::StartSyncFlare()));
1073 virtual void TearDown() {
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();
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
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
);
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",
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",
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",
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
;
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());