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/location.h"
9 #include "base/prefs/pref_service.h"
10 #include "base/single_thread_task_runner.h"
11 #include "base/stl_util.h"
12 #include "base/strings/string_number_conversions.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/thread_task_runner_handle.h"
17 #include "base/time/time.h"
18 #include "chrome/browser/password_manager/native_backend_libsecret.h"
19 #include "chrome/test/base/testing_profile.h"
20 #include "components/autofill/core/common/password_form.h"
21 #include "components/password_manager/core/browser/psl_matching_helper.h"
22 #include "components/password_manager/core/common/password_manager_pref_names.h"
23 #include "testing/gtest/include/gtest/gtest.h"
25 using autofill::PasswordForm
;
26 using base::UTF8ToUTF16
;
27 using base::UTF16ToUTF8
;
28 using password_manager::PasswordStoreChange
;
29 using password_manager::PasswordStoreChangeList
;
33 // What follows is a very simple implementation of the subset of the Libsecret
34 // API that we actually use. It gets substituted for the real one by
35 // MockLibsecretLoader, which hooks into the facility normally used to load
36 // the libsecret library at runtime to avoid a static dependency on it.
38 struct MockSecretValue
{
40 explicit MockSecretValue(gchar
* password
) : password(password
) {}
41 ~MockSecretValue() { g_free(password
); }
44 struct MockSecretItem
{
45 MockSecretValue
* value
;
46 GHashTable
* attributes
;
48 MockSecretItem(MockSecretValue
* value
, GHashTable
* attributes
)
49 : value(value
), attributes(attributes
) {}
52 g_hash_table_destroy(attributes
);
55 void RemoveAttribute(const char* keyname
) {
56 g_hash_table_remove(attributes
, keyname
);
60 bool Matches(MockSecretItem
* item
, GHashTable
* query
) {
61 GHashTable
* attributes
= item
->attributes
;
65 g_hash_table_iter_init(&iter
, query
);
67 while (g_hash_table_iter_next(&iter
, reinterpret_cast<gpointer
*>(&name
),
68 reinterpret_cast<gpointer
*>(&query_value
))) {
69 gchar
* value
= static_cast<gchar
*>(g_hash_table_lookup(attributes
, name
));
70 if (value
== nullptr || strcmp(value
, query_value
) != 0)
76 bool IsStringAttribute(const SecretSchema
* schema
, const std::string
& name
) {
77 for (size_t i
= 0; schema
->attributes
[i
].name
; ++i
)
78 if (name
== schema
->attributes
[i
].name
)
79 return schema
->attributes
[i
].type
== SECRET_SCHEMA_ATTRIBUTE_STRING
;
80 NOTREACHED() << "Requested type of nonexistent attribute";
84 // The list of all libsecret items we have stored.
85 ScopedVector
<MockSecretItem
>* global_mock_libsecret_items
;
86 bool global_mock_libsecret_reject_local_ids
= false;
88 gboolean
mock_secret_password_store_sync(const SecretSchema
* schema
,
89 const gchar
* collection
,
91 const gchar
* password
,
92 GCancellable
* cancellable
,
95 GHashTable
* attributes
=
96 g_hash_table_new_full(g_str_hash
, g_str_equal
, g_free
, g_free
);
100 while ((name
= va_arg(ap
, gchar
*))) {
102 if (IsStringAttribute(schema
, name
)) {
103 value
= g_strdup(va_arg(ap
, gchar
*));
104 VLOG(1) << "Adding item attribute " << name
<< ", value '" << value
107 uint32_t intvalue
= va_arg(ap
, uint32_t);
108 VLOG(1) << "Adding item attribute " << name
<< ", value " << intvalue
;
109 value
= g_strdup_printf("%u", intvalue
);
111 g_hash_table_insert(attributes
, g_strdup(name
), value
);
114 MockSecretValue
* secret_value
= new MockSecretValue(g_strdup(password
));
115 MockSecretItem
* item
= new MockSecretItem(secret_value
, attributes
);
116 global_mock_libsecret_items
->push_back(item
);
120 GList
* mock_secret_service_search_sync(SecretService
* service
,
121 const SecretSchema
* schema
,
122 GHashTable
* attributes
,
123 SecretSearchFlags flags
,
124 GCancellable
* cancellable
,
126 GList
* result
= nullptr;
127 for (MockSecretItem
* item
: *global_mock_libsecret_items
) {
128 if (Matches(item
, attributes
))
129 result
= g_list_append(result
, item
);
134 gboolean
mock_secret_password_clear_sync(const SecretSchema
* schema
,
135 GCancellable
* cancellable
,
138 GHashTable
* attributes
=
139 g_hash_table_new_full(g_str_hash
, g_str_equal
, g_free
, g_free
);
143 while ((name
= va_arg(ap
, gchar
*))) {
145 if (IsStringAttribute(schema
, name
)) {
146 value
= g_strdup(va_arg(ap
, gchar
*));
147 VLOG(1) << "Adding item attribute " << name
<< ", value '" << value
150 uint32_t intvalue
= va_arg(ap
, uint32_t);
151 VLOG(1) << "Adding item attribute " << name
<< ", value " << intvalue
;
152 value
= g_strdup_printf("%u", intvalue
);
154 g_hash_table_insert(attributes
, g_strdup(name
), value
);
158 ScopedVector
<MockSecretItem
> kept_mock_libsecret_items
;
159 kept_mock_libsecret_items
.reserve(global_mock_libsecret_items
->size());
160 for (auto& item
: *global_mock_libsecret_items
) {
161 if (!Matches(item
, attributes
)) {
162 kept_mock_libsecret_items
.push_back(item
);
166 global_mock_libsecret_items
->swap(kept_mock_libsecret_items
);
168 g_hash_table_unref(attributes
);
170 global_mock_libsecret_items
->size() != kept_mock_libsecret_items
.size();
173 MockSecretValue
* mock_secret_item_get_secret(MockSecretItem
* self
) {
177 const gchar
* mock_secret_value_get_text(MockSecretValue
* value
) {
178 return value
->password
;
181 GHashTable
* mock_secret_item_get_attributes(MockSecretItem
* self
) {
182 // Libsecret backend will make unreference of received attributes, so in
183 // order to save them we need to increase their reference number.
184 g_hash_table_ref(self
->attributes
);
185 return self
->attributes
;
188 gboolean
mock_secret_item_load_secret_sync(MockSecretItem
* self
,
189 GCancellable
* cancellable
,
194 void mock_secret_value_unref(gpointer value
) {
197 // Inherit to get access to protected fields.
198 class MockLibsecretLoader
: public LibsecretLoader
{
200 static bool LoadMockLibsecret() {
201 secret_password_store_sync
= &mock_secret_password_store_sync
;
202 secret_service_search_sync
= &mock_secret_service_search_sync
;
203 secret_password_clear_sync
= &mock_secret_password_clear_sync
;
204 secret_item_get_secret
=
205 (decltype(&::secret_item_get_secret
)) & mock_secret_item_get_secret
;
206 secret_value_get_text
=
207 (decltype(&::secret_value_get_text
)) & mock_secret_value_get_text
;
208 secret_item_get_attributes
= (decltype(&::secret_item_get_attributes
)) &
209 mock_secret_item_get_attributes
;
210 secret_item_load_secret_sync
= (decltype(&::secret_item_load_secret_sync
)) &
211 mock_secret_item_load_secret_sync
;
213 (decltype(&::secret_value_unref
)) & mock_secret_value_unref
;
214 libsecret_loaded
= true;
215 // Reset the state of the mock library.
216 global_mock_libsecret_items
->clear();
217 global_mock_libsecret_reject_local_ids
= false;
222 void CheckPasswordChanges(const PasswordStoreChangeList
& expected_list
,
223 const PasswordStoreChangeList
& actual_list
) {
224 ASSERT_EQ(expected_list
.size(), actual_list
.size());
225 for (size_t i
= 0; i
< expected_list
.size(); ++i
) {
226 EXPECT_EQ(expected_list
[i
].type(), actual_list
[i
].type());
227 EXPECT_EQ(expected_list
[i
].form(), actual_list
[i
].form());
231 void VerifiedAdd(NativeBackendLibsecret
* backend
, const PasswordForm
& form
) {
232 SCOPED_TRACE("VerifiedAdd");
233 PasswordStoreChangeList changes
= backend
->AddLogin(form
);
234 PasswordStoreChangeList
expected(1,
235 PasswordStoreChange(PasswordStoreChange::ADD
,
237 CheckPasswordChanges(expected
, changes
);
240 void VerifiedUpdate(NativeBackendLibsecret
* backend
, const PasswordForm
& form
) {
241 SCOPED_TRACE("VerifiedUpdate");
242 PasswordStoreChangeList changes
;
243 EXPECT_TRUE(backend
->UpdateLogin(form
, &changes
));
244 PasswordStoreChangeList
expected(1, PasswordStoreChange(
245 PasswordStoreChange::UPDATE
, form
));
246 CheckPasswordChanges(expected
, changes
);
249 void VerifiedRemove(NativeBackendLibsecret
* backend
, const PasswordForm
& form
) {
250 SCOPED_TRACE("VerifiedRemove");
251 PasswordStoreChangeList changes
;
252 EXPECT_TRUE(backend
->RemoveLogin(form
, &changes
));
253 CheckPasswordChanges(PasswordStoreChangeList(1, PasswordStoreChange(
254 PasswordStoreChange::REMOVE
, form
)), changes
);
257 } // anonymous namespace
259 class NativeBackendLibsecretTest
: public testing::Test
{
261 enum UpdateType
{ // Used in CheckPSLUpdate().
262 UPDATE_BY_UPDATELOGIN
,
265 enum RemoveBetweenMethod
{ // Used in CheckRemoveLoginsBetween().
270 NativeBackendLibsecretTest() {}
272 void SetUp() override
{
273 ASSERT_FALSE(global_mock_libsecret_items
);
274 global_mock_libsecret_items
= &mock_libsecret_items_
;
276 ASSERT_TRUE(MockLibsecretLoader::LoadMockLibsecret());
278 form_google_
.origin
= GURL("http://www.google.com/");
279 form_google_
.action
= GURL("http://www.google.com/login");
280 form_google_
.username_element
= UTF8ToUTF16("user");
281 form_google_
.username_value
= UTF8ToUTF16("joeschmoe");
282 form_google_
.password_element
= UTF8ToUTF16("pass");
283 form_google_
.password_value
= UTF8ToUTF16("seekrit");
284 form_google_
.submit_element
= UTF8ToUTF16("submit");
285 form_google_
.signon_realm
= "http://www.google.com/";
286 form_google_
.type
= PasswordForm::TYPE_GENERATED
;
287 form_google_
.date_created
= base::Time::Now();
288 form_google_
.date_synced
= base::Time::Now();
289 form_google_
.display_name
= UTF8ToUTF16("Joe Schmoe");
290 form_google_
.icon_url
= GURL("http://www.google.com/icon");
291 form_google_
.federation_url
= GURL("http://www.google.com/federation_url");
292 form_google_
.skip_zero_click
= true;
293 form_google_
.generation_upload_status
= PasswordForm::POSITIVE_SIGNAL_SENT
;
294 form_google_
.form_data
.name
= UTF8ToUTF16("form_name");
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_
.icon_url
= GURL("http://www.facebook.com/icon");
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::ThreadTaskRunnerHandle::Get()->PostTask(
333 FROM_HERE
, base::MessageLoop::QuitClosure());
334 base::MessageLoop::current()->Run();
335 ASSERT_TRUE(global_mock_libsecret_items
);
336 global_mock_libsecret_items
= nullptr;
339 void RunUIThread() { base::MessageLoop::current()->Run(); }
341 void CheckUint32Attribute(const MockSecretItem
* item
,
342 const std::string
& attribute
,
344 gpointer item_value
=
345 g_hash_table_lookup(item
->attributes
, attribute
.c_str());
346 EXPECT_TRUE(item_value
) << " in attribute " << attribute
;
349 bool conversion_ok
= base::StringToUint((char*)item_value
, &int_value
);
350 EXPECT_TRUE(conversion_ok
);
351 EXPECT_EQ(value
, int_value
);
355 void CheckStringAttribute(const MockSecretItem
* item
,
356 const std::string
& attribute
,
357 const std::string
& value
) {
358 gpointer item_value
=
359 g_hash_table_lookup(item
->attributes
, attribute
.c_str());
360 EXPECT_TRUE(item_value
) << " in attribute " << attribute
;
362 EXPECT_EQ(value
, static_cast<char*>(item_value
));
366 void CheckMockSecretItem(const MockSecretItem
* item
,
367 const PasswordForm
& form
,
368 const std::string
& app_string
) {
369 EXPECT_EQ(UTF16ToUTF8(form
.password_value
), item
->value
->password
);
370 EXPECT_EQ(22u, g_hash_table_size(item
->attributes
));
371 CheckStringAttribute(item
, "origin_url", form
.origin
.spec());
372 CheckStringAttribute(item
, "action_url", form
.action
.spec());
373 CheckStringAttribute(item
, "username_element",
374 UTF16ToUTF8(form
.username_element
));
375 CheckStringAttribute(item
, "username_value",
376 UTF16ToUTF8(form
.username_value
));
377 CheckStringAttribute(item
, "password_element",
378 UTF16ToUTF8(form
.password_element
));
379 CheckStringAttribute(item
, "submit_element",
380 UTF16ToUTF8(form
.submit_element
));
381 CheckStringAttribute(item
, "signon_realm", form
.signon_realm
);
382 CheckUint32Attribute(item
, "ssl_valid", form
.ssl_valid
);
383 CheckUint32Attribute(item
, "preferred", form
.preferred
);
384 // We don't check the date created. It varies.
385 CheckUint32Attribute(item
, "blacklisted_by_user", form
.blacklisted_by_user
);
386 CheckUint32Attribute(item
, "type", form
.type
);
387 CheckUint32Attribute(item
, "times_used", form
.times_used
);
388 CheckUint32Attribute(item
, "scheme", form
.scheme
);
389 CheckStringAttribute(
391 base::Int64ToString(form
.date_synced
.ToInternalValue()));
392 CheckStringAttribute(item
, "display_name", UTF16ToUTF8(form
.display_name
));
393 CheckStringAttribute(item
, "avatar_url", form
.icon_url
.spec());
394 CheckStringAttribute(item
, "federation_url", form
.federation_url
.spec());
395 CheckUint32Attribute(item
, "skip_zero_click", form
.skip_zero_click
);
396 CheckUint32Attribute(item
, "generation_upload_status",
397 form
.generation_upload_status
);
398 CheckStringAttribute(item
, "application", app_string
);
399 autofill::FormData actual
;
400 DeserializeFormDataFromBase64String(
401 static_cast<char*>(g_hash_table_lookup(item
->attributes
, "form_data")),
403 EXPECT_TRUE(form
.form_data
.SameFormAs(actual
));
406 // Saves |credentials| and then gets logins matching |url| and |scheme|.
407 // Returns true when something is found, and in such case copies the result to
408 // |result| when |result| is not nullptr. (Note that there can be max. 1
409 // result derived from |credentials|.)
410 bool CheckCredentialAvailability(const PasswordForm
& credentials
,
412 const PasswordForm::Scheme
& scheme
,
413 PasswordForm
* result
) {
414 NativeBackendLibsecret
backend(321);
416 VerifiedAdd(&backend
, credentials
);
418 PasswordForm target_form
;
419 target_form
.origin
= url
;
420 target_form
.signon_realm
= url
.spec();
421 if (scheme
!= PasswordForm::SCHEME_HTML
) {
422 // For non-HTML forms, the realm used for authentication
423 // (http://tools.ietf.org/html/rfc1945#section-10.2) is appended to the
424 // signon_realm. Just use a default value for now.
425 target_form
.signon_realm
.append("Realm");
426 target_form
.scheme
= scheme
;
428 ScopedVector
<autofill::PasswordForm
> form_list
;
429 EXPECT_TRUE(backend
.GetLogins(target_form
, &form_list
));
431 EXPECT_EQ(1u, global_mock_libsecret_items
->size());
432 if (!global_mock_libsecret_items
->empty())
433 CheckMockSecretItem((*global_mock_libsecret_items
)[0], credentials
,
435 global_mock_libsecret_items
->clear();
437 if (form_list
.empty())
439 EXPECT_EQ(1u, form_list
.size());
441 *result
= *form_list
[0];
445 // Test that updating does not use PSL matching: Add a www.facebook.com
446 // password, then use PSL matching to get a copy of it for m.facebook.com, and
447 // add that copy as well. Now update the www.facebook.com password -- the
448 // m.facebook.com password should not get updated. Depending on the argument,
449 // the credential update is done via UpdateLogin or AddLogin.
450 void CheckPSLUpdate(UpdateType update_type
) {
451 NativeBackendLibsecret
backend(321);
453 VerifiedAdd(&backend
, form_facebook_
);
455 // Get the PSL-matched copy of the saved login for m.facebook.
456 const GURL
kMobileURL("http://m.facebook.com/");
457 PasswordForm m_facebook_lookup
;
458 m_facebook_lookup
.origin
= kMobileURL
;
459 m_facebook_lookup
.signon_realm
= kMobileURL
.spec();
460 ScopedVector
<autofill::PasswordForm
> form_list
;
461 EXPECT_TRUE(backend
.GetLogins(m_facebook_lookup
, &form_list
));
463 EXPECT_EQ(1u, global_mock_libsecret_items
->size());
464 EXPECT_EQ(1u, form_list
.size());
465 PasswordForm m_facebook
= *form_list
[0];
467 EXPECT_EQ(kMobileURL
, m_facebook
.origin
);
468 EXPECT_EQ(kMobileURL
.spec(), m_facebook
.signon_realm
);
470 // Add the PSL-matched copy to saved logins.
471 VerifiedAdd(&backend
, m_facebook
);
472 EXPECT_EQ(2u, global_mock_libsecret_items
->size());
474 // Update www.facebook.com login.
475 PasswordForm
new_facebook(form_facebook_
);
476 const base::string16
kOldPassword(form_facebook_
.password_value
);
477 const base::string16
kNewPassword(UTF8ToUTF16("new_b"));
478 EXPECT_NE(kOldPassword
, kNewPassword
);
479 new_facebook
.password_value
= kNewPassword
;
480 switch (update_type
) {
481 case UPDATE_BY_UPDATELOGIN
:
482 VerifiedUpdate(&backend
, new_facebook
);
484 case UPDATE_BY_ADDLOGIN
:
485 // This is an overwrite call.
486 backend
.AddLogin(new_facebook
);
490 EXPECT_EQ(2u, global_mock_libsecret_items
->size());
492 // Check that m.facebook.com login was not modified by the update.
493 EXPECT_TRUE(backend
.GetLogins(m_facebook_lookup
, &form_list
));
495 // There should be two results -- the exact one, and the PSL-matched one.
496 EXPECT_EQ(2u, form_list
.size());
497 size_t index_non_psl
= 0;
498 if (!form_list
[index_non_psl
]->original_signon_realm
.empty())
500 EXPECT_EQ(kMobileURL
, form_list
[index_non_psl
]->origin
);
501 EXPECT_EQ(kMobileURL
.spec(), form_list
[index_non_psl
]->signon_realm
);
502 EXPECT_EQ(kOldPassword
, form_list
[index_non_psl
]->password_value
);
505 // Check that www.facebook.com login was modified by the update.
506 EXPECT_TRUE(backend
.GetLogins(form_facebook_
, &form_list
));
507 // There should be two results -- the exact one, and the PSL-matched one.
508 EXPECT_EQ(2u, form_list
.size());
510 if (!form_list
[index_non_psl
]->original_signon_realm
.empty())
512 EXPECT_EQ(form_facebook_
.origin
, form_list
[index_non_psl
]->origin
);
513 EXPECT_EQ(form_facebook_
.signon_realm
,
514 form_list
[index_non_psl
]->signon_realm
);
515 EXPECT_EQ(kNewPassword
, form_list
[index_non_psl
]->password_value
);
519 // Checks various types of matching for forms with a non-HTML |scheme|.
520 void CheckMatchingWithScheme(const PasswordForm::Scheme
& scheme
) {
521 ASSERT_NE(PasswordForm::SCHEME_HTML
, scheme
);
522 other_auth_
.scheme
= scheme
;
524 // Don't match a non-HTML form with an HTML form.
526 CheckCredentialAvailability(other_auth_
, GURL("http://www.example.com"),
527 PasswordForm::SCHEME_HTML
, nullptr));
528 // Don't match an HTML form with non-HTML auth form.
529 EXPECT_FALSE(CheckCredentialAvailability(
530 form_google_
, GURL("http://www.google.com/"), scheme
, nullptr));
531 // Don't match two different non-HTML auth forms with different origin.
532 EXPECT_FALSE(CheckCredentialAvailability(
533 other_auth_
, GURL("http://first.example.com"), scheme
, nullptr));
534 // Do match non-HTML forms from the same origin.
535 EXPECT_TRUE(CheckCredentialAvailability(
536 other_auth_
, GURL("http://www.example.com/"), scheme
, nullptr));
539 void CheckRemoveLoginsBetween(RemoveBetweenMethod date_to_test
) {
540 NativeBackendLibsecret
backend(42);
542 base::Time now
= base::Time::Now();
543 base::Time next_day
= now
+ base::TimeDelta::FromDays(1);
544 form_google_
.date_synced
= base::Time();
545 form_isc_
.date_synced
= base::Time();
546 form_google_
.date_created
= now
;
547 form_isc_
.date_created
= now
;
548 if (date_to_test
== CREATED
) {
549 form_google_
.date_created
= now
;
550 form_isc_
.date_created
= next_day
;
552 form_google_
.date_synced
= now
;
553 form_isc_
.date_synced
= next_day
;
556 VerifiedAdd(&backend
, form_google_
);
557 VerifiedAdd(&backend
, form_isc_
);
559 PasswordStoreChangeList expected_changes
;
560 expected_changes
.push_back(
561 PasswordStoreChange(PasswordStoreChange::REMOVE
, form_google_
));
562 PasswordStoreChangeList changes
;
563 bool (NativeBackendLibsecret::*method
)(
564 base::Time
, base::Time
, password_manager::PasswordStoreChangeList
*) =
565 date_to_test
== CREATED
566 ? &NativeBackendLibsecret::RemoveLoginsCreatedBetween
567 : &NativeBackendLibsecret::RemoveLoginsSyncedBetween
;
569 EXPECT_TRUE(base::Bind(method
, base::Unretained(&backend
), base::Time(),
570 next_day
, &changes
).Run());
571 CheckPasswordChanges(expected_changes
, changes
);
573 EXPECT_EQ(1u, global_mock_libsecret_items
->size());
574 if (!global_mock_libsecret_items
->empty() > 0)
575 CheckMockSecretItem((*global_mock_libsecret_items
)[0], form_isc_
,
579 expected_changes
.clear();
580 expected_changes
.push_back(
581 PasswordStoreChange(PasswordStoreChange::REMOVE
, form_isc_
));
583 EXPECT_TRUE(base::Bind(method
, base::Unretained(&backend
), next_day
,
584 base::Time(), &changes
).Run());
585 CheckPasswordChanges(expected_changes
, changes
);
587 EXPECT_TRUE(global_mock_libsecret_items
->empty());
590 base::MessageLoopForUI message_loop_
;
592 // Provide some test forms to avoid having to set them up in each test.
593 PasswordForm form_google_
;
594 PasswordForm form_facebook_
;
595 PasswordForm form_isc_
;
596 PasswordForm other_auth_
;
598 ScopedVector
<MockSecretItem
> mock_libsecret_items_
;
601 TEST_F(NativeBackendLibsecretTest
, BasicAddLogin
) {
602 NativeBackendLibsecret
backend(42);
604 VerifiedAdd(&backend
, form_google_
);
606 EXPECT_EQ(1u, global_mock_libsecret_items
->size());
607 if (!global_mock_libsecret_items
->empty())
608 CheckMockSecretItem((*global_mock_libsecret_items
)[0], form_google_
,
612 TEST_F(NativeBackendLibsecretTest
, BasicListLogins
) {
613 NativeBackendLibsecret
backend(42);
615 VerifiedAdd(&backend
, form_google_
);
617 ScopedVector
<autofill::PasswordForm
> form_list
;
618 EXPECT_TRUE(backend
.GetAutofillableLogins(&form_list
));
620 ASSERT_EQ(1u, form_list
.size());
621 EXPECT_EQ(form_google_
, *form_list
[0]);
623 EXPECT_EQ(1u, global_mock_libsecret_items
->size());
624 if (!global_mock_libsecret_items
->empty())
625 CheckMockSecretItem((*global_mock_libsecret_items
)[0], form_google_
,
629 // Save a password for www.facebook.com and see it suggested for m.facebook.com.
630 TEST_F(NativeBackendLibsecretTest
, PSLMatchingPositive
) {
632 const GURL
kMobileURL("http://m.facebook.com/");
633 EXPECT_TRUE(CheckCredentialAvailability(form_facebook_
, kMobileURL
,
634 PasswordForm::SCHEME_HTML
, &result
));
635 EXPECT_EQ(kMobileURL
, result
.origin
);
636 EXPECT_EQ(kMobileURL
.spec(), result
.signon_realm
);
639 // Save a password for www.facebook.com and see it not suggested for
641 TEST_F(NativeBackendLibsecretTest
, PSLMatchingNegativeDomainMismatch
) {
642 EXPECT_FALSE(CheckCredentialAvailability(form_facebook_
,
643 GURL("http://m-facebook.com/"),
644 PasswordForm::SCHEME_HTML
, nullptr));
647 // Test PSL matching is off for domains excluded from it.
648 TEST_F(NativeBackendLibsecretTest
, PSLMatchingDisabledDomains
) {
649 EXPECT_FALSE(CheckCredentialAvailability(form_google_
,
650 GURL("http://one.google.com/"),
651 PasswordForm::SCHEME_HTML
, nullptr));
654 // Make sure PSL matches aren't available for non-HTML forms.
655 TEST_F(NativeBackendLibsecretTest
, PSLMatchingDisabledForNonHTMLForms
) {
656 CheckMatchingWithScheme(PasswordForm::SCHEME_BASIC
);
657 CheckMatchingWithScheme(PasswordForm::SCHEME_DIGEST
);
658 CheckMatchingWithScheme(PasswordForm::SCHEME_OTHER
);
661 TEST_F(NativeBackendLibsecretTest
, PSLUpdatingStrictUpdateLogin
) {
662 CheckPSLUpdate(UPDATE_BY_UPDATELOGIN
);
665 TEST_F(NativeBackendLibsecretTest
, PSLUpdatingStrictAddLogin
) {
666 // TODO(vabr): if AddLogin becomes no longer valid for existing logins, then
667 // just delete this test.
668 CheckPSLUpdate(UPDATE_BY_ADDLOGIN
);
671 TEST_F(NativeBackendLibsecretTest
, BasicUpdateLogin
) {
672 NativeBackendLibsecret
backend(42);
674 VerifiedAdd(&backend
, form_google_
);
676 PasswordForm
new_form_google(form_google_
);
677 new_form_google
.times_used
= 1;
678 new_form_google
.action
= GURL("http://www.google.com/different/login");
680 EXPECT_EQ(1u, global_mock_libsecret_items
->size());
681 if (!global_mock_libsecret_items
->empty()) {
682 CheckMockSecretItem((*global_mock_libsecret_items
)[0], form_google_
,
687 VerifiedUpdate(&backend
, new_form_google
);
689 EXPECT_EQ(1u, global_mock_libsecret_items
->size());
690 if (!global_mock_libsecret_items
->empty())
691 CheckMockSecretItem((*global_mock_libsecret_items
)[0], new_form_google
,
695 TEST_F(NativeBackendLibsecretTest
, BasicRemoveLogin
) {
696 NativeBackendLibsecret
backend(42);
698 VerifiedAdd(&backend
, form_google_
);
700 EXPECT_EQ(1u, global_mock_libsecret_items
->size());
701 if (!global_mock_libsecret_items
->empty())
702 CheckMockSecretItem((*global_mock_libsecret_items
)[0], form_google_
,
705 VerifiedRemove(&backend
, form_google_
);
707 EXPECT_TRUE(global_mock_libsecret_items
->empty());
710 // Verify fix for http://crbug.com/408783.
711 TEST_F(NativeBackendLibsecretTest
, RemoveLoginActionMismatch
) {
712 NativeBackendLibsecret
backend(42);
714 VerifiedAdd(&backend
, form_google_
);
716 EXPECT_EQ(1u, global_mock_libsecret_items
->size());
717 if (!global_mock_libsecret_items
->empty())
718 CheckMockSecretItem((*global_mock_libsecret_items
)[0], form_google_
,
721 // Action url match not required for removal.
722 form_google_
.action
= GURL("https://some.other.url.com/path");
723 VerifiedRemove(&backend
, form_google_
);
725 EXPECT_TRUE(global_mock_libsecret_items
->empty());
728 TEST_F(NativeBackendLibsecretTest
, RemoveNonexistentLogin
) {
729 NativeBackendLibsecret
backend(42);
731 // First add an unrelated login.
732 VerifiedAdd(&backend
, form_google_
);
734 EXPECT_EQ(1u, global_mock_libsecret_items
->size());
735 if (!global_mock_libsecret_items
->empty())
736 CheckMockSecretItem((*global_mock_libsecret_items
)[0], form_google_
,
739 // Attempt to remove a login that doesn't exist.
740 PasswordStoreChangeList changes
;
741 EXPECT_TRUE(backend
.RemoveLogin(form_isc_
, &changes
));
742 CheckPasswordChanges(PasswordStoreChangeList(), changes
);
744 // Make sure we can still get the first form back.
745 ScopedVector
<autofill::PasswordForm
> form_list
;
746 EXPECT_TRUE(backend
.GetAutofillableLogins(&form_list
));
748 // Quick check that we got something back.
749 EXPECT_EQ(1u, form_list
.size());
752 EXPECT_EQ(1u, global_mock_libsecret_items
->size());
753 if (!global_mock_libsecret_items
->empty())
754 CheckMockSecretItem((*global_mock_libsecret_items
)[0], form_google_
,
758 TEST_F(NativeBackendLibsecretTest
, UpdateNonexistentLogin
) {
759 NativeBackendLibsecret
backend(42);
761 // First add an unrelated login.
762 VerifiedAdd(&backend
, form_google_
);
764 EXPECT_EQ(1u, global_mock_libsecret_items
->size());
765 if (!global_mock_libsecret_items
->empty()) {
766 CheckMockSecretItem((*global_mock_libsecret_items
)[0], form_google_
,
770 // Attempt to update a login that doesn't exist.
771 PasswordStoreChangeList changes
;
772 EXPECT_TRUE(backend
.UpdateLogin(form_isc_
, &changes
));
773 CheckPasswordChanges(PasswordStoreChangeList(), changes
);
775 EXPECT_EQ(1u, global_mock_libsecret_items
->size());
776 if (!global_mock_libsecret_items
->empty())
777 CheckMockSecretItem((*global_mock_libsecret_items
)[0], form_google_
,
781 TEST_F(NativeBackendLibsecretTest
, UpdateSameLogin
) {
782 NativeBackendLibsecret
backend(42);
784 VerifiedAdd(&backend
, form_google_
);
786 EXPECT_EQ(1u, global_mock_libsecret_items
->size());
787 if (!global_mock_libsecret_items
->empty()) {
788 CheckMockSecretItem((*global_mock_libsecret_items
)[0], form_google_
,
792 // Attempt to update the same login without changing anything.
793 PasswordStoreChangeList changes
;
794 EXPECT_TRUE(backend
.UpdateLogin(form_google_
, &changes
));
795 CheckPasswordChanges(PasswordStoreChangeList(), changes
);
797 EXPECT_EQ(1u, global_mock_libsecret_items
->size());
798 if (!global_mock_libsecret_items
->empty()) {
799 CheckMockSecretItem((*global_mock_libsecret_items
)[0], form_google_
,
804 TEST_F(NativeBackendLibsecretTest
, AddDuplicateLogin
) {
805 NativeBackendLibsecret
backend(42);
807 VerifiedAdd(&backend
, form_google_
);
809 PasswordStoreChangeList expected_changes
;
810 expected_changes
.push_back(
811 PasswordStoreChange(PasswordStoreChange::REMOVE
, form_google_
));
812 form_google_
.times_used
++;
813 form_google_
.submit_element
= UTF8ToUTF16("submit2");
814 expected_changes
.push_back(
815 PasswordStoreChange(PasswordStoreChange::ADD
, form_google_
));
817 PasswordStoreChangeList actual_changes
= backend
.AddLogin(form_google_
);
818 CheckPasswordChanges(expected_changes
, actual_changes
);
820 EXPECT_EQ(1u, global_mock_libsecret_items
->size());
821 if (!global_mock_libsecret_items
->empty())
822 CheckMockSecretItem((*global_mock_libsecret_items
)[0], form_google_
,
826 TEST_F(NativeBackendLibsecretTest
, AndroidCredentials
) {
827 NativeBackendLibsecret
backend(42);
830 PasswordForm observed_android_form
;
831 observed_android_form
.scheme
= PasswordForm::SCHEME_HTML
;
832 observed_android_form
.signon_realm
=
833 "android://7x7IDboo8u9YKraUsbmVkuf1-@net.rateflix.app/";
834 PasswordForm saved_android_form
= observed_android_form
;
835 saved_android_form
.username_value
= base::UTF8ToUTF16("randomusername");
836 saved_android_form
.password_value
= base::UTF8ToUTF16("password");
837 saved_android_form
.date_created
= base::Time::Now();
839 VerifiedAdd(&backend
, saved_android_form
);
841 ScopedVector
<autofill::PasswordForm
> form_list
;
842 EXPECT_TRUE(backend
.GetAutofillableLogins(&form_list
));
844 EXPECT_EQ(1u, form_list
.size());
845 EXPECT_EQ(saved_android_form
, *form_list
[0]);
848 TEST_F(NativeBackendLibsecretTest
, RemoveLoginsCreatedBetween
) {
849 CheckRemoveLoginsBetween(CREATED
);
852 TEST_F(NativeBackendLibsecretTest
, RemoveLoginsSyncedBetween
) {
853 CheckRemoveLoginsBetween(SYNCED
);
856 TEST_F(NativeBackendLibsecretTest
, SomeKeyringAttributesAreMissing
) {
857 // Absent attributes should be filled with default values.
858 NativeBackendLibsecret
backend(42);
860 VerifiedAdd(&backend
, form_google_
);
862 EXPECT_EQ(1u, global_mock_libsecret_items
->size());
863 // Remove a string attribute.
864 (*global_mock_libsecret_items
)[0]->RemoveAttribute("avatar_url");
865 // Remove an integer attribute.
866 (*global_mock_libsecret_items
)[0]->RemoveAttribute("ssl_valid");
868 ScopedVector
<autofill::PasswordForm
> form_list
;
869 EXPECT_TRUE(backend
.GetAutofillableLogins(&form_list
));
871 EXPECT_EQ(1u, form_list
.size());
872 EXPECT_EQ(GURL(""), form_list
[0]->icon_url
);
873 EXPECT_FALSE(form_list
[0]->ssl_valid
);
876 TEST_F(NativeBackendLibsecretTest
, ReadDuplicateForms
) {
877 NativeBackendLibsecret
backend(42);
879 // Add 2 slightly different password forms.
880 const char unique_string
[] = "unique_unique_string";
881 const char unique_string_replacement
[] = "uniKue_unique_string";
882 form_google_
.origin
=
883 GURL(std::string("http://www.google.com/") + unique_string
);
884 VerifiedAdd(&backend
, form_google_
);
885 form_google_
.origin
=
886 GURL(std::string("http://www.google.com/") + unique_string_replacement
);
887 VerifiedAdd(&backend
, form_google_
);
889 // Read the raw value back. Change the |unique_string| to
890 // |unique_string_replacement| so the forms become unique.
891 ASSERT_EQ(2u, global_mock_libsecret_items
->size());
892 gpointer item_value
= g_hash_table_lookup(
893 global_mock_libsecret_items
->front()->attributes
, "origin_url");
894 ASSERT_TRUE(item_value
);
895 char* substr
= strstr(static_cast<char*>(item_value
), unique_string
);
897 ASSERT_EQ(strlen(unique_string
), strlen(unique_string_replacement
));
898 strncpy(substr
, unique_string_replacement
, strlen(unique_string
));
900 // Now test that GetAutofillableLogins returns only one form.
901 ScopedVector
<autofill::PasswordForm
> form_list
;
902 EXPECT_TRUE(backend
.GetAutofillableLogins(&form_list
));
904 EXPECT_EQ(1u, form_list
.size());
905 EXPECT_EQ(form_google_
, *form_list
[0]);
907 EXPECT_EQ(1u, global_mock_libsecret_items
->size());
908 if (!global_mock_libsecret_items
->empty()) {
909 CheckMockSecretItem((*global_mock_libsecret_items
)[0], form_google_
,
914 // TODO(mdm): add more basic tests here at some point.