Adding instrumentation to locate the source of jankiness
[chromium-blink-merge.git] / chrome / browser / password_manager / password_store_mac_unittest.cc
blob26758999a9a82e6069ac9b51572c1f0e77c2ad86
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;
33 using testing::_;
34 using testing::DoAll;
35 using testing::Invoke;
36 using testing::WithArg;
38 namespace {
40 class MockPasswordStoreConsumer : public PasswordStoreConsumer {
41 public:
42 MOCK_METHOD1(OnGetPasswordStoreResults,
43 void(const std::vector<autofill::PasswordForm*>&));
45 void CopyElements(const std::vector<autofill::PasswordForm*>& forms) {
46 last_result.clear();
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 {
65 public:
66 TestPasswordStoreMac(
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,
72 db_thread_runner,
73 keychain,
74 login_db) {
77 using PasswordStoreMac::GetBackgroundTaskRunner;
79 private:
80 virtual ~TestPasswordStoreMac() {}
82 DISALLOW_COPY_AND_ASSIGN(TestPasswordStoreMac);
85 } // namespace
87 #pragma mark -
89 class PasswordStoreMacInternalsTest : public testing::Test {
90 public:
91 virtual void SetUp() {
92 MockAppleKeychain::KeychainTestData test_data[] = {
93 // Basic HTML form.
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",
108 "", "", true },
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",
120 "19980330100000Z",
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();
142 delete keychain_;
145 protected:
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_;
164 #pragma mark -
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;
170 const char* origin;
171 const char* action;
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);
210 } else {
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");
215 return form;
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())
236 return;
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)
245 << test_label;
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)
249 << test_label;
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;
262 } else {
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);
277 #pragma mark -
279 TEST_F(PasswordStoreMacInternalsTest, TestKeychainToFormTranslation) {
280 typedef struct {
281 const PasswordForm::Scheme scheme;
282 const char* signon_realm;
283 const char* origin;
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;
293 } TestExpectations;
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
321 // which is 1601.
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);
331 PasswordForm form;
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;
348 } else {
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);
370 PasswordForm form;
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 },
388 2, 2 },
389 { { PasswordForm::SCHEME_HTML, "http://some.domain.com/",
390 NULL, NULL, NULL, NULL, NULL, L"wrong_user", NULL, false, false, 0 },
391 2, 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 },
395 0, 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,
399 0 },
400 1, 1 },
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,
404 0 },
405 0, 0 },
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,
409 0 },
410 0, 0 },
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 },
414 1, 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,
418 0 },
419 0, 0 },
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,
435 query_form->scheme);
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,
446 *keychain_);
447 matching_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) {
455 keychain_->Free(*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(
512 base_form_data[i]));
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(
543 *modified_forms[j]);
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;
554 bool should_succeed;
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
576 // reported.
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);
590 if (add_succeeded) {
591 EXPECT_TRUE(owned_keychain_adapter.HasPasswordsMergeableWithForm(
592 *in_form));
593 EXPECT_TRUE(owned_keychain_adapter.HasPasswordExactlyMatchingForm(
594 *in_form));
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_,
611 keychain_item,
612 &stored_form,
613 true);
614 EXPECT_EQ(update_form->password_value, stored_form.password_value);
618 TEST_F(PasswordStoreMacInternalsTest, TestKeychainRemove) {
619 struct TestDataAndExpectation {
620 PasswordFormData data;
621 bool should_succeed;
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));
640 delete add_form;
642 for (unsigned int i = 0; i < arraysize(test_data); ++i) {
643 scoped_ptr<PasswordForm> form(CreatePasswordFormFromData(
644 test_data[i].data));
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();
671 EXPECT_TRUE(
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");
676 EXPECT_TRUE(
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;
684 EXPECT_FALSE(
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/");
690 EXPECT_FALSE(
691 FormsMatchForMerge(base_form, different_form, STRICT_FORM_MATCH));
694 PasswordForm different_form(base_form);
695 different_form.username_value = ASCIIToUTF16("john.doe");
696 EXPECT_FALSE(
697 FormsMatchForMerge(base_form, different_form, STRICT_FORM_MATCH));
700 PasswordForm different_form(base_form);
701 different_form.blacklisted_by_user = true;
702 EXPECT_FALSE(
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
779 // simple.
780 enum {
781 KEYCHAIN_INPUT = 0,
782 DATABASE_INPUT,
783 MERGE_OUTPUT,
784 KEYCHAIN_OUTPUT,
785 DATABASE_OUTPUT,
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
807 // a stored account.
808 ++current_test;
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.
821 ++current_test;
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.
830 ++current_test;
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,
856 &database_forms,
857 &merged_forms);
859 CHECK_FORMS(keychain_forms, test_data[KEYCHAIN_OUTPUT][test_case],
860 test_case);
861 CHECK_FORMS(database_forms, test_data[DATABASE_OUTPUT][test_case],
862 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_,
903 &database_forms);
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_,
933 &database_forms);
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(
947 *keychain_,
948 keychain_item,
949 &form_without_extracted_password,
950 false); // Do not extract password.
951 EXPECT_TRUE(parsed);
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(
961 *keychain_,
962 keychain_item,
963 &form_with_extracted_password,
964 true); // Extract password.
965 EXPECT_TRUE(parsed);
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(
976 *keychain_,
977 keychain_item,
978 &negative_form,
979 true); // Extract password.
980 EXPECT_TRUE(parsed);
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(
991 *keychain_,
992 keychain_item,
993 &form_with_empty_password_a,
994 true); // Extract password.
995 EXPECT_TRUE(parsed);
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(
1005 *keychain_,
1006 keychain_item,
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);
1049 #pragma mark -
1051 class PasswordStoreMacTest : public testing::Test {
1052 public:
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(),
1066 keychain_,
1067 login_db_);
1068 ASSERT_TRUE(store_->Init(syncer::SyncableService::StartSyncFlare(), ""));
1071 virtual void TearDown() {
1072 store_->Shutdown();
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_; }
1089 protected:
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
1104 // fail.
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);
1126 struct UpdateData {
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",
1137 true, false, 2 },
1138 "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",
1145 true, false, 2 },
1146 "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",
1154 true, false, 2 },
1155 NULL,
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;
1179 } else {
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());
1256 namespace {
1258 class PasswordsChangeObserver :
1259 public password_manager::PasswordStore::Observer {
1260 public:
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));
1274 private:
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
1287 // |check_created|.
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;
1316 } else {
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();
1344 // Remove facebook.
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));
1357 list.clear();
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());
1380 } // namespace
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
1392 // profile.
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());