1 // Copyright (c) 2011 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 <windows.h> 6 #include <wincrypt.h> 7 #include <string> 8 #include <vector> 9 10 #include "base/memory/scoped_ptr.h" 11 #include "base/memory/scoped_temp_dir.h" 12 #include "base/message_loop.h" 13 #include "base/stl_util-inl.h" 14 #include "base/time.h" 15 #include "base/synchronization/waitable_event.h" 16 #include "chrome/browser/password_manager/password_form_data.h" 17 #include "chrome/browser/password_manager/password_store_consumer.h" 18 #include "chrome/browser/password_manager/password_store_win.h" 19 #include "chrome/browser/password_manager/ie7_password.h" 20 #include "chrome/browser/prefs/pref_service.h" 21 #include "chrome/browser/webdata/web_data_service.h" 22 #include "chrome/common/pref_names.h" 23 #include "chrome/test/signaling_task.h" 24 #include "chrome/test/testing_profile.h" 25 #include "content/browser/browser_thread.h" 26 #include "testing/gmock/include/gmock/gmock.h" 27 #include "testing/gtest/include/gtest/gtest.h" 28 29 using base::WaitableEvent; 30 using testing::_; 31 using testing::DoAll; 32 using testing::WithArg; 33 using webkit_glue::PasswordForm; 34 35 namespace { 36 37 class MockPasswordStoreConsumer : public PasswordStoreConsumer { 38 public: 39 MOCK_METHOD2(OnPasswordStoreRequestDone, 40 void(CancelableRequestProvider::Handle, 41 const std::vector<webkit_glue::PasswordForm*>&)); 42 }; 43 44 class MockWebDataServiceConsumer : public WebDataServiceConsumer { 45 public: 46 MOCK_METHOD2(OnWebDataServiceRequestDone, 47 void(WebDataService::Handle, const WDTypedResult*)); 48 }; 49 50 } // anonymous namespace 51 52 typedef std::vector<PasswordForm*> VectorOfForms; 53 54 class PasswordStoreWinTest : public testing::Test { 55 protected: 56 PasswordStoreWinTest() 57 : ui_thread_(BrowserThread::UI, &message_loop_), 58 db_thread_(BrowserThread::DB) { 59 } 60 61 bool CreateIE7PasswordInfo(const std::wstring& url, const base::Time& created, 62 IE7PasswordInfo* info) { 63 // Copied from chrome/browser/importer/importer_unittest.cc 64 // The username is "abcdefgh" and the password "abcdefghijkl". 65 unsigned char data[] = "\x0c\x00\x00\x00\x38\x00\x00\x00\x2c\x00\x00\x00" 66 "\x57\x49\x43\x4b\x18\x00\x00\x00\x02\x00\x00\x00" 67 "\x67\x00\x72\x00\x01\x00\x00\x00\x00\x00\x00\x00" 68 "\x00\x00\x00\x00\x4e\xfa\x67\x76\x22\x94\xc8\x01" 69 "\x08\x00\x00\x00\x12\x00\x00\x00\x4e\xfa\x67\x76" 70 "\x22\x94\xc8\x01\x0c\x00\x00\x00\x61\x00\x62\x00" 71 "\x63\x00\x64\x00\x65\x00\x66\x00\x67\x00\x68\x00" 72 "\x00\x00\x61\x00\x62\x00\x63\x00\x64\x00\x65\x00" 73 "\x66\x00\x67\x00\x68\x00\x69\x00\x6a\x00\x6b\x00" 74 "\x6c\x00\x00\x00"; 75 DATA_BLOB input = {0}; 76 DATA_BLOB url_key = {0}; 77 DATA_BLOB output = {0}; 78 79 input.pbData = data; 80 input.cbData = sizeof(data); 81 82 url_key.pbData = reinterpret_cast<unsigned char*>( 83 const_cast<wchar_t*>(url.data())); 84 url_key.cbData = static_cast<DWORD>((url.size() + 1) * 85 sizeof(std::wstring::value_type)); 86 87 if (!CryptProtectData(&input, NULL, &url_key, NULL, NULL, 88 CRYPTPROTECT_UI_FORBIDDEN, &output)) 89 return false; 90 91 std::vector<unsigned char> encrypted_data; 92 encrypted_data.resize(output.cbData); 93 memcpy(&encrypted_data.front(), output.pbData, output.cbData); 94 95 LocalFree(output.pbData); 96 97 info->url_hash = ie7_password::GetUrlHash(url); 98 info->encrypted_data = encrypted_data; 99 info->date_created = created; 100 101 return true; 102 } 103 104 virtual void SetUp() { 105 ASSERT_TRUE(db_thread_.Start()); 106 ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); 107 108 profile_.reset(new TestingProfile()); 109 110 login_db_.reset(new LoginDatabase()); 111 ASSERT_TRUE(login_db_->Init(temp_dir_.path().Append( 112 FILE_PATH_LITERAL("login_test")))); 113 114 wds_ = new WebDataService(); 115 ASSERT_TRUE(wds_->Init(temp_dir_.path())); 116 } 117 118 virtual void TearDown() { 119 if (wds_.get()) 120 wds_->Shutdown(); 121 MessageLoop::current()->PostTask(FROM_HERE, new MessageLoop::QuitTask); 122 MessageLoop::current()->Run(); 123 db_thread_.Stop(); 124 } 125 126 MessageLoopForUI message_loop_; 127 BrowserThread ui_thread_; 128 BrowserThread db_thread_; // PasswordStore, WDS schedule work on this thread. 129 130 scoped_ptr<LoginDatabase> login_db_; 131 scoped_ptr<TestingProfile> profile_; 132 scoped_refptr<WebDataService> wds_; 133 ScopedTempDir temp_dir_; 134 }; 135 136 ACTION(STLDeleteElements0) { 137 STLDeleteContainerPointers(arg0.begin(), arg0.end()); 138 } 139 140 ACTION(QuitUIMessageLoop) { 141 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 142 MessageLoop::current()->Quit(); 143 } 144 145 MATCHER(EmptyWDResult, "") { 146 return static_cast<const WDResult<std::vector<PasswordForm*> >*>( 147 arg)->GetValue().empty(); 148 } 149 150 // Hangs flakily, http://crbug.com/71385. 151 TEST_F(PasswordStoreWinTest, DISABLED_ConvertIE7Login) { 152 IE7PasswordInfo password_info; 153 ASSERT_TRUE(CreateIE7PasswordInfo(L"http://example.com/origin", 154 base::Time::FromDoubleT(1), 155 &password_info)); 156 // Verify the URL hash 157 ASSERT_EQ(L"39471418FF5453FEEB3731E382DEB5D53E14FAF9B5", 158 password_info.url_hash); 159 160 // This IE7 password will be retrieved by the GetLogins call. 161 wds_->AddIE7Login(password_info); 162 163 // The WDS schedules tasks to run on the DB thread so we schedule yet another 164 // task to notify us that it's safe to carry on with the test. 165 WaitableEvent done(false, false); 166 BrowserThread::PostTask(BrowserThread::DB, FROM_HERE, 167 new SignalingTask(&done)); 168 done.Wait(); 169 170 // Prentend that the migration has already taken place. 171 profile_->GetPrefs()->RegisterBooleanPref(prefs::kLoginDatabaseMigrated, 172 true); 173 174 // Initializing the PasswordStore shouldn't trigger a migration. 175 scoped_refptr<PasswordStore> store( 176 new PasswordStoreWin(login_db_.release(), profile_.get(), wds_.get())); 177 EXPECT_TRUE(store->Init()); 178 179 MockPasswordStoreConsumer consumer; 180 181 // Make sure we quit the MessageLoop even if the test fails. 182 ON_CALL(consumer, OnPasswordStoreRequestDone(_, _)) 183 .WillByDefault(QuitUIMessageLoop()); 184 185 PasswordFormData form_data = { 186 PasswordForm::SCHEME_HTML, 187 "http://example.com/", 188 "http://example.com/origin", 189 "http://example.com/action", 190 L"submit_element", 191 L"username_element", 192 L"password_element", 193 L"", 194 L"", 195 true, false, 1, 196 }; 197 scoped_ptr<PasswordForm> form(CreatePasswordFormFromData(form_data)); 198 199 PasswordFormData expected_form_data = { 200 PasswordForm::SCHEME_HTML, 201 "http://example.com/", 202 "http://example.com/origin", 203 "http://example.com/action", 204 L"submit_element", 205 L"username_element", 206 L"password_element", 207 L"abcdefgh", 208 L"abcdefghijkl", 209 true, false, 1, 210 }; 211 std::vector<PasswordForm*> forms; 212 forms.push_back(CreatePasswordFormFromData(expected_form_data)); 213 214 // The IE7 password should be returned. 215 EXPECT_CALL(consumer, 216 OnPasswordStoreRequestDone(_, 217 ContainsAllPasswordForms(forms))) 218 .WillOnce(QuitUIMessageLoop()); 219 220 store->GetLogins(*form, &consumer); 221 MessageLoop::current()->Run(); 222 223 STLDeleteElements(&forms); 224 225 store->Shutdown(); 226 } 227 228 TEST_F(PasswordStoreWinTest, OutstandingWDSQueries) { 229 // Prentend that the migration has already taken place. 230 profile_->GetPrefs()->RegisterBooleanPref(prefs::kLoginDatabaseMigrated, 231 true); 232 233 // Initializing the PasswordStore shouldn't trigger a migration. 234 scoped_refptr<PasswordStore> store( 235 new PasswordStoreWin(login_db_.release(), profile_.get(), wds_.get())); 236 EXPECT_TRUE(store->Init()); 237 238 PasswordFormData form_data = { 239 PasswordForm::SCHEME_HTML, 240 "http://example.com/", 241 "http://example.com/origin", 242 "http://example.com/action", 243 L"submit_element", 244 L"username_element", 245 L"password_element", 246 L"", 247 L"", 248 true, false, 1, 249 }; 250 scoped_ptr<PasswordForm> form(CreatePasswordFormFromData(form_data)); 251 252 MockPasswordStoreConsumer consumer; 253 store->GetLogins(*form, &consumer); 254 255 // Release the PSW and the WDS before the query can return. 256 store->Shutdown(); 257 store = NULL; 258 wds_->Shutdown(); 259 wds_ = NULL; 260 261 MessageLoop::current()->RunAllPending(); 262 } 263 264 // Hangs flakily, see http://crbug.com/43836. 265 TEST_F(PasswordStoreWinTest, DISABLED_MultipleWDSQueriesOnDifferentThreads) { 266 IE7PasswordInfo password_info; 267 ASSERT_TRUE(CreateIE7PasswordInfo(L"http://example.com/origin", 268 base::Time::FromDoubleT(1), 269 &password_info)); 270 wds_->AddIE7Login(password_info); 271 272 // The WDS schedules tasks to run on the DB thread so we schedule yet another 273 // task to notify us that it's safe to carry on with the test. 274 WaitableEvent done(false, false); 275 BrowserThread::PostTask(BrowserThread::DB, FROM_HERE, 276 new SignalingTask(&done)); 277 done.Wait(); 278 279 // Prentend that the migration has already taken place. 280 profile_->GetPrefs()->RegisterBooleanPref(prefs::kLoginDatabaseMigrated, 281 true); 282 283 // Initializing the PasswordStore shouldn't trigger a migration. 284 scoped_refptr<PasswordStore> store( 285 new PasswordStoreWin(login_db_.release(), profile_.get(), wds_.get())); 286 EXPECT_TRUE(store->Init()); 287 288 MockPasswordStoreConsumer password_consumer; 289 // Make sure we quit the MessageLoop even if the test fails. 290 ON_CALL(password_consumer, OnPasswordStoreRequestDone(_, _)) 291 .WillByDefault(QuitUIMessageLoop()); 292 293 PasswordFormData form_data = { 294 PasswordForm::SCHEME_HTML, 295 "http://example.com/", 296 "http://example.com/origin", 297 "http://example.com/action", 298 L"submit_element", 299 L"username_element", 300 L"password_element", 301 L"", 302 L"", 303 true, false, 1, 304 }; 305 scoped_ptr<PasswordForm> form(CreatePasswordFormFromData(form_data)); 306 307 PasswordFormData expected_form_data = { 308 PasswordForm::SCHEME_HTML, 309 "http://example.com/", 310 "http://example.com/origin", 311 "http://example.com/action", 312 L"submit_element", 313 L"username_element", 314 L"password_element", 315 L"abcdefgh", 316 L"abcdefghijkl", 317 true, false, 1, 318 }; 319 std::vector<PasswordForm*> forms; 320 forms.push_back(CreatePasswordFormFromData(expected_form_data)); 321 322 // The IE7 password should be returned. 323 EXPECT_CALL(password_consumer, 324 OnPasswordStoreRequestDone(_, 325 ContainsAllPasswordForms(forms))) 326 .WillOnce(QuitUIMessageLoop()); 327 328 store->GetLogins(*form, &password_consumer); 329 330 MockWebDataServiceConsumer wds_consumer; 331 332 EXPECT_CALL(wds_consumer, 333 OnWebDataServiceRequestDone(_, _)) 334 .WillOnce(QuitUIMessageLoop()); 335 336 wds_->GetIE7Login(password_info, &wds_consumer); 337 338 // Run the MessageLoop twice: once for the GetIE7Login that PasswordStoreWin 339 // schedules on the DB thread and once for the one we just scheduled on the UI 340 // thread. 341 MessageLoop::current()->Run(); 342 MessageLoop::current()->Run(); 343 344 STLDeleteElements(&forms); 345 346 store->Shutdown(); 347 } 348 349 TEST_F(PasswordStoreWinTest, Migration) { 350 PasswordFormData autofillable_data[] = { 351 { PasswordForm::SCHEME_HTML, 352 "http://foo.example.com", 353 "http://foo.example.com/origin", 354 "http://foo.example.com/action", 355 L"submit_element", 356 L"username_element", 357 L"password_element", 358 L"username_value", 359 L"password_value", 360 true, false, 1 }, 361 { PasswordForm::SCHEME_HTML, 362 "http://bar.example.com", 363 "http://bar.example.com/origin", 364 "http://bar.example.com/action", 365 L"submit_element", 366 L"username_element", 367 L"password_element", 368 L"username_value", 369 L"password_value", 370 true, false, 2 }, 371 { PasswordForm::SCHEME_HTML, 372 "http://baz.example.com", 373 "http://baz.example.com/origin", 374 "http://baz.example.com/action", 375 L"submit_element", 376 L"username_element", 377 L"password_element", 378 L"username_value", 379 L"password_value", 380 true, false, 3 }, 381 }; 382 PasswordFormData blacklisted_data[] = { 383 { PasswordForm::SCHEME_HTML, 384 "http://blacklisted.example.com", 385 "http://blacklisted.example.com/origin", 386 "http://blacklisted.example.com/action", 387 L"submit_element", 388 L"username_element", 389 L"password_element", 390 NULL, 391 NULL, 392 false, false, 1 }, 393 { PasswordForm::SCHEME_HTML, 394 "http://blacklisted2.example.com", 395 "http://blacklisted2.example.com/origin", 396 "http://blacklisted2.example.com/action", 397 L"submit_element", 398 L"username_element", 399 L"password_element", 400 NULL, 401 NULL, 402 false, false, 2 }, 403 }; 404 405 VectorOfForms expected_autofillable; 406 for (unsigned int i = 0; i < ARRAYSIZE_UNSAFE(autofillable_data); ++i) { 407 expected_autofillable.push_back( 408 CreatePasswordFormFromData(autofillable_data[i])); 409 } 410 411 VectorOfForms expected_blacklisted; 412 for (unsigned int i = 0; i < ARRAYSIZE_UNSAFE(blacklisted_data); ++i) { 413 expected_blacklisted.push_back( 414 CreatePasswordFormFromData(blacklisted_data[i])); 415 } 416 417 // Populate the WDS with logins that should be migrated. 418 for (VectorOfForms::iterator it = expected_autofillable.begin(); 419 it != expected_autofillable.end(); ++it) { 420 wds_->AddLogin(**it); 421 } 422 for (VectorOfForms::iterator it = expected_blacklisted.begin(); 423 it != expected_blacklisted.end(); ++it) { 424 wds_->AddLogin(**it); 425 } 426 427 // The WDS schedules tasks to run on the DB thread so we schedule yet another 428 // task to notify us that it's safe to carry on with the test. 429 WaitableEvent done(false, false); 430 BrowserThread::PostTask(BrowserThread::DB, FROM_HERE, 431 new SignalingTask(&done)); 432 done.Wait(); 433 434 // Initializing the PasswordStore should trigger a migration. 435 scoped_refptr<PasswordStore> store( 436 new PasswordStoreWin(login_db_.release(), profile_.get(), wds_.get())); 437 store->Init(); 438 439 // Check that the migration preference has not been initialized; 440 ASSERT_TRUE(NULL == profile_->GetPrefs()->FindPreference( 441 prefs::kLoginDatabaseMigrated)); 442 443 // Again, the WDS schedules tasks to run on the DB thread, so schedule a task 444 // to signal us when it is safe to continue. 445 BrowserThread::PostTask(BrowserThread::DB, FROM_HERE, 446 new SignalingTask(&done)); 447 done.Wait(); 448 449 // Let the WDS callbacks proceed so the logins can be migrated. 450 MessageLoop::current()->RunAllPending(); 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 from the WDS to the login 459 // database. 460 EXPECT_CALL(consumer, 461 OnPasswordStoreRequestDone(_, 462 ContainsAllPasswordForms(expected_autofillable))) 463 .WillOnce(DoAll(WithArg<1>(STLDeleteElements0()), QuitUIMessageLoop())); 464 465 store->GetAutofillableLogins(&consumer); 466 MessageLoop::current()->Run(); 467 468 // The blacklisted forms should have been migrated from the WDS to the login 469 // database. 470 EXPECT_CALL(consumer, 471 OnPasswordStoreRequestDone(_, 472 ContainsAllPasswordForms(expected_blacklisted))) 473 .WillOnce(DoAll(WithArg<1>(STLDeleteElements0()), QuitUIMessageLoop())); 474 475 store->GetBlacklistLogins(&consumer); 476 MessageLoop::current()->Run(); 477 478 // Check that the migration updated the migrated preference. 479 ASSERT_TRUE(profile_->GetPrefs()->GetBoolean(prefs::kLoginDatabaseMigrated)); 480 481 MockWebDataServiceConsumer wds_consumer; 482 483 // No autofillable logins should be left in the WDS. 484 EXPECT_CALL(wds_consumer, 485 OnWebDataServiceRequestDone(_, EmptyWDResult())); 486 487 wds_->GetAutofillableLogins(&wds_consumer); 488 489 // Wait for the WDS methods to execute on the DB thread. 490 BrowserThread::PostTask(BrowserThread::DB, FROM_HERE, 491 new SignalingTask(&done)); 492 done.Wait(); 493 494 // Handle the callback from the WDS. 495 MessageLoop::current()->RunAllPending(); 496 497 // Likewise, no blacklisted logins should be left in the WDS. 498 EXPECT_CALL(wds_consumer, 499 OnWebDataServiceRequestDone(_, EmptyWDResult())); 500 501 wds_->GetBlacklistLogins(&wds_consumer); 502 503 // Wait for the WDS methods to execute on the DB thread. 504 BrowserThread::PostTask(BrowserThread::DB, FROM_HERE, 505 new SignalingTask(&done)); 506 done.Wait(); 507 508 // Handle the callback from the WDS. 509 MessageLoop::current()->RunAllPending(); 510 511 STLDeleteElements(&expected_autofillable); 512 STLDeleteElements(&expected_blacklisted); 513 } 514 515 TEST_F(PasswordStoreWinTest, EmptyLogins) { 516 scoped_refptr<PasswordStore> store( 517 new PasswordStoreWin(login_db_.release(), profile_.get(), wds_.get())); 518 store->Init(); 519 520 PasswordFormData form_data = { 521 PasswordForm::SCHEME_HTML, 522 "http://example.com/", 523 "http://example.com/origin", 524 "http://example.com/action", 525 L"submit_element", 526 L"username_element", 527 L"password_element", 528 L"", 529 L"", 530 true, false, 1, 531 }; 532 scoped_ptr<PasswordForm> form(CreatePasswordFormFromData(form_data)); 533 534 MockPasswordStoreConsumer consumer; 535 536 // Make sure we quit the MessageLoop even if the test fails. 537 ON_CALL(consumer, OnPasswordStoreRequestDone(_, _)) 538 .WillByDefault(QuitUIMessageLoop()); 539 540 VectorOfForms expect_none; 541 // expect that we get no results; 542 EXPECT_CALL(consumer, OnPasswordStoreRequestDone( 543 _, ContainsAllPasswordForms(expect_none))) 544 .WillOnce(DoAll(WithArg<1>(STLDeleteElements0()), QuitUIMessageLoop())); 545 546 store->GetLogins(*form, &consumer); 547 MessageLoop::current()->Run(); 548 549 store->Shutdown(); 550 } 551 552 TEST_F(PasswordStoreWinTest, EmptyBlacklistLogins) { 553 scoped_refptr<PasswordStore> store( 554 new PasswordStoreWin(login_db_.release(), profile_.get(), wds_.get())); 555 store->Init(); 556 557 MockPasswordStoreConsumer consumer; 558 559 // Make sure we quit the MessageLoop even if the test fails. 560 ON_CALL(consumer, OnPasswordStoreRequestDone(_, _)) 561 .WillByDefault(QuitUIMessageLoop()); 562 563 VectorOfForms expect_none; 564 // expect that we get no results; 565 EXPECT_CALL(consumer, OnPasswordStoreRequestDone( 566 _, ContainsAllPasswordForms(expect_none))) 567 .WillOnce(DoAll(WithArg<1>(STLDeleteElements0()), QuitUIMessageLoop())); 568 569 store->GetBlacklistLogins(&consumer); 570 MessageLoop::current()->Run(); 571 572 store->Shutdown(); 573 } 574 575 TEST_F(PasswordStoreWinTest, EmptyAutofillableLogins) { 576 scoped_refptr<PasswordStore> store( 577 new PasswordStoreWin(login_db_.release(), profile_.get(), wds_.get())); 578 store->Init(); 579 580 MockPasswordStoreConsumer consumer; 581 582 // Make sure we quit the MessageLoop even if the test fails. 583 ON_CALL(consumer, OnPasswordStoreRequestDone(_, _)) 584 .WillByDefault(QuitUIMessageLoop()); 585 586 VectorOfForms expect_none; 587 // expect that we get no results; 588 EXPECT_CALL(consumer, OnPasswordStoreRequestDone( 589 _, ContainsAllPasswordForms(expect_none))) 590 .WillOnce(DoAll(WithArg<1>(STLDeleteElements0()), QuitUIMessageLoop())); 591 592 store->GetAutofillableLogins(&consumer); 593 MessageLoop::current()->Run(); 594 595 store->Shutdown(); 596 } 597