Home | History | Annotate | Download | only in password_manager
      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/prefs/pref_service.h"
      9 #include "base/stl_util.h"
     10 #include "base/strings/string_util.h"
     11 #include "base/strings/utf_string_conversions.h"
     12 #include "base/synchronization/waitable_event.h"
     13 #include "base/time/time.h"
     14 #include "chrome/browser/chrome_notification_types.h"
     15 #include "chrome/browser/password_manager/password_form_data.h"
     16 #include "chrome/browser/password_manager/password_store_change.h"
     17 #include "chrome/browser/password_manager/password_store_consumer.h"
     18 #include "chrome/browser/password_manager/password_store_default.h"
     19 #include "chrome/common/pref_names.h"
     20 #include "chrome/test/base/testing_profile.h"
     21 #include "content/public/browser/notification_details.h"
     22 #include "content/public/browser/notification_registrar.h"
     23 #include "content/public/browser/notification_source.h"
     24 #include "content/public/test/mock_notification_observer.h"
     25 #include "content/public/test/test_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 content::BrowserThread;
     31 using content::PasswordForm;
     32 using testing::_;
     33 using testing::DoAll;
     34 using testing::ElementsAreArray;
     35 using testing::Pointee;
     36 using testing::Property;
     37 using testing::WithArg;
     38 
     39 namespace {
     40 
     41 class MockPasswordStoreConsumer : public PasswordStoreConsumer {
     42  public:
     43   MOCK_METHOD2(OnPasswordStoreRequestDone,
     44                void(CancelableRequestProvider::Handle,
     45                     const std::vector<PasswordForm*>&));
     46   MOCK_METHOD1(OnGetPasswordStoreResults,
     47                void(const std::vector<PasswordForm*>&));
     48 };
     49 
     50 // This class will add and remove a mock notification observer from
     51 // the DB thread.
     52 class DBThreadObserverHelper
     53     : public base::RefCountedThreadSafe<DBThreadObserverHelper,
     54                                         BrowserThread::DeleteOnDBThread> {
     55  public:
     56   DBThreadObserverHelper() : done_event_(true, false) {}
     57 
     58   void Init(PasswordStore* password_store) {
     59     DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
     60     BrowserThread::PostTask(
     61         BrowserThread::DB,
     62         FROM_HERE,
     63         base::Bind(&DBThreadObserverHelper::AddObserverTask,
     64                    this,
     65                    make_scoped_refptr(password_store)));
     66     done_event_.Wait();
     67   }
     68 
     69   content::MockNotificationObserver& observer() {
     70     return observer_;
     71   }
     72 
     73  protected:
     74   friend struct BrowserThread::DeleteOnThread<BrowserThread::DB>;
     75   friend class base::DeleteHelper<DBThreadObserverHelper>;
     76 
     77   virtual ~DBThreadObserverHelper() {
     78     DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB));
     79     registrar_.RemoveAll();
     80   }
     81 
     82   void AddObserverTask(PasswordStore* password_store) {
     83     DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB));
     84     registrar_.Add(&observer_,
     85                    chrome::NOTIFICATION_LOGINS_CHANGED,
     86                    content::Source<PasswordStore>(password_store));
     87     done_event_.Signal();
     88   }
     89 
     90   WaitableEvent done_event_;
     91   content::NotificationRegistrar registrar_;
     92   content::MockNotificationObserver observer_;
     93 };
     94 
     95 }  // anonymous namespace
     96 
     97 class PasswordStoreDefaultTest : public testing::Test {
     98  protected:
     99   PasswordStoreDefaultTest()
    100       : ui_thread_(BrowserThread::UI, &message_loop_),
    101         db_thread_(BrowserThread::DB) {
    102   }
    103 
    104   virtual void SetUp() {
    105     ASSERT_TRUE(db_thread_.Start());
    106 
    107     profile_.reset(new TestingProfile());
    108 
    109     login_db_.reset(new LoginDatabase());
    110     ASSERT_TRUE(login_db_->Init(profile_->GetPath().Append(
    111         FILE_PATH_LITERAL("login_test"))));
    112   }
    113 
    114   virtual void TearDown() {
    115     base::MessageLoop::current()->PostTask(FROM_HERE,
    116                                            base::MessageLoop::QuitClosure());
    117     base::MessageLoop::current()->Run();
    118     db_thread_.Stop();
    119   }
    120 
    121   base::MessageLoopForUI message_loop_;
    122   content::TestBrowserThread ui_thread_;
    123   // PasswordStore, WDS schedule work on this thread.
    124   content::TestBrowserThread db_thread_;
    125 
    126   scoped_ptr<LoginDatabase> login_db_;
    127   scoped_ptr<TestingProfile> profile_;
    128 };
    129 
    130 ACTION(STLDeleteElements0) {
    131   STLDeleteContainerPointers(arg0.begin(), arg0.end());
    132 }
    133 
    134 ACTION(QuitUIMessageLoop) {
    135   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    136   base::MessageLoop::current()->Quit();
    137 }
    138 
    139 TEST_F(PasswordStoreDefaultTest, NonASCIIData) {
    140   scoped_refptr<PasswordStoreDefault> store(
    141       new PasswordStoreDefault(login_db_.release(), profile_.get()));
    142   store->Init();
    143 
    144   // Some non-ASCII password form data.
    145   static const PasswordFormData form_data[] = {
    146     { PasswordForm::SCHEME_HTML,
    147       "http://foo.example.com",
    148       "http://foo.example.com/origin",
    149       "http://foo.example.com/action",
    150       L"",
    151       L"?",
    152       L"",
    153       L" ",
    154       L"",
    155       true, false, 1 },
    156   };
    157 
    158   // Build the expected forms vector and add the forms to the store.
    159   std::vector<PasswordForm*> expected_forms;
    160   for (unsigned int i = 0; i < ARRAYSIZE_UNSAFE(form_data); ++i) {
    161     PasswordForm* form = CreatePasswordFormFromData(form_data[i]);
    162     expected_forms.push_back(form);
    163     store->AddLogin(*form);
    164   }
    165 
    166   // The PasswordStore schedules tasks to run on the DB thread so we schedule
    167   // yet another task to notify us that it's safe to carry on with the test.
    168   // The PasswordStore doesn't really understand that it's "done" once the tasks
    169   // we posted above have completed, so there's no formal notification for that.
    170   WaitableEvent done(false, false);
    171   BrowserThread::PostTask(BrowserThread::DB, FROM_HERE,
    172       base::Bind(&WaitableEvent::Signal, base::Unretained(&done)));
    173   done.Wait();
    174 
    175   MockPasswordStoreConsumer consumer;
    176 
    177   // Make sure we quit the MessageLoop even if the test fails.
    178   ON_CALL(consumer, OnPasswordStoreRequestDone(_, _))
    179       .WillByDefault(QuitUIMessageLoop());
    180 
    181   // We expect to get the same data back, even though it's not all ASCII.
    182   EXPECT_CALL(consumer,
    183       OnPasswordStoreRequestDone(_,
    184           ContainsAllPasswordForms(expected_forms)))
    185       .WillOnce(DoAll(WithArg<1>(STLDeleteElements0()), QuitUIMessageLoop()));
    186 
    187   store->GetAutofillableLogins(&consumer);
    188   base::MessageLoop::current()->Run();
    189 
    190   STLDeleteElements(&expected_forms);
    191 }
    192 
    193 TEST_F(PasswordStoreDefaultTest, Notifications) {
    194   scoped_refptr<PasswordStore> store(
    195       new PasswordStoreDefault(login_db_.release(), profile_.get()));
    196   store->Init();
    197 
    198   PasswordFormData form_data =
    199   { PasswordForm::SCHEME_HTML,
    200     "http://bar.example.com",
    201     "http://bar.example.com/origin",
    202     "http://bar.example.com/action",
    203     L"submit_element",
    204     L"username_element",
    205     L"password_element",
    206     L"username_value",
    207     L"password_value",
    208     true, false, 1 };
    209   scoped_ptr<PasswordForm> form(CreatePasswordFormFromData(form_data));
    210 
    211   scoped_refptr<DBThreadObserverHelper> helper = new DBThreadObserverHelper;
    212   helper->Init(store.get());
    213 
    214   const PasswordStoreChange expected_add_changes[] = {
    215     PasswordStoreChange(PasswordStoreChange::ADD, *form),
    216   };
    217 
    218   EXPECT_CALL(
    219       helper->observer(),
    220       Observe(int(chrome::NOTIFICATION_LOGINS_CHANGED),
    221               content::Source<PasswordStore>(store.get()),
    222               Property(&content::Details<const PasswordStoreChangeList>::ptr,
    223                        Pointee(ElementsAreArray(expected_add_changes)))));
    224 
    225   // Adding a login should trigger a notification.
    226   store->AddLogin(*form);
    227 
    228   // The PasswordStore schedules tasks to run on the DB thread so we schedule
    229   // yet another task to notify us that it's safe to carry on with the test.
    230   WaitableEvent done(false, false);
    231   BrowserThread::PostTask(BrowserThread::DB, FROM_HERE,
    232       base::Bind(&WaitableEvent::Signal, base::Unretained(&done)));
    233   done.Wait();
    234 
    235   // Change the password.
    236   form->password_value = WideToUTF16(L"a different password");
    237 
    238   const PasswordStoreChange expected_update_changes[] = {
    239     PasswordStoreChange(PasswordStoreChange::UPDATE, *form),
    240   };
    241 
    242   EXPECT_CALL(
    243       helper->observer(),
    244       Observe(int(chrome::NOTIFICATION_LOGINS_CHANGED),
    245               content::Source<PasswordStore>(store.get()),
    246               Property(&content::Details<const PasswordStoreChangeList>::ptr,
    247                        Pointee(ElementsAreArray(expected_update_changes)))));
    248 
    249   // Updating the login with the new password should trigger a notification.
    250   store->UpdateLogin(*form);
    251 
    252   // Wait for PasswordStore to send the notification.
    253   BrowserThread::PostTask(BrowserThread::DB, FROM_HERE,
    254       base::Bind(&WaitableEvent::Signal, base::Unretained(&done)));
    255   done.Wait();
    256 
    257   const PasswordStoreChange expected_delete_changes[] = {
    258     PasswordStoreChange(PasswordStoreChange::REMOVE, *form),
    259   };
    260 
    261   EXPECT_CALL(
    262       helper->observer(),
    263       Observe(int(chrome::NOTIFICATION_LOGINS_CHANGED),
    264               content::Source<PasswordStore>(store.get()),
    265               Property(&content::Details<const PasswordStoreChangeList>::ptr,
    266                        Pointee(ElementsAreArray(expected_delete_changes)))));
    267 
    268   // Deleting the login should trigger a notification.
    269   store->RemoveLogin(*form);
    270 
    271   // Wait for PasswordStore to send the notification.
    272   BrowserThread::PostTask(BrowserThread::DB, FROM_HERE,
    273       base::Bind(&WaitableEvent::Signal, base::Unretained(&done)));
    274   done.Wait();
    275 
    276   store->ShutdownOnUIThread();
    277 }
    278