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