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