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