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/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"
28 using autofill::PasswordForm
;
29 using content::BrowserThread
;
30 using password_manager::ContainsAllPasswordForms
;
31 using password_manager::PasswordStoreChange
;
32 using password_manager::PasswordStoreChangeList
;
35 using testing::ElementsAreArray
;
36 using testing::Pointee
;
37 using testing::Property
;
38 using testing::WithArg
;
40 typedef std::vector
<PasswordForm
*> VectorOfForms
;
44 class MockPasswordStoreConsumer
45 : public password_manager::PasswordStoreConsumer
{
47 MOCK_METHOD1(OnGetPasswordStoreResults
,
48 void(const std::vector
<PasswordForm
*>&));
51 class MockPasswordStoreObserver
52 : public password_manager::PasswordStore::Observer
{
54 MOCK_METHOD1(OnLoginsChanged
,
55 void(const password_manager::PasswordStoreChangeList
& changes
));
58 class FailingBackend
: public PasswordStoreX::NativeBackend
{
60 virtual bool Init() OVERRIDE
{ return true; }
62 virtual PasswordStoreChangeList
AddLogin(const PasswordForm
& form
) OVERRIDE
{
63 return PasswordStoreChangeList();
65 virtual bool UpdateLogin(const PasswordForm
& form
,
66 PasswordStoreChangeList
* changes
) OVERRIDE
{
69 virtual bool RemoveLogin(const PasswordForm
& form
) OVERRIDE
{ return false; }
71 virtual bool RemoveLoginsCreatedBetween(
72 base::Time delete_begin
,
73 base::Time delete_end
,
74 password_manager::PasswordStoreChangeList
* changes
) OVERRIDE
{
78 virtual bool RemoveLoginsSyncedBetween(
79 base::Time delete_begin
,
80 base::Time delete_end
,
81 password_manager::PasswordStoreChangeList
* changes
) OVERRIDE
{
85 virtual bool GetLogins(const PasswordForm
& form
,
86 PasswordFormList
* forms
) OVERRIDE
{
90 virtual bool GetAutofillableLogins(PasswordFormList
* forms
) OVERRIDE
{
93 virtual bool GetBlacklistLogins(PasswordFormList
* forms
) OVERRIDE
{
98 class MockBackend
: public PasswordStoreX::NativeBackend
{
100 virtual bool Init() OVERRIDE
{ return true; }
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
);
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
,
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))
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
))
138 virtual 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 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
]));
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
]));
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
]));
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();
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
)
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
;
194 std::vector
<PasswordForm
> all_forms_
;
197 class MockLoginDatabaseReturn
{
199 MOCK_METHOD1(OnLoginDatabaseQueryDone
,
200 void(const std::vector
<PasswordForm
*>&));
203 void LoginDatabaseQueryCallback(password_manager::LoginDatabase
* login_db
,
205 MockLoginDatabaseReturn
* mock_return
) {
206 std::vector
<PasswordForm
*> forms
;
208 login_db
->GetAutofillableLogins(&forms
);
210 login_db
->GetBlacklistLogins(&forms
);
211 mock_return
->OnLoginDatabaseQueryDone(forms
);
214 // Generate |count| expected logins, either auto-fillable or blacklisted.
215 void 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",
221 std::string action
= base::StringPrintf("http://%zu.%s.com/action",
223 password_manager::PasswordFormData data
= {
224 PasswordForm::SCHEME_HTML
,
231 autofillable
? L
"username_value" : NULL
,
232 autofillable
? L
"password_value" : NULL
,
235 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 login_db_
.reset(new password_manager::LoginDatabase());
254 ASSERT_TRUE(login_db_
->Init(temp_dir_
.path().Append("login_test")));
257 virtual void TearDown() {
258 base::RunLoop().RunUntilIdle();
261 PasswordStoreX::NativeBackend
* GetBackend() {
262 switch (GetParam()) {
263 case FAILING_BACKEND
:
264 return new FailingBackend();
265 case WORKING_BACKEND
:
266 return new MockBackend();
272 content::TestBrowserThreadBundle thread_bundle_
;
274 scoped_ptr
<password_manager::LoginDatabase
> login_db_
;
275 base::ScopedTempDir temp_dir_
;
278 ACTION(STLDeleteElements0
) {
279 STLDeleteContainerPointers(arg0
.begin(), arg0
.end());
282 TEST_P(PasswordStoreXTest
, Notifications
) {
283 scoped_refptr
<PasswordStoreX
> store(
284 new PasswordStoreX(base::MessageLoopProxy::current(),
285 base::MessageLoopProxy::current(),
288 store
->Init(syncer::SyncableService::StartSyncFlare(), "");
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,
297 scoped_ptr
<PasswordForm
> form(CreatePasswordFormFromData(form_data
));
299 MockPasswordStoreObserver observer
;
300 store
->AddObserver(&observer
);
302 const PasswordStoreChange expected_add_changes
[] = {
303 PasswordStoreChange(PasswordStoreChange::ADD
, *form
),
308 OnLoginsChanged(ElementsAreArray(expected_add_changes
)));
310 // Adding a login should trigger a notification.
311 store
->AddLogin(*form
);
313 // The PasswordStore schedules tasks to run on the DB thread. Wait for them
315 base::RunLoop().RunUntilIdle();
317 // Change the password.
318 form
->password_value
= base::ASCIIToUTF16("a different password");
320 const PasswordStoreChange expected_update_changes
[] = {
321 PasswordStoreChange(PasswordStoreChange::UPDATE
, *form
),
326 OnLoginsChanged(ElementsAreArray(expected_update_changes
)));
328 // Updating the login with the new password should trigger a notification.
329 store
->UpdateLogin(*form
);
331 // Wait for PasswordStore to send execute.
332 base::RunLoop().RunUntilIdle();
334 const PasswordStoreChange expected_delete_changes
[] = {
335 PasswordStoreChange(PasswordStoreChange::REMOVE
, *form
),
340 OnLoginsChanged(ElementsAreArray(expected_delete_changes
)));
342 // Deleting the login should trigger a notification.
343 store
->RemoveLogin(*form
);
345 // Wait for PasswordStore to execute.
346 base::RunLoop().RunUntilIdle();
348 store
->RemoveObserver(&observer
);
353 TEST_P(PasswordStoreXTest
, NativeMigration
) {
354 VectorOfForms expected_autofillable
;
355 InitExpectedForms(true, 50, &expected_autofillable
);
357 VectorOfForms expected_blacklisted
;
358 InitExpectedForms(false, 50, &expected_blacklisted
);
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
));
366 password_manager::LoginDatabase
* login_db
= login_db_
.get();
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
);
373 for (VectorOfForms::iterator it
= expected_blacklisted
.begin();
374 it
!= expected_blacklisted
.end(); ++it
) {
375 login_db
->AddLogin(**it
);
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
);
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(),
389 store
->Init(syncer::SyncableService::StartSyncFlare(), "");
391 MockPasswordStoreConsumer consumer
;
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()));
399 store
->GetAutofillableLogins(&consumer
);
400 base::RunLoop().RunUntilIdle();
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()));
407 store
->GetBlacklistLogins(&consumer
);
408 base::RunLoop().RunUntilIdle();
411 MockLoginDatabaseReturn ld_return
;
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
)));
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()));
425 LoginDatabaseQueryCallback(login_db
, true, &ld_return
);
427 // Wait for the login DB methods to execute.
428 base::RunLoop().RunUntilIdle();
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
)));
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()));
442 LoginDatabaseQueryCallback(login_db
, false, &ld_return
);
444 // Wait for the login DB methods to execute.
445 base::RunLoop().RunUntilIdle();
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
);
458 STLDeleteElements(&expected_autofillable
);
459 STLDeleteElements(&expected_blacklisted
);
464 INSTANTIATE_TEST_CASE_P(NoBackend
,
466 testing::Values(NO_BACKEND
));
467 INSTANTIATE_TEST_CASE_P(FailingBackend
,
469 testing::Values(FAILING_BACKEND
));
470 INSTANTIATE_TEST_CASE_P(WorkingBackend
,
472 testing::Values(WORKING_BACKEND
));