Home | History | Annotate | Download | only in password_manager
      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 "base/basictypes.h"
      6 #include "base/memory/scoped_temp_dir.h"
      7 #include "base/stl_util-inl.h"
      8 #include "base/string_util.h"
      9 #include "base/time.h"
     10 #include "base/utf_string_conversions.h"
     11 #include "base/synchronization/waitable_event.h"
     12 #include "chrome/browser/password_manager/password_store_change.h"
     13 #include "chrome/browser/password_manager/password_store_consumer.h"
     14 #include "chrome/browser/password_manager/password_store_default.h"
     15 #include "chrome/browser/password_manager/password_form_data.h"
     16 #include "chrome/browser/prefs/pref_service.h"
     17 #include "chrome/browser/webdata/web_data_service.h"
     18 #include "chrome/common/pref_names.h"
     19 #include "chrome/test/signaling_task.h"
     20 #include "chrome/test/testing_profile.h"
     21 #include "content/common/notification_details.h"
     22 #include "content/common/notification_observer_mock.h"
     23 #include "content/common/notification_registrar.h"
     24 #include "content/common/notification_source.h"
     25 #include "testing/gmock/include/gmock/gmock.h"
     26 #include "testing/gtest/include/gtest/gtest.h"
     27 
     28 using base::WaitableEvent;
     29 using testing::_;
     30 using testing::DoAll;
     31 using testing::ElementsAreArray;
     32 using testing::Pointee;
     33 using testing::Property;
     34 using testing::WithArg;
     35 using webkit_glue::PasswordForm;
     36 
     37 namespace {
     38 
     39 class MockPasswordStoreConsumer : public PasswordStoreConsumer {
     40  public:
     41   MOCK_METHOD2(OnPasswordStoreRequestDone,
     42                void(CancelableRequestProvider::Handle,
     43                     const std::vector<webkit_glue::PasswordForm*>&));
     44 };
     45 
     46 class MockWebDataServiceConsumer : public WebDataServiceConsumer {
     47  public:
     48   MOCK_METHOD2(OnWebDataServiceRequestDone, void(WebDataService::Handle,
     49                                                  const WDTypedResult*));
     50 };
     51 
     52 // This class will add and remove a mock notification observer from
     53 // the DB thread.
     54 class DBThreadObserverHelper :
     55     public base::RefCountedThreadSafe<DBThreadObserverHelper,
     56                                       BrowserThread::DeleteOnDBThread> {
     57  public:
     58   DBThreadObserverHelper() : done_event_(true, false) {}
     59 
     60   void Init(PasswordStore* password_store) {
     61     DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
     62     BrowserThread::PostTask(
     63         BrowserThread::DB,
     64         FROM_HERE,
     65         NewRunnableMethod(this,
     66                           &DBThreadObserverHelper::AddObserverTask,
     67                           make_scoped_refptr(password_store)));
     68     done_event_.Wait();
     69   }
     70 
     71   virtual ~DBThreadObserverHelper() {
     72     DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB));
     73     registrar_.RemoveAll();
     74   }
     75 
     76   NotificationObserverMock& observer() {
     77     return observer_;
     78   }
     79 
     80  protected:
     81   friend class base::RefCountedThreadSafe<DBThreadObserverHelper>;
     82 
     83   void AddObserverTask(PasswordStore* password_store) {
     84     DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB));
     85     registrar_.Add(&observer_,
     86                    NotificationType::LOGINS_CHANGED,
     87                    Source<PasswordStore>(password_store));
     88     done_event_.Signal();
     89   }
     90 
     91   WaitableEvent done_event_;
     92   NotificationRegistrar registrar_;
     93   NotificationObserverMock observer_;
     94 };
     95 
     96 }  // anonymous namespace
     97 
     98 typedef std::vector<PasswordForm*> VectorOfForms;
     99 
    100 class PasswordStoreDefaultTest : public testing::Test {
    101  protected:
    102   PasswordStoreDefaultTest()
    103       : ui_thread_(BrowserThread::UI, &message_loop_),
    104         db_thread_(BrowserThread::DB) {
    105   }
    106 
    107   virtual void SetUp() {
    108     ASSERT_TRUE(db_thread_.Start());
    109     ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
    110 
    111     profile_.reset(new TestingProfile());
    112 
    113     login_db_.reset(new LoginDatabase());
    114     ASSERT_TRUE(login_db_->Init(temp_dir_.path().Append(
    115         FILE_PATH_LITERAL("login_test"))));
    116 
    117     wds_ = new WebDataService();
    118     ASSERT_TRUE(wds_->Init(temp_dir_.path()));
    119   }
    120 
    121   virtual void TearDown() {
    122     wds_->Shutdown();
    123     MessageLoop::current()->PostTask(FROM_HERE, new MessageLoop::QuitTask);
    124     MessageLoop::current()->Run();
    125     db_thread_.Stop();
    126   }
    127 
    128   MessageLoopForUI message_loop_;
    129   BrowserThread ui_thread_;
    130   BrowserThread db_thread_;  // PasswordStore, WDS schedule work on this thread.
    131 
    132   scoped_ptr<LoginDatabase> login_db_;
    133   scoped_ptr<TestingProfile> profile_;
    134   scoped_refptr<WebDataService> wds_;
    135   ScopedTempDir temp_dir_;
    136 };
    137 
    138 ACTION(STLDeleteElements0) {
    139   STLDeleteContainerPointers(arg0.begin(), arg0.end());
    140 }
    141 
    142 ACTION(QuitUIMessageLoop) {
    143   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    144   MessageLoop::current()->Quit();
    145 }
    146 
    147 MATCHER(EmptyWDResult, "") {
    148   return static_cast<const WDResult<std::vector<PasswordForm*> >*>(
    149       arg)->GetValue().empty();
    150 }
    151 
    152 TEST_F(PasswordStoreDefaultTest, NonASCIIData) {
    153   // Prentend that the migration has already taken place.
    154   profile_->GetPrefs()->RegisterBooleanPref(prefs::kLoginDatabaseMigrated,
    155                                             true);
    156 
    157   // Initializing the PasswordStore shouldn't trigger a migration.
    158   scoped_refptr<PasswordStoreDefault> store(
    159       new PasswordStoreDefault(login_db_.release(), profile_.get(),
    160                                wds_.get()));
    161   store->Init();
    162 
    163   // Some non-ASCII password form data.
    164   PasswordFormData form_data[] = {
    165     { PasswordForm::SCHEME_HTML,
    166       "http://foo.example.com",
    167       "http://foo.example.com/origin",
    168       "http://foo.example.com/action",
    169       L"",
    170       L"?",
    171       L"",
    172       L" ",
    173       L"",
    174       true, false, 1 },
    175   };
    176 
    177   // Build the expected forms vector and add the forms to the store.
    178   VectorOfForms expected_forms;
    179   for (unsigned int i = 0; i < ARRAYSIZE_UNSAFE(form_data); ++i) {
    180     PasswordForm* form = CreatePasswordFormFromData(form_data[i]);
    181     expected_forms.push_back(form);
    182     store->AddLogin(*form);
    183   }
    184 
    185   // The PasswordStore schedules tasks to run on the DB thread so we schedule
    186   // yet another task to notify us that it's safe to carry on with the test.
    187   WaitableEvent done(false, false);
    188   BrowserThread::PostTask(BrowserThread::DB, FROM_HERE,
    189       new SignalingTask(&done));
    190   done.Wait();
    191 
    192   MockPasswordStoreConsumer consumer;
    193 
    194   // Make sure we quit the MessageLoop even if the test fails.
    195   ON_CALL(consumer, OnPasswordStoreRequestDone(_, _))
    196       .WillByDefault(QuitUIMessageLoop());
    197 
    198   // We expect to get the same data back, even though it's not all ASCII.
    199   EXPECT_CALL(consumer,
    200       OnPasswordStoreRequestDone(_,
    201           ContainsAllPasswordForms(expected_forms)))
    202       .WillOnce(DoAll(WithArg<1>(STLDeleteElements0()), QuitUIMessageLoop()));
    203 
    204   store->GetAutofillableLogins(&consumer);
    205   MessageLoop::current()->Run();
    206 
    207   STLDeleteElements(&expected_forms);
    208 }
    209 
    210 TEST_F(PasswordStoreDefaultTest, Migration) {
    211   PasswordFormData autofillable_data[] = {
    212     { PasswordForm::SCHEME_HTML,
    213       "http://foo.example.com",
    214       "http://foo.example.com/origin",
    215       "http://foo.example.com/action",
    216       L"submit_element",
    217       L"username_element",
    218       L"password_element",
    219       L"username_value",
    220       L"password_value",
    221       true, false, 1 },
    222     { PasswordForm::SCHEME_HTML,
    223       "http://bar.example.com",
    224       "http://bar.example.com/origin",
    225       "http://bar.example.com/action",
    226       L"submit_element",
    227       L"username_element",
    228       L"password_element",
    229       L"username_value",
    230       L"password_value",
    231       true, false, 2 },
    232     { PasswordForm::SCHEME_HTML,
    233       "http://baz.example.com",
    234       "http://baz.example.com/origin",
    235       "http://baz.example.com/action",
    236       L"submit_element",
    237       L"username_element",
    238       L"password_element",
    239       L"username_value",
    240       L"password_value",
    241       true, false, 3 },
    242   };
    243   PasswordFormData blacklisted_data[] = {
    244     { PasswordForm::SCHEME_HTML,
    245       "http://blacklisted.example.com",
    246       "http://blacklisted.example.com/origin",
    247       "http://blacklisted.example.com/action",
    248       L"submit_element",
    249       L"username_element",
    250       L"password_element",
    251       NULL,
    252       NULL,
    253       false, false, 1 },
    254     { PasswordForm::SCHEME_HTML,
    255       "http://blacklisted2.example.com",
    256       "http://blacklisted2.example.com/origin",
    257       "http://blacklisted2.example.com/action",
    258       L"submit_element",
    259       L"username_element",
    260       L"password_element",
    261       NULL,
    262       NULL,
    263       false, false, 2 },
    264   };
    265 
    266   // Build the expected forms vectors and populate the WDS with logins.
    267   VectorOfForms expected_autofillable;
    268   for (unsigned int i = 0; i < ARRAYSIZE_UNSAFE(autofillable_data); ++i) {
    269     PasswordForm* form = CreatePasswordFormFromData(autofillable_data[i]);
    270     expected_autofillable.push_back(form);
    271     wds_->AddLogin(*form);
    272   }
    273   VectorOfForms expected_blacklisted;
    274   for (unsigned int i = 0; i < ARRAYSIZE_UNSAFE(blacklisted_data); ++i) {
    275     PasswordForm* form = CreatePasswordFormFromData(blacklisted_data[i]);
    276     expected_blacklisted.push_back(form);
    277     wds_->AddLogin(*form);
    278   }
    279 
    280   // The WDS schedules tasks to run on the DB thread so we schedule yet another
    281   // task to notify us that it's safe to carry on with the test.
    282   WaitableEvent done(false, false);
    283   BrowserThread::PostTask(BrowserThread::DB, FROM_HERE,
    284       new SignalingTask(&done));
    285   done.Wait();
    286 
    287   // Initializing the PasswordStore should trigger a migration.
    288   scoped_refptr<PasswordStore> store(
    289       new PasswordStoreDefault(login_db_.release(),
    290           profile_.get(), wds_.get()));
    291   store->Init();
    292 
    293   // Check that the migration preference has not been initialized;
    294   ASSERT_TRUE(NULL == profile_->GetPrefs()->FindPreference(
    295       prefs::kLoginDatabaseMigrated));
    296 
    297   // Again, the WDS schedules tasks to run on the DB thread, so schedule a task
    298   // to signal us when it is safe to continue.
    299   BrowserThread::PostTask(BrowserThread::DB, FROM_HERE,
    300       new SignalingTask(&done));
    301   done.Wait();
    302 
    303   // Let the WDS callbacks proceed so the logins can be migrated.
    304   MessageLoop::current()->RunAllPending();
    305 
    306   MockPasswordStoreConsumer consumer;
    307 
    308   // Make sure we quit the MessageLoop even if the test fails.
    309   ON_CALL(consumer, OnPasswordStoreRequestDone(_, _))
    310       .WillByDefault(QuitUIMessageLoop());
    311 
    312   // The autofillable forms should have been migrated from the WDS to the login
    313   // database.
    314   EXPECT_CALL(consumer,
    315       OnPasswordStoreRequestDone(_,
    316           ContainsAllPasswordForms(expected_autofillable)))
    317       .WillOnce(DoAll(WithArg<1>(STLDeleteElements0()), QuitUIMessageLoop()));
    318 
    319   store->GetAutofillableLogins(&consumer);
    320   MessageLoop::current()->Run();
    321 
    322   // The blacklisted forms should have been migrated from the WDS to the login
    323   // database.
    324   EXPECT_CALL(consumer,
    325       OnPasswordStoreRequestDone(_,
    326           ContainsAllPasswordForms(expected_blacklisted)))
    327       .WillOnce(DoAll(WithArg<1>(STLDeleteElements0()), QuitUIMessageLoop()));
    328 
    329   store->GetBlacklistLogins(&consumer);
    330   MessageLoop::current()->Run();
    331 
    332   // Check that the migration updated the migrated preference.
    333   ASSERT_TRUE(profile_->GetPrefs()->GetBoolean(prefs::kLoginDatabaseMigrated));
    334 
    335   MockWebDataServiceConsumer wds_consumer;
    336 
    337   // No autofillable logins should be left in the WDS.
    338   EXPECT_CALL(wds_consumer,
    339       OnWebDataServiceRequestDone(_, EmptyWDResult()));
    340 
    341   wds_->GetAutofillableLogins(&wds_consumer);
    342 
    343   // Wait for the WDS methods to execute on the DB thread.
    344   BrowserThread::PostTask(BrowserThread::DB, FROM_HERE,
    345       new SignalingTask(&done));
    346   done.Wait();
    347 
    348   // Handle the callback from the WDS.
    349   MessageLoop::current()->RunAllPending();
    350 
    351   // Likewise, no blacklisted logins should be left in the WDS.
    352   EXPECT_CALL(wds_consumer,
    353       OnWebDataServiceRequestDone(_, EmptyWDResult()));
    354 
    355   wds_->GetBlacklistLogins(&wds_consumer);
    356 
    357   // Wait for the WDS methods to execute on the DB thread.
    358   BrowserThread::PostTask(BrowserThread::DB, FROM_HERE,
    359       new SignalingTask(&done));
    360   done.Wait();
    361 
    362   // Handle the callback from the WDS.
    363   MessageLoop::current()->RunAllPending();
    364 
    365   STLDeleteElements(&expected_autofillable);
    366   STLDeleteElements(&expected_blacklisted);
    367 
    368   store->Shutdown();
    369 }
    370 
    371 TEST_F(PasswordStoreDefaultTest, MigrationAlreadyDone) {
    372   PasswordFormData wds_data[] = {
    373     { PasswordForm::SCHEME_HTML,
    374       "http://bar.example.com",
    375       "http://bar.example.com/origin",
    376       "http://bar.example.com/action",
    377       L"submit_element",
    378       L"username_element",
    379       L"password_element",
    380       L"username_value",
    381       L"password_value",
    382       true, false, 1 },
    383   };
    384 
    385   // Build the expected forms vector and populate the WDS with logins.
    386   VectorOfForms unexpected_autofillable;
    387   for (unsigned int i = 0; i < ARRAYSIZE_UNSAFE(wds_data); ++i) {
    388     PasswordForm* form = CreatePasswordFormFromData(wds_data[i]);
    389     unexpected_autofillable.push_back(form);
    390     wds_->AddLogin(*form);
    391   }
    392 
    393   // The WDS schedules tasks to run on the DB thread so we schedule yet another
    394   // task to notify us that it's safe to carry on with the test.
    395   WaitableEvent done(false, false);
    396   BrowserThread::PostTask(BrowserThread::DB, FROM_HERE,
    397       new SignalingTask(&done));
    398   done.Wait();
    399 
    400   // Prentend that the migration has already taken place.
    401   profile_->GetPrefs()->RegisterBooleanPref(prefs::kLoginDatabaseMigrated,
    402                                             true);
    403 
    404   // Initializing the PasswordStore shouldn't trigger a migration.
    405   scoped_refptr<PasswordStore> store(
    406       new PasswordStoreDefault(login_db_.release(), profile_.get(),
    407                                wds_.get()));
    408   store->Init();
    409 
    410   MockPasswordStoreConsumer consumer;
    411   // Make sure we quit the MessageLoop even if the test fails.
    412   ON_CALL(consumer, OnPasswordStoreRequestDone(_, _))
    413       .WillByDefault(QuitUIMessageLoop());
    414 
    415   // No forms should be migrated.
    416   VectorOfForms empty;
    417   EXPECT_CALL(consumer,
    418       OnPasswordStoreRequestDone(_,
    419           ContainsAllPasswordForms(empty)))
    420       .WillOnce(QuitUIMessageLoop());
    421 
    422   store->GetAutofillableLogins(&consumer);
    423   MessageLoop::current()->Run();
    424 
    425   STLDeleteElements(&unexpected_autofillable);
    426 
    427   store->Shutdown();
    428 }
    429 
    430 TEST_F(PasswordStoreDefaultTest, Notifications) {
    431   // Prentend that the migration has already taken place.
    432   profile_->GetPrefs()->RegisterBooleanPref(prefs::kLoginDatabaseMigrated,
    433                                             true);
    434 
    435   // Initializing the PasswordStore shouldn't trigger a migration.
    436   scoped_refptr<PasswordStore> store(
    437       new PasswordStoreDefault(login_db_.release(), profile_.get(),
    438                                wds_.get()));
    439   store->Init();
    440 
    441   PasswordFormData form_data =
    442   { PasswordForm::SCHEME_HTML,
    443     "http://bar.example.com",
    444     "http://bar.example.com/origin",
    445     "http://bar.example.com/action",
    446     L"submit_element",
    447     L"username_element",
    448     L"password_element",
    449     L"username_value",
    450     L"password_value",
    451     true, false, 1 };
    452   scoped_ptr<PasswordForm> form(CreatePasswordFormFromData(form_data));
    453 
    454   scoped_refptr<DBThreadObserverHelper> helper = new DBThreadObserverHelper;
    455   helper->Init(store);
    456 
    457   const PasswordStoreChange expected_add_changes[] = {
    458     PasswordStoreChange(PasswordStoreChange::ADD, *form),
    459   };
    460 
    461   EXPECT_CALL(helper->observer(),
    462               Observe(NotificationType(NotificationType::LOGINS_CHANGED),
    463                       Source<PasswordStore>(store),
    464                       Property(&Details<const PasswordStoreChangeList>::ptr,
    465                                Pointee(ElementsAreArray(
    466                                    expected_add_changes)))));
    467 
    468   // Adding a login should trigger a notification.
    469   store->AddLogin(*form);
    470 
    471   // The PasswordStore schedules tasks to run on the DB thread so we schedule
    472   // yet another task to notify us that it's safe to carry on with the test.
    473   WaitableEvent done(false, false);
    474   BrowserThread::PostTask(BrowserThread::DB, FROM_HERE,
    475       new SignalingTask(&done));
    476   done.Wait();
    477 
    478   // Change the password.
    479   form->password_value = WideToUTF16(L"a different password");
    480 
    481   const PasswordStoreChange expected_update_changes[] = {
    482     PasswordStoreChange(PasswordStoreChange::UPDATE, *form),
    483   };
    484 
    485   EXPECT_CALL(helper->observer(),
    486               Observe(NotificationType(NotificationType::LOGINS_CHANGED),
    487                       Source<PasswordStore>(store),
    488                       Property(&Details<const PasswordStoreChangeList>::ptr,
    489                                Pointee(ElementsAreArray(
    490                                    expected_update_changes)))));
    491 
    492   // Updating the login with the new password should trigger a notification.
    493   store->UpdateLogin(*form);
    494 
    495   // Wait for PasswordStore to send the notification.
    496   BrowserThread::PostTask(BrowserThread::DB, FROM_HERE,
    497       new SignalingTask(&done));
    498   done.Wait();
    499 
    500   const PasswordStoreChange expected_delete_changes[] = {
    501     PasswordStoreChange(PasswordStoreChange::REMOVE, *form),
    502   };
    503 
    504   EXPECT_CALL(helper->observer(),
    505               Observe(NotificationType(NotificationType::LOGINS_CHANGED),
    506                       Source<PasswordStore>(store),
    507                       Property(&Details<const PasswordStoreChangeList>::ptr,
    508                                Pointee(ElementsAreArray(
    509                                    expected_delete_changes)))));
    510 
    511   // Deleting the login should trigger a notification.
    512   store->RemoveLogin(*form);
    513 
    514   // Wait for PasswordStore to send the notification.
    515   BrowserThread::PostTask(BrowserThread::DB, FROM_HERE,
    516       new SignalingTask(&done));
    517   done.Wait();
    518 
    519   store->Shutdown();
    520 }
    521