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 "chrome/browser/password_manager/password_store_mac.h"
7 #include "base/basictypes.h"
8 #include "base/files/scoped_temp_dir.h"
9 #include "base/memory/scoped_vector.h"
10 #include "base/path_service.h"
11 #include "base/scoped_observer.h"
12 #include "base/stl_util.h"
13 #include "base/strings/string_util.h"
14 #include "base/strings/utf_string_conversions.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"
20 #include "testing/gmock/include/gmock/gmock.h"
21 #include "testing/gtest/include/gtest/gtest.h"
23 using autofill::PasswordForm
;
24 using base::ASCIIToUTF16
;
25 using base::WideToUTF16
;
26 using content::BrowserThread
;
27 using crypto::MockAppleKeychain
;
28 using internal_keychain_helpers::FormsMatchForMerge
;
29 using internal_keychain_helpers::STRICT_FORM_MATCH
;
30 using password_manager::LoginDatabase
;
31 using password_manager::PasswordStore
;
32 using password_manager::PasswordStoreConsumer
;
35 using testing::Invoke
;
36 using testing::WithArg
;
40 class MockPasswordStoreConsumer
: public PasswordStoreConsumer
{
42 MOCK_METHOD1(OnGetPasswordStoreResults
,
43 void(const std::vector
<autofill::PasswordForm
*>&));
45 void CopyElements(const std::vector
<autofill::PasswordForm
*>& forms
) {
47 for (size_t i
= 0; i
< forms
.size(); ++i
) {
48 last_result
.push_back(*forms
[i
]);
52 std::vector
<PasswordForm
> last_result
;
55 ACTION(STLDeleteElements0
) {
56 STLDeleteContainerPointers(arg0
.begin(), arg0
.end());
59 ACTION(QuitUIMessageLoop
) {
60 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
61 base::MessageLoop::current()->Quit();
64 class TestPasswordStoreMac
: public PasswordStoreMac
{
67 scoped_refptr
<base::SingleThreadTaskRunner
> main_thread_runner
,
68 scoped_refptr
<base::SingleThreadTaskRunner
> db_thread_runner
,
69 crypto::AppleKeychain
* keychain
,
70 LoginDatabase
* login_db
)
71 : PasswordStoreMac(main_thread_runner
,
77 using PasswordStoreMac::GetBackgroundTaskRunner
;
80 virtual ~TestPasswordStoreMac() {}
82 DISALLOW_COPY_AND_ASSIGN(TestPasswordStoreMac
);
89 class PasswordStoreMacInternalsTest
: public testing::Test
{
91 virtual void SetUp() {
92 MockAppleKeychain::KeychainTestData test_data
[] = {
94 { kSecAuthenticationTypeHTMLForm
, "some.domain.com",
95 kSecProtocolTypeHTTP
, NULL
, 0, NULL
, "20020601171500Z",
96 "joe_user", "sekrit", false },
97 // HTML form with path.
98 { kSecAuthenticationTypeHTMLForm
, "some.domain.com",
99 kSecProtocolTypeHTTP
, "/insecure.html", 0, NULL
, "19991231235959Z",
100 "joe_user", "sekrit", false },
101 // Secure HTML form with path.
102 { kSecAuthenticationTypeHTMLForm
, "some.domain.com",
103 kSecProtocolTypeHTTPS
, "/secure.html", 0, NULL
, "20100908070605Z",
104 "secure_user", "password", false },
105 // True negative item.
106 { kSecAuthenticationTypeHTMLForm
, "dont.remember.com",
107 kSecProtocolTypeHTTP
, NULL
, 0, NULL
, "20000101000000Z",
109 // De-facto negative item, type one.
110 { kSecAuthenticationTypeHTMLForm
, "dont.remember.com",
111 kSecProtocolTypeHTTP
, NULL
, 0, NULL
, "20000101000000Z",
112 "Password Not Stored", "", false },
113 // De-facto negative item, type two.
114 { kSecAuthenticationTypeHTMLForm
, "dont.remember.com",
115 kSecProtocolTypeHTTPS
, NULL
, 0, NULL
, "20000101000000Z",
116 "Password Not Stored", " ", false },
117 // HTTP auth basic, with port and path.
118 { kSecAuthenticationTypeHTTPBasic
, "some.domain.com",
119 kSecProtocolTypeHTTP
, "/insecure.html", 4567, "low_security",
121 "basic_auth_user", "basic", false },
122 // HTTP auth digest, secure.
123 { kSecAuthenticationTypeHTTPDigest
, "some.domain.com",
124 kSecProtocolTypeHTTPS
, NULL
, 0, "high_security", "19980330100000Z",
125 "digest_auth_user", "digest", false },
126 // An FTP password with an invalid date, for edge-case testing.
127 { kSecAuthenticationTypeDefault
, "a.server.com",
128 kSecProtocolTypeFTP
, NULL
, 0, NULL
, "20010203040",
129 "abc", "123", false },
132 keychain_
= new MockAppleKeychain();
134 for (unsigned int i
= 0; i
< arraysize(test_data
); ++i
) {
135 keychain_
->AddTestItem(test_data
[i
]);
139 virtual void TearDown() {
140 ExpectCreatesAndFreesBalanced();
141 ExpectCreatorCodesSet();
146 // Causes a test failure unless everything returned from keychain_'s
147 // ItemCopyAttributesAndData, SearchCreateFromAttributes, and SearchCopyNext
148 // was correctly freed.
149 void ExpectCreatesAndFreesBalanced() {
150 EXPECT_EQ(0, keychain_
->UnfreedSearchCount());
151 EXPECT_EQ(0, keychain_
->UnfreedKeychainItemCount());
152 EXPECT_EQ(0, keychain_
->UnfreedAttributeDataCount());
155 // Causes a test failure unless any Keychain items added during the test have
156 // their creator code set.
157 void ExpectCreatorCodesSet() {
158 EXPECT_TRUE(keychain_
->CreatorCodesSetForAddedItems());
161 MockAppleKeychain
* keychain_
;
166 // Struct used for creation of PasswordForms from static arrays of data.
167 struct PasswordFormData
{
168 const PasswordForm::Scheme scheme
;
169 const char* signon_realm
;
172 const wchar_t* submit_element
;
173 const wchar_t* username_element
;
174 const wchar_t* password_element
;
175 const wchar_t* username_value
; // Set to NULL for a blacklist entry.
176 const wchar_t* password_value
;
177 const bool preferred
;
178 const bool ssl_valid
;
179 const double creation_time
;
182 // Creates and returns a new PasswordForm built from form_data. Caller is
183 // responsible for deleting the object when finished with it.
184 static PasswordForm
* CreatePasswordFormFromData(
185 const PasswordFormData
& form_data
) {
186 PasswordForm
* form
= new PasswordForm();
187 form
->scheme
= form_data
.scheme
;
188 form
->preferred
= form_data
.preferred
;
189 form
->ssl_valid
= form_data
.ssl_valid
;
190 form
->date_created
= base::Time::FromDoubleT(form_data
.creation_time
);
191 form
->date_synced
= form
->date_created
+ base::TimeDelta::FromDays(1);
192 if (form_data
.signon_realm
)
193 form
->signon_realm
= std::string(form_data
.signon_realm
);
194 if (form_data
.origin
)
195 form
->origin
= GURL(form_data
.origin
);
196 if (form_data
.action
)
197 form
->action
= GURL(form_data
.action
);
198 if (form_data
.submit_element
)
199 form
->submit_element
= WideToUTF16(form_data
.submit_element
);
200 if (form_data
.username_element
)
201 form
->username_element
= WideToUTF16(form_data
.username_element
);
202 if (form_data
.password_element
)
203 form
->password_element
= WideToUTF16(form_data
.password_element
);
204 if (form_data
.username_value
) {
205 form
->username_value
= WideToUTF16(form_data
.username_value
);
206 form
->display_name
= form
->username_value
;
207 form
->is_zero_click
= true;
208 if (form_data
.password_value
)
209 form
->password_value
= WideToUTF16(form_data
.password_value
);
211 form
->blacklisted_by_user
= true;
213 form
->avatar_url
= GURL("https://accounts.google.com/Avatar");
214 form
->federation_url
= GURL("https://accounts.google.com/login");
218 // Macro to simplify calling CheckFormsAgainstExpectations with a useful label.
219 #define CHECK_FORMS(forms, expectations, i) \
220 CheckFormsAgainstExpectations(forms, expectations, #forms, i)
222 // Ensures that the data in |forms| match |expectations|, causing test failures
223 // for any discrepencies.
224 // TODO(stuartmorgan): This is current order-dependent; ideally it shouldn't
225 // matter if |forms| and |expectations| are scrambled.
226 static void CheckFormsAgainstExpectations(
227 const std::vector
<PasswordForm
*>& forms
,
228 const std::vector
<PasswordFormData
*>& expectations
,
229 const char* forms_label
, unsigned int test_number
) {
230 const unsigned int kBufferSize
= 128;
231 char test_label
[kBufferSize
];
232 snprintf(test_label
, kBufferSize
, "%s in test %u", forms_label
, test_number
);
234 EXPECT_EQ(expectations
.size(), forms
.size()) << test_label
;
235 if (expectations
.size() != forms
.size())
238 for (unsigned int i
= 0; i
< expectations
.size(); ++i
) {
239 snprintf(test_label
, kBufferSize
, "%s in test %u, item %u",
240 forms_label
, test_number
, i
);
241 PasswordForm
* form
= forms
[i
];
242 PasswordFormData
* expectation
= expectations
[i
];
243 EXPECT_EQ(expectation
->scheme
, form
->scheme
) << test_label
;
244 EXPECT_EQ(std::string(expectation
->signon_realm
), form
->signon_realm
)
246 EXPECT_EQ(GURL(expectation
->origin
), form
->origin
) << test_label
;
247 EXPECT_EQ(GURL(expectation
->action
), form
->action
) << test_label
;
248 EXPECT_EQ(WideToUTF16(expectation
->submit_element
), form
->submit_element
)
250 EXPECT_EQ(WideToUTF16(expectation
->username_element
),
251 form
->username_element
) << test_label
;
252 EXPECT_EQ(WideToUTF16(expectation
->password_element
),
253 form
->password_element
) << test_label
;
254 if (expectation
->username_value
) {
255 EXPECT_EQ(WideToUTF16(expectation
->username_value
),
256 form
->username_value
) << test_label
;
257 EXPECT_EQ(WideToUTF16(expectation
->username_value
),
258 form
->display_name
) << test_label
;
259 EXPECT_TRUE(form
->is_zero_click
) << test_label
;
260 EXPECT_EQ(WideToUTF16(expectation
->password_value
),
261 form
->password_value
) << test_label
;
263 EXPECT_TRUE(form
->blacklisted_by_user
) << test_label
;
265 EXPECT_EQ(expectation
->preferred
, form
->preferred
) << test_label
;
266 EXPECT_EQ(expectation
->ssl_valid
, form
->ssl_valid
) << test_label
;
267 EXPECT_DOUBLE_EQ(expectation
->creation_time
,
268 form
->date_created
.ToDoubleT()) << test_label
;
269 base::Time created
= base::Time::FromDoubleT(expectation
->creation_time
);
270 EXPECT_EQ(created
+ base::TimeDelta::FromDays(1),
271 form
->date_synced
) << test_label
;
272 EXPECT_EQ(GURL("https://accounts.google.com/Avatar"), form
->avatar_url
);
273 EXPECT_EQ(GURL("https://accounts.google.com/login"), form
->federation_url
);
279 TEST_F(PasswordStoreMacInternalsTest
, TestKeychainToFormTranslation
) {
281 const PasswordForm::Scheme scheme
;
282 const char* signon_realm
;
284 const wchar_t* username
; // Set to NULL to check for a blacklist entry.
285 const wchar_t* password
;
286 const bool ssl_valid
;
287 const int creation_year
;
288 const int creation_month
;
289 const int creation_day
;
290 const int creation_hour
;
291 const int creation_minute
;
292 const int creation_second
;
295 TestExpectations expected
[] = {
296 { PasswordForm::SCHEME_HTML
, "http://some.domain.com/",
297 "http://some.domain.com/", L
"joe_user", L
"sekrit", false,
298 2002, 6, 1, 17, 15, 0 },
299 { PasswordForm::SCHEME_HTML
, "http://some.domain.com/",
300 "http://some.domain.com/insecure.html", L
"joe_user", L
"sekrit", false,
301 1999, 12, 31, 23, 59, 59 },
302 { PasswordForm::SCHEME_HTML
, "https://some.domain.com/",
303 "https://some.domain.com/secure.html", L
"secure_user", L
"password", true,
304 2010, 9, 8, 7, 6, 5 },
305 { PasswordForm::SCHEME_HTML
, "http://dont.remember.com/",
306 "http://dont.remember.com/", NULL
, NULL
, false,
307 2000, 1, 1, 0, 0, 0 },
308 { PasswordForm::SCHEME_HTML
, "http://dont.remember.com/",
309 "http://dont.remember.com/", NULL
, NULL
, false,
310 2000, 1, 1, 0, 0, 0 },
311 { PasswordForm::SCHEME_HTML
, "https://dont.remember.com/",
312 "https://dont.remember.com/", NULL
, NULL
, true,
313 2000, 1, 1, 0, 0, 0 },
314 { PasswordForm::SCHEME_BASIC
, "http://some.domain.com:4567/low_security",
315 "http://some.domain.com:4567/insecure.html", L
"basic_auth_user", L
"basic",
316 false, 1998, 03, 30, 10, 00, 00 },
317 { PasswordForm::SCHEME_DIGEST
, "https://some.domain.com/high_security",
318 "https://some.domain.com/", L
"digest_auth_user", L
"digest", true,
319 1998, 3, 30, 10, 0, 0 },
320 // This one gives us an invalid date, which we will treat as a "NULL" date
322 { PasswordForm::SCHEME_OTHER
, "http://a.server.com/",
323 "http://a.server.com/", L
"abc", L
"123", false,
324 1601, 1, 1, 0, 0, 0 },
327 for (unsigned int i
= 0; i
< arraysize(expected
); ++i
) {
328 // Create our fake KeychainItemRef; see MockAppleKeychain docs.
329 SecKeychainItemRef keychain_item
=
330 reinterpret_cast<SecKeychainItemRef
>(i
+ 1);
332 bool parsed
= internal_keychain_helpers::FillPasswordFormFromKeychainItem(
333 *keychain_
, keychain_item
, &form
, true);
335 EXPECT_TRUE(parsed
) << "In iteration " << i
;
337 EXPECT_EQ(expected
[i
].scheme
, form
.scheme
) << "In iteration " << i
;
338 EXPECT_EQ(GURL(expected
[i
].origin
), form
.origin
) << "In iteration " << i
;
339 EXPECT_EQ(expected
[i
].ssl_valid
, form
.ssl_valid
) << "In iteration " << i
;
340 EXPECT_EQ(std::string(expected
[i
].signon_realm
), form
.signon_realm
)
341 << "In iteration " << i
;
342 if (expected
[i
].username
) {
343 EXPECT_EQ(WideToUTF16(expected
[i
].username
), form
.username_value
)
344 << "In iteration " << i
;
345 EXPECT_EQ(WideToUTF16(expected
[i
].password
), form
.password_value
)
346 << "In iteration " << i
;
347 EXPECT_FALSE(form
.blacklisted_by_user
) << "In iteration " << i
;
349 EXPECT_TRUE(form
.blacklisted_by_user
) << "In iteration " << i
;
351 base::Time::Exploded exploded_time
;
352 form
.date_created
.UTCExplode(&exploded_time
);
353 EXPECT_EQ(expected
[i
].creation_year
, exploded_time
.year
)
354 << "In iteration " << i
;
355 EXPECT_EQ(expected
[i
].creation_month
, exploded_time
.month
)
356 << "In iteration " << i
;
357 EXPECT_EQ(expected
[i
].creation_day
, exploded_time
.day_of_month
)
358 << "In iteration " << i
;
359 EXPECT_EQ(expected
[i
].creation_hour
, exploded_time
.hour
)
360 << "In iteration " << i
;
361 EXPECT_EQ(expected
[i
].creation_minute
, exploded_time
.minute
)
362 << "In iteration " << i
;
363 EXPECT_EQ(expected
[i
].creation_second
, exploded_time
.second
)
364 << "In iteration " << i
;
368 // Use an invalid ref, to make sure errors are reported.
369 SecKeychainItemRef keychain_item
= reinterpret_cast<SecKeychainItemRef
>(99);
371 bool parsed
= internal_keychain_helpers::FillPasswordFormFromKeychainItem(
372 *keychain_
, keychain_item
, &form
, true);
373 EXPECT_FALSE(parsed
);
377 TEST_F(PasswordStoreMacInternalsTest
, TestKeychainSearch
) {
378 struct TestDataAndExpectation
{
379 const PasswordFormData data
;
380 const size_t expected_fill_matches
;
381 const size_t expected_merge_matches
;
383 // Most fields are left blank because we don't care about them for searching.
384 TestDataAndExpectation test_data
[] = {
385 // An HTML form we've seen.
386 { { PasswordForm::SCHEME_HTML
, "http://some.domain.com/",
387 NULL
, NULL
, NULL
, NULL
, NULL
, L
"joe_user", NULL
, false, false, 0 },
389 { { PasswordForm::SCHEME_HTML
, "http://some.domain.com/",
390 NULL
, NULL
, NULL
, NULL
, NULL
, L
"wrong_user", NULL
, false, false, 0 },
392 // An HTML form we haven't seen
393 { { PasswordForm::SCHEME_HTML
, "http://www.unseendomain.com/",
394 NULL
, NULL
, NULL
, NULL
, NULL
, L
"joe_user", NULL
, false, false, 0 },
396 // Basic auth that should match.
397 { { PasswordForm::SCHEME_BASIC
, "http://some.domain.com:4567/low_security",
398 NULL
, NULL
, NULL
, NULL
, NULL
, L
"basic_auth_user", NULL
, false, false,
401 // Basic auth with the wrong port.
402 { { PasswordForm::SCHEME_BASIC
, "http://some.domain.com:1111/low_security",
403 NULL
, NULL
, NULL
, NULL
, NULL
, L
"basic_auth_user", NULL
, false, false,
406 // Digest auth we've saved under https, visited with http.
407 { { PasswordForm::SCHEME_DIGEST
, "http://some.domain.com/high_security",
408 NULL
, NULL
, NULL
, NULL
, NULL
, L
"digest_auth_user", NULL
, false, false,
411 // Digest auth that should match.
412 { { PasswordForm::SCHEME_DIGEST
, "https://some.domain.com/high_security",
413 NULL
, NULL
, NULL
, NULL
, NULL
, L
"wrong_user", NULL
, false, true, 0 },
415 // Digest auth with the wrong domain.
416 { { PasswordForm::SCHEME_DIGEST
, "https://some.domain.com/other_domain",
417 NULL
, NULL
, NULL
, NULL
, NULL
, L
"digest_auth_user", NULL
, false, true,
420 // Garbage forms should have no matches.
421 { { PasswordForm::SCHEME_HTML
, "foo/bar/baz",
422 NULL
, NULL
, NULL
, NULL
, NULL
, NULL
, NULL
, false, false, 0 }, 0, 0 },
425 MacKeychainPasswordFormAdapter
keychain_adapter(keychain_
);
426 MacKeychainPasswordFormAdapter
owned_keychain_adapter(keychain_
);
427 owned_keychain_adapter
.SetFindsOnlyOwnedItems(true);
428 for (unsigned int i
= 0; i
< arraysize(test_data
); ++i
) {
429 scoped_ptr
<PasswordForm
> query_form(
430 CreatePasswordFormFromData(test_data
[i
].data
));
432 // Check matches treating the form as a fill target.
433 std::vector
<PasswordForm
*> matching_items
=
434 keychain_adapter
.PasswordsFillingForm(query_form
->signon_realm
,
436 EXPECT_EQ(test_data
[i
].expected_fill_matches
, matching_items
.size());
437 STLDeleteElements(&matching_items
);
439 // Check matches treating the form as a merging target.
440 EXPECT_EQ(test_data
[i
].expected_merge_matches
> 0,
441 keychain_adapter
.HasPasswordsMergeableWithForm(*query_form
));
442 std::vector
<SecKeychainItemRef
> keychain_items
;
443 std::vector
<internal_keychain_helpers::ItemFormPair
> item_form_pairs
=
444 internal_keychain_helpers::
445 ExtractAllKeychainItemAttributesIntoPasswordForms(&keychain_items
,
448 internal_keychain_helpers::ExtractPasswordsMergeableWithForm(
449 *keychain_
, item_form_pairs
, *query_form
);
450 EXPECT_EQ(test_data
[i
].expected_merge_matches
, matching_items
.size());
451 STLDeleteContainerPairSecondPointers(item_form_pairs
.begin(),
452 item_form_pairs
.end());
453 for (std::vector
<SecKeychainItemRef
>::iterator i
= keychain_items
.begin();
454 i
!= keychain_items
.end(); ++i
) {
457 STLDeleteElements(&matching_items
);
459 // None of the pre-seeded items are owned by us, so none should match an
460 // owned-passwords-only search.
461 matching_items
= owned_keychain_adapter
.PasswordsFillingForm(
462 query_form
->signon_realm
, query_form
->scheme
);
463 EXPECT_EQ(0U, matching_items
.size());
464 STLDeleteElements(&matching_items
);
468 // Changes just the origin path of |form|.
469 static void SetPasswordFormPath(PasswordForm
* form
, const char* path
) {
470 GURL::Replacements replacement
;
471 std::string
new_value(path
);
472 replacement
.SetPathStr(new_value
);
473 form
->origin
= form
->origin
.ReplaceComponents(replacement
);
476 // Changes just the signon_realm port of |form|.
477 static void SetPasswordFormPort(PasswordForm
* form
, const char* port
) {
478 GURL::Replacements replacement
;
479 std::string
new_value(port
);
480 replacement
.SetPortStr(new_value
);
481 GURL signon_gurl
= GURL(form
->signon_realm
);
482 form
->signon_realm
= signon_gurl
.ReplaceComponents(replacement
).spec();
485 // Changes just the signon_ream auth realm of |form|.
486 static void SetPasswordFormRealm(PasswordForm
* form
, const char* realm
) {
487 GURL::Replacements replacement
;
488 std::string
new_value(realm
);
489 replacement
.SetPathStr(new_value
);
490 GURL signon_gurl
= GURL(form
->signon_realm
);
491 form
->signon_realm
= signon_gurl
.ReplaceComponents(replacement
).spec();
494 TEST_F(PasswordStoreMacInternalsTest
, TestKeychainExactSearch
) {
495 MacKeychainPasswordFormAdapter
keychain_adapter(keychain_
);
497 PasswordFormData base_form_data
[] = {
498 { PasswordForm::SCHEME_HTML
, "http://some.domain.com/",
499 "http://some.domain.com/insecure.html",
500 NULL
, NULL
, NULL
, NULL
, L
"joe_user", NULL
, true, false, 0 },
501 { PasswordForm::SCHEME_BASIC
, "http://some.domain.com:4567/low_security",
502 "http://some.domain.com:4567/insecure.html",
503 NULL
, NULL
, NULL
, NULL
, L
"basic_auth_user", NULL
, true, false, 0 },
504 { PasswordForm::SCHEME_DIGEST
, "https://some.domain.com/high_security",
505 "https://some.domain.com",
506 NULL
, NULL
, NULL
, NULL
, L
"digest_auth_user", NULL
, true, true, 0 },
509 for (unsigned int i
= 0; i
< arraysize(base_form_data
); ++i
) {
510 // Create a base form and make sure we find a match.
511 scoped_ptr
<PasswordForm
> base_form(CreatePasswordFormFromData(
513 EXPECT_TRUE(keychain_adapter
.HasPasswordsMergeableWithForm(*base_form
));
514 EXPECT_TRUE(keychain_adapter
.HasPasswordExactlyMatchingForm(*base_form
));
516 // Make sure that the matching isn't looser than it should be by checking
517 // that slightly altered forms don't match.
518 std::vector
<PasswordForm
*> modified_forms
;
520 modified_forms
.push_back(new PasswordForm(*base_form
));
521 modified_forms
.back()->username_value
= ASCIIToUTF16("wrong_user");
523 modified_forms
.push_back(new PasswordForm(*base_form
));
524 SetPasswordFormPath(modified_forms
.back(), "elsewhere.html");
526 modified_forms
.push_back(new PasswordForm(*base_form
));
527 modified_forms
.back()->scheme
= PasswordForm::SCHEME_OTHER
;
529 modified_forms
.push_back(new PasswordForm(*base_form
));
530 SetPasswordFormPort(modified_forms
.back(), "1234");
532 modified_forms
.push_back(new PasswordForm(*base_form
));
533 modified_forms
.back()->blacklisted_by_user
= true;
535 if (base_form
->scheme
== PasswordForm::SCHEME_BASIC
||
536 base_form
->scheme
== PasswordForm::SCHEME_DIGEST
) {
537 modified_forms
.push_back(new PasswordForm(*base_form
));
538 SetPasswordFormRealm(modified_forms
.back(), "incorrect");
541 for (unsigned int j
= 0; j
< modified_forms
.size(); ++j
) {
542 bool match
= keychain_adapter
.HasPasswordExactlyMatchingForm(
544 EXPECT_FALSE(match
) << "In modified version " << j
545 << " of base form " << i
;
547 STLDeleteElements(&modified_forms
);
551 TEST_F(PasswordStoreMacInternalsTest
, TestKeychainAdd
) {
552 struct TestDataAndExpectation
{
553 PasswordFormData data
;
556 TestDataAndExpectation test_data
[] = {
557 // Test a variety of scheme/port/protocol/path variations.
558 { { PasswordForm::SCHEME_HTML
, "http://web.site.com/",
559 "http://web.site.com/path/to/page.html", NULL
, NULL
, NULL
, NULL
,
560 L
"anonymous", L
"knock-knock", false, false, 0 }, true },
561 { { PasswordForm::SCHEME_HTML
, "https://web.site.com/",
562 "https://web.site.com/", NULL
, NULL
, NULL
, NULL
,
563 L
"admin", L
"p4ssw0rd", false, false, 0 }, true },
564 { { PasswordForm::SCHEME_BASIC
, "http://a.site.com:2222/therealm",
565 "http://a.site.com:2222/", NULL
, NULL
, NULL
, NULL
,
566 L
"username", L
"password", false, false, 0 }, true },
567 { { PasswordForm::SCHEME_DIGEST
, "https://digest.site.com/differentrealm",
568 "https://digest.site.com/secure.html", NULL
, NULL
, NULL
, NULL
,
569 L
"testname", L
"testpass", false, false, 0 }, true },
570 // Make sure that garbage forms are rejected.
571 { { PasswordForm::SCHEME_HTML
, "gobbledygook",
572 "gobbledygook", NULL
, NULL
, NULL
, NULL
,
573 L
"anonymous", L
"knock-knock", false, false, 0 }, false },
574 // Test that failing to update a duplicate (forced using the magic failure
575 // password; see MockAppleKeychain::ItemModifyAttributesAndData) is
577 { { PasswordForm::SCHEME_HTML
, "http://some.domain.com",
578 "http://some.domain.com/insecure.html", NULL
, NULL
, NULL
, NULL
,
579 L
"joe_user", L
"fail_me", false, false, 0 }, false },
582 MacKeychainPasswordFormAdapter
owned_keychain_adapter(keychain_
);
583 owned_keychain_adapter
.SetFindsOnlyOwnedItems(true);
585 for (unsigned int i
= 0; i
< arraysize(test_data
); ++i
) {
586 scoped_ptr
<PasswordForm
> in_form(
587 CreatePasswordFormFromData(test_data
[i
].data
));
588 bool add_succeeded
= owned_keychain_adapter
.AddPassword(*in_form
);
589 EXPECT_EQ(test_data
[i
].should_succeed
, add_succeeded
);
591 EXPECT_TRUE(owned_keychain_adapter
.HasPasswordsMergeableWithForm(
593 EXPECT_TRUE(owned_keychain_adapter
.HasPasswordExactlyMatchingForm(
598 // Test that adding duplicate item updates the existing item.
600 PasswordFormData data
= {
601 PasswordForm::SCHEME_HTML
, "http://some.domain.com",
602 "http://some.domain.com/insecure.html", NULL
,
603 NULL
, NULL
, NULL
, L
"joe_user", L
"updated_password", false, false, 0
605 scoped_ptr
<PasswordForm
> update_form(CreatePasswordFormFromData(data
));
606 MacKeychainPasswordFormAdapter
keychain_adapter(keychain_
);
607 EXPECT_TRUE(keychain_adapter
.AddPassword(*update_form
));
608 SecKeychainItemRef keychain_item
= reinterpret_cast<SecKeychainItemRef
>(2);
609 PasswordForm stored_form
;
610 internal_keychain_helpers::FillPasswordFormFromKeychainItem(*keychain_
,
614 EXPECT_EQ(update_form
->password_value
, stored_form
.password_value
);
618 TEST_F(PasswordStoreMacInternalsTest
, TestKeychainRemove
) {
619 struct TestDataAndExpectation
{
620 PasswordFormData data
;
623 TestDataAndExpectation test_data
[] = {
624 // Test deletion of an item that we add.
625 { { PasswordForm::SCHEME_HTML
, "http://web.site.com/",
626 "http://web.site.com/path/to/page.html", NULL
, NULL
, NULL
, NULL
,
627 L
"anonymous", L
"knock-knock", false, false, 0 }, true },
628 // Make sure we don't delete items we don't own.
629 { { PasswordForm::SCHEME_HTML
, "http://some.domain.com/",
630 "http://some.domain.com/insecure.html", NULL
, NULL
, NULL
, NULL
,
631 L
"joe_user", NULL
, true, false, 0 }, false },
634 MacKeychainPasswordFormAdapter
owned_keychain_adapter(keychain_
);
635 owned_keychain_adapter
.SetFindsOnlyOwnedItems(true);
637 // Add our test item so that we can delete it.
638 PasswordForm
* add_form
= CreatePasswordFormFromData(test_data
[0].data
);
639 EXPECT_TRUE(owned_keychain_adapter
.AddPassword(*add_form
));
642 for (unsigned int i
= 0; i
< arraysize(test_data
); ++i
) {
643 scoped_ptr
<PasswordForm
> form(CreatePasswordFormFromData(
645 EXPECT_EQ(test_data
[i
].should_succeed
,
646 owned_keychain_adapter
.RemovePassword(*form
));
648 MacKeychainPasswordFormAdapter
keychain_adapter(keychain_
);
649 bool match
= keychain_adapter
.HasPasswordExactlyMatchingForm(*form
);
650 EXPECT_EQ(test_data
[i
].should_succeed
, !match
);
654 TEST_F(PasswordStoreMacInternalsTest
, TestFormMatch
) {
655 PasswordForm base_form
;
656 base_form
.signon_realm
= std::string("http://some.domain.com/");
657 base_form
.origin
= GURL("http://some.domain.com/page.html");
658 base_form
.username_value
= ASCIIToUTF16("joe_user");
661 // Check that everything unimportant can be changed.
662 PasswordForm
different_form(base_form
);
663 different_form
.username_element
= ASCIIToUTF16("username");
664 different_form
.submit_element
= ASCIIToUTF16("submit");
665 different_form
.username_element
= ASCIIToUTF16("password");
666 different_form
.password_value
= ASCIIToUTF16("sekrit");
667 different_form
.action
= GURL("http://some.domain.com/action.cgi");
668 different_form
.ssl_valid
= true;
669 different_form
.preferred
= true;
670 different_form
.date_created
= base::Time::Now();
672 FormsMatchForMerge(base_form
, different_form
, STRICT_FORM_MATCH
));
674 // Check that path differences don't prevent a match.
675 base_form
.origin
= GURL("http://some.domain.com/other_page.html");
677 FormsMatchForMerge(base_form
, different_form
, STRICT_FORM_MATCH
));
680 // Check that any one primary key changing is enough to prevent matching.
682 PasswordForm
different_form(base_form
);
683 different_form
.scheme
= PasswordForm::SCHEME_DIGEST
;
685 FormsMatchForMerge(base_form
, different_form
, STRICT_FORM_MATCH
));
688 PasswordForm
different_form(base_form
);
689 different_form
.signon_realm
= std::string("http://some.domain.com:8080/");
691 FormsMatchForMerge(base_form
, different_form
, STRICT_FORM_MATCH
));
694 PasswordForm
different_form(base_form
);
695 different_form
.username_value
= ASCIIToUTF16("john.doe");
697 FormsMatchForMerge(base_form
, different_form
, STRICT_FORM_MATCH
));
700 PasswordForm
different_form(base_form
);
701 different_form
.blacklisted_by_user
= true;
703 FormsMatchForMerge(base_form
, different_form
, STRICT_FORM_MATCH
));
706 // Blacklist forms should *never* match for merging, even when identical
707 // (and certainly not when only one is a blacklist entry).
709 PasswordForm
form_a(base_form
);
710 form_a
.blacklisted_by_user
= true;
711 PasswordForm
form_b(form_a
);
712 EXPECT_FALSE(FormsMatchForMerge(form_a
, form_b
, STRICT_FORM_MATCH
));
716 TEST_F(PasswordStoreMacInternalsTest
, TestFormMerge
) {
717 // Set up a bunch of test data to use in varying combinations.
718 PasswordFormData keychain_user_1
=
719 { PasswordForm::SCHEME_HTML
, "http://some.domain.com/",
720 "http://some.domain.com/", "", L
"", L
"", L
"", L
"joe_user", L
"sekrit",
721 false, false, 1010101010 };
722 PasswordFormData keychain_user_1_with_path
=
723 { PasswordForm::SCHEME_HTML
, "http://some.domain.com/",
724 "http://some.domain.com/page.html",
725 "", L
"", L
"", L
"", L
"joe_user", L
"otherpassword",
726 false, false, 1010101010 };
727 PasswordFormData keychain_user_2
=
728 { PasswordForm::SCHEME_HTML
, "http://some.domain.com/",
729 "http://some.domain.com/", "", L
"", L
"", L
"", L
"john.doe", L
"sesame",
730 false, false, 958739876 };
731 PasswordFormData keychain_blacklist
=
732 { PasswordForm::SCHEME_HTML
, "http://some.domain.com/",
733 "http://some.domain.com/", "", L
"", L
"", L
"", NULL
, NULL
,
734 false, false, 1010101010 };
736 PasswordFormData db_user_1
=
737 { PasswordForm::SCHEME_HTML
, "http://some.domain.com/",
738 "http://some.domain.com/", "http://some.domain.com/action.cgi",
739 L
"submit", L
"username", L
"password", L
"joe_user", L
"",
740 true, false, 1212121212 };
741 PasswordFormData db_user_1_with_path
=
742 { PasswordForm::SCHEME_HTML
, "http://some.domain.com/",
743 "http://some.domain.com/page.html",
744 "http://some.domain.com/handlepage.cgi",
745 L
"submit", L
"username", L
"password", L
"joe_user", L
"",
746 true, false, 1234567890 };
747 PasswordFormData db_user_3_with_path
=
748 { PasswordForm::SCHEME_HTML
, "http://some.domain.com/",
749 "http://some.domain.com/page.html",
750 "http://some.domain.com/handlepage.cgi",
751 L
"submit", L
"username", L
"password", L
"second-account", L
"",
752 true, false, 1240000000 };
753 PasswordFormData database_blacklist_with_path
=
754 { PasswordForm::SCHEME_HTML
, "http://some.domain.com/",
755 "http://some.domain.com/path.html", "http://some.domain.com/action.cgi",
756 L
"submit", L
"username", L
"password", NULL
, NULL
,
757 true, false, 1212121212 };
759 PasswordFormData merged_user_1
=
760 { PasswordForm::SCHEME_HTML
, "http://some.domain.com/",
761 "http://some.domain.com/", "http://some.domain.com/action.cgi",
762 L
"submit", L
"username", L
"password", L
"joe_user", L
"sekrit",
763 true, false, 1212121212 };
764 PasswordFormData merged_user_1_with_db_path
=
765 { PasswordForm::SCHEME_HTML
, "http://some.domain.com/",
766 "http://some.domain.com/page.html",
767 "http://some.domain.com/handlepage.cgi",
768 L
"submit", L
"username", L
"password", L
"joe_user", L
"sekrit",
769 true, false, 1234567890 };
770 PasswordFormData merged_user_1_with_both_paths
=
771 { PasswordForm::SCHEME_HTML
, "http://some.domain.com/",
772 "http://some.domain.com/page.html",
773 "http://some.domain.com/handlepage.cgi",
774 L
"submit", L
"username", L
"password", L
"joe_user", L
"otherpassword",
775 true, false, 1234567890 };
777 // Build up the big multi-dimensional array of data sets that will actually
778 // drive the test. Use vectors rather than arrays so that initialization is
786 MERGE_IO_ARRAY_COUNT
// termination marker
788 const unsigned int kTestCount
= 4;
789 std::vector
< std::vector
< std::vector
<PasswordFormData
*> > > test_data(
790 MERGE_IO_ARRAY_COUNT
, std::vector
< std::vector
<PasswordFormData
*> >(
791 kTestCount
, std::vector
<PasswordFormData
*>()));
792 unsigned int current_test
= 0;
794 // Test a merge with a few accounts in both systems, with partial overlap.
795 CHECK(current_test
< kTestCount
);
796 test_data
[KEYCHAIN_INPUT
][current_test
].push_back(&keychain_user_1
);
797 test_data
[KEYCHAIN_INPUT
][current_test
].push_back(&keychain_user_2
);
798 test_data
[DATABASE_INPUT
][current_test
].push_back(&db_user_1
);
799 test_data
[DATABASE_INPUT
][current_test
].push_back(&db_user_1_with_path
);
800 test_data
[DATABASE_INPUT
][current_test
].push_back(&db_user_3_with_path
);
801 test_data
[MERGE_OUTPUT
][current_test
].push_back(&merged_user_1
);
802 test_data
[MERGE_OUTPUT
][current_test
].push_back(&merged_user_1_with_db_path
);
803 test_data
[KEYCHAIN_OUTPUT
][current_test
].push_back(&keychain_user_2
);
804 test_data
[DATABASE_OUTPUT
][current_test
].push_back(&db_user_3_with_path
);
806 // Test a merge where Chrome has a blacklist entry, and the keychain has
809 CHECK(current_test
< kTestCount
);
810 test_data
[KEYCHAIN_INPUT
][current_test
].push_back(&keychain_user_1
);
811 test_data
[DATABASE_INPUT
][current_test
].push_back(
812 &database_blacklist_with_path
);
813 // We expect both to be present because a blacklist could be specific to a
814 // subpath, and we want access to the password on other paths.
815 test_data
[MERGE_OUTPUT
][current_test
].push_back(
816 &database_blacklist_with_path
);
817 test_data
[KEYCHAIN_OUTPUT
][current_test
].push_back(&keychain_user_1
);
819 // Test a merge where Chrome has an account, and Keychain has a blacklist
820 // (from another browser) and the Chrome password data.
822 CHECK(current_test
< kTestCount
);
823 test_data
[KEYCHAIN_INPUT
][current_test
].push_back(&keychain_blacklist
);
824 test_data
[KEYCHAIN_INPUT
][current_test
].push_back(&keychain_user_1
);
825 test_data
[DATABASE_INPUT
][current_test
].push_back(&db_user_1
);
826 test_data
[MERGE_OUTPUT
][current_test
].push_back(&merged_user_1
);
827 test_data
[KEYCHAIN_OUTPUT
][current_test
].push_back(&keychain_blacklist
);
829 // Test that matches are done using exact path when possible.
831 CHECK(current_test
< kTestCount
);
832 test_data
[KEYCHAIN_INPUT
][current_test
].push_back(&keychain_user_1
);
833 test_data
[KEYCHAIN_INPUT
][current_test
].push_back(&keychain_user_1_with_path
);
834 test_data
[DATABASE_INPUT
][current_test
].push_back(&db_user_1
);
835 test_data
[DATABASE_INPUT
][current_test
].push_back(&db_user_1_with_path
);
836 test_data
[MERGE_OUTPUT
][current_test
].push_back(&merged_user_1
);
837 test_data
[MERGE_OUTPUT
][current_test
].push_back(
838 &merged_user_1_with_both_paths
);
840 for (unsigned int test_case
= 0; test_case
<= current_test
; ++test_case
) {
841 std::vector
<PasswordForm
*> keychain_forms
;
842 for (std::vector
<PasswordFormData
*>::iterator i
=
843 test_data
[KEYCHAIN_INPUT
][test_case
].begin();
844 i
!= test_data
[KEYCHAIN_INPUT
][test_case
].end(); ++i
) {
845 keychain_forms
.push_back(CreatePasswordFormFromData(*(*i
)));
847 std::vector
<PasswordForm
*> database_forms
;
848 for (std::vector
<PasswordFormData
*>::iterator i
=
849 test_data
[DATABASE_INPUT
][test_case
].begin();
850 i
!= test_data
[DATABASE_INPUT
][test_case
].end(); ++i
) {
851 database_forms
.push_back(CreatePasswordFormFromData(*(*i
)));
854 std::vector
<PasswordForm
*> merged_forms
;
855 internal_keychain_helpers::MergePasswordForms(&keychain_forms
,
859 CHECK_FORMS(keychain_forms
, test_data
[KEYCHAIN_OUTPUT
][test_case
],
861 CHECK_FORMS(database_forms
, test_data
[DATABASE_OUTPUT
][test_case
],
863 CHECK_FORMS(merged_forms
, test_data
[MERGE_OUTPUT
][test_case
], test_case
);
865 STLDeleteElements(&keychain_forms
);
866 STLDeleteElements(&database_forms
);
867 STLDeleteElements(&merged_forms
);
871 TEST_F(PasswordStoreMacInternalsTest
, TestPasswordBulkLookup
) {
872 PasswordFormData db_data
[] = {
873 { PasswordForm::SCHEME_HTML
, "http://some.domain.com/",
874 "http://some.domain.com/", "http://some.domain.com/action.cgi",
875 L
"submit", L
"username", L
"password", L
"joe_user", L
"",
876 true, false, 1212121212 },
877 { PasswordForm::SCHEME_HTML
, "http://some.domain.com/",
878 "http://some.domain.com/page.html",
879 "http://some.domain.com/handlepage.cgi",
880 L
"submit", L
"username", L
"password", L
"joe_user", L
"",
881 true, false, 1234567890 },
882 { PasswordForm::SCHEME_HTML
, "http://some.domain.com/",
883 "http://some.domain.com/page.html",
884 "http://some.domain.com/handlepage.cgi",
885 L
"submit", L
"username", L
"password", L
"second-account", L
"",
886 true, false, 1240000000 },
887 { PasswordForm::SCHEME_HTML
, "http://dont.remember.com/",
888 "http://dont.remember.com/",
889 "http://dont.remember.com/handlepage.cgi",
890 L
"submit", L
"username", L
"password", L
"joe_user", L
"",
891 true, false, 1240000000 },
892 { PasswordForm::SCHEME_HTML
, "http://some.domain.com/",
893 "http://some.domain.com/path.html", "http://some.domain.com/action.cgi",
894 L
"submit", L
"username", L
"password", NULL
, NULL
,
895 true, false, 1212121212 },
897 std::vector
<PasswordForm
*> database_forms
;
898 for (unsigned int i
= 0; i
< arraysize(db_data
); ++i
) {
899 database_forms
.push_back(CreatePasswordFormFromData(db_data
[i
]));
901 std::vector
<PasswordForm
*> merged_forms
=
902 internal_keychain_helpers::GetPasswordsForForms(*keychain_
,
904 EXPECT_EQ(2U, database_forms
.size());
905 ASSERT_EQ(3U, merged_forms
.size());
906 EXPECT_EQ(ASCIIToUTF16("sekrit"), merged_forms
[0]->password_value
);
907 EXPECT_EQ(ASCIIToUTF16("sekrit"), merged_forms
[1]->password_value
);
908 EXPECT_TRUE(merged_forms
[2]->blacklisted_by_user
);
910 STLDeleteElements(&database_forms
);
911 STLDeleteElements(&merged_forms
);
914 TEST_F(PasswordStoreMacInternalsTest
, TestBlacklistedFiltering
) {
915 PasswordFormData db_data
[] = {
916 { PasswordForm::SCHEME_HTML
, "http://dont.remember.com/",
917 "http://dont.remember.com/",
918 "http://dont.remember.com/handlepage.cgi",
919 L
"submit", L
"username", L
"password", L
"joe_user", L
"non_empty_password",
920 true, false, 1240000000 },
921 { PasswordForm::SCHEME_HTML
, "https://dont.remember.com/",
922 "https://dont.remember.com/",
923 "https://dont.remember.com/handlepage_secure.cgi",
924 L
"submit", L
"username", L
"password", L
"joe_user", L
"non_empty_password",
925 true, false, 1240000000 },
927 std::vector
<PasswordForm
*> database_forms
;
928 for (unsigned int i
= 0; i
< arraysize(db_data
); ++i
) {
929 database_forms
.push_back(CreatePasswordFormFromData(db_data
[i
]));
931 std::vector
<PasswordForm
*> merged_forms
=
932 internal_keychain_helpers::GetPasswordsForForms(*keychain_
,
934 EXPECT_EQ(2U, database_forms
.size());
935 ASSERT_EQ(0U, merged_forms
.size());
937 STLDeleteElements(&database_forms
);
938 STLDeleteElements(&merged_forms
);
941 TEST_F(PasswordStoreMacInternalsTest
, TestFillPasswordFormFromKeychainItem
) {
942 // When |extract_password_data| is false, the password field must be empty,
943 // and |blacklisted_by_user| must be false.
944 SecKeychainItemRef keychain_item
= reinterpret_cast<SecKeychainItemRef
>(1);
945 PasswordForm form_without_extracted_password
;
946 bool parsed
= internal_keychain_helpers::FillPasswordFormFromKeychainItem(
949 &form_without_extracted_password
,
950 false); // Do not extract password.
952 ASSERT_TRUE(form_without_extracted_password
.password_value
.empty());
953 ASSERT_FALSE(form_without_extracted_password
.blacklisted_by_user
);
955 // When |extract_password_data| is true and the keychain entry has a non-empty
956 // password, the password field must be non-empty, and the value of
957 // |blacklisted_by_user| must be false.
958 keychain_item
= reinterpret_cast<SecKeychainItemRef
>(1);
959 PasswordForm form_with_extracted_password
;
960 parsed
= internal_keychain_helpers::FillPasswordFormFromKeychainItem(
963 &form_with_extracted_password
,
964 true); // Extract password.
966 ASSERT_EQ(ASCIIToUTF16("sekrit"),
967 form_with_extracted_password
.password_value
);
968 ASSERT_FALSE(form_with_extracted_password
.blacklisted_by_user
);
970 // When |extract_password_data| is true and the keychain entry has an empty
971 // username and password (""), the password field must be empty, and the value
972 // of |blacklisted_by_user| must be true.
973 keychain_item
= reinterpret_cast<SecKeychainItemRef
>(4);
974 PasswordForm negative_form
;
975 parsed
= internal_keychain_helpers::FillPasswordFormFromKeychainItem(
979 true); // Extract password.
981 ASSERT_TRUE(negative_form
.username_value
.empty());
982 ASSERT_TRUE(negative_form
.password_value
.empty());
983 ASSERT_TRUE(negative_form
.blacklisted_by_user
);
985 // When |extract_password_data| is true and the keychain entry has an empty
986 // password (""), the password field must be empty (""), and the value of
987 // |blacklisted_by_user| must be true.
988 keychain_item
= reinterpret_cast<SecKeychainItemRef
>(5);
989 PasswordForm form_with_empty_password_a
;
990 parsed
= internal_keychain_helpers::FillPasswordFormFromKeychainItem(
993 &form_with_empty_password_a
,
994 true); // Extract password.
996 ASSERT_TRUE(form_with_empty_password_a
.password_value
.empty());
997 ASSERT_TRUE(form_with_empty_password_a
.blacklisted_by_user
);
999 // When |extract_password_data| is true and the keychain entry has a single
1000 // space password (" "), the password field must be a single space (" "), and
1001 // the value of |blacklisted_by_user| must be true.
1002 keychain_item
= reinterpret_cast<SecKeychainItemRef
>(6);
1003 PasswordForm form_with_empty_password_b
;
1004 parsed
= internal_keychain_helpers::FillPasswordFormFromKeychainItem(
1007 &form_with_empty_password_b
,
1008 true); // Extract password.
1009 EXPECT_TRUE(parsed
);
1010 ASSERT_EQ(ASCIIToUTF16(" "),
1011 form_with_empty_password_b
.password_value
);
1012 ASSERT_TRUE(form_with_empty_password_b
.blacklisted_by_user
);
1015 TEST_F(PasswordStoreMacInternalsTest
, TestPasswordGetAll
) {
1016 MacKeychainPasswordFormAdapter
keychain_adapter(keychain_
);
1017 MacKeychainPasswordFormAdapter
owned_keychain_adapter(keychain_
);
1018 owned_keychain_adapter
.SetFindsOnlyOwnedItems(true);
1020 // Add a few passwords of various types so that we own some.
1021 PasswordFormData owned_password_data
[] = {
1022 { PasswordForm::SCHEME_HTML
, "http://web.site.com/",
1023 "http://web.site.com/path/to/page.html", NULL
, NULL
, NULL
, NULL
,
1024 L
"anonymous", L
"knock-knock", false, false, 0 },
1025 { PasswordForm::SCHEME_BASIC
, "http://a.site.com:2222/therealm",
1026 "http://a.site.com:2222/", NULL
, NULL
, NULL
, NULL
,
1027 L
"username", L
"password", false, false, 0 },
1028 { PasswordForm::SCHEME_DIGEST
, "https://digest.site.com/differentrealm",
1029 "https://digest.site.com/secure.html", NULL
, NULL
, NULL
, NULL
,
1030 L
"testname", L
"testpass", false, false, 0 },
1032 for (unsigned int i
= 0; i
< arraysize(owned_password_data
); ++i
) {
1033 scoped_ptr
<PasswordForm
> form(CreatePasswordFormFromData(
1034 owned_password_data
[i
]));
1035 owned_keychain_adapter
.AddPassword(*form
);
1038 std::vector
<PasswordForm
*> all_passwords
=
1039 keychain_adapter
.GetAllPasswordFormPasswords();
1040 EXPECT_EQ(8 + arraysize(owned_password_data
), all_passwords
.size());
1041 STLDeleteElements(&all_passwords
);
1043 std::vector
<PasswordForm
*> owned_passwords
=
1044 owned_keychain_adapter
.GetAllPasswordFormPasswords();
1045 EXPECT_EQ(arraysize(owned_password_data
), owned_passwords
.size());
1046 STLDeleteElements(&owned_passwords
);
1051 class PasswordStoreMacTest
: public testing::Test
{
1053 PasswordStoreMacTest() : ui_thread_(BrowserThread::UI
, &message_loop_
) {}
1055 virtual void SetUp() {
1056 login_db_
= new LoginDatabase();
1057 ASSERT_TRUE(db_dir_
.CreateUniqueTempDir());
1058 base::FilePath db_file
= db_dir_
.path().AppendASCII("login.db");
1059 ASSERT_TRUE(login_db_
->Init(db_file
));
1061 keychain_
= new MockAppleKeychain();
1063 store_
= new TestPasswordStoreMac(
1064 base::MessageLoopProxy::current(),
1065 base::MessageLoopProxy::current(),
1068 ASSERT_TRUE(store_
->Init(syncer::SyncableService::StartSyncFlare(), ""));
1071 virtual void TearDown() {
1073 EXPECT_FALSE(store_
->GetBackgroundTaskRunner().get());
1076 void WaitForStoreUpdate() {
1077 // Do a store-level query to wait for all the operations above to be done.
1078 MockPasswordStoreConsumer consumer
;
1079 EXPECT_CALL(consumer
, OnGetPasswordStoreResults(_
))
1080 .WillOnce(DoAll(WithArg
<0>(STLDeleteElements0()), QuitUIMessageLoop()));
1081 store_
->GetLogins(PasswordForm(), PasswordStore::ALLOW_PROMPT
, &consumer
);
1082 base::MessageLoop::current()->Run();
1085 TestPasswordStoreMac
* store() { return store_
.get(); }
1087 MockAppleKeychain
* keychain() { return keychain_
; }
1090 base::MessageLoopForUI message_loop_
;
1091 content::TestBrowserThread ui_thread_
;
1093 MockAppleKeychain
* keychain_
; // Owned by store_.
1094 LoginDatabase
* login_db_
; // Owned by store_.
1095 scoped_refptr
<TestPasswordStoreMac
> store_
;
1096 base::ScopedTempDir db_dir_
;
1099 TEST_F(PasswordStoreMacTest
, TestStoreUpdate
) {
1100 // Insert a password into both the database and the keychain.
1101 // This is done manually, rather than through store_->AddLogin, because the
1102 // Mock Keychain isn't smart enough to be able to support update generically,
1103 // so some.domain.com triggers special handling to test it that make inserting
1105 PasswordFormData joint_data
= {
1106 PasswordForm::SCHEME_HTML
, "http://some.domain.com/",
1107 "http://some.domain.com/insecure.html", "login.cgi",
1108 L
"username", L
"password", L
"submit", L
"joe_user", L
"sekrit", true, false, 1
1110 scoped_ptr
<PasswordForm
> joint_form(CreatePasswordFormFromData(joint_data
));
1111 login_db_
->AddLogin(*joint_form
);
1112 MockAppleKeychain::KeychainTestData joint_keychain_data
= {
1113 kSecAuthenticationTypeHTMLForm
, "some.domain.com",
1114 kSecProtocolTypeHTTP
, "/insecure.html", 0, NULL
, "20020601171500Z",
1115 "joe_user", "sekrit", false };
1116 keychain_
->AddTestItem(joint_keychain_data
);
1118 // Insert a password into the keychain only.
1119 MockAppleKeychain::KeychainTestData keychain_only_data
= {
1120 kSecAuthenticationTypeHTMLForm
, "keychain.only.com",
1121 kSecProtocolTypeHTTP
, NULL
, 0, NULL
, "20020601171500Z",
1122 "keychain", "only", false
1124 keychain_
->AddTestItem(keychain_only_data
);
1127 PasswordFormData form_data
;
1128 const char* password
; // NULL indicates no entry should be present.
1131 // Make a series of update calls.
1132 UpdateData updates
[] = {
1133 // Update the keychain+db passwords (the normal password update case).
1134 { { PasswordForm::SCHEME_HTML
, "http://some.domain.com/",
1135 "http://some.domain.com/insecure.html", "login.cgi",
1136 L
"username", L
"password", L
"submit", L
"joe_user", L
"53krit",
1140 // Update the keychain-only password; this simulates the initial use of a
1141 // password stored by another browsers.
1142 { { PasswordForm::SCHEME_HTML
, "http://keychain.only.com/",
1143 "http://keychain.only.com/login.html", "login.cgi",
1144 L
"username", L
"password", L
"submit", L
"keychain", L
"only",
1148 // Update a password that doesn't exist in either location. This tests the
1149 // case where a form is filled, then the stored login is removed, then the
1150 // form is submitted.
1151 { { PasswordForm::SCHEME_HTML
, "http://different.com/",
1152 "http://different.com/index.html", "login.cgi",
1153 L
"username", L
"password", L
"submit", L
"abc", L
"123",
1158 for (unsigned int i
= 0; i
< arraysize(updates
); ++i
) {
1159 scoped_ptr
<PasswordForm
> form(CreatePasswordFormFromData(
1160 updates
[i
].form_data
));
1161 store_
->UpdateLogin(*form
);
1164 WaitForStoreUpdate();
1166 MacKeychainPasswordFormAdapter
keychain_adapter(keychain_
);
1167 for (unsigned int i
= 0; i
< arraysize(updates
); ++i
) {
1168 scoped_ptr
<PasswordForm
> query_form(
1169 CreatePasswordFormFromData(updates
[i
].form_data
));
1171 std::vector
<PasswordForm
*> matching_items
=
1172 keychain_adapter
.PasswordsFillingForm(query_form
->signon_realm
,
1173 query_form
->scheme
);
1174 if (updates
[i
].password
) {
1175 EXPECT_GT(matching_items
.size(), 0U) << "iteration " << i
;
1176 if (matching_items
.size() >= 1)
1177 EXPECT_EQ(ASCIIToUTF16(updates
[i
].password
),
1178 matching_items
[0]->password_value
) << "iteration " << i
;
1180 EXPECT_EQ(0U, matching_items
.size()) << "iteration " << i
;
1182 STLDeleteElements(&matching_items
);
1184 login_db_
->GetLogins(*query_form
, &matching_items
);
1185 EXPECT_EQ(updates
[i
].password
? 1U : 0U, matching_items
.size())
1186 << "iteration " << i
;
1187 STLDeleteElements(&matching_items
);
1191 TEST_F(PasswordStoreMacTest
, TestDBKeychainAssociation
) {
1192 // Tests that association between the keychain and login database parts of a
1193 // password added by fuzzy (PSL) matching works.
1194 // 1. Add a password for www.facebook.com
1195 // 2. Get a password for m.facebook.com. This fuzzy matches and returns the
1196 // www.facebook.com password.
1197 // 3. Add the returned password for m.facebook.com.
1198 // 4. Remove both passwords.
1199 // -> check: that both are gone from the login DB and the keychain
1200 // This test should in particular ensure that we don't keep passwords in the
1201 // keychain just before we think we still have other (fuzzy-)matching entries
1202 // for them in the login database. (For example, here if we deleted the
1203 // www.facebook.com password from the login database, we should not be blocked
1204 // from deleting it from the keystore just becaus the m.facebook.com password
1205 // fuzzy-matches the www.facebook.com one.)
1207 // 1. Add a password for www.facebook.com
1208 PasswordFormData www_form_data
= {
1209 PasswordForm::SCHEME_HTML
, "http://www.facebook.com/",
1210 "http://www.facebook.com/index.html", "login",
1211 L
"username", L
"password", L
"submit", L
"joe_user", L
"sekrit", true, false, 1
1213 scoped_ptr
<PasswordForm
> www_form(CreatePasswordFormFromData(www_form_data
));
1214 login_db_
->AddLogin(*www_form
);
1215 MacKeychainPasswordFormAdapter
owned_keychain_adapter(keychain_
);
1216 owned_keychain_adapter
.SetFindsOnlyOwnedItems(true);
1217 owned_keychain_adapter
.AddPassword(*www_form
);
1219 // 2. Get a password for m.facebook.com.
1220 PasswordForm
m_form(*www_form
);
1221 m_form
.signon_realm
= "http://m.facebook.com";
1222 m_form
.origin
= GURL("http://m.facebook.com/index.html");
1223 MockPasswordStoreConsumer consumer
;
1224 EXPECT_CALL(consumer
, OnGetPasswordStoreResults(_
)).WillOnce(DoAll(
1225 WithArg
<0>(Invoke(&consumer
, &MockPasswordStoreConsumer::CopyElements
)),
1226 WithArg
<0>(STLDeleteElements0()),
1227 QuitUIMessageLoop()));
1228 store_
->GetLogins(m_form
, PasswordStore::ALLOW_PROMPT
, &consumer
);
1229 base::MessageLoop::current()->Run();
1230 EXPECT_EQ(1u, consumer
.last_result
.size());
1232 // 3. Add the returned password for m.facebook.com.
1233 login_db_
->AddLogin(consumer
.last_result
[0]);
1234 owned_keychain_adapter
.AddPassword(m_form
);
1236 // 4. Remove both passwords.
1237 store_
->RemoveLogin(*www_form
);
1238 store_
->RemoveLogin(m_form
);
1239 WaitForStoreUpdate();
1241 std::vector
<PasswordForm
*> matching_items
;
1242 // No trace of www.facebook.com.
1243 matching_items
= owned_keychain_adapter
.PasswordsFillingForm(
1244 www_form
->signon_realm
, www_form
->scheme
);
1245 EXPECT_EQ(0u, matching_items
.size());
1246 login_db_
->GetLogins(*www_form
, &matching_items
);
1247 EXPECT_EQ(0u, matching_items
.size());
1248 // No trace of m.facebook.com.
1249 matching_items
= owned_keychain_adapter
.PasswordsFillingForm(
1250 m_form
.signon_realm
, m_form
.scheme
);
1251 EXPECT_EQ(0u, matching_items
.size());
1252 login_db_
->GetLogins(m_form
, &matching_items
);
1253 EXPECT_EQ(0u, matching_items
.size());
1258 class PasswordsChangeObserver
:
1259 public password_manager::PasswordStore::Observer
{
1261 PasswordsChangeObserver(TestPasswordStoreMac
* store
) : observer_(this) {
1262 observer_
.Add(store
);
1265 void WaitAndVerify(PasswordStoreMacTest
* test
) {
1266 test
->WaitForStoreUpdate();
1267 ::testing::Mock::VerifyAndClearExpectations(this);
1270 // password_manager::PasswordStore::Observer:
1271 MOCK_METHOD1(OnLoginsChanged
,
1272 void(const password_manager::PasswordStoreChangeList
& changes
));
1275 ScopedObserver
<password_manager::PasswordStore
,
1276 PasswordsChangeObserver
> observer_
;
1279 password_manager::PasswordStoreChangeList
GetAddChangeList(
1280 const PasswordForm
& form
) {
1281 password_manager::PasswordStoreChange
change(
1282 password_manager::PasswordStoreChange::ADD
, form
);
1283 return password_manager::PasswordStoreChangeList(1, change
);
1286 // Tests RemoveLoginsCreatedBetween or RemoveLoginsSyncedBetween depending on
1288 void CheckRemoveLoginsBetween(PasswordStoreMacTest
* test
, bool check_created
) {
1289 PasswordFormData www_form_data_facebook
= {
1290 PasswordForm::SCHEME_HTML
, "http://www.facebook.com/",
1291 "http://www.facebook.com/index.html", "login", L
"submit", L
"username",
1292 L
"password", L
"joe_user", L
"sekrit", true, false, 0 };
1293 // The old form doesn't have elements names.
1294 PasswordFormData www_form_data_facebook_old
= {
1295 PasswordForm::SCHEME_HTML
, "http://www.facebook.com/",
1296 "http://www.facebook.com/index.html", "login", L
"", L
"",
1297 L
"", L
"joe_user", L
"oldsekrit", true, false, 0 };
1298 PasswordFormData www_form_data_other
= {
1299 PasswordForm::SCHEME_HTML
, "http://different.com/",
1300 "http://different.com/index.html", "login", L
"submit", L
"username",
1301 L
"password", L
"different_joe_user", L
"sekrit", true, false, 0 };
1302 scoped_ptr
<PasswordForm
> form_facebook(
1303 CreatePasswordFormFromData(www_form_data_facebook
));
1304 scoped_ptr
<PasswordForm
> form_facebook_old(
1305 CreatePasswordFormFromData(www_form_data_facebook_old
));
1306 scoped_ptr
<PasswordForm
> form_other(
1307 CreatePasswordFormFromData(www_form_data_other
));
1308 base::Time now
= base::Time::Now();
1309 // TODO(vasilii): remove the next line once crbug/374132 is fixed.
1310 now
= base::Time::FromTimeT(now
.ToTimeT());
1311 base::Time next_day
= now
+ base::TimeDelta::FromDays(1);
1312 if (check_created
) {
1313 form_facebook_old
->date_created
= now
;
1314 form_facebook
->date_created
= next_day
;
1315 form_other
->date_created
= next_day
;
1317 form_facebook_old
->date_synced
= now
;
1318 form_facebook
->date_synced
= next_day
;
1319 form_other
->date_synced
= next_day
;
1322 PasswordsChangeObserver
observer(test
->store());
1323 test
->store()->AddLogin(*form_facebook_old
);
1324 test
->store()->AddLogin(*form_facebook
);
1325 test
->store()->AddLogin(*form_other
);
1326 EXPECT_CALL(observer
, OnLoginsChanged(GetAddChangeList(*form_facebook_old
)));
1327 EXPECT_CALL(observer
, OnLoginsChanged(GetAddChangeList(*form_facebook
)));
1328 EXPECT_CALL(observer
, OnLoginsChanged(GetAddChangeList(*form_other
)));
1329 observer
.WaitAndVerify(test
);
1331 // Check the keychain content.
1332 MacKeychainPasswordFormAdapter
owned_keychain_adapter(test
->keychain());
1333 owned_keychain_adapter
.SetFindsOnlyOwnedItems(false);
1334 ScopedVector
<PasswordForm
> matching_items
;
1335 matching_items
.get() = owned_keychain_adapter
.PasswordsFillingForm(
1336 form_facebook
->signon_realm
, form_facebook
->scheme
);
1337 EXPECT_EQ(1u, matching_items
.size());
1338 matching_items
.clear();
1339 matching_items
.get() = owned_keychain_adapter
.PasswordsFillingForm(
1340 form_other
->signon_realm
, form_other
->scheme
);
1341 EXPECT_EQ(1u, matching_items
.size());
1342 matching_items
.clear();
1345 void (PasswordStore::*method
)(base::Time
, base::Time
) =
1346 check_created
? &PasswordStore::RemoveLoginsCreatedBetween
1347 : &PasswordStore::RemoveLoginsSyncedBetween
;
1348 (test
->store()->*method
)(base::Time(), next_day
);
1349 password_manager::PasswordStoreChangeList list
;
1350 form_facebook_old
->password_value
.clear();
1351 form_facebook
->password_value
.clear();
1352 list
.push_back(password_manager::PasswordStoreChange(
1353 password_manager::PasswordStoreChange::REMOVE
, *form_facebook_old
));
1354 list
.push_back(password_manager::PasswordStoreChange(
1355 password_manager::PasswordStoreChange::REMOVE
, *form_facebook
));
1356 EXPECT_CALL(observer
, OnLoginsChanged(list
));
1358 observer
.WaitAndVerify(test
);
1360 matching_items
.get() = owned_keychain_adapter
.PasswordsFillingForm(
1361 form_facebook
->signon_realm
, form_facebook
->scheme
);
1362 EXPECT_EQ(0u, matching_items
.size());
1363 matching_items
.get() = owned_keychain_adapter
.PasswordsFillingForm(
1364 form_other
->signon_realm
, form_other
->scheme
);
1365 EXPECT_EQ(1u, matching_items
.size());
1366 matching_items
.clear();
1368 // Remove form_other.
1369 (test
->store()->*method
)(next_day
, base::Time());
1370 form_other
->password_value
.clear();
1371 list
.push_back(password_manager::PasswordStoreChange(
1372 password_manager::PasswordStoreChange::REMOVE
, *form_other
));
1373 EXPECT_CALL(observer
, OnLoginsChanged(list
));
1374 observer
.WaitAndVerify(test
);
1375 matching_items
.get() = owned_keychain_adapter
.PasswordsFillingForm(
1376 form_other
->signon_realm
, form_other
->scheme
);
1377 EXPECT_EQ(0u, matching_items
.size());
1382 TEST_F(PasswordStoreMacTest
, TestRemoveLoginsCreatedBetween
) {
1383 CheckRemoveLoginsBetween(this, true);
1386 TEST_F(PasswordStoreMacTest
, TestRemoveLoginsSyncedBetween
) {
1387 CheckRemoveLoginsBetween(this, false);
1390 TEST_F(PasswordStoreMacTest
, TestRemoveLoginsMultiProfile
) {
1391 // Make sure that RemoveLoginsCreatedBetween does affect only the correct
1394 // Add a third-party password.
1395 MockAppleKeychain::KeychainTestData keychain_data
= {
1396 kSecAuthenticationTypeHTMLForm
, "some.domain.com",
1397 kSecProtocolTypeHTTP
, "/insecure.html", 0, NULL
, "20020601171500Z",
1398 "joe_user", "sekrit", false };
1399 keychain_
->AddTestItem(keychain_data
);
1401 // Add a password through the adapter. It has the "Chrome" creator tag.
1402 // However, it's not referenced by the password database.
1403 MacKeychainPasswordFormAdapter
owned_keychain_adapter(keychain_
);
1404 owned_keychain_adapter
.SetFindsOnlyOwnedItems(true);
1405 PasswordFormData www_form_data1
= {
1406 PasswordForm::SCHEME_HTML
, "http://www.facebook.com/",
1407 "http://www.facebook.com/index.html", "login", L
"username", L
"password",
1408 L
"submit", L
"joe_user", L
"sekrit", true, false, 1 };
1409 scoped_ptr
<PasswordForm
> www_form(CreatePasswordFormFromData(www_form_data1
));
1410 EXPECT_TRUE(owned_keychain_adapter
.AddPassword(*www_form
));
1412 // Add a password from the current profile.
1413 PasswordFormData www_form_data2
= {
1414 PasswordForm::SCHEME_HTML
, "http://www.facebook.com/",
1415 "http://www.facebook.com/index.html", "login", L
"username", L
"password",
1416 L
"submit", L
"not_joe_user", L
"12345", true, false, 1 };
1417 www_form
.reset(CreatePasswordFormFromData(www_form_data2
));
1418 store_
->AddLogin(*www_form
);
1419 WaitForStoreUpdate();
1421 ScopedVector
<PasswordForm
> matching_items
;
1422 login_db_
->GetLogins(*www_form
, &matching_items
.get());
1423 EXPECT_EQ(1u, matching_items
.size());
1424 matching_items
.clear();
1426 store_
->RemoveLoginsCreatedBetween(base::Time(), base::Time());
1427 WaitForStoreUpdate();
1429 // Check the second facebook form is gone.
1430 login_db_
->GetLogins(*www_form
, &matching_items
.get());
1431 EXPECT_EQ(0u, matching_items
.size());
1433 // Check the first facebook form is still there.
1434 matching_items
.get() = owned_keychain_adapter
.PasswordsFillingForm(
1435 www_form
->signon_realm
, www_form
->scheme
);
1436 ASSERT_EQ(1u, matching_items
.size());
1437 EXPECT_EQ(ASCIIToUTF16("joe_user"), matching_items
[0]->username_value
);
1438 matching_items
.clear();
1440 // Check the third-party password is still there.
1441 owned_keychain_adapter
.SetFindsOnlyOwnedItems(false);
1442 matching_items
.get() = owned_keychain_adapter
.PasswordsFillingForm(
1443 "http://some.domain.com/insecure.html", PasswordForm::SCHEME_HTML
);
1444 ASSERT_EQ(1u, matching_items
.size());