Revert of Remove OneClickSigninHelper since it is no longer used. (patchset #5 id...
[chromium-blink-merge.git] / chrome / browser / password_manager / native_backend_libsecret_unittest.cc
blob40fbbc41a80913112f61772085d0a19e7d4cb55d
1 // Copyright (c) 2015 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include <stdarg.h>
7 #include "base/basictypes.h"
8 #include "base/prefs/pref_service.h"
9 #include "base/stl_util.h"
10 #include "base/strings/string_number_conversions.h"
11 #include "base/strings/string_util.h"
12 #include "base/strings/stringprintf.h"
13 #include "base/strings/utf_string_conversions.h"
14 #include "base/time/time.h"
15 #include "chrome/browser/password_manager/native_backend_libsecret.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 "testing/gtest/include/gtest/gtest.h"
22 using autofill::PasswordForm;
23 using base::UTF8ToUTF16;
24 using base::UTF16ToUTF8;
25 using password_manager::PasswordStoreChange;
26 using password_manager::PasswordStoreChangeList;
28 namespace {
30 // What follows is a very simple implementation of the subset of the Libsecret
31 // API that we actually use. It gets substituted for the real one by
32 // MockLibsecretLoader, which hooks into the facility normally used to load
33 // the libsecret library at runtime to avoid a static dependency on it.
35 struct MockSecretValue {
36 gchar* password;
37 explicit MockSecretValue(gchar* password) : password(password) {}
38 ~MockSecretValue() { g_free(password); }
41 struct MockSecretItem {
42 MockSecretValue* value;
43 GHashTable* attributes;
45 MockSecretItem(MockSecretValue* value, GHashTable* attributes)
46 : value(value), attributes(attributes) {}
47 ~MockSecretItem() {
48 delete value;
49 g_hash_table_destroy(attributes);
52 void RemoveAttribute(const char* keyname) {
53 g_hash_table_remove(attributes, keyname);
57 bool Matches(MockSecretItem* item, GHashTable* query) {
58 GHashTable* attributes = item->attributes;
59 GHashTableIter iter;
60 gchar* name;
61 gchar* query_value;
62 g_hash_table_iter_init(&iter, query);
64 while (g_hash_table_iter_next(&iter, reinterpret_cast<gpointer*>(&name),
65 reinterpret_cast<gpointer*>(&query_value))) {
66 gchar* value = static_cast<gchar*>(g_hash_table_lookup(attributes, name));
67 if (value == nullptr || strcmp(value, query_value) != 0)
68 return false;
70 return true;
73 bool IsStringAttribute(const SecretSchema* schema, const std::string& name) {
74 for (size_t i = 0; schema->attributes[i].name; ++i)
75 if (name == schema->attributes[i].name)
76 return schema->attributes[i].type == SECRET_SCHEMA_ATTRIBUTE_STRING;
77 NOTREACHED() << "Requested type of nonexistent attribute";
78 return false;
81 // The list of all libsecret items we have stored.
82 ScopedVector<MockSecretItem>* global_mock_libsecret_items;
83 bool global_mock_libsecret_reject_local_ids = false;
85 gboolean mock_secret_password_store_sync(const SecretSchema* schema,
86 const gchar* collection,
87 const gchar* label,
88 const gchar* password,
89 GCancellable* cancellable,
90 GError** error,
91 ...) {
92 GHashTable* attributes =
93 g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
94 va_list ap;
95 va_start(ap, error);
96 char* name;
97 while ((name = va_arg(ap, gchar*))) {
98 char* value;
99 if (IsStringAttribute(schema, name)) {
100 value = g_strdup(va_arg(ap, gchar*));
101 VLOG(1) << "Adding item attribute " << name << ", value '" << value
102 << "'";
103 } else {
104 uint32_t intvalue = va_arg(ap, uint32_t);
105 VLOG(1) << "Adding item attribute " << name << ", value " << intvalue;
106 value = g_strdup_printf("%u", intvalue);
108 g_hash_table_insert(attributes, g_strdup(name), value);
110 va_end(ap);
111 MockSecretValue* secret_value = new MockSecretValue(g_strdup(password));
112 MockSecretItem* item = new MockSecretItem(secret_value, attributes);
113 global_mock_libsecret_items->push_back(item);
114 return true;
117 GList* mock_secret_service_search_sync(SecretService* service,
118 const SecretSchema* schema,
119 GHashTable* attributes,
120 SecretSearchFlags flags,
121 GCancellable* cancellable,
122 GError** error) {
123 GList* result = nullptr;
124 for (MockSecretItem* item : *global_mock_libsecret_items) {
125 if (Matches(item, attributes))
126 result = g_list_append(result, item);
128 return result;
131 gboolean mock_secret_password_clear_sync(const SecretSchema* schema,
132 GCancellable* cancellable,
133 GError** error,
134 ...) {
135 GHashTable* attributes =
136 g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
137 va_list ap;
138 va_start(ap, error);
139 char* name;
140 while ((name = va_arg(ap, gchar*))) {
141 char* value;
142 if (IsStringAttribute(schema, name)) {
143 value = g_strdup(va_arg(ap, gchar*));
144 VLOG(1) << "Adding item attribute " << name << ", value '" << value
145 << "'";
146 } else {
147 uint32_t intvalue = va_arg(ap, uint32_t);
148 VLOG(1) << "Adding item attribute " << name << ", value " << intvalue;
149 value = g_strdup_printf("%u", intvalue);
151 g_hash_table_insert(attributes, g_strdup(name), value);
153 va_end(ap);
154 for (uint32_t i = 0; i < global_mock_libsecret_items->size();)
155 if (Matches((*global_mock_libsecret_items)[i], attributes)) {
156 global_mock_libsecret_items->erase(global_mock_libsecret_items->begin() +
158 } else {
159 ++i;
161 g_hash_table_unref(attributes);
162 return true;
165 MockSecretValue* mock_secret_item_get_secret(MockSecretItem* self) {
166 return self->value;
169 const gchar* mock_secret_value_get_text(MockSecretValue* value) {
170 return value->password;
173 GHashTable* mock_secret_item_get_attributes(MockSecretItem* self) {
174 // Libsecret backend will make unreference of received attributes, so in
175 // order to save them we need to increase their reference number.
176 g_hash_table_ref(self->attributes);
177 return self->attributes;
180 gboolean mock_secret_item_load_secret_sync(MockSecretItem* self,
181 GCancellable* cancellable,
182 GError** error) {
183 return true;
186 void mock_secret_value_unref(gpointer value) {
189 // Inherit to get access to protected fields.
190 class MockLibsecretLoader : public LibsecretLoader {
191 public:
192 static bool LoadMockLibsecret() {
193 secret_password_store_sync = &mock_secret_password_store_sync;
194 secret_service_search_sync = &mock_secret_service_search_sync;
195 secret_password_clear_sync = &mock_secret_password_clear_sync;
196 secret_item_get_secret =
197 (decltype(&::secret_item_get_secret)) & mock_secret_item_get_secret;
198 secret_value_get_text =
199 (decltype(&::secret_value_get_text)) & mock_secret_value_get_text;
200 secret_item_get_attributes = (decltype(&::secret_item_get_attributes)) &
201 mock_secret_item_get_attributes;
202 secret_item_load_secret_sync = (decltype(&::secret_item_load_secret_sync)) &
203 mock_secret_item_load_secret_sync;
204 secret_value_unref =
205 (decltype(&::secret_value_unref)) & mock_secret_value_unref;
206 libsecret_loaded = true;
207 // Reset the state of the mock library.
208 global_mock_libsecret_items->clear();
209 global_mock_libsecret_reject_local_ids = false;
210 return true;
214 void CheckPasswordChanges(const PasswordStoreChangeList& expected_list,
215 const PasswordStoreChangeList& actual_list) {
216 ASSERT_EQ(expected_list.size(), actual_list.size());
217 for (size_t i = 0; i < expected_list.size(); ++i) {
218 EXPECT_EQ(expected_list[i].type(), actual_list[i].type());
219 const PasswordForm& expected = expected_list[i].form();
220 const PasswordForm& actual = actual_list[i].form();
222 EXPECT_EQ(expected.origin, actual.origin);
223 EXPECT_EQ(expected.password_value, actual.password_value);
224 EXPECT_EQ(expected.action, actual.action);
225 EXPECT_EQ(expected.username_element, actual.username_element);
226 EXPECT_EQ(expected.username_value, actual.username_value);
227 EXPECT_EQ(expected.password_element, actual.password_element);
228 EXPECT_EQ(expected.submit_element, actual.submit_element);
229 EXPECT_EQ(expected.signon_realm, actual.signon_realm);
230 EXPECT_EQ(expected.ssl_valid, actual.ssl_valid);
231 EXPECT_EQ(expected.preferred, actual.preferred);
232 EXPECT_EQ(expected.date_created, actual.date_created);
233 EXPECT_EQ(expected.blacklisted_by_user, actual.blacklisted_by_user);
234 EXPECT_EQ(expected.type, actual.type);
235 EXPECT_EQ(expected.times_used, actual.times_used);
236 EXPECT_EQ(expected.scheme, actual.scheme);
237 EXPECT_EQ(expected.date_synced, actual.date_synced);
238 EXPECT_EQ(expected.display_name, actual.display_name);
239 EXPECT_EQ(expected.avatar_url, actual.avatar_url);
240 EXPECT_EQ(expected.federation_url, actual.federation_url);
241 EXPECT_EQ(expected.skip_zero_click, actual.skip_zero_click);
242 EXPECT_EQ(expected.generation_upload_status,
243 actual.generation_upload_status);
247 } // anonymous namespace
249 class NativeBackendLibsecretTest : public testing::Test {
250 protected:
251 enum UpdateType { // Used in CheckPSLUpdate().
252 UPDATE_BY_UPDATELOGIN,
253 UPDATE_BY_ADDLOGIN,
255 enum RemoveBetweenMethod { // Used in CheckRemoveLoginsBetween().
256 CREATED,
257 SYNCED,
260 NativeBackendLibsecretTest() {}
262 void SetUp() override {
263 ASSERT_FALSE(global_mock_libsecret_items);
264 global_mock_libsecret_items = &mock_libsecret_items_;
266 ASSERT_TRUE(MockLibsecretLoader::LoadMockLibsecret());
268 form_google_.origin = GURL("http://www.google.com/");
269 form_google_.action = GURL("http://www.google.com/login");
270 form_google_.username_element = UTF8ToUTF16("user");
271 form_google_.username_value = UTF8ToUTF16("joeschmoe");
272 form_google_.password_element = UTF8ToUTF16("pass");
273 form_google_.password_value = UTF8ToUTF16("seekrit");
274 form_google_.submit_element = UTF8ToUTF16("submit");
275 form_google_.signon_realm = "http://www.google.com/";
276 form_google_.type = PasswordForm::TYPE_GENERATED;
277 form_google_.date_created = base::Time::Now();
278 form_google_.date_synced = base::Time::Now();
279 form_google_.display_name = UTF8ToUTF16("Joe Schmoe");
280 form_google_.avatar_url = GURL("http://www.google.com/avatar");
281 form_google_.federation_url = GURL("http://www.google.com/federation_url");
282 form_google_.skip_zero_click = true;
283 form_google_.generation_upload_status = PasswordForm::POSITIVE_SIGNAL_SENT;
284 form_google_.form_data.name = UTF8ToUTF16("form_name");
285 form_google_.form_data.user_submitted = true;
287 form_facebook_.origin = GURL("http://www.facebook.com/");
288 form_facebook_.action = GURL("http://www.facebook.com/login");
289 form_facebook_.username_element = UTF8ToUTF16("user");
290 form_facebook_.username_value = UTF8ToUTF16("a");
291 form_facebook_.password_element = UTF8ToUTF16("password");
292 form_facebook_.password_value = UTF8ToUTF16("b");
293 form_facebook_.submit_element = UTF8ToUTF16("submit");
294 form_facebook_.signon_realm = "http://www.facebook.com/";
295 form_facebook_.date_created = base::Time::Now();
296 form_facebook_.date_synced = base::Time::Now();
297 form_facebook_.display_name = UTF8ToUTF16("Joe Schmoe");
298 form_facebook_.avatar_url = GURL("http://www.facebook.com/avatar");
299 form_facebook_.federation_url = GURL("http://www.facebook.com/federation");
300 form_facebook_.skip_zero_click = true;
301 form_facebook_.generation_upload_status = PasswordForm::NO_SIGNAL_SENT;
303 form_isc_.origin = GURL("http://www.isc.org/");
304 form_isc_.action = GURL("http://www.isc.org/auth");
305 form_isc_.username_element = UTF8ToUTF16("id");
306 form_isc_.username_value = UTF8ToUTF16("janedoe");
307 form_isc_.password_element = UTF8ToUTF16("passwd");
308 form_isc_.password_value = UTF8ToUTF16("ihazabukkit");
309 form_isc_.submit_element = UTF8ToUTF16("login");
310 form_isc_.signon_realm = "http://www.isc.org/";
311 form_isc_.date_created = base::Time::Now();
312 form_isc_.date_synced = base::Time::Now();
314 other_auth_.origin = GURL("http://www.example.com/");
315 other_auth_.username_value = UTF8ToUTF16("username");
316 other_auth_.password_value = UTF8ToUTF16("pass");
317 other_auth_.signon_realm = "http://www.example.com/Realm";
318 other_auth_.date_created = base::Time::Now();
319 other_auth_.date_synced = base::Time::Now();
322 void TearDown() override {
323 base::MessageLoop::current()->PostTask(FROM_HERE,
324 base::MessageLoop::QuitClosure());
325 base::MessageLoop::current()->Run();
326 ASSERT_TRUE(global_mock_libsecret_items);
327 global_mock_libsecret_items = nullptr;
330 void RunUIThread() { base::MessageLoop::current()->Run(); }
332 void CheckUint32Attribute(const MockSecretItem* item,
333 const std::string& attribute,
334 uint32_t value) {
335 gpointer item_value =
336 g_hash_table_lookup(item->attributes, attribute.c_str());
337 EXPECT_TRUE(item_value) << " in attribute " << attribute;
338 if (item_value) {
339 uint32_t int_value;
340 bool conversion_ok = base::StringToUint((char*)item_value, &int_value);
341 EXPECT_TRUE(conversion_ok);
342 EXPECT_EQ(value, int_value);
346 void CheckStringAttribute(const MockSecretItem* item,
347 const std::string& attribute,
348 const std::string& value) {
349 gpointer item_value =
350 g_hash_table_lookup(item->attributes, attribute.c_str());
351 EXPECT_TRUE(item_value) << " in attribute " << attribute;
352 if (item_value) {
353 EXPECT_EQ(value, static_cast<char*>(item_value));
357 void CheckMockSecretItem(const MockSecretItem* item,
358 const PasswordForm& form,
359 const std::string& app_string) {
360 EXPECT_EQ(UTF16ToUTF8(form.password_value), item->value->password);
361 EXPECT_EQ(22u, g_hash_table_size(item->attributes));
362 CheckStringAttribute(item, "origin_url", form.origin.spec());
363 CheckStringAttribute(item, "action_url", form.action.spec());
364 CheckStringAttribute(item, "username_element",
365 UTF16ToUTF8(form.username_element));
366 CheckStringAttribute(item, "username_value",
367 UTF16ToUTF8(form.username_value));
368 CheckStringAttribute(item, "password_element",
369 UTF16ToUTF8(form.password_element));
370 CheckStringAttribute(item, "submit_element",
371 UTF16ToUTF8(form.submit_element));
372 CheckStringAttribute(item, "signon_realm", form.signon_realm);
373 CheckUint32Attribute(item, "ssl_valid", form.ssl_valid);
374 CheckUint32Attribute(item, "preferred", form.preferred);
375 // We don't check the date created. It varies.
376 CheckUint32Attribute(item, "blacklisted_by_user", form.blacklisted_by_user);
377 CheckUint32Attribute(item, "type", form.type);
378 CheckUint32Attribute(item, "times_used", form.times_used);
379 CheckUint32Attribute(item, "scheme", form.scheme);
380 CheckStringAttribute(
381 item, "date_synced",
382 base::Int64ToString(form.date_synced.ToInternalValue()));
383 CheckStringAttribute(item, "display_name", UTF16ToUTF8(form.display_name));
384 CheckStringAttribute(item, "avatar_url", form.avatar_url.spec());
385 CheckStringAttribute(item, "federation_url", form.federation_url.spec());
386 CheckUint32Attribute(item, "skip_zero_click", form.skip_zero_click);
387 CheckUint32Attribute(item, "generation_upload_status",
388 form.generation_upload_status);
389 CheckStringAttribute(item, "application", app_string);
390 autofill::FormData actual;
391 DeserializeFormDataFromBase64String(
392 static_cast<char*>(g_hash_table_lookup(item->attributes, "form_data")),
393 &actual);
394 EXPECT_TRUE(form.form_data.SameFormAs(actual));
397 // Saves |credentials| and then gets logins matching |url| and |scheme|.
398 // Returns true when something is found, and in such case copies the result to
399 // |result| when |result| is not nullptr. (Note that there can be max. 1
400 // result derived from |credentials|.)
401 bool CheckCredentialAvailability(const PasswordForm& credentials,
402 const GURL& url,
403 const PasswordForm::Scheme& scheme,
404 PasswordForm* result) {
405 NativeBackendLibsecret backend(321);
407 backend.AddLogin(credentials);
409 PasswordForm target_form;
410 target_form.origin = url;
411 target_form.signon_realm = url.spec();
412 if (scheme != PasswordForm::SCHEME_HTML) {
413 // For non-HTML forms, the realm used for authentication
414 // (http://tools.ietf.org/html/rfc1945#section-10.2) is appended to the
415 // signon_realm. Just use a default value for now.
416 target_form.signon_realm.append("Realm");
417 target_form.scheme = scheme;
419 ScopedVector<autofill::PasswordForm> form_list;
420 backend.GetLogins(target_form, &form_list);
422 EXPECT_EQ(1u, global_mock_libsecret_items->size());
423 if (!global_mock_libsecret_items->empty())
424 CheckMockSecretItem((*global_mock_libsecret_items)[0], credentials,
425 "chrome-321");
426 global_mock_libsecret_items->clear();
428 if (form_list.empty())
429 return false;
430 EXPECT_EQ(1u, form_list.size());
431 if (result)
432 *result = *form_list[0];
433 return true;
436 // Test that updating does not use PSL matching: Add a www.facebook.com
437 // password, then use PSL matching to get a copy of it for m.facebook.com, and
438 // add that copy as well. Now update the www.facebook.com password -- the
439 // m.facebook.com password should not get updated. Depending on the argument,
440 // the credential update is done via UpdateLogin or AddLogin.
441 void CheckPSLUpdate(UpdateType update_type) {
442 NativeBackendLibsecret backend(321);
444 backend.AddLogin(form_facebook_);
446 // Get the PSL-matched copy of the saved login for m.facebook.
447 const GURL kMobileURL("http://m.facebook.com/");
448 PasswordForm m_facebook_lookup;
449 m_facebook_lookup.origin = kMobileURL;
450 m_facebook_lookup.signon_realm = kMobileURL.spec();
451 ScopedVector<autofill::PasswordForm> form_list;
452 backend.GetLogins(m_facebook_lookup, &form_list);
454 EXPECT_EQ(1u, global_mock_libsecret_items->size());
455 EXPECT_EQ(1u, form_list.size());
456 PasswordForm m_facebook = *form_list[0];
457 form_list.clear();
458 EXPECT_EQ(kMobileURL, m_facebook.origin);
459 EXPECT_EQ(kMobileURL.spec(), m_facebook.signon_realm);
461 // Add the PSL-matched copy to saved logins.
462 backend.AddLogin(m_facebook);
463 EXPECT_EQ(2u, global_mock_libsecret_items->size());
465 // Update www.facebook.com login.
466 PasswordForm new_facebook(form_facebook_);
467 const base::string16 kOldPassword(form_facebook_.password_value);
468 const base::string16 kNewPassword(UTF8ToUTF16("new_b"));
469 EXPECT_NE(kOldPassword, kNewPassword);
470 new_facebook.password_value = kNewPassword;
471 scoped_ptr<PasswordStoreChangeList> not_used(new PasswordStoreChangeList());
472 switch (update_type) {
473 case UPDATE_BY_UPDATELOGIN:
474 backend.UpdateLogin(new_facebook, not_used.get());
475 break;
476 case UPDATE_BY_ADDLOGIN:
477 backend.AddLogin(new_facebook);
478 break;
481 EXPECT_EQ(2u, global_mock_libsecret_items->size());
483 // Check that m.facebook.com login was not modified by the update.
484 backend.GetLogins(m_facebook_lookup, &form_list);
486 // There should be two results -- the exact one, and the PSL-matched one.
487 EXPECT_EQ(2u, form_list.size());
488 size_t index_non_psl = 0;
489 if (!form_list[index_non_psl]->original_signon_realm.empty())
490 index_non_psl = 1;
491 EXPECT_EQ(kMobileURL, form_list[index_non_psl]->origin);
492 EXPECT_EQ(kMobileURL.spec(), form_list[index_non_psl]->signon_realm);
493 EXPECT_EQ(kOldPassword, form_list[index_non_psl]->password_value);
494 form_list.clear();
496 // Check that www.facebook.com login was modified by the update.
497 backend.GetLogins(form_facebook_, &form_list);
498 // There should be two results -- the exact one, and the PSL-matched one.
499 EXPECT_EQ(2u, form_list.size());
500 index_non_psl = 0;
501 if (!form_list[index_non_psl]->original_signon_realm.empty())
502 index_non_psl = 1;
503 EXPECT_EQ(form_facebook_.origin, form_list[index_non_psl]->origin);
504 EXPECT_EQ(form_facebook_.signon_realm,
505 form_list[index_non_psl]->signon_realm);
506 EXPECT_EQ(kNewPassword, form_list[index_non_psl]->password_value);
507 form_list.clear();
510 // Checks various types of matching for forms with a non-HTML |scheme|.
511 void CheckMatchingWithScheme(const PasswordForm::Scheme& scheme) {
512 ASSERT_NE(PasswordForm::SCHEME_HTML, scheme);
513 other_auth_.scheme = scheme;
515 // Don't match a non-HTML form with an HTML form.
516 EXPECT_FALSE(
517 CheckCredentialAvailability(other_auth_, GURL("http://www.example.com"),
518 PasswordForm::SCHEME_HTML, nullptr));
519 // Don't match an HTML form with non-HTML auth form.
520 EXPECT_FALSE(CheckCredentialAvailability(
521 form_google_, GURL("http://www.google.com/"), scheme, nullptr));
522 // Don't match two different non-HTML auth forms with different origin.
523 EXPECT_FALSE(CheckCredentialAvailability(
524 other_auth_, GURL("http://first.example.com"), scheme, nullptr));
525 // Do match non-HTML forms from the same origin.
526 EXPECT_TRUE(CheckCredentialAvailability(
527 other_auth_, GURL("http://www.example.com/"), scheme, nullptr));
530 void CheckRemoveLoginsBetween(RemoveBetweenMethod date_to_test) {
531 NativeBackendLibsecret backend(42);
533 base::Time now = base::Time::Now();
534 base::Time next_day = now + base::TimeDelta::FromDays(1);
535 form_google_.date_synced = base::Time();
536 form_isc_.date_synced = base::Time();
537 form_google_.date_created = now;
538 form_isc_.date_created = now;
539 if (date_to_test == CREATED) {
540 form_google_.date_created = now;
541 form_isc_.date_created = next_day;
542 } else {
543 form_google_.date_synced = now;
544 form_isc_.date_synced = next_day;
547 backend.AddLogin(form_google_);
548 backend.AddLogin(form_isc_);
550 PasswordStoreChangeList expected_changes;
551 expected_changes.push_back(
552 PasswordStoreChange(PasswordStoreChange::REMOVE, form_google_));
553 PasswordStoreChangeList changes;
554 bool (NativeBackendLibsecret::*method)(
555 base::Time, base::Time, password_manager::PasswordStoreChangeList*) =
556 date_to_test == CREATED
557 ? &NativeBackendLibsecret::RemoveLoginsCreatedBetween
558 : &NativeBackendLibsecret::RemoveLoginsSyncedBetween;
560 EXPECT_TRUE(base::Bind(method, base::Unretained(&backend), base::Time(),
561 next_day, &changes).Run());
562 CheckPasswordChanges(expected_changes, changes);
564 EXPECT_EQ(1u, global_mock_libsecret_items->size());
565 if (!global_mock_libsecret_items->empty() > 0)
566 CheckMockSecretItem((*global_mock_libsecret_items)[0], form_isc_,
567 "chrome-42");
569 // Remove form_isc_.
570 expected_changes.clear();
571 expected_changes.push_back(
572 PasswordStoreChange(PasswordStoreChange::REMOVE, form_isc_));
574 EXPECT_TRUE(base::Bind(method, base::Unretained(&backend), next_day,
575 base::Time(), &changes).Run());
576 CheckPasswordChanges(expected_changes, changes);
578 EXPECT_TRUE(global_mock_libsecret_items->empty());
581 base::MessageLoopForUI message_loop_;
583 // Provide some test forms to avoid having to set them up in each test.
584 PasswordForm form_google_;
585 PasswordForm form_facebook_;
586 PasswordForm form_isc_;
587 PasswordForm other_auth_;
589 ScopedVector<MockSecretItem> mock_libsecret_items_;
592 TEST_F(NativeBackendLibsecretTest, BasicAddLogin) {
593 NativeBackendLibsecret backend(42);
595 backend.AddLogin(form_google_);
597 EXPECT_EQ(1u, global_mock_libsecret_items->size());
598 if (!global_mock_libsecret_items->empty())
599 CheckMockSecretItem((*global_mock_libsecret_items)[0], form_google_,
600 "chrome-42");
603 TEST_F(NativeBackendLibsecretTest, BasicListLogins) {
604 NativeBackendLibsecret backend(42);
606 backend.AddLogin(form_google_);
608 ScopedVector<autofill::PasswordForm> form_list;
609 backend.GetAutofillableLogins(&form_list);
611 // Quick check that we got something back.
612 EXPECT_EQ(1u, form_list.size());
613 form_list.clear();
615 EXPECT_EQ(1u, global_mock_libsecret_items->size());
616 if (!global_mock_libsecret_items->empty())
617 CheckMockSecretItem((*global_mock_libsecret_items)[0], form_google_,
618 "chrome-42");
621 // Save a password for www.facebook.com and see it suggested for m.facebook.com.
622 TEST_F(NativeBackendLibsecretTest, PSLMatchingPositive) {
623 PasswordForm result;
624 const GURL kMobileURL("http://m.facebook.com/");
625 EXPECT_TRUE(CheckCredentialAvailability(form_facebook_, kMobileURL,
626 PasswordForm::SCHEME_HTML, &result));
627 EXPECT_EQ(kMobileURL, result.origin);
628 EXPECT_EQ(kMobileURL.spec(), result.signon_realm);
631 // Save a password for www.facebook.com and see it not suggested for
632 // m-facebook.com.
633 TEST_F(NativeBackendLibsecretTest, PSLMatchingNegativeDomainMismatch) {
634 EXPECT_FALSE(CheckCredentialAvailability(form_facebook_,
635 GURL("http://m-facebook.com/"),
636 PasswordForm::SCHEME_HTML, nullptr));
639 // Test PSL matching is off for domains excluded from it.
640 TEST_F(NativeBackendLibsecretTest, PSLMatchingDisabledDomains) {
641 EXPECT_FALSE(CheckCredentialAvailability(form_google_,
642 GURL("http://one.google.com/"),
643 PasswordForm::SCHEME_HTML, nullptr));
646 // Make sure PSL matches aren't available for non-HTML forms.
647 TEST_F(NativeBackendLibsecretTest, PSLMatchingDisabledForNonHTMLForms) {
648 CheckMatchingWithScheme(PasswordForm::SCHEME_BASIC);
649 CheckMatchingWithScheme(PasswordForm::SCHEME_DIGEST);
650 CheckMatchingWithScheme(PasswordForm::SCHEME_OTHER);
653 TEST_F(NativeBackendLibsecretTest, PSLUpdatingStrictUpdateLogin) {
654 CheckPSLUpdate(UPDATE_BY_UPDATELOGIN);
657 TEST_F(NativeBackendLibsecretTest, PSLUpdatingStrictAddLogin) {
658 // TODO(vabr): if AddLogin becomes no longer valid for existing logins, then
659 // just delete this test.
660 CheckPSLUpdate(UPDATE_BY_ADDLOGIN);
663 TEST_F(NativeBackendLibsecretTest, BasicUpdateLogin) {
664 NativeBackendLibsecret backend(42);
666 backend.AddLogin(form_google_);
668 PasswordForm new_form_google(form_google_);
669 new_form_google.times_used = 1;
670 new_form_google.action = GURL("http://www.google.com/different/login");
672 EXPECT_EQ(1u, global_mock_libsecret_items->size());
673 if (!global_mock_libsecret_items->empty())
674 CheckMockSecretItem((*global_mock_libsecret_items)[0], form_google_,
675 "chrome-42");
677 // Update login
678 PasswordStoreChangeList changes;
679 backend.UpdateLogin(new_form_google, &changes);
681 ASSERT_EQ(1u, changes.size());
682 EXPECT_EQ(PasswordStoreChange::UPDATE, changes.front().type());
683 EXPECT_EQ(new_form_google, changes.front().form());
684 EXPECT_EQ(1u, global_mock_libsecret_items->size());
685 if (!global_mock_libsecret_items->empty())
686 CheckMockSecretItem((*global_mock_libsecret_items)[0], new_form_google,
687 "chrome-42");
690 TEST_F(NativeBackendLibsecretTest, BasicRemoveLogin) {
691 NativeBackendLibsecret backend(42);
693 backend.AddLogin(form_google_);
695 EXPECT_EQ(1u, global_mock_libsecret_items->size());
696 if (!global_mock_libsecret_items->empty())
697 CheckMockSecretItem((*global_mock_libsecret_items)[0], form_google_,
698 "chrome-42");
700 backend.RemoveLogin(form_google_);
702 EXPECT_TRUE(global_mock_libsecret_items->empty());
705 // Verify fix for http://crbug.com/408783.
706 TEST_F(NativeBackendLibsecretTest, RemoveLoginActionMismatch) {
707 NativeBackendLibsecret backend(42);
709 backend.AddLogin(form_google_);
711 EXPECT_EQ(1u, global_mock_libsecret_items->size());
712 if (!global_mock_libsecret_items->empty())
713 CheckMockSecretItem((*global_mock_libsecret_items)[0], form_google_,
714 "chrome-42");
716 // Action url match not required for removal.
717 form_google_.action = GURL("https://some.other.url.com/path");
719 backend.RemoveLogin(form_google_);
721 EXPECT_TRUE(global_mock_libsecret_items->empty());
724 TEST_F(NativeBackendLibsecretTest, RemoveNonexistentLogin) {
725 NativeBackendLibsecret backend(42);
727 // First add an unrelated login.
728 backend.AddLogin(form_google_);
730 EXPECT_EQ(1u, global_mock_libsecret_items->size());
731 if (!global_mock_libsecret_items->empty())
732 CheckMockSecretItem((*global_mock_libsecret_items)[0], form_google_,
733 "chrome-42");
735 // Attempt to remove a login that doesn't exist.
736 backend.RemoveLogin(form_isc_);
738 // Make sure we can still get the first form back.
739 ScopedVector<autofill::PasswordForm> form_list;
740 backend.GetAutofillableLogins(&form_list);
742 // Quick check that we got something back.
743 EXPECT_EQ(1u, form_list.size());
744 form_list.clear();
746 EXPECT_EQ(1u, global_mock_libsecret_items->size());
747 if (!global_mock_libsecret_items->empty())
748 CheckMockSecretItem((*global_mock_libsecret_items)[0], form_google_,
749 "chrome-42");
752 TEST_F(NativeBackendLibsecretTest, UpdateNonexistentLogin) {
753 NativeBackendLibsecret backend(42);
755 // First add an unrelated login.
756 backend.AddLogin(form_google_);
758 EXPECT_EQ(1u, global_mock_libsecret_items->size());
759 if (!global_mock_libsecret_items->empty())
760 CheckMockSecretItem((*global_mock_libsecret_items)[0], form_google_,
761 "chrome-42");
763 // Attempt to update a login that doesn't exist.
764 PasswordStoreChangeList changes;
765 backend.UpdateLogin(form_isc_, &changes);
767 EXPECT_EQ(PasswordStoreChangeList(), changes);
768 EXPECT_EQ(1u, global_mock_libsecret_items->size());
769 if (!global_mock_libsecret_items->empty())
770 CheckMockSecretItem((*global_mock_libsecret_items)[0], form_google_,
771 "chrome-42");
774 TEST_F(NativeBackendLibsecretTest, AddDuplicateLogin) {
775 NativeBackendLibsecret backend(42);
777 PasswordStoreChangeList expected_changes, actual_changes;
778 expected_changes.push_back(
779 PasswordStoreChange(PasswordStoreChange::ADD, form_google_));
780 actual_changes = backend.AddLogin(form_google_);
781 CheckPasswordChanges(expected_changes, actual_changes);
783 expected_changes.clear();
784 expected_changes.push_back(
785 PasswordStoreChange(PasswordStoreChange::REMOVE, form_google_));
786 form_google_.times_used++;
787 expected_changes.push_back(
788 PasswordStoreChange(PasswordStoreChange::ADD, form_google_));
790 actual_changes = backend.AddLogin(form_google_);
791 CheckPasswordChanges(expected_changes, actual_changes);
793 EXPECT_EQ(1u, global_mock_libsecret_items->size());
794 if (!global_mock_libsecret_items->empty())
795 CheckMockSecretItem((*global_mock_libsecret_items)[0], form_google_,
796 "chrome-42");
799 TEST_F(NativeBackendLibsecretTest, ListLoginsAppends) {
800 NativeBackendLibsecret backend(42);
802 backend.AddLogin(form_google_);
804 // Send the same request twice with the same list both times.
805 ScopedVector<autofill::PasswordForm> form_list;
806 backend.GetAutofillableLogins(&form_list);
807 backend.GetAutofillableLogins(&form_list);
809 // Quick check that we got two results back.
810 EXPECT_EQ(2u, form_list.size());
811 form_list.clear();
813 EXPECT_EQ(1u, global_mock_libsecret_items->size());
814 if (!global_mock_libsecret_items->empty())
815 CheckMockSecretItem((*global_mock_libsecret_items)[0], form_google_,
816 "chrome-42");
819 TEST_F(NativeBackendLibsecretTest, AndroidCredentials) {
820 NativeBackendLibsecret backend(42);
821 backend.Init();
823 PasswordForm observed_android_form;
824 observed_android_form.scheme = PasswordForm::SCHEME_HTML;
825 observed_android_form.signon_realm =
826 "android://7x7IDboo8u9YKraUsbmVkuf1-@net.rateflix.app/";
827 PasswordForm saved_android_form = observed_android_form;
828 saved_android_form.username_value = base::UTF8ToUTF16("randomusername");
829 saved_android_form.password_value = base::UTF8ToUTF16("password");
830 saved_android_form.date_created = base::Time::Now();
832 backend.AddLogin(saved_android_form);
834 ScopedVector<autofill::PasswordForm> form_list;
835 backend.GetAutofillableLogins(&form_list);
837 EXPECT_EQ(1u, form_list.size());
838 EXPECT_EQ(saved_android_form, *form_list[0]);
842 TEST_F(NativeBackendLibsecretTest, RemoveLoginsCreatedBetween) {
843 CheckRemoveLoginsBetween(CREATED);
846 TEST_F(NativeBackendLibsecretTest, RemoveLoginsSyncedBetween) {
847 CheckRemoveLoginsBetween(SYNCED);
850 TEST_F(NativeBackendLibsecretTest, SomeKeyringAttributesAreMissing) {
851 // Absent attributes should be filled with default values.
852 NativeBackendLibsecret backend(42);
854 backend.AddLogin(form_google_);
856 EXPECT_EQ(1u, global_mock_libsecret_items->size());
857 // Remove a string attribute.
858 (*global_mock_libsecret_items)[0]->RemoveAttribute("avatar_url");
859 // Remove an integer attribute.
860 (*global_mock_libsecret_items)[0]->RemoveAttribute("ssl_valid");
862 ScopedVector<autofill::PasswordForm> form_list;
863 backend.GetAutofillableLogins(&form_list);
865 EXPECT_EQ(1u, form_list.size());
866 EXPECT_EQ(GURL(""), form_list[0]->avatar_url);
867 EXPECT_FALSE(form_list[0]->ssl_valid);
870 // TODO(mdm): add more basic tests here at some point.