Adding instrumentation to locate the source of jankiness
[chromium-blink-merge.git] / chrome / browser / password_manager / native_backend_gnome_x_unittest.cc
blob723d4947b3ffe1ed819aa2b3e5db7d7d80b2e76f
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.
5 #include <stdarg.h>
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;
30 namespace {
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 {
38 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),
44 password(password) {}
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;
75 return true;
78 std::string keyring;
79 std::string display_name;
80 std::string password;
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";
95 return false;
98 gboolean mock_gnome_keyring_is_available() {
99 return true;
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,
108 gpointer data,
109 GDestroyNotify destroy_data,
110 ...) {
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;
119 va_list ap;
120 va_start(ap, destroy_data);
121 char* name;
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 << "'";
128 } else {
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;
135 va_end(ap);
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);
147 return NULL;
150 // GnomeKeyringResult, data
151 callback(GNOME_KEYRING_RESULT_OK, data);
152 return NULL;
155 gpointer mock_gnome_keyring_delete_password(
156 const GnomeKeyringPasswordSchema* schema,
157 GnomeKeyringOperationDoneCallback callback,
158 gpointer data,
159 GDestroyNotify destroy_data,
160 ...) {
161 MockKeyringItem::attribute_query query;
162 va_list ap;
163 va_start(ap, destroy_data);
164 char* name;
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 << "'";
171 } else {
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;
178 va_end(ap);
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));
185 deleted = true;
188 // GnomeKeyringResult, data
189 callback(deleted ? GNOME_KEYRING_RESULT_OK
190 : GNOME_KEYRING_RESULT_NO_MATCH, data);
191 return NULL;
194 gpointer mock_gnome_keyring_find_items(
195 GnomeKeyringItemType type,
196 GnomeKeyringAttributeList* attributes,
197 GnomeKeyringOperationGetListCallback callback,
198 gpointer data,
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) {
205 query.push_back(
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 << "'";
210 } else {
211 query.push_back(
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());
225 found->item_id = i;
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();
230 ++it) {
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());
235 } else {
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);
250 while (element) {
251 GnomeKeyringFound* found = static_cast<GnomeKeyringFound*>(element->data);
252 free(found->keyring);
253 gnome_keyring_attribute_list_free(found->attributes);
254 free(found->secret);
255 delete found;
256 element = g_list_next(element);
258 g_list_free(results);
259 return NULL;
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 {
268 public:
269 static bool LoadMockGnomeKeyring() {
270 if (!LoadGnomeKeyring())
271 return false;
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;
280 return true;
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,
318 bool result) {
319 EXPECT_TRUE(result);
320 CheckPasswordChanges(*expected, *actual);
323 } // anonymous namespace
325 class NativeBackendGnomeTest : public testing::Test {
326 protected:
327 enum UpdateType { // Used in CheckPSLUpdate().
328 UPDATE_BY_UPDATELOGIN,
329 UPDATE_BY_ADDLOGIN,
331 enum RemoveBetweenMethod { // Used in CheckRemoveLoginsBetween().
332 CREATED,
333 SYNCED,
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();
400 db_thread_.Stop();
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,
420 uint32_t value) {
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,
482 const GURL& url,
483 const PasswordForm::Scheme& scheme,
484 PasswordForm* result) {
485 NativeBackendGnome backend(321);
486 backend.Init();
488 BrowserThread::PostTask(
489 BrowserThread::DB,
490 FROM_HERE,
491 base::Bind(base::IgnoreResult(&NativeBackendGnome::AddLogin),
492 base::Unretained(&backend),
493 credentials));
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(
507 BrowserThread::DB,
508 FROM_HERE,
509 base::Bind(base::IgnoreResult(&NativeBackendGnome::GetLogins),
510 base::Unretained(&backend),
511 target_form,
512 &form_list));
514 RunBothThreads();
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())
522 return false;
523 EXPECT_EQ(1u, form_list.size());
524 if (result)
525 *result = *form_list[0];
526 STLDeleteElements(&form_list);
527 return true;
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);
537 backend.Init();
539 // Add |form_facebook_| to saved logins.
540 BrowserThread::PostTask(
541 BrowserThread::DB,
542 FROM_HERE,
543 base::Bind(base::IgnoreResult(&NativeBackendGnome::AddLogin),
544 base::Unretained(&backend),
545 form_facebook_));
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(
554 BrowserThread::DB,
555 FROM_HERE,
556 base::Bind(base::IgnoreResult(&NativeBackendGnome::GetLogins),
557 base::Unretained(&backend),
558 m_facebook_lookup,
559 &form_list));
560 RunBothThreads();
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(
570 BrowserThread::DB,
571 FROM_HERE,
572 base::Bind(base::IgnoreResult(&NativeBackendGnome::AddLogin),
573 base::Unretained(&backend),
574 m_facebook));
575 RunBothThreads();
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(
587 BrowserThread::DB,
588 FROM_HERE,
589 base::Bind(base::IgnoreResult(&NativeBackendGnome::UpdateLogin),
590 base::Unretained(&backend),
591 new_facebook,
592 base::Owned(new PasswordStoreChangeList)));
593 break;
594 case UPDATE_BY_ADDLOGIN:
595 BrowserThread::PostTask(
596 BrowserThread::DB,
597 FROM_HERE,
598 base::Bind(base::IgnoreResult(&NativeBackendGnome::AddLogin),
599 base::Unretained(&backend),
600 new_facebook));
601 break;
604 RunBothThreads();
605 EXPECT_EQ(2u, mock_keyring_items.size());
607 // Check that m.facebook.com login was not modified by the update.
608 BrowserThread::PostTask(
609 BrowserThread::DB,
610 FROM_HERE,
611 base::Bind(base::IgnoreResult(&NativeBackendGnome::GetLogins),
612 base::Unretained(&backend),
613 m_facebook_lookup,
614 &form_list));
615 RunBothThreads();
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())
620 index_non_psl = 1;
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(
628 BrowserThread::DB,
629 FROM_HERE,
630 base::Bind(base::IgnoreResult(&NativeBackendGnome::GetLogins),
631 base::Unretained(&backend),
632 form_facebook_,
633 &form_list));
634 RunBothThreads();
635 // There should be two results -- the exact one, and the PSL-matched one.
636 EXPECT_EQ(2u, form_list.size());
637 index_non_psl = 0;
638 if (!form_list[index_non_psl]->original_signon_realm.empty())
639 index_non_psl = 1;
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);
667 backend.Init();
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;
680 } else {
681 form_google_.date_synced = now;
682 form_isc_.date_synced = next_day;
685 BrowserThread::PostTask(
686 BrowserThread::DB,
687 FROM_HERE,
688 base::Bind(base::IgnoreResult(&NativeBackendGnome::AddLogin),
689 base::Unretained(&backend),
690 form_google_));
691 BrowserThread::PostTask(
692 BrowserThread::DB,
693 FROM_HERE,
694 base::Bind(base::IgnoreResult(&NativeBackendGnome::AddLogin),
695 base::Unretained(&backend),
696 form_isc_));
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(
708 BrowserThread::DB,
709 FROM_HERE,
710 base::Bind(method,
711 base::Unretained(&backend),
712 base::Time(),
713 next_day,
714 &changes),
715 base::Bind(
716 &CheckPasswordChangesWithResult, &expected_changes, &changes));
717 RunBothThreads();
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");
723 // Remove form_isc_.
724 expected_changes.clear();
725 expected_changes.push_back(
726 PasswordStoreChange(PasswordStoreChange::REMOVE, form_isc_));
727 BrowserThread::PostTaskAndReplyWithResult(
728 BrowserThread::DB,
729 FROM_HERE,
730 base::Bind(method,
731 base::Unretained(&backend),
732 next_day,
733 base::Time(),
734 &changes),
735 base::Bind(
736 &CheckPasswordChangesWithResult, &expected_changes, &changes));
737 RunBothThreads();
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);
755 backend.Init();
757 BrowserThread::PostTask(
758 BrowserThread::DB, FROM_HERE,
759 base::Bind(base::IgnoreResult(&NativeBackendGnome::AddLogin),
760 base::Unretained(&backend), form_google_));
762 RunBothThreads();
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);
771 backend.Init();
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,
781 base::Bind(
782 base::IgnoreResult(&NativeBackendGnome::GetAutofillableLogins),
783 base::Unretained(&backend), &form_list));
785 RunBothThreads();
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) {
798 PasswordForm result;
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
807 // m-facebook.com.
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);
841 backend.Init();
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_));
849 RunBothThreads();
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");
859 // Update login
860 PasswordStoreChangeList changes;
861 BrowserThread::PostTask(
862 BrowserThread::DB, FROM_HERE,
863 base::Bind(base::IgnoreResult(&NativeBackendGnome::UpdateLogin),
864 base::Unretained(&backend),
865 new_form_google,
866 base::Unretained(&changes)));
868 RunBothThreads();
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);
880 backend.Init();
882 BrowserThread::PostTask(
883 BrowserThread::DB, FROM_HERE,
884 base::Bind(base::IgnoreResult(&NativeBackendGnome::AddLogin),
885 base::Unretained(&backend), form_google_));
887 RunBothThreads();
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_));
898 RunBothThreads();
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);
906 backend.Init();
908 BrowserThread::PostTask(
909 BrowserThread::DB, FROM_HERE,
910 base::Bind(base::IgnoreResult(&NativeBackendGnome::AddLogin),
911 base::Unretained(&backend), form_google_));
913 RunBothThreads();
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_));
927 RunBothThreads();
929 EXPECT_EQ(0u, mock_keyring_items.size());
932 TEST_F(NativeBackendGnomeTest, RemoveNonexistentLogin) {
933 NativeBackendGnome backend(42);
934 backend.Init();
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_));
942 RunBothThreads();
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,
958 base::Bind(
959 base::IgnoreResult(&NativeBackendGnome::GetAutofillableLogins),
960 base::Unretained(&backend), &form_list));
962 RunBothThreads();
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);
975 backend.Init();
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_));
983 RunBothThreads();
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),
995 form_isc_,
996 base::Unretained(&changes)));
998 RunBothThreads();
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);
1008 backend.Init();
1010 PasswordStoreChangeList changes;
1011 changes.push_back(PasswordStoreChange(PasswordStoreChange::ADD,
1012 form_google_));
1013 BrowserThread::PostTaskAndReplyWithResult(
1014 BrowserThread::DB, FROM_HERE,
1015 base::Bind(&NativeBackendGnome::AddLogin,
1016 base::Unretained(&backend), form_google_),
1017 base::Bind(&CheckPasswordChanges, changes));
1019 changes.clear();
1020 changes.push_back(PasswordStoreChange(PasswordStoreChange::REMOVE,
1021 form_google_));
1022 form_google_.times_used++;
1023 changes.push_back(PasswordStoreChange(PasswordStoreChange::ADD,
1024 form_google_));
1026 BrowserThread::PostTaskAndReplyWithResult(
1027 BrowserThread::DB, FROM_HERE,
1028 base::Bind(&NativeBackendGnome::AddLogin,
1029 base::Unretained(&backend), form_google_),
1030 base::Bind(&CheckPasswordChanges, changes));
1032 RunBothThreads();
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);
1041 backend.Init();
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,
1052 base::Bind(
1053 base::IgnoreResult(&NativeBackendGnome::GetAutofillableLogins),
1054 base::Unretained(&backend), &form_list));
1055 BrowserThread::PostTask(
1056 BrowserThread::DB, FROM_HERE,
1057 base::Bind(
1058 base::IgnoreResult(&NativeBackendGnome::GetAutofillableLogins),
1059 base::Unretained(&backend), &form_list));
1061 RunBothThreads();
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.