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