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