base::Time multiplicative operator overloading
[chromium-blink-merge.git] / chrome / browser / password_manager / native_backend_gnome_x_unittest.cc
blob4d0425e66f1d8a84f8ec2abac7015a789d797497
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/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;
29 namespace {
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 {
37 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),
43 password(password) {}
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;
74 return true;
77 std::string keyring;
78 std::string display_name;
79 std::string password;
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";
94 return false;
97 gboolean mock_gnome_keyring_is_available() {
98 return true;
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,
107 gpointer data,
108 GDestroyNotify destroy_data,
109 ...) {
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;
118 va_list ap;
119 va_start(ap, destroy_data);
120 char* name;
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 << "'";
127 } else {
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;
134 va_end(ap);
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);
146 return NULL;
149 // GnomeKeyringResult, data
150 callback(GNOME_KEYRING_RESULT_OK, data);
151 return NULL;
154 gpointer mock_gnome_keyring_delete_password(
155 const GnomeKeyringPasswordSchema* schema,
156 GnomeKeyringOperationDoneCallback callback,
157 gpointer data,
158 GDestroyNotify destroy_data,
159 ...) {
160 MockKeyringItem::attribute_query query;
161 va_list ap;
162 va_start(ap, destroy_data);
163 char* name;
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 << "'";
170 } else {
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;
177 va_end(ap);
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));
184 deleted = true;
187 // GnomeKeyringResult, data
188 callback(deleted ? GNOME_KEYRING_RESULT_OK
189 : GNOME_KEYRING_RESULT_NO_MATCH, data);
190 return NULL;
193 gpointer mock_gnome_keyring_find_items(
194 GnomeKeyringItemType type,
195 GnomeKeyringAttributeList* attributes,
196 GnomeKeyringOperationGetListCallback callback,
197 gpointer data,
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) {
204 query.push_back(
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 << "'";
209 } else {
210 query.push_back(
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());
224 found->item_id = i;
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();
229 ++it) {
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());
234 } else {
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);
249 while (element) {
250 GnomeKeyringFound* found = static_cast<GnomeKeyringFound*>(element->data);
251 free(found->keyring);
252 gnome_keyring_attribute_list_free(found->attributes);
253 free(found->secret);
254 delete found;
255 element = g_list_next(element);
257 g_list_free(results);
258 return NULL;
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 {
267 public:
268 static bool LoadMockGnomeKeyring() {
269 if (!LoadGnomeKeyring())
270 return false;
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;
279 return true;
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,
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 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();
402 db_thread_.Stop();
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,
422 uint32_t value) {
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,
486 const GURL& url,
487 const PasswordForm::Scheme& scheme,
488 PasswordForm* result) {
489 NativeBackendGnome backend(321);
490 backend.Init();
492 BrowserThread::PostTask(
493 BrowserThread::DB,
494 FROM_HERE,
495 base::Bind(base::IgnoreResult(&NativeBackendGnome::AddLogin),
496 base::Unretained(&backend),
497 credentials));
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(
511 BrowserThread::DB,
512 FROM_HERE,
513 base::Bind(base::IgnoreResult(&NativeBackendGnome::GetLogins),
514 base::Unretained(&backend),
515 target_form,
516 &form_list));
518 RunBothThreads();
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())
526 return false;
527 EXPECT_EQ(1u, form_list.size());
528 if (result)
529 *result = *form_list[0];
530 return true;
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);
540 backend.Init();
542 // Add |form_facebook_| to saved logins.
543 BrowserThread::PostTask(
544 BrowserThread::DB,
545 FROM_HERE,
546 base::Bind(base::IgnoreResult(&NativeBackendGnome::AddLogin),
547 base::Unretained(&backend),
548 form_facebook_));
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(
557 BrowserThread::DB,
558 FROM_HERE,
559 base::Bind(base::IgnoreResult(&NativeBackendGnome::GetLogins),
560 base::Unretained(&backend),
561 m_facebook_lookup,
562 &form_list));
563 RunBothThreads();
564 EXPECT_EQ(1u, mock_keyring_items.size());
565 EXPECT_EQ(1u, form_list.size());
566 PasswordForm m_facebook = *form_list[0];
567 form_list.clear();
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(
573 BrowserThread::DB,
574 FROM_HERE,
575 base::Bind(base::IgnoreResult(&NativeBackendGnome::AddLogin),
576 base::Unretained(&backend),
577 m_facebook));
578 RunBothThreads();
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(
590 BrowserThread::DB,
591 FROM_HERE,
592 base::Bind(base::IgnoreResult(&NativeBackendGnome::UpdateLogin),
593 base::Unretained(&backend),
594 new_facebook,
595 base::Owned(new PasswordStoreChangeList)));
596 break;
597 case UPDATE_BY_ADDLOGIN:
598 BrowserThread::PostTask(
599 BrowserThread::DB,
600 FROM_HERE,
601 base::Bind(base::IgnoreResult(&NativeBackendGnome::AddLogin),
602 base::Unretained(&backend),
603 new_facebook));
604 break;
607 RunBothThreads();
608 EXPECT_EQ(2u, mock_keyring_items.size());
610 // Check that m.facebook.com login was not modified by the update.
611 BrowserThread::PostTask(
612 BrowserThread::DB,
613 FROM_HERE,
614 base::Bind(base::IgnoreResult(&NativeBackendGnome::GetLogins),
615 base::Unretained(&backend),
616 m_facebook_lookup,
617 &form_list));
618 RunBothThreads();
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())
623 index_non_psl = 1;
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);
627 form_list.clear();
629 // Check that www.facebook.com login was modified by the update.
630 BrowserThread::PostTask(
631 BrowserThread::DB,
632 FROM_HERE,
633 base::Bind(base::IgnoreResult(&NativeBackendGnome::GetLogins),
634 base::Unretained(&backend),
635 form_facebook_,
636 &form_list));
637 RunBothThreads();
638 // There should be two results -- the exact one, and the PSL-matched one.
639 EXPECT_EQ(2u, form_list.size());
640 index_non_psl = 0;
641 if (!form_list[index_non_psl]->original_signon_realm.empty())
642 index_non_psl = 1;
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);
669 backend.Init();
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;
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 ScopedVector<autofill::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());
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) {
797 PasswordForm result;
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
806 // m-facebook.com.
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);
840 backend.Init();
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_));
848 RunBothThreads();
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");
858 // Update login
859 PasswordStoreChangeList changes;
860 BrowserThread::PostTask(
861 BrowserThread::DB, FROM_HERE,
862 base::Bind(base::IgnoreResult(&NativeBackendGnome::UpdateLogin),
863 base::Unretained(&backend),
864 new_form_google,
865 base::Unretained(&changes)));
867 RunBothThreads();
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);
879 backend.Init();
881 BrowserThread::PostTask(
882 BrowserThread::DB, FROM_HERE,
883 base::Bind(base::IgnoreResult(&NativeBackendGnome::AddLogin),
884 base::Unretained(&backend), form_google_));
886 RunBothThreads();
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_));
897 RunBothThreads();
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);
905 backend.Init();
907 BrowserThread::PostTask(
908 BrowserThread::DB, FROM_HERE,
909 base::Bind(base::IgnoreResult(&NativeBackendGnome::AddLogin),
910 base::Unretained(&backend), form_google_));
912 RunBothThreads();
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_));
926 RunBothThreads();
928 EXPECT_EQ(0u, mock_keyring_items.size());
931 TEST_F(NativeBackendGnomeTest, RemoveNonexistentLogin) {
932 NativeBackendGnome backend(42);
933 backend.Init();
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_));
941 RunBothThreads();
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,
957 base::Bind(
958 base::IgnoreResult(&NativeBackendGnome::GetAutofillableLogins),
959 base::Unretained(&backend), &form_list));
961 RunBothThreads();
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);
973 backend.Init();
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_));
981 RunBothThreads();
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),
993 form_isc_,
994 base::Unretained(&changes)));
996 RunBothThreads();
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);
1006 backend.Init();
1008 PasswordStoreChangeList changes;
1009 changes.push_back(PasswordStoreChange(PasswordStoreChange::ADD,
1010 form_google_));
1011 BrowserThread::PostTaskAndReplyWithResult(
1012 BrowserThread::DB, FROM_HERE,
1013 base::Bind(&NativeBackendGnome::AddLogin,
1014 base::Unretained(&backend), form_google_),
1015 base::Bind(&CheckPasswordChanges, changes));
1017 changes.clear();
1018 changes.push_back(PasswordStoreChange(PasswordStoreChange::REMOVE,
1019 form_google_));
1020 form_google_.times_used++;
1021 changes.push_back(PasswordStoreChange(PasswordStoreChange::ADD,
1022 form_google_));
1024 BrowserThread::PostTaskAndReplyWithResult(
1025 BrowserThread::DB, FROM_HERE,
1026 base::Bind(&NativeBackendGnome::AddLogin,
1027 base::Unretained(&backend), form_google_),
1028 base::Bind(&CheckPasswordChanges, changes));
1030 RunBothThreads();
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);
1039 backend.Init();
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,
1050 base::Bind(
1051 base::IgnoreResult(&NativeBackendGnome::GetAutofillableLogins),
1052 base::Unretained(&backend), &form_list));
1053 BrowserThread::PostTask(
1054 BrowserThread::DB, FROM_HERE,
1055 base::Bind(
1056 base::IgnoreResult(&NativeBackendGnome::GetAutofillableLogins),
1057 base::Unretained(&backend), &form_list));
1059 RunBothThreads();
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);
1071 backend.Init();
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,
1092 &form_list));
1094 RunBothThreads();
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.