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