password_store_x_unittest.cc revision f8ee788a64d60abd8f2d742a5fdedde054ecd910
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 "base/basictypes.h" 6#include "base/bind.h" 7#include "base/bind_helpers.h" 8#include "base/file_util.h" 9#include "base/files/scoped_temp_dir.h" 10#include "base/prefs/pref_service.h" 11#include "base/run_loop.h" 12#include "base/stl_util.h" 13#include "base/strings/string_util.h" 14#include "base/strings/stringprintf.h" 15#include "base/strings/utf_string_conversions.h" 16#include "base/time/time.h" 17#include "chrome/browser/password_manager/password_store_x.h" 18#include "chrome/test/base/testing_browser_process.h" 19#include "components/password_manager/core/browser/password_form_data.h" 20#include "components/password_manager/core/browser/password_store_change.h" 21#include "components/password_manager/core/browser/password_store_consumer.h" 22#include "components/password_manager/core/common/password_manager_pref_names.h" 23#include "content/public/browser/browser_thread.h" 24#include "content/public/test/test_browser_thread_bundle.h" 25#include "testing/gmock/include/gmock/gmock.h" 26#include "testing/gtest/include/gtest/gtest.h" 27 28using autofill::PasswordForm; 29using content::BrowserThread; 30using password_manager::ContainsAllPasswordForms; 31using password_manager::PasswordStoreChange; 32using password_manager::PasswordStoreChangeList; 33using testing::_; 34using testing::DoAll; 35using testing::ElementsAreArray; 36using testing::Pointee; 37using testing::Property; 38using testing::WithArg; 39 40typedef std::vector<PasswordForm*> VectorOfForms; 41 42namespace { 43 44class MockPasswordStoreConsumer 45 : public password_manager::PasswordStoreConsumer { 46 public: 47 MOCK_METHOD1(OnGetPasswordStoreResults, 48 void(const std::vector<PasswordForm*>&)); 49}; 50 51class MockPasswordStoreObserver 52 : public password_manager::PasswordStore::Observer { 53 public: 54 MOCK_METHOD1(OnLoginsChanged, 55 void(const password_manager::PasswordStoreChangeList& changes)); 56}; 57 58class FailingBackend : public PasswordStoreX::NativeBackend { 59 public: 60 virtual bool Init() OVERRIDE { return true; } 61 62 virtual PasswordStoreChangeList AddLogin(const PasswordForm& form) OVERRIDE { 63 return PasswordStoreChangeList(); 64 } 65 virtual bool UpdateLogin(const PasswordForm& form, 66 PasswordStoreChangeList* changes) OVERRIDE { 67 return false; 68 } 69 virtual bool RemoveLogin(const PasswordForm& form) OVERRIDE { return false; } 70 71 virtual bool RemoveLoginsCreatedBetween(base::Time delete_begin, 72 base::Time delete_end) OVERRIDE { 73 return false; 74 } 75 76 virtual bool RemoveLoginsSyncedBetween( 77 base::Time delete_begin, 78 base::Time delete_end, 79 password_manager::PasswordStoreChangeList* changes) OVERRIDE { 80 return false; 81 } 82 83 virtual bool GetLogins(const PasswordForm& form, 84 PasswordFormList* forms) OVERRIDE { 85 return false; 86 } 87 88 virtual bool GetLoginsCreatedBetween(base::Time get_begin, 89 base::Time get_end, 90 PasswordFormList* forms) OVERRIDE { 91 return false; 92 } 93 94 virtual bool GetAutofillableLogins(PasswordFormList* forms) OVERRIDE { 95 return false; 96 } 97 virtual bool GetBlacklistLogins(PasswordFormList* forms) OVERRIDE { 98 return false; 99 } 100}; 101 102class MockBackend : public PasswordStoreX::NativeBackend { 103 public: 104 virtual bool Init() OVERRIDE { return true; } 105 106 virtual PasswordStoreChangeList AddLogin(const PasswordForm& form) OVERRIDE { 107 all_forms_.push_back(form); 108 PasswordStoreChange change(PasswordStoreChange::ADD, form); 109 return PasswordStoreChangeList(1, change); 110 } 111 112 virtual bool UpdateLogin(const PasswordForm& form, 113 PasswordStoreChangeList* changes) OVERRIDE { 114 for (size_t i = 0; i < all_forms_.size(); ++i) 115 if (CompareForms(all_forms_[i], form, true)) { 116 all_forms_[i] = form; 117 changes->push_back(PasswordStoreChange(PasswordStoreChange::UPDATE, 118 form)); 119 } 120 return true; 121 } 122 123 virtual bool RemoveLogin(const PasswordForm& form) OVERRIDE { 124 for (size_t i = 0; i < all_forms_.size(); ++i) 125 if (CompareForms(all_forms_[i], form, false)) 126 erase(i--); 127 return true; 128 } 129 130 virtual bool RemoveLoginsCreatedBetween(base::Time delete_begin, 131 base::Time delete_end) OVERRIDE { 132 for (size_t i = 0; i < all_forms_.size(); ++i) { 133 if (delete_begin <= all_forms_[i].date_created && 134 (delete_end.is_null() || all_forms_[i].date_created < delete_end)) 135 erase(i--); 136 } 137 return true; 138 } 139 140 virtual bool RemoveLoginsSyncedBetween( 141 base::Time delete_begin, 142 base::Time delete_end, 143 password_manager::PasswordStoreChangeList* changes) OVERRIDE { 144 DCHECK(changes); 145 for (size_t i = 0; i < all_forms_.size(); ++i) { 146 if (delete_begin <= all_forms_[i].date_synced && 147 (delete_end.is_null() || all_forms_[i].date_synced < delete_end)) { 148 changes->push_back(password_manager::PasswordStoreChange( 149 password_manager::PasswordStoreChange::REMOVE, all_forms_[i])); 150 erase(i--); 151 } 152 } 153 return true; 154 } 155 156 virtual bool GetLogins(const PasswordForm& form, 157 PasswordFormList* forms) OVERRIDE { 158 for (size_t i = 0; i < all_forms_.size(); ++i) 159 if (all_forms_[i].signon_realm == form.signon_realm) 160 forms->push_back(new PasswordForm(all_forms_[i])); 161 return true; 162 } 163 164 virtual bool GetLoginsCreatedBetween(base::Time get_begin, 165 base::Time get_end, 166 PasswordFormList* forms) OVERRIDE { 167 for (size_t i = 0; i < all_forms_.size(); ++i) 168 if (get_begin <= all_forms_[i].date_created && 169 (get_end.is_null() || all_forms_[i].date_created < get_end)) 170 forms->push_back(new PasswordForm(all_forms_[i])); 171 return true; 172 } 173 174 virtual bool GetAutofillableLogins(PasswordFormList* forms) OVERRIDE { 175 for (size_t i = 0; i < all_forms_.size(); ++i) 176 if (!all_forms_[i].blacklisted_by_user) 177 forms->push_back(new PasswordForm(all_forms_[i])); 178 return true; 179 } 180 181 virtual bool GetBlacklistLogins(PasswordFormList* forms) OVERRIDE { 182 for (size_t i = 0; i < all_forms_.size(); ++i) 183 if (all_forms_[i].blacklisted_by_user) 184 forms->push_back(new PasswordForm(all_forms_[i])); 185 return true; 186 } 187 188 private: 189 void erase(size_t index) { 190 if (index < all_forms_.size() - 1) 191 all_forms_[index] = all_forms_[all_forms_.size() - 1]; 192 all_forms_.pop_back(); 193 } 194 195 bool CompareForms(const PasswordForm& a, const PasswordForm& b, bool update) { 196 // An update check doesn't care about the submit element. 197 if (!update && a.submit_element != b.submit_element) 198 return false; 199 return a.origin == b.origin && 200 a.password_element == b.password_element && 201 a.signon_realm == b.signon_realm && 202 a.username_element == b.username_element && 203 a.username_value == b.username_value; 204 } 205 206 std::vector<PasswordForm> all_forms_; 207}; 208 209class MockLoginDatabaseReturn { 210 public: 211 MOCK_METHOD1(OnLoginDatabaseQueryDone, 212 void(const std::vector<PasswordForm*>&)); 213}; 214 215void LoginDatabaseQueryCallback(password_manager::LoginDatabase* login_db, 216 bool autofillable, 217 MockLoginDatabaseReturn* mock_return) { 218 std::vector<PasswordForm*> forms; 219 if (autofillable) 220 login_db->GetAutofillableLogins(&forms); 221 else 222 login_db->GetBlacklistLogins(&forms); 223 mock_return->OnLoginDatabaseQueryDone(forms); 224} 225 226// Generate |count| expected logins, either auto-fillable or blacklisted. 227void InitExpectedForms(bool autofillable, size_t count, VectorOfForms* forms) { 228 const char* domain = autofillable ? "example" : "blacklisted"; 229 for (size_t i = 0; i < count; ++i) { 230 std::string realm = base::StringPrintf("http://%zu.%s.com", i, domain); 231 std::string origin = base::StringPrintf("http://%zu.%s.com/origin", 232 i, domain); 233 std::string action = base::StringPrintf("http://%zu.%s.com/action", 234 i, domain); 235 password_manager::PasswordFormData data = { 236 PasswordForm::SCHEME_HTML, 237 realm.c_str(), 238 origin.c_str(), 239 action.c_str(), 240 L"submit_element", 241 L"username_element", 242 L"password_element", 243 autofillable ? L"username_value" : NULL, 244 autofillable ? L"password_value" : NULL, 245 autofillable, 246 false, 247 static_cast<double>(i + 1)}; 248 forms->push_back(CreatePasswordFormFromData(data)); 249 } 250} 251 252} // anonymous namespace 253 254enum BackendType { 255 NO_BACKEND, 256 FAILING_BACKEND, 257 WORKING_BACKEND 258}; 259 260class PasswordStoreXTest : public testing::TestWithParam<BackendType> { 261 protected: 262 virtual void SetUp() { 263 ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); 264 265 login_db_.reset(new password_manager::LoginDatabase()); 266 ASSERT_TRUE(login_db_->Init(temp_dir_.path().Append("login_test"))); 267 } 268 269 virtual void TearDown() { 270 base::RunLoop().RunUntilIdle(); 271 } 272 273 PasswordStoreX::NativeBackend* GetBackend() { 274 switch (GetParam()) { 275 case FAILING_BACKEND: 276 return new FailingBackend(); 277 case WORKING_BACKEND: 278 return new MockBackend(); 279 default: 280 return NULL; 281 } 282 } 283 284 content::TestBrowserThreadBundle thread_bundle_; 285 286 scoped_ptr<password_manager::LoginDatabase> login_db_; 287 base::ScopedTempDir temp_dir_; 288}; 289 290ACTION(STLDeleteElements0) { 291 STLDeleteContainerPointers(arg0.begin(), arg0.end()); 292} 293 294TEST_P(PasswordStoreXTest, Notifications) { 295 scoped_refptr<PasswordStoreX> store( 296 new PasswordStoreX(base::MessageLoopProxy::current(), 297 base::MessageLoopProxy::current(), 298 login_db_.release(), 299 GetBackend())); 300 store->Init(syncer::SyncableService::StartSyncFlare()); 301 302 password_manager::PasswordFormData form_data = { 303 PasswordForm::SCHEME_HTML, "http://bar.example.com", 304 "http://bar.example.com/origin", "http://bar.example.com/action", 305 L"submit_element", L"username_element", 306 L"password_element", L"username_value", 307 L"password_value", true, 308 false, 1}; 309 scoped_ptr<PasswordForm> form(CreatePasswordFormFromData(form_data)); 310 311 MockPasswordStoreObserver observer; 312 store->AddObserver(&observer); 313 314 const PasswordStoreChange expected_add_changes[] = { 315 PasswordStoreChange(PasswordStoreChange::ADD, *form), 316 }; 317 318 EXPECT_CALL( 319 observer, 320 OnLoginsChanged(ElementsAreArray(expected_add_changes))); 321 322 // Adding a login should trigger a notification. 323 store->AddLogin(*form); 324 325 // The PasswordStore schedules tasks to run on the DB thread. Wait for them 326 // to complete. 327 base::RunLoop().RunUntilIdle(); 328 329 // Change the password. 330 form->password_value = base::ASCIIToUTF16("a different password"); 331 332 const PasswordStoreChange expected_update_changes[] = { 333 PasswordStoreChange(PasswordStoreChange::UPDATE, *form), 334 }; 335 336 EXPECT_CALL( 337 observer, 338 OnLoginsChanged(ElementsAreArray(expected_update_changes))); 339 340 // Updating the login with the new password should trigger a notification. 341 store->UpdateLogin(*form); 342 343 // Wait for PasswordStore to send execute. 344 base::RunLoop().RunUntilIdle(); 345 346 const PasswordStoreChange expected_delete_changes[] = { 347 PasswordStoreChange(PasswordStoreChange::REMOVE, *form), 348 }; 349 350 EXPECT_CALL( 351 observer, 352 OnLoginsChanged(ElementsAreArray(expected_delete_changes))); 353 354 // Deleting the login should trigger a notification. 355 store->RemoveLogin(*form); 356 357 // Wait for PasswordStore to execute. 358 base::RunLoop().RunUntilIdle(); 359 360 store->RemoveObserver(&observer); 361 362 store->Shutdown(); 363} 364 365TEST_P(PasswordStoreXTest, NativeMigration) { 366 VectorOfForms expected_autofillable; 367 InitExpectedForms(true, 50, &expected_autofillable); 368 369 VectorOfForms expected_blacklisted; 370 InitExpectedForms(false, 50, &expected_blacklisted); 371 372 // Get the initial size of the login DB file, before we populate it. 373 // This will be used later to make sure it gets back to this size. 374 const base::FilePath login_db_file = temp_dir_.path().Append("login_test"); 375 base::File::Info db_file_start_info; 376 ASSERT_TRUE(base::GetFileInfo(login_db_file, &db_file_start_info)); 377 378 password_manager::LoginDatabase* login_db = login_db_.get(); 379 380 // Populate the login DB with logins that should be migrated. 381 for (VectorOfForms::iterator it = expected_autofillable.begin(); 382 it != expected_autofillable.end(); ++it) { 383 login_db->AddLogin(**it); 384 } 385 for (VectorOfForms::iterator it = expected_blacklisted.begin(); 386 it != expected_blacklisted.end(); ++it) { 387 login_db->AddLogin(**it); 388 } 389 390 // Get the new size of the login DB file. We expect it to be larger. 391 base::File::Info db_file_full_info; 392 ASSERT_TRUE(base::GetFileInfo(login_db_file, &db_file_full_info)); 393 EXPECT_GT(db_file_full_info.size, db_file_start_info.size); 394 395 // Initializing the PasswordStore shouldn't trigger a native migration (yet). 396 scoped_refptr<PasswordStoreX> store( 397 new PasswordStoreX(base::MessageLoopProxy::current(), 398 base::MessageLoopProxy::current(), 399 login_db_.release(), 400 GetBackend())); 401 store->Init(syncer::SyncableService::StartSyncFlare()); 402 403 MockPasswordStoreConsumer consumer; 404 405 // The autofillable forms should have been migrated to the native backend. 406 EXPECT_CALL(consumer, 407 OnGetPasswordStoreResults( 408 ContainsAllPasswordForms(expected_autofillable))) 409 .WillOnce(WithArg<0>(STLDeleteElements0())); 410 411 store->GetAutofillableLogins(&consumer); 412 base::RunLoop().RunUntilIdle(); 413 414 // The blacklisted forms should have been migrated to the native backend. 415 EXPECT_CALL(consumer, 416 OnGetPasswordStoreResults(ContainsAllPasswordForms(expected_blacklisted))) 417 .WillOnce(WithArg<0>(STLDeleteElements0())); 418 419 store->GetBlacklistLogins(&consumer); 420 base::RunLoop().RunUntilIdle(); 421 422 VectorOfForms empty; 423 MockLoginDatabaseReturn ld_return; 424 425 if (GetParam() == WORKING_BACKEND) { 426 // No autofillable logins should be left in the login DB. 427 EXPECT_CALL(ld_return, 428 OnLoginDatabaseQueryDone(ContainsAllPasswordForms(empty))); 429 } else { 430 // The autofillable logins should still be in the login DB. 431 EXPECT_CALL(ld_return, 432 OnLoginDatabaseQueryDone( 433 ContainsAllPasswordForms(expected_autofillable))) 434 .WillOnce(WithArg<0>(STLDeleteElements0())); 435 } 436 437 LoginDatabaseQueryCallback(login_db, true, &ld_return); 438 439 // Wait for the login DB methods to execute. 440 base::RunLoop().RunUntilIdle(); 441 442 if (GetParam() == WORKING_BACKEND) { 443 // Likewise, no blacklisted logins should be left in the login DB. 444 EXPECT_CALL(ld_return, 445 OnLoginDatabaseQueryDone(ContainsAllPasswordForms(empty))); 446 } else { 447 // The blacklisted logins should still be in the login DB. 448 EXPECT_CALL(ld_return, 449 OnLoginDatabaseQueryDone( 450 ContainsAllPasswordForms(expected_blacklisted))) 451 .WillOnce(WithArg<0>(STLDeleteElements0())); 452 } 453 454 LoginDatabaseQueryCallback(login_db, false, &ld_return); 455 456 // Wait for the login DB methods to execute. 457 base::RunLoop().RunUntilIdle(); 458 459 if (GetParam() == WORKING_BACKEND) { 460 // If the migration succeeded, then not only should there be no logins left 461 // in the login DB, but also the file should have been deleted and then 462 // recreated. We approximate checking for this by checking that the file 463 // size is equal to the size before we populated it, even though it was 464 // larger after populating it. 465 base::File::Info db_file_end_info; 466 ASSERT_TRUE(base::GetFileInfo(login_db_file, &db_file_end_info)); 467 EXPECT_EQ(db_file_start_info.size, db_file_end_info.size); 468 } 469 470 STLDeleteElements(&expected_autofillable); 471 STLDeleteElements(&expected_blacklisted); 472 473 store->Shutdown(); 474} 475 476INSTANTIATE_TEST_CASE_P(NoBackend, 477 PasswordStoreXTest, 478 testing::Values(NO_BACKEND)); 479INSTANTIATE_TEST_CASE_P(FailingBackend, 480 PasswordStoreXTest, 481 testing::Values(FAILING_BACKEND)); 482INSTANTIATE_TEST_CASE_P(WorkingBackend, 483 PasswordStoreXTest, 484 testing::Values(WORKING_BACKEND)); 485