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