1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
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/strings/string_number_conversions.h"
12 #include "base/strings/string_util.h"
13 #include "base/strings/stringprintf.h"
14 #include "base/strings/utf_string_conversions.h"
15 #include "base/thread_task_runner_handle.h"
16 #include "base/time/time.h"
17 #include "chrome/browser/password_manager/native_backend_gnome_x.h"
18 #include "chrome/test/base/testing_profile.h"
19 #include "components/autofill/core/common/password_form.h"
20 #include "components/password_manager/core/browser/psl_matching_helper.h"
21 #include "components/password_manager/core/common/password_manager_pref_names.h"
22 #include "content/public/test/test_browser_thread.h"
23 #include "testing/gtest/include/gtest/gtest.h"
25 using autofill::PasswordForm
;
26 using base::UTF8ToUTF16
;
27 using base::UTF16ToUTF8
;
28 using content::BrowserThread
;
29 using password_manager::PasswordStoreChange
;
30 using password_manager::PasswordStoreChangeList
;
34 // What follows is a very simple implementation of the subset of the GNOME
35 // Keyring API that we actually use. It gets substituted for the real one by
36 // MockGnomeKeyringLoader, which hooks into the facility normally used to load
37 // the GNOME Keyring library at runtime to avoid a static dependency on it.
39 struct MockKeyringItem
{
41 MockKeyringItem(const char* keyring
,
42 const std::string
& display_name
,
43 const std::string
& password
)
44 : keyring(keyring
? keyring
: "login"),
45 display_name(display_name
),
48 struct ItemAttribute
{
49 ItemAttribute() : type(UINT32
), value_uint32(0) {}
50 explicit ItemAttribute(uint32_t value
)
51 : type(UINT32
), value_uint32(value
) {}
52 explicit ItemAttribute(const std::string
& value
)
53 : type(STRING
), value_string(value
) {}
55 bool Equals(const ItemAttribute
& x
) const {
56 if (type
!= x
.type
) return false;
57 return (type
== STRING
) ? value_string
== x
.value_string
58 : value_uint32
== x
.value_uint32
;
61 enum Type
{ UINT32
, STRING
} type
;
62 uint32_t value_uint32
;
63 std::string value_string
;
66 typedef std::map
<std::string
, ItemAttribute
> attribute_map
;
67 typedef std::vector
<std::pair
<std::string
, ItemAttribute
> > attribute_query
;
69 bool Matches(const attribute_query
& query
) const {
70 // The real GNOME Keyring doesn't match empty queries.
71 if (query
.empty()) return false;
72 for (size_t i
= 0; i
< query
.size(); ++i
) {
73 attribute_map::const_iterator match
= attributes
.find(query
[i
].first
);
74 if (match
== attributes
.end()) return false;
75 if (!match
->second
.Equals(query
[i
].second
)) return false;
81 std::string display_name
;
84 attribute_map attributes
;
87 // The list of all keyring items we have stored.
88 std::vector
<MockKeyringItem
> mock_keyring_items
;
89 bool mock_keyring_reject_local_ids
= false;
91 bool IsStringAttribute(const GnomeKeyringPasswordSchema
* schema
,
92 const std::string
& name
) {
93 for (size_t i
= 0; schema
->attributes
[i
].name
; ++i
)
94 if (name
== schema
->attributes
[i
].name
)
95 return schema
->attributes
[i
].type
== GNOME_KEYRING_ATTRIBUTE_TYPE_STRING
;
96 NOTREACHED() << "Requested type of nonexistent attribute";
100 gboolean
mock_gnome_keyring_is_available() {
104 gpointer
mock_gnome_keyring_store_password(
105 const GnomeKeyringPasswordSchema
* schema
,
106 const gchar
* keyring
,
107 const gchar
* display_name
,
108 const gchar
* password
,
109 GnomeKeyringOperationDoneCallback callback
,
111 GDestroyNotify destroy_data
,
113 mock_keyring_items
.push_back(
114 MockKeyringItem(keyring
, display_name
, password
));
115 MockKeyringItem
* item
= &mock_keyring_items
.back();
116 const std::string keyring_desc
=
117 keyring
? base::StringPrintf("keyring %s", keyring
)
118 : std::string("default keyring");
119 VLOG(1) << "Adding item with origin " << display_name
120 << " to " << keyring_desc
;
122 va_start(ap
, destroy_data
);
124 while ((name
= va_arg(ap
, gchar
*))) {
125 if (IsStringAttribute(schema
, name
)) {
126 item
->attributes
[name
] =
127 MockKeyringItem::ItemAttribute(va_arg(ap
, gchar
*));
128 VLOG(1) << "Adding item attribute " << name
129 << ", value '" << item
->attributes
[name
].value_string
<< "'";
131 item
->attributes
[name
] =
132 MockKeyringItem::ItemAttribute(va_arg(ap
, uint32_t));
133 VLOG(1) << "Adding item attribute " << name
134 << ", value " << item
->attributes
[name
].value_uint32
;
138 // As a hack to ease testing migration, make it possible to reject the new
139 // format for the app string. This way we can add them easily to migrate.
140 if (mock_keyring_reject_local_ids
) {
141 MockKeyringItem::attribute_map::iterator it
=
142 item
->attributes
.find("application");
143 if (it
!= item
->attributes
.end() &&
144 it
->second
.type
== MockKeyringItem::ItemAttribute::STRING
&&
145 base::StringPiece(it
->second
.value_string
).starts_with("chrome-")) {
146 mock_keyring_items
.pop_back();
147 // GnomeKeyringResult, data
148 callback(GNOME_KEYRING_RESULT_IO_ERROR
, data
);
152 // GnomeKeyringResult, data
153 callback(GNOME_KEYRING_RESULT_OK
, data
);
157 gpointer
mock_gnome_keyring_delete_password(
158 const GnomeKeyringPasswordSchema
* schema
,
159 GnomeKeyringOperationDoneCallback callback
,
161 GDestroyNotify destroy_data
,
163 MockKeyringItem::attribute_query query
;
165 va_start(ap
, destroy_data
);
167 while ((name
= va_arg(ap
, gchar
*))) {
168 if (IsStringAttribute(schema
, name
)) {
169 query
.push_back(make_pair(std::string(name
),
170 MockKeyringItem::ItemAttribute(va_arg(ap
, gchar
*))));
171 VLOG(1) << "Querying with item attribute " << name
172 << ", value '" << query
.back().second
.value_string
<< "'";
174 query
.push_back(make_pair(std::string(name
),
175 MockKeyringItem::ItemAttribute(va_arg(ap
, uint32_t))));
176 VLOG(1) << "Querying with item attribute " << name
177 << ", value " << query
.back().second
.value_uint32
;
181 bool deleted
= false;
182 for (size_t i
= mock_keyring_items
.size(); i
> 0; --i
) {
183 const MockKeyringItem
* item
= &mock_keyring_items
[i
- 1];
184 if (item
->Matches(query
)) {
185 VLOG(1) << "Deleting item with origin " << item
->display_name
;
186 mock_keyring_items
.erase(mock_keyring_items
.begin() + (i
- 1));
190 // GnomeKeyringResult, data
191 callback(deleted
? GNOME_KEYRING_RESULT_OK
192 : GNOME_KEYRING_RESULT_NO_MATCH
, data
);
196 gpointer
mock_gnome_keyring_find_items(
197 GnomeKeyringItemType type
,
198 GnomeKeyringAttributeList
* attributes
,
199 GnomeKeyringOperationGetListCallback callback
,
201 GDestroyNotify destroy_data
) {
202 MockKeyringItem::attribute_query query
;
203 for (size_t i
= 0; i
< attributes
->len
; ++i
) {
204 GnomeKeyringAttribute attribute
=
205 g_array_index(attributes
, GnomeKeyringAttribute
, i
);
206 if (attribute
.type
== GNOME_KEYRING_ATTRIBUTE_TYPE_STRING
) {
208 make_pair(std::string(attribute
.name
),
209 MockKeyringItem::ItemAttribute(attribute
.value
.string
)));
210 VLOG(1) << "Querying with item attribute " << attribute
.name
211 << ", value '" << query
.back().second
.value_string
<< "'";
214 make_pair(std::string(attribute
.name
),
215 MockKeyringItem::ItemAttribute(attribute
.value
.integer
)));
216 VLOG(1) << "Querying with item attribute " << attribute
.name
<< ", value "
217 << query
.back().second
.value_uint32
;
220 // Find matches and add them to a list of results.
221 GList
* results
= nullptr;
222 for (size_t i
= 0; i
< mock_keyring_items
.size(); ++i
) {
223 const MockKeyringItem
* item
= &mock_keyring_items
[i
];
224 if (item
->Matches(query
)) {
225 GnomeKeyringFound
* found
= new GnomeKeyringFound
;
226 found
->keyring
= strdup(item
->keyring
.c_str());
228 found
->attributes
= gnome_keyring_attribute_list_new();
229 for (MockKeyringItem::attribute_map::const_iterator it
=
230 item
->attributes
.begin();
231 it
!= item
->attributes
.end();
233 if (it
->second
.type
== MockKeyringItem::ItemAttribute::STRING
) {
234 gnome_keyring_attribute_list_append_string(
235 found
->attributes
, it
->first
.c_str(),
236 it
->second
.value_string
.c_str());
238 gnome_keyring_attribute_list_append_uint32(
239 found
->attributes
, it
->first
.c_str(),
240 it
->second
.value_uint32
);
243 found
->secret
= strdup(item
->password
.c_str());
244 results
= g_list_prepend(results
, found
);
247 // GnomeKeyringResult, GList*, data
248 callback(results
? GNOME_KEYRING_RESULT_OK
249 : GNOME_KEYRING_RESULT_NO_MATCH
, results
, data
);
250 // Now free the list of results.
251 GList
* element
= g_list_first(results
);
253 GnomeKeyringFound
* found
= static_cast<GnomeKeyringFound
*>(element
->data
);
254 free(found
->keyring
);
255 gnome_keyring_attribute_list_free(found
->attributes
);
258 element
= g_list_next(element
);
260 g_list_free(results
);
264 const gchar
* mock_gnome_keyring_result_to_message(GnomeKeyringResult res
) {
265 return "mock keyring simulating failure";
268 // Inherit to get access to protected fields.
269 class MockGnomeKeyringLoader
: public GnomeKeyringLoader
{
271 static bool LoadMockGnomeKeyring() {
272 if (!LoadGnomeKeyring())
274 #define GNOME_KEYRING_ASSIGN_POINTER(name) \
275 gnome_keyring_##name = &mock_gnome_keyring_##name;
276 GNOME_KEYRING_FOR_EACH_MOCKED_FUNC(GNOME_KEYRING_ASSIGN_POINTER
)
277 #undef GNOME_KEYRING_ASSIGN_POINTER
278 keyring_loaded
= true;
279 // Reset the state of the mock library.
280 mock_keyring_items
.clear();
281 mock_keyring_reject_local_ids
= false;
286 void CheckPasswordChanges(const PasswordStoreChangeList
& expected_list
,
287 const PasswordStoreChangeList
& actual_list
) {
288 ASSERT_EQ(expected_list
.size(), actual_list
.size());
289 for (size_t i
= 0; i
< expected_list
.size(); ++i
) {
290 EXPECT_EQ(expected_list
[i
].type(), actual_list
[i
].type());
291 const PasswordForm
& expected
= expected_list
[i
].form();
292 const PasswordForm
& actual
= actual_list
[i
].form();
294 EXPECT_EQ(expected
.origin
, actual
.origin
);
295 EXPECT_EQ(expected
.password_value
, actual
.password_value
);
296 EXPECT_EQ(expected
.action
, actual
.action
);
297 EXPECT_EQ(expected
.username_element
, actual
.username_element
);
298 EXPECT_EQ(expected
.username_value
, actual
.username_value
);
299 EXPECT_EQ(expected
.password_element
, actual
.password_element
);
300 EXPECT_EQ(expected
.submit_element
, actual
.submit_element
);
301 EXPECT_EQ(expected
.signon_realm
, actual
.signon_realm
);
302 EXPECT_EQ(expected
.ssl_valid
, actual
.ssl_valid
);
303 EXPECT_EQ(expected
.preferred
, actual
.preferred
);
304 EXPECT_EQ(expected
.date_created
, actual
.date_created
);
305 EXPECT_EQ(expected
.blacklisted_by_user
, actual
.blacklisted_by_user
);
306 EXPECT_EQ(expected
.type
, actual
.type
);
307 EXPECT_EQ(expected
.times_used
, actual
.times_used
);
308 EXPECT_EQ(expected
.scheme
, actual
.scheme
);
309 EXPECT_EQ(expected
.date_synced
, actual
.date_synced
);
310 EXPECT_EQ(expected
.display_name
, actual
.display_name
);
311 EXPECT_EQ(expected
.icon_url
, actual
.icon_url
);
312 EXPECT_EQ(expected
.federation_url
, actual
.federation_url
);
313 EXPECT_EQ(expected
.skip_zero_click
, actual
.skip_zero_click
);
314 EXPECT_EQ(expected
.generation_upload_status
,
315 actual
.generation_upload_status
);
319 void CheckPasswordChangesWithResult(const PasswordStoreChangeList
* expected
,
320 const PasswordStoreChangeList
* actual
,
323 CheckPasswordChanges(*expected
, *actual
);
326 } // anonymous namespace
328 class NativeBackendGnomeTest
: public testing::Test
{
330 enum UpdateType
{ // Used in CheckPSLUpdate().
331 UPDATE_BY_UPDATELOGIN
,
334 enum RemoveBetweenMethod
{ // Used in CheckRemoveLoginsBetween().
339 NativeBackendGnomeTest()
340 : ui_thread_(BrowserThread::UI
, &message_loop_
),
341 db_thread_(BrowserThread::DB
) {
344 void SetUp() override
{
345 ASSERT_TRUE(db_thread_
.Start());
347 ASSERT_TRUE(MockGnomeKeyringLoader::LoadMockGnomeKeyring());
349 form_google_
.origin
= GURL("http://www.google.com/");
350 form_google_
.action
= GURL("http://www.google.com/login");
351 form_google_
.username_element
= UTF8ToUTF16("user");
352 form_google_
.username_value
= UTF8ToUTF16("joeschmoe");
353 form_google_
.password_element
= UTF8ToUTF16("pass");
354 form_google_
.password_value
= UTF8ToUTF16("seekrit");
355 form_google_
.submit_element
= UTF8ToUTF16("submit");
356 form_google_
.signon_realm
= "http://www.google.com/";
357 form_google_
.type
= PasswordForm::TYPE_GENERATED
;
358 form_google_
.date_created
= base::Time::Now();
359 form_google_
.date_synced
= base::Time::Now();
360 form_google_
.display_name
= UTF8ToUTF16("Joe Schmoe");
361 form_google_
.icon_url
= GURL("http://www.google.com/icon");
362 form_google_
.federation_url
= GURL("http://www.google.com/federation_url");
363 form_google_
.skip_zero_click
= true;
364 form_google_
.generation_upload_status
= PasswordForm::POSITIVE_SIGNAL_SENT
;
365 form_google_
.form_data
.name
= UTF8ToUTF16("form_name");
367 form_facebook_
.origin
= GURL("http://www.facebook.com/");
368 form_facebook_
.action
= GURL("http://www.facebook.com/login");
369 form_facebook_
.username_element
= UTF8ToUTF16("user");
370 form_facebook_
.username_value
= UTF8ToUTF16("a");
371 form_facebook_
.password_element
= UTF8ToUTF16("password");
372 form_facebook_
.password_value
= UTF8ToUTF16("b");
373 form_facebook_
.submit_element
= UTF8ToUTF16("submit");
374 form_facebook_
.signon_realm
= "http://www.facebook.com/";
375 form_facebook_
.date_created
= base::Time::Now();
376 form_facebook_
.date_synced
= base::Time::Now();
377 form_facebook_
.display_name
= UTF8ToUTF16("Joe Schmoe");
378 form_facebook_
.icon_url
= GURL("http://www.facebook.com/icon");
379 form_facebook_
.federation_url
= GURL("http://www.facebook.com/federation");
380 form_facebook_
.skip_zero_click
= true;
381 form_facebook_
.generation_upload_status
= PasswordForm::NO_SIGNAL_SENT
;
383 form_isc_
.origin
= GURL("http://www.isc.org/");
384 form_isc_
.action
= GURL("http://www.isc.org/auth");
385 form_isc_
.username_element
= UTF8ToUTF16("id");
386 form_isc_
.username_value
= UTF8ToUTF16("janedoe");
387 form_isc_
.password_element
= UTF8ToUTF16("passwd");
388 form_isc_
.password_value
= UTF8ToUTF16("ihazabukkit");
389 form_isc_
.submit_element
= UTF8ToUTF16("login");
390 form_isc_
.signon_realm
= "http://www.isc.org/";
391 form_isc_
.date_created
= base::Time::Now();
392 form_isc_
.date_synced
= base::Time::Now();
394 other_auth_
.origin
= GURL("http://www.example.com/");
395 other_auth_
.username_value
= UTF8ToUTF16("username");
396 other_auth_
.password_value
= UTF8ToUTF16("pass");
397 other_auth_
.signon_realm
= "http://www.example.com/Realm";
398 other_auth_
.date_created
= base::Time::Now();
399 other_auth_
.date_synced
= base::Time::Now();
402 void TearDown() override
{
403 base::ThreadTaskRunnerHandle::Get()->PostTask(
404 FROM_HERE
, base::MessageLoop::QuitClosure());
405 base::MessageLoop::current()->Run();
409 void RunBothThreads() {
410 // First we post a message to the DB thread that will run after all other
411 // messages that have been posted to the DB thread (we don't expect more
412 // to be posted), which posts a message to the UI thread to quit the loop.
413 // That way we can run both loops and be sure that the UI thread loop will
414 // quit so we can get on with the rest of the test.
415 BrowserThread::PostTask(BrowserThread::DB
, FROM_HERE
,
416 base::Bind(&PostQuitTask
, &message_loop_
));
417 base::MessageLoop::current()->Run();
420 static void PostQuitTask(base::MessageLoop
* loop
) {
421 loop
->task_runner()->PostTask(FROM_HERE
, base::MessageLoop::QuitClosure());
424 void CheckUint32Attribute(const MockKeyringItem
* item
,
425 const std::string
& attribute
,
427 MockKeyringItem::attribute_map::const_iterator it
=
428 item
->attributes
.find(attribute
);
429 EXPECT_NE(item
->attributes
.end(), it
);
430 if (it
!= item
->attributes
.end()) {
431 EXPECT_EQ(MockKeyringItem::ItemAttribute::UINT32
, it
->second
.type
);
432 EXPECT_EQ(value
, it
->second
.value_uint32
);
436 void CheckStringAttribute(const MockKeyringItem
* item
,
437 const std::string
& attribute
,
438 const std::string
& value
) {
439 MockKeyringItem::attribute_map::const_iterator it
=
440 item
->attributes
.find(attribute
);
441 EXPECT_NE(item
->attributes
.end(), it
);
442 if (it
!= item
->attributes
.end()) {
443 EXPECT_EQ(MockKeyringItem::ItemAttribute::STRING
, it
->second
.type
);
444 EXPECT_EQ(value
, it
->second
.value_string
);
448 void CheckMockKeyringItem(const MockKeyringItem
* item
,
449 const PasswordForm
& form
,
450 const std::string
& app_string
) {
451 // We always add items to the login keyring.
452 EXPECT_EQ("login", item
->keyring
);
453 EXPECT_EQ(form
.origin
.spec(), item
->display_name
);
454 EXPECT_EQ(UTF16ToUTF8(form
.password_value
), item
->password
);
455 EXPECT_EQ(22u, item
->attributes
.size());
456 CheckStringAttribute(item
, "origin_url", form
.origin
.spec());
457 CheckStringAttribute(item
, "action_url", form
.action
.spec());
458 CheckStringAttribute(item
, "username_element",
459 UTF16ToUTF8(form
.username_element
));
460 CheckStringAttribute(item
, "username_value",
461 UTF16ToUTF8(form
.username_value
));
462 CheckStringAttribute(item
, "password_element",
463 UTF16ToUTF8(form
.password_element
));
464 CheckStringAttribute(item
, "submit_element",
465 UTF16ToUTF8(form
.submit_element
));
466 CheckStringAttribute(item
, "signon_realm", form
.signon_realm
);
467 CheckUint32Attribute(item
, "ssl_valid", form
.ssl_valid
);
468 CheckUint32Attribute(item
, "preferred", form
.preferred
);
469 // We don't check the date created. It varies.
470 CheckUint32Attribute(item
, "blacklisted_by_user", form
.blacklisted_by_user
);
471 CheckUint32Attribute(item
, "type", form
.type
);
472 CheckUint32Attribute(item
, "times_used", form
.times_used
);
473 CheckUint32Attribute(item
, "scheme", form
.scheme
);
474 CheckStringAttribute(item
, "date_synced", base::Int64ToString(
475 form
.date_synced
.ToInternalValue()));
476 CheckStringAttribute(item
, "display_name", UTF16ToUTF8(form
.display_name
));
477 CheckStringAttribute(item
, "avatar_url", form
.icon_url
.spec());
478 CheckStringAttribute(item
, "federation_url", form
.federation_url
.spec());
479 CheckUint32Attribute(item
, "skip_zero_click", form
.skip_zero_click
);
480 CheckUint32Attribute(item
, "generation_upload_status",
481 form
.generation_upload_status
);
482 CheckStringAttribute(item
, "application", app_string
);
483 autofill::FormData actual
;
484 DeserializeFormDataFromBase64String(
485 item
->attributes
.at("form_data").value_string
, &actual
);
486 EXPECT_TRUE(form
.form_data
.SameFormAs(actual
));
489 // Saves |credentials| and then gets logins matching |url| and |scheme|.
490 // Returns true when something is found, and in such case copies the result to
491 // |result| when |result| is not NULL. (Note that there can be max. 1 result,
492 // derived from |credentials|.)
493 bool CheckCredentialAvailability(const PasswordForm
& credentials
,
495 const PasswordForm::Scheme
& scheme
,
496 PasswordForm
* result
) {
497 NativeBackendGnome
backend(321);
500 BrowserThread::PostTask(
503 base::Bind(base::IgnoreResult(&NativeBackendGnome::AddLogin
),
504 base::Unretained(&backend
),
507 PasswordForm target_form
;
508 target_form
.origin
= url
;
509 target_form
.signon_realm
= url
.spec();
510 if (scheme
!= PasswordForm::SCHEME_HTML
) {
511 // For non-HTML forms, the realm used for authentication
512 // (http://tools.ietf.org/html/rfc1945#section-10.2) is appended to the
513 // signon_realm. Just use a default value for now.
514 target_form
.signon_realm
.append("Realm");
515 target_form
.scheme
= scheme
;
517 ScopedVector
<autofill::PasswordForm
> form_list
;
518 BrowserThread::PostTask(
521 base::Bind(base::IgnoreResult(&NativeBackendGnome::GetLogins
),
522 base::Unretained(&backend
),
528 EXPECT_EQ(1u, mock_keyring_items
.size());
529 if (mock_keyring_items
.size() > 0)
530 CheckMockKeyringItem(&mock_keyring_items
[0], credentials
, "chrome-321");
531 mock_keyring_items
.clear();
533 if (form_list
.empty())
535 EXPECT_EQ(1u, form_list
.size());
537 *result
= *form_list
[0];
541 // Test that updating does not use PSL matching: Add a www.facebook.com
542 // password, then use PSL matching to get a copy of it for m.facebook.com, and
543 // add that copy as well. Now update the www.facebook.com password -- the
544 // m.facebook.com password should not get updated. Depending on the argument,
545 // the credential update is done via UpdateLogin or AddLogin.
546 void CheckPSLUpdate(UpdateType update_type
) {
547 NativeBackendGnome
backend(321);
550 // Add |form_facebook_| to saved logins.
551 BrowserThread::PostTask(
554 base::Bind(base::IgnoreResult(&NativeBackendGnome::AddLogin
),
555 base::Unretained(&backend
),
558 // Get the PSL-matched copy of the saved login for m.facebook.
559 const GURL
kMobileURL("http://m.facebook.com/");
560 PasswordForm m_facebook_lookup
;
561 m_facebook_lookup
.origin
= kMobileURL
;
562 m_facebook_lookup
.signon_realm
= kMobileURL
.spec();
563 ScopedVector
<autofill::PasswordForm
> form_list
;
564 BrowserThread::PostTask(
567 base::Bind(base::IgnoreResult(&NativeBackendGnome::GetLogins
),
568 base::Unretained(&backend
),
572 EXPECT_EQ(1u, mock_keyring_items
.size());
573 EXPECT_EQ(1u, form_list
.size());
574 PasswordForm m_facebook
= *form_list
[0];
576 EXPECT_EQ(kMobileURL
, m_facebook
.origin
);
577 EXPECT_EQ(kMobileURL
.spec(), m_facebook
.signon_realm
);
579 // Add the PSL-matched copy to saved logins.
580 BrowserThread::PostTask(
583 base::Bind(base::IgnoreResult(&NativeBackendGnome::AddLogin
),
584 base::Unretained(&backend
),
587 EXPECT_EQ(2u, mock_keyring_items
.size());
589 // Update www.facebook.com login.
590 PasswordForm
new_facebook(form_facebook_
);
591 const base::string16
kOldPassword(form_facebook_
.password_value
);
592 const base::string16
kNewPassword(UTF8ToUTF16("new_b"));
593 EXPECT_NE(kOldPassword
, kNewPassword
);
594 new_facebook
.password_value
= kNewPassword
;
595 switch (update_type
) {
596 case UPDATE_BY_UPDATELOGIN
:
597 BrowserThread::PostTask(
600 base::Bind(base::IgnoreResult(&NativeBackendGnome::UpdateLogin
),
601 base::Unretained(&backend
),
603 base::Owned(new PasswordStoreChangeList
)));
605 case UPDATE_BY_ADDLOGIN
:
606 BrowserThread::PostTask(
609 base::Bind(base::IgnoreResult(&NativeBackendGnome::AddLogin
),
610 base::Unretained(&backend
),
616 EXPECT_EQ(2u, mock_keyring_items
.size());
618 // Check that m.facebook.com login was not modified by the update.
619 BrowserThread::PostTask(
622 base::Bind(base::IgnoreResult(&NativeBackendGnome::GetLogins
),
623 base::Unretained(&backend
),
627 // There should be two results -- the exact one, and the PSL-matched one.
628 EXPECT_EQ(2u, form_list
.size());
629 size_t index_non_psl
= 0;
630 if (!form_list
[index_non_psl
]->original_signon_realm
.empty())
632 EXPECT_EQ(kMobileURL
, form_list
[index_non_psl
]->origin
);
633 EXPECT_EQ(kMobileURL
.spec(), form_list
[index_non_psl
]->signon_realm
);
634 EXPECT_EQ(kOldPassword
, form_list
[index_non_psl
]->password_value
);
637 // Check that www.facebook.com login was modified by the update.
638 BrowserThread::PostTask(
641 base::Bind(base::IgnoreResult(&NativeBackendGnome::GetLogins
),
642 base::Unretained(&backend
),
646 // There should be two results -- the exact one, and the PSL-matched one.
647 EXPECT_EQ(2u, form_list
.size());
649 if (!form_list
[index_non_psl
]->original_signon_realm
.empty())
651 EXPECT_EQ(form_facebook_
.origin
, form_list
[index_non_psl
]->origin
);
652 EXPECT_EQ(form_facebook_
.signon_realm
,
653 form_list
[index_non_psl
]->signon_realm
);
654 EXPECT_EQ(kNewPassword
, form_list
[index_non_psl
]->password_value
);
657 void CheckMatchingWithScheme(const PasswordForm::Scheme
& scheme
) {
658 other_auth_
.scheme
= scheme
;
660 // Don't match a non-HTML form with an HTML form.
661 EXPECT_FALSE(CheckCredentialAvailability(
662 other_auth_
, GURL("http://www.example.com"),
663 PasswordForm::SCHEME_HTML
, nullptr));
664 // Don't match an HTML form with non-HTML auth form.
665 EXPECT_FALSE(CheckCredentialAvailability(
666 form_google_
, GURL("http://www.google.com/"), scheme
, nullptr));
667 // Don't match two different non-HTML auth forms with different origin.
668 EXPECT_FALSE(CheckCredentialAvailability(
669 other_auth_
, GURL("http://first.example.com"), scheme
, nullptr));
670 // Do match non-HTML forms from the same origin.
671 EXPECT_TRUE(CheckCredentialAvailability(
672 other_auth_
, GURL("http://www.example.com/"), scheme
, nullptr));
675 void CheckRemoveLoginsBetween(RemoveBetweenMethod date_to_test
) {
676 NativeBackendGnome
backend(42);
679 base::Time now
= base::Time::Now();
680 base::Time next_day
= now
+ base::TimeDelta::FromDays(1);
681 form_google_
.date_synced
= base::Time();
682 form_isc_
.date_synced
= base::Time();
683 form_google_
.date_created
= now
;
684 form_isc_
.date_created
= now
;
685 if (date_to_test
== CREATED
) {
686 form_google_
.date_created
= now
;
687 form_isc_
.date_created
= next_day
;
689 form_google_
.date_synced
= now
;
690 form_isc_
.date_synced
= next_day
;
693 BrowserThread::PostTask(
696 base::Bind(base::IgnoreResult(&NativeBackendGnome::AddLogin
),
697 base::Unretained(&backend
),
699 BrowserThread::PostTask(
702 base::Bind(base::IgnoreResult(&NativeBackendGnome::AddLogin
),
703 base::Unretained(&backend
),
706 PasswordStoreChangeList expected_changes
;
707 expected_changes
.push_back(
708 PasswordStoreChange(PasswordStoreChange::REMOVE
, form_google_
));
709 PasswordStoreChangeList changes
;
710 bool (NativeBackendGnome::*method
)(
711 base::Time
, base::Time
, password_manager::PasswordStoreChangeList
*) =
712 date_to_test
== CREATED
713 ? &NativeBackendGnome::RemoveLoginsCreatedBetween
714 : &NativeBackendGnome::RemoveLoginsSyncedBetween
;
715 BrowserThread::PostTaskAndReplyWithResult(
719 base::Unretained(&backend
),
724 &CheckPasswordChangesWithResult
, &expected_changes
, &changes
));
727 EXPECT_EQ(1u, mock_keyring_items
.size());
728 if (mock_keyring_items
.size() > 0)
729 CheckMockKeyringItem(&mock_keyring_items
[0], form_isc_
, "chrome-42");
732 expected_changes
.clear();
733 expected_changes
.push_back(
734 PasswordStoreChange(PasswordStoreChange::REMOVE
, form_isc_
));
735 BrowserThread::PostTaskAndReplyWithResult(
739 base::Unretained(&backend
),
744 &CheckPasswordChangesWithResult
, &expected_changes
, &changes
));
747 EXPECT_EQ(0u, mock_keyring_items
.size());
750 base::MessageLoopForUI message_loop_
;
751 content::TestBrowserThread ui_thread_
;
752 content::TestBrowserThread db_thread_
;
754 // Provide some test forms to avoid having to set them up in each test.
755 PasswordForm form_google_
;
756 PasswordForm form_facebook_
;
757 PasswordForm form_isc_
;
758 PasswordForm other_auth_
;
761 TEST_F(NativeBackendGnomeTest
, BasicAddLogin
) {
762 NativeBackendGnome
backend(42);
765 BrowserThread::PostTask(
766 BrowserThread::DB
, FROM_HERE
,
767 base::Bind(base::IgnoreResult(&NativeBackendGnome::AddLogin
),
768 base::Unretained(&backend
), form_google_
));
772 EXPECT_EQ(1u, mock_keyring_items
.size());
773 if (mock_keyring_items
.size() > 0)
774 CheckMockKeyringItem(&mock_keyring_items
[0], form_google_
, "chrome-42");
777 TEST_F(NativeBackendGnomeTest
, BasicListLogins
) {
778 NativeBackendGnome
backend(42);
781 BrowserThread::PostTask(
782 BrowserThread::DB
, FROM_HERE
,
783 base::Bind(base::IgnoreResult(&NativeBackendGnome::AddLogin
),
784 base::Unretained(&backend
), form_google_
));
786 ScopedVector
<autofill::PasswordForm
> form_list
;
787 BrowserThread::PostTask(
788 BrowserThread::DB
, FROM_HERE
,
790 base::IgnoreResult(&NativeBackendGnome::GetAutofillableLogins
),
791 base::Unretained(&backend
), &form_list
));
795 // Quick check that we got something back.
796 EXPECT_EQ(1u, form_list
.size());
798 EXPECT_EQ(1u, mock_keyring_items
.size());
799 if (mock_keyring_items
.size() > 0)
800 CheckMockKeyringItem(&mock_keyring_items
[0], form_google_
, "chrome-42");
803 // Save a password for www.facebook.com and see it suggested for m.facebook.com.
804 TEST_F(NativeBackendGnomeTest
, PSLMatchingPositive
) {
806 const GURL
kMobileURL("http://m.facebook.com/");
807 EXPECT_TRUE(CheckCredentialAvailability(
808 form_facebook_
, kMobileURL
, PasswordForm::SCHEME_HTML
, &result
));
809 EXPECT_EQ(kMobileURL
, result
.origin
);
810 EXPECT_EQ(kMobileURL
.spec(), result
.signon_realm
);
813 // Save a password for www.facebook.com and see it not suggested for
815 TEST_F(NativeBackendGnomeTest
, PSLMatchingNegativeDomainMismatch
) {
816 EXPECT_FALSE(CheckCredentialAvailability(
817 form_facebook_
, GURL("http://m-facebook.com/"),
818 PasswordForm::SCHEME_HTML
, nullptr));
821 // Test PSL matching is off for domains excluded from it.
822 TEST_F(NativeBackendGnomeTest
, PSLMatchingDisabledDomains
) {
823 EXPECT_FALSE(CheckCredentialAvailability(
824 form_google_
, GURL("http://one.google.com/"),
825 PasswordForm::SCHEME_HTML
, nullptr));
828 // Make sure PSL matches aren't available for non-HTML forms.
829 TEST_F(NativeBackendGnomeTest
, PSLMatchingDisabledForNonHTMLForms
) {
830 CheckMatchingWithScheme(PasswordForm::SCHEME_BASIC
);
831 CheckMatchingWithScheme(PasswordForm::SCHEME_DIGEST
);
832 CheckMatchingWithScheme(PasswordForm::SCHEME_OTHER
);
836 TEST_F(NativeBackendGnomeTest
, PSLUpdatingStrictUpdateLogin
) {
837 CheckPSLUpdate(UPDATE_BY_UPDATELOGIN
);
840 TEST_F(NativeBackendGnomeTest
, PSLUpdatingStrictAddLogin
) {
841 // TODO(vabr): if AddLogin becomes no longer valid for existing logins, then
842 // just delete this test.
843 CheckPSLUpdate(UPDATE_BY_ADDLOGIN
);
846 TEST_F(NativeBackendGnomeTest
, BasicUpdateLogin
) {
847 NativeBackendGnome
backend(42);
850 // First add google login.
851 BrowserThread::PostTask(
852 BrowserThread::DB
, FROM_HERE
,
853 base::Bind(base::IgnoreResult(&NativeBackendGnome::AddLogin
),
854 base::Unretained(&backend
), form_google_
));
858 PasswordForm
new_form_google(form_google_
);
859 new_form_google
.times_used
= 1;
860 new_form_google
.action
= GURL("http://www.google.com/different/login");
862 EXPECT_EQ(1u, mock_keyring_items
.size());
863 if (mock_keyring_items
.size() > 0)
864 CheckMockKeyringItem(&mock_keyring_items
[0], form_google_
, "chrome-42");
867 PasswordStoreChangeList changes
;
868 BrowserThread::PostTask(
869 BrowserThread::DB
, FROM_HERE
,
870 base::Bind(base::IgnoreResult(&NativeBackendGnome::UpdateLogin
),
871 base::Unretained(&backend
),
873 base::Unretained(&changes
)));
877 ASSERT_EQ(1u, changes
.size());
878 EXPECT_EQ(PasswordStoreChange::UPDATE
, changes
.front().type());
879 EXPECT_EQ(new_form_google
, changes
.front().form());
880 EXPECT_EQ(1u, mock_keyring_items
.size());
881 if (mock_keyring_items
.size() > 0)
882 CheckMockKeyringItem(&mock_keyring_items
[0], new_form_google
, "chrome-42");
885 TEST_F(NativeBackendGnomeTest
, BasicRemoveLogin
) {
886 NativeBackendGnome
backend(42);
889 BrowserThread::PostTask(
890 BrowserThread::DB
, FROM_HERE
,
891 base::Bind(base::IgnoreResult(&NativeBackendGnome::AddLogin
),
892 base::Unretained(&backend
), form_google_
));
896 EXPECT_EQ(1u, mock_keyring_items
.size());
897 if (mock_keyring_items
.size() > 0)
898 CheckMockKeyringItem(&mock_keyring_items
[0], form_google_
, "chrome-42");
900 BrowserThread::PostTask(
901 BrowserThread::DB
, FROM_HERE
,
902 base::Bind(base::IgnoreResult(&NativeBackendGnome::RemoveLogin
),
903 base::Unretained(&backend
), form_google_
));
907 EXPECT_EQ(0u, mock_keyring_items
.size());
910 // Verify fix for http://crbug.com/408783.
911 TEST_F(NativeBackendGnomeTest
, RemoveLoginActionMismatch
) {
912 NativeBackendGnome
backend(42);
915 BrowserThread::PostTask(
916 BrowserThread::DB
, FROM_HERE
,
917 base::Bind(base::IgnoreResult(&NativeBackendGnome::AddLogin
),
918 base::Unretained(&backend
), form_google_
));
922 EXPECT_EQ(1u, mock_keyring_items
.size());
923 if (mock_keyring_items
.size() > 0)
924 CheckMockKeyringItem(&mock_keyring_items
[0], form_google_
, "chrome-42");
926 // Action url match not required for removal.
927 form_google_
.action
= GURL("https://some.other.url.com/path");
929 BrowserThread::PostTask(
930 BrowserThread::DB
, FROM_HERE
,
931 base::Bind(base::IgnoreResult(&NativeBackendGnome::RemoveLogin
),
932 base::Unretained(&backend
), form_google_
));
936 EXPECT_EQ(0u, mock_keyring_items
.size());
939 TEST_F(NativeBackendGnomeTest
, RemoveNonexistentLogin
) {
940 NativeBackendGnome
backend(42);
943 // First add an unrelated login.
944 BrowserThread::PostTask(
945 BrowserThread::DB
, FROM_HERE
,
946 base::Bind(base::IgnoreResult(&NativeBackendGnome::AddLogin
),
947 base::Unretained(&backend
), form_google_
));
951 EXPECT_EQ(1u, mock_keyring_items
.size());
952 if (mock_keyring_items
.size() > 0)
953 CheckMockKeyringItem(&mock_keyring_items
[0], form_google_
, "chrome-42");
955 // Attempt to remove a login that doesn't exist.
956 BrowserThread::PostTask(
957 BrowserThread::DB
, FROM_HERE
,
958 base::Bind(base::IgnoreResult(&NativeBackendGnome::RemoveLogin
),
959 base::Unretained(&backend
), form_isc_
));
961 // Make sure we can still get the first form back.
962 ScopedVector
<autofill::PasswordForm
> form_list
;
963 BrowserThread::PostTask(
964 BrowserThread::DB
, FROM_HERE
,
966 base::IgnoreResult(&NativeBackendGnome::GetAutofillableLogins
),
967 base::Unretained(&backend
), &form_list
));
971 // Quick check that we got something back.
972 EXPECT_EQ(1u, form_list
.size());
974 EXPECT_EQ(1u, mock_keyring_items
.size());
975 if (mock_keyring_items
.size() > 0)
976 CheckMockKeyringItem(&mock_keyring_items
[0], form_google_
, "chrome-42");
979 TEST_F(NativeBackendGnomeTest
, UpdateNonexistentLogin
) {
980 NativeBackendGnome
backend(42);
983 // First add an unrelated login.
984 BrowserThread::PostTask(
985 BrowserThread::DB
, FROM_HERE
,
986 base::Bind(base::IgnoreResult(&NativeBackendGnome::AddLogin
),
987 base::Unretained(&backend
), form_google_
));
991 EXPECT_EQ(1u, mock_keyring_items
.size());
992 if (mock_keyring_items
.size() > 0)
993 CheckMockKeyringItem(&mock_keyring_items
[0], form_google_
, "chrome-42");
995 // Attempt to update a login that doesn't exist.
996 PasswordStoreChangeList changes
;
997 BrowserThread::PostTask(
998 BrowserThread::DB
, FROM_HERE
,
999 base::Bind(base::IgnoreResult(&NativeBackendGnome::UpdateLogin
),
1000 base::Unretained(&backend
),
1002 base::Unretained(&changes
)));
1006 EXPECT_EQ(PasswordStoreChangeList(), changes
);
1007 EXPECT_EQ(1u, mock_keyring_items
.size());
1008 if (mock_keyring_items
.size() > 0)
1009 CheckMockKeyringItem(&mock_keyring_items
[0], form_google_
, "chrome-42");
1012 TEST_F(NativeBackendGnomeTest
, AddDuplicateLogin
) {
1013 NativeBackendGnome
backend(42);
1016 PasswordStoreChangeList changes
;
1017 changes
.push_back(PasswordStoreChange(PasswordStoreChange::ADD
,
1019 BrowserThread::PostTaskAndReplyWithResult(
1020 BrowserThread::DB
, FROM_HERE
,
1021 base::Bind(&NativeBackendGnome::AddLogin
,
1022 base::Unretained(&backend
), form_google_
),
1023 base::Bind(&CheckPasswordChanges
, changes
));
1026 changes
.push_back(PasswordStoreChange(PasswordStoreChange::REMOVE
,
1028 form_google_
.times_used
++;
1029 changes
.push_back(PasswordStoreChange(PasswordStoreChange::ADD
,
1032 BrowserThread::PostTaskAndReplyWithResult(
1033 BrowserThread::DB
, FROM_HERE
,
1034 base::Bind(&NativeBackendGnome::AddLogin
,
1035 base::Unretained(&backend
), form_google_
),
1036 base::Bind(&CheckPasswordChanges
, changes
));
1040 EXPECT_EQ(1u, mock_keyring_items
.size());
1041 if (mock_keyring_items
.size() > 0)
1042 CheckMockKeyringItem(&mock_keyring_items
[0], form_google_
, "chrome-42");
1045 TEST_F(NativeBackendGnomeTest
, AndroidCredentials
) {
1046 NativeBackendGnome
backend(42);
1049 PasswordForm observed_android_form
;
1050 observed_android_form
.scheme
= PasswordForm::SCHEME_HTML
;
1051 observed_android_form
.signon_realm
=
1052 "android://7x7IDboo8u9YKraUsbmVkuf1-@net.rateflix.app/";
1053 PasswordForm saved_android_form
= observed_android_form
;
1054 saved_android_form
.username_value
= base::UTF8ToUTF16("randomusername");
1055 saved_android_form
.password_value
= base::UTF8ToUTF16("password");
1056 saved_android_form
.date_created
= base::Time::Now();
1058 BrowserThread::PostTask(
1059 BrowserThread::DB
, FROM_HERE
,
1060 base::Bind(base::IgnoreResult(&NativeBackendGnome::AddLogin
),
1061 base::Unretained(&backend
), saved_android_form
));
1063 ScopedVector
<autofill::PasswordForm
> form_list
;
1064 BrowserThread::PostTask(
1065 BrowserThread::DB
, FROM_HERE
,
1066 base::Bind(base::IgnoreResult(&NativeBackendGnome::GetLogins
),
1067 base::Unretained(&backend
), observed_android_form
,
1072 EXPECT_EQ(1u, form_list
.size());
1073 EXPECT_EQ(saved_android_form
, *form_list
[0]);
1076 TEST_F(NativeBackendGnomeTest
, RemoveLoginsCreatedBetween
) {
1077 CheckRemoveLoginsBetween(CREATED
);
1080 TEST_F(NativeBackendGnomeTest
, RemoveLoginsSyncedBetween
) {
1081 CheckRemoveLoginsBetween(SYNCED
);
1084 // TODO(mdm): add more basic tests here at some point.