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/stl_util.h"
10 #include "base/strings/string_number_conversions.h"
11 #include "base/strings/string_util.h"
12 #include "base/strings/stringprintf.h"
13 #include "base/strings/utf_string_conversions.h"
14 #include "base/time/time.h"
15 #include "chrome/browser/password_manager/native_backend_gnome_x.h"
16 #include "chrome/test/base/testing_profile.h"
17 #include "components/autofill/core/common/password_form.h"
18 #include "components/password_manager/core/browser/psl_matching_helper.h"
19 #include "components/password_manager/core/common/password_manager_pref_names.h"
20 #include "content/public/test/test_browser_thread.h"
21 #include "testing/gtest/include/gtest/gtest.h"
23 using autofill::PasswordForm
;
24 using base::UTF8ToUTF16
;
25 using base::UTF16ToUTF8
;
26 using content::BrowserThread
;
27 using password_manager::PasswordStoreChange
;
28 using password_manager::PasswordStoreChangeList
;
32 // What follows is a very simple implementation of the subset of the GNOME
33 // Keyring API that we actually use. It gets substituted for the real one by
34 // MockGnomeKeyringLoader, which hooks into the facility normally used to load
35 // the GNOME Keyring library at runtime to avoid a static dependency on it.
37 struct MockKeyringItem
{
39 MockKeyringItem(const char* keyring
,
40 const std::string
& display_name
,
41 const std::string
& password
)
42 : keyring(keyring
? keyring
: "login"),
43 display_name(display_name
),
46 struct ItemAttribute
{
47 ItemAttribute() : type(UINT32
), value_uint32(0) {}
48 explicit ItemAttribute(uint32_t value
)
49 : type(UINT32
), value_uint32(value
) {}
50 explicit ItemAttribute(const std::string
& value
)
51 : type(STRING
), value_string(value
) {}
53 bool Equals(const ItemAttribute
& x
) const {
54 if (type
!= x
.type
) return false;
55 return (type
== STRING
) ? value_string
== x
.value_string
56 : value_uint32
== x
.value_uint32
;
59 enum Type
{ UINT32
, STRING
} type
;
60 uint32_t value_uint32
;
61 std::string value_string
;
64 typedef std::map
<std::string
, ItemAttribute
> attribute_map
;
65 typedef std::vector
<std::pair
<std::string
, ItemAttribute
> > attribute_query
;
67 bool Matches(const attribute_query
& query
) const {
68 // The real GNOME Keyring doesn't match empty queries.
69 if (query
.empty()) return false;
70 for (size_t i
= 0; i
< query
.size(); ++i
) {
71 attribute_map::const_iterator match
= attributes
.find(query
[i
].first
);
72 if (match
== attributes
.end()) return false;
73 if (!match
->second
.Equals(query
[i
].second
)) return false;
79 std::string display_name
;
82 attribute_map attributes
;
85 // The list of all keyring items we have stored.
86 std::vector
<MockKeyringItem
> mock_keyring_items
;
87 bool mock_keyring_reject_local_ids
= false;
89 bool IsStringAttribute(const GnomeKeyringPasswordSchema
* schema
,
90 const std::string
& name
) {
91 for (size_t i
= 0; schema
->attributes
[i
].name
; ++i
)
92 if (name
== schema
->attributes
[i
].name
)
93 return schema
->attributes
[i
].type
== GNOME_KEYRING_ATTRIBUTE_TYPE_STRING
;
94 NOTREACHED() << "Requested type of nonexistent attribute";
98 gboolean
mock_gnome_keyring_is_available() {
102 gpointer
mock_gnome_keyring_store_password(
103 const GnomeKeyringPasswordSchema
* schema
,
104 const gchar
* keyring
,
105 const gchar
* display_name
,
106 const gchar
* password
,
107 GnomeKeyringOperationDoneCallback callback
,
109 GDestroyNotify destroy_data
,
111 mock_keyring_items
.push_back(
112 MockKeyringItem(keyring
, display_name
, password
));
113 MockKeyringItem
* item
= &mock_keyring_items
.back();
114 const std::string keyring_desc
=
115 keyring
? base::StringPrintf("keyring %s", keyring
)
116 : std::string("default keyring");
117 VLOG(1) << "Adding item with origin " << display_name
118 << " to " << keyring_desc
;
120 va_start(ap
, destroy_data
);
122 while ((name
= va_arg(ap
, gchar
*))) {
123 if (IsStringAttribute(schema
, name
)) {
124 item
->attributes
[name
] =
125 MockKeyringItem::ItemAttribute(va_arg(ap
, gchar
*));
126 VLOG(1) << "Adding item attribute " << name
127 << ", value '" << item
->attributes
[name
].value_string
<< "'";
129 item
->attributes
[name
] =
130 MockKeyringItem::ItemAttribute(va_arg(ap
, uint32_t));
131 VLOG(1) << "Adding item attribute " << name
132 << ", value " << item
->attributes
[name
].value_uint32
;
136 // As a hack to ease testing migration, make it possible to reject the new
137 // format for the app string. This way we can add them easily to migrate.
138 if (mock_keyring_reject_local_ids
) {
139 MockKeyringItem::attribute_map::iterator it
=
140 item
->attributes
.find("application");
141 if (it
!= item
->attributes
.end() &&
142 it
->second
.type
== MockKeyringItem::ItemAttribute::STRING
&&
143 base::StringPiece(it
->second
.value_string
).starts_with("chrome-")) {
144 mock_keyring_items
.pop_back();
145 // GnomeKeyringResult, data
146 callback(GNOME_KEYRING_RESULT_IO_ERROR
, data
);
150 // GnomeKeyringResult, data
151 callback(GNOME_KEYRING_RESULT_OK
, data
);
155 gpointer
mock_gnome_keyring_delete_password(
156 const GnomeKeyringPasswordSchema
* schema
,
157 GnomeKeyringOperationDoneCallback callback
,
159 GDestroyNotify destroy_data
,
161 MockKeyringItem::attribute_query query
;
163 va_start(ap
, destroy_data
);
165 while ((name
= va_arg(ap
, gchar
*))) {
166 if (IsStringAttribute(schema
, name
)) {
167 query
.push_back(make_pair(std::string(name
),
168 MockKeyringItem::ItemAttribute(va_arg(ap
, gchar
*))));
169 VLOG(1) << "Querying with item attribute " << name
170 << ", value '" << query
.back().second
.value_string
<< "'";
172 query
.push_back(make_pair(std::string(name
),
173 MockKeyringItem::ItemAttribute(va_arg(ap
, uint32_t))));
174 VLOG(1) << "Querying with item attribute " << name
175 << ", value " << query
.back().second
.value_uint32
;
179 bool deleted
= false;
180 for (size_t i
= mock_keyring_items
.size(); i
> 0; --i
) {
181 const MockKeyringItem
* item
= &mock_keyring_items
[i
- 1];
182 if (item
->Matches(query
)) {
183 VLOG(1) << "Deleting item with origin " << item
->display_name
;
184 mock_keyring_items
.erase(mock_keyring_items
.begin() + (i
- 1));
188 // GnomeKeyringResult, data
189 callback(deleted
? GNOME_KEYRING_RESULT_OK
190 : GNOME_KEYRING_RESULT_NO_MATCH
, data
);
194 gpointer
mock_gnome_keyring_find_items(
195 GnomeKeyringItemType type
,
196 GnomeKeyringAttributeList
* attributes
,
197 GnomeKeyringOperationGetListCallback callback
,
199 GDestroyNotify destroy_data
) {
200 MockKeyringItem::attribute_query query
;
201 for (size_t i
= 0; i
< attributes
->len
; ++i
) {
202 GnomeKeyringAttribute attribute
=
203 g_array_index(attributes
, GnomeKeyringAttribute
, i
);
204 if (attribute
.type
== GNOME_KEYRING_ATTRIBUTE_TYPE_STRING
) {
206 make_pair(std::string(attribute
.name
),
207 MockKeyringItem::ItemAttribute(attribute
.value
.string
)));
208 VLOG(1) << "Querying with item attribute " << attribute
.name
209 << ", value '" << query
.back().second
.value_string
<< "'";
212 make_pair(std::string(attribute
.name
),
213 MockKeyringItem::ItemAttribute(attribute
.value
.integer
)));
214 VLOG(1) << "Querying with item attribute " << attribute
.name
<< ", value "
215 << query
.back().second
.value_uint32
;
218 // Find matches and add them to a list of results.
219 GList
* results
= NULL
;
220 for (size_t i
= 0; i
< mock_keyring_items
.size(); ++i
) {
221 const MockKeyringItem
* item
= &mock_keyring_items
[i
];
222 if (item
->Matches(query
)) {
223 GnomeKeyringFound
* found
= new GnomeKeyringFound
;
224 found
->keyring
= strdup(item
->keyring
.c_str());
226 found
->attributes
= gnome_keyring_attribute_list_new();
227 for (MockKeyringItem::attribute_map::const_iterator it
=
228 item
->attributes
.begin();
229 it
!= item
->attributes
.end();
231 if (it
->second
.type
== MockKeyringItem::ItemAttribute::STRING
) {
232 gnome_keyring_attribute_list_append_string(
233 found
->attributes
, it
->first
.c_str(),
234 it
->second
.value_string
.c_str());
236 gnome_keyring_attribute_list_append_uint32(
237 found
->attributes
, it
->first
.c_str(),
238 it
->second
.value_uint32
);
241 found
->secret
= strdup(item
->password
.c_str());
242 results
= g_list_prepend(results
, found
);
245 // GnomeKeyringResult, GList*, data
246 callback(results
? GNOME_KEYRING_RESULT_OK
247 : GNOME_KEYRING_RESULT_NO_MATCH
, results
, data
);
248 // Now free the list of results.
249 GList
* element
= g_list_first(results
);
251 GnomeKeyringFound
* found
= static_cast<GnomeKeyringFound
*>(element
->data
);
252 free(found
->keyring
);
253 gnome_keyring_attribute_list_free(found
->attributes
);
256 element
= g_list_next(element
);
258 g_list_free(results
);
262 const gchar
* mock_gnome_keyring_result_to_message(GnomeKeyringResult res
) {
263 return "mock keyring simulating failure";
266 // Inherit to get access to protected fields.
267 class MockGnomeKeyringLoader
: public GnomeKeyringLoader
{
269 static bool LoadMockGnomeKeyring() {
270 if (!LoadGnomeKeyring())
272 #define GNOME_KEYRING_ASSIGN_POINTER(name) \
273 gnome_keyring_##name = &mock_gnome_keyring_##name;
274 GNOME_KEYRING_FOR_EACH_MOCKED_FUNC(GNOME_KEYRING_ASSIGN_POINTER
)
275 #undef GNOME_KEYRING_ASSIGN_POINTER
276 keyring_loaded
= true;
277 // Reset the state of the mock library.
278 mock_keyring_items
.clear();
279 mock_keyring_reject_local_ids
= false;
284 void CheckPasswordChanges(const PasswordStoreChangeList
& expected_list
,
285 const PasswordStoreChangeList
& actual_list
) {
286 ASSERT_EQ(expected_list
.size(), actual_list
.size());
287 for (size_t i
= 0; i
< expected_list
.size(); ++i
) {
288 EXPECT_EQ(expected_list
[i
].type(), actual_list
[i
].type());
289 const PasswordForm
& expected
= expected_list
[i
].form();
290 const PasswordForm
& actual
= actual_list
[i
].form();
292 EXPECT_EQ(expected
.origin
, actual
.origin
);
293 EXPECT_EQ(expected
.password_value
, actual
.password_value
);
294 EXPECT_EQ(expected
.action
, actual
.action
);
295 EXPECT_EQ(expected
.username_element
, actual
.username_element
);
296 EXPECT_EQ(expected
.username_value
, actual
.username_value
);
297 EXPECT_EQ(expected
.password_element
, actual
.password_element
);
298 EXPECT_EQ(expected
.submit_element
, actual
.submit_element
);
299 EXPECT_EQ(expected
.signon_realm
, actual
.signon_realm
);
300 EXPECT_EQ(expected
.ssl_valid
, actual
.ssl_valid
);
301 EXPECT_EQ(expected
.preferred
, actual
.preferred
);
302 // We don't check the date created. It varies due to bug in the
303 // serialization. Integer seconds are saved instead of microseconds.
304 EXPECT_EQ(expected
.blacklisted_by_user
, actual
.blacklisted_by_user
);
305 EXPECT_EQ(expected
.type
, actual
.type
);
306 EXPECT_EQ(expected
.times_used
, actual
.times_used
);
307 EXPECT_EQ(expected
.scheme
, actual
.scheme
);
308 EXPECT_EQ(expected
.date_synced
, actual
.date_synced
);
309 EXPECT_EQ(expected
.display_name
, actual
.display_name
);
310 EXPECT_EQ(expected
.avatar_url
, actual
.avatar_url
);
311 EXPECT_EQ(expected
.federation_url
, actual
.federation_url
);
312 EXPECT_EQ(expected
.is_zero_click
, actual
.is_zero_click
);
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 virtual void SetUp() {
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_
.is_zero_click
= true;
362 form_facebook_
.origin
= GURL("http://www.facebook.com/");
363 form_facebook_
.action
= GURL("http://www.facebook.com/login");
364 form_facebook_
.username_element
= UTF8ToUTF16("user");
365 form_facebook_
.username_value
= UTF8ToUTF16("a");
366 form_facebook_
.password_element
= UTF8ToUTF16("password");
367 form_facebook_
.password_value
= UTF8ToUTF16("b");
368 form_facebook_
.submit_element
= UTF8ToUTF16("submit");
369 form_facebook_
.signon_realm
= "http://www.facebook.com/";
370 form_facebook_
.date_created
= base::Time::Now();
371 form_facebook_
.date_synced
= base::Time::Now();
372 form_facebook_
.display_name
= UTF8ToUTF16("Joe Schmoe");
373 form_facebook_
.avatar_url
= GURL("http://www.facebook.com/avatar");
374 form_facebook_
.federation_url
= GURL("http://www.facebook.com/federation");
375 form_facebook_
.is_zero_click
= true;
377 form_isc_
.origin
= GURL("http://www.isc.org/");
378 form_isc_
.action
= GURL("http://www.isc.org/auth");
379 form_isc_
.username_element
= UTF8ToUTF16("id");
380 form_isc_
.username_value
= UTF8ToUTF16("janedoe");
381 form_isc_
.password_element
= UTF8ToUTF16("passwd");
382 form_isc_
.password_value
= UTF8ToUTF16("ihazabukkit");
383 form_isc_
.submit_element
= UTF8ToUTF16("login");
384 form_isc_
.signon_realm
= "http://www.isc.org/";
385 form_isc_
.date_created
= base::Time::Now();
386 form_isc_
.date_synced
= base::Time::Now();
388 other_auth_
.origin
= GURL("http://www.example.com/");
389 other_auth_
.username_value
= UTF8ToUTF16("username");
390 other_auth_
.password_value
= UTF8ToUTF16("pass");
391 other_auth_
.signon_realm
= "http://www.example.com/Realm";
392 other_auth_
.date_created
= base::Time::Now();
393 other_auth_
.date_synced
= base::Time::Now();
396 virtual void TearDown() {
397 base::MessageLoop::current()->PostTask(FROM_HERE
,
398 base::MessageLoop::QuitClosure());
399 base::MessageLoop::current()->Run();
403 void RunBothThreads() {
404 // First we post a message to the DB thread that will run after all other
405 // messages that have been posted to the DB thread (we don't expect more
406 // to be posted), which posts a message to the UI thread to quit the loop.
407 // That way we can run both loops and be sure that the UI thread loop will
408 // quit so we can get on with the rest of the test.
409 BrowserThread::PostTask(BrowserThread::DB
, FROM_HERE
,
410 base::Bind(&PostQuitTask
, &message_loop_
));
411 base::MessageLoop::current()->Run();
414 static void PostQuitTask(base::MessageLoop
* loop
) {
415 loop
->PostTask(FROM_HERE
, base::MessageLoop::QuitClosure());
418 void CheckUint32Attribute(const MockKeyringItem
* item
,
419 const std::string
& attribute
,
421 MockKeyringItem::attribute_map::const_iterator it
=
422 item
->attributes
.find(attribute
);
423 EXPECT_NE(item
->attributes
.end(), it
);
424 if (it
!= item
->attributes
.end()) {
425 EXPECT_EQ(MockKeyringItem::ItemAttribute::UINT32
, it
->second
.type
);
426 EXPECT_EQ(value
, it
->second
.value_uint32
);
430 void CheckStringAttribute(const MockKeyringItem
* item
,
431 const std::string
& attribute
,
432 const std::string
& value
) {
433 MockKeyringItem::attribute_map::const_iterator it
=
434 item
->attributes
.find(attribute
);
435 EXPECT_NE(item
->attributes
.end(), it
);
436 if (it
!= item
->attributes
.end()) {
437 EXPECT_EQ(MockKeyringItem::ItemAttribute::STRING
, it
->second
.type
);
438 EXPECT_EQ(value
, it
->second
.value_string
);
442 void CheckMockKeyringItem(const MockKeyringItem
* item
,
443 const PasswordForm
& form
,
444 const std::string
& app_string
) {
445 // We always add items to the login keyring.
446 EXPECT_EQ("login", item
->keyring
);
447 EXPECT_EQ(form
.origin
.spec(), item
->display_name
);
448 EXPECT_EQ(UTF16ToUTF8(form
.password_value
), item
->password
);
449 EXPECT_EQ(20u, item
->attributes
.size());
450 CheckStringAttribute(item
, "origin_url", form
.origin
.spec());
451 CheckStringAttribute(item
, "action_url", form
.action
.spec());
452 CheckStringAttribute(item
, "username_element",
453 UTF16ToUTF8(form
.username_element
));
454 CheckStringAttribute(item
, "username_value",
455 UTF16ToUTF8(form
.username_value
));
456 CheckStringAttribute(item
, "password_element",
457 UTF16ToUTF8(form
.password_element
));
458 CheckStringAttribute(item
, "submit_element",
459 UTF16ToUTF8(form
.submit_element
));
460 CheckStringAttribute(item
, "signon_realm", form
.signon_realm
);
461 CheckUint32Attribute(item
, "ssl_valid", form
.ssl_valid
);
462 CheckUint32Attribute(item
, "preferred", form
.preferred
);
463 // We don't check the date created. It varies.
464 CheckUint32Attribute(item
, "blacklisted_by_user", form
.blacklisted_by_user
);
465 CheckUint32Attribute(item
, "type", form
.type
);
466 CheckUint32Attribute(item
, "times_used", form
.times_used
);
467 CheckUint32Attribute(item
, "scheme", form
.scheme
);
468 CheckStringAttribute(item
, "date_synced", base::Int64ToString(
469 form
.date_synced
.ToInternalValue()));
470 CheckStringAttribute(item
, "display_name", UTF16ToUTF8(form
.display_name
));
471 CheckStringAttribute(item
, "avatar_url", form
.avatar_url
.spec());
472 CheckStringAttribute(item
, "federation_url", form
.federation_url
.spec());
473 CheckUint32Attribute(item
, "is_zero_click", form
.is_zero_click
);
474 CheckStringAttribute(item
, "application", app_string
);
477 // Saves |credentials| and then gets logins matching |url| and |scheme|.
478 // Returns true when something is found, and in such case copies the result to
479 // |result| when |result| is not NULL. (Note that there can be max. 1 result,
480 // derived from |credentials|.)
481 bool CheckCredentialAvailability(const PasswordForm
& credentials
,
483 const PasswordForm::Scheme
& scheme
,
484 PasswordForm
* result
) {
485 NativeBackendGnome
backend(321);
488 BrowserThread::PostTask(
491 base::Bind(base::IgnoreResult(&NativeBackendGnome::AddLogin
),
492 base::Unretained(&backend
),
495 PasswordForm target_form
;
496 target_form
.origin
= url
;
497 target_form
.signon_realm
= url
.spec();
498 if (scheme
!= PasswordForm::SCHEME_HTML
) {
499 // For non-HTML forms, the realm used for authentication
500 // (http://tools.ietf.org/html/rfc1945#section-10.2) is appended to the
501 // signon_realm. Just use a default value for now.
502 target_form
.signon_realm
.append("Realm");
503 target_form
.scheme
= scheme
;
505 std::vector
<PasswordForm
*> form_list
;
506 BrowserThread::PostTask(
509 base::Bind(base::IgnoreResult(&NativeBackendGnome::GetLogins
),
510 base::Unretained(&backend
),
516 EXPECT_EQ(1u, mock_keyring_items
.size());
517 if (mock_keyring_items
.size() > 0)
518 CheckMockKeyringItem(&mock_keyring_items
[0], credentials
, "chrome-321");
519 mock_keyring_items
.clear();
521 if (form_list
.empty())
523 EXPECT_EQ(1u, form_list
.size());
525 *result
= *form_list
[0];
526 STLDeleteElements(&form_list
);
530 // Test that updating does not use PSL matching: Add a www.facebook.com
531 // password, then use PSL matching to get a copy of it for m.facebook.com, and
532 // add that copy as well. Now update the www.facebook.com password -- the
533 // m.facebook.com password should not get updated. Depending on the argument,
534 // the credential update is done via UpdateLogin or AddLogin.
535 void CheckPSLUpdate(UpdateType update_type
) {
536 NativeBackendGnome
backend(321);
539 // Add |form_facebook_| to saved logins.
540 BrowserThread::PostTask(
543 base::Bind(base::IgnoreResult(&NativeBackendGnome::AddLogin
),
544 base::Unretained(&backend
),
547 // Get the PSL-matched copy of the saved login for m.facebook.
548 const GURL
kMobileURL("http://m.facebook.com/");
549 PasswordForm m_facebook_lookup
;
550 m_facebook_lookup
.origin
= kMobileURL
;
551 m_facebook_lookup
.signon_realm
= kMobileURL
.spec();
552 std::vector
<PasswordForm
*> form_list
;
553 BrowserThread::PostTask(
556 base::Bind(base::IgnoreResult(&NativeBackendGnome::GetLogins
),
557 base::Unretained(&backend
),
561 EXPECT_EQ(1u, mock_keyring_items
.size());
562 EXPECT_EQ(1u, form_list
.size());
563 PasswordForm m_facebook
= *form_list
[0];
564 STLDeleteElements(&form_list
);
565 EXPECT_EQ(kMobileURL
, m_facebook
.origin
);
566 EXPECT_EQ(kMobileURL
.spec(), m_facebook
.signon_realm
);
568 // Add the PSL-matched copy to saved logins.
569 BrowserThread::PostTask(
572 base::Bind(base::IgnoreResult(&NativeBackendGnome::AddLogin
),
573 base::Unretained(&backend
),
576 EXPECT_EQ(2u, mock_keyring_items
.size());
578 // Update www.facebook.com login.
579 PasswordForm
new_facebook(form_facebook_
);
580 const base::string16
kOldPassword(form_facebook_
.password_value
);
581 const base::string16
kNewPassword(UTF8ToUTF16("new_b"));
582 EXPECT_NE(kOldPassword
, kNewPassword
);
583 new_facebook
.password_value
= kNewPassword
;
584 switch (update_type
) {
585 case UPDATE_BY_UPDATELOGIN
:
586 BrowserThread::PostTask(
589 base::Bind(base::IgnoreResult(&NativeBackendGnome::UpdateLogin
),
590 base::Unretained(&backend
),
592 base::Owned(new PasswordStoreChangeList
)));
594 case UPDATE_BY_ADDLOGIN
:
595 BrowserThread::PostTask(
598 base::Bind(base::IgnoreResult(&NativeBackendGnome::AddLogin
),
599 base::Unretained(&backend
),
605 EXPECT_EQ(2u, mock_keyring_items
.size());
607 // Check that m.facebook.com login was not modified by the update.
608 BrowserThread::PostTask(
611 base::Bind(base::IgnoreResult(&NativeBackendGnome::GetLogins
),
612 base::Unretained(&backend
),
616 // There should be two results -- the exact one, and the PSL-matched one.
617 EXPECT_EQ(2u, form_list
.size());
618 size_t index_non_psl
= 0;
619 if (!form_list
[index_non_psl
]->original_signon_realm
.empty())
621 EXPECT_EQ(kMobileURL
, form_list
[index_non_psl
]->origin
);
622 EXPECT_EQ(kMobileURL
.spec(), form_list
[index_non_psl
]->signon_realm
);
623 EXPECT_EQ(kOldPassword
, form_list
[index_non_psl
]->password_value
);
624 STLDeleteElements(&form_list
);
626 // Check that www.facebook.com login was modified by the update.
627 BrowserThread::PostTask(
630 base::Bind(base::IgnoreResult(&NativeBackendGnome::GetLogins
),
631 base::Unretained(&backend
),
635 // There should be two results -- the exact one, and the PSL-matched one.
636 EXPECT_EQ(2u, form_list
.size());
638 if (!form_list
[index_non_psl
]->original_signon_realm
.empty())
640 EXPECT_EQ(form_facebook_
.origin
, form_list
[index_non_psl
]->origin
);
641 EXPECT_EQ(form_facebook_
.signon_realm
,
642 form_list
[index_non_psl
]->signon_realm
);
643 EXPECT_EQ(kNewPassword
, form_list
[index_non_psl
]->password_value
);
644 STLDeleteElements(&form_list
);
647 void CheckMatchingWithScheme(const PasswordForm::Scheme
& scheme
) {
648 other_auth_
.scheme
= scheme
;
650 // Don't match a non-HTML form with an HTML form.
651 EXPECT_FALSE(CheckCredentialAvailability(
652 other_auth_
, GURL("http://www.example.com"),
653 PasswordForm::SCHEME_HTML
, NULL
));
654 // Don't match an HTML form with non-HTML auth form.
655 EXPECT_FALSE(CheckCredentialAvailability(
656 form_google_
, GURL("http://www.google.com/"), scheme
, NULL
));
657 // Don't match two different non-HTML auth forms with different origin.
658 EXPECT_FALSE(CheckCredentialAvailability(
659 other_auth_
, GURL("http://first.example.com"), scheme
, NULL
));
660 // Do match non-HTML forms from the same origin.
661 EXPECT_TRUE(CheckCredentialAvailability(
662 other_auth_
, GURL("http://www.example.com/"), scheme
, NULL
));
665 void CheckRemoveLoginsBetween(RemoveBetweenMethod date_to_test
) {
666 NativeBackendGnome
backend(42);
669 form_google_
.date_synced
= base::Time();
670 form_isc_
.date_synced
= base::Time();
671 form_google_
.date_created
= base::Time();
672 form_isc_
.date_created
= base::Time();
673 base::Time now
= base::Time::Now();
674 base::Time next_day
= now
+ base::TimeDelta::FromDays(1);
675 if (date_to_test
== CREATED
) {
676 // crbug/374132. Remove the next line once it's fixed.
677 next_day
= base::Time::FromTimeT(next_day
.ToTimeT());
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 std::vector
<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());
789 STLDeleteElements(&form_list
);
791 EXPECT_EQ(1u, mock_keyring_items
.size());
792 if (mock_keyring_items
.size() > 0)
793 CheckMockKeyringItem(&mock_keyring_items
[0], form_google_
, "chrome-42");
796 // Save a password for www.facebook.com and see it suggested for m.facebook.com.
797 TEST_F(NativeBackendGnomeTest
, PSLMatchingPositive
) {
799 const GURL
kMobileURL("http://m.facebook.com/");
800 EXPECT_TRUE(CheckCredentialAvailability(
801 form_facebook_
, kMobileURL
, PasswordForm::SCHEME_HTML
, &result
));
802 EXPECT_EQ(kMobileURL
, result
.origin
);
803 EXPECT_EQ(kMobileURL
.spec(), result
.signon_realm
);
806 // Save a password for www.facebook.com and see it not suggested for
808 TEST_F(NativeBackendGnomeTest
, PSLMatchingNegativeDomainMismatch
) {
809 EXPECT_FALSE(CheckCredentialAvailability(
810 form_facebook_
, GURL("http://m-facebook.com/"),
811 PasswordForm::SCHEME_HTML
, NULL
));
814 // Test PSL matching is off for domains excluded from it.
815 TEST_F(NativeBackendGnomeTest
, PSLMatchingDisabledDomains
) {
816 EXPECT_FALSE(CheckCredentialAvailability(
817 form_google_
, GURL("http://one.google.com/"),
818 PasswordForm::SCHEME_HTML
, NULL
));
821 // Make sure PSL matches aren't available for non-HTML forms.
822 TEST_F(NativeBackendGnomeTest
, PSLMatchingDisabledForNonHTMLForms
) {
823 CheckMatchingWithScheme(PasswordForm::SCHEME_BASIC
);
824 CheckMatchingWithScheme(PasswordForm::SCHEME_DIGEST
);
825 CheckMatchingWithScheme(PasswordForm::SCHEME_OTHER
);
829 TEST_F(NativeBackendGnomeTest
, PSLUpdatingStrictUpdateLogin
) {
830 CheckPSLUpdate(UPDATE_BY_UPDATELOGIN
);
833 TEST_F(NativeBackendGnomeTest
, PSLUpdatingStrictAddLogin
) {
834 // TODO(vabr): if AddLogin becomes no longer valid for existing logins, then
835 // just delete this test.
836 CheckPSLUpdate(UPDATE_BY_ADDLOGIN
);
839 TEST_F(NativeBackendGnomeTest
, BasicUpdateLogin
) {
840 NativeBackendGnome
backend(42);
843 // First add google login.
844 BrowserThread::PostTask(
845 BrowserThread::DB
, FROM_HERE
,
846 base::Bind(base::IgnoreResult(&NativeBackendGnome::AddLogin
),
847 base::Unretained(&backend
), form_google_
));
851 PasswordForm
new_form_google(form_google_
);
852 new_form_google
.times_used
= 1;
853 new_form_google
.action
= GURL("http://www.google.com/different/login");
855 EXPECT_EQ(1u, mock_keyring_items
.size());
856 if (mock_keyring_items
.size() > 0)
857 CheckMockKeyringItem(&mock_keyring_items
[0], form_google_
, "chrome-42");
860 PasswordStoreChangeList changes
;
861 BrowserThread::PostTask(
862 BrowserThread::DB
, FROM_HERE
,
863 base::Bind(base::IgnoreResult(&NativeBackendGnome::UpdateLogin
),
864 base::Unretained(&backend
),
866 base::Unretained(&changes
)));
870 ASSERT_EQ(1u, changes
.size());
871 EXPECT_EQ(PasswordStoreChange::UPDATE
, changes
.front().type());
872 EXPECT_EQ(new_form_google
, changes
.front().form());
873 EXPECT_EQ(1u, mock_keyring_items
.size());
874 if (mock_keyring_items
.size() > 0)
875 CheckMockKeyringItem(&mock_keyring_items
[0], new_form_google
, "chrome-42");
878 TEST_F(NativeBackendGnomeTest
, BasicRemoveLogin
) {
879 NativeBackendGnome
backend(42);
882 BrowserThread::PostTask(
883 BrowserThread::DB
, FROM_HERE
,
884 base::Bind(base::IgnoreResult(&NativeBackendGnome::AddLogin
),
885 base::Unretained(&backend
), form_google_
));
889 EXPECT_EQ(1u, mock_keyring_items
.size());
890 if (mock_keyring_items
.size() > 0)
891 CheckMockKeyringItem(&mock_keyring_items
[0], form_google_
, "chrome-42");
893 BrowserThread::PostTask(
894 BrowserThread::DB
, FROM_HERE
,
895 base::Bind(base::IgnoreResult(&NativeBackendGnome::RemoveLogin
),
896 base::Unretained(&backend
), form_google_
));
900 EXPECT_EQ(0u, mock_keyring_items
.size());
903 // Verify fix for http://crbug.com/408783.
904 TEST_F(NativeBackendGnomeTest
, RemoveLoginActionMismatch
) {
905 NativeBackendGnome
backend(42);
908 BrowserThread::PostTask(
909 BrowserThread::DB
, FROM_HERE
,
910 base::Bind(base::IgnoreResult(&NativeBackendGnome::AddLogin
),
911 base::Unretained(&backend
), form_google_
));
915 EXPECT_EQ(1u, mock_keyring_items
.size());
916 if (mock_keyring_items
.size() > 0)
917 CheckMockKeyringItem(&mock_keyring_items
[0], form_google_
, "chrome-42");
919 // Action url match not required for removal.
920 form_google_
.action
= GURL("https://some.other.url.com/path");
922 BrowserThread::PostTask(
923 BrowserThread::DB
, FROM_HERE
,
924 base::Bind(base::IgnoreResult(&NativeBackendGnome::RemoveLogin
),
925 base::Unretained(&backend
), form_google_
));
929 EXPECT_EQ(0u, mock_keyring_items
.size());
932 TEST_F(NativeBackendGnomeTest
, RemoveNonexistentLogin
) {
933 NativeBackendGnome
backend(42);
936 // First add an unrelated login.
937 BrowserThread::PostTask(
938 BrowserThread::DB
, FROM_HERE
,
939 base::Bind(base::IgnoreResult(&NativeBackendGnome::AddLogin
),
940 base::Unretained(&backend
), form_google_
));
944 EXPECT_EQ(1u, mock_keyring_items
.size());
945 if (mock_keyring_items
.size() > 0)
946 CheckMockKeyringItem(&mock_keyring_items
[0], form_google_
, "chrome-42");
948 // Attempt to remove a login that doesn't exist.
949 BrowserThread::PostTask(
950 BrowserThread::DB
, FROM_HERE
,
951 base::Bind(base::IgnoreResult(&NativeBackendGnome::RemoveLogin
),
952 base::Unretained(&backend
), form_isc_
));
954 // Make sure we can still get the first form back.
955 std::vector
<PasswordForm
*> form_list
;
956 BrowserThread::PostTask(
957 BrowserThread::DB
, FROM_HERE
,
959 base::IgnoreResult(&NativeBackendGnome::GetAutofillableLogins
),
960 base::Unretained(&backend
), &form_list
));
964 // Quick check that we got something back.
965 EXPECT_EQ(1u, form_list
.size());
966 STLDeleteElements(&form_list
);
968 EXPECT_EQ(1u, mock_keyring_items
.size());
969 if (mock_keyring_items
.size() > 0)
970 CheckMockKeyringItem(&mock_keyring_items
[0], form_google_
, "chrome-42");
973 TEST_F(NativeBackendGnomeTest
, UpdateNonexistentLogin
) {
974 NativeBackendGnome
backend(42);
977 // First add an unrelated login.
978 BrowserThread::PostTask(
979 BrowserThread::DB
, FROM_HERE
,
980 base::Bind(base::IgnoreResult(&NativeBackendGnome::AddLogin
),
981 base::Unretained(&backend
), form_google_
));
985 EXPECT_EQ(1u, mock_keyring_items
.size());
986 if (mock_keyring_items
.size() > 0)
987 CheckMockKeyringItem(&mock_keyring_items
[0], form_google_
, "chrome-42");
989 // Attempt to update a login that doesn't exist.
990 PasswordStoreChangeList changes
;
991 BrowserThread::PostTask(
992 BrowserThread::DB
, FROM_HERE
,
993 base::Bind(base::IgnoreResult(&NativeBackendGnome::UpdateLogin
),
994 base::Unretained(&backend
),
996 base::Unretained(&changes
)));
1000 EXPECT_EQ(PasswordStoreChangeList(), changes
);
1001 EXPECT_EQ(1u, mock_keyring_items
.size());
1002 if (mock_keyring_items
.size() > 0)
1003 CheckMockKeyringItem(&mock_keyring_items
[0], form_google_
, "chrome-42");
1006 TEST_F(NativeBackendGnomeTest
, AddDuplicateLogin
) {
1007 NativeBackendGnome
backend(42);
1010 PasswordStoreChangeList changes
;
1011 changes
.push_back(PasswordStoreChange(PasswordStoreChange::ADD
,
1013 BrowserThread::PostTaskAndReplyWithResult(
1014 BrowserThread::DB
, FROM_HERE
,
1015 base::Bind(&NativeBackendGnome::AddLogin
,
1016 base::Unretained(&backend
), form_google_
),
1017 base::Bind(&CheckPasswordChanges
, changes
));
1020 changes
.push_back(PasswordStoreChange(PasswordStoreChange::REMOVE
,
1022 form_google_
.times_used
++;
1023 changes
.push_back(PasswordStoreChange(PasswordStoreChange::ADD
,
1026 BrowserThread::PostTaskAndReplyWithResult(
1027 BrowserThread::DB
, FROM_HERE
,
1028 base::Bind(&NativeBackendGnome::AddLogin
,
1029 base::Unretained(&backend
), form_google_
),
1030 base::Bind(&CheckPasswordChanges
, changes
));
1034 EXPECT_EQ(1u, mock_keyring_items
.size());
1035 if (mock_keyring_items
.size() > 0)
1036 CheckMockKeyringItem(&mock_keyring_items
[0], form_google_
, "chrome-42");
1039 TEST_F(NativeBackendGnomeTest
, ListLoginsAppends
) {
1040 NativeBackendGnome
backend(42);
1043 BrowserThread::PostTask(
1044 BrowserThread::DB
, FROM_HERE
,
1045 base::Bind(base::IgnoreResult(&NativeBackendGnome::AddLogin
),
1046 base::Unretained(&backend
), form_google_
));
1048 // Send the same request twice with the same list both times.
1049 std::vector
<PasswordForm
*> form_list
;
1050 BrowserThread::PostTask(
1051 BrowserThread::DB
, FROM_HERE
,
1053 base::IgnoreResult(&NativeBackendGnome::GetAutofillableLogins
),
1054 base::Unretained(&backend
), &form_list
));
1055 BrowserThread::PostTask(
1056 BrowserThread::DB
, FROM_HERE
,
1058 base::IgnoreResult(&NativeBackendGnome::GetAutofillableLogins
),
1059 base::Unretained(&backend
), &form_list
));
1063 // Quick check that we got two results back.
1064 EXPECT_EQ(2u, form_list
.size());
1065 STLDeleteElements(&form_list
);
1067 EXPECT_EQ(1u, mock_keyring_items
.size());
1068 if (mock_keyring_items
.size() > 0)
1069 CheckMockKeyringItem(&mock_keyring_items
[0], form_google_
, "chrome-42");
1072 TEST_F(NativeBackendGnomeTest
, RemoveLoginsCreatedBetween
) {
1073 CheckRemoveLoginsBetween(CREATED
);
1076 TEST_F(NativeBackendGnomeTest
, RemoveLoginsSyncedBetween
) {
1077 CheckRemoveLoginsBetween(SYNCED
);
1080 // TODO(mdm): add more basic tests here at some point.