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.
5 #include "base/basictypes.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"
35 using autofill::PasswordForm;
36 using content::BrowserThread;
39 using testing::ElementsAreArray;
40 using testing::Pointee;
41 using testing::Property;
42 using testing::WithArg;
44 typedef std::vector<PasswordForm*> VectorOfForms;
48 class MockPasswordStoreConsumer : public PasswordStoreConsumer {
50 MOCK_METHOD1(OnGetPasswordStoreResults,
51 void(const std::vector<PasswordForm*>&));
54 // This class will add and remove a mock notification observer from
56 class DBThreadObserverHelper {
58 DBThreadObserverHelper() {}
60 ~DBThreadObserverHelper() {
61 registrar_.RemoveAll();
64 void Init(PasswordStore* password_store) {
65 registrar_.Add(&observer_,
66 chrome::NOTIFICATION_LOGINS_CHANGED,
67 content::Source<PasswordStore>(password_store));
70 content::MockNotificationObserver& observer() {
75 content::NotificationRegistrar registrar_;
76 content::MockNotificationObserver observer_;
79 class FailingBackend : public PasswordStoreX::NativeBackend {
81 virtual bool Init() OVERRIDE { return true; }
83 virtual bool AddLogin(const PasswordForm& form) OVERRIDE { return false; }
84 virtual bool UpdateLogin(const PasswordForm& form) OVERRIDE { return false; }
85 virtual bool RemoveLogin(const PasswordForm& form) OVERRIDE { return false; }
87 virtual bool RemoveLoginsCreatedBetween(
88 const base::Time& delete_begin,
89 const base::Time& delete_end) OVERRIDE {
93 virtual bool GetLogins(const PasswordForm& form,
94 PasswordFormList* forms) OVERRIDE {
98 virtual bool GetLoginsCreatedBetween(const base::Time& get_begin,
99 const base::Time& get_end,
100 PasswordFormList* forms) OVERRIDE {
104 virtual bool GetAutofillableLogins(PasswordFormList* forms) OVERRIDE {
107 virtual bool GetBlacklistLogins(PasswordFormList* forms) OVERRIDE {
112 class MockBackend : public PasswordStoreX::NativeBackend {
114 virtual bool Init() OVERRIDE { return true; }
116 virtual bool AddLogin(const PasswordForm& form) OVERRIDE {
117 all_forms_.push_back(form);
121 virtual bool UpdateLogin(const PasswordForm& form) OVERRIDE {
122 for (size_t i = 0; i < all_forms_.size(); ++i)
123 if (CompareForms(all_forms_[i], form, true))
124 all_forms_[i] = form;
128 virtual bool RemoveLogin(const PasswordForm& form) OVERRIDE {
129 for (size_t i = 0; i < all_forms_.size(); ++i)
130 if (CompareForms(all_forms_[i], form, false))
135 virtual bool RemoveLoginsCreatedBetween(
136 const base::Time& delete_begin,
137 const base::Time& delete_end) OVERRIDE {
138 for (size_t i = 0; i < all_forms_.size(); ++i) {
139 if (delete_begin <= all_forms_[i].date_created &&
140 (delete_end.is_null() || all_forms_[i].date_created < delete_end))
146 virtual bool GetLogins(const PasswordForm& form,
147 PasswordFormList* forms) OVERRIDE {
148 for (size_t i = 0; i < all_forms_.size(); ++i)
149 if (all_forms_[i].signon_realm == form.signon_realm)
150 forms->push_back(new PasswordForm(all_forms_[i]));
154 virtual bool GetLoginsCreatedBetween(const base::Time& get_begin,
155 const base::Time& get_end,
156 PasswordFormList* forms) OVERRIDE {
157 for (size_t i = 0; i < all_forms_.size(); ++i)
158 if (get_begin <= all_forms_[i].date_created &&
159 (get_end.is_null() || all_forms_[i].date_created < get_end))
160 forms->push_back(new PasswordForm(all_forms_[i]));
164 virtual bool GetAutofillableLogins(PasswordFormList* forms) OVERRIDE {
165 for (size_t i = 0; i < all_forms_.size(); ++i)
166 if (!all_forms_[i].blacklisted_by_user)
167 forms->push_back(new PasswordForm(all_forms_[i]));
171 virtual bool GetBlacklistLogins(PasswordFormList* forms) OVERRIDE {
172 for (size_t i = 0; i < all_forms_.size(); ++i)
173 if (all_forms_[i].blacklisted_by_user)
174 forms->push_back(new PasswordForm(all_forms_[i]));
179 void erase(size_t index) {
180 if (index < all_forms_.size() - 1)
181 all_forms_[index] = all_forms_[all_forms_.size() - 1];
182 all_forms_.pop_back();
185 bool CompareForms(const PasswordForm& a, const PasswordForm& b, bool update) {
186 // An update check doesn't care about the submit element.
187 if (!update && a.submit_element != b.submit_element)
189 return a.origin == b.origin &&
190 a.password_element == b.password_element &&
191 a.signon_realm == b.signon_realm &&
192 a.username_element == b.username_element &&
193 a.username_value == b.username_value;
196 std::vector<PasswordForm> all_forms_;
199 class MockLoginDatabaseReturn {
201 MOCK_METHOD1(OnLoginDatabaseQueryDone,
202 void(const std::vector<PasswordForm*>&));
205 void LoginDatabaseQueryCallback(LoginDatabase* login_db,
207 MockLoginDatabaseReturn* mock_return) {
208 std::vector<PasswordForm*> forms;
210 login_db->GetAutofillableLogins(&forms);
212 login_db->GetBlacklistLogins(&forms);
213 mock_return->OnLoginDatabaseQueryDone(forms);
216 // Generate |count| expected logins, either auto-fillable or blacklisted.
217 void InitExpectedForms(bool autofillable, size_t count, VectorOfForms* forms) {
218 const char* domain = autofillable ? "example" : "blacklisted";
219 for (size_t i = 0; i < count; ++i) {
220 std::string realm = base::StringPrintf("http://%zu.%s.com", i, domain);
221 std::string origin = base::StringPrintf("http://%zu.%s.com/origin",
223 std::string action = base::StringPrintf("http://%zu.%s.com/action",
225 PasswordFormData data = {
226 PasswordForm::SCHEME_HTML,
233 autofillable ? L"username_value" : NULL,
234 autofillable ? L"password_value" : NULL,
235 autofillable, false, static_cast<double>(i + 1) };
236 forms->push_back(CreatePasswordFormFromData(data));
240 } // anonymous namespace
248 class PasswordStoreXTest : public testing::TestWithParam<BackendType> {
250 virtual void SetUp() {
251 ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
253 profile_.reset(new TestingProfile());
255 login_db_.reset(new LoginDatabase());
256 ASSERT_TRUE(login_db_->Init(temp_dir_.path().Append("login_test")));
259 virtual void TearDown() {
260 base::RunLoop().RunUntilIdle();
263 PasswordStoreX::NativeBackend* GetBackend() {
264 switch (GetParam()) {
265 case FAILING_BACKEND:
266 return new FailingBackend();
267 case WORKING_BACKEND:
268 return new MockBackend();
274 content::TestBrowserThreadBundle thread_bundle_;
276 scoped_ptr<LoginDatabase> login_db_;
277 scoped_ptr<TestingProfile> profile_;
278 base::ScopedTempDir temp_dir_;
281 ACTION(STLDeleteElements0) {
282 STLDeleteContainerPointers(arg0.begin(), arg0.end());
285 TEST_P(PasswordStoreXTest, Notifications) {
286 scoped_refptr<PasswordStoreX> store(
287 new PasswordStoreX(login_db_.release(),
292 PasswordFormData form_data =
293 { PasswordForm::SCHEME_HTML,
294 "http://bar.example.com",
295 "http://bar.example.com/origin",
296 "http://bar.example.com/action",
303 scoped_ptr<PasswordForm> form(CreatePasswordFormFromData(form_data));
305 DBThreadObserverHelper helper;
306 helper.Init(store.get());
308 const PasswordStoreChange expected_add_changes[] = {
309 PasswordStoreChange(PasswordStoreChange::ADD, *form),
314 Observe(int(chrome::NOTIFICATION_LOGINS_CHANGED),
315 content::Source<PasswordStore>(store.get()),
316 Property(&content::Details<const PasswordStoreChangeList>::ptr,
317 Pointee(ElementsAreArray(expected_add_changes)))));
319 // Adding a login should trigger a notification.
320 store->AddLogin(*form);
322 // The PasswordStore schedules tasks to run on the DB thread. Wait for them
324 base::RunLoop().RunUntilIdle();
326 // Change the password.
327 form->password_value = base::ASCIIToUTF16("a different password");
329 const PasswordStoreChange expected_update_changes[] = {
330 PasswordStoreChange(PasswordStoreChange::UPDATE, *form),
335 Observe(int(chrome::NOTIFICATION_LOGINS_CHANGED),
336 content::Source<PasswordStore>(store.get()),
337 Property(&content::Details<const PasswordStoreChangeList>::ptr,
338 Pointee(ElementsAreArray(expected_update_changes)))));
340 // Updating the login with the new password should trigger a notification.
341 store->UpdateLogin(*form);
343 // Wait for PasswordStore to send execute.
344 base::RunLoop().RunUntilIdle();
346 const PasswordStoreChange expected_delete_changes[] = {
347 PasswordStoreChange(PasswordStoreChange::REMOVE, *form),
352 Observe(int(chrome::NOTIFICATION_LOGINS_CHANGED),
353 content::Source<PasswordStore>(store.get()),
354 Property(&content::Details<const PasswordStoreChangeList>::ptr,
355 Pointee(ElementsAreArray(expected_delete_changes)))));
357 // Deleting the login should trigger a notification.
358 store->RemoveLogin(*form);
360 // Wait for PasswordStore to execute.
361 base::RunLoop().RunUntilIdle();
363 // Public in PasswordStore, protected in PasswordStoreX.
364 static_cast<PasswordStore*>(store.get())->ShutdownOnUIThread();
367 TEST_P(PasswordStoreXTest, NativeMigration) {
368 VectorOfForms expected_autofillable;
369 InitExpectedForms(true, 50, &expected_autofillable);
371 VectorOfForms expected_blacklisted;
372 InitExpectedForms(false, 50, &expected_blacklisted);
374 // Get the initial size of the login DB file, before we populate it.
375 // This will be used later to make sure it gets back to this size.
376 const base::FilePath login_db_file = temp_dir_.path().Append("login_test");
377 base::File::Info db_file_start_info;
378 ASSERT_TRUE(base::GetFileInfo(login_db_file, &db_file_start_info));
380 LoginDatabase* login_db = login_db_.get();
382 // Populate the login DB with logins that should be migrated.
383 for (VectorOfForms::iterator it = expected_autofillable.begin();
384 it != expected_autofillable.end(); ++it) {
385 login_db->AddLogin(**it);
387 for (VectorOfForms::iterator it = expected_blacklisted.begin();
388 it != expected_blacklisted.end(); ++it) {
389 login_db->AddLogin(**it);
392 // Get the new size of the login DB file. We expect it to be larger.
393 base::File::Info db_file_full_info;
394 ASSERT_TRUE(base::GetFileInfo(login_db_file, &db_file_full_info));
395 EXPECT_GT(db_file_full_info.size, db_file_start_info.size);
397 // Initializing the PasswordStore shouldn't trigger a native migration (yet).
398 scoped_refptr<PasswordStoreX> store(
399 new PasswordStoreX(login_db_.release(),
404 MockPasswordStoreConsumer consumer;
406 // The autofillable forms should have been migrated to the native backend.
407 EXPECT_CALL(consumer,
408 OnGetPasswordStoreResults(
409 ContainsAllPasswordForms(expected_autofillable)))
410 .WillOnce(WithArg<0>(STLDeleteElements0()));
412 store->GetAutofillableLogins(&consumer);
413 base::RunLoop().RunUntilIdle();
415 // The blacklisted forms should have been migrated to the native backend.
416 EXPECT_CALL(consumer,
417 OnGetPasswordStoreResults(ContainsAllPasswordForms(expected_blacklisted)))
418 .WillOnce(WithArg<0>(STLDeleteElements0()));
420 store->GetBlacklistLogins(&consumer);
421 base::RunLoop().RunUntilIdle();
424 MockLoginDatabaseReturn ld_return;
426 if (GetParam() == WORKING_BACKEND) {
427 // No autofillable logins should be left in the login DB.
428 EXPECT_CALL(ld_return,
429 OnLoginDatabaseQueryDone(ContainsAllPasswordForms(empty)));
431 // The autofillable logins should still be in the login DB.
432 EXPECT_CALL(ld_return,
433 OnLoginDatabaseQueryDone(
434 ContainsAllPasswordForms(expected_autofillable)))
435 .WillOnce(WithArg<0>(STLDeleteElements0()));
438 LoginDatabaseQueryCallback(login_db, true, &ld_return);
440 // Wait for the login DB methods to execute.
441 base::RunLoop().RunUntilIdle();
443 if (GetParam() == WORKING_BACKEND) {
444 // Likewise, no blacklisted logins should be left in the login DB.
445 EXPECT_CALL(ld_return,
446 OnLoginDatabaseQueryDone(ContainsAllPasswordForms(empty)));
448 // The blacklisted logins should still be in the login DB.
449 EXPECT_CALL(ld_return,
450 OnLoginDatabaseQueryDone(
451 ContainsAllPasswordForms(expected_blacklisted)))
452 .WillOnce(WithArg<0>(STLDeleteElements0()));
455 LoginDatabaseQueryCallback(login_db, false, &ld_return);
457 // Wait for the login DB methods to execute.
458 base::RunLoop().RunUntilIdle();
460 if (GetParam() == WORKING_BACKEND) {
461 // If the migration succeeded, then not only should there be no logins left
462 // in the login DB, but also the file should have been deleted and then
463 // recreated. We approximate checking for this by checking that the file
464 // size is equal to the size before we populated it, even though it was
465 // larger after populating it.
466 base::File::Info db_file_end_info;
467 ASSERT_TRUE(base::GetFileInfo(login_db_file, &db_file_end_info));
468 EXPECT_EQ(db_file_start_info.size, db_file_end_info.size);
471 STLDeleteElements(&expected_autofillable);
472 STLDeleteElements(&expected_blacklisted);
474 // Public in PasswordStore, protected in PasswordStoreX.
475 static_cast<PasswordStore*>(store.get())->ShutdownOnUIThread();
478 INSTANTIATE_TEST_CASE_P(NoBackend,
480 testing::Values(NO_BACKEND));
481 INSTANTIATE_TEST_CASE_P(FailingBackend,
483 testing::Values(FAILING_BACKEND));
484 INSTANTIATE_TEST_CASE_P(WorkingBackend,
486 testing::Values(WORKING_BACKEND));