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