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