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/prefs/pref_service.h"
9 #include "base/strings/string_number_conversions.h"
10 #include "base/strings/string_util.h"
11 #include "base/strings/stringprintf.h"
12 #include "base/strings/utf_string_conversions.h"
13 #include "base/time/time.h"
14 #include "chrome/browser/password_manager/native_backend_gnome_x.h"
15 #include "chrome/test/base/testing_profile.h"
16 #include "components/autofill/core/common/password_form.h"
17 #include "components/password_manager/core/browser/psl_matching_helper.h"
18 #include "components/password_manager/core/common/password_manager_pref_names.h"
19 #include "content/public/test/test_browser_thread.h"
20 #include "testing/gtest/include/gtest/gtest.h"
22 using autofill::PasswordForm
;
23 using base::UTF8ToUTF16
;
24 using base::UTF16ToUTF8
;
25 using content::BrowserThread
;
26 using password_manager::PasswordStoreChange
;
27 using password_manager::PasswordStoreChangeList
;
31 // What follows is a very simple implementation of the subset of the GNOME
32 // Keyring API that we actually use. It gets substituted for the real one by
33 // MockGnomeKeyringLoader, which hooks into the facility normally used to load
34 // the GNOME Keyring library at runtime to avoid a static dependency on it.
36 struct MockKeyringItem
{
38 MockKeyringItem(const char* keyring
,
39 const std::string
& display_name
,
40 const std::string
& password
)
41 : keyring(keyring
? keyring
: "login"),
42 display_name(display_name
),
45 struct ItemAttribute
{
46 ItemAttribute() : type(UINT32
), value_uint32(0) {}
47 explicit ItemAttribute(uint32_t value
)
48 : type(UINT32
), value_uint32(value
) {}
49 explicit ItemAttribute(const std::string
& value
)
50 : type(STRING
), value_string(value
) {}
52 bool Equals(const ItemAttribute
& x
) const {
53 if (type
!= x
.type
) return false;
54 return (type
== STRING
) ? value_string
== x
.value_string
55 : value_uint32
== x
.value_uint32
;
58 enum Type
{ UINT32
, STRING
} type
;
59 uint32_t value_uint32
;
60 std::string value_string
;
63 typedef std::map
<std::string
, ItemAttribute
> attribute_map
;
64 typedef std::vector
<std::pair
<std::string
, ItemAttribute
> > attribute_query
;
66 bool Matches(const attribute_query
& query
) const {
67 // The real GNOME Keyring doesn't match empty queries.
68 if (query
.empty()) return false;
69 for (size_t i
= 0; i
< query
.size(); ++i
) {
70 attribute_map::const_iterator match
= attributes
.find(query
[i
].first
);
71 if (match
== attributes
.end()) return false;
72 if (!match
->second
.Equals(query
[i
].second
)) return false;
78 std::string display_name
;
81 attribute_map attributes
;
84 // The list of all keyring items we have stored.
85 std::vector
<MockKeyringItem
> mock_keyring_items
;
86 bool mock_keyring_reject_local_ids
= false;
88 bool IsStringAttribute(const GnomeKeyringPasswordSchema
* schema
,
89 const std::string
& name
) {
90 for (size_t i
= 0; schema
->attributes
[i
].name
; ++i
)
91 if (name
== schema
->attributes
[i
].name
)
92 return schema
->attributes
[i
].type
== GNOME_KEYRING_ATTRIBUTE_TYPE_STRING
;
93 NOTREACHED() << "Requested type of nonexistent attribute";
97 gboolean
mock_gnome_keyring_is_available() {
101 gpointer
mock_gnome_keyring_store_password(
102 const GnomeKeyringPasswordSchema
* schema
,
103 const gchar
* keyring
,
104 const gchar
* display_name
,
105 const gchar
* password
,
106 GnomeKeyringOperationDoneCallback callback
,
108 GDestroyNotify destroy_data
,
110 mock_keyring_items
.push_back(
111 MockKeyringItem(keyring
, display_name
, password
));
112 MockKeyringItem
* item
= &mock_keyring_items
.back();
113 const std::string keyring_desc
=
114 keyring
? base::StringPrintf("keyring %s", keyring
)
115 : std::string("default keyring");
116 VLOG(1) << "Adding item with origin " << display_name
117 << " to " << keyring_desc
;
119 va_start(ap
, destroy_data
);
121 while ((name
= va_arg(ap
, gchar
*))) {
122 if (IsStringAttribute(schema
, name
)) {
123 item
->attributes
[name
] =
124 MockKeyringItem::ItemAttribute(va_arg(ap
, gchar
*));
125 VLOG(1) << "Adding item attribute " << name
126 << ", value '" << item
->attributes
[name
].value_string
<< "'";
128 item
->attributes
[name
] =
129 MockKeyringItem::ItemAttribute(va_arg(ap
, uint32_t));
130 VLOG(1) << "Adding item attribute " << name
131 << ", value " << item
->attributes
[name
].value_uint32
;
135 // As a hack to ease testing migration, make it possible to reject the new
136 // format for the app string. This way we can add them easily to migrate.
137 if (mock_keyring_reject_local_ids
) {
138 MockKeyringItem::attribute_map::iterator it
=
139 item
->attributes
.find("application");
140 if (it
!= item
->attributes
.end() &&
141 it
->second
.type
== MockKeyringItem::ItemAttribute::STRING
&&
142 base::StringPiece(it
->second
.value_string
).starts_with("chrome-")) {
143 mock_keyring_items
.pop_back();
144 // GnomeKeyringResult, data
145 callback(GNOME_KEYRING_RESULT_IO_ERROR
, data
);
149 // GnomeKeyringResult, data
150 callback(GNOME_KEYRING_RESULT_OK
, data
);
154 gpointer
mock_gnome_keyring_delete_password(
155 const GnomeKeyringPasswordSchema
* schema
,
156 GnomeKeyringOperationDoneCallback callback
,
158 GDestroyNotify destroy_data
,
160 MockKeyringItem::attribute_query query
;
162 va_start(ap
, destroy_data
);
164 while ((name
= va_arg(ap
, gchar
*))) {
165 if (IsStringAttribute(schema
, name
)) {
166 query
.push_back(make_pair(std::string(name
),
167 MockKeyringItem::ItemAttribute(va_arg(ap
, gchar
*))));
168 VLOG(1) << "Querying with item attribute " << name
169 << ", value '" << query
.back().second
.value_string
<< "'";
171 query
.push_back(make_pair(std::string(name
),
172 MockKeyringItem::ItemAttribute(va_arg(ap
, uint32_t))));
173 VLOG(1) << "Querying with item attribute " << name
174 << ", value " << query
.back().second
.value_uint32
;
178 bool deleted
= false;
179 for (size_t i
= mock_keyring_items
.size(); i
> 0; --i
) {
180 const MockKeyringItem
* item
= &mock_keyring_items
[i
- 1];
181 if (item
->Matches(query
)) {
182 VLOG(1) << "Deleting item with origin " << item
->display_name
;
183 mock_keyring_items
.erase(mock_keyring_items
.begin() + (i
- 1));
187 // GnomeKeyringResult, data
188 callback(deleted
? GNOME_KEYRING_RESULT_OK
189 : GNOME_KEYRING_RESULT_NO_MATCH
, data
);
193 gpointer
mock_gnome_keyring_find_items(
194 GnomeKeyringItemType type
,
195 GnomeKeyringAttributeList
* attributes
,
196 GnomeKeyringOperationGetListCallback callback
,
198 GDestroyNotify destroy_data
) {
199 MockKeyringItem::attribute_query query
;
200 for (size_t i
= 0; i
< attributes
->len
; ++i
) {
201 GnomeKeyringAttribute attribute
=
202 g_array_index(attributes
, GnomeKeyringAttribute
, i
);
203 if (attribute
.type
== GNOME_KEYRING_ATTRIBUTE_TYPE_STRING
) {
205 make_pair(std::string(attribute
.name
),
206 MockKeyringItem::ItemAttribute(attribute
.value
.string
)));
207 VLOG(1) << "Querying with item attribute " << attribute
.name
208 << ", value '" << query
.back().second
.value_string
<< "'";
211 make_pair(std::string(attribute
.name
),
212 MockKeyringItem::ItemAttribute(attribute
.value
.integer
)));
213 VLOG(1) << "Querying with item attribute " << attribute
.name
<< ", value "
214 << query
.back().second
.value_uint32
;
217 // Find matches and add them to a list of results.
218 GList
* results
= NULL
;
219 for (size_t i
= 0; i
< mock_keyring_items
.size(); ++i
) {
220 const MockKeyringItem
* item
= &mock_keyring_items
[i
];
221 if (item
->Matches(query
)) {
222 GnomeKeyringFound
* found
= new GnomeKeyringFound
;
223 found
->keyring
= strdup(item
->keyring
.c_str());
225 found
->attributes
= gnome_keyring_attribute_list_new();
226 for (MockKeyringItem::attribute_map::const_iterator it
=
227 item
->attributes
.begin();
228 it
!= item
->attributes
.end();
230 if (it
->second
.type
== MockKeyringItem::ItemAttribute::STRING
) {
231 gnome_keyring_attribute_list_append_string(
232 found
->attributes
, it
->first
.c_str(),
233 it
->second
.value_string
.c_str());
235 gnome_keyring_attribute_list_append_uint32(
236 found
->attributes
, it
->first
.c_str(),
237 it
->second
.value_uint32
);
240 found
->secret
= strdup(item
->password
.c_str());
241 results
= g_list_prepend(results
, found
);
244 // GnomeKeyringResult, GList*, data
245 callback(results
? GNOME_KEYRING_RESULT_OK
246 : GNOME_KEYRING_RESULT_NO_MATCH
, results
, data
);
247 // Now free the list of results.
248 GList
* element
= g_list_first(results
);
250 GnomeKeyringFound
* found
= static_cast<GnomeKeyringFound
*>(element
->data
);
251 free(found
->keyring
);
252 gnome_keyring_attribute_list_free(found
->attributes
);
255 element
= g_list_next(element
);
257 g_list_free(results
);
261 const gchar
* mock_gnome_keyring_result_to_message(GnomeKeyringResult res
) {
262 return "mock keyring simulating failure";
265 // Inherit to get access to protected fields.
266 class MockGnomeKeyringLoader
: public GnomeKeyringLoader
{
268 static bool LoadMockGnomeKeyring() {
269 if (!LoadGnomeKeyring())
271 #define GNOME_KEYRING_ASSIGN_POINTER(name) \
272 gnome_keyring_##name = &mock_gnome_keyring_##name;
273 GNOME_KEYRING_FOR_EACH_MOCKED_FUNC(GNOME_KEYRING_ASSIGN_POINTER
)
274 #undef GNOME_KEYRING_ASSIGN_POINTER
275 keyring_loaded
= true;
276 // Reset the state of the mock library.
277 mock_keyring_items
.clear();
278 mock_keyring_reject_local_ids
= false;
283 void CheckPasswordChanges(const PasswordStoreChangeList
& expected_list
,
284 const PasswordStoreChangeList
& actual_list
) {
285 ASSERT_EQ(expected_list
.size(), actual_list
.size());
286 for (size_t i
= 0; i
< expected_list
.size(); ++i
) {
287 EXPECT_EQ(expected_list
[i
].type(), actual_list
[i
].type());
288 const PasswordForm
& expected
= expected_list
[i
].form();
289 const PasswordForm
& actual
= actual_list
[i
].form();
291 EXPECT_EQ(expected
.origin
, actual
.origin
);
292 EXPECT_EQ(expected
.password_value
, actual
.password_value
);
293 EXPECT_EQ(expected
.action
, actual
.action
);
294 EXPECT_EQ(expected
.username_element
, actual
.username_element
);
295 EXPECT_EQ(expected
.username_value
, actual
.username_value
);
296 EXPECT_EQ(expected
.password_element
, actual
.password_element
);
297 EXPECT_EQ(expected
.submit_element
, actual
.submit_element
);
298 EXPECT_EQ(expected
.signon_realm
, actual
.signon_realm
);
299 EXPECT_EQ(expected
.ssl_valid
, actual
.ssl_valid
);
300 EXPECT_EQ(expected
.preferred
, actual
.preferred
);
301 EXPECT_EQ(expected
.date_created
, actual
.date_created
);
302 EXPECT_EQ(expected
.blacklisted_by_user
, actual
.blacklisted_by_user
);
303 EXPECT_EQ(expected
.type
, actual
.type
);
304 EXPECT_EQ(expected
.times_used
, actual
.times_used
);
305 EXPECT_EQ(expected
.scheme
, actual
.scheme
);
306 EXPECT_EQ(expected
.date_synced
, actual
.date_synced
);
307 EXPECT_EQ(expected
.display_name
, actual
.display_name
);
308 EXPECT_EQ(expected
.avatar_url
, actual
.avatar_url
);
309 EXPECT_EQ(expected
.federation_url
, actual
.federation_url
);
310 EXPECT_EQ(expected
.skip_zero_click
, actual
.skip_zero_click
);
311 EXPECT_EQ(expected
.generation_upload_status
,
312 actual
.generation_upload_status
);
316 void CheckPasswordChangesWithResult(const PasswordStoreChangeList
* expected
,
317 const PasswordStoreChangeList
* actual
,
320 CheckPasswordChanges(*expected
, *actual
);
323 } // anonymous namespace
325 class NativeBackendGnomeTest
: public testing::Test
{
327 enum UpdateType
{ // Used in CheckPSLUpdate().
328 UPDATE_BY_UPDATELOGIN
,
331 enum RemoveBetweenMethod
{ // Used in CheckRemoveLoginsBetween().
336 NativeBackendGnomeTest()
337 : ui_thread_(BrowserThread::UI
, &message_loop_
),
338 db_thread_(BrowserThread::DB
) {
341 void SetUp() override
{
342 ASSERT_TRUE(db_thread_
.Start());
344 ASSERT_TRUE(MockGnomeKeyringLoader::LoadMockGnomeKeyring());
346 form_google_
.origin
= GURL("http://www.google.com/");
347 form_google_
.action
= GURL("http://www.google.com/login");
348 form_google_
.username_element
= UTF8ToUTF16("user");
349 form_google_
.username_value
= UTF8ToUTF16("joeschmoe");
350 form_google_
.password_element
= UTF8ToUTF16("pass");
351 form_google_
.password_value
= UTF8ToUTF16("seekrit");
352 form_google_
.submit_element
= UTF8ToUTF16("submit");
353 form_google_
.signon_realm
= "http://www.google.com/";
354 form_google_
.type
= PasswordForm::TYPE_GENERATED
;
355 form_google_
.date_created
= base::Time::Now();
356 form_google_
.date_synced
= base::Time::Now();
357 form_google_
.display_name
= UTF8ToUTF16("Joe Schmoe");
358 form_google_
.avatar_url
= GURL("http://www.google.com/avatar");
359 form_google_
.federation_url
= GURL("http://www.google.com/federation_url");
360 form_google_
.skip_zero_click
= true;
361 form_google_
.generation_upload_status
= PasswordForm::POSITIVE_SIGNAL_SENT
;
363 form_facebook_
.origin
= GURL("http://www.facebook.com/");
364 form_facebook_
.action
= GURL("http://www.facebook.com/login");
365 form_facebook_
.username_element
= UTF8ToUTF16("user");
366 form_facebook_
.username_value
= UTF8ToUTF16("a");
367 form_facebook_
.password_element
= UTF8ToUTF16("password");
368 form_facebook_
.password_value
= UTF8ToUTF16("b");
369 form_facebook_
.submit_element
= UTF8ToUTF16("submit");
370 form_facebook_
.signon_realm
= "http://www.facebook.com/";
371 form_facebook_
.date_created
= base::Time::Now();
372 form_facebook_
.date_synced
= base::Time::Now();
373 form_facebook_
.display_name
= UTF8ToUTF16("Joe Schmoe");
374 form_facebook_
.avatar_url
= GURL("http://www.facebook.com/avatar");
375 form_facebook_
.federation_url
= GURL("http://www.facebook.com/federation");
376 form_facebook_
.skip_zero_click
= true;
377 form_facebook_
.generation_upload_status
= PasswordForm::NO_SIGNAL_SENT
;
379 form_isc_
.origin
= GURL("http://www.isc.org/");
380 form_isc_
.action
= GURL("http://www.isc.org/auth");
381 form_isc_
.username_element
= UTF8ToUTF16("id");
382 form_isc_
.username_value
= UTF8ToUTF16("janedoe");
383 form_isc_
.password_element
= UTF8ToUTF16("passwd");
384 form_isc_
.password_value
= UTF8ToUTF16("ihazabukkit");
385 form_isc_
.submit_element
= UTF8ToUTF16("login");
386 form_isc_
.signon_realm
= "http://www.isc.org/";
387 form_isc_
.date_created
= base::Time::Now();
388 form_isc_
.date_synced
= base::Time::Now();
390 other_auth_
.origin
= GURL("http://www.example.com/");
391 other_auth_
.username_value
= UTF8ToUTF16("username");
392 other_auth_
.password_value
= UTF8ToUTF16("pass");
393 other_auth_
.signon_realm
= "http://www.example.com/Realm";
394 other_auth_
.date_created
= base::Time::Now();
395 other_auth_
.date_synced
= base::Time::Now();
398 void TearDown() override
{
399 base::MessageLoop::current()->PostTask(FROM_HERE
,
400 base::MessageLoop::QuitClosure());
401 base::MessageLoop::current()->Run();
405 void RunBothThreads() {
406 // First we post a message to the DB thread that will run after all other
407 // messages that have been posted to the DB thread (we don't expect more
408 // to be posted), which posts a message to the UI thread to quit the loop.
409 // That way we can run both loops and be sure that the UI thread loop will
410 // quit so we can get on with the rest of the test.
411 BrowserThread::PostTask(BrowserThread::DB
, FROM_HERE
,
412 base::Bind(&PostQuitTask
, &message_loop_
));
413 base::MessageLoop::current()->Run();
416 static void PostQuitTask(base::MessageLoop
* loop
) {
417 loop
->PostTask(FROM_HERE
, base::MessageLoop::QuitClosure());
420 void CheckUint32Attribute(const MockKeyringItem
* item
,
421 const std::string
& attribute
,
423 MockKeyringItem::attribute_map::const_iterator it
=
424 item
->attributes
.find(attribute
);
425 EXPECT_NE(item
->attributes
.end(), it
);
426 if (it
!= item
->attributes
.end()) {
427 EXPECT_EQ(MockKeyringItem::ItemAttribute::UINT32
, it
->second
.type
);
428 EXPECT_EQ(value
, it
->second
.value_uint32
);
432 void CheckStringAttribute(const MockKeyringItem
* item
,
433 const std::string
& attribute
,
434 const std::string
& value
) {
435 MockKeyringItem::attribute_map::const_iterator it
=
436 item
->attributes
.find(attribute
);
437 EXPECT_NE(item
->attributes
.end(), it
);
438 if (it
!= item
->attributes
.end()) {
439 EXPECT_EQ(MockKeyringItem::ItemAttribute::STRING
, it
->second
.type
);
440 EXPECT_EQ(value
, it
->second
.value_string
);
444 void CheckMockKeyringItem(const MockKeyringItem
* item
,
445 const PasswordForm
& form
,
446 const std::string
& app_string
) {
447 // We always add items to the login keyring.
448 EXPECT_EQ("login", item
->keyring
);
449 EXPECT_EQ(form
.origin
.spec(), item
->display_name
);
450 EXPECT_EQ(UTF16ToUTF8(form
.password_value
), item
->password
);
451 EXPECT_EQ(21u, item
->attributes
.size());
452 CheckStringAttribute(item
, "origin_url", form
.origin
.spec());
453 CheckStringAttribute(item
, "action_url", form
.action
.spec());
454 CheckStringAttribute(item
, "username_element",
455 UTF16ToUTF8(form
.username_element
));
456 CheckStringAttribute(item
, "username_value",
457 UTF16ToUTF8(form
.username_value
));
458 CheckStringAttribute(item
, "password_element",
459 UTF16ToUTF8(form
.password_element
));
460 CheckStringAttribute(item
, "submit_element",
461 UTF16ToUTF8(form
.submit_element
));
462 CheckStringAttribute(item
, "signon_realm", form
.signon_realm
);
463 CheckUint32Attribute(item
, "ssl_valid", form
.ssl_valid
);
464 CheckUint32Attribute(item
, "preferred", form
.preferred
);
465 // We don't check the date created. It varies.
466 CheckUint32Attribute(item
, "blacklisted_by_user", form
.blacklisted_by_user
);
467 CheckUint32Attribute(item
, "type", form
.type
);
468 CheckUint32Attribute(item
, "times_used", form
.times_used
);
469 CheckUint32Attribute(item
, "scheme", form
.scheme
);
470 CheckStringAttribute(item
, "date_synced", base::Int64ToString(
471 form
.date_synced
.ToInternalValue()));
472 CheckStringAttribute(item
, "display_name", UTF16ToUTF8(form
.display_name
));
473 CheckStringAttribute(item
, "avatar_url", form
.avatar_url
.spec());
474 CheckStringAttribute(item
, "federation_url", form
.federation_url
.spec());
475 CheckUint32Attribute(item
, "skip_zero_click", form
.skip_zero_click
);
476 CheckUint32Attribute(item
, "generation_upload_status",
477 form
.generation_upload_status
);
478 CheckStringAttribute(item
, "application", app_string
);
481 // Saves |credentials| and then gets logins matching |url| and |scheme|.
482 // Returns true when something is found, and in such case copies the result to
483 // |result| when |result| is not NULL. (Note that there can be max. 1 result,
484 // derived from |credentials|.)
485 bool CheckCredentialAvailability(const PasswordForm
& credentials
,
487 const PasswordForm::Scheme
& scheme
,
488 PasswordForm
* result
) {
489 NativeBackendGnome
backend(321);
492 BrowserThread::PostTask(
495 base::Bind(base::IgnoreResult(&NativeBackendGnome::AddLogin
),
496 base::Unretained(&backend
),
499 PasswordForm target_form
;
500 target_form
.origin
= url
;
501 target_form
.signon_realm
= url
.spec();
502 if (scheme
!= PasswordForm::SCHEME_HTML
) {
503 // For non-HTML forms, the realm used for authentication
504 // (http://tools.ietf.org/html/rfc1945#section-10.2) is appended to the
505 // signon_realm. Just use a default value for now.
506 target_form
.signon_realm
.append("Realm");
507 target_form
.scheme
= scheme
;
509 ScopedVector
<autofill::PasswordForm
> form_list
;
510 BrowserThread::PostTask(
513 base::Bind(base::IgnoreResult(&NativeBackendGnome::GetLogins
),
514 base::Unretained(&backend
),
520 EXPECT_EQ(1u, mock_keyring_items
.size());
521 if (mock_keyring_items
.size() > 0)
522 CheckMockKeyringItem(&mock_keyring_items
[0], credentials
, "chrome-321");
523 mock_keyring_items
.clear();
525 if (form_list
.empty())
527 EXPECT_EQ(1u, form_list
.size());
529 *result
= *form_list
[0];
533 // Test that updating does not use PSL matching: Add a www.facebook.com
534 // password, then use PSL matching to get a copy of it for m.facebook.com, and
535 // add that copy as well. Now update the www.facebook.com password -- the
536 // m.facebook.com password should not get updated. Depending on the argument,
537 // the credential update is done via UpdateLogin or AddLogin.
538 void CheckPSLUpdate(UpdateType update_type
) {
539 NativeBackendGnome
backend(321);
542 // Add |form_facebook_| to saved logins.
543 BrowserThread::PostTask(
546 base::Bind(base::IgnoreResult(&NativeBackendGnome::AddLogin
),
547 base::Unretained(&backend
),
550 // Get the PSL-matched copy of the saved login for m.facebook.
551 const GURL
kMobileURL("http://m.facebook.com/");
552 PasswordForm m_facebook_lookup
;
553 m_facebook_lookup
.origin
= kMobileURL
;
554 m_facebook_lookup
.signon_realm
= kMobileURL
.spec();
555 ScopedVector
<autofill::PasswordForm
> form_list
;
556 BrowserThread::PostTask(
559 base::Bind(base::IgnoreResult(&NativeBackendGnome::GetLogins
),
560 base::Unretained(&backend
),
564 EXPECT_EQ(1u, mock_keyring_items
.size());
565 EXPECT_EQ(1u, form_list
.size());
566 PasswordForm m_facebook
= *form_list
[0];
568 EXPECT_EQ(kMobileURL
, m_facebook
.origin
);
569 EXPECT_EQ(kMobileURL
.spec(), m_facebook
.signon_realm
);
571 // Add the PSL-matched copy to saved logins.
572 BrowserThread::PostTask(
575 base::Bind(base::IgnoreResult(&NativeBackendGnome::AddLogin
),
576 base::Unretained(&backend
),
579 EXPECT_EQ(2u, mock_keyring_items
.size());
581 // Update www.facebook.com login.
582 PasswordForm
new_facebook(form_facebook_
);
583 const base::string16
kOldPassword(form_facebook_
.password_value
);
584 const base::string16
kNewPassword(UTF8ToUTF16("new_b"));
585 EXPECT_NE(kOldPassword
, kNewPassword
);
586 new_facebook
.password_value
= kNewPassword
;
587 switch (update_type
) {
588 case UPDATE_BY_UPDATELOGIN
:
589 BrowserThread::PostTask(
592 base::Bind(base::IgnoreResult(&NativeBackendGnome::UpdateLogin
),
593 base::Unretained(&backend
),
595 base::Owned(new PasswordStoreChangeList
)));
597 case UPDATE_BY_ADDLOGIN
:
598 BrowserThread::PostTask(
601 base::Bind(base::IgnoreResult(&NativeBackendGnome::AddLogin
),
602 base::Unretained(&backend
),
608 EXPECT_EQ(2u, mock_keyring_items
.size());
610 // Check that m.facebook.com login was not modified by the update.
611 BrowserThread::PostTask(
614 base::Bind(base::IgnoreResult(&NativeBackendGnome::GetLogins
),
615 base::Unretained(&backend
),
619 // There should be two results -- the exact one, and the PSL-matched one.
620 EXPECT_EQ(2u, form_list
.size());
621 size_t index_non_psl
= 0;
622 if (!form_list
[index_non_psl
]->original_signon_realm
.empty())
624 EXPECT_EQ(kMobileURL
, form_list
[index_non_psl
]->origin
);
625 EXPECT_EQ(kMobileURL
.spec(), form_list
[index_non_psl
]->signon_realm
);
626 EXPECT_EQ(kOldPassword
, form_list
[index_non_psl
]->password_value
);
629 // Check that www.facebook.com login was modified by the update.
630 BrowserThread::PostTask(
633 base::Bind(base::IgnoreResult(&NativeBackendGnome::GetLogins
),
634 base::Unretained(&backend
),
638 // There should be two results -- the exact one, and the PSL-matched one.
639 EXPECT_EQ(2u, form_list
.size());
641 if (!form_list
[index_non_psl
]->original_signon_realm
.empty())
643 EXPECT_EQ(form_facebook_
.origin
, form_list
[index_non_psl
]->origin
);
644 EXPECT_EQ(form_facebook_
.signon_realm
,
645 form_list
[index_non_psl
]->signon_realm
);
646 EXPECT_EQ(kNewPassword
, form_list
[index_non_psl
]->password_value
);
649 void CheckMatchingWithScheme(const PasswordForm::Scheme
& scheme
) {
650 other_auth_
.scheme
= scheme
;
652 // Don't match a non-HTML form with an HTML form.
653 EXPECT_FALSE(CheckCredentialAvailability(
654 other_auth_
, GURL("http://www.example.com"),
655 PasswordForm::SCHEME_HTML
, NULL
));
656 // Don't match an HTML form with non-HTML auth form.
657 EXPECT_FALSE(CheckCredentialAvailability(
658 form_google_
, GURL("http://www.google.com/"), scheme
, NULL
));
659 // Don't match two different non-HTML auth forms with different origin.
660 EXPECT_FALSE(CheckCredentialAvailability(
661 other_auth_
, GURL("http://first.example.com"), scheme
, NULL
));
662 // Do match non-HTML forms from the same origin.
663 EXPECT_TRUE(CheckCredentialAvailability(
664 other_auth_
, GURL("http://www.example.com/"), scheme
, NULL
));
667 void CheckRemoveLoginsBetween(RemoveBetweenMethod date_to_test
) {
668 NativeBackendGnome
backend(42);
671 base::Time now
= base::Time::Now();
672 base::Time next_day
= now
+ base::TimeDelta::FromDays(1);
673 form_google_
.date_synced
= base::Time();
674 form_isc_
.date_synced
= base::Time();
675 form_google_
.date_created
= now
;
676 form_isc_
.date_created
= now
;
677 if (date_to_test
== CREATED
) {
678 form_google_
.date_created
= now
;
679 form_isc_
.date_created
= next_day
;
681 form_google_
.date_synced
= now
;
682 form_isc_
.date_synced
= next_day
;
685 BrowserThread::PostTask(
688 base::Bind(base::IgnoreResult(&NativeBackendGnome::AddLogin
),
689 base::Unretained(&backend
),
691 BrowserThread::PostTask(
694 base::Bind(base::IgnoreResult(&NativeBackendGnome::AddLogin
),
695 base::Unretained(&backend
),
698 PasswordStoreChangeList expected_changes
;
699 expected_changes
.push_back(
700 PasswordStoreChange(PasswordStoreChange::REMOVE
, form_google_
));
701 PasswordStoreChangeList changes
;
702 bool (NativeBackendGnome::*method
)(
703 base::Time
, base::Time
, password_manager::PasswordStoreChangeList
*) =
704 date_to_test
== CREATED
705 ? &NativeBackendGnome::RemoveLoginsCreatedBetween
706 : &NativeBackendGnome::RemoveLoginsSyncedBetween
;
707 BrowserThread::PostTaskAndReplyWithResult(
711 base::Unretained(&backend
),
716 &CheckPasswordChangesWithResult
, &expected_changes
, &changes
));
719 EXPECT_EQ(1u, mock_keyring_items
.size());
720 if (mock_keyring_items
.size() > 0)
721 CheckMockKeyringItem(&mock_keyring_items
[0], form_isc_
, "chrome-42");
724 expected_changes
.clear();
725 expected_changes
.push_back(
726 PasswordStoreChange(PasswordStoreChange::REMOVE
, form_isc_
));
727 BrowserThread::PostTaskAndReplyWithResult(
731 base::Unretained(&backend
),
736 &CheckPasswordChangesWithResult
, &expected_changes
, &changes
));
739 EXPECT_EQ(0u, mock_keyring_items
.size());
742 base::MessageLoopForUI message_loop_
;
743 content::TestBrowserThread ui_thread_
;
744 content::TestBrowserThread db_thread_
;
746 // Provide some test forms to avoid having to set them up in each test.
747 PasswordForm form_google_
;
748 PasswordForm form_facebook_
;
749 PasswordForm form_isc_
;
750 PasswordForm other_auth_
;
753 TEST_F(NativeBackendGnomeTest
, BasicAddLogin
) {
754 NativeBackendGnome
backend(42);
757 BrowserThread::PostTask(
758 BrowserThread::DB
, FROM_HERE
,
759 base::Bind(base::IgnoreResult(&NativeBackendGnome::AddLogin
),
760 base::Unretained(&backend
), form_google_
));
764 EXPECT_EQ(1u, mock_keyring_items
.size());
765 if (mock_keyring_items
.size() > 0)
766 CheckMockKeyringItem(&mock_keyring_items
[0], form_google_
, "chrome-42");
769 TEST_F(NativeBackendGnomeTest
, BasicListLogins
) {
770 NativeBackendGnome
backend(42);
773 BrowserThread::PostTask(
774 BrowserThread::DB
, FROM_HERE
,
775 base::Bind(base::IgnoreResult( &NativeBackendGnome::AddLogin
),
776 base::Unretained(&backend
), form_google_
));
778 ScopedVector
<autofill::PasswordForm
> form_list
;
779 BrowserThread::PostTask(
780 BrowserThread::DB
, FROM_HERE
,
782 base::IgnoreResult(&NativeBackendGnome::GetAutofillableLogins
),
783 base::Unretained(&backend
), &form_list
));
787 // Quick check that we got something back.
788 EXPECT_EQ(1u, form_list
.size());
790 EXPECT_EQ(1u, mock_keyring_items
.size());
791 if (mock_keyring_items
.size() > 0)
792 CheckMockKeyringItem(&mock_keyring_items
[0], form_google_
, "chrome-42");
795 // Save a password for www.facebook.com and see it suggested for m.facebook.com.
796 TEST_F(NativeBackendGnomeTest
, PSLMatchingPositive
) {
798 const GURL
kMobileURL("http://m.facebook.com/");
799 EXPECT_TRUE(CheckCredentialAvailability(
800 form_facebook_
, kMobileURL
, PasswordForm::SCHEME_HTML
, &result
));
801 EXPECT_EQ(kMobileURL
, result
.origin
);
802 EXPECT_EQ(kMobileURL
.spec(), result
.signon_realm
);
805 // Save a password for www.facebook.com and see it not suggested for
807 TEST_F(NativeBackendGnomeTest
, PSLMatchingNegativeDomainMismatch
) {
808 EXPECT_FALSE(CheckCredentialAvailability(
809 form_facebook_
, GURL("http://m-facebook.com/"),
810 PasswordForm::SCHEME_HTML
, NULL
));
813 // Test PSL matching is off for domains excluded from it.
814 TEST_F(NativeBackendGnomeTest
, PSLMatchingDisabledDomains
) {
815 EXPECT_FALSE(CheckCredentialAvailability(
816 form_google_
, GURL("http://one.google.com/"),
817 PasswordForm::SCHEME_HTML
, NULL
));
820 // Make sure PSL matches aren't available for non-HTML forms.
821 TEST_F(NativeBackendGnomeTest
, PSLMatchingDisabledForNonHTMLForms
) {
822 CheckMatchingWithScheme(PasswordForm::SCHEME_BASIC
);
823 CheckMatchingWithScheme(PasswordForm::SCHEME_DIGEST
);
824 CheckMatchingWithScheme(PasswordForm::SCHEME_OTHER
);
828 TEST_F(NativeBackendGnomeTest
, PSLUpdatingStrictUpdateLogin
) {
829 CheckPSLUpdate(UPDATE_BY_UPDATELOGIN
);
832 TEST_F(NativeBackendGnomeTest
, PSLUpdatingStrictAddLogin
) {
833 // TODO(vabr): if AddLogin becomes no longer valid for existing logins, then
834 // just delete this test.
835 CheckPSLUpdate(UPDATE_BY_ADDLOGIN
);
838 TEST_F(NativeBackendGnomeTest
, BasicUpdateLogin
) {
839 NativeBackendGnome
backend(42);
842 // First add google login.
843 BrowserThread::PostTask(
844 BrowserThread::DB
, FROM_HERE
,
845 base::Bind(base::IgnoreResult(&NativeBackendGnome::AddLogin
),
846 base::Unretained(&backend
), form_google_
));
850 PasswordForm
new_form_google(form_google_
);
851 new_form_google
.times_used
= 1;
852 new_form_google
.action
= GURL("http://www.google.com/different/login");
854 EXPECT_EQ(1u, mock_keyring_items
.size());
855 if (mock_keyring_items
.size() > 0)
856 CheckMockKeyringItem(&mock_keyring_items
[0], form_google_
, "chrome-42");
859 PasswordStoreChangeList changes
;
860 BrowserThread::PostTask(
861 BrowserThread::DB
, FROM_HERE
,
862 base::Bind(base::IgnoreResult(&NativeBackendGnome::UpdateLogin
),
863 base::Unretained(&backend
),
865 base::Unretained(&changes
)));
869 ASSERT_EQ(1u, changes
.size());
870 EXPECT_EQ(PasswordStoreChange::UPDATE
, changes
.front().type());
871 EXPECT_EQ(new_form_google
, changes
.front().form());
872 EXPECT_EQ(1u, mock_keyring_items
.size());
873 if (mock_keyring_items
.size() > 0)
874 CheckMockKeyringItem(&mock_keyring_items
[0], new_form_google
, "chrome-42");
877 TEST_F(NativeBackendGnomeTest
, BasicRemoveLogin
) {
878 NativeBackendGnome
backend(42);
881 BrowserThread::PostTask(
882 BrowserThread::DB
, FROM_HERE
,
883 base::Bind(base::IgnoreResult(&NativeBackendGnome::AddLogin
),
884 base::Unretained(&backend
), form_google_
));
888 EXPECT_EQ(1u, mock_keyring_items
.size());
889 if (mock_keyring_items
.size() > 0)
890 CheckMockKeyringItem(&mock_keyring_items
[0], form_google_
, "chrome-42");
892 BrowserThread::PostTask(
893 BrowserThread::DB
, FROM_HERE
,
894 base::Bind(base::IgnoreResult(&NativeBackendGnome::RemoveLogin
),
895 base::Unretained(&backend
), form_google_
));
899 EXPECT_EQ(0u, mock_keyring_items
.size());
902 // Verify fix for http://crbug.com/408783.
903 TEST_F(NativeBackendGnomeTest
, RemoveLoginActionMismatch
) {
904 NativeBackendGnome
backend(42);
907 BrowserThread::PostTask(
908 BrowserThread::DB
, FROM_HERE
,
909 base::Bind(base::IgnoreResult(&NativeBackendGnome::AddLogin
),
910 base::Unretained(&backend
), form_google_
));
914 EXPECT_EQ(1u, mock_keyring_items
.size());
915 if (mock_keyring_items
.size() > 0)
916 CheckMockKeyringItem(&mock_keyring_items
[0], form_google_
, "chrome-42");
918 // Action url match not required for removal.
919 form_google_
.action
= GURL("https://some.other.url.com/path");
921 BrowserThread::PostTask(
922 BrowserThread::DB
, FROM_HERE
,
923 base::Bind(base::IgnoreResult(&NativeBackendGnome::RemoveLogin
),
924 base::Unretained(&backend
), form_google_
));
928 EXPECT_EQ(0u, mock_keyring_items
.size());
931 TEST_F(NativeBackendGnomeTest
, RemoveNonexistentLogin
) {
932 NativeBackendGnome
backend(42);
935 // First add an unrelated login.
936 BrowserThread::PostTask(
937 BrowserThread::DB
, FROM_HERE
,
938 base::Bind(base::IgnoreResult(&NativeBackendGnome::AddLogin
),
939 base::Unretained(&backend
), form_google_
));
943 EXPECT_EQ(1u, mock_keyring_items
.size());
944 if (mock_keyring_items
.size() > 0)
945 CheckMockKeyringItem(&mock_keyring_items
[0], form_google_
, "chrome-42");
947 // Attempt to remove a login that doesn't exist.
948 BrowserThread::PostTask(
949 BrowserThread::DB
, FROM_HERE
,
950 base::Bind(base::IgnoreResult(&NativeBackendGnome::RemoveLogin
),
951 base::Unretained(&backend
), form_isc_
));
953 // Make sure we can still get the first form back.
954 ScopedVector
<autofill::PasswordForm
> form_list
;
955 BrowserThread::PostTask(
956 BrowserThread::DB
, FROM_HERE
,
958 base::IgnoreResult(&NativeBackendGnome::GetAutofillableLogins
),
959 base::Unretained(&backend
), &form_list
));
963 // Quick check that we got something back.
964 EXPECT_EQ(1u, form_list
.size());
966 EXPECT_EQ(1u, mock_keyring_items
.size());
967 if (mock_keyring_items
.size() > 0)
968 CheckMockKeyringItem(&mock_keyring_items
[0], form_google_
, "chrome-42");
971 TEST_F(NativeBackendGnomeTest
, UpdateNonexistentLogin
) {
972 NativeBackendGnome
backend(42);
975 // First add an unrelated login.
976 BrowserThread::PostTask(
977 BrowserThread::DB
, FROM_HERE
,
978 base::Bind(base::IgnoreResult(&NativeBackendGnome::AddLogin
),
979 base::Unretained(&backend
), form_google_
));
983 EXPECT_EQ(1u, mock_keyring_items
.size());
984 if (mock_keyring_items
.size() > 0)
985 CheckMockKeyringItem(&mock_keyring_items
[0], form_google_
, "chrome-42");
987 // Attempt to update a login that doesn't exist.
988 PasswordStoreChangeList changes
;
989 BrowserThread::PostTask(
990 BrowserThread::DB
, FROM_HERE
,
991 base::Bind(base::IgnoreResult(&NativeBackendGnome::UpdateLogin
),
992 base::Unretained(&backend
),
994 base::Unretained(&changes
)));
998 EXPECT_EQ(PasswordStoreChangeList(), changes
);
999 EXPECT_EQ(1u, mock_keyring_items
.size());
1000 if (mock_keyring_items
.size() > 0)
1001 CheckMockKeyringItem(&mock_keyring_items
[0], form_google_
, "chrome-42");
1004 TEST_F(NativeBackendGnomeTest
, AddDuplicateLogin
) {
1005 NativeBackendGnome
backend(42);
1008 PasswordStoreChangeList changes
;
1009 changes
.push_back(PasswordStoreChange(PasswordStoreChange::ADD
,
1011 BrowserThread::PostTaskAndReplyWithResult(
1012 BrowserThread::DB
, FROM_HERE
,
1013 base::Bind(&NativeBackendGnome::AddLogin
,
1014 base::Unretained(&backend
), form_google_
),
1015 base::Bind(&CheckPasswordChanges
, changes
));
1018 changes
.push_back(PasswordStoreChange(PasswordStoreChange::REMOVE
,
1020 form_google_
.times_used
++;
1021 changes
.push_back(PasswordStoreChange(PasswordStoreChange::ADD
,
1024 BrowserThread::PostTaskAndReplyWithResult(
1025 BrowserThread::DB
, FROM_HERE
,
1026 base::Bind(&NativeBackendGnome::AddLogin
,
1027 base::Unretained(&backend
), form_google_
),
1028 base::Bind(&CheckPasswordChanges
, changes
));
1032 EXPECT_EQ(1u, mock_keyring_items
.size());
1033 if (mock_keyring_items
.size() > 0)
1034 CheckMockKeyringItem(&mock_keyring_items
[0], form_google_
, "chrome-42");
1037 TEST_F(NativeBackendGnomeTest
, ListLoginsAppends
) {
1038 NativeBackendGnome
backend(42);
1041 BrowserThread::PostTask(
1042 BrowserThread::DB
, FROM_HERE
,
1043 base::Bind(base::IgnoreResult(&NativeBackendGnome::AddLogin
),
1044 base::Unretained(&backend
), form_google_
));
1046 // Send the same request twice with the same list both times.
1047 ScopedVector
<autofill::PasswordForm
> form_list
;
1048 BrowserThread::PostTask(
1049 BrowserThread::DB
, FROM_HERE
,
1051 base::IgnoreResult(&NativeBackendGnome::GetAutofillableLogins
),
1052 base::Unretained(&backend
), &form_list
));
1053 BrowserThread::PostTask(
1054 BrowserThread::DB
, FROM_HERE
,
1056 base::IgnoreResult(&NativeBackendGnome::GetAutofillableLogins
),
1057 base::Unretained(&backend
), &form_list
));
1061 // Quick check that we got two results back.
1062 EXPECT_EQ(2u, form_list
.size());
1064 EXPECT_EQ(1u, mock_keyring_items
.size());
1065 if (mock_keyring_items
.size() > 0)
1066 CheckMockKeyringItem(&mock_keyring_items
[0], form_google_
, "chrome-42");
1069 TEST_F(NativeBackendGnomeTest
, AndroidCredentials
) {
1070 NativeBackendGnome
backend(42);
1073 PasswordForm observed_android_form
;
1074 observed_android_form
.scheme
= PasswordForm::SCHEME_HTML
;
1075 observed_android_form
.signon_realm
=
1076 "android://7x7IDboo8u9YKraUsbmVkuf1-@net.rateflix.app/";
1077 PasswordForm saved_android_form
= observed_android_form
;
1078 saved_android_form
.username_value
= base::UTF8ToUTF16("randomusername");
1079 saved_android_form
.password_value
= base::UTF8ToUTF16("password");
1080 saved_android_form
.date_created
= base::Time::Now();
1082 BrowserThread::PostTask(
1083 BrowserThread::DB
, FROM_HERE
,
1084 base::Bind(base::IgnoreResult(&NativeBackendGnome::AddLogin
),
1085 base::Unretained(&backend
), saved_android_form
));
1087 ScopedVector
<autofill::PasswordForm
> form_list
;
1088 BrowserThread::PostTask(
1089 BrowserThread::DB
, FROM_HERE
,
1090 base::Bind(base::IgnoreResult(&NativeBackendGnome::GetLogins
),
1091 base::Unretained(&backend
), observed_android_form
,
1096 EXPECT_EQ(1u, form_list
.size());
1097 EXPECT_EQ(saved_android_form
, *form_list
[0]);
1100 TEST_F(NativeBackendGnomeTest
, RemoveLoginsCreatedBetween
) {
1101 CheckRemoveLoginsBetween(CREATED
);
1104 TEST_F(NativeBackendGnomeTest
, RemoveLoginsSyncedBetween
) {
1105 CheckRemoveLoginsBetween(SYNCED
);
1108 // TODO(mdm): add more basic tests here at some point.