Upstream version 10.39.225.0
[platform/framework/web/crosswalk.git] / src / chrome / browser / password_manager / native_backend_gnome_x_unittest.cc
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.
4
5 #include <stdarg.h>
6
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"
22
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;
29
30 namespace {
31
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.
36
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) {}
45
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) {}
52
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;
57     }
58
59     enum Type { UINT32, STRING } type;
60     uint32_t value_uint32;
61     std::string value_string;
62   };
63
64   typedef std::map<std::string, ItemAttribute> attribute_map;
65   typedef std::vector<std::pair<std::string, ItemAttribute> > attribute_query;
66
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;
74     }
75     return true;
76   }
77
78   std::string keyring;
79   std::string display_name;
80   std::string password;
81
82   attribute_map attributes;
83 };
84
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;
88
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;
96 }
97
98 gboolean mock_gnome_keyring_is_available() {
99   return true;
100 }
101
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;
133     }
134   }
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;
148     }
149   }
150   // GnomeKeyringResult, data
151   callback(GNOME_KEYRING_RESULT_OK, data);
152   return NULL;
153 }
154
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;
176     }
177   }
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;
186     }
187   }
188   // GnomeKeyringResult, data
189   callback(deleted ? GNOME_KEYRING_RESULT_OK
190                    : GNOME_KEYRING_RESULT_NO_MATCH, data);
191   return NULL;
192 }
193
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;
216     }
217   }
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);
239         }
240       }
241       found->secret = strdup(item->password.c_str());
242       results = g_list_prepend(results, found);
243     }
244   }
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);
257   }
258   g_list_free(results);
259   return NULL;
260 }
261
262 const gchar* mock_gnome_keyring_result_to_message(GnomeKeyringResult res) {
263   return "mock keyring simulating failure";
264 }
265
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;
281   }
282 };
283
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();
291
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);
313   }
314 }
315
316 void CheckPasswordChangesWithResult(const PasswordStoreChangeList* expected,
317                                     const PasswordStoreChangeList* actual,
318                                     bool result) {
319   EXPECT_TRUE(result);
320   CheckPasswordChanges(*expected, *actual);
321 }
322
323 }  // anonymous namespace
324
325 class NativeBackendGnomeTest : public testing::Test {
326  protected:
327   enum UpdateType {  // Used in CheckPSLUpdate().
328     UPDATE_BY_UPDATELOGIN,
329     UPDATE_BY_ADDLOGIN,
330   };
331   enum RemoveBetweenMethod {  // Used in CheckRemoveLoginsBetween().
332     CREATED,
333     SYNCED,
334   };
335
336   NativeBackendGnomeTest()
337       : ui_thread_(BrowserThread::UI, &message_loop_),
338         db_thread_(BrowserThread::DB) {
339   }
340
341   virtual void SetUp() {
342     ASSERT_TRUE(db_thread_.Start());
343
344     ASSERT_TRUE(MockGnomeKeyringLoader::LoadMockGnomeKeyring());
345
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;
361
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;
376
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();
387
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();
394   }
395
396   virtual void TearDown() {
397     base::MessageLoop::current()->PostTask(FROM_HERE,
398                                            base::MessageLoop::QuitClosure());
399     base::MessageLoop::current()->Run();
400     db_thread_.Stop();
401   }
402
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();
412   }
413
414   static void PostQuitTask(base::MessageLoop* loop) {
415     loop->PostTask(FROM_HERE, base::MessageLoop::QuitClosure());
416   }
417
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);
427     }
428   }
429
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);
439     }
440   }
441
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);
475   }
476
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();
487
488     BrowserThread::PostTask(
489         BrowserThread::DB,
490         FROM_HERE,
491         base::Bind(base::IgnoreResult(&NativeBackendGnome::AddLogin),
492                    base::Unretained(&backend),
493                    credentials));
494
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;
504     }
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));
513
514     RunBothThreads();
515
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();
520
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;
528   }
529
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();
538
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_));
546
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);
567
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());
577
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;
602     }
603
604     RunBothThreads();
605     EXPECT_EQ(2u, mock_keyring_items.size());
606
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);
625
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);
645   }
646
647   void CheckMatchingWithScheme(const PasswordForm::Scheme& scheme) {
648     other_auth_.scheme = scheme;
649
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));
663   }
664
665   void CheckRemoveLoginsBetween(RemoveBetweenMethod date_to_test) {
666     NativeBackendGnome backend(42);
667     backend.Init();
668
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;
683     }
684
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_));
697
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();
718
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");
722
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();
738
739     EXPECT_EQ(0u, mock_keyring_items.size());
740   }
741
742   base::MessageLoopForUI message_loop_;
743   content::TestBrowserThread ui_thread_;
744   content::TestBrowserThread db_thread_;
745
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_;
751 };
752
753 TEST_F(NativeBackendGnomeTest, BasicAddLogin) {
754   NativeBackendGnome backend(42);
755   backend.Init();
756
757   BrowserThread::PostTask(
758       BrowserThread::DB, FROM_HERE,
759       base::Bind(base::IgnoreResult(&NativeBackendGnome::AddLogin),
760                  base::Unretained(&backend), form_google_));
761
762   RunBothThreads();
763
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");
767 }
768
769 TEST_F(NativeBackendGnomeTest, BasicListLogins) {
770   NativeBackendGnome backend(42);
771   backend.Init();
772
773   BrowserThread::PostTask(
774       BrowserThread::DB, FROM_HERE,
775       base::Bind(base::IgnoreResult( &NativeBackendGnome::AddLogin),
776                  base::Unretained(&backend), form_google_));
777
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));
784
785   RunBothThreads();
786
787   // Quick check that we got something back.
788   EXPECT_EQ(1u, form_list.size());
789   STLDeleteElements(&form_list);
790
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");
794 }
795
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);
804 }
805
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));
812 }
813
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));
819 }
820
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);
826
827 }
828
829 TEST_F(NativeBackendGnomeTest, PSLUpdatingStrictUpdateLogin) {
830   CheckPSLUpdate(UPDATE_BY_UPDATELOGIN);
831 }
832
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);
837 }
838
839 TEST_F(NativeBackendGnomeTest, BasicUpdateLogin) {
840   NativeBackendGnome backend(42);
841   backend.Init();
842
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_));
848
849   RunBothThreads();
850
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");
854
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");
858
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)));
867
868   RunBothThreads();
869
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");
876 }
877
878 TEST_F(NativeBackendGnomeTest, BasicRemoveLogin) {
879   NativeBackendGnome backend(42);
880   backend.Init();
881
882   BrowserThread::PostTask(
883       BrowserThread::DB, FROM_HERE,
884       base::Bind(base::IgnoreResult(&NativeBackendGnome::AddLogin),
885                  base::Unretained(&backend), form_google_));
886
887   RunBothThreads();
888
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");
892
893   BrowserThread::PostTask(
894       BrowserThread::DB, FROM_HERE,
895       base::Bind(base::IgnoreResult(&NativeBackendGnome::RemoveLogin),
896                  base::Unretained(&backend), form_google_));
897
898   RunBothThreads();
899
900   EXPECT_EQ(0u, mock_keyring_items.size());
901 }
902
903 // Verify fix for http://crbug.com/408783.
904 TEST_F(NativeBackendGnomeTest, RemoveLoginActionMismatch) {
905   NativeBackendGnome backend(42);
906   backend.Init();
907
908   BrowserThread::PostTask(
909       BrowserThread::DB, FROM_HERE,
910       base::Bind(base::IgnoreResult(&NativeBackendGnome::AddLogin),
911                  base::Unretained(&backend), form_google_));
912
913   RunBothThreads();
914
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");
918
919   // Action url match not required for removal.
920   form_google_.action = GURL("https://some.other.url.com/path");
921
922   BrowserThread::PostTask(
923       BrowserThread::DB, FROM_HERE,
924       base::Bind(base::IgnoreResult(&NativeBackendGnome::RemoveLogin),
925                  base::Unretained(&backend), form_google_));
926
927   RunBothThreads();
928
929   EXPECT_EQ(0u, mock_keyring_items.size());
930 }
931
932 TEST_F(NativeBackendGnomeTest, RemoveNonexistentLogin) {
933   NativeBackendGnome backend(42);
934   backend.Init();
935
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_));
941
942   RunBothThreads();
943
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");
947
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_));
953
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));
961
962   RunBothThreads();
963
964   // Quick check that we got something back.
965   EXPECT_EQ(1u, form_list.size());
966   STLDeleteElements(&form_list);
967
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");
971 }
972
973 TEST_F(NativeBackendGnomeTest, UpdateNonexistentLogin) {
974   NativeBackendGnome backend(42);
975   backend.Init();
976
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_));
982
983   RunBothThreads();
984
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");
988
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)));
997
998   RunBothThreads();
999
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");
1004 }
1005
1006 TEST_F(NativeBackendGnomeTest, AddDuplicateLogin) {
1007   NativeBackendGnome backend(42);
1008   backend.Init();
1009
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));
1018
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_));
1025
1026   BrowserThread::PostTaskAndReplyWithResult(
1027       BrowserThread::DB, FROM_HERE,
1028       base::Bind(&NativeBackendGnome::AddLogin,
1029                  base::Unretained(&backend), form_google_),
1030       base::Bind(&CheckPasswordChanges, changes));
1031
1032   RunBothThreads();
1033
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");
1037 }
1038
1039 TEST_F(NativeBackendGnomeTest, ListLoginsAppends) {
1040   NativeBackendGnome backend(42);
1041   backend.Init();
1042
1043   BrowserThread::PostTask(
1044       BrowserThread::DB, FROM_HERE,
1045       base::Bind(base::IgnoreResult(&NativeBackendGnome::AddLogin),
1046                  base::Unretained(&backend), form_google_));
1047
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));
1060
1061   RunBothThreads();
1062
1063   // Quick check that we got two results back.
1064   EXPECT_EQ(2u, form_list.size());
1065   STLDeleteElements(&form_list);
1066
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");
1070 }
1071
1072 TEST_F(NativeBackendGnomeTest, RemoveLoginsCreatedBetween) {
1073   CheckRemoveLoginsBetween(CREATED);
1074 }
1075
1076 TEST_F(NativeBackendGnomeTest, RemoveLoginsSyncedBetween) {
1077   CheckRemoveLoginsBetween(SYNCED);
1078 }
1079
1080 // TODO(mdm): add more basic tests here at some point.