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