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 "chrome/browser/password_manager/password_store_mac.h" 6 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" 22 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; 37 38 namespace { 39 40 class MockPasswordStoreConsumer : public PasswordStoreConsumer { 41 public: 42 MOCK_METHOD1(OnGetPasswordStoreResults, 43 void(const std::vector<autofill::PasswordForm*>&)); 44 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]); 49 } 50 } 51 52 std::vector<PasswordForm> last_result; 53 }; 54 55 ACTION(STLDeleteElements0) { 56 STLDeleteContainerPointers(arg0.begin(), arg0.end()); 57 } 58 59 ACTION(QuitUIMessageLoop) { 60 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 61 base::MessageLoop::current()->Quit(); 62 } 63 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) { 75 } 76 77 using PasswordStoreMac::GetBackgroundTaskRunner; 78 79 private: 80 virtual ~TestPasswordStoreMac() {} 81 82 DISALLOW_COPY_AND_ASSIGN(TestPasswordStoreMac); 83 }; 84 85 } // namespace 86 87 #pragma mark - 88 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 }, 130 }; 131 132 keychain_ = new MockAppleKeychain(); 133 134 for (unsigned int i = 0; i < arraysize(test_data); ++i) { 135 keychain_->AddTestItem(test_data[i]); 136 } 137 } 138 139 virtual void TearDown() { 140 ExpectCreatesAndFreesBalanced(); 141 ExpectCreatorCodesSet(); 142 delete keychain_; 143 } 144 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()); 153 } 154 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()); 159 } 160 161 MockAppleKeychain* keychain_; 162 }; 163 164 #pragma mark - 165 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; 180 }; 181 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; 212 } 213 form->avatar_url = GURL("https://accounts.google.com/Avatar"); 214 form->federation_url = GURL("https://accounts.google.com/login"); 215 return form; 216 } 217 218 // Macro to simplify calling CheckFormsAgainstExpectations with a useful label. 219 #define CHECK_FORMS(forms, expectations, i) \ 220 CheckFormsAgainstExpectations(forms, expectations, #forms, i) 221 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); 233 234 EXPECT_EQ(expectations.size(), forms.size()) << test_label; 235 if (expectations.size() != forms.size()) 236 return; 237 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; 264 } 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); 274 } 275 } 276 277 #pragma mark - 278 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; 294 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 }, 325 }; 326 327 for (unsigned int i = 0; i < ARRAYSIZE_UNSAFE(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); 334 335 EXPECT_TRUE(parsed) << "In iteration " << i; 336 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; 350 } 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; 365 } 366 367 { 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); 374 } 375 } 376 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; 382 }; 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 }, 423 }; 424 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_UNSAFE(test_data); ++i) { 429 scoped_ptr<PasswordForm> query_form( 430 CreatePasswordFormFromData(test_data[i].data)); 431 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); 438 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); 456 } 457 STLDeleteElements(&matching_items); 458 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); 465 } 466 } 467 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); 474 } 475 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(); 483 } 484 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(); 492 } 493 494 TEST_F(PasswordStoreMacInternalsTest, TestKeychainExactSearch) { 495 MacKeychainPasswordFormAdapter keychain_adapter(keychain_); 496 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 }, 507 }; 508 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)); 515 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; 519 520 modified_forms.push_back(new PasswordForm(*base_form)); 521 modified_forms.back()->username_value = ASCIIToUTF16("wrong_user"); 522 523 modified_forms.push_back(new PasswordForm(*base_form)); 524 SetPasswordFormPath(modified_forms.back(), "elsewhere.html"); 525 526 modified_forms.push_back(new PasswordForm(*base_form)); 527 modified_forms.back()->scheme = PasswordForm::SCHEME_OTHER; 528 529 modified_forms.push_back(new PasswordForm(*base_form)); 530 SetPasswordFormPort(modified_forms.back(), "1234"); 531 532 modified_forms.push_back(new PasswordForm(*base_form)); 533 modified_forms.back()->blacklisted_by_user = true; 534 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"); 539 } 540 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; 546 } 547 STLDeleteElements(&modified_forms); 548 } 549 } 550 551 TEST_F(PasswordStoreMacInternalsTest, TestKeychainAdd) { 552 struct TestDataAndExpectation { 553 PasswordFormData data; 554 bool should_succeed; 555 }; 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 }, 580 }; 581 582 MacKeychainPasswordFormAdapter owned_keychain_adapter(keychain_); 583 owned_keychain_adapter.SetFindsOnlyOwnedItems(true); 584 585 for (unsigned int i = 0; i < ARRAYSIZE_UNSAFE(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)); 595 } 596 } 597 598 // Test that adding duplicate item updates the existing item. 599 { 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 604 }; 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); 615 } 616 } 617 618 TEST_F(PasswordStoreMacInternalsTest, TestKeychainRemove) { 619 struct TestDataAndExpectation { 620 PasswordFormData data; 621 bool should_succeed; 622 }; 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 }, 632 }; 633 634 MacKeychainPasswordFormAdapter owned_keychain_adapter(keychain_); 635 owned_keychain_adapter.SetFindsOnlyOwnedItems(true); 636 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; 641 642 for (unsigned int i = 0; i < ARRAYSIZE_UNSAFE(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)); 647 648 MacKeychainPasswordFormAdapter keychain_adapter(keychain_); 649 bool match = keychain_adapter.HasPasswordExactlyMatchingForm(*form); 650 EXPECT_EQ(test_data[i].should_succeed, !match); 651 } 652 } 653 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"); 659 660 { 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)); 673 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)); 678 } 679 680 // Check that any one primary key changing is enough to prevent matching. 681 { 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)); 686 } 687 { 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)); 692 } 693 { 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)); 698 } 699 { 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)); 704 } 705 706 // Blacklist forms should *never* match for merging, even when identical 707 // (and certainly not when only one is a blacklist entry). 708 { 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)); 713 } 714 } 715 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 }; 735 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 }; 758 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 }; 776 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 787 }; 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; 793 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); 805 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); 818 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); 828 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); 839 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))); 846 } 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))); 852 } 853 854 std::vector<PasswordForm*> merged_forms; 855 internal_keychain_helpers::MergePasswordForms(&keychain_forms, 856 &database_forms, 857 &merged_forms); 858 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); 864 865 STLDeleteElements(&keychain_forms); 866 STLDeleteElements(&database_forms); 867 STLDeleteElements(&merged_forms); 868 } 869 } 870 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 }, 896 }; 897 std::vector<PasswordForm*> database_forms; 898 for (unsigned int i = 0; i < ARRAYSIZE_UNSAFE(db_data); ++i) { 899 database_forms.push_back(CreatePasswordFormFromData(db_data[i])); 900 } 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); 909 910 STLDeleteElements(&database_forms); 911 STLDeleteElements(&merged_forms); 912 } 913 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 }, 926 }; 927 std::vector<PasswordForm*> database_forms; 928 for (unsigned int i = 0; i < ARRAYSIZE_UNSAFE(db_data); ++i) { 929 database_forms.push_back(CreatePasswordFormFromData(db_data[i])); 930 } 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()); 936 937 STLDeleteElements(&database_forms); 938 STLDeleteElements(&merged_forms); 939 } 940 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); 954 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); 969 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); 984 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); 998 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); 1013 } 1014 1015 TEST_F(PasswordStoreMacInternalsTest, TestPasswordGetAll) { 1016 MacKeychainPasswordFormAdapter keychain_adapter(keychain_); 1017 MacKeychainPasswordFormAdapter owned_keychain_adapter(keychain_); 1018 owned_keychain_adapter.SetFindsOnlyOwnedItems(true); 1019 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 }, 1031 }; 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); 1036 } 1037 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); 1042 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); 1047 } 1048 1049 #pragma mark - 1050 1051 class PasswordStoreMacTest : public testing::Test { 1052 public: 1053 PasswordStoreMacTest() : ui_thread_(BrowserThread::UI, &message_loop_) {} 1054 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)); 1060 1061 keychain_ = new MockAppleKeychain(); 1062 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(), "")); 1069 } 1070 1071 virtual void TearDown() { 1072 store_->Shutdown(); 1073 EXPECT_FALSE(store_->GetBackgroundTaskRunner().get()); 1074 } 1075 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(); 1083 } 1084 1085 TestPasswordStoreMac* store() { return store_.get(); } 1086 1087 MockAppleKeychain* keychain() { return keychain_; } 1088 1089 protected: 1090 base::MessageLoopForUI message_loop_; 1091 content::TestBrowserThread ui_thread_; 1092 1093 MockAppleKeychain* keychain_; // Owned by store_. 1094 LoginDatabase* login_db_; // Owned by store_. 1095 scoped_refptr<TestPasswordStoreMac> store_; 1096 base::ScopedTempDir db_dir_; 1097 }; 1098 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 1109 }; 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); 1117 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 1123 }; 1124 keychain_->AddTestItem(keychain_only_data); 1125 1126 struct UpdateData { 1127 PasswordFormData form_data; 1128 const char* password; // NULL indicates no entry should be present. 1129 }; 1130 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", 1139 }, 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", 1147 }, 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, 1156 }, 1157 }; 1158 for (unsigned int i = 0; i < ARRAYSIZE_UNSAFE(updates); ++i) { 1159 scoped_ptr<PasswordForm> form(CreatePasswordFormFromData( 1160 updates[i].form_data)); 1161 store_->UpdateLogin(*form); 1162 } 1163 1164 WaitForStoreUpdate(); 1165 1166 MacKeychainPasswordFormAdapter keychain_adapter(keychain_); 1167 for (unsigned int i = 0; i < ARRAYSIZE_UNSAFE(updates); ++i) { 1168 scoped_ptr<PasswordForm> query_form( 1169 CreatePasswordFormFromData(updates[i].form_data)); 1170 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; 1181 } 1182 STLDeleteElements(&matching_items); 1183 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); 1188 } 1189 } 1190 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.) 1206 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 1212 }; 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); 1218 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()); 1231 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); 1235 1236 // 4. Remove both passwords. 1237 store_->RemoveLogin(*www_form); 1238 store_->RemoveLogin(m_form); 1239 WaitForStoreUpdate(); 1240 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()); 1254 } 1255 1256 namespace { 1257 1258 class PasswordsChangeObserver : 1259 public password_manager::PasswordStore::Observer { 1260 public: 1261 PasswordsChangeObserver(TestPasswordStoreMac* store) : observer_(this) { 1262 observer_.Add(store); 1263 } 1264 1265 void WaitAndVerify(PasswordStoreMacTest* test) { 1266 test->WaitForStoreUpdate(); 1267 ::testing::Mock::VerifyAndClearExpectations(this); 1268 } 1269 1270 // password_manager::PasswordStore::Observer: 1271 MOCK_METHOD1(OnLoginsChanged, 1272 void(const password_manager::PasswordStoreChangeList& changes)); 1273 1274 private: 1275 ScopedObserver<password_manager::PasswordStore, 1276 PasswordsChangeObserver> observer_; 1277 }; 1278 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); 1284 } 1285 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; 1320 } 1321 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); 1330 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(); 1343 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); 1359 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(); 1367 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()); 1378 } 1379 1380 } // namespace 1381 1382 TEST_F(PasswordStoreMacTest, TestRemoveLoginsCreatedBetween) { 1383 CheckRemoveLoginsBetween(this, true); 1384 } 1385 1386 TEST_F(PasswordStoreMacTest, TestRemoveLoginsSyncedBetween) { 1387 CheckRemoveLoginsBetween(this, false); 1388 } 1389 1390 TEST_F(PasswordStoreMacTest, TestRemoveLoginsMultiProfile) { 1391 // Make sure that RemoveLoginsCreatedBetween does affect only the correct 1392 // profile. 1393 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); 1400 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)); 1411 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(); 1420 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(); 1425 1426 store_->RemoveLoginsCreatedBetween(base::Time(), base::Time()); 1427 WaitForStoreUpdate(); 1428 1429 // Check the second facebook form is gone. 1430 login_db_->GetLogins(*www_form, &matching_items.get()); 1431 EXPECT_EQ(0u, matching_items.size()); 1432 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(); 1439 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()); 1445 } 1446