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