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