Pin Chrome's shortcut to the Win10 Start menu on install and OS upgrade.
[chromium-blink-merge.git] / chrome / browser / password_manager / native_backend_libsecret_unittest.cc
blob2bd0f0886f14ee2c2ad6c531aabf6dc4a6be46b8
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/location.h"
9 #include "base/prefs/pref_service.h"
10 #include "base/single_thread_task_runner.h"
11 #include "base/stl_util.h"
12 #include "base/strings/string_number_conversions.h"
13 #include "base/strings/string_util.h"
14 #include "base/strings/stringprintf.h"
15 #include "base/strings/utf_string_conversions.h"
16 #include "base/thread_task_runner_handle.h"
17 #include "base/time/time.h"
18 #include "chrome/browser/password_manager/native_backend_libsecret.h"
19 #include "chrome/test/base/testing_profile.h"
20 #include "components/autofill/core/common/password_form.h"
21 #include "components/password_manager/core/browser/psl_matching_helper.h"
22 #include "components/password_manager/core/common/password_manager_pref_names.h"
23 #include "testing/gtest/include/gtest/gtest.h"
25 using autofill::PasswordForm;
26 using base::UTF8ToUTF16;
27 using base::UTF16ToUTF8;
28 using password_manager::PasswordStoreChange;
29 using password_manager::PasswordStoreChangeList;
31 namespace {
33 // What follows is a very simple implementation of the subset of the Libsecret
34 // API that we actually use. It gets substituted for the real one by
35 // MockLibsecretLoader, which hooks into the facility normally used to load
36 // the libsecret library at runtime to avoid a static dependency on it.
38 struct MockSecretValue {
39 gchar* password;
40 explicit MockSecretValue(gchar* password) : password(password) {}
41 ~MockSecretValue() { g_free(password); }
44 struct MockSecretItem {
45 MockSecretValue* value;
46 GHashTable* attributes;
48 MockSecretItem(MockSecretValue* value, GHashTable* attributes)
49 : value(value), attributes(attributes) {}
50 ~MockSecretItem() {
51 delete value;
52 g_hash_table_destroy(attributes);
55 void RemoveAttribute(const char* keyname) {
56 g_hash_table_remove(attributes, keyname);
60 bool Matches(MockSecretItem* item, GHashTable* query) {
61 GHashTable* attributes = item->attributes;
62 GHashTableIter iter;
63 gchar* name;
64 gchar* query_value;
65 g_hash_table_iter_init(&iter, query);
67 while (g_hash_table_iter_next(&iter, reinterpret_cast<gpointer*>(&name),
68 reinterpret_cast<gpointer*>(&query_value))) {
69 gchar* value = static_cast<gchar*>(g_hash_table_lookup(attributes, name));
70 if (value == nullptr || strcmp(value, query_value) != 0)
71 return false;
73 return true;
76 bool IsStringAttribute(const SecretSchema* schema, const std::string& name) {
77 for (size_t i = 0; schema->attributes[i].name; ++i)
78 if (name == schema->attributes[i].name)
79 return schema->attributes[i].type == SECRET_SCHEMA_ATTRIBUTE_STRING;
80 NOTREACHED() << "Requested type of nonexistent attribute";
81 return false;
84 // The list of all libsecret items we have stored.
85 ScopedVector<MockSecretItem>* global_mock_libsecret_items;
86 bool global_mock_libsecret_reject_local_ids = false;
88 gboolean mock_secret_password_store_sync(const SecretSchema* schema,
89 const gchar* collection,
90 const gchar* label,
91 const gchar* password,
92 GCancellable* cancellable,
93 GError** error,
94 ...) {
95 GHashTable* attributes =
96 g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
97 va_list ap;
98 va_start(ap, error);
99 char* name;
100 while ((name = va_arg(ap, gchar*))) {
101 char* value;
102 if (IsStringAttribute(schema, name)) {
103 value = g_strdup(va_arg(ap, gchar*));
104 VLOG(1) << "Adding item attribute " << name << ", value '" << value
105 << "'";
106 } else {
107 uint32_t intvalue = va_arg(ap, uint32_t);
108 VLOG(1) << "Adding item attribute " << name << ", value " << intvalue;
109 value = g_strdup_printf("%u", intvalue);
111 g_hash_table_insert(attributes, g_strdup(name), value);
113 va_end(ap);
114 MockSecretValue* secret_value = new MockSecretValue(g_strdup(password));
115 MockSecretItem* item = new MockSecretItem(secret_value, attributes);
116 global_mock_libsecret_items->push_back(item);
117 return true;
120 GList* mock_secret_service_search_sync(SecretService* service,
121 const SecretSchema* schema,
122 GHashTable* attributes,
123 SecretSearchFlags flags,
124 GCancellable* cancellable,
125 GError** error) {
126 GList* result = nullptr;
127 for (MockSecretItem* item : *global_mock_libsecret_items) {
128 if (Matches(item, attributes))
129 result = g_list_append(result, item);
131 return result;
134 gboolean mock_secret_password_clear_sync(const SecretSchema* schema,
135 GCancellable* cancellable,
136 GError** error,
137 ...) {
138 GHashTable* attributes =
139 g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
140 va_list ap;
141 va_start(ap, error);
142 char* name;
143 while ((name = va_arg(ap, gchar*))) {
144 char* value;
145 if (IsStringAttribute(schema, name)) {
146 value = g_strdup(va_arg(ap, gchar*));
147 VLOG(1) << "Adding item attribute " << name << ", value '" << value
148 << "'";
149 } else {
150 uint32_t intvalue = va_arg(ap, uint32_t);
151 VLOG(1) << "Adding item attribute " << name << ", value " << intvalue;
152 value = g_strdup_printf("%u", intvalue);
154 g_hash_table_insert(attributes, g_strdup(name), value);
156 va_end(ap);
158 ScopedVector<MockSecretItem> kept_mock_libsecret_items;
159 kept_mock_libsecret_items.reserve(global_mock_libsecret_items->size());
160 for (auto& item : *global_mock_libsecret_items) {
161 if (!Matches(item, attributes)) {
162 kept_mock_libsecret_items.push_back(item);
163 item = nullptr;
166 global_mock_libsecret_items->swap(kept_mock_libsecret_items);
168 g_hash_table_unref(attributes);
169 return true;
172 MockSecretValue* mock_secret_item_get_secret(MockSecretItem* self) {
173 return self->value;
176 const gchar* mock_secret_value_get_text(MockSecretValue* value) {
177 return value->password;
180 GHashTable* mock_secret_item_get_attributes(MockSecretItem* self) {
181 // Libsecret backend will make unreference of received attributes, so in
182 // order to save them we need to increase their reference number.
183 g_hash_table_ref(self->attributes);
184 return self->attributes;
187 gboolean mock_secret_item_load_secret_sync(MockSecretItem* self,
188 GCancellable* cancellable,
189 GError** error) {
190 return true;
193 void mock_secret_value_unref(gpointer value) {
196 // Inherit to get access to protected fields.
197 class MockLibsecretLoader : public LibsecretLoader {
198 public:
199 static bool LoadMockLibsecret() {
200 secret_password_store_sync = &mock_secret_password_store_sync;
201 secret_service_search_sync = &mock_secret_service_search_sync;
202 secret_password_clear_sync = &mock_secret_password_clear_sync;
203 secret_item_get_secret =
204 (decltype(&::secret_item_get_secret)) & mock_secret_item_get_secret;
205 secret_value_get_text =
206 (decltype(&::secret_value_get_text)) & mock_secret_value_get_text;
207 secret_item_get_attributes = (decltype(&::secret_item_get_attributes)) &
208 mock_secret_item_get_attributes;
209 secret_item_load_secret_sync = (decltype(&::secret_item_load_secret_sync)) &
210 mock_secret_item_load_secret_sync;
211 secret_value_unref =
212 (decltype(&::secret_value_unref)) & mock_secret_value_unref;
213 libsecret_loaded = true;
214 // Reset the state of the mock library.
215 global_mock_libsecret_items->clear();
216 global_mock_libsecret_reject_local_ids = false;
217 return true;
221 void CheckPasswordChanges(const PasswordStoreChangeList& expected_list,
222 const PasswordStoreChangeList& actual_list) {
223 ASSERT_EQ(expected_list.size(), actual_list.size());
224 for (size_t i = 0; i < expected_list.size(); ++i) {
225 EXPECT_EQ(expected_list[i].type(), actual_list[i].type());
226 const PasswordForm& expected = expected_list[i].form();
227 const PasswordForm& actual = actual_list[i].form();
229 EXPECT_EQ(expected.origin, actual.origin);
230 EXPECT_EQ(expected.password_value, actual.password_value);
231 EXPECT_EQ(expected.action, actual.action);
232 EXPECT_EQ(expected.username_element, actual.username_element);
233 EXPECT_EQ(expected.username_value, actual.username_value);
234 EXPECT_EQ(expected.password_element, actual.password_element);
235 EXPECT_EQ(expected.submit_element, actual.submit_element);
236 EXPECT_EQ(expected.signon_realm, actual.signon_realm);
237 EXPECT_EQ(expected.ssl_valid, actual.ssl_valid);
238 EXPECT_EQ(expected.preferred, actual.preferred);
239 EXPECT_EQ(expected.date_created, actual.date_created);
240 EXPECT_EQ(expected.blacklisted_by_user, actual.blacklisted_by_user);
241 EXPECT_EQ(expected.type, actual.type);
242 EXPECT_EQ(expected.times_used, actual.times_used);
243 EXPECT_EQ(expected.scheme, actual.scheme);
244 EXPECT_EQ(expected.date_synced, actual.date_synced);
245 EXPECT_EQ(expected.display_name, actual.display_name);
246 EXPECT_EQ(expected.icon_url, actual.icon_url);
247 EXPECT_EQ(expected.federation_url, actual.federation_url);
248 EXPECT_EQ(expected.skip_zero_click, actual.skip_zero_click);
249 EXPECT_EQ(expected.generation_upload_status,
250 actual.generation_upload_status);
254 } // anonymous namespace
256 class NativeBackendLibsecretTest : public testing::Test {
257 protected:
258 enum UpdateType { // Used in CheckPSLUpdate().
259 UPDATE_BY_UPDATELOGIN,
260 UPDATE_BY_ADDLOGIN,
262 enum RemoveBetweenMethod { // Used in CheckRemoveLoginsBetween().
263 CREATED,
264 SYNCED,
267 NativeBackendLibsecretTest() {}
269 void SetUp() override {
270 ASSERT_FALSE(global_mock_libsecret_items);
271 global_mock_libsecret_items = &mock_libsecret_items_;
273 ASSERT_TRUE(MockLibsecretLoader::LoadMockLibsecret());
275 form_google_.origin = GURL("http://www.google.com/");
276 form_google_.action = GURL("http://www.google.com/login");
277 form_google_.username_element = UTF8ToUTF16("user");
278 form_google_.username_value = UTF8ToUTF16("joeschmoe");
279 form_google_.password_element = UTF8ToUTF16("pass");
280 form_google_.password_value = UTF8ToUTF16("seekrit");
281 form_google_.submit_element = UTF8ToUTF16("submit");
282 form_google_.signon_realm = "http://www.google.com/";
283 form_google_.type = PasswordForm::TYPE_GENERATED;
284 form_google_.date_created = base::Time::Now();
285 form_google_.date_synced = base::Time::Now();
286 form_google_.display_name = UTF8ToUTF16("Joe Schmoe");
287 form_google_.icon_url = GURL("http://www.google.com/icon");
288 form_google_.federation_url = GURL("http://www.google.com/federation_url");
289 form_google_.skip_zero_click = true;
290 form_google_.generation_upload_status = PasswordForm::POSITIVE_SIGNAL_SENT;
291 form_google_.form_data.name = UTF8ToUTF16("form_name");
293 form_facebook_.origin = GURL("http://www.facebook.com/");
294 form_facebook_.action = GURL("http://www.facebook.com/login");
295 form_facebook_.username_element = UTF8ToUTF16("user");
296 form_facebook_.username_value = UTF8ToUTF16("a");
297 form_facebook_.password_element = UTF8ToUTF16("password");
298 form_facebook_.password_value = UTF8ToUTF16("b");
299 form_facebook_.submit_element = UTF8ToUTF16("submit");
300 form_facebook_.signon_realm = "http://www.facebook.com/";
301 form_facebook_.date_created = base::Time::Now();
302 form_facebook_.date_synced = base::Time::Now();
303 form_facebook_.display_name = UTF8ToUTF16("Joe Schmoe");
304 form_facebook_.icon_url = GURL("http://www.facebook.com/icon");
305 form_facebook_.federation_url = GURL("http://www.facebook.com/federation");
306 form_facebook_.skip_zero_click = true;
307 form_facebook_.generation_upload_status = PasswordForm::NO_SIGNAL_SENT;
309 form_isc_.origin = GURL("http://www.isc.org/");
310 form_isc_.action = GURL("http://www.isc.org/auth");
311 form_isc_.username_element = UTF8ToUTF16("id");
312 form_isc_.username_value = UTF8ToUTF16("janedoe");
313 form_isc_.password_element = UTF8ToUTF16("passwd");
314 form_isc_.password_value = UTF8ToUTF16("ihazabukkit");
315 form_isc_.submit_element = UTF8ToUTF16("login");
316 form_isc_.signon_realm = "http://www.isc.org/";
317 form_isc_.date_created = base::Time::Now();
318 form_isc_.date_synced = base::Time::Now();
320 other_auth_.origin = GURL("http://www.example.com/");
321 other_auth_.username_value = UTF8ToUTF16("username");
322 other_auth_.password_value = UTF8ToUTF16("pass");
323 other_auth_.signon_realm = "http://www.example.com/Realm";
324 other_auth_.date_created = base::Time::Now();
325 other_auth_.date_synced = base::Time::Now();
328 void TearDown() override {
329 base::ThreadTaskRunnerHandle::Get()->PostTask(
330 FROM_HERE, base::MessageLoop::QuitClosure());
331 base::MessageLoop::current()->Run();
332 ASSERT_TRUE(global_mock_libsecret_items);
333 global_mock_libsecret_items = nullptr;
336 void RunUIThread() { base::MessageLoop::current()->Run(); }
338 void CheckUint32Attribute(const MockSecretItem* item,
339 const std::string& attribute,
340 uint32_t value) {
341 gpointer item_value =
342 g_hash_table_lookup(item->attributes, attribute.c_str());
343 EXPECT_TRUE(item_value) << " in attribute " << attribute;
344 if (item_value) {
345 uint32_t int_value;
346 bool conversion_ok = base::StringToUint((char*)item_value, &int_value);
347 EXPECT_TRUE(conversion_ok);
348 EXPECT_EQ(value, int_value);
352 void CheckStringAttribute(const MockSecretItem* item,
353 const std::string& attribute,
354 const std::string& value) {
355 gpointer item_value =
356 g_hash_table_lookup(item->attributes, attribute.c_str());
357 EXPECT_TRUE(item_value) << " in attribute " << attribute;
358 if (item_value) {
359 EXPECT_EQ(value, static_cast<char*>(item_value));
363 void CheckMockSecretItem(const MockSecretItem* item,
364 const PasswordForm& form,
365 const std::string& app_string) {
366 EXPECT_EQ(UTF16ToUTF8(form.password_value), item->value->password);
367 EXPECT_EQ(22u, g_hash_table_size(item->attributes));
368 CheckStringAttribute(item, "origin_url", form.origin.spec());
369 CheckStringAttribute(item, "action_url", form.action.spec());
370 CheckStringAttribute(item, "username_element",
371 UTF16ToUTF8(form.username_element));
372 CheckStringAttribute(item, "username_value",
373 UTF16ToUTF8(form.username_value));
374 CheckStringAttribute(item, "password_element",
375 UTF16ToUTF8(form.password_element));
376 CheckStringAttribute(item, "submit_element",
377 UTF16ToUTF8(form.submit_element));
378 CheckStringAttribute(item, "signon_realm", form.signon_realm);
379 CheckUint32Attribute(item, "ssl_valid", form.ssl_valid);
380 CheckUint32Attribute(item, "preferred", form.preferred);
381 // We don't check the date created. It varies.
382 CheckUint32Attribute(item, "blacklisted_by_user", form.blacklisted_by_user);
383 CheckUint32Attribute(item, "type", form.type);
384 CheckUint32Attribute(item, "times_used", form.times_used);
385 CheckUint32Attribute(item, "scheme", form.scheme);
386 CheckStringAttribute(
387 item, "date_synced",
388 base::Int64ToString(form.date_synced.ToInternalValue()));
389 CheckStringAttribute(item, "display_name", UTF16ToUTF8(form.display_name));
390 CheckStringAttribute(item, "avatar_url", form.icon_url.spec());
391 CheckStringAttribute(item, "federation_url", form.federation_url.spec());
392 CheckUint32Attribute(item, "skip_zero_click", form.skip_zero_click);
393 CheckUint32Attribute(item, "generation_upload_status",
394 form.generation_upload_status);
395 CheckStringAttribute(item, "application", app_string);
396 autofill::FormData actual;
397 DeserializeFormDataFromBase64String(
398 static_cast<char*>(g_hash_table_lookup(item->attributes, "form_data")),
399 &actual);
400 EXPECT_TRUE(form.form_data.SameFormAs(actual));
403 // Saves |credentials| and then gets logins matching |url| and |scheme|.
404 // Returns true when something is found, and in such case copies the result to
405 // |result| when |result| is not nullptr. (Note that there can be max. 1
406 // result derived from |credentials|.)
407 bool CheckCredentialAvailability(const PasswordForm& credentials,
408 const GURL& url,
409 const PasswordForm::Scheme& scheme,
410 PasswordForm* result) {
411 NativeBackendLibsecret backend(321);
413 backend.AddLogin(credentials);
415 PasswordForm target_form;
416 target_form.origin = url;
417 target_form.signon_realm = url.spec();
418 if (scheme != PasswordForm::SCHEME_HTML) {
419 // For non-HTML forms, the realm used for authentication
420 // (http://tools.ietf.org/html/rfc1945#section-10.2) is appended to the
421 // signon_realm. Just use a default value for now.
422 target_form.signon_realm.append("Realm");
423 target_form.scheme = scheme;
425 ScopedVector<autofill::PasswordForm> form_list;
426 backend.GetLogins(target_form, &form_list);
428 EXPECT_EQ(1u, global_mock_libsecret_items->size());
429 if (!global_mock_libsecret_items->empty())
430 CheckMockSecretItem((*global_mock_libsecret_items)[0], credentials,
431 "chrome-321");
432 global_mock_libsecret_items->clear();
434 if (form_list.empty())
435 return false;
436 EXPECT_EQ(1u, form_list.size());
437 if (result)
438 *result = *form_list[0];
439 return true;
442 // Test that updating does not use PSL matching: Add a www.facebook.com
443 // password, then use PSL matching to get a copy of it for m.facebook.com, and
444 // add that copy as well. Now update the www.facebook.com password -- the
445 // m.facebook.com password should not get updated. Depending on the argument,
446 // the credential update is done via UpdateLogin or AddLogin.
447 void CheckPSLUpdate(UpdateType update_type) {
448 NativeBackendLibsecret backend(321);
450 backend.AddLogin(form_facebook_);
452 // Get the PSL-matched copy of the saved login for m.facebook.
453 const GURL kMobileURL("http://m.facebook.com/");
454 PasswordForm m_facebook_lookup;
455 m_facebook_lookup.origin = kMobileURL;
456 m_facebook_lookup.signon_realm = kMobileURL.spec();
457 ScopedVector<autofill::PasswordForm> form_list;
458 backend.GetLogins(m_facebook_lookup, &form_list);
460 EXPECT_EQ(1u, global_mock_libsecret_items->size());
461 EXPECT_EQ(1u, form_list.size());
462 PasswordForm m_facebook = *form_list[0];
463 form_list.clear();
464 EXPECT_EQ(kMobileURL, m_facebook.origin);
465 EXPECT_EQ(kMobileURL.spec(), m_facebook.signon_realm);
467 // Add the PSL-matched copy to saved logins.
468 backend.AddLogin(m_facebook);
469 EXPECT_EQ(2u, global_mock_libsecret_items->size());
471 // Update www.facebook.com login.
472 PasswordForm new_facebook(form_facebook_);
473 const base::string16 kOldPassword(form_facebook_.password_value);
474 const base::string16 kNewPassword(UTF8ToUTF16("new_b"));
475 EXPECT_NE(kOldPassword, kNewPassword);
476 new_facebook.password_value = kNewPassword;
477 scoped_ptr<PasswordStoreChangeList> not_used(new PasswordStoreChangeList());
478 switch (update_type) {
479 case UPDATE_BY_UPDATELOGIN:
480 backend.UpdateLogin(new_facebook, not_used.get());
481 break;
482 case UPDATE_BY_ADDLOGIN:
483 backend.AddLogin(new_facebook);
484 break;
487 EXPECT_EQ(2u, global_mock_libsecret_items->size());
489 // Check that m.facebook.com login was not modified by the update.
490 backend.GetLogins(m_facebook_lookup, &form_list);
492 // There should be two results -- the exact one, and the PSL-matched one.
493 EXPECT_EQ(2u, form_list.size());
494 size_t index_non_psl = 0;
495 if (!form_list[index_non_psl]->original_signon_realm.empty())
496 index_non_psl = 1;
497 EXPECT_EQ(kMobileURL, form_list[index_non_psl]->origin);
498 EXPECT_EQ(kMobileURL.spec(), form_list[index_non_psl]->signon_realm);
499 EXPECT_EQ(kOldPassword, form_list[index_non_psl]->password_value);
500 form_list.clear();
502 // Check that www.facebook.com login was modified by the update.
503 backend.GetLogins(form_facebook_, &form_list);
504 // There should be two results -- the exact one, and the PSL-matched one.
505 EXPECT_EQ(2u, form_list.size());
506 index_non_psl = 0;
507 if (!form_list[index_non_psl]->original_signon_realm.empty())
508 index_non_psl = 1;
509 EXPECT_EQ(form_facebook_.origin, form_list[index_non_psl]->origin);
510 EXPECT_EQ(form_facebook_.signon_realm,
511 form_list[index_non_psl]->signon_realm);
512 EXPECT_EQ(kNewPassword, form_list[index_non_psl]->password_value);
513 form_list.clear();
516 // Checks various types of matching for forms with a non-HTML |scheme|.
517 void CheckMatchingWithScheme(const PasswordForm::Scheme& scheme) {
518 ASSERT_NE(PasswordForm::SCHEME_HTML, scheme);
519 other_auth_.scheme = scheme;
521 // Don't match a non-HTML form with an HTML form.
522 EXPECT_FALSE(
523 CheckCredentialAvailability(other_auth_, GURL("http://www.example.com"),
524 PasswordForm::SCHEME_HTML, nullptr));
525 // Don't match an HTML form with non-HTML auth form.
526 EXPECT_FALSE(CheckCredentialAvailability(
527 form_google_, GURL("http://www.google.com/"), scheme, nullptr));
528 // Don't match two different non-HTML auth forms with different origin.
529 EXPECT_FALSE(CheckCredentialAvailability(
530 other_auth_, GURL("http://first.example.com"), scheme, nullptr));
531 // Do match non-HTML forms from the same origin.
532 EXPECT_TRUE(CheckCredentialAvailability(
533 other_auth_, GURL("http://www.example.com/"), scheme, nullptr));
536 void CheckRemoveLoginsBetween(RemoveBetweenMethod date_to_test) {
537 NativeBackendLibsecret backend(42);
539 base::Time now = base::Time::Now();
540 base::Time next_day = now + base::TimeDelta::FromDays(1);
541 form_google_.date_synced = base::Time();
542 form_isc_.date_synced = base::Time();
543 form_google_.date_created = now;
544 form_isc_.date_created = now;
545 if (date_to_test == CREATED) {
546 form_google_.date_created = now;
547 form_isc_.date_created = next_day;
548 } else {
549 form_google_.date_synced = now;
550 form_isc_.date_synced = next_day;
553 backend.AddLogin(form_google_);
554 backend.AddLogin(form_isc_);
556 PasswordStoreChangeList expected_changes;
557 expected_changes.push_back(
558 PasswordStoreChange(PasswordStoreChange::REMOVE, form_google_));
559 PasswordStoreChangeList changes;
560 bool (NativeBackendLibsecret::*method)(
561 base::Time, base::Time, password_manager::PasswordStoreChangeList*) =
562 date_to_test == CREATED
563 ? &NativeBackendLibsecret::RemoveLoginsCreatedBetween
564 : &NativeBackendLibsecret::RemoveLoginsSyncedBetween;
566 EXPECT_TRUE(base::Bind(method, base::Unretained(&backend), base::Time(),
567 next_day, &changes).Run());
568 CheckPasswordChanges(expected_changes, changes);
570 EXPECT_EQ(1u, global_mock_libsecret_items->size());
571 if (!global_mock_libsecret_items->empty() > 0)
572 CheckMockSecretItem((*global_mock_libsecret_items)[0], form_isc_,
573 "chrome-42");
575 // Remove form_isc_.
576 expected_changes.clear();
577 expected_changes.push_back(
578 PasswordStoreChange(PasswordStoreChange::REMOVE, form_isc_));
580 EXPECT_TRUE(base::Bind(method, base::Unretained(&backend), next_day,
581 base::Time(), &changes).Run());
582 CheckPasswordChanges(expected_changes, changes);
584 EXPECT_TRUE(global_mock_libsecret_items->empty());
587 base::MessageLoopForUI message_loop_;
589 // Provide some test forms to avoid having to set them up in each test.
590 PasswordForm form_google_;
591 PasswordForm form_facebook_;
592 PasswordForm form_isc_;
593 PasswordForm other_auth_;
595 ScopedVector<MockSecretItem> mock_libsecret_items_;
598 TEST_F(NativeBackendLibsecretTest, BasicAddLogin) {
599 NativeBackendLibsecret backend(42);
601 backend.AddLogin(form_google_);
603 EXPECT_EQ(1u, global_mock_libsecret_items->size());
604 if (!global_mock_libsecret_items->empty())
605 CheckMockSecretItem((*global_mock_libsecret_items)[0], form_google_,
606 "chrome-42");
609 TEST_F(NativeBackendLibsecretTest, BasicListLogins) {
610 NativeBackendLibsecret backend(42);
612 backend.AddLogin(form_google_);
614 ScopedVector<autofill::PasswordForm> form_list;
615 backend.GetAutofillableLogins(&form_list);
617 // Quick check that we got something back.
618 EXPECT_EQ(1u, form_list.size());
619 form_list.clear();
621 EXPECT_EQ(1u, global_mock_libsecret_items->size());
622 if (!global_mock_libsecret_items->empty())
623 CheckMockSecretItem((*global_mock_libsecret_items)[0], form_google_,
624 "chrome-42");
627 // Save a password for www.facebook.com and see it suggested for m.facebook.com.
628 TEST_F(NativeBackendLibsecretTest, PSLMatchingPositive) {
629 PasswordForm result;
630 const GURL kMobileURL("http://m.facebook.com/");
631 EXPECT_TRUE(CheckCredentialAvailability(form_facebook_, kMobileURL,
632 PasswordForm::SCHEME_HTML, &result));
633 EXPECT_EQ(kMobileURL, result.origin);
634 EXPECT_EQ(kMobileURL.spec(), result.signon_realm);
637 // Save a password for www.facebook.com and see it not suggested for
638 // m-facebook.com.
639 TEST_F(NativeBackendLibsecretTest, PSLMatchingNegativeDomainMismatch) {
640 EXPECT_FALSE(CheckCredentialAvailability(form_facebook_,
641 GURL("http://m-facebook.com/"),
642 PasswordForm::SCHEME_HTML, nullptr));
645 // Test PSL matching is off for domains excluded from it.
646 TEST_F(NativeBackendLibsecretTest, PSLMatchingDisabledDomains) {
647 EXPECT_FALSE(CheckCredentialAvailability(form_google_,
648 GURL("http://one.google.com/"),
649 PasswordForm::SCHEME_HTML, nullptr));
652 // Make sure PSL matches aren't available for non-HTML forms.
653 TEST_F(NativeBackendLibsecretTest, PSLMatchingDisabledForNonHTMLForms) {
654 CheckMatchingWithScheme(PasswordForm::SCHEME_BASIC);
655 CheckMatchingWithScheme(PasswordForm::SCHEME_DIGEST);
656 CheckMatchingWithScheme(PasswordForm::SCHEME_OTHER);
659 TEST_F(NativeBackendLibsecretTest, PSLUpdatingStrictUpdateLogin) {
660 CheckPSLUpdate(UPDATE_BY_UPDATELOGIN);
663 TEST_F(NativeBackendLibsecretTest, PSLUpdatingStrictAddLogin) {
664 // TODO(vabr): if AddLogin becomes no longer valid for existing logins, then
665 // just delete this test.
666 CheckPSLUpdate(UPDATE_BY_ADDLOGIN);
669 TEST_F(NativeBackendLibsecretTest, BasicUpdateLogin) {
670 NativeBackendLibsecret backend(42);
672 backend.AddLogin(form_google_);
674 PasswordForm new_form_google(form_google_);
675 new_form_google.times_used = 1;
676 new_form_google.action = GURL("http://www.google.com/different/login");
678 EXPECT_EQ(1u, global_mock_libsecret_items->size());
679 if (!global_mock_libsecret_items->empty())
680 CheckMockSecretItem((*global_mock_libsecret_items)[0], form_google_,
681 "chrome-42");
683 // Update login
684 PasswordStoreChangeList changes;
685 backend.UpdateLogin(new_form_google, &changes);
687 ASSERT_EQ(1u, changes.size());
688 EXPECT_EQ(PasswordStoreChange::UPDATE, changes.front().type());
689 EXPECT_EQ(new_form_google, changes.front().form());
690 EXPECT_EQ(1u, global_mock_libsecret_items->size());
691 if (!global_mock_libsecret_items->empty())
692 CheckMockSecretItem((*global_mock_libsecret_items)[0], new_form_google,
693 "chrome-42");
696 TEST_F(NativeBackendLibsecretTest, BasicRemoveLogin) {
697 NativeBackendLibsecret backend(42);
699 backend.AddLogin(form_google_);
701 EXPECT_EQ(1u, global_mock_libsecret_items->size());
702 if (!global_mock_libsecret_items->empty())
703 CheckMockSecretItem((*global_mock_libsecret_items)[0], form_google_,
704 "chrome-42");
706 backend.RemoveLogin(form_google_);
708 EXPECT_TRUE(global_mock_libsecret_items->empty());
711 // Verify fix for http://crbug.com/408783.
712 TEST_F(NativeBackendLibsecretTest, RemoveLoginActionMismatch) {
713 NativeBackendLibsecret backend(42);
715 backend.AddLogin(form_google_);
717 EXPECT_EQ(1u, global_mock_libsecret_items->size());
718 if (!global_mock_libsecret_items->empty())
719 CheckMockSecretItem((*global_mock_libsecret_items)[0], form_google_,
720 "chrome-42");
722 // Action url match not required for removal.
723 form_google_.action = GURL("https://some.other.url.com/path");
725 backend.RemoveLogin(form_google_);
727 EXPECT_TRUE(global_mock_libsecret_items->empty());
730 TEST_F(NativeBackendLibsecretTest, RemoveNonexistentLogin) {
731 NativeBackendLibsecret backend(42);
733 // First add an unrelated login.
734 backend.AddLogin(form_google_);
736 EXPECT_EQ(1u, global_mock_libsecret_items->size());
737 if (!global_mock_libsecret_items->empty())
738 CheckMockSecretItem((*global_mock_libsecret_items)[0], form_google_,
739 "chrome-42");
741 // Attempt to remove a login that doesn't exist.
742 backend.RemoveLogin(form_isc_);
744 // Make sure we can still get the first form back.
745 ScopedVector<autofill::PasswordForm> form_list;
746 backend.GetAutofillableLogins(&form_list);
748 // Quick check that we got something back.
749 EXPECT_EQ(1u, form_list.size());
750 form_list.clear();
752 EXPECT_EQ(1u, global_mock_libsecret_items->size());
753 if (!global_mock_libsecret_items->empty())
754 CheckMockSecretItem((*global_mock_libsecret_items)[0], form_google_,
755 "chrome-42");
758 TEST_F(NativeBackendLibsecretTest, UpdateNonexistentLogin) {
759 NativeBackendLibsecret backend(42);
761 // First add an unrelated login.
762 backend.AddLogin(form_google_);
764 EXPECT_EQ(1u, global_mock_libsecret_items->size());
765 if (!global_mock_libsecret_items->empty())
766 CheckMockSecretItem((*global_mock_libsecret_items)[0], form_google_,
767 "chrome-42");
769 // Attempt to update a login that doesn't exist.
770 PasswordStoreChangeList changes;
771 backend.UpdateLogin(form_isc_, &changes);
773 EXPECT_EQ(PasswordStoreChangeList(), changes);
774 EXPECT_EQ(1u, global_mock_libsecret_items->size());
775 if (!global_mock_libsecret_items->empty())
776 CheckMockSecretItem((*global_mock_libsecret_items)[0], form_google_,
777 "chrome-42");
780 TEST_F(NativeBackendLibsecretTest, AddDuplicateLogin) {
781 NativeBackendLibsecret backend(42);
783 PasswordStoreChangeList expected_changes, actual_changes;
784 expected_changes.push_back(
785 PasswordStoreChange(PasswordStoreChange::ADD, form_google_));
786 actual_changes = backend.AddLogin(form_google_);
787 CheckPasswordChanges(expected_changes, actual_changes);
789 expected_changes.clear();
790 expected_changes.push_back(
791 PasswordStoreChange(PasswordStoreChange::REMOVE, form_google_));
792 form_google_.times_used++;
793 expected_changes.push_back(
794 PasswordStoreChange(PasswordStoreChange::ADD, form_google_));
796 actual_changes = backend.AddLogin(form_google_);
797 CheckPasswordChanges(expected_changes, actual_changes);
799 EXPECT_EQ(1u, global_mock_libsecret_items->size());
800 if (!global_mock_libsecret_items->empty())
801 CheckMockSecretItem((*global_mock_libsecret_items)[0], form_google_,
802 "chrome-42");
805 TEST_F(NativeBackendLibsecretTest, AndroidCredentials) {
806 NativeBackendLibsecret backend(42);
807 backend.Init();
809 PasswordForm observed_android_form;
810 observed_android_form.scheme = PasswordForm::SCHEME_HTML;
811 observed_android_form.signon_realm =
812 "android://7x7IDboo8u9YKraUsbmVkuf1-@net.rateflix.app/";
813 PasswordForm saved_android_form = observed_android_form;
814 saved_android_form.username_value = base::UTF8ToUTF16("randomusername");
815 saved_android_form.password_value = base::UTF8ToUTF16("password");
816 saved_android_form.date_created = base::Time::Now();
818 backend.AddLogin(saved_android_form);
820 ScopedVector<autofill::PasswordForm> form_list;
821 backend.GetAutofillableLogins(&form_list);
823 EXPECT_EQ(1u, form_list.size());
824 EXPECT_EQ(saved_android_form, *form_list[0]);
827 TEST_F(NativeBackendLibsecretTest, RemoveLoginsCreatedBetween) {
828 CheckRemoveLoginsBetween(CREATED);
831 TEST_F(NativeBackendLibsecretTest, RemoveLoginsSyncedBetween) {
832 CheckRemoveLoginsBetween(SYNCED);
835 TEST_F(NativeBackendLibsecretTest, SomeKeyringAttributesAreMissing) {
836 // Absent attributes should be filled with default values.
837 NativeBackendLibsecret backend(42);
839 backend.AddLogin(form_google_);
841 EXPECT_EQ(1u, global_mock_libsecret_items->size());
842 // Remove a string attribute.
843 (*global_mock_libsecret_items)[0]->RemoveAttribute("avatar_url");
844 // Remove an integer attribute.
845 (*global_mock_libsecret_items)[0]->RemoveAttribute("ssl_valid");
847 ScopedVector<autofill::PasswordForm> form_list;
848 backend.GetAutofillableLogins(&form_list);
850 EXPECT_EQ(1u, form_list.size());
851 EXPECT_EQ(GURL(""), form_list[0]->icon_url);
852 EXPECT_FALSE(form_list[0]->ssl_valid);
855 // TODO(mdm): add more basic tests here at some point.