password_store_x_unittest.cc revision 90dce4d38c5ff5333bea97d859d4e484e27edf0c
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/platform_file.h" 11#include "base/prefs/pref_service.h" 12#include "base/stl_util.h" 13#include "base/string_util.h" 14#include "base/stringprintf.h" 15#include "base/synchronization/waitable_event.h" 16#include "base/time.h" 17#include "base/utf_string_conversions.h" 18#include "chrome/browser/password_manager/password_form_data.h" 19#include "chrome/browser/password_manager/password_store_change.h" 20#include "chrome/browser/password_manager/password_store_consumer.h" 21#include "chrome/browser/password_manager/password_store_x.h" 22#include "chrome/common/chrome_notification_types.h" 23#include "chrome/common/pref_names.h" 24#include "chrome/test/base/testing_browser_process.h" 25#include "chrome/test/base/testing_profile.h" 26#include "content/public/browser/notification_details.h" 27#include "content/public/browser/notification_registrar.h" 28#include "content/public/browser/notification_source.h" 29#include "content/public/test/mock_notification_observer.h" 30#include "content/public/test/test_browser_thread.h" 31#include "testing/gmock/include/gmock/gmock.h" 32#include "testing/gtest/include/gtest/gtest.h" 33 34using base::WaitableEvent; 35using content::BrowserThread; 36using testing::_; 37using testing::DoAll; 38using testing::ElementsAreArray; 39using testing::Pointee; 40using testing::Property; 41using testing::WithArg; 42using content::PasswordForm; 43 44typedef std::vector<PasswordForm*> VectorOfForms; 45 46namespace { 47 48class MockPasswordStoreConsumer : public PasswordStoreConsumer { 49 public: 50 MOCK_METHOD2(OnPasswordStoreRequestDone, 51 void(CancelableRequestProvider::Handle, 52 const std::vector<PasswordForm*>&)); 53 MOCK_METHOD1(OnGetPasswordStoreResults, 54 void(const std::vector<PasswordForm*>&)); 55}; 56 57// This class will add and remove a mock notification observer from 58// the DB thread. 59class DBThreadObserverHelper 60 : public base::RefCountedThreadSafe<DBThreadObserverHelper, 61 BrowserThread::DeleteOnDBThread> { 62 public: 63 DBThreadObserverHelper() : done_event_(true, false) {} 64 65 void Init(PasswordStore* password_store) { 66 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 67 BrowserThread::PostTask( 68 BrowserThread::DB, 69 FROM_HERE, 70 base::Bind(&DBThreadObserverHelper::AddObserverTask, 71 this, make_scoped_refptr(password_store))); 72 done_event_.Wait(); 73 } 74 75 content::MockNotificationObserver& observer() { 76 return observer_; 77 } 78 79 protected: 80 friend struct BrowserThread::DeleteOnThread<BrowserThread::DB>; 81 friend class base::DeleteHelper<DBThreadObserverHelper>; 82 83 virtual ~DBThreadObserverHelper() { 84 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB)); 85 registrar_.RemoveAll(); 86 } 87 88 void AddObserverTask(PasswordStore* password_store) { 89 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB)); 90 registrar_.Add(&observer_, 91 chrome::NOTIFICATION_LOGINS_CHANGED, 92 content::Source<PasswordStore>(password_store)); 93 done_event_.Signal(); 94 } 95 96 WaitableEvent done_event_; 97 content::NotificationRegistrar registrar_; 98 content::MockNotificationObserver observer_; 99}; 100 101class FailingBackend : public PasswordStoreX::NativeBackend { 102 public: 103 virtual bool Init() OVERRIDE { return true; } 104 105 virtual bool AddLogin(const PasswordForm& form) OVERRIDE { return false; } 106 virtual bool UpdateLogin(const PasswordForm& form) OVERRIDE { return false; } 107 virtual bool RemoveLogin(const PasswordForm& form) OVERRIDE { return false; } 108 109 virtual bool RemoveLoginsCreatedBetween( 110 const base::Time& delete_begin, 111 const base::Time& delete_end) OVERRIDE { 112 return false; 113 } 114 115 virtual bool GetLogins(const PasswordForm& form, 116 PasswordFormList* forms) OVERRIDE { 117 return false; 118 } 119 120 virtual bool GetLoginsCreatedBetween(const base::Time& get_begin, 121 const base::Time& get_end, 122 PasswordFormList* forms) OVERRIDE { 123 return false; 124 } 125 126 virtual bool GetAutofillableLogins(PasswordFormList* forms) OVERRIDE { 127 return false; 128 } 129 virtual bool GetBlacklistLogins(PasswordFormList* forms) OVERRIDE { 130 return false; 131 } 132}; 133 134class MockBackend : public PasswordStoreX::NativeBackend { 135 public: 136 virtual bool Init() OVERRIDE { return true; } 137 138 virtual bool AddLogin(const PasswordForm& form) OVERRIDE { 139 all_forms_.push_back(form); 140 return true; 141 } 142 143 virtual bool UpdateLogin(const PasswordForm& form) OVERRIDE { 144 for (size_t i = 0; i < all_forms_.size(); ++i) 145 if (CompareForms(all_forms_[i], form, true)) 146 all_forms_[i] = form; 147 return true; 148 } 149 150 virtual bool RemoveLogin(const PasswordForm& form) OVERRIDE { 151 for (size_t i = 0; i < all_forms_.size(); ++i) 152 if (CompareForms(all_forms_[i], form, false)) 153 erase(i--); 154 return true; 155 } 156 157 virtual bool RemoveLoginsCreatedBetween( 158 const base::Time& delete_begin, 159 const base::Time& delete_end) OVERRIDE { 160 for (size_t i = 0; i < all_forms_.size(); ++i) { 161 if (delete_begin <= all_forms_[i].date_created && 162 (delete_end.is_null() || all_forms_[i].date_created < delete_end)) 163 erase(i--); 164 } 165 return true; 166 } 167 168 virtual bool GetLogins(const PasswordForm& form, 169 PasswordFormList* forms) OVERRIDE { 170 for (size_t i = 0; i < all_forms_.size(); ++i) 171 if (all_forms_[i].signon_realm == form.signon_realm) 172 forms->push_back(new PasswordForm(all_forms_[i])); 173 return true; 174 } 175 176 virtual bool GetLoginsCreatedBetween(const base::Time& get_begin, 177 const base::Time& get_end, 178 PasswordFormList* forms) OVERRIDE { 179 for (size_t i = 0; i < all_forms_.size(); ++i) 180 if (get_begin <= all_forms_[i].date_created && 181 (get_end.is_null() || all_forms_[i].date_created < get_end)) 182 forms->push_back(new PasswordForm(all_forms_[i])); 183 return true; 184 } 185 186 virtual bool GetAutofillableLogins(PasswordFormList* forms) OVERRIDE { 187 for (size_t i = 0; i < all_forms_.size(); ++i) 188 if (!all_forms_[i].blacklisted_by_user) 189 forms->push_back(new PasswordForm(all_forms_[i])); 190 return true; 191 } 192 193 virtual bool GetBlacklistLogins(PasswordFormList* forms) OVERRIDE { 194 for (size_t i = 0; i < all_forms_.size(); ++i) 195 if (all_forms_[i].blacklisted_by_user) 196 forms->push_back(new PasswordForm(all_forms_[i])); 197 return true; 198 } 199 200 private: 201 void erase(size_t index) { 202 if (index < all_forms_.size() - 1) 203 all_forms_[index] = all_forms_[all_forms_.size() - 1]; 204 all_forms_.pop_back(); 205 } 206 207 bool CompareForms(const PasswordForm& a, const PasswordForm& b, bool update) { 208 // An update check doesn't care about the submit element. 209 if (!update && a.submit_element != b.submit_element) 210 return false; 211 return a.origin == b.origin && 212 a.password_element == b.password_element && 213 a.signon_realm == b.signon_realm && 214 a.username_element == b.username_element && 215 a.username_value == b.username_value; 216 } 217 218 std::vector<PasswordForm> all_forms_; 219}; 220 221class MockLoginDatabaseReturn { 222 public: 223 MOCK_METHOD1(OnLoginDatabaseQueryDone, 224 void(const std::vector<PasswordForm*>&)); 225}; 226 227void LoginDatabaseQueryCallback(LoginDatabase* login_db, 228 bool autofillable, 229 MockLoginDatabaseReturn* mock_return) { 230 std::vector<PasswordForm*> forms; 231 if (autofillable) 232 login_db->GetAutofillableLogins(&forms); 233 else 234 login_db->GetBlacklistLogins(&forms); 235 mock_return->OnLoginDatabaseQueryDone(forms); 236} 237 238// Generate |count| expected logins, either auto-fillable or blacklisted. 239void InitExpectedForms(bool autofillable, size_t count, VectorOfForms* forms) { 240 const char* domain = autofillable ? "example" : "blacklisted"; 241 for (size_t i = 0; i < count; ++i) { 242 std::string realm = base::StringPrintf("http://%zu.%s.com", i, domain); 243 std::string origin = base::StringPrintf("http://%zu.%s.com/origin", 244 i, domain); 245 std::string action = base::StringPrintf("http://%zu.%s.com/action", 246 i, domain); 247 PasswordFormData data = { 248 PasswordForm::SCHEME_HTML, 249 realm.c_str(), 250 origin.c_str(), 251 action.c_str(), 252 L"submit_element", 253 L"username_element", 254 L"password_element", 255 autofillable ? L"username_value" : NULL, 256 autofillable ? L"password_value" : NULL, 257 autofillable, false, static_cast<double>(i + 1) }; 258 forms->push_back(CreatePasswordFormFromData(data)); 259 } 260} 261 262} // anonymous namespace 263 264enum BackendType { 265 NO_BACKEND, 266 FAILING_BACKEND, 267 WORKING_BACKEND 268}; 269 270class PasswordStoreXTest : public testing::TestWithParam<BackendType> { 271 protected: 272 PasswordStoreXTest() 273 : ui_thread_(BrowserThread::UI, &message_loop_), 274 db_thread_(BrowserThread::DB) { 275 } 276 277 virtual void SetUp() { 278 ASSERT_TRUE(db_thread_.Start()); 279 ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); 280 281 profile_.reset(new TestingProfile()); 282 283 login_db_.reset(new LoginDatabase()); 284 ASSERT_TRUE(login_db_->Init(temp_dir_.path().Append("login_test"))); 285 } 286 287 virtual void TearDown() { 288 base::MessageLoop::current()->PostTask(FROM_HERE, 289 base::MessageLoop::QuitClosure()); 290 base::MessageLoop::current()->Run(); 291 db_thread_.Stop(); 292 } 293 294 PasswordStoreX::NativeBackend* GetBackend() { 295 switch (GetParam()) { 296 case FAILING_BACKEND: 297 return new FailingBackend(); 298 case WORKING_BACKEND: 299 return new MockBackend(); 300 default: 301 return NULL; 302 } 303 } 304 305 base::MessageLoopForUI message_loop_; 306 content::TestBrowserThread ui_thread_; 307 // PasswordStore, WDS schedule work on this thread. 308 content::TestBrowserThread db_thread_; 309 310 scoped_ptr<LoginDatabase> login_db_; 311 scoped_ptr<TestingProfile> profile_; 312 base::ScopedTempDir temp_dir_; 313}; 314 315ACTION(STLDeleteElements0) { 316 STLDeleteContainerPointers(arg0.begin(), arg0.end()); 317} 318 319ACTION(QuitUIMessageLoop) { 320 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 321 base::MessageLoop::current()->Quit(); 322} 323 324TEST_P(PasswordStoreXTest, Notifications) { 325 scoped_refptr<PasswordStoreX> store( 326 new PasswordStoreX(login_db_.release(), 327 profile_.get(), 328 GetBackend())); 329 store->Init(); 330 331 PasswordFormData form_data = 332 { PasswordForm::SCHEME_HTML, 333 "http://bar.example.com", 334 "http://bar.example.com/origin", 335 "http://bar.example.com/action", 336 L"submit_element", 337 L"username_element", 338 L"password_element", 339 L"username_value", 340 L"password_value", 341 true, false, 1 }; 342 scoped_ptr<PasswordForm> form(CreatePasswordFormFromData(form_data)); 343 344 scoped_refptr<DBThreadObserverHelper> helper = new DBThreadObserverHelper; 345 helper->Init(store); 346 347 const PasswordStoreChange expected_add_changes[] = { 348 PasswordStoreChange(PasswordStoreChange::ADD, *form), 349 }; 350 351 EXPECT_CALL(helper->observer(), 352 Observe(int(chrome::NOTIFICATION_LOGINS_CHANGED), 353 content::Source<PasswordStore>(store), 354 Property(&content::Details<const PasswordStoreChangeList>::ptr, 355 Pointee(ElementsAreArray( 356 expected_add_changes))))); 357 358 // Adding a login should trigger a notification. 359 store->AddLogin(*form); 360 361 // The PasswordStore schedules tasks to run on the DB thread so we schedule 362 // yet another task to notify us that it's safe to carry on with the test. 363 WaitableEvent done(false, false); 364 BrowserThread::PostTask(BrowserThread::DB, FROM_HERE, 365 base::Bind(&WaitableEvent::Signal, base::Unretained(&done))); 366 done.Wait(); 367 368 // Change the password. 369 form->password_value = WideToUTF16(L"a different password"); 370 371 const PasswordStoreChange expected_update_changes[] = { 372 PasswordStoreChange(PasswordStoreChange::UPDATE, *form), 373 }; 374 375 EXPECT_CALL(helper->observer(), 376 Observe(int(chrome::NOTIFICATION_LOGINS_CHANGED), 377 content::Source<PasswordStore>(store), 378 Property(&content::Details<const PasswordStoreChangeList>::ptr, 379 Pointee(ElementsAreArray( 380 expected_update_changes))))); 381 382 // Updating the login with the new password should trigger a notification. 383 store->UpdateLogin(*form); 384 385 // Wait for PasswordStore to send the notification. 386 BrowserThread::PostTask(BrowserThread::DB, FROM_HERE, 387 base::Bind(&WaitableEvent::Signal, base::Unretained(&done))); 388 done.Wait(); 389 390 const PasswordStoreChange expected_delete_changes[] = { 391 PasswordStoreChange(PasswordStoreChange::REMOVE, *form), 392 }; 393 394 EXPECT_CALL(helper->observer(), 395 Observe(int(chrome::NOTIFICATION_LOGINS_CHANGED), 396 content::Source<PasswordStore>(store), 397 Property(&content::Details<const PasswordStoreChangeList>::ptr, 398 Pointee(ElementsAreArray( 399 expected_delete_changes))))); 400 401 // Deleting the login should trigger a notification. 402 store->RemoveLogin(*form); 403 404 // Wait for PasswordStore to send the notification. 405 BrowserThread::PostTask(BrowserThread::DB, FROM_HERE, 406 base::Bind(&WaitableEvent::Signal, base::Unretained(&done))); 407 done.Wait(); 408 409 // Public in PasswordStore, protected in PasswordStoreX. 410 static_cast<PasswordStore*>(store)->ShutdownOnUIThread(); 411} 412 413TEST_P(PasswordStoreXTest, NativeMigration) { 414 VectorOfForms expected_autofillable; 415 InitExpectedForms(true, 50, &expected_autofillable); 416 417 VectorOfForms expected_blacklisted; 418 InitExpectedForms(false, 50, &expected_blacklisted); 419 420 // Get the initial size of the login DB file, before we populate it. 421 // This will be used later to make sure it gets back to this size. 422 const base::FilePath login_db_file = temp_dir_.path().Append("login_test"); 423 base::PlatformFileInfo db_file_start_info; 424 ASSERT_TRUE(file_util::GetFileInfo(login_db_file, &db_file_start_info)); 425 426 LoginDatabase* login_db = login_db_.get(); 427 428 // Populate the login DB with logins that should be migrated. 429 for (VectorOfForms::iterator it = expected_autofillable.begin(); 430 it != expected_autofillable.end(); ++it) { 431 BrowserThread::PostTask(BrowserThread::DB, FROM_HERE, 432 base::Bind( 433 base::IgnoreResult(&LoginDatabase::AddLogin), 434 base::Unretained(login_db), **it)); 435 } 436 for (VectorOfForms::iterator it = expected_blacklisted.begin(); 437 it != expected_blacklisted.end(); ++it) { 438 BrowserThread::PostTask(BrowserThread::DB, FROM_HERE, 439 base::Bind( 440 base::IgnoreResult(&LoginDatabase::AddLogin), 441 base::Unretained(login_db), **it)); 442 } 443 444 // Schedule another task on the DB thread to notify us that it's safe to 445 // carry on with the test. 446 WaitableEvent done(false, false); 447 BrowserThread::PostTask(BrowserThread::DB, FROM_HERE, 448 base::Bind(&WaitableEvent::Signal, base::Unretained(&done))); 449 done.Wait(); 450 451 // Get the new size of the login DB file. We expect it to be larger. 452 base::PlatformFileInfo db_file_full_info; 453 ASSERT_TRUE(file_util::GetFileInfo(login_db_file, &db_file_full_info)); 454 EXPECT_GT(db_file_full_info.size, db_file_start_info.size); 455 456 // Initializing the PasswordStore shouldn't trigger a native migration (yet). 457 scoped_refptr<PasswordStoreX> store( 458 new PasswordStoreX(login_db_.release(), 459 profile_.get(), 460 GetBackend())); 461 store->Init(); 462 463 MockPasswordStoreConsumer consumer; 464 465 // Make sure we quit the MessageLoop even if the test fails. 466 ON_CALL(consumer, OnPasswordStoreRequestDone(_, _)) 467 .WillByDefault(QuitUIMessageLoop()); 468 469 // The autofillable forms should have been migrated to the native backend. 470 EXPECT_CALL(consumer, 471 OnPasswordStoreRequestDone(_, 472 ContainsAllPasswordForms(expected_autofillable))) 473 .WillOnce(DoAll(WithArg<1>(STLDeleteElements0()), QuitUIMessageLoop())); 474 475 store->GetAutofillableLogins(&consumer); 476 base::MessageLoop::current()->Run(); 477 478 // The blacklisted forms should have been migrated to the native backend. 479 EXPECT_CALL(consumer, 480 OnPasswordStoreRequestDone(_, 481 ContainsAllPasswordForms(expected_blacklisted))) 482 .WillOnce(DoAll(WithArg<1>(STLDeleteElements0()), QuitUIMessageLoop())); 483 484 store->GetBlacklistLogins(&consumer); 485 base::MessageLoop::current()->Run(); 486 487 VectorOfForms empty; 488 MockLoginDatabaseReturn ld_return; 489 490 if (GetParam() == WORKING_BACKEND) { 491 // No autofillable logins should be left in the login DB. 492 EXPECT_CALL(ld_return, 493 OnLoginDatabaseQueryDone(ContainsAllPasswordForms(empty))); 494 } else { 495 // The autofillable logins should still be in the login DB. 496 EXPECT_CALL(ld_return, 497 OnLoginDatabaseQueryDone( 498 ContainsAllPasswordForms(expected_autofillable))) 499 .WillOnce(WithArg<0>(STLDeleteElements0())); 500 } 501 502 BrowserThread::PostTask( 503 BrowserThread::DB, FROM_HERE, 504 base::Bind(&LoginDatabaseQueryCallback, login_db, true, &ld_return)); 505 506 // Wait for the login DB methods to execute on the DB thread. 507 BrowserThread::PostTask(BrowserThread::DB, FROM_HERE, 508 base::Bind(&WaitableEvent::Signal, base::Unretained(&done))); 509 done.Wait(); 510 511 if (GetParam() == WORKING_BACKEND) { 512 // Likewise, no blacklisted logins should be left in the login DB. 513 EXPECT_CALL(ld_return, 514 OnLoginDatabaseQueryDone(ContainsAllPasswordForms(empty))); 515 } else { 516 // The blacklisted logins should still be in the login DB. 517 EXPECT_CALL(ld_return, 518 OnLoginDatabaseQueryDone( 519 ContainsAllPasswordForms(expected_blacklisted))) 520 .WillOnce(WithArg<0>(STLDeleteElements0())); 521 } 522 523 BrowserThread::PostTask( 524 BrowserThread::DB, FROM_HERE, 525 base::Bind(&LoginDatabaseQueryCallback, login_db, false, &ld_return)); 526 527 // Wait for the login DB methods to execute on the DB thread. 528 BrowserThread::PostTask(BrowserThread::DB, FROM_HERE, 529 base::Bind(&WaitableEvent::Signal, base::Unretained(&done))); 530 done.Wait(); 531 532 if (GetParam() == WORKING_BACKEND) { 533 // If the migration succeeded, then not only should there be no logins left 534 // in the login DB, but also the file should have been deleted and then 535 // recreated. We approximate checking for this by checking that the file 536 // size is equal to the size before we populated it, even though it was 537 // larger after populating it. 538 base::PlatformFileInfo db_file_end_info; 539 ASSERT_TRUE(file_util::GetFileInfo(login_db_file, &db_file_end_info)); 540 EXPECT_EQ(db_file_start_info.size, db_file_end_info.size); 541 } 542 543 STLDeleteElements(&expected_autofillable); 544 STLDeleteElements(&expected_blacklisted); 545 546 // Public in PasswordStore, protected in PasswordStoreX. 547 static_cast<PasswordStore*>(store)->ShutdownOnUIThread(); 548} 549 550INSTANTIATE_TEST_CASE_P(NoBackend, 551 PasswordStoreXTest, 552 testing::Values(NO_BACKEND)); 553INSTANTIATE_TEST_CASE_P(FailingBackend, 554 PasswordStoreXTest, 555 testing::Values(FAILING_BACKEND)); 556INSTANTIATE_TEST_CASE_P(WorkingBackend, 557 PasswordStoreXTest, 558 testing::Values(WORKING_BACKEND)); 559