password_store_x_unittest.cc revision 90dce4d38c5ff5333bea97d859d4e484e27edf0c
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/stl_util.h"
13#include "base/string_util.h"
14#include "base/stringprintf.h"
15#include "base/synchronization/waitable_event.h"
16#include "base/time.h"
17#include "base/utf_string_conversions.h"
18#include "chrome/browser/password_manager/password_form_data.h"
19#include "chrome/browser/password_manager/password_store_change.h"
20#include "chrome/browser/password_manager/password_store_consumer.h"
21#include "chrome/browser/password_manager/password_store_x.h"
22#include "chrome/common/chrome_notification_types.h"
23#include "chrome/common/pref_names.h"
24#include "chrome/test/base/testing_browser_process.h"
25#include "chrome/test/base/testing_profile.h"
26#include "content/public/browser/notification_details.h"
27#include "content/public/browser/notification_registrar.h"
28#include "content/public/browser/notification_source.h"
29#include "content/public/test/mock_notification_observer.h"
30#include "content/public/test/test_browser_thread.h"
31#include "testing/gmock/include/gmock/gmock.h"
32#include "testing/gtest/include/gtest/gtest.h"
33
34using base::WaitableEvent;
35using content::BrowserThread;
36using testing::_;
37using testing::DoAll;
38using testing::ElementsAreArray;
39using testing::Pointee;
40using testing::Property;
41using testing::WithArg;
42using content::PasswordForm;
43
44typedef std::vector<PasswordForm*> VectorOfForms;
45
46namespace {
47
48class MockPasswordStoreConsumer : public PasswordStoreConsumer {
49 public:
50  MOCK_METHOD2(OnPasswordStoreRequestDone,
51               void(CancelableRequestProvider::Handle,
52                    const std::vector<PasswordForm*>&));
53  MOCK_METHOD1(OnGetPasswordStoreResults,
54               void(const std::vector<PasswordForm*>&));
55};
56
57// This class will add and remove a mock notification observer from
58// the DB thread.
59class DBThreadObserverHelper
60    : public base::RefCountedThreadSafe<DBThreadObserverHelper,
61                                        BrowserThread::DeleteOnDBThread> {
62 public:
63  DBThreadObserverHelper() : done_event_(true, false) {}
64
65  void Init(PasswordStore* password_store) {
66    DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
67    BrowserThread::PostTask(
68        BrowserThread::DB,
69        FROM_HERE,
70        base::Bind(&DBThreadObserverHelper::AddObserverTask,
71                   this, make_scoped_refptr(password_store)));
72    done_event_.Wait();
73  }
74
75  content::MockNotificationObserver& observer() {
76    return observer_;
77  }
78
79 protected:
80  friend struct BrowserThread::DeleteOnThread<BrowserThread::DB>;
81  friend class base::DeleteHelper<DBThreadObserverHelper>;
82
83  virtual ~DBThreadObserverHelper() {
84    DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB));
85    registrar_.RemoveAll();
86  }
87
88  void AddObserverTask(PasswordStore* password_store) {
89    DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB));
90    registrar_.Add(&observer_,
91                   chrome::NOTIFICATION_LOGINS_CHANGED,
92                   content::Source<PasswordStore>(password_store));
93    done_event_.Signal();
94  }
95
96  WaitableEvent done_event_;
97  content::NotificationRegistrar registrar_;
98  content::MockNotificationObserver observer_;
99};
100
101class FailingBackend : public PasswordStoreX::NativeBackend {
102 public:
103  virtual bool Init() OVERRIDE { return true; }
104
105  virtual bool AddLogin(const PasswordForm& form) OVERRIDE { return false; }
106  virtual bool UpdateLogin(const PasswordForm& form) OVERRIDE { return false; }
107  virtual bool RemoveLogin(const PasswordForm& form) OVERRIDE { return false; }
108
109  virtual bool RemoveLoginsCreatedBetween(
110      const base::Time& delete_begin,
111      const base::Time& delete_end) OVERRIDE {
112    return false;
113  }
114
115  virtual bool GetLogins(const PasswordForm& form,
116                         PasswordFormList* forms) OVERRIDE {
117    return false;
118  }
119
120  virtual bool GetLoginsCreatedBetween(const base::Time& get_begin,
121                                       const base::Time& get_end,
122                                       PasswordFormList* forms) OVERRIDE {
123    return false;
124  }
125
126  virtual bool GetAutofillableLogins(PasswordFormList* forms) OVERRIDE {
127    return false;
128  }
129  virtual bool GetBlacklistLogins(PasswordFormList* forms) OVERRIDE {
130    return false;
131  }
132};
133
134class MockBackend : public PasswordStoreX::NativeBackend {
135 public:
136  virtual bool Init() OVERRIDE { return true; }
137
138  virtual bool AddLogin(const PasswordForm& form) OVERRIDE {
139    all_forms_.push_back(form);
140    return true;
141  }
142
143  virtual bool UpdateLogin(const PasswordForm& form) OVERRIDE {
144    for (size_t i = 0; i < all_forms_.size(); ++i)
145      if (CompareForms(all_forms_[i], form, true))
146        all_forms_[i] = form;
147    return true;
148  }
149
150  virtual bool RemoveLogin(const PasswordForm& form) OVERRIDE {
151    for (size_t i = 0; i < all_forms_.size(); ++i)
152      if (CompareForms(all_forms_[i], form, false))
153        erase(i--);
154    return true;
155  }
156
157  virtual bool RemoveLoginsCreatedBetween(
158      const base::Time& delete_begin,
159      const base::Time& delete_end) OVERRIDE {
160    for (size_t i = 0; i < all_forms_.size(); ++i) {
161      if (delete_begin <= all_forms_[i].date_created &&
162          (delete_end.is_null() || all_forms_[i].date_created < delete_end))
163        erase(i--);
164    }
165    return true;
166  }
167
168  virtual bool GetLogins(const PasswordForm& form,
169                         PasswordFormList* forms) OVERRIDE {
170    for (size_t i = 0; i < all_forms_.size(); ++i)
171      if (all_forms_[i].signon_realm == form.signon_realm)
172        forms->push_back(new PasswordForm(all_forms_[i]));
173    return true;
174  }
175
176  virtual bool GetLoginsCreatedBetween(const base::Time& get_begin,
177                                       const base::Time& get_end,
178                                       PasswordFormList* forms) OVERRIDE {
179    for (size_t i = 0; i < all_forms_.size(); ++i)
180      if (get_begin <= all_forms_[i].date_created &&
181          (get_end.is_null() || all_forms_[i].date_created < get_end))
182        forms->push_back(new PasswordForm(all_forms_[i]));
183    return true;
184  }
185
186  virtual bool GetAutofillableLogins(PasswordFormList* forms) OVERRIDE {
187    for (size_t i = 0; i < all_forms_.size(); ++i)
188      if (!all_forms_[i].blacklisted_by_user)
189        forms->push_back(new PasswordForm(all_forms_[i]));
190    return true;
191  }
192
193  virtual bool GetBlacklistLogins(PasswordFormList* forms) OVERRIDE {
194    for (size_t i = 0; i < all_forms_.size(); ++i)
195      if (all_forms_[i].blacklisted_by_user)
196        forms->push_back(new PasswordForm(all_forms_[i]));
197    return true;
198  }
199
200 private:
201  void erase(size_t index) {
202    if (index < all_forms_.size() - 1)
203      all_forms_[index] = all_forms_[all_forms_.size() - 1];
204    all_forms_.pop_back();
205  }
206
207  bool CompareForms(const PasswordForm& a, const PasswordForm& b, bool update) {
208    // An update check doesn't care about the submit element.
209    if (!update && a.submit_element != b.submit_element)
210      return false;
211    return a.origin           == b.origin &&
212           a.password_element == b.password_element &&
213           a.signon_realm     == b.signon_realm &&
214           a.username_element == b.username_element &&
215           a.username_value   == b.username_value;
216  }
217
218  std::vector<PasswordForm> all_forms_;
219};
220
221class MockLoginDatabaseReturn {
222 public:
223  MOCK_METHOD1(OnLoginDatabaseQueryDone,
224               void(const std::vector<PasswordForm*>&));
225};
226
227void LoginDatabaseQueryCallback(LoginDatabase* login_db,
228                                bool autofillable,
229                                MockLoginDatabaseReturn* mock_return) {
230  std::vector<PasswordForm*> forms;
231  if (autofillable)
232    login_db->GetAutofillableLogins(&forms);
233  else
234    login_db->GetBlacklistLogins(&forms);
235  mock_return->OnLoginDatabaseQueryDone(forms);
236}
237
238// Generate |count| expected logins, either auto-fillable or blacklisted.
239void InitExpectedForms(bool autofillable, size_t count, VectorOfForms* forms) {
240  const char* domain = autofillable ? "example" : "blacklisted";
241  for (size_t i = 0; i < count; ++i) {
242    std::string realm = base::StringPrintf("http://%zu.%s.com", i, domain);
243    std::string origin = base::StringPrintf("http://%zu.%s.com/origin",
244                                            i, domain);
245    std::string action = base::StringPrintf("http://%zu.%s.com/action",
246                                            i, domain);
247    PasswordFormData data = {
248      PasswordForm::SCHEME_HTML,
249      realm.c_str(),
250      origin.c_str(),
251      action.c_str(),
252      L"submit_element",
253      L"username_element",
254      L"password_element",
255      autofillable ? L"username_value" : NULL,
256      autofillable ? L"password_value" : NULL,
257      autofillable, false, static_cast<double>(i + 1) };
258    forms->push_back(CreatePasswordFormFromData(data));
259  }
260}
261
262}  // anonymous namespace
263
264enum BackendType {
265  NO_BACKEND,
266  FAILING_BACKEND,
267  WORKING_BACKEND
268};
269
270class PasswordStoreXTest : public testing::TestWithParam<BackendType> {
271 protected:
272  PasswordStoreXTest()
273      : ui_thread_(BrowserThread::UI, &message_loop_),
274        db_thread_(BrowserThread::DB) {
275  }
276
277  virtual void SetUp() {
278    ASSERT_TRUE(db_thread_.Start());
279    ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
280
281    profile_.reset(new TestingProfile());
282
283    login_db_.reset(new LoginDatabase());
284    ASSERT_TRUE(login_db_->Init(temp_dir_.path().Append("login_test")));
285  }
286
287  virtual void TearDown() {
288    base::MessageLoop::current()->PostTask(FROM_HERE,
289                                           base::MessageLoop::QuitClosure());
290    base::MessageLoop::current()->Run();
291    db_thread_.Stop();
292  }
293
294  PasswordStoreX::NativeBackend* GetBackend() {
295    switch (GetParam()) {
296      case FAILING_BACKEND:
297        return new FailingBackend();
298      case WORKING_BACKEND:
299        return new MockBackend();
300      default:
301        return NULL;
302    }
303  }
304
305  base::MessageLoopForUI message_loop_;
306  content::TestBrowserThread ui_thread_;
307  // PasswordStore, WDS schedule work on this thread.
308  content::TestBrowserThread db_thread_;
309
310  scoped_ptr<LoginDatabase> login_db_;
311  scoped_ptr<TestingProfile> profile_;
312  base::ScopedTempDir temp_dir_;
313};
314
315ACTION(STLDeleteElements0) {
316  STLDeleteContainerPointers(arg0.begin(), arg0.end());
317}
318
319ACTION(QuitUIMessageLoop) {
320  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
321  base::MessageLoop::current()->Quit();
322}
323
324TEST_P(PasswordStoreXTest, Notifications) {
325  scoped_refptr<PasswordStoreX> store(
326      new PasswordStoreX(login_db_.release(),
327                         profile_.get(),
328                         GetBackend()));
329  store->Init();
330
331  PasswordFormData form_data =
332  { PasswordForm::SCHEME_HTML,
333    "http://bar.example.com",
334    "http://bar.example.com/origin",
335    "http://bar.example.com/action",
336    L"submit_element",
337    L"username_element",
338    L"password_element",
339    L"username_value",
340    L"password_value",
341    true, false, 1 };
342  scoped_ptr<PasswordForm> form(CreatePasswordFormFromData(form_data));
343
344  scoped_refptr<DBThreadObserverHelper> helper = new DBThreadObserverHelper;
345  helper->Init(store);
346
347  const PasswordStoreChange expected_add_changes[] = {
348    PasswordStoreChange(PasswordStoreChange::ADD, *form),
349  };
350
351  EXPECT_CALL(helper->observer(),
352      Observe(int(chrome::NOTIFICATION_LOGINS_CHANGED),
353              content::Source<PasswordStore>(store),
354              Property(&content::Details<const PasswordStoreChangeList>::ptr,
355                       Pointee(ElementsAreArray(
356                           expected_add_changes)))));
357
358  // Adding a login should trigger a notification.
359  store->AddLogin(*form);
360
361  // The PasswordStore schedules tasks to run on the DB thread so we schedule
362  // yet another task to notify us that it's safe to carry on with the test.
363  WaitableEvent done(false, false);
364  BrowserThread::PostTask(BrowserThread::DB, FROM_HERE,
365      base::Bind(&WaitableEvent::Signal, base::Unretained(&done)));
366  done.Wait();
367
368  // Change the password.
369  form->password_value = WideToUTF16(L"a different password");
370
371  const PasswordStoreChange expected_update_changes[] = {
372    PasswordStoreChange(PasswordStoreChange::UPDATE, *form),
373  };
374
375  EXPECT_CALL(helper->observer(),
376      Observe(int(chrome::NOTIFICATION_LOGINS_CHANGED),
377              content::Source<PasswordStore>(store),
378              Property(&content::Details<const PasswordStoreChangeList>::ptr,
379                       Pointee(ElementsAreArray(
380                           expected_update_changes)))));
381
382  // Updating the login with the new password should trigger a notification.
383  store->UpdateLogin(*form);
384
385  // Wait for PasswordStore to send the notification.
386  BrowserThread::PostTask(BrowserThread::DB, FROM_HERE,
387      base::Bind(&WaitableEvent::Signal, base::Unretained(&done)));
388  done.Wait();
389
390  const PasswordStoreChange expected_delete_changes[] = {
391    PasswordStoreChange(PasswordStoreChange::REMOVE, *form),
392  };
393
394  EXPECT_CALL(helper->observer(),
395      Observe(int(chrome::NOTIFICATION_LOGINS_CHANGED),
396              content::Source<PasswordStore>(store),
397              Property(&content::Details<const PasswordStoreChangeList>::ptr,
398                       Pointee(ElementsAreArray(
399                           expected_delete_changes)))));
400
401  // Deleting the login should trigger a notification.
402  store->RemoveLogin(*form);
403
404  // Wait for PasswordStore to send the notification.
405  BrowserThread::PostTask(BrowserThread::DB, FROM_HERE,
406      base::Bind(&WaitableEvent::Signal, base::Unretained(&done)));
407  done.Wait();
408
409  // Public in PasswordStore, protected in PasswordStoreX.
410  static_cast<PasswordStore*>(store)->ShutdownOnUIThread();
411}
412
413TEST_P(PasswordStoreXTest, NativeMigration) {
414  VectorOfForms expected_autofillable;
415  InitExpectedForms(true, 50, &expected_autofillable);
416
417  VectorOfForms expected_blacklisted;
418  InitExpectedForms(false, 50, &expected_blacklisted);
419
420  // Get the initial size of the login DB file, before we populate it.
421  // This will be used later to make sure it gets back to this size.
422  const base::FilePath login_db_file = temp_dir_.path().Append("login_test");
423  base::PlatformFileInfo db_file_start_info;
424  ASSERT_TRUE(file_util::GetFileInfo(login_db_file, &db_file_start_info));
425
426  LoginDatabase* login_db = login_db_.get();
427
428  // Populate the login DB with logins that should be migrated.
429  for (VectorOfForms::iterator it = expected_autofillable.begin();
430       it != expected_autofillable.end(); ++it) {
431    BrowserThread::PostTask(BrowserThread::DB, FROM_HERE,
432                            base::Bind(
433                                base::IgnoreResult(&LoginDatabase::AddLogin),
434                                base::Unretained(login_db), **it));
435  }
436  for (VectorOfForms::iterator it = expected_blacklisted.begin();
437       it != expected_blacklisted.end(); ++it) {
438    BrowserThread::PostTask(BrowserThread::DB, FROM_HERE,
439                            base::Bind(
440                                base::IgnoreResult(&LoginDatabase::AddLogin),
441                                base::Unretained(login_db), **it));
442  }
443
444  // Schedule another task on the DB thread to notify us that it's safe to
445  // carry on with the test.
446  WaitableEvent done(false, false);
447  BrowserThread::PostTask(BrowserThread::DB, FROM_HERE,
448      base::Bind(&WaitableEvent::Signal, base::Unretained(&done)));
449  done.Wait();
450
451  // Get the new size of the login DB file. We expect it to be larger.
452  base::PlatformFileInfo db_file_full_info;
453  ASSERT_TRUE(file_util::GetFileInfo(login_db_file, &db_file_full_info));
454  EXPECT_GT(db_file_full_info.size, db_file_start_info.size);
455
456  // Initializing the PasswordStore shouldn't trigger a native migration (yet).
457  scoped_refptr<PasswordStoreX> store(
458      new PasswordStoreX(login_db_.release(),
459                         profile_.get(),
460                         GetBackend()));
461  store->Init();
462
463  MockPasswordStoreConsumer consumer;
464
465  // Make sure we quit the MessageLoop even if the test fails.
466  ON_CALL(consumer, OnPasswordStoreRequestDone(_, _))
467      .WillByDefault(QuitUIMessageLoop());
468
469  // The autofillable forms should have been migrated to the native backend.
470  EXPECT_CALL(consumer,
471      OnPasswordStoreRequestDone(_,
472          ContainsAllPasswordForms(expected_autofillable)))
473      .WillOnce(DoAll(WithArg<1>(STLDeleteElements0()), QuitUIMessageLoop()));
474
475  store->GetAutofillableLogins(&consumer);
476  base::MessageLoop::current()->Run();
477
478  // The blacklisted forms should have been migrated to the native backend.
479  EXPECT_CALL(consumer,
480      OnPasswordStoreRequestDone(_,
481          ContainsAllPasswordForms(expected_blacklisted)))
482      .WillOnce(DoAll(WithArg<1>(STLDeleteElements0()), QuitUIMessageLoop()));
483
484  store->GetBlacklistLogins(&consumer);
485  base::MessageLoop::current()->Run();
486
487  VectorOfForms empty;
488  MockLoginDatabaseReturn ld_return;
489
490  if (GetParam() == WORKING_BACKEND) {
491    // No autofillable logins should be left in the login DB.
492    EXPECT_CALL(ld_return,
493                OnLoginDatabaseQueryDone(ContainsAllPasswordForms(empty)));
494  } else {
495    // The autofillable logins should still be in the login DB.
496    EXPECT_CALL(ld_return,
497                OnLoginDatabaseQueryDone(
498                    ContainsAllPasswordForms(expected_autofillable)))
499        .WillOnce(WithArg<0>(STLDeleteElements0()));
500  }
501
502  BrowserThread::PostTask(
503      BrowserThread::DB, FROM_HERE,
504      base::Bind(&LoginDatabaseQueryCallback, login_db, true, &ld_return));
505
506  // Wait for the login DB methods to execute on the DB thread.
507  BrowserThread::PostTask(BrowserThread::DB, FROM_HERE,
508      base::Bind(&WaitableEvent::Signal, base::Unretained(&done)));
509  done.Wait();
510
511  if (GetParam() == WORKING_BACKEND) {
512    // Likewise, no blacklisted logins should be left in the login DB.
513    EXPECT_CALL(ld_return,
514                OnLoginDatabaseQueryDone(ContainsAllPasswordForms(empty)));
515  } else {
516    // The blacklisted logins should still be in the login DB.
517    EXPECT_CALL(ld_return,
518                OnLoginDatabaseQueryDone(
519                    ContainsAllPasswordForms(expected_blacklisted)))
520        .WillOnce(WithArg<0>(STLDeleteElements0()));
521  }
522
523  BrowserThread::PostTask(
524      BrowserThread::DB, FROM_HERE,
525      base::Bind(&LoginDatabaseQueryCallback, login_db, false, &ld_return));
526
527  // Wait for the login DB methods to execute on the DB thread.
528  BrowserThread::PostTask(BrowserThread::DB, FROM_HERE,
529      base::Bind(&WaitableEvent::Signal, base::Unretained(&done)));
530  done.Wait();
531
532  if (GetParam() == WORKING_BACKEND) {
533    // If the migration succeeded, then not only should there be no logins left
534    // in the login DB, but also the file should have been deleted and then
535    // recreated. We approximate checking for this by checking that the file
536    // size is equal to the size before we populated it, even though it was
537    // larger after populating it.
538    base::PlatformFileInfo db_file_end_info;
539    ASSERT_TRUE(file_util::GetFileInfo(login_db_file, &db_file_end_info));
540    EXPECT_EQ(db_file_start_info.size, db_file_end_info.size);
541  }
542
543  STLDeleteElements(&expected_autofillable);
544  STLDeleteElements(&expected_blacklisted);
545
546  // Public in PasswordStore, protected in PasswordStoreX.
547  static_cast<PasswordStore*>(store)->ShutdownOnUIThread();
548}
549
550INSTANTIATE_TEST_CASE_P(NoBackend,
551                        PasswordStoreXTest,
552                        testing::Values(NO_BACKEND));
553INSTANTIATE_TEST_CASE_P(FailingBackend,
554                        PasswordStoreXTest,
555                        testing::Values(FAILING_BACKEND));
556INSTANTIATE_TEST_CASE_P(WorkingBackend,
557                        PasswordStoreXTest,
558                        testing::Values(WORKING_BACKEND));
559