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
23using autofill::PasswordForm;
24using base::UTF8ToUTF16;
25using base::UTF16ToUTF8;
26using content::BrowserThread;
27using password_manager::PasswordStoreChange;
28using password_manager::PasswordStoreChangeList;
29
30namespace {
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
37struct 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.
86std::vector<MockKeyringItem> mock_keyring_items;
87bool mock_keyring_reject_local_ids = false;
88
89bool 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
98gboolean mock_gnome_keyring_is_available() {
99  return true;
100}
101
102gpointer 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
155gpointer 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
194gpointer 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
262const 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.
267class 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
284void 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
316void 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
325class 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
753TEST_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
769TEST_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.
797TEST_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.
808TEST_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.
815TEST_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.
822TEST_F(NativeBackendGnomeTest, PSLMatchingDisabledForNonHTMLForms) {
823  CheckMatchingWithScheme(PasswordForm::SCHEME_BASIC);
824  CheckMatchingWithScheme(PasswordForm::SCHEME_DIGEST);
825  CheckMatchingWithScheme(PasswordForm::SCHEME_OTHER);
826
827}
828
829TEST_F(NativeBackendGnomeTest, PSLUpdatingStrictUpdateLogin) {
830  CheckPSLUpdate(UPDATE_BY_UPDATELOGIN);
831}
832
833TEST_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
839TEST_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
878TEST_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.
904TEST_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
932TEST_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
973TEST_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
1006TEST_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
1039TEST_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
1072TEST_F(NativeBackendGnomeTest, RemoveLoginsCreatedBetween) {
1073  CheckRemoveLoginsBetween(CREATED);
1074}
1075
1076TEST_F(NativeBackendGnomeTest, RemoveLoginsSyncedBetween) {
1077  CheckRemoveLoginsBetween(SYNCED);
1078}
1079
1080// TODO(mdm): add more basic tests here at some point.
1081