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/stl_util.h"
      8 #include "base/strings/string_util.h"
      9 #include "base/synchronization/waitable_event.h"
     10 #include "base/time/time.h"
     11 #include "chrome/browser/chrome_notification_types.h"
     12 #include "chrome/browser/password_manager/password_form_data.h"
     13 #include "chrome/browser/password_manager/password_store_consumer.h"
     14 #include "chrome/browser/password_manager/password_store_default.h"
     15 #include "chrome/test/base/testing_profile.h"
     16 #include "content/public/browser/notification_details.h"
     17 #include "content/public/browser/notification_registrar.h"
     18 #include "content/public/browser/notification_source.h"
     19 #include "content/public/test/mock_notification_observer.h"
     20 #include "content/public/test/test_browser_thread.h"
     21 #include "testing/gmock/include/gmock/gmock.h"
     22 #include "testing/gtest/include/gtest/gtest.h"
     23 
     24 using base::WaitableEvent;
     25 using content::BrowserThread;
     26 using testing::_;
     27 using testing::DoAll;
     28 using testing::WithArg;
     29 using content::PasswordForm;
     30 
     31 namespace {
     32 
     33 class MockPasswordStoreConsumer : public PasswordStoreConsumer {
     34  public:
     35   MOCK_METHOD2(OnPasswordStoreRequestDone,
     36                void(CancelableRequestProvider::Handle,
     37                     const std::vector<PasswordForm*>&));
     38   MOCK_METHOD1(OnGetPasswordStoreResults,
     39                void(const std::vector<PasswordForm*>&));
     40 };
     41 
     42 // This class will add and remove a mock notification observer from
     43 // the DB thread.
     44 class DBThreadObserverHelper
     45     : public base::RefCountedThreadSafe<DBThreadObserverHelper,
     46                                         BrowserThread::DeleteOnDBThread> {
     47  public:
     48   DBThreadObserverHelper() : done_event_(true, false) {}
     49 
     50   void Init(PasswordStore* password_store) {
     51     DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
     52     BrowserThread::PostTask(
     53         BrowserThread::DB,
     54         FROM_HERE,
     55         base::Bind(&DBThreadObserverHelper::AddObserverTask,
     56                    this,
     57                    make_scoped_refptr(password_store)));
     58     done_event_.Wait();
     59   }
     60 
     61   content::MockNotificationObserver& observer() {
     62     return observer_;
     63   }
     64 
     65  protected:
     66   virtual ~DBThreadObserverHelper() {
     67     DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB));
     68     registrar_.RemoveAll();
     69   }
     70 
     71   void AddObserverTask(PasswordStore* password_store) {
     72     DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB));
     73     registrar_.Add(&observer_,
     74                    chrome::NOTIFICATION_LOGINS_CHANGED,
     75                    content::Source<PasswordStore>(password_store));
     76     done_event_.Signal();
     77   }
     78 
     79   WaitableEvent done_event_;
     80   content::NotificationRegistrar registrar_;
     81   content::MockNotificationObserver observer_;
     82 
     83  private:
     84   friend struct BrowserThread::DeleteOnThread<BrowserThread::DB>;
     85   friend class base::DeleteHelper<DBThreadObserverHelper>;
     86 };
     87 
     88 }  // anonymous namespace
     89 
     90 class PasswordStoreTest : public testing::Test {
     91  protected:
     92   PasswordStoreTest()
     93       : ui_thread_(BrowserThread::UI, &message_loop_),
     94         db_thread_(BrowserThread::DB) {
     95   }
     96 
     97   virtual void SetUp() {
     98     ASSERT_TRUE(db_thread_.Start());
     99 
    100     profile_.reset(new TestingProfile());
    101 
    102     login_db_.reset(new LoginDatabase());
    103     ASSERT_TRUE(login_db_->Init(profile_->GetPath().Append(
    104         FILE_PATH_LITERAL("login_test"))));
    105   }
    106 
    107   virtual void TearDown() {
    108     db_thread_.Stop();
    109     base::MessageLoop::current()->PostTask(FROM_HERE,
    110                                            base::MessageLoop::QuitClosure());
    111     base::MessageLoop::current()->Run();
    112   }
    113 
    114   base::MessageLoopForUI message_loop_;
    115   content::TestBrowserThread ui_thread_;
    116   // PasswordStore schedules work on this thread.
    117   content::TestBrowserThread db_thread_;
    118 
    119   scoped_ptr<LoginDatabase> login_db_;
    120   scoped_ptr<TestingProfile> profile_;
    121 };
    122 
    123 ACTION(STLDeleteElements0) {
    124   STLDeleteContainerPointers(arg0.begin(), arg0.end());
    125 }
    126 
    127 ACTION(QuitUIMessageLoop) {
    128   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    129   base::MessageLoop::current()->Quit();
    130 }
    131 
    132 TEST_F(PasswordStoreTest, IgnoreOldWwwGoogleLogins) {
    133   scoped_refptr<PasswordStoreDefault> store(
    134       new PasswordStoreDefault(login_db_.release(), profile_.get()));
    135   store->Init();
    136 
    137   const time_t cutoff = 1325376000;  // 00:00 Jan 1 2012 UTC
    138   // The passwords are all empty because PasswordStoreDefault doesn't store the
    139   // actual passwords on OS X (they're stored in the Keychain instead). We could
    140   // special-case it, but it's easier to just have empty passwords.
    141   static const PasswordFormData form_data[] = {
    142     // A form on https://www.google.com/ older than the cutoff. Will be ignored.
    143     { PasswordForm::SCHEME_HTML,
    144       "https://www.google.com",
    145       "https://www.google.com/origin",
    146       "https://www.google.com/action",
    147       L"submit_element",
    148       L"username_element",
    149       L"password_element",
    150       L"username_value_1",
    151       L"",
    152       true, true, cutoff - 1 },
    153     // A form on https://www.google.com/ older than the cutoff. Will be ignored.
    154     { PasswordForm::SCHEME_HTML,
    155       "https://www.google.com",
    156       "https://www.google.com/origin",
    157       "https://www.google.com/action",
    158       L"submit_element",
    159       L"username_element",
    160       L"password_element",
    161       L"username_value_2",
    162       L"",
    163       true, true, cutoff - 1 },
    164     // A form on https://www.google.com/ newer than the cutoff.
    165     { PasswordForm::SCHEME_HTML,
    166       "https://www.google.com",
    167       "https://www.google.com/origin",
    168       "https://www.google.com/action",
    169       L"submit_element",
    170       L"username_element",
    171       L"password_element",
    172       L"username_value_3",
    173       L"",
    174       true, true, cutoff + 1 },
    175     // A form on https://accounts.google.com/ older than the cutoff.
    176     { PasswordForm::SCHEME_HTML,
    177       "https://accounts.google.com",
    178       "https://accounts.google.com/origin",
    179       "https://accounts.google.com/action",
    180       L"submit_element",
    181       L"username_element",
    182       L"password_element",
    183       L"username_value",
    184       L"",
    185       true, true, cutoff - 1 },
    186     // A form on http://bar.example.com/ older than the cutoff.
    187     { PasswordForm::SCHEME_HTML,
    188       "http://bar.example.com",
    189       "http://bar.example.com/origin",
    190       "http://bar.example.com/action",
    191       L"submit_element",
    192       L"username_element",
    193       L"password_element",
    194       L"username_value",
    195       L"",
    196       true, false, cutoff - 1 },
    197   };
    198 
    199   // Build the forms vector and add the forms to the store.
    200   std::vector<PasswordForm*> all_forms;
    201   for (size_t i = 0; i < ARRAYSIZE_UNSAFE(form_data); ++i) {
    202     PasswordForm* form = CreatePasswordFormFromData(form_data[i]);
    203     all_forms.push_back(form);
    204     store->AddLogin(*form);
    205   }
    206 
    207   // The PasswordStore schedules tasks to run on the DB thread so we schedule
    208   // yet another task to notify us that it's safe to carry on with the test.
    209   // The PasswordStore doesn't really understand that it's "done" once the tasks
    210   // we posted above have completed, so there's no formal notification for that.
    211   WaitableEvent done(false, false);
    212   BrowserThread::PostTask(BrowserThread::DB, FROM_HERE,
    213       base::Bind(&WaitableEvent::Signal, base::Unretained(&done)));
    214   done.Wait();
    215 
    216   // We expect to get back only the "recent" www.google.com login.
    217   // Theoretically these should never actually exist since there are no longer
    218   // any login forms on www.google.com to save, but we technically allow them.
    219   // We should not get back the older saved password though.
    220   PasswordForm www_google;
    221   www_google.scheme = PasswordForm::SCHEME_HTML;
    222   www_google.signon_realm = "https://www.google.com";
    223   std::vector<PasswordForm*> www_google_expected;
    224   www_google_expected.push_back(all_forms[2]);
    225 
    226   // We should still get the accounts.google.com login even though it's older
    227   // than our cutoff - this is the new location of all Google login forms.
    228   PasswordForm accounts_google;
    229   accounts_google.scheme = PasswordForm::SCHEME_HTML;
    230   accounts_google.signon_realm = "https://accounts.google.com";
    231   std::vector<PasswordForm*> accounts_google_expected;
    232   accounts_google_expected.push_back(all_forms[3]);
    233 
    234   // Same thing for a generic saved login.
    235   PasswordForm bar_example;
    236   bar_example.scheme = PasswordForm::SCHEME_HTML;
    237   bar_example.signon_realm = "http://bar.example.com";
    238   std::vector<PasswordForm*> bar_example_expected;
    239   bar_example_expected.push_back(all_forms[4]);
    240 
    241   MockPasswordStoreConsumer consumer;
    242 
    243   // Make sure we quit the MessageLoop even if the test fails.
    244   ON_CALL(consumer, OnGetPasswordStoreResults(_))
    245       .WillByDefault(QuitUIMessageLoop());
    246 
    247   // Expect the appropriate replies, as above, in reverse order than we will
    248   // issue the queries. Each retires on saturation to avoid matcher spew, except
    249   // the last which quits the message loop.
    250   EXPECT_CALL(consumer,
    251       OnGetPasswordStoreResults(ContainsAllPasswordForms(bar_example_expected)))
    252       .WillOnce(DoAll(WithArg<0>(STLDeleteElements0()), QuitUIMessageLoop()));
    253   EXPECT_CALL(consumer,
    254       OnGetPasswordStoreResults(
    255           ContainsAllPasswordForms(accounts_google_expected)))
    256       .WillOnce(WithArg<0>(STLDeleteElements0())).RetiresOnSaturation();
    257   EXPECT_CALL(consumer,
    258       OnGetPasswordStoreResults(
    259           ContainsAllPasswordForms(www_google_expected)))
    260       .WillOnce(WithArg<0>(STLDeleteElements0())).RetiresOnSaturation();
    261 
    262   store->GetLogins(www_google, &consumer);
    263   store->GetLogins(accounts_google, &consumer);
    264   store->GetLogins(bar_example, &consumer);
    265 
    266   base::MessageLoop::current()->Run();
    267 
    268   STLDeleteElements(&all_forms);
    269 }
    270