1 // Copyright (c) 2015 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.
7 #include "base/basictypes.h"
8 #include "base/prefs/pref_service.h"
9 #include "base/stl_util.h"
10 #include "base/strings/string_number_conversions.h"
11 #include "base/strings/string_util.h"
12 #include "base/strings/stringprintf.h"
13 #include "base/strings/utf_string_conversions.h"
14 #include "base/time/time.h"
15 #include "chrome/browser/password_manager/native_backend_libsecret.h"
16 #include "chrome/test/base/testing_profile.h"
17 #include "components/autofill/core/common/password_form.h"
18 #include "components/password_manager/core/browser/psl_matching_helper.h"
19 #include "components/password_manager/core/common/password_manager_pref_names.h"
20 #include "testing/gtest/include/gtest/gtest.h"
22 using autofill::PasswordForm
;
23 using base::UTF8ToUTF16
;
24 using base::UTF16ToUTF8
;
25 using password_manager::PasswordStoreChange
;
26 using password_manager::PasswordStoreChangeList
;
30 // What follows is a very simple implementation of the subset of the Libsecret
31 // API that we actually use. It gets substituted for the real one by
32 // MockLibsecretLoader, which hooks into the facility normally used to load
33 // the libsecret library at runtime to avoid a static dependency on it.
35 struct MockSecretValue
{
37 explicit MockSecretValue(gchar
* password
) : password(password
) {}
38 ~MockSecretValue() { g_free(password
); }
41 struct MockSecretItem
{
42 MockSecretValue
* value
;
43 GHashTable
* attributes
;
45 MockSecretItem(MockSecretValue
* value
, GHashTable
* attributes
)
46 : value(value
), attributes(attributes
) {}
49 g_hash_table_destroy(attributes
);
52 void RemoveAttribute(const char* keyname
) {
53 g_hash_table_remove(attributes
, keyname
);
57 bool Matches(MockSecretItem
* item
, GHashTable
* query
) {
58 GHashTable
* attributes
= item
->attributes
;
62 g_hash_table_iter_init(&iter
, query
);
64 while (g_hash_table_iter_next(&iter
, reinterpret_cast<gpointer
*>(&name
),
65 reinterpret_cast<gpointer
*>(&query_value
))) {
66 gchar
* value
= static_cast<gchar
*>(g_hash_table_lookup(attributes
, name
));
67 if (value
== nullptr || strcmp(value
, query_value
) != 0)
73 bool IsStringAttribute(const SecretSchema
* schema
, const std::string
& name
) {
74 for (size_t i
= 0; schema
->attributes
[i
].name
; ++i
)
75 if (name
== schema
->attributes
[i
].name
)
76 return schema
->attributes
[i
].type
== SECRET_SCHEMA_ATTRIBUTE_STRING
;
77 NOTREACHED() << "Requested type of nonexistent attribute";
81 // The list of all libsecret items we have stored.
82 ScopedVector
<MockSecretItem
>* global_mock_libsecret_items
;
83 bool global_mock_libsecret_reject_local_ids
= false;
85 gboolean
mock_secret_password_store_sync(const SecretSchema
* schema
,
86 const gchar
* collection
,
88 const gchar
* password
,
89 GCancellable
* cancellable
,
92 GHashTable
* attributes
=
93 g_hash_table_new_full(g_str_hash
, g_str_equal
, g_free
, g_free
);
97 while ((name
= va_arg(ap
, gchar
*))) {
99 if (IsStringAttribute(schema
, name
)) {
100 value
= g_strdup(va_arg(ap
, gchar
*));
101 VLOG(1) << "Adding item attribute " << name
<< ", value '" << value
104 uint32_t intvalue
= va_arg(ap
, uint32_t);
105 VLOG(1) << "Adding item attribute " << name
<< ", value " << intvalue
;
106 value
= g_strdup_printf("%u", intvalue
);
108 g_hash_table_insert(attributes
, g_strdup(name
), value
);
111 MockSecretValue
* secret_value
= new MockSecretValue(g_strdup(password
));
112 MockSecretItem
* item
= new MockSecretItem(secret_value
, attributes
);
113 global_mock_libsecret_items
->push_back(item
);
117 GList
* mock_secret_service_search_sync(SecretService
* service
,
118 const SecretSchema
* schema
,
119 GHashTable
* attributes
,
120 SecretSearchFlags flags
,
121 GCancellable
* cancellable
,
123 GList
* result
= nullptr;
124 for (MockSecretItem
* item
: *global_mock_libsecret_items
) {
125 if (Matches(item
, attributes
))
126 result
= g_list_append(result
, item
);
131 gboolean
mock_secret_password_clear_sync(const SecretSchema
* schema
,
132 GCancellable
* cancellable
,
135 GHashTable
* attributes
=
136 g_hash_table_new_full(g_str_hash
, g_str_equal
, g_free
, g_free
);
140 while ((name
= va_arg(ap
, gchar
*))) {
142 if (IsStringAttribute(schema
, name
)) {
143 value
= g_strdup(va_arg(ap
, gchar
*));
144 VLOG(1) << "Adding item attribute " << name
<< ", value '" << value
147 uint32_t intvalue
= va_arg(ap
, uint32_t);
148 VLOG(1) << "Adding item attribute " << name
<< ", value " << intvalue
;
149 value
= g_strdup_printf("%u", intvalue
);
151 g_hash_table_insert(attributes
, g_strdup(name
), value
);
154 for (uint32_t i
= 0; i
< global_mock_libsecret_items
->size();)
155 if (Matches((*global_mock_libsecret_items
)[i
], attributes
)) {
156 global_mock_libsecret_items
->erase(global_mock_libsecret_items
->begin() +
161 g_hash_table_unref(attributes
);
165 MockSecretValue
* mock_secret_item_get_secret(MockSecretItem
* self
) {
169 const gchar
* mock_secret_value_get_text(MockSecretValue
* value
) {
170 return value
->password
;
173 GHashTable
* mock_secret_item_get_attributes(MockSecretItem
* self
) {
174 // Libsecret backend will make unreference of received attributes, so in
175 // order to save them we need to increase their reference number.
176 g_hash_table_ref(self
->attributes
);
177 return self
->attributes
;
180 gboolean
mock_secret_item_load_secret_sync(MockSecretItem
* self
,
181 GCancellable
* cancellable
,
186 void mock_secret_value_unref(gpointer value
) {
189 // Inherit to get access to protected fields.
190 class MockLibsecretLoader
: public LibsecretLoader
{
192 static bool LoadMockLibsecret() {
193 secret_password_store_sync
= &mock_secret_password_store_sync
;
194 secret_service_search_sync
= &mock_secret_service_search_sync
;
195 secret_password_clear_sync
= &mock_secret_password_clear_sync
;
196 secret_item_get_secret
=
197 (decltype(&::secret_item_get_secret
)) & mock_secret_item_get_secret
;
198 secret_value_get_text
=
199 (decltype(&::secret_value_get_text
)) & mock_secret_value_get_text
;
200 secret_item_get_attributes
= (decltype(&::secret_item_get_attributes
)) &
201 mock_secret_item_get_attributes
;
202 secret_item_load_secret_sync
= (decltype(&::secret_item_load_secret_sync
)) &
203 mock_secret_item_load_secret_sync
;
205 (decltype(&::secret_value_unref
)) & mock_secret_value_unref
;
206 libsecret_loaded
= true;
207 // Reset the state of the mock library.
208 global_mock_libsecret_items
->clear();
209 global_mock_libsecret_reject_local_ids
= false;
214 void CheckPasswordChanges(const PasswordStoreChangeList
& expected_list
,
215 const PasswordStoreChangeList
& actual_list
) {
216 ASSERT_EQ(expected_list
.size(), actual_list
.size());
217 for (size_t i
= 0; i
< expected_list
.size(); ++i
) {
218 EXPECT_EQ(expected_list
[i
].type(), actual_list
[i
].type());
219 const PasswordForm
& expected
= expected_list
[i
].form();
220 const PasswordForm
& actual
= actual_list
[i
].form();
222 EXPECT_EQ(expected
.origin
, actual
.origin
);
223 EXPECT_EQ(expected
.password_value
, actual
.password_value
);
224 EXPECT_EQ(expected
.action
, actual
.action
);
225 EXPECT_EQ(expected
.username_element
, actual
.username_element
);
226 EXPECT_EQ(expected
.username_value
, actual
.username_value
);
227 EXPECT_EQ(expected
.password_element
, actual
.password_element
);
228 EXPECT_EQ(expected
.submit_element
, actual
.submit_element
);
229 EXPECT_EQ(expected
.signon_realm
, actual
.signon_realm
);
230 EXPECT_EQ(expected
.ssl_valid
, actual
.ssl_valid
);
231 EXPECT_EQ(expected
.preferred
, actual
.preferred
);
232 EXPECT_EQ(expected
.date_created
, actual
.date_created
);
233 EXPECT_EQ(expected
.blacklisted_by_user
, actual
.blacklisted_by_user
);
234 EXPECT_EQ(expected
.type
, actual
.type
);
235 EXPECT_EQ(expected
.times_used
, actual
.times_used
);
236 EXPECT_EQ(expected
.scheme
, actual
.scheme
);
237 EXPECT_EQ(expected
.date_synced
, actual
.date_synced
);
238 EXPECT_EQ(expected
.display_name
, actual
.display_name
);
239 EXPECT_EQ(expected
.avatar_url
, actual
.avatar_url
);
240 EXPECT_EQ(expected
.federation_url
, actual
.federation_url
);
241 EXPECT_EQ(expected
.skip_zero_click
, actual
.skip_zero_click
);
242 EXPECT_EQ(expected
.generation_upload_status
,
243 actual
.generation_upload_status
);
247 } // anonymous namespace
249 class NativeBackendLibsecretTest
: public testing::Test
{
251 enum UpdateType
{ // Used in CheckPSLUpdate().
252 UPDATE_BY_UPDATELOGIN
,
255 enum RemoveBetweenMethod
{ // Used in CheckRemoveLoginsBetween().
260 NativeBackendLibsecretTest() {}
262 void SetUp() override
{
263 ASSERT_FALSE(global_mock_libsecret_items
);
264 global_mock_libsecret_items
= &mock_libsecret_items_
;
266 ASSERT_TRUE(MockLibsecretLoader::LoadMockLibsecret());
268 form_google_
.origin
= GURL("http://www.google.com/");
269 form_google_
.action
= GURL("http://www.google.com/login");
270 form_google_
.username_element
= UTF8ToUTF16("user");
271 form_google_
.username_value
= UTF8ToUTF16("joeschmoe");
272 form_google_
.password_element
= UTF8ToUTF16("pass");
273 form_google_
.password_value
= UTF8ToUTF16("seekrit");
274 form_google_
.submit_element
= UTF8ToUTF16("submit");
275 form_google_
.signon_realm
= "http://www.google.com/";
276 form_google_
.type
= PasswordForm::TYPE_GENERATED
;
277 form_google_
.date_created
= base::Time::Now();
278 form_google_
.date_synced
= base::Time::Now();
279 form_google_
.display_name
= UTF8ToUTF16("Joe Schmoe");
280 form_google_
.avatar_url
= GURL("http://www.google.com/avatar");
281 form_google_
.federation_url
= GURL("http://www.google.com/federation_url");
282 form_google_
.skip_zero_click
= true;
283 form_google_
.generation_upload_status
= PasswordForm::POSITIVE_SIGNAL_SENT
;
284 form_google_
.form_data
.name
= UTF8ToUTF16("form_name");
285 form_google_
.form_data
.user_submitted
= true;
287 form_facebook_
.origin
= GURL("http://www.facebook.com/");
288 form_facebook_
.action
= GURL("http://www.facebook.com/login");
289 form_facebook_
.username_element
= UTF8ToUTF16("user");
290 form_facebook_
.username_value
= UTF8ToUTF16("a");
291 form_facebook_
.password_element
= UTF8ToUTF16("password");
292 form_facebook_
.password_value
= UTF8ToUTF16("b");
293 form_facebook_
.submit_element
= UTF8ToUTF16("submit");
294 form_facebook_
.signon_realm
= "http://www.facebook.com/";
295 form_facebook_
.date_created
= base::Time::Now();
296 form_facebook_
.date_synced
= base::Time::Now();
297 form_facebook_
.display_name
= UTF8ToUTF16("Joe Schmoe");
298 form_facebook_
.avatar_url
= GURL("http://www.facebook.com/avatar");
299 form_facebook_
.federation_url
= GURL("http://www.facebook.com/federation");
300 form_facebook_
.skip_zero_click
= true;
301 form_facebook_
.generation_upload_status
= PasswordForm::NO_SIGNAL_SENT
;
303 form_isc_
.origin
= GURL("http://www.isc.org/");
304 form_isc_
.action
= GURL("http://www.isc.org/auth");
305 form_isc_
.username_element
= UTF8ToUTF16("id");
306 form_isc_
.username_value
= UTF8ToUTF16("janedoe");
307 form_isc_
.password_element
= UTF8ToUTF16("passwd");
308 form_isc_
.password_value
= UTF8ToUTF16("ihazabukkit");
309 form_isc_
.submit_element
= UTF8ToUTF16("login");
310 form_isc_
.signon_realm
= "http://www.isc.org/";
311 form_isc_
.date_created
= base::Time::Now();
312 form_isc_
.date_synced
= base::Time::Now();
314 other_auth_
.origin
= GURL("http://www.example.com/");
315 other_auth_
.username_value
= UTF8ToUTF16("username");
316 other_auth_
.password_value
= UTF8ToUTF16("pass");
317 other_auth_
.signon_realm
= "http://www.example.com/Realm";
318 other_auth_
.date_created
= base::Time::Now();
319 other_auth_
.date_synced
= base::Time::Now();
322 void TearDown() override
{
323 base::MessageLoop::current()->PostTask(FROM_HERE
,
324 base::MessageLoop::QuitClosure());
325 base::MessageLoop::current()->Run();
326 ASSERT_TRUE(global_mock_libsecret_items
);
327 global_mock_libsecret_items
= nullptr;
330 void RunUIThread() { base::MessageLoop::current()->Run(); }
332 void CheckUint32Attribute(const MockSecretItem
* item
,
333 const std::string
& attribute
,
335 gpointer item_value
=
336 g_hash_table_lookup(item
->attributes
, attribute
.c_str());
337 EXPECT_TRUE(item_value
) << " in attribute " << attribute
;
340 bool conversion_ok
= base::StringToUint((char*)item_value
, &int_value
);
341 EXPECT_TRUE(conversion_ok
);
342 EXPECT_EQ(value
, int_value
);
346 void CheckStringAttribute(const MockSecretItem
* item
,
347 const std::string
& attribute
,
348 const std::string
& value
) {
349 gpointer item_value
=
350 g_hash_table_lookup(item
->attributes
, attribute
.c_str());
351 EXPECT_TRUE(item_value
) << " in attribute " << attribute
;
353 EXPECT_EQ(value
, static_cast<char*>(item_value
));
357 void CheckMockSecretItem(const MockSecretItem
* item
,
358 const PasswordForm
& form
,
359 const std::string
& app_string
) {
360 EXPECT_EQ(UTF16ToUTF8(form
.password_value
), item
->value
->password
);
361 EXPECT_EQ(22u, g_hash_table_size(item
->attributes
));
362 CheckStringAttribute(item
, "origin_url", form
.origin
.spec());
363 CheckStringAttribute(item
, "action_url", form
.action
.spec());
364 CheckStringAttribute(item
, "username_element",
365 UTF16ToUTF8(form
.username_element
));
366 CheckStringAttribute(item
, "username_value",
367 UTF16ToUTF8(form
.username_value
));
368 CheckStringAttribute(item
, "password_element",
369 UTF16ToUTF8(form
.password_element
));
370 CheckStringAttribute(item
, "submit_element",
371 UTF16ToUTF8(form
.submit_element
));
372 CheckStringAttribute(item
, "signon_realm", form
.signon_realm
);
373 CheckUint32Attribute(item
, "ssl_valid", form
.ssl_valid
);
374 CheckUint32Attribute(item
, "preferred", form
.preferred
);
375 // We don't check the date created. It varies.
376 CheckUint32Attribute(item
, "blacklisted_by_user", form
.blacklisted_by_user
);
377 CheckUint32Attribute(item
, "type", form
.type
);
378 CheckUint32Attribute(item
, "times_used", form
.times_used
);
379 CheckUint32Attribute(item
, "scheme", form
.scheme
);
380 CheckStringAttribute(
382 base::Int64ToString(form
.date_synced
.ToInternalValue()));
383 CheckStringAttribute(item
, "display_name", UTF16ToUTF8(form
.display_name
));
384 CheckStringAttribute(item
, "avatar_url", form
.avatar_url
.spec());
385 CheckStringAttribute(item
, "federation_url", form
.federation_url
.spec());
386 CheckUint32Attribute(item
, "skip_zero_click", form
.skip_zero_click
);
387 CheckUint32Attribute(item
, "generation_upload_status",
388 form
.generation_upload_status
);
389 CheckStringAttribute(item
, "application", app_string
);
390 autofill::FormData actual
;
391 DeserializeFormDataFromBase64String(
392 static_cast<char*>(g_hash_table_lookup(item
->attributes
, "form_data")),
394 EXPECT_TRUE(form
.form_data
.SameFormAs(actual
));
397 // Saves |credentials| and then gets logins matching |url| and |scheme|.
398 // Returns true when something is found, and in such case copies the result to
399 // |result| when |result| is not nullptr. (Note that there can be max. 1
400 // result derived from |credentials|.)
401 bool CheckCredentialAvailability(const PasswordForm
& credentials
,
403 const PasswordForm::Scheme
& scheme
,
404 PasswordForm
* result
) {
405 NativeBackendLibsecret
backend(321);
407 backend
.AddLogin(credentials
);
409 PasswordForm target_form
;
410 target_form
.origin
= url
;
411 target_form
.signon_realm
= url
.spec();
412 if (scheme
!= PasswordForm::SCHEME_HTML
) {
413 // For non-HTML forms, the realm used for authentication
414 // (http://tools.ietf.org/html/rfc1945#section-10.2) is appended to the
415 // signon_realm. Just use a default value for now.
416 target_form
.signon_realm
.append("Realm");
417 target_form
.scheme
= scheme
;
419 ScopedVector
<autofill::PasswordForm
> form_list
;
420 backend
.GetLogins(target_form
, &form_list
);
422 EXPECT_EQ(1u, global_mock_libsecret_items
->size());
423 if (!global_mock_libsecret_items
->empty())
424 CheckMockSecretItem((*global_mock_libsecret_items
)[0], credentials
,
426 global_mock_libsecret_items
->clear();
428 if (form_list
.empty())
430 EXPECT_EQ(1u, form_list
.size());
432 *result
= *form_list
[0];
436 // Test that updating does not use PSL matching: Add a www.facebook.com
437 // password, then use PSL matching to get a copy of it for m.facebook.com, and
438 // add that copy as well. Now update the www.facebook.com password -- the
439 // m.facebook.com password should not get updated. Depending on the argument,
440 // the credential update is done via UpdateLogin or AddLogin.
441 void CheckPSLUpdate(UpdateType update_type
) {
442 NativeBackendLibsecret
backend(321);
444 backend
.AddLogin(form_facebook_
);
446 // Get the PSL-matched copy of the saved login for m.facebook.
447 const GURL
kMobileURL("http://m.facebook.com/");
448 PasswordForm m_facebook_lookup
;
449 m_facebook_lookup
.origin
= kMobileURL
;
450 m_facebook_lookup
.signon_realm
= kMobileURL
.spec();
451 ScopedVector
<autofill::PasswordForm
> form_list
;
452 backend
.GetLogins(m_facebook_lookup
, &form_list
);
454 EXPECT_EQ(1u, global_mock_libsecret_items
->size());
455 EXPECT_EQ(1u, form_list
.size());
456 PasswordForm m_facebook
= *form_list
[0];
458 EXPECT_EQ(kMobileURL
, m_facebook
.origin
);
459 EXPECT_EQ(kMobileURL
.spec(), m_facebook
.signon_realm
);
461 // Add the PSL-matched copy to saved logins.
462 backend
.AddLogin(m_facebook
);
463 EXPECT_EQ(2u, global_mock_libsecret_items
->size());
465 // Update www.facebook.com login.
466 PasswordForm
new_facebook(form_facebook_
);
467 const base::string16
kOldPassword(form_facebook_
.password_value
);
468 const base::string16
kNewPassword(UTF8ToUTF16("new_b"));
469 EXPECT_NE(kOldPassword
, kNewPassword
);
470 new_facebook
.password_value
= kNewPassword
;
471 scoped_ptr
<PasswordStoreChangeList
> not_used(new PasswordStoreChangeList());
472 switch (update_type
) {
473 case UPDATE_BY_UPDATELOGIN
:
474 backend
.UpdateLogin(new_facebook
, not_used
.get());
476 case UPDATE_BY_ADDLOGIN
:
477 backend
.AddLogin(new_facebook
);
481 EXPECT_EQ(2u, global_mock_libsecret_items
->size());
483 // Check that m.facebook.com login was not modified by the update.
484 backend
.GetLogins(m_facebook_lookup
, &form_list
);
486 // There should be two results -- the exact one, and the PSL-matched one.
487 EXPECT_EQ(2u, form_list
.size());
488 size_t index_non_psl
= 0;
489 if (!form_list
[index_non_psl
]->original_signon_realm
.empty())
491 EXPECT_EQ(kMobileURL
, form_list
[index_non_psl
]->origin
);
492 EXPECT_EQ(kMobileURL
.spec(), form_list
[index_non_psl
]->signon_realm
);
493 EXPECT_EQ(kOldPassword
, form_list
[index_non_psl
]->password_value
);
496 // Check that www.facebook.com login was modified by the update.
497 backend
.GetLogins(form_facebook_
, &form_list
);
498 // There should be two results -- the exact one, and the PSL-matched one.
499 EXPECT_EQ(2u, form_list
.size());
501 if (!form_list
[index_non_psl
]->original_signon_realm
.empty())
503 EXPECT_EQ(form_facebook_
.origin
, form_list
[index_non_psl
]->origin
);
504 EXPECT_EQ(form_facebook_
.signon_realm
,
505 form_list
[index_non_psl
]->signon_realm
);
506 EXPECT_EQ(kNewPassword
, form_list
[index_non_psl
]->password_value
);
510 // Checks various types of matching for forms with a non-HTML |scheme|.
511 void CheckMatchingWithScheme(const PasswordForm::Scheme
& scheme
) {
512 ASSERT_NE(PasswordForm::SCHEME_HTML
, scheme
);
513 other_auth_
.scheme
= scheme
;
515 // Don't match a non-HTML form with an HTML form.
517 CheckCredentialAvailability(other_auth_
, GURL("http://www.example.com"),
518 PasswordForm::SCHEME_HTML
, nullptr));
519 // Don't match an HTML form with non-HTML auth form.
520 EXPECT_FALSE(CheckCredentialAvailability(
521 form_google_
, GURL("http://www.google.com/"), scheme
, nullptr));
522 // Don't match two different non-HTML auth forms with different origin.
523 EXPECT_FALSE(CheckCredentialAvailability(
524 other_auth_
, GURL("http://first.example.com"), scheme
, nullptr));
525 // Do match non-HTML forms from the same origin.
526 EXPECT_TRUE(CheckCredentialAvailability(
527 other_auth_
, GURL("http://www.example.com/"), scheme
, nullptr));
530 void CheckRemoveLoginsBetween(RemoveBetweenMethod date_to_test
) {
531 NativeBackendLibsecret
backend(42);
533 base::Time now
= base::Time::Now();
534 base::Time next_day
= now
+ base::TimeDelta::FromDays(1);
535 form_google_
.date_synced
= base::Time();
536 form_isc_
.date_synced
= base::Time();
537 form_google_
.date_created
= now
;
538 form_isc_
.date_created
= now
;
539 if (date_to_test
== CREATED
) {
540 form_google_
.date_created
= now
;
541 form_isc_
.date_created
= next_day
;
543 form_google_
.date_synced
= now
;
544 form_isc_
.date_synced
= next_day
;
547 backend
.AddLogin(form_google_
);
548 backend
.AddLogin(form_isc_
);
550 PasswordStoreChangeList expected_changes
;
551 expected_changes
.push_back(
552 PasswordStoreChange(PasswordStoreChange::REMOVE
, form_google_
));
553 PasswordStoreChangeList changes
;
554 bool (NativeBackendLibsecret::*method
)(
555 base::Time
, base::Time
, password_manager::PasswordStoreChangeList
*) =
556 date_to_test
== CREATED
557 ? &NativeBackendLibsecret::RemoveLoginsCreatedBetween
558 : &NativeBackendLibsecret::RemoveLoginsSyncedBetween
;
560 EXPECT_TRUE(base::Bind(method
, base::Unretained(&backend
), base::Time(),
561 next_day
, &changes
).Run());
562 CheckPasswordChanges(expected_changes
, changes
);
564 EXPECT_EQ(1u, global_mock_libsecret_items
->size());
565 if (!global_mock_libsecret_items
->empty() > 0)
566 CheckMockSecretItem((*global_mock_libsecret_items
)[0], form_isc_
,
570 expected_changes
.clear();
571 expected_changes
.push_back(
572 PasswordStoreChange(PasswordStoreChange::REMOVE
, form_isc_
));
574 EXPECT_TRUE(base::Bind(method
, base::Unretained(&backend
), next_day
,
575 base::Time(), &changes
).Run());
576 CheckPasswordChanges(expected_changes
, changes
);
578 EXPECT_TRUE(global_mock_libsecret_items
->empty());
581 base::MessageLoopForUI message_loop_
;
583 // Provide some test forms to avoid having to set them up in each test.
584 PasswordForm form_google_
;
585 PasswordForm form_facebook_
;
586 PasswordForm form_isc_
;
587 PasswordForm other_auth_
;
589 ScopedVector
<MockSecretItem
> mock_libsecret_items_
;
592 TEST_F(NativeBackendLibsecretTest
, BasicAddLogin
) {
593 NativeBackendLibsecret
backend(42);
595 backend
.AddLogin(form_google_
);
597 EXPECT_EQ(1u, global_mock_libsecret_items
->size());
598 if (!global_mock_libsecret_items
->empty())
599 CheckMockSecretItem((*global_mock_libsecret_items
)[0], form_google_
,
603 TEST_F(NativeBackendLibsecretTest
, BasicListLogins
) {
604 NativeBackendLibsecret
backend(42);
606 backend
.AddLogin(form_google_
);
608 ScopedVector
<autofill::PasswordForm
> form_list
;
609 backend
.GetAutofillableLogins(&form_list
);
611 // Quick check that we got something back.
612 EXPECT_EQ(1u, form_list
.size());
615 EXPECT_EQ(1u, global_mock_libsecret_items
->size());
616 if (!global_mock_libsecret_items
->empty())
617 CheckMockSecretItem((*global_mock_libsecret_items
)[0], form_google_
,
621 // Save a password for www.facebook.com and see it suggested for m.facebook.com.
622 TEST_F(NativeBackendLibsecretTest
, PSLMatchingPositive
) {
624 const GURL
kMobileURL("http://m.facebook.com/");
625 EXPECT_TRUE(CheckCredentialAvailability(form_facebook_
, kMobileURL
,
626 PasswordForm::SCHEME_HTML
, &result
));
627 EXPECT_EQ(kMobileURL
, result
.origin
);
628 EXPECT_EQ(kMobileURL
.spec(), result
.signon_realm
);
631 // Save a password for www.facebook.com and see it not suggested for
633 TEST_F(NativeBackendLibsecretTest
, PSLMatchingNegativeDomainMismatch
) {
634 EXPECT_FALSE(CheckCredentialAvailability(form_facebook_
,
635 GURL("http://m-facebook.com/"),
636 PasswordForm::SCHEME_HTML
, nullptr));
639 // Test PSL matching is off for domains excluded from it.
640 TEST_F(NativeBackendLibsecretTest
, PSLMatchingDisabledDomains
) {
641 EXPECT_FALSE(CheckCredentialAvailability(form_google_
,
642 GURL("http://one.google.com/"),
643 PasswordForm::SCHEME_HTML
, nullptr));
646 // Make sure PSL matches aren't available for non-HTML forms.
647 TEST_F(NativeBackendLibsecretTest
, PSLMatchingDisabledForNonHTMLForms
) {
648 CheckMatchingWithScheme(PasswordForm::SCHEME_BASIC
);
649 CheckMatchingWithScheme(PasswordForm::SCHEME_DIGEST
);
650 CheckMatchingWithScheme(PasswordForm::SCHEME_OTHER
);
653 TEST_F(NativeBackendLibsecretTest
, PSLUpdatingStrictUpdateLogin
) {
654 CheckPSLUpdate(UPDATE_BY_UPDATELOGIN
);
657 TEST_F(NativeBackendLibsecretTest
, PSLUpdatingStrictAddLogin
) {
658 // TODO(vabr): if AddLogin becomes no longer valid for existing logins, then
659 // just delete this test.
660 CheckPSLUpdate(UPDATE_BY_ADDLOGIN
);
663 TEST_F(NativeBackendLibsecretTest
, BasicUpdateLogin
) {
664 NativeBackendLibsecret
backend(42);
666 backend
.AddLogin(form_google_
);
668 PasswordForm
new_form_google(form_google_
);
669 new_form_google
.times_used
= 1;
670 new_form_google
.action
= GURL("http://www.google.com/different/login");
672 EXPECT_EQ(1u, global_mock_libsecret_items
->size());
673 if (!global_mock_libsecret_items
->empty())
674 CheckMockSecretItem((*global_mock_libsecret_items
)[0], form_google_
,
678 PasswordStoreChangeList changes
;
679 backend
.UpdateLogin(new_form_google
, &changes
);
681 ASSERT_EQ(1u, changes
.size());
682 EXPECT_EQ(PasswordStoreChange::UPDATE
, changes
.front().type());
683 EXPECT_EQ(new_form_google
, changes
.front().form());
684 EXPECT_EQ(1u, global_mock_libsecret_items
->size());
685 if (!global_mock_libsecret_items
->empty())
686 CheckMockSecretItem((*global_mock_libsecret_items
)[0], new_form_google
,
690 TEST_F(NativeBackendLibsecretTest
, BasicRemoveLogin
) {
691 NativeBackendLibsecret
backend(42);
693 backend
.AddLogin(form_google_
);
695 EXPECT_EQ(1u, global_mock_libsecret_items
->size());
696 if (!global_mock_libsecret_items
->empty())
697 CheckMockSecretItem((*global_mock_libsecret_items
)[0], form_google_
,
700 backend
.RemoveLogin(form_google_
);
702 EXPECT_TRUE(global_mock_libsecret_items
->empty());
705 // Verify fix for http://crbug.com/408783.
706 TEST_F(NativeBackendLibsecretTest
, RemoveLoginActionMismatch
) {
707 NativeBackendLibsecret
backend(42);
709 backend
.AddLogin(form_google_
);
711 EXPECT_EQ(1u, global_mock_libsecret_items
->size());
712 if (!global_mock_libsecret_items
->empty())
713 CheckMockSecretItem((*global_mock_libsecret_items
)[0], form_google_
,
716 // Action url match not required for removal.
717 form_google_
.action
= GURL("https://some.other.url.com/path");
719 backend
.RemoveLogin(form_google_
);
721 EXPECT_TRUE(global_mock_libsecret_items
->empty());
724 TEST_F(NativeBackendLibsecretTest
, RemoveNonexistentLogin
) {
725 NativeBackendLibsecret
backend(42);
727 // First add an unrelated login.
728 backend
.AddLogin(form_google_
);
730 EXPECT_EQ(1u, global_mock_libsecret_items
->size());
731 if (!global_mock_libsecret_items
->empty())
732 CheckMockSecretItem((*global_mock_libsecret_items
)[0], form_google_
,
735 // Attempt to remove a login that doesn't exist.
736 backend
.RemoveLogin(form_isc_
);
738 // Make sure we can still get the first form back.
739 ScopedVector
<autofill::PasswordForm
> form_list
;
740 backend
.GetAutofillableLogins(&form_list
);
742 // Quick check that we got something back.
743 EXPECT_EQ(1u, form_list
.size());
746 EXPECT_EQ(1u, global_mock_libsecret_items
->size());
747 if (!global_mock_libsecret_items
->empty())
748 CheckMockSecretItem((*global_mock_libsecret_items
)[0], form_google_
,
752 TEST_F(NativeBackendLibsecretTest
, UpdateNonexistentLogin
) {
753 NativeBackendLibsecret
backend(42);
755 // First add an unrelated login.
756 backend
.AddLogin(form_google_
);
758 EXPECT_EQ(1u, global_mock_libsecret_items
->size());
759 if (!global_mock_libsecret_items
->empty())
760 CheckMockSecretItem((*global_mock_libsecret_items
)[0], form_google_
,
763 // Attempt to update a login that doesn't exist.
764 PasswordStoreChangeList changes
;
765 backend
.UpdateLogin(form_isc_
, &changes
);
767 EXPECT_EQ(PasswordStoreChangeList(), changes
);
768 EXPECT_EQ(1u, global_mock_libsecret_items
->size());
769 if (!global_mock_libsecret_items
->empty())
770 CheckMockSecretItem((*global_mock_libsecret_items
)[0], form_google_
,
774 TEST_F(NativeBackendLibsecretTest
, AddDuplicateLogin
) {
775 NativeBackendLibsecret
backend(42);
777 PasswordStoreChangeList expected_changes
, actual_changes
;
778 expected_changes
.push_back(
779 PasswordStoreChange(PasswordStoreChange::ADD
, form_google_
));
780 actual_changes
= backend
.AddLogin(form_google_
);
781 CheckPasswordChanges(expected_changes
, actual_changes
);
783 expected_changes
.clear();
784 expected_changes
.push_back(
785 PasswordStoreChange(PasswordStoreChange::REMOVE
, form_google_
));
786 form_google_
.times_used
++;
787 expected_changes
.push_back(
788 PasswordStoreChange(PasswordStoreChange::ADD
, form_google_
));
790 actual_changes
= backend
.AddLogin(form_google_
);
791 CheckPasswordChanges(expected_changes
, actual_changes
);
793 EXPECT_EQ(1u, global_mock_libsecret_items
->size());
794 if (!global_mock_libsecret_items
->empty())
795 CheckMockSecretItem((*global_mock_libsecret_items
)[0], form_google_
,
799 TEST_F(NativeBackendLibsecretTest
, ListLoginsAppends
) {
800 NativeBackendLibsecret
backend(42);
802 backend
.AddLogin(form_google_
);
804 // Send the same request twice with the same list both times.
805 ScopedVector
<autofill::PasswordForm
> form_list
;
806 backend
.GetAutofillableLogins(&form_list
);
807 backend
.GetAutofillableLogins(&form_list
);
809 // Quick check that we got two results back.
810 EXPECT_EQ(2u, form_list
.size());
813 EXPECT_EQ(1u, global_mock_libsecret_items
->size());
814 if (!global_mock_libsecret_items
->empty())
815 CheckMockSecretItem((*global_mock_libsecret_items
)[0], form_google_
,
819 TEST_F(NativeBackendLibsecretTest
, AndroidCredentials
) {
820 NativeBackendLibsecret
backend(42);
823 PasswordForm observed_android_form
;
824 observed_android_form
.scheme
= PasswordForm::SCHEME_HTML
;
825 observed_android_form
.signon_realm
=
826 "android://7x7IDboo8u9YKraUsbmVkuf1-@net.rateflix.app/";
827 PasswordForm saved_android_form
= observed_android_form
;
828 saved_android_form
.username_value
= base::UTF8ToUTF16("randomusername");
829 saved_android_form
.password_value
= base::UTF8ToUTF16("password");
830 saved_android_form
.date_created
= base::Time::Now();
832 backend
.AddLogin(saved_android_form
);
834 ScopedVector
<autofill::PasswordForm
> form_list
;
835 backend
.GetAutofillableLogins(&form_list
);
837 EXPECT_EQ(1u, form_list
.size());
838 EXPECT_EQ(saved_android_form
, *form_list
[0]);
842 TEST_F(NativeBackendLibsecretTest
, RemoveLoginsCreatedBetween
) {
843 CheckRemoveLoginsBetween(CREATED
);
846 TEST_F(NativeBackendLibsecretTest
, RemoveLoginsSyncedBetween
) {
847 CheckRemoveLoginsBetween(SYNCED
);
850 TEST_F(NativeBackendLibsecretTest
, SomeKeyringAttributesAreMissing
) {
851 // Absent attributes should be filled with default values.
852 NativeBackendLibsecret
backend(42);
854 backend
.AddLogin(form_google_
);
856 EXPECT_EQ(1u, global_mock_libsecret_items
->size());
857 // Remove a string attribute.
858 (*global_mock_libsecret_items
)[0]->RemoveAttribute("avatar_url");
859 // Remove an integer attribute.
860 (*global_mock_libsecret_items
)[0]->RemoveAttribute("ssl_valid");
862 ScopedVector
<autofill::PasswordForm
> form_list
;
863 backend
.GetAutofillableLogins(&form_list
);
865 EXPECT_EQ(1u, form_list
.size());
866 EXPECT_EQ(GURL(""), form_list
[0]->avatar_url
);
867 EXPECT_FALSE(form_list
[0]->ssl_valid
);
870 // TODO(mdm): add more basic tests here at some point.