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
;
30 using testing::Invoke
;
31 using testing::WithArg
;
35 class MockPasswordStoreConsumer
: public PasswordStoreConsumer
{
37 MOCK_METHOD1(OnGetPasswordStoreResults
,
38 void(const std::vector
<autofill::PasswordForm
*>&));
40 void CopyElements(const std::vector
<autofill::PasswordForm
*>& forms
) {
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();
63 class PasswordStoreMacInternalsTest
: public testing::Test
{
65 virtual void SetUp() {
66 MockAppleKeychain::KeychainTestData test_data
[] = {
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",
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",
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();
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_
;
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
;
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
);
182 form
->blacklisted_by_user
= true;
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())
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
)
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
)
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
;
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
;
240 TEST_F(PasswordStoreMacInternalsTest
, TestKeychainToFormTranslation
) {
242 const PasswordForm::Scheme scheme
;
243 const char* signon_realm
;
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
;
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
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);
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
;
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);
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 },
350 { { PasswordForm::SCHEME_HTML
, "http://some.domain.com/",
351 NULL
, NULL
, NULL
, NULL
, NULL
, L
"wrong_user", NULL
, false, false, 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 },
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,
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,
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,
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 },
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,
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
,
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
,
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
) {
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(
474 EXPECT_TRUE(keychain_adapter
.HasPasswordsMergeableWithForm(*base_form
));
475 PasswordForm
* match
=
476 keychain_adapter
.PasswordExactlyMatchingForm(*base_form
);
477 EXPECT_TRUE(match
!= NULL
);
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
);
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 "
516 STLDeleteElements(&modified_forms
);
520 TEST_F(PasswordStoreMacInternalsTest
, TestKeychainAdd
) {
521 struct TestDataAndExpectation
{
522 PasswordFormData data
;
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
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
);
560 EXPECT_TRUE(owned_keychain_adapter
.HasPasswordsMergeableWithForm(
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_
,
589 EXPECT_EQ(update_form
->password_value
, stored_form
.password_value
);
593 TEST_F(PasswordStoreMacInternalsTest
, TestKeychainRemove
) {
594 struct TestDataAndExpectation
{
595 PasswordFormData data
;
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
));
617 for (unsigned int i
= 0; i
< ARRAYSIZE_UNSAFE(test_data
); ++i
) {
618 scoped_ptr
<PasswordForm
> form(CreatePasswordFormFromData(
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
);
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();
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");
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
;
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/");
669 FormsMatchForMerge(base_form
, different_form
, STRICT_FORM_MATCH
));
672 PasswordForm
different_form(base_form
);
673 different_form
.username_value
= ASCIIToUTF16("john.doe");
675 FormsMatchForMerge(base_form
, different_form
, STRICT_FORM_MATCH
));
678 PasswordForm
different_form(base_form
);
679 different_form
.blacklisted_by_user
= true;
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
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
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.
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.
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
,
837 CHECK_FORMS(keychain_forms
, test_data
[KEYCHAIN_OUTPUT
][test_case
],
839 CHECK_FORMS(database_forms
, test_data
[DATABASE_OUTPUT
][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_
,
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_
,
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(
927 &form_without_extracted_password
,
928 false); // Do not extract password.
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(
941 &form_with_extracted_password
,
942 true); // Extract password.
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(
957 true); // Extract password.
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(
971 &form_with_empty_password_a
,
972 true); // Extract password.
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(
985 &form_with_empty_password_b
,
986 true); // Extract password.
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
);
1029 class PasswordStoreMacTest
: public testing::Test
{
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(),
1046 ASSERT_TRUE(store_
->Init());
1049 virtual void TearDown() {
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();
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
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
);
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",
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",
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",
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
;
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());