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