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/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_manager_test_utils.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"
28 using autofill::PasswordForm
;
29 using password_manager::ContainsSamePasswordForms
;
30 using password_manager::PasswordStoreChange
;
31 using password_manager::PasswordStoreChangeList
;
32 using testing::ElementsAreArray
;
33 using testing::IsEmpty
;
37 class MockPasswordStoreConsumer
38 : public password_manager::PasswordStoreConsumer
{
40 MOCK_METHOD1(OnGetPasswordStoreResultsConstRef
,
41 void(const std::vector
<PasswordForm
*>&));
43 // GMock cannot mock methods with move-only args.
44 void OnGetPasswordStoreResults(ScopedVector
<PasswordForm
> results
) override
{
45 OnGetPasswordStoreResultsConstRef(results
.get());
49 class MockPasswordStoreObserver
50 : public password_manager::PasswordStore::Observer
{
52 MOCK_METHOD1(OnLoginsChanged
,
53 void(const password_manager::PasswordStoreChangeList
& changes
));
56 class FailingBackend
: public PasswordStoreX::NativeBackend
{
58 bool Init() override
{ return true; }
60 PasswordStoreChangeList
AddLogin(const PasswordForm
& form
) override
{
61 return PasswordStoreChangeList();
63 bool UpdateLogin(const PasswordForm
& form
,
64 PasswordStoreChangeList
* changes
) override
{
67 bool RemoveLogin(const PasswordForm
& form
) override
{ return false; }
69 bool RemoveLoginsCreatedBetween(
70 base::Time delete_begin
,
71 base::Time delete_end
,
72 password_manager::PasswordStoreChangeList
* changes
) override
{
76 bool RemoveLoginsSyncedBetween(
77 base::Time delete_begin
,
78 base::Time delete_end
,
79 password_manager::PasswordStoreChangeList
* changes
) override
{
83 bool GetLogins(const PasswordForm
& form
,
84 ScopedVector
<autofill::PasswordForm
>* forms
) override
{
88 bool GetAutofillableLogins(
89 ScopedVector
<autofill::PasswordForm
>* forms
) override
{
92 bool GetBlacklistLogins(
93 ScopedVector
<autofill::PasswordForm
>* forms
) override
{
98 class MockBackend
: public PasswordStoreX::NativeBackend
{
100 bool Init() override
{ return true; }
102 PasswordStoreChangeList
AddLogin(const PasswordForm
& form
) override
{
103 all_forms_
.push_back(form
);
104 PasswordStoreChange
change(PasswordStoreChange::ADD
, form
);
105 return PasswordStoreChangeList(1, change
);
108 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
,
119 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))
126 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
))
138 bool RemoveLoginsSyncedBetween(
139 base::Time delete_begin
,
140 base::Time delete_end
,
141 password_manager::PasswordStoreChangeList
* changes
) override
{
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
]));
154 bool GetLogins(const PasswordForm
& form
,
155 ScopedVector
<autofill::PasswordForm
>* 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
]));
162 bool GetAutofillableLogins(
163 ScopedVector
<autofill::PasswordForm
>* forms
) override
{
164 for (size_t i
= 0; i
< all_forms_
.size(); ++i
)
165 if (!all_forms_
[i
].blacklisted_by_user
)
166 forms
->push_back(new PasswordForm(all_forms_
[i
]));
170 bool GetBlacklistLogins(
171 ScopedVector
<autofill::PasswordForm
>* 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(password_manager::LoginDatabase
* login_db
,
207 MockLoginDatabaseReturn
* mock_return
) {
208 ScopedVector
<autofill::PasswordForm
> forms
;
210 login_db
->GetAutofillableLogins(&forms
);
212 login_db
->GetBlacklistLogins(&forms
);
213 mock_return
->OnLoginDatabaseQueryDone(forms
.get());
216 // Generate |count| expected logins, either auto-fillable or blacklisted.
217 void InitExpectedForms(bool autofillable
,
219 ScopedVector
<autofill::PasswordForm
>* forms
) {
220 const char* domain
= autofillable
? "example" : "blacklisted";
221 for (size_t i
= 0; i
< count
; ++i
) {
222 std::string realm
= base::StringPrintf("http://%zu.%s.com", i
, domain
);
223 std::string origin
= base::StringPrintf("http://%zu.%s.com/origin",
225 std::string action
= base::StringPrintf("http://%zu.%s.com/action",
227 password_manager::PasswordFormData data
= {
228 PasswordForm::SCHEME_HTML
,
235 autofillable
? L
"username_value" : nullptr,
236 autofillable
? L
"password_value" : nullptr,
239 static_cast<double>(i
+ 1)};
240 forms
->push_back(CreatePasswordFormFromDataForTesting(data
).release());
244 PasswordStoreChangeList
AddChangeForForm(const PasswordForm
& form
) {
245 return PasswordStoreChangeList(
246 1, PasswordStoreChange(PasswordStoreChange::ADD
, form
));
249 } // anonymous namespace
257 class PasswordStoreXTest
: public testing::TestWithParam
<BackendType
> {
259 void SetUp() override
{
260 ASSERT_TRUE(temp_dir_
.CreateUniqueTempDir());
263 void TearDown() override
{ base::RunLoop().RunUntilIdle(); }
265 base::FilePath
test_login_db_file_path() const {
266 return temp_dir_
.path().Append(FILE_PATH_LITERAL("login_test"));
269 PasswordStoreX::NativeBackend
* GetBackend() {
270 switch (GetParam()) {
271 case FAILING_BACKEND
:
272 return new FailingBackend();
273 case WORKING_BACKEND
:
274 return new MockBackend();
280 content::TestBrowserThreadBundle thread_bundle_
;
282 base::ScopedTempDir temp_dir_
;
285 ACTION(STLDeleteElements0
) {
286 STLDeleteContainerPointers(arg0
.begin(), arg0
.end());
289 TEST_P(PasswordStoreXTest
, Notifications
) {
290 scoped_ptr
<password_manager::LoginDatabase
> login_db(
291 new password_manager::LoginDatabase(test_login_db_file_path()));
292 scoped_refptr
<PasswordStoreX
> store(new PasswordStoreX(
293 base::MessageLoopProxy::current(), base::MessageLoopProxy::current(),
294 login_db
.Pass(), GetBackend()));
295 store
->Init(syncer::SyncableService::StartSyncFlare());
297 password_manager::PasswordFormData form_data
= {
298 PasswordForm::SCHEME_HTML
, "http://bar.example.com",
299 "http://bar.example.com/origin", "http://bar.example.com/action",
300 L
"submit_element", L
"username_element",
301 L
"password_element", L
"username_value",
302 L
"password_value", true,
304 scoped_ptr
<PasswordForm
> form
=
305 CreatePasswordFormFromDataForTesting(form_data
);
307 MockPasswordStoreObserver observer
;
308 store
->AddObserver(&observer
);
310 const PasswordStoreChange expected_add_changes
[] = {
311 PasswordStoreChange(PasswordStoreChange::ADD
, *form
),
316 OnLoginsChanged(ElementsAreArray(expected_add_changes
)));
318 // Adding a login should trigger a notification.
319 store
->AddLogin(*form
);
321 // The PasswordStore schedules tasks to run on the DB thread. Wait for them
323 base::RunLoop().RunUntilIdle();
325 // Change the password.
326 form
->password_value
= base::ASCIIToUTF16("a different password");
328 const PasswordStoreChange expected_update_changes
[] = {
329 PasswordStoreChange(PasswordStoreChange::UPDATE
, *form
),
334 OnLoginsChanged(ElementsAreArray(expected_update_changes
)));
336 // Updating the login with the new password should trigger a notification.
337 store
->UpdateLogin(*form
);
339 // Wait for PasswordStore to send execute.
340 base::RunLoop().RunUntilIdle();
342 const PasswordStoreChange expected_delete_changes
[] = {
343 PasswordStoreChange(PasswordStoreChange::REMOVE
, *form
),
348 OnLoginsChanged(ElementsAreArray(expected_delete_changes
)));
350 // Deleting the login should trigger a notification.
351 store
->RemoveLogin(*form
);
353 // Wait for PasswordStore to execute.
354 base::RunLoop().RunUntilIdle();
356 store
->RemoveObserver(&observer
);
361 TEST_P(PasswordStoreXTest
, NativeMigration
) {
362 ScopedVector
<autofill::PasswordForm
> expected_autofillable
;
363 InitExpectedForms(true, 50, &expected_autofillable
);
365 ScopedVector
<autofill::PasswordForm
> expected_blacklisted
;
366 InitExpectedForms(false, 50, &expected_blacklisted
);
368 const base::FilePath login_db_file
= test_login_db_file_path();
369 scoped_ptr
<password_manager::LoginDatabase
> login_db(
370 new password_manager::LoginDatabase(login_db_file
));
371 ASSERT_TRUE(login_db
->Init());
373 // Get the initial size of the login DB file, before we populate it.
374 // This will be used later to make sure it gets back to this size.
375 base::File::Info db_file_start_info
;
376 ASSERT_TRUE(base::GetFileInfo(login_db_file
, &db_file_start_info
));
378 // Populate the login DB with logins that should be migrated.
379 for (const autofill::PasswordForm
* form
: expected_autofillable
) {
380 EXPECT_EQ(AddChangeForForm(*form
), login_db
->AddLogin(*form
));
382 for (const autofill::PasswordForm
* form
: expected_blacklisted
) {
383 EXPECT_EQ(AddChangeForForm(*form
), login_db
->AddLogin(*form
));
386 // Get the new size of the login DB file. We expect it to be larger.
387 base::File::Info db_file_full_info
;
388 ASSERT_TRUE(base::GetFileInfo(login_db_file
, &db_file_full_info
));
389 EXPECT_GT(db_file_full_info
.size
, db_file_start_info
.size
);
391 // Initializing the PasswordStore shouldn't trigger a native migration (yet).
392 login_db
.reset(new password_manager::LoginDatabase(login_db_file
));
393 scoped_refptr
<PasswordStoreX
> store(new PasswordStoreX(
394 base::MessageLoopProxy::current(), base::MessageLoopProxy::current(),
395 login_db
.Pass(), GetBackend()));
396 store
->Init(syncer::SyncableService::StartSyncFlare());
398 MockPasswordStoreConsumer consumer
;
400 // The autofillable forms should have been migrated to the native backend.
401 EXPECT_CALL(consumer
,
402 OnGetPasswordStoreResultsConstRef(
403 ContainsSamePasswordForms(expected_autofillable
.get())));
405 store
->GetAutofillableLogins(&consumer
);
406 base::RunLoop().RunUntilIdle();
408 // The blacklisted forms should have been migrated to the native backend.
409 EXPECT_CALL(consumer
,
410 OnGetPasswordStoreResultsConstRef(
411 ContainsSamePasswordForms(expected_blacklisted
.get())));
413 store
->GetBlacklistLogins(&consumer
);
414 base::RunLoop().RunUntilIdle();
416 MockLoginDatabaseReturn ld_return
;
418 if (GetParam() == WORKING_BACKEND
) {
419 // No autofillable logins should be left in the login DB.
420 EXPECT_CALL(ld_return
, OnLoginDatabaseQueryDone(IsEmpty()));
422 // The autofillable logins should still be in the login DB.
423 EXPECT_CALL(ld_return
, OnLoginDatabaseQueryDone(ContainsSamePasswordForms(
424 expected_autofillable
.get())));
427 LoginDatabaseQueryCallback(store
->login_db(), true, &ld_return
);
429 // Wait for the login DB methods to execute.
430 base::RunLoop().RunUntilIdle();
432 if (GetParam() == WORKING_BACKEND
) {
433 // Likewise, no blacklisted logins should be left in the login DB.
434 EXPECT_CALL(ld_return
, OnLoginDatabaseQueryDone(IsEmpty()));
436 // The blacklisted logins should still be in the login DB.
437 EXPECT_CALL(ld_return
, OnLoginDatabaseQueryDone(ContainsSamePasswordForms(
438 expected_blacklisted
.get())));
441 LoginDatabaseQueryCallback(store
->login_db(), false, &ld_return
);
443 // Wait for the login DB methods to execute.
444 base::RunLoop().RunUntilIdle();
446 if (GetParam() == WORKING_BACKEND
) {
447 // If the migration succeeded, then not only should there be no logins left
448 // in the login DB, but also the file should have been deleted and then
449 // recreated. We approximate checking for this by checking that the file
450 // size is equal to the size before we populated it, even though it was
451 // larger after populating it.
452 base::File::Info db_file_end_info
;
453 ASSERT_TRUE(base::GetFileInfo(login_db_file
, &db_file_end_info
));
454 EXPECT_EQ(db_file_start_info
.size
, db_file_end_info
.size
);
460 INSTANTIATE_TEST_CASE_P(NoBackend
,
462 testing::Values(NO_BACKEND
));
463 INSTANTIATE_TEST_CASE_P(FailingBackend
,
465 testing::Values(FAILING_BACKEND
));
466 INSTANTIATE_TEST_CASE_P(WorkingBackend
,
468 testing::Values(WORKING_BACKEND
));