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 std::vector
<MockSecretItem
*> global_mock_libsecret_items
;
83 bool global_mock_libsecret_reject_local_ids
= false;
85 void ClearMockObjects() {
86 for (size_t i
= 0; i
< global_mock_libsecret_items
.size(); ++i
)
87 delete global_mock_libsecret_items
[i
];
88 global_mock_libsecret_items
.clear();
91 gboolean
mock_secret_password_store_sync(const SecretSchema
* schema
,
92 const gchar
* collection
,
94 const gchar
* password
,
95 GCancellable
* cancellable
,
98 GHashTable
* attributes
=
99 g_hash_table_new_full(g_str_hash
, g_str_equal
, g_free
, g_free
);
103 while ((name
= va_arg(ap
, gchar
*))) {
105 if (IsStringAttribute(schema
, name
)) {
106 value
= g_strdup(va_arg(ap
, gchar
*));
107 VLOG(1) << "Adding item attribute " << name
<< ", value '" << value
110 uint32_t intvalue
= va_arg(ap
, uint32_t);
111 VLOG(1) << "Adding item attribute " << name
<< ", value " << intvalue
;
112 value
= g_strdup_printf("%u", intvalue
);
114 g_hash_table_insert(attributes
, g_strdup(name
), value
);
117 MockSecretValue
* secret_value
= new MockSecretValue(g_strdup(password
));
118 MockSecretItem
* item
= new MockSecretItem(secret_value
, attributes
);
119 global_mock_libsecret_items
.push_back(item
);
123 GList
* mock_secret_service_search_sync(SecretService
* service
,
124 const SecretSchema
* schema
,
125 GHashTable
* attributes
,
126 SecretSearchFlags flags
,
127 GCancellable
* cancellable
,
129 GList
* result
= nullptr;
130 for (uint32_t i
= 0; i
< global_mock_libsecret_items
.size(); ++i
) {
131 if (Matches(global_mock_libsecret_items
[i
], attributes
))
132 result
= g_list_append(result
, global_mock_libsecret_items
[i
]);
137 gboolean
mock_secret_password_clear_sync(const SecretSchema
* schema
,
138 GCancellable
* cancellable
,
141 GHashTable
* attributes
=
142 g_hash_table_new_full(g_str_hash
, g_str_equal
, g_free
, g_free
);
146 while ((name
= va_arg(ap
, gchar
*))) {
148 if (IsStringAttribute(schema
, name
)) {
149 value
= g_strdup(va_arg(ap
, gchar
*));
150 VLOG(1) << "Adding item attribute " << name
<< ", value '" << value
153 uint32_t intvalue
= va_arg(ap
, uint32_t);
154 VLOG(1) << "Adding item attribute " << name
<< ", value " << intvalue
;
155 value
= g_strdup_printf("%u", intvalue
);
157 g_hash_table_insert(attributes
, g_strdup(name
), value
);
160 for (uint32_t i
= 0; i
< global_mock_libsecret_items
.size();)
161 if (Matches(global_mock_libsecret_items
[i
], attributes
)) {
162 delete global_mock_libsecret_items
[i
];
163 global_mock_libsecret_items
.erase(global_mock_libsecret_items
.begin() +
168 g_hash_table_unref(attributes
);
172 MockSecretValue
* mock_secret_item_get_secret(MockSecretItem
* self
) {
176 const gchar
* mock_secret_value_get_text(MockSecretValue
* value
) {
177 return value
->password
;
180 GHashTable
* mock_secret_item_get_attributes(MockSecretItem
* self
) {
181 // Libsecret backend will make unreference of received attributes, so in
182 // order to save them we need to increase their reference number.
183 g_hash_table_ref(self
->attributes
);
184 return self
->attributes
;
187 gboolean
mock_secret_item_load_secret_sync(MockSecretItem
* self
,
188 GCancellable
* cancellable
,
193 void mock_secret_value_unref(gpointer value
) {
196 // Inherit to get access to protected fields.
197 class MockLibsecretLoader
: public LibsecretLoader
{
199 static bool LoadMockLibsecret() {
200 secret_password_store_sync
= &mock_secret_password_store_sync
;
201 secret_service_search_sync
= &mock_secret_service_search_sync
;
202 secret_password_clear_sync
= &mock_secret_password_clear_sync
;
203 secret_item_get_secret
=
204 (decltype(&::secret_item_get_secret
)) & mock_secret_item_get_secret
;
205 secret_value_get_text
=
206 (decltype(&::secret_value_get_text
)) & mock_secret_value_get_text
;
207 secret_item_get_attributes
= (decltype(&::secret_item_get_attributes
)) &
208 mock_secret_item_get_attributes
;
209 secret_item_load_secret_sync
= (decltype(&::secret_item_load_secret_sync
)) &
210 mock_secret_item_load_secret_sync
;
212 (decltype(&::secret_value_unref
)) & mock_secret_value_unref
;
213 libsecret_loaded
= true;
214 // Reset the state of the mock library.
216 global_mock_libsecret_reject_local_ids
= false;
221 void CheckPasswordChanges(const PasswordStoreChangeList
& expected_list
,
222 const PasswordStoreChangeList
& actual_list
) {
223 ASSERT_EQ(expected_list
.size(), actual_list
.size());
224 for (size_t i
= 0; i
< expected_list
.size(); ++i
) {
225 EXPECT_EQ(expected_list
[i
].type(), actual_list
[i
].type());
226 const PasswordForm
& expected
= expected_list
[i
].form();
227 const PasswordForm
& actual
= actual_list
[i
].form();
229 EXPECT_EQ(expected
.origin
, actual
.origin
);
230 EXPECT_EQ(expected
.password_value
, actual
.password_value
);
231 EXPECT_EQ(expected
.action
, actual
.action
);
232 EXPECT_EQ(expected
.username_element
, actual
.username_element
);
233 EXPECT_EQ(expected
.username_value
, actual
.username_value
);
234 EXPECT_EQ(expected
.password_element
, actual
.password_element
);
235 EXPECT_EQ(expected
.submit_element
, actual
.submit_element
);
236 EXPECT_EQ(expected
.signon_realm
, actual
.signon_realm
);
237 EXPECT_EQ(expected
.ssl_valid
, actual
.ssl_valid
);
238 EXPECT_EQ(expected
.preferred
, actual
.preferred
);
239 EXPECT_EQ(expected
.date_created
, actual
.date_created
);
240 EXPECT_EQ(expected
.blacklisted_by_user
, actual
.blacklisted_by_user
);
241 EXPECT_EQ(expected
.type
, actual
.type
);
242 EXPECT_EQ(expected
.times_used
, actual
.times_used
);
243 EXPECT_EQ(expected
.scheme
, actual
.scheme
);
244 EXPECT_EQ(expected
.date_synced
, actual
.date_synced
);
245 EXPECT_EQ(expected
.display_name
, actual
.display_name
);
246 EXPECT_EQ(expected
.avatar_url
, actual
.avatar_url
);
247 EXPECT_EQ(expected
.federation_url
, actual
.federation_url
);
248 EXPECT_EQ(expected
.skip_zero_click
, actual
.skip_zero_click
);
249 EXPECT_EQ(expected
.generation_upload_status
,
250 actual
.generation_upload_status
);
254 void CheckPasswordChangesWithResult(const PasswordStoreChangeList
* expected
,
255 const PasswordStoreChangeList
* actual
,
258 CheckPasswordChanges(*expected
, *actual
);
261 } // anonymous namespace
263 class NativeBackendLibsecretTest
: public testing::Test
{
265 enum UpdateType
{ // Used in CheckPSLUpdate().
266 UPDATE_BY_UPDATELOGIN
,
269 enum RemoveBetweenMethod
{ // Used in CheckRemoveLoginsBetween().
274 NativeBackendLibsecretTest() {}
276 void SetUp() override
{
277 ASSERT_TRUE(MockLibsecretLoader::LoadMockLibsecret());
279 form_google_
.origin
= GURL("http://www.google.com/");
280 form_google_
.action
= GURL("http://www.google.com/login");
281 form_google_
.username_element
= UTF8ToUTF16("user");
282 form_google_
.username_value
= UTF8ToUTF16("joeschmoe");
283 form_google_
.password_element
= UTF8ToUTF16("pass");
284 form_google_
.password_value
= UTF8ToUTF16("seekrit");
285 form_google_
.submit_element
= UTF8ToUTF16("submit");
286 form_google_
.signon_realm
= "http://www.google.com/";
287 form_google_
.type
= PasswordForm::TYPE_GENERATED
;
288 form_google_
.date_created
= base::Time::Now();
289 form_google_
.date_synced
= base::Time::Now();
290 form_google_
.display_name
= UTF8ToUTF16("Joe Schmoe");
291 form_google_
.avatar_url
= GURL("http://www.google.com/avatar");
292 form_google_
.federation_url
= GURL("http://www.google.com/federation_url");
293 form_google_
.skip_zero_click
= true;
294 form_google_
.generation_upload_status
= PasswordForm::POSITIVE_SIGNAL_SENT
;
296 form_facebook_
.origin
= GURL("http://www.facebook.com/");
297 form_facebook_
.action
= GURL("http://www.facebook.com/login");
298 form_facebook_
.username_element
= UTF8ToUTF16("user");
299 form_facebook_
.username_value
= UTF8ToUTF16("a");
300 form_facebook_
.password_element
= UTF8ToUTF16("password");
301 form_facebook_
.password_value
= UTF8ToUTF16("b");
302 form_facebook_
.submit_element
= UTF8ToUTF16("submit");
303 form_facebook_
.signon_realm
= "http://www.facebook.com/";
304 form_facebook_
.date_created
= base::Time::Now();
305 form_facebook_
.date_synced
= base::Time::Now();
306 form_facebook_
.display_name
= UTF8ToUTF16("Joe Schmoe");
307 form_facebook_
.avatar_url
= GURL("http://www.facebook.com/avatar");
308 form_facebook_
.federation_url
= GURL("http://www.facebook.com/federation");
309 form_facebook_
.skip_zero_click
= true;
310 form_facebook_
.generation_upload_status
= PasswordForm::NO_SIGNAL_SENT
;
312 form_isc_
.origin
= GURL("http://www.isc.org/");
313 form_isc_
.action
= GURL("http://www.isc.org/auth");
314 form_isc_
.username_element
= UTF8ToUTF16("id");
315 form_isc_
.username_value
= UTF8ToUTF16("janedoe");
316 form_isc_
.password_element
= UTF8ToUTF16("passwd");
317 form_isc_
.password_value
= UTF8ToUTF16("ihazabukkit");
318 form_isc_
.submit_element
= UTF8ToUTF16("login");
319 form_isc_
.signon_realm
= "http://www.isc.org/";
320 form_isc_
.date_created
= base::Time::Now();
321 form_isc_
.date_synced
= base::Time::Now();
323 other_auth_
.origin
= GURL("http://www.example.com/");
324 other_auth_
.username_value
= UTF8ToUTF16("username");
325 other_auth_
.password_value
= UTF8ToUTF16("pass");
326 other_auth_
.signon_realm
= "http://www.example.com/Realm";
327 other_auth_
.date_created
= base::Time::Now();
328 other_auth_
.date_synced
= base::Time::Now();
331 void TearDown() override
{
332 base::MessageLoop::current()->PostTask(FROM_HERE
,
333 base::MessageLoop::QuitClosure());
334 base::MessageLoop::current()->Run();
338 void RunUIThread() { base::MessageLoop::current()->Run(); }
340 void CheckUint32Attribute(const MockSecretItem
* item
,
341 const std::string
& attribute
,
343 gpointer item_value
=
344 g_hash_table_lookup(item
->attributes
, attribute
.c_str());
345 EXPECT_TRUE(item_value
) << " in attribute " << attribute
;
348 bool conversion_ok
= base::StringToUint((char*)item_value
, &int_value
);
349 EXPECT_TRUE(conversion_ok
);
350 EXPECT_EQ(value
, int_value
);
354 void CheckStringAttribute(const MockSecretItem
* item
,
355 const std::string
& attribute
,
356 const std::string
& value
) {
357 gpointer item_value
=
358 g_hash_table_lookup(item
->attributes
, attribute
.c_str());
359 EXPECT_TRUE(item_value
) << " in attribute " << attribute
;
361 EXPECT_EQ(value
, static_cast<char*>(item_value
));
365 void CheckMockSecretItem(const MockSecretItem
* item
,
366 const PasswordForm
& form
,
367 const std::string
& app_string
) {
368 EXPECT_EQ(UTF16ToUTF8(form
.password_value
), item
->value
->password
);
369 EXPECT_EQ(21u, g_hash_table_size(item
->attributes
));
370 CheckStringAttribute(item
, "origin_url", form
.origin
.spec());
371 CheckStringAttribute(item
, "action_url", form
.action
.spec());
372 CheckStringAttribute(item
, "username_element",
373 UTF16ToUTF8(form
.username_element
));
374 CheckStringAttribute(item
, "username_value",
375 UTF16ToUTF8(form
.username_value
));
376 CheckStringAttribute(item
, "password_element",
377 UTF16ToUTF8(form
.password_element
));
378 CheckStringAttribute(item
, "submit_element",
379 UTF16ToUTF8(form
.submit_element
));
380 CheckStringAttribute(item
, "signon_realm", form
.signon_realm
);
381 CheckUint32Attribute(item
, "ssl_valid", form
.ssl_valid
);
382 CheckUint32Attribute(item
, "preferred", form
.preferred
);
383 // We don't check the date created. It varies.
384 CheckUint32Attribute(item
, "blacklisted_by_user", form
.blacklisted_by_user
);
385 CheckUint32Attribute(item
, "type", form
.type
);
386 CheckUint32Attribute(item
, "times_used", form
.times_used
);
387 CheckUint32Attribute(item
, "scheme", form
.scheme
);
388 CheckStringAttribute(
390 base::Int64ToString(form
.date_synced
.ToInternalValue()));
391 CheckStringAttribute(item
, "display_name", UTF16ToUTF8(form
.display_name
));
392 CheckStringAttribute(item
, "avatar_url", form
.avatar_url
.spec());
393 CheckStringAttribute(item
, "federation_url", form
.federation_url
.spec());
394 CheckUint32Attribute(item
, "skip_zero_click", form
.skip_zero_click
);
395 CheckUint32Attribute(item
, "generation_upload_status",
396 form
.generation_upload_status
);
397 CheckStringAttribute(item
, "application", app_string
);
400 // Saves |credentials| and then gets logins matching |url| and |scheme|.
401 // Returns true when something is found, and in such case copies the result to
402 // |result| when |result| is not nullptr. (Note that there can be max. 1
403 // result derived from |credentials|.)
404 bool CheckCredentialAvailability(const PasswordForm
& credentials
,
406 const PasswordForm::Scheme
& scheme
,
407 PasswordForm
* result
) {
408 NativeBackendLibsecret
backend(321);
410 backend
.AddLogin(credentials
);
412 PasswordForm target_form
;
413 target_form
.origin
= url
;
414 target_form
.signon_realm
= url
.spec();
415 if (scheme
!= PasswordForm::SCHEME_HTML
) {
416 // For non-HTML forms, the realm used for authentication
417 // (http://tools.ietf.org/html/rfc1945#section-10.2) is appended to the
418 // signon_realm. Just use a default value for now.
419 target_form
.signon_realm
.append("Realm");
420 target_form
.scheme
= scheme
;
422 ScopedVector
<autofill::PasswordForm
> form_list
;
423 backend
.GetLogins(target_form
, &form_list
);
425 EXPECT_EQ(1u, global_mock_libsecret_items
.size());
426 if (global_mock_libsecret_items
.size() > 0)
427 CheckMockSecretItem(global_mock_libsecret_items
[0], credentials
,
431 if (form_list
.empty())
433 EXPECT_EQ(1u, form_list
.size());
435 *result
= *form_list
[0];
439 // Test that updating does not use PSL matching: Add a www.facebook.com
440 // password, then use PSL matching to get a copy of it for m.facebook.com, and
441 // add that copy as well. Now update the www.facebook.com password -- the
442 // m.facebook.com password should not get updated. Depending on the argument,
443 // the credential update is done via UpdateLogin or AddLogin.
444 void CheckPSLUpdate(UpdateType update_type
) {
445 NativeBackendLibsecret
backend(321);
447 backend
.AddLogin(form_facebook_
);
449 // Get the PSL-matched copy of the saved login for m.facebook.
450 const GURL
kMobileURL("http://m.facebook.com/");
451 PasswordForm m_facebook_lookup
;
452 m_facebook_lookup
.origin
= kMobileURL
;
453 m_facebook_lookup
.signon_realm
= kMobileURL
.spec();
454 ScopedVector
<autofill::PasswordForm
> form_list
;
455 backend
.GetLogins(m_facebook_lookup
, &form_list
);
457 EXPECT_EQ(1u, global_mock_libsecret_items
.size());
458 EXPECT_EQ(1u, form_list
.size());
459 PasswordForm m_facebook
= *form_list
[0];
461 EXPECT_EQ(kMobileURL
, m_facebook
.origin
);
462 EXPECT_EQ(kMobileURL
.spec(), m_facebook
.signon_realm
);
464 // Add the PSL-matched copy to saved logins.
465 backend
.AddLogin(m_facebook
);
466 EXPECT_EQ(2u, global_mock_libsecret_items
.size());
468 // Update www.facebook.com login.
469 PasswordForm
new_facebook(form_facebook_
);
470 const base::string16
kOldPassword(form_facebook_
.password_value
);
471 const base::string16
kNewPassword(UTF8ToUTF16("new_b"));
472 EXPECT_NE(kOldPassword
, kNewPassword
);
473 new_facebook
.password_value
= kNewPassword
;
474 scoped_ptr
<PasswordStoreChangeList
> not_used(new PasswordStoreChangeList());
475 switch (update_type
) {
476 case UPDATE_BY_UPDATELOGIN
:
477 backend
.UpdateLogin(new_facebook
, not_used
.get());
479 case UPDATE_BY_ADDLOGIN
:
480 backend
.AddLogin(new_facebook
);
484 EXPECT_EQ(2u, global_mock_libsecret_items
.size());
486 // Check that m.facebook.com login was not modified by the update.
487 backend
.GetLogins(m_facebook_lookup
, &form_list
);
489 // There should be two results -- the exact one, and the PSL-matched one.
490 EXPECT_EQ(2u, form_list
.size());
491 size_t index_non_psl
= 0;
492 if (!form_list
[index_non_psl
]->original_signon_realm
.empty())
494 EXPECT_EQ(kMobileURL
, form_list
[index_non_psl
]->origin
);
495 EXPECT_EQ(kMobileURL
.spec(), form_list
[index_non_psl
]->signon_realm
);
496 EXPECT_EQ(kOldPassword
, form_list
[index_non_psl
]->password_value
);
499 // Check that www.facebook.com login was modified by the update.
500 backend
.GetLogins(form_facebook_
, &form_list
);
501 // There should be two results -- the exact one, and the PSL-matched one.
502 EXPECT_EQ(2u, form_list
.size());
504 if (!form_list
[index_non_psl
]->original_signon_realm
.empty())
506 EXPECT_EQ(form_facebook_
.origin
, form_list
[index_non_psl
]->origin
);
507 EXPECT_EQ(form_facebook_
.signon_realm
,
508 form_list
[index_non_psl
]->signon_realm
);
509 EXPECT_EQ(kNewPassword
, form_list
[index_non_psl
]->password_value
);
513 // Checks various types of matching for forms with a non-HTML |scheme|.
514 void CheckMatchingWithScheme(const PasswordForm::Scheme
& scheme
) {
515 ASSERT_NE(PasswordForm::SCHEME_HTML
, scheme
);
516 other_auth_
.scheme
= scheme
;
518 // Don't match a non-HTML form with an HTML form.
520 CheckCredentialAvailability(other_auth_
, GURL("http://www.example.com"),
521 PasswordForm::SCHEME_HTML
, nullptr));
522 // Don't match an HTML form with non-HTML auth form.
523 EXPECT_FALSE(CheckCredentialAvailability(
524 form_google_
, GURL("http://www.google.com/"), scheme
, nullptr));
525 // Don't match two different non-HTML auth forms with different origin.
526 EXPECT_FALSE(CheckCredentialAvailability(
527 other_auth_
, GURL("http://first.example.com"), scheme
, nullptr));
528 // Do match non-HTML forms from the same origin.
529 EXPECT_TRUE(CheckCredentialAvailability(
530 other_auth_
, GURL("http://www.example.com/"), scheme
, nullptr));
533 void CheckRemoveLoginsBetween(RemoveBetweenMethod date_to_test
) {
534 NativeBackendLibsecret
backend(42);
536 base::Time now
= base::Time::Now();
537 base::Time next_day
= now
+ base::TimeDelta::FromDays(1);
538 form_google_
.date_synced
= base::Time();
539 form_isc_
.date_synced
= base::Time();
540 form_google_
.date_created
= now
;
541 form_isc_
.date_created
= now
;
542 if (date_to_test
== CREATED
) {
543 form_google_
.date_created
= now
;
544 form_isc_
.date_created
= next_day
;
546 form_google_
.date_synced
= now
;
547 form_isc_
.date_synced
= next_day
;
550 backend
.AddLogin(form_google_
);
551 backend
.AddLogin(form_isc_
);
553 PasswordStoreChangeList expected_changes
;
554 expected_changes
.push_back(
555 PasswordStoreChange(PasswordStoreChange::REMOVE
, form_google_
));
556 PasswordStoreChangeList changes
;
557 bool (NativeBackendLibsecret::*method
)(
558 base::Time
, base::Time
, password_manager::PasswordStoreChangeList
*) =
559 date_to_test
== CREATED
560 ? &NativeBackendLibsecret::RemoveLoginsCreatedBetween
561 : &NativeBackendLibsecret::RemoveLoginsSyncedBetween
;
563 bool result
= base::Bind(method
, base::Unretained(&backend
), base::Time(),
564 next_day
, &changes
).Run();
565 CheckPasswordChangesWithResult(&expected_changes
, &changes
, result
);
567 EXPECT_EQ(1u, global_mock_libsecret_items
.size());
568 if (global_mock_libsecret_items
.size() > 0)
569 CheckMockSecretItem(global_mock_libsecret_items
[0], form_isc_
,
573 expected_changes
.clear();
574 expected_changes
.push_back(
575 PasswordStoreChange(PasswordStoreChange::REMOVE
, form_isc_
));
577 result
= base::Bind(method
, base::Unretained(&backend
), next_day
,
578 base::Time(), &changes
).Run();
579 CheckPasswordChangesWithResult(&expected_changes
, &changes
, result
);
581 EXPECT_EQ(0u, global_mock_libsecret_items
.size());
584 base::MessageLoopForUI message_loop_
;
586 // Provide some test forms to avoid having to set them up in each test.
587 PasswordForm form_google_
;
588 PasswordForm form_facebook_
;
589 PasswordForm form_isc_
;
590 PasswordForm other_auth_
;
593 TEST_F(NativeBackendLibsecretTest
, BasicAddLogin
) {
594 NativeBackendLibsecret
backend(42);
596 backend
.AddLogin(form_google_
);
598 EXPECT_EQ(1u, global_mock_libsecret_items
.size());
599 if (global_mock_libsecret_items
.size() > 0)
600 CheckMockSecretItem(global_mock_libsecret_items
[0], form_google_
,
604 TEST_F(NativeBackendLibsecretTest
, BasicListLogins
) {
605 NativeBackendLibsecret
backend(42);
607 backend
.AddLogin(form_google_
);
609 ScopedVector
<autofill::PasswordForm
> form_list
;
610 backend
.GetAutofillableLogins(&form_list
);
612 // Quick check that we got something back.
613 EXPECT_EQ(1u, form_list
.size());
616 EXPECT_EQ(1u, global_mock_libsecret_items
.size());
617 if (global_mock_libsecret_items
.size() > 0)
618 CheckMockSecretItem(global_mock_libsecret_items
[0], form_google_
,
622 // Save a password for www.facebook.com and see it suggested for m.facebook.com.
623 TEST_F(NativeBackendLibsecretTest
, PSLMatchingPositive
) {
625 const GURL
kMobileURL("http://m.facebook.com/");
626 EXPECT_TRUE(CheckCredentialAvailability(form_facebook_
, kMobileURL
,
627 PasswordForm::SCHEME_HTML
, &result
));
628 EXPECT_EQ(kMobileURL
, result
.origin
);
629 EXPECT_EQ(kMobileURL
.spec(), result
.signon_realm
);
632 // Save a password for www.facebook.com and see it not suggested for
634 TEST_F(NativeBackendLibsecretTest
, PSLMatchingNegativeDomainMismatch
) {
635 EXPECT_FALSE(CheckCredentialAvailability(form_facebook_
,
636 GURL("http://m-facebook.com/"),
637 PasswordForm::SCHEME_HTML
, nullptr));
640 // Test PSL matching is off for domains excluded from it.
641 TEST_F(NativeBackendLibsecretTest
, PSLMatchingDisabledDomains
) {
642 EXPECT_FALSE(CheckCredentialAvailability(form_google_
,
643 GURL("http://one.google.com/"),
644 PasswordForm::SCHEME_HTML
, nullptr));
647 // Make sure PSL matches aren't available for non-HTML forms.
648 TEST_F(NativeBackendLibsecretTest
, PSLMatchingDisabledForNonHTMLForms
) {
649 CheckMatchingWithScheme(PasswordForm::SCHEME_BASIC
);
650 CheckMatchingWithScheme(PasswordForm::SCHEME_DIGEST
);
651 CheckMatchingWithScheme(PasswordForm::SCHEME_OTHER
);
654 TEST_F(NativeBackendLibsecretTest
, PSLUpdatingStrictUpdateLogin
) {
655 CheckPSLUpdate(UPDATE_BY_UPDATELOGIN
);
658 TEST_F(NativeBackendLibsecretTest
, PSLUpdatingStrictAddLogin
) {
659 // TODO(vabr): if AddLogin becomes no longer valid for existing logins, then
660 // just delete this test.
661 CheckPSLUpdate(UPDATE_BY_ADDLOGIN
);
664 TEST_F(NativeBackendLibsecretTest
, BasicUpdateLogin
) {
665 NativeBackendLibsecret
backend(42);
667 backend
.AddLogin(form_google_
);
669 PasswordForm
new_form_google(form_google_
);
670 new_form_google
.times_used
= 1;
671 new_form_google
.action
= GURL("http://www.google.com/different/login");
673 EXPECT_EQ(1u, global_mock_libsecret_items
.size());
674 if (global_mock_libsecret_items
.size() > 0)
675 CheckMockSecretItem(global_mock_libsecret_items
[0], form_google_
,
679 PasswordStoreChangeList changes
;
680 backend
.UpdateLogin(new_form_google
, &changes
);
682 ASSERT_EQ(1u, changes
.size());
683 EXPECT_EQ(PasswordStoreChange::UPDATE
, changes
.front().type());
684 EXPECT_EQ(new_form_google
, changes
.front().form());
685 EXPECT_EQ(1u, global_mock_libsecret_items
.size());
686 if (global_mock_libsecret_items
.size() > 0)
687 CheckMockSecretItem(global_mock_libsecret_items
[0], new_form_google
,
691 TEST_F(NativeBackendLibsecretTest
, BasicRemoveLogin
) {
692 NativeBackendLibsecret
backend(42);
694 backend
.AddLogin(form_google_
);
696 EXPECT_EQ(1u, global_mock_libsecret_items
.size());
697 if (global_mock_libsecret_items
.size() > 0)
698 CheckMockSecretItem(global_mock_libsecret_items
[0], form_google_
,
701 backend
.RemoveLogin(form_google_
);
703 EXPECT_EQ(0u, global_mock_libsecret_items
.size());
706 // Verify fix for http://crbug.com/408783.
707 TEST_F(NativeBackendLibsecretTest
, RemoveLoginActionMismatch
) {
708 NativeBackendLibsecret
backend(42);
710 backend
.AddLogin(form_google_
);
712 EXPECT_EQ(1u, global_mock_libsecret_items
.size());
713 if (global_mock_libsecret_items
.size() > 0)
714 CheckMockSecretItem(global_mock_libsecret_items
[0], form_google_
,
717 // Action url match not required for removal.
718 form_google_
.action
= GURL("https://some.other.url.com/path");
720 backend
.RemoveLogin(form_google_
);
722 EXPECT_EQ(0u, global_mock_libsecret_items
.size());
725 TEST_F(NativeBackendLibsecretTest
, RemoveNonexistentLogin
) {
726 NativeBackendLibsecret
backend(42);
728 // First add an unrelated login.
729 backend
.AddLogin(form_google_
);
731 EXPECT_EQ(1u, global_mock_libsecret_items
.size());
732 if (global_mock_libsecret_items
.size() > 0)
733 CheckMockSecretItem(global_mock_libsecret_items
[0], form_google_
,
736 // Attempt to remove a login that doesn't exist.
737 backend
.RemoveLogin(form_isc_
);
739 // Make sure we can still get the first form back.
740 ScopedVector
<autofill::PasswordForm
> form_list
;
741 backend
.GetAutofillableLogins(&form_list
);
743 // Quick check that we got something back.
744 EXPECT_EQ(1u, form_list
.size());
747 EXPECT_EQ(1u, global_mock_libsecret_items
.size());
748 if (global_mock_libsecret_items
.size() > 0)
749 CheckMockSecretItem(global_mock_libsecret_items
[0], form_google_
,
753 TEST_F(NativeBackendLibsecretTest
, UpdateNonexistentLogin
) {
754 NativeBackendLibsecret
backend(42);
756 // First add an unrelated login.
757 backend
.AddLogin(form_google_
);
759 EXPECT_EQ(1u, global_mock_libsecret_items
.size());
760 if (global_mock_libsecret_items
.size() > 0)
761 CheckMockSecretItem(global_mock_libsecret_items
[0], form_google_
,
764 // Attempt to update a login that doesn't exist.
765 PasswordStoreChangeList changes
;
766 backend
.UpdateLogin(form_isc_
, &changes
);
768 EXPECT_EQ(PasswordStoreChangeList(), changes
);
769 EXPECT_EQ(1u, global_mock_libsecret_items
.size());
770 if (global_mock_libsecret_items
.size() > 0)
771 CheckMockSecretItem(global_mock_libsecret_items
[0], form_google_
,
775 TEST_F(NativeBackendLibsecretTest
, AddDuplicateLogin
) {
776 NativeBackendLibsecret
backend(42);
778 PasswordStoreChangeList expected_changes
, actual_changes
;
779 expected_changes
.push_back(
780 PasswordStoreChange(PasswordStoreChange::ADD
, form_google_
));
781 actual_changes
= backend
.AddLogin(form_google_
);
782 CheckPasswordChanges(expected_changes
, actual_changes
);
784 expected_changes
.clear();
785 expected_changes
.push_back(
786 PasswordStoreChange(PasswordStoreChange::REMOVE
, form_google_
));
787 form_google_
.times_used
++;
788 expected_changes
.push_back(
789 PasswordStoreChange(PasswordStoreChange::ADD
, form_google_
));
791 actual_changes
= backend
.AddLogin(form_google_
);
792 CheckPasswordChanges(expected_changes
, actual_changes
);
794 EXPECT_EQ(1u, global_mock_libsecret_items
.size());
795 if (global_mock_libsecret_items
.size() > 0)
796 CheckMockSecretItem(global_mock_libsecret_items
[0], form_google_
,
800 TEST_F(NativeBackendLibsecretTest
, ListLoginsAppends
) {
801 NativeBackendLibsecret
backend(42);
803 backend
.AddLogin(form_google_
);
805 // Send the same request twice with the same list both times.
806 ScopedVector
<autofill::PasswordForm
> form_list
;
807 backend
.GetAutofillableLogins(&form_list
);
808 backend
.GetAutofillableLogins(&form_list
);
810 // Quick check that we got two results back.
811 EXPECT_EQ(2u, form_list
.size());
814 EXPECT_EQ(1u, global_mock_libsecret_items
.size());
815 if (global_mock_libsecret_items
.size() > 0)
816 CheckMockSecretItem(global_mock_libsecret_items
[0], form_google_
,
820 TEST_F(NativeBackendLibsecretTest
, AndroidCredentials
) {
821 NativeBackendLibsecret
backend(42);
824 PasswordForm observed_android_form
;
825 observed_android_form
.scheme
= PasswordForm::SCHEME_HTML
;
826 observed_android_form
.signon_realm
=
827 "android://7x7IDboo8u9YKraUsbmVkuf1-@net.rateflix.app/";
828 PasswordForm saved_android_form
= observed_android_form
;
829 saved_android_form
.username_value
= base::UTF8ToUTF16("randomusername");
830 saved_android_form
.password_value
= base::UTF8ToUTF16("password");
831 saved_android_form
.date_created
= base::Time::Now();
833 backend
.AddLogin(saved_android_form
);
835 ScopedVector
<autofill::PasswordForm
> form_list
;
836 backend
.GetAutofillableLogins(&form_list
);
838 EXPECT_EQ(1u, form_list
.size());
839 EXPECT_EQ(saved_android_form
, *form_list
[0]);
843 TEST_F(NativeBackendLibsecretTest
, RemoveLoginsCreatedBetween
) {
844 CheckRemoveLoginsBetween(CREATED
);
847 TEST_F(NativeBackendLibsecretTest
, RemoveLoginsSyncedBetween
) {
848 CheckRemoveLoginsBetween(SYNCED
);
851 TEST_F(NativeBackendLibsecretTest
, SomeKeyringAttributesAreMissing
) {
852 // Absent attributes should be filled with default values.
853 NativeBackendLibsecret
backend(42);
855 backend
.AddLogin(form_google_
);
857 EXPECT_EQ(1u, global_mock_libsecret_items
.size());
858 // Remove a string attribute.
859 global_mock_libsecret_items
[0]->RemoveAttribute("avatar_url");
860 // Remove an integer attribute.
861 global_mock_libsecret_items
[0]->RemoveAttribute("ssl_valid");
863 ScopedVector
<autofill::PasswordForm
> form_list
;
864 backend
.GetAutofillableLogins(&form_list
);
866 EXPECT_EQ(1u, form_list
.size());
867 EXPECT_EQ(GURL(""), form_list
[0]->avatar_url
);
868 EXPECT_FALSE(form_list
[0]->ssl_valid
);
871 // TODO(mdm): add more basic tests here at some point.