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_METHOD2(OnPasswordStoreRequestDone
,
51 void(CancelableRequestProvider::Handle
,
52 const std::vector
<PasswordForm
*>&));
53 MOCK_METHOD1(OnGetPasswordStoreResults
,
54 void(const std::vector
<PasswordForm
*>&));
57 // This class will add and remove a mock notification observer from
59 class DBThreadObserverHelper
{
61 DBThreadObserverHelper() {}
63 ~DBThreadObserverHelper() {
64 registrar_
.RemoveAll();
67 void Init(PasswordStore
* password_store
) {
68 registrar_
.Add(&observer_
,
69 chrome::NOTIFICATION_LOGINS_CHANGED
,
70 content::Source
<PasswordStore
>(password_store
));
73 content::MockNotificationObserver
& observer() {
78 content::NotificationRegistrar registrar_
;
79 content::MockNotificationObserver observer_
;
82 class FailingBackend
: public PasswordStoreX::NativeBackend
{
84 virtual bool Init() OVERRIDE
{ return true; }
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; }
90 virtual bool RemoveLoginsCreatedBetween(
91 const base::Time
& delete_begin
,
92 const base::Time
& delete_end
) OVERRIDE
{
96 virtual bool GetLogins(const PasswordForm
& form
,
97 PasswordFormList
* forms
) OVERRIDE
{
101 virtual bool GetLoginsCreatedBetween(const base::Time
& get_begin
,
102 const base::Time
& get_end
,
103 PasswordFormList
* forms
) OVERRIDE
{
107 virtual bool GetAutofillableLogins(PasswordFormList
* forms
) OVERRIDE
{
110 virtual bool GetBlacklistLogins(PasswordFormList
* forms
) OVERRIDE
{
115 class MockBackend
: public PasswordStoreX::NativeBackend
{
117 virtual bool Init() OVERRIDE
{ return true; }
119 virtual bool AddLogin(const PasswordForm
& form
) OVERRIDE
{
120 all_forms_
.push_back(form
);
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
;
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))
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
))
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
]));
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
]));
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
]));
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
]));
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();
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
)
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
;
199 std::vector
<PasswordForm
> all_forms_
;
202 class MockLoginDatabaseReturn
{
204 MOCK_METHOD1(OnLoginDatabaseQueryDone
,
205 void(const std::vector
<PasswordForm
*>&));
208 void LoginDatabaseQueryCallback(LoginDatabase
* login_db
,
210 MockLoginDatabaseReturn
* mock_return
) {
211 std::vector
<PasswordForm
*> forms
;
213 login_db
->GetAutofillableLogins(&forms
);
215 login_db
->GetBlacklistLogins(&forms
);
216 mock_return
->OnLoginDatabaseQueryDone(forms
);
219 // Generate |count| expected logins, either auto-fillable or blacklisted.
220 void 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",
226 std::string action
= base::StringPrintf("http://%zu.%s.com/action",
228 PasswordFormData data
= {
229 PasswordForm::SCHEME_HTML
,
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
));
243 } // anonymous namespace
251 class PasswordStoreXTest
: public testing::TestWithParam
<BackendType
> {
253 virtual void SetUp() {
254 ASSERT_TRUE(temp_dir_
.CreateUniqueTempDir());
256 profile_
.reset(new TestingProfile());
258 login_db_
.reset(new LoginDatabase());
259 ASSERT_TRUE(login_db_
->Init(temp_dir_
.path().Append("login_test")));
262 virtual void TearDown() {
263 base::RunLoop().RunUntilIdle();
266 PasswordStoreX::NativeBackend
* GetBackend() {
267 switch (GetParam()) {
268 case FAILING_BACKEND
:
269 return new FailingBackend();
270 case WORKING_BACKEND
:
271 return new MockBackend();
277 content::TestBrowserThreadBundle thread_bundle_
;
279 scoped_ptr
<LoginDatabase
> login_db_
;
280 scoped_ptr
<TestingProfile
> profile_
;
281 base::ScopedTempDir temp_dir_
;
284 ACTION(STLDeleteElements0
) {
285 STLDeleteContainerPointers(arg0
.begin(), arg0
.end());
288 TEST_P(PasswordStoreXTest
, Notifications
) {
289 scoped_refptr
<PasswordStoreX
> store(
290 new PasswordStoreX(login_db_
.release(),
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",
306 scoped_ptr
<PasswordForm
> form(CreatePasswordFormFromData(form_data
));
308 DBThreadObserverHelper helper
;
309 helper
.Init(store
.get());
311 const PasswordStoreChange expected_add_changes
[] = {
312 PasswordStoreChange(PasswordStoreChange::ADD
, *form
),
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
)))));
322 // Adding a login should trigger a notification.
323 store
->AddLogin(*form
);
325 // The PasswordStore schedules tasks to run on the DB thread. Wait for them
327 base::RunLoop().RunUntilIdle();
329 // Change the password.
330 form
->password_value
= base::ASCIIToUTF16("a different password");
332 const PasswordStoreChange expected_update_changes
[] = {
333 PasswordStoreChange(PasswordStoreChange::UPDATE
, *form
),
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
)))));
343 // Updating the login with the new password should trigger a notification.
344 store
->UpdateLogin(*form
);
346 // Wait for PasswordStore to send execute.
347 base::RunLoop().RunUntilIdle();
349 const PasswordStoreChange expected_delete_changes
[] = {
350 PasswordStoreChange(PasswordStoreChange::REMOVE
, *form
),
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
)))));
360 // Deleting the login should trigger a notification.
361 store
->RemoveLogin(*form
);
363 // Wait for PasswordStore to execute.
364 base::RunLoop().RunUntilIdle();
366 // Public in PasswordStore, protected in PasswordStoreX.
367 static_cast<PasswordStore
*>(store
.get())->ShutdownOnUIThread();
370 TEST_P(PasswordStoreXTest
, NativeMigration
) {
371 VectorOfForms expected_autofillable
;
372 InitExpectedForms(true, 50, &expected_autofillable
);
374 VectorOfForms expected_blacklisted
;
375 InitExpectedForms(false, 50, &expected_blacklisted
);
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::File::Info db_file_start_info
;
381 ASSERT_TRUE(base::GetFileInfo(login_db_file
, &db_file_start_info
));
383 LoginDatabase
* login_db
= login_db_
.get();
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
);
390 for (VectorOfForms::iterator it
= expected_blacklisted
.begin();
391 it
!= expected_blacklisted
.end(); ++it
) {
392 login_db
->AddLogin(**it
);
395 // Get the new size of the login DB file. We expect it to be larger.
396 base::File::Info 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
);
400 // Initializing the PasswordStore shouldn't trigger a native migration (yet).
401 scoped_refptr
<PasswordStoreX
> store(
402 new PasswordStoreX(login_db_
.release(),
407 MockPasswordStoreConsumer consumer
;
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()));
415 store
->GetAutofillableLogins(&consumer
);
416 base::RunLoop().RunUntilIdle();
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()));
424 store
->GetBlacklistLogins(&consumer
);
425 base::RunLoop().RunUntilIdle();
428 MockLoginDatabaseReturn ld_return
;
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
)));
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()));
442 LoginDatabaseQueryCallback(login_db
, true, &ld_return
);
444 // Wait for the login DB methods to execute.
445 base::RunLoop().RunUntilIdle();
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
)));
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()));
459 LoginDatabaseQueryCallback(login_db
, false, &ld_return
);
461 // Wait for the login DB methods to execute.
462 base::RunLoop().RunUntilIdle();
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::File::Info 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
);
475 STLDeleteElements(&expected_autofillable
);
476 STLDeleteElements(&expected_blacklisted
);
478 // Public in PasswordStore, protected in PasswordStoreX.
479 static_cast<PasswordStore
*>(store
.get())->ShutdownOnUIThread();
482 INSTANTIATE_TEST_CASE_P(NoBackend
,
484 testing::Values(NO_BACKEND
));
485 INSTANTIATE_TEST_CASE_P(FailingBackend
,
487 testing::Values(FAILING_BACKEND
));
488 INSTANTIATE_TEST_CASE_P(WorkingBackend
,
490 testing::Values(WORKING_BACKEND
));