Check USB device path access when prompting users to select a device.
[chromium-blink-merge.git] / chrome / browser / password_manager / native_backend_libsecret_unittest.cc
blob9ebd4f6873ff2f007ed6557a8c42034af1bf7543
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);
155 ScopedVector<MockSecretItem> kept_mock_libsecret_items;
156 kept_mock_libsecret_items.reserve(global_mock_libsecret_items->size());
157 for (auto& item : *global_mock_libsecret_items) {
158 if (!Matches(item, attributes)) {
159 kept_mock_libsecret_items.push_back(item);
160 item = nullptr;
163 global_mock_libsecret_items->swap(kept_mock_libsecret_items);
165 g_hash_table_unref(attributes);
166 return true;
169 MockSecretValue* mock_secret_item_get_secret(MockSecretItem* self) {
170 return self->value;
173 const gchar* mock_secret_value_get_text(MockSecretValue* value) {
174 return value->password;
177 GHashTable* mock_secret_item_get_attributes(MockSecretItem* self) {
178 // Libsecret backend will make unreference of received attributes, so in
179 // order to save them we need to increase their reference number.
180 g_hash_table_ref(self->attributes);
181 return self->attributes;
184 gboolean mock_secret_item_load_secret_sync(MockSecretItem* self,
185 GCancellable* cancellable,
186 GError** error) {
187 return true;
190 void mock_secret_value_unref(gpointer value) {
193 // Inherit to get access to protected fields.
194 class MockLibsecretLoader : public LibsecretLoader {
195 public:
196 static bool LoadMockLibsecret() {
197 secret_password_store_sync = &mock_secret_password_store_sync;
198 secret_service_search_sync = &mock_secret_service_search_sync;
199 secret_password_clear_sync = &mock_secret_password_clear_sync;
200 secret_item_get_secret =
201 (decltype(&::secret_item_get_secret)) & mock_secret_item_get_secret;
202 secret_value_get_text =
203 (decltype(&::secret_value_get_text)) & mock_secret_value_get_text;
204 secret_item_get_attributes = (decltype(&::secret_item_get_attributes)) &
205 mock_secret_item_get_attributes;
206 secret_item_load_secret_sync = (decltype(&::secret_item_load_secret_sync)) &
207 mock_secret_item_load_secret_sync;
208 secret_value_unref =
209 (decltype(&::secret_value_unref)) & mock_secret_value_unref;
210 libsecret_loaded = true;
211 // Reset the state of the mock library.
212 global_mock_libsecret_items->clear();
213 global_mock_libsecret_reject_local_ids = false;
214 return true;
218 void CheckPasswordChanges(const PasswordStoreChangeList& expected_list,
219 const PasswordStoreChangeList& actual_list) {
220 ASSERT_EQ(expected_list.size(), actual_list.size());
221 for (size_t i = 0; i < expected_list.size(); ++i) {
222 EXPECT_EQ(expected_list[i].type(), actual_list[i].type());
223 const PasswordForm& expected = expected_list[i].form();
224 const PasswordForm& actual = actual_list[i].form();
226 EXPECT_EQ(expected.origin, actual.origin);
227 EXPECT_EQ(expected.password_value, actual.password_value);
228 EXPECT_EQ(expected.action, actual.action);
229 EXPECT_EQ(expected.username_element, actual.username_element);
230 EXPECT_EQ(expected.username_value, actual.username_value);
231 EXPECT_EQ(expected.password_element, actual.password_element);
232 EXPECT_EQ(expected.submit_element, actual.submit_element);
233 EXPECT_EQ(expected.signon_realm, actual.signon_realm);
234 EXPECT_EQ(expected.ssl_valid, actual.ssl_valid);
235 EXPECT_EQ(expected.preferred, actual.preferred);
236 EXPECT_EQ(expected.date_created, actual.date_created);
237 EXPECT_EQ(expected.blacklisted_by_user, actual.blacklisted_by_user);
238 EXPECT_EQ(expected.type, actual.type);
239 EXPECT_EQ(expected.times_used, actual.times_used);
240 EXPECT_EQ(expected.scheme, actual.scheme);
241 EXPECT_EQ(expected.date_synced, actual.date_synced);
242 EXPECT_EQ(expected.display_name, actual.display_name);
243 EXPECT_EQ(expected.avatar_url, actual.avatar_url);
244 EXPECT_EQ(expected.federation_url, actual.federation_url);
245 EXPECT_EQ(expected.skip_zero_click, actual.skip_zero_click);
246 EXPECT_EQ(expected.generation_upload_status,
247 actual.generation_upload_status);
251 } // anonymous namespace
253 class NativeBackendLibsecretTest : public testing::Test {
254 protected:
255 enum UpdateType { // Used in CheckPSLUpdate().
256 UPDATE_BY_UPDATELOGIN,
257 UPDATE_BY_ADDLOGIN,
259 enum RemoveBetweenMethod { // Used in CheckRemoveLoginsBetween().
260 CREATED,
261 SYNCED,
264 NativeBackendLibsecretTest() {}
266 void SetUp() override {
267 ASSERT_FALSE(global_mock_libsecret_items);
268 global_mock_libsecret_items = &mock_libsecret_items_;
270 ASSERT_TRUE(MockLibsecretLoader::LoadMockLibsecret());
272 form_google_.origin = GURL("http://www.google.com/");
273 form_google_.action = GURL("http://www.google.com/login");
274 form_google_.username_element = UTF8ToUTF16("user");
275 form_google_.username_value = UTF8ToUTF16("joeschmoe");
276 form_google_.password_element = UTF8ToUTF16("pass");
277 form_google_.password_value = UTF8ToUTF16("seekrit");
278 form_google_.submit_element = UTF8ToUTF16("submit");
279 form_google_.signon_realm = "http://www.google.com/";
280 form_google_.type = PasswordForm::TYPE_GENERATED;
281 form_google_.date_created = base::Time::Now();
282 form_google_.date_synced = base::Time::Now();
283 form_google_.display_name = UTF8ToUTF16("Joe Schmoe");
284 form_google_.avatar_url = GURL("http://www.google.com/avatar");
285 form_google_.federation_url = GURL("http://www.google.com/federation_url");
286 form_google_.skip_zero_click = true;
287 form_google_.generation_upload_status = PasswordForm::POSITIVE_SIGNAL_SENT;
288 form_google_.form_data.name = UTF8ToUTF16("form_name");
289 form_google_.form_data.user_submitted = true;
291 form_facebook_.origin = GURL("http://www.facebook.com/");
292 form_facebook_.action = GURL("http://www.facebook.com/login");
293 form_facebook_.username_element = UTF8ToUTF16("user");
294 form_facebook_.username_value = UTF8ToUTF16("a");
295 form_facebook_.password_element = UTF8ToUTF16("password");
296 form_facebook_.password_value = UTF8ToUTF16("b");
297 form_facebook_.submit_element = UTF8ToUTF16("submit");
298 form_facebook_.signon_realm = "http://www.facebook.com/";
299 form_facebook_.date_created = base::Time::Now();
300 form_facebook_.date_synced = base::Time::Now();
301 form_facebook_.display_name = UTF8ToUTF16("Joe Schmoe");
302 form_facebook_.avatar_url = GURL("http://www.facebook.com/avatar");
303 form_facebook_.federation_url = GURL("http://www.facebook.com/federation");
304 form_facebook_.skip_zero_click = true;
305 form_facebook_.generation_upload_status = PasswordForm::NO_SIGNAL_SENT;
307 form_isc_.origin = GURL("http://www.isc.org/");
308 form_isc_.action = GURL("http://www.isc.org/auth");
309 form_isc_.username_element = UTF8ToUTF16("id");
310 form_isc_.username_value = UTF8ToUTF16("janedoe");
311 form_isc_.password_element = UTF8ToUTF16("passwd");
312 form_isc_.password_value = UTF8ToUTF16("ihazabukkit");
313 form_isc_.submit_element = UTF8ToUTF16("login");
314 form_isc_.signon_realm = "http://www.isc.org/";
315 form_isc_.date_created = base::Time::Now();
316 form_isc_.date_synced = base::Time::Now();
318 other_auth_.origin = GURL("http://www.example.com/");
319 other_auth_.username_value = UTF8ToUTF16("username");
320 other_auth_.password_value = UTF8ToUTF16("pass");
321 other_auth_.signon_realm = "http://www.example.com/Realm";
322 other_auth_.date_created = base::Time::Now();
323 other_auth_.date_synced = base::Time::Now();
326 void TearDown() override {
327 base::MessageLoop::current()->PostTask(FROM_HERE,
328 base::MessageLoop::QuitClosure());
329 base::MessageLoop::current()->Run();
330 ASSERT_TRUE(global_mock_libsecret_items);
331 global_mock_libsecret_items = nullptr;
334 void RunUIThread() { base::MessageLoop::current()->Run(); }
336 void CheckUint32Attribute(const MockSecretItem* item,
337 const std::string& attribute,
338 uint32_t value) {
339 gpointer item_value =
340 g_hash_table_lookup(item->attributes, attribute.c_str());
341 EXPECT_TRUE(item_value) << " in attribute " << attribute;
342 if (item_value) {
343 uint32_t int_value;
344 bool conversion_ok = base::StringToUint((char*)item_value, &int_value);
345 EXPECT_TRUE(conversion_ok);
346 EXPECT_EQ(value, int_value);
350 void CheckStringAttribute(const MockSecretItem* item,
351 const std::string& attribute,
352 const std::string& value) {
353 gpointer item_value =
354 g_hash_table_lookup(item->attributes, attribute.c_str());
355 EXPECT_TRUE(item_value) << " in attribute " << attribute;
356 if (item_value) {
357 EXPECT_EQ(value, static_cast<char*>(item_value));
361 void CheckMockSecretItem(const MockSecretItem* item,
362 const PasswordForm& form,
363 const std::string& app_string) {
364 EXPECT_EQ(UTF16ToUTF8(form.password_value), item->value->password);
365 EXPECT_EQ(22u, g_hash_table_size(item->attributes));
366 CheckStringAttribute(item, "origin_url", form.origin.spec());
367 CheckStringAttribute(item, "action_url", form.action.spec());
368 CheckStringAttribute(item, "username_element",
369 UTF16ToUTF8(form.username_element));
370 CheckStringAttribute(item, "username_value",
371 UTF16ToUTF8(form.username_value));
372 CheckStringAttribute(item, "password_element",
373 UTF16ToUTF8(form.password_element));
374 CheckStringAttribute(item, "submit_element",
375 UTF16ToUTF8(form.submit_element));
376 CheckStringAttribute(item, "signon_realm", form.signon_realm);
377 CheckUint32Attribute(item, "ssl_valid", form.ssl_valid);
378 CheckUint32Attribute(item, "preferred", form.preferred);
379 // We don't check the date created. It varies.
380 CheckUint32Attribute(item, "blacklisted_by_user", form.blacklisted_by_user);
381 CheckUint32Attribute(item, "type", form.type);
382 CheckUint32Attribute(item, "times_used", form.times_used);
383 CheckUint32Attribute(item, "scheme", form.scheme);
384 CheckStringAttribute(
385 item, "date_synced",
386 base::Int64ToString(form.date_synced.ToInternalValue()));
387 CheckStringAttribute(item, "display_name", UTF16ToUTF8(form.display_name));
388 CheckStringAttribute(item, "avatar_url", form.avatar_url.spec());
389 CheckStringAttribute(item, "federation_url", form.federation_url.spec());
390 CheckUint32Attribute(item, "skip_zero_click", form.skip_zero_click);
391 CheckUint32Attribute(item, "generation_upload_status",
392 form.generation_upload_status);
393 CheckStringAttribute(item, "application", app_string);
394 autofill::FormData actual;
395 DeserializeFormDataFromBase64String(
396 static_cast<char*>(g_hash_table_lookup(item->attributes, "form_data")),
397 &actual);
398 EXPECT_TRUE(form.form_data.SameFormAs(actual));
401 // Saves |credentials| and then gets logins matching |url| and |scheme|.
402 // Returns true when something is found, and in such case copies the result to
403 // |result| when |result| is not nullptr. (Note that there can be max. 1
404 // result derived from |credentials|.)
405 bool CheckCredentialAvailability(const PasswordForm& credentials,
406 const GURL& url,
407 const PasswordForm::Scheme& scheme,
408 PasswordForm* result) {
409 NativeBackendLibsecret backend(321);
411 backend.AddLogin(credentials);
413 PasswordForm target_form;
414 target_form.origin = url;
415 target_form.signon_realm = url.spec();
416 if (scheme != PasswordForm::SCHEME_HTML) {
417 // For non-HTML forms, the realm used for authentication
418 // (http://tools.ietf.org/html/rfc1945#section-10.2) is appended to the
419 // signon_realm. Just use a default value for now.
420 target_form.signon_realm.append("Realm");
421 target_form.scheme = scheme;
423 ScopedVector<autofill::PasswordForm> form_list;
424 backend.GetLogins(target_form, &form_list);
426 EXPECT_EQ(1u, global_mock_libsecret_items->size());
427 if (!global_mock_libsecret_items->empty())
428 CheckMockSecretItem((*global_mock_libsecret_items)[0], credentials,
429 "chrome-321");
430 global_mock_libsecret_items->clear();
432 if (form_list.empty())
433 return false;
434 EXPECT_EQ(1u, form_list.size());
435 if (result)
436 *result = *form_list[0];
437 return true;
440 // Test that updating does not use PSL matching: Add a www.facebook.com
441 // password, then use PSL matching to get a copy of it for m.facebook.com, and
442 // add that copy as well. Now update the www.facebook.com password -- the
443 // m.facebook.com password should not get updated. Depending on the argument,
444 // the credential update is done via UpdateLogin or AddLogin.
445 void CheckPSLUpdate(UpdateType update_type) {
446 NativeBackendLibsecret backend(321);
448 backend.AddLogin(form_facebook_);
450 // Get the PSL-matched copy of the saved login for m.facebook.
451 const GURL kMobileURL("http://m.facebook.com/");
452 PasswordForm m_facebook_lookup;
453 m_facebook_lookup.origin = kMobileURL;
454 m_facebook_lookup.signon_realm = kMobileURL.spec();
455 ScopedVector<autofill::PasswordForm> form_list;
456 backend.GetLogins(m_facebook_lookup, &form_list);
458 EXPECT_EQ(1u, global_mock_libsecret_items->size());
459 EXPECT_EQ(1u, form_list.size());
460 PasswordForm m_facebook = *form_list[0];
461 form_list.clear();
462 EXPECT_EQ(kMobileURL, m_facebook.origin);
463 EXPECT_EQ(kMobileURL.spec(), m_facebook.signon_realm);
465 // Add the PSL-matched copy to saved logins.
466 backend.AddLogin(m_facebook);
467 EXPECT_EQ(2u, global_mock_libsecret_items->size());
469 // Update www.facebook.com login.
470 PasswordForm new_facebook(form_facebook_);
471 const base::string16 kOldPassword(form_facebook_.password_value);
472 const base::string16 kNewPassword(UTF8ToUTF16("new_b"));
473 EXPECT_NE(kOldPassword, kNewPassword);
474 new_facebook.password_value = kNewPassword;
475 scoped_ptr<PasswordStoreChangeList> not_used(new PasswordStoreChangeList());
476 switch (update_type) {
477 case UPDATE_BY_UPDATELOGIN:
478 backend.UpdateLogin(new_facebook, not_used.get());
479 break;
480 case UPDATE_BY_ADDLOGIN:
481 backend.AddLogin(new_facebook);
482 break;
485 EXPECT_EQ(2u, global_mock_libsecret_items->size());
487 // Check that m.facebook.com login was not modified by the update.
488 backend.GetLogins(m_facebook_lookup, &form_list);
490 // There should be two results -- the exact one, and the PSL-matched one.
491 EXPECT_EQ(2u, form_list.size());
492 size_t index_non_psl = 0;
493 if (!form_list[index_non_psl]->original_signon_realm.empty())
494 index_non_psl = 1;
495 EXPECT_EQ(kMobileURL, form_list[index_non_psl]->origin);
496 EXPECT_EQ(kMobileURL.spec(), form_list[index_non_psl]->signon_realm);
497 EXPECT_EQ(kOldPassword, form_list[index_non_psl]->password_value);
498 form_list.clear();
500 // Check that www.facebook.com login was modified by the update.
501 backend.GetLogins(form_facebook_, &form_list);
502 // There should be two results -- the exact one, and the PSL-matched one.
503 EXPECT_EQ(2u, form_list.size());
504 index_non_psl = 0;
505 if (!form_list[index_non_psl]->original_signon_realm.empty())
506 index_non_psl = 1;
507 EXPECT_EQ(form_facebook_.origin, form_list[index_non_psl]->origin);
508 EXPECT_EQ(form_facebook_.signon_realm,
509 form_list[index_non_psl]->signon_realm);
510 EXPECT_EQ(kNewPassword, form_list[index_non_psl]->password_value);
511 form_list.clear();
514 // Checks various types of matching for forms with a non-HTML |scheme|.
515 void CheckMatchingWithScheme(const PasswordForm::Scheme& scheme) {
516 ASSERT_NE(PasswordForm::SCHEME_HTML, scheme);
517 other_auth_.scheme = scheme;
519 // Don't match a non-HTML form with an HTML form.
520 EXPECT_FALSE(
521 CheckCredentialAvailability(other_auth_, GURL("http://www.example.com"),
522 PasswordForm::SCHEME_HTML, nullptr));
523 // Don't match an HTML form with non-HTML auth form.
524 EXPECT_FALSE(CheckCredentialAvailability(
525 form_google_, GURL("http://www.google.com/"), scheme, nullptr));
526 // Don't match two different non-HTML auth forms with different origin.
527 EXPECT_FALSE(CheckCredentialAvailability(
528 other_auth_, GURL("http://first.example.com"), scheme, nullptr));
529 // Do match non-HTML forms from the same origin.
530 EXPECT_TRUE(CheckCredentialAvailability(
531 other_auth_, GURL("http://www.example.com/"), scheme, nullptr));
534 void CheckRemoveLoginsBetween(RemoveBetweenMethod date_to_test) {
535 NativeBackendLibsecret backend(42);
537 base::Time now = base::Time::Now();
538 base::Time next_day = now + base::TimeDelta::FromDays(1);
539 form_google_.date_synced = base::Time();
540 form_isc_.date_synced = base::Time();
541 form_google_.date_created = now;
542 form_isc_.date_created = now;
543 if (date_to_test == CREATED) {
544 form_google_.date_created = now;
545 form_isc_.date_created = next_day;
546 } else {
547 form_google_.date_synced = now;
548 form_isc_.date_synced = next_day;
551 backend.AddLogin(form_google_);
552 backend.AddLogin(form_isc_);
554 PasswordStoreChangeList expected_changes;
555 expected_changes.push_back(
556 PasswordStoreChange(PasswordStoreChange::REMOVE, form_google_));
557 PasswordStoreChangeList changes;
558 bool (NativeBackendLibsecret::*method)(
559 base::Time, base::Time, password_manager::PasswordStoreChangeList*) =
560 date_to_test == CREATED
561 ? &NativeBackendLibsecret::RemoveLoginsCreatedBetween
562 : &NativeBackendLibsecret::RemoveLoginsSyncedBetween;
564 EXPECT_TRUE(base::Bind(method, base::Unretained(&backend), base::Time(),
565 next_day, &changes).Run());
566 CheckPasswordChanges(expected_changes, changes);
568 EXPECT_EQ(1u, global_mock_libsecret_items->size());
569 if (!global_mock_libsecret_items->empty() > 0)
570 CheckMockSecretItem((*global_mock_libsecret_items)[0], form_isc_,
571 "chrome-42");
573 // Remove form_isc_.
574 expected_changes.clear();
575 expected_changes.push_back(
576 PasswordStoreChange(PasswordStoreChange::REMOVE, form_isc_));
578 EXPECT_TRUE(base::Bind(method, base::Unretained(&backend), next_day,
579 base::Time(), &changes).Run());
580 CheckPasswordChanges(expected_changes, changes);
582 EXPECT_TRUE(global_mock_libsecret_items->empty());
585 base::MessageLoopForUI message_loop_;
587 // Provide some test forms to avoid having to set them up in each test.
588 PasswordForm form_google_;
589 PasswordForm form_facebook_;
590 PasswordForm form_isc_;
591 PasswordForm other_auth_;
593 ScopedVector<MockSecretItem> mock_libsecret_items_;
596 TEST_F(NativeBackendLibsecretTest, BasicAddLogin) {
597 NativeBackendLibsecret backend(42);
599 backend.AddLogin(form_google_);
601 EXPECT_EQ(1u, global_mock_libsecret_items->size());
602 if (!global_mock_libsecret_items->empty())
603 CheckMockSecretItem((*global_mock_libsecret_items)[0], form_google_,
604 "chrome-42");
607 TEST_F(NativeBackendLibsecretTest, BasicListLogins) {
608 NativeBackendLibsecret backend(42);
610 backend.AddLogin(form_google_);
612 ScopedVector<autofill::PasswordForm> form_list;
613 backend.GetAutofillableLogins(&form_list);
615 // Quick check that we got something back.
616 EXPECT_EQ(1u, form_list.size());
617 form_list.clear();
619 EXPECT_EQ(1u, global_mock_libsecret_items->size());
620 if (!global_mock_libsecret_items->empty())
621 CheckMockSecretItem((*global_mock_libsecret_items)[0], form_google_,
622 "chrome-42");
625 // Save a password for www.facebook.com and see it suggested for m.facebook.com.
626 TEST_F(NativeBackendLibsecretTest, PSLMatchingPositive) {
627 PasswordForm result;
628 const GURL kMobileURL("http://m.facebook.com/");
629 EXPECT_TRUE(CheckCredentialAvailability(form_facebook_, kMobileURL,
630 PasswordForm::SCHEME_HTML, &result));
631 EXPECT_EQ(kMobileURL, result.origin);
632 EXPECT_EQ(kMobileURL.spec(), result.signon_realm);
635 // Save a password for www.facebook.com and see it not suggested for
636 // m-facebook.com.
637 TEST_F(NativeBackendLibsecretTest, PSLMatchingNegativeDomainMismatch) {
638 EXPECT_FALSE(CheckCredentialAvailability(form_facebook_,
639 GURL("http://m-facebook.com/"),
640 PasswordForm::SCHEME_HTML, nullptr));
643 // Test PSL matching is off for domains excluded from it.
644 TEST_F(NativeBackendLibsecretTest, PSLMatchingDisabledDomains) {
645 EXPECT_FALSE(CheckCredentialAvailability(form_google_,
646 GURL("http://one.google.com/"),
647 PasswordForm::SCHEME_HTML, nullptr));
650 // Make sure PSL matches aren't available for non-HTML forms.
651 TEST_F(NativeBackendLibsecretTest, PSLMatchingDisabledForNonHTMLForms) {
652 CheckMatchingWithScheme(PasswordForm::SCHEME_BASIC);
653 CheckMatchingWithScheme(PasswordForm::SCHEME_DIGEST);
654 CheckMatchingWithScheme(PasswordForm::SCHEME_OTHER);
657 TEST_F(NativeBackendLibsecretTest, PSLUpdatingStrictUpdateLogin) {
658 CheckPSLUpdate(UPDATE_BY_UPDATELOGIN);
661 TEST_F(NativeBackendLibsecretTest, PSLUpdatingStrictAddLogin) {
662 // TODO(vabr): if AddLogin becomes no longer valid for existing logins, then
663 // just delete this test.
664 CheckPSLUpdate(UPDATE_BY_ADDLOGIN);
667 TEST_F(NativeBackendLibsecretTest, BasicUpdateLogin) {
668 NativeBackendLibsecret backend(42);
670 backend.AddLogin(form_google_);
672 PasswordForm new_form_google(form_google_);
673 new_form_google.times_used = 1;
674 new_form_google.action = GURL("http://www.google.com/different/login");
676 EXPECT_EQ(1u, global_mock_libsecret_items->size());
677 if (!global_mock_libsecret_items->empty())
678 CheckMockSecretItem((*global_mock_libsecret_items)[0], form_google_,
679 "chrome-42");
681 // Update login
682 PasswordStoreChangeList changes;
683 backend.UpdateLogin(new_form_google, &changes);
685 ASSERT_EQ(1u, changes.size());
686 EXPECT_EQ(PasswordStoreChange::UPDATE, changes.front().type());
687 EXPECT_EQ(new_form_google, changes.front().form());
688 EXPECT_EQ(1u, global_mock_libsecret_items->size());
689 if (!global_mock_libsecret_items->empty())
690 CheckMockSecretItem((*global_mock_libsecret_items)[0], new_form_google,
691 "chrome-42");
694 TEST_F(NativeBackendLibsecretTest, BasicRemoveLogin) {
695 NativeBackendLibsecret backend(42);
697 backend.AddLogin(form_google_);
699 EXPECT_EQ(1u, global_mock_libsecret_items->size());
700 if (!global_mock_libsecret_items->empty())
701 CheckMockSecretItem((*global_mock_libsecret_items)[0], form_google_,
702 "chrome-42");
704 backend.RemoveLogin(form_google_);
706 EXPECT_TRUE(global_mock_libsecret_items->empty());
709 // Verify fix for http://crbug.com/408783.
710 TEST_F(NativeBackendLibsecretTest, RemoveLoginActionMismatch) {
711 NativeBackendLibsecret backend(42);
713 backend.AddLogin(form_google_);
715 EXPECT_EQ(1u, global_mock_libsecret_items->size());
716 if (!global_mock_libsecret_items->empty())
717 CheckMockSecretItem((*global_mock_libsecret_items)[0], form_google_,
718 "chrome-42");
720 // Action url match not required for removal.
721 form_google_.action = GURL("https://some.other.url.com/path");
723 backend.RemoveLogin(form_google_);
725 EXPECT_TRUE(global_mock_libsecret_items->empty());
728 TEST_F(NativeBackendLibsecretTest, RemoveNonexistentLogin) {
729 NativeBackendLibsecret backend(42);
731 // First add an unrelated login.
732 backend.AddLogin(form_google_);
734 EXPECT_EQ(1u, global_mock_libsecret_items->size());
735 if (!global_mock_libsecret_items->empty())
736 CheckMockSecretItem((*global_mock_libsecret_items)[0], form_google_,
737 "chrome-42");
739 // Attempt to remove a login that doesn't exist.
740 backend.RemoveLogin(form_isc_);
742 // Make sure we can still get the first form back.
743 ScopedVector<autofill::PasswordForm> form_list;
744 backend.GetAutofillableLogins(&form_list);
746 // Quick check that we got something back.
747 EXPECT_EQ(1u, form_list.size());
748 form_list.clear();
750 EXPECT_EQ(1u, global_mock_libsecret_items->size());
751 if (!global_mock_libsecret_items->empty())
752 CheckMockSecretItem((*global_mock_libsecret_items)[0], form_google_,
753 "chrome-42");
756 TEST_F(NativeBackendLibsecretTest, UpdateNonexistentLogin) {
757 NativeBackendLibsecret backend(42);
759 // First add an unrelated login.
760 backend.AddLogin(form_google_);
762 EXPECT_EQ(1u, global_mock_libsecret_items->size());
763 if (!global_mock_libsecret_items->empty())
764 CheckMockSecretItem((*global_mock_libsecret_items)[0], form_google_,
765 "chrome-42");
767 // Attempt to update a login that doesn't exist.
768 PasswordStoreChangeList changes;
769 backend.UpdateLogin(form_isc_, &changes);
771 EXPECT_EQ(PasswordStoreChangeList(), changes);
772 EXPECT_EQ(1u, global_mock_libsecret_items->size());
773 if (!global_mock_libsecret_items->empty())
774 CheckMockSecretItem((*global_mock_libsecret_items)[0], form_google_,
775 "chrome-42");
778 TEST_F(NativeBackendLibsecretTest, AddDuplicateLogin) {
779 NativeBackendLibsecret backend(42);
781 PasswordStoreChangeList expected_changes, actual_changes;
782 expected_changes.push_back(
783 PasswordStoreChange(PasswordStoreChange::ADD, form_google_));
784 actual_changes = backend.AddLogin(form_google_);
785 CheckPasswordChanges(expected_changes, actual_changes);
787 expected_changes.clear();
788 expected_changes.push_back(
789 PasswordStoreChange(PasswordStoreChange::REMOVE, form_google_));
790 form_google_.times_used++;
791 expected_changes.push_back(
792 PasswordStoreChange(PasswordStoreChange::ADD, form_google_));
794 actual_changes = backend.AddLogin(form_google_);
795 CheckPasswordChanges(expected_changes, actual_changes);
797 EXPECT_EQ(1u, global_mock_libsecret_items->size());
798 if (!global_mock_libsecret_items->empty())
799 CheckMockSecretItem((*global_mock_libsecret_items)[0], form_google_,
800 "chrome-42");
803 TEST_F(NativeBackendLibsecretTest, AndroidCredentials) {
804 NativeBackendLibsecret backend(42);
805 backend.Init();
807 PasswordForm observed_android_form;
808 observed_android_form.scheme = PasswordForm::SCHEME_HTML;
809 observed_android_form.signon_realm =
810 "android://7x7IDboo8u9YKraUsbmVkuf1-@net.rateflix.app/";
811 PasswordForm saved_android_form = observed_android_form;
812 saved_android_form.username_value = base::UTF8ToUTF16("randomusername");
813 saved_android_form.password_value = base::UTF8ToUTF16("password");
814 saved_android_form.date_created = base::Time::Now();
816 backend.AddLogin(saved_android_form);
818 ScopedVector<autofill::PasswordForm> form_list;
819 backend.GetAutofillableLogins(&form_list);
821 EXPECT_EQ(1u, form_list.size());
822 EXPECT_EQ(saved_android_form, *form_list[0]);
825 TEST_F(NativeBackendLibsecretTest, RemoveLoginsCreatedBetween) {
826 CheckRemoveLoginsBetween(CREATED);
829 TEST_F(NativeBackendLibsecretTest, RemoveLoginsSyncedBetween) {
830 CheckRemoveLoginsBetween(SYNCED);
833 TEST_F(NativeBackendLibsecretTest, SomeKeyringAttributesAreMissing) {
834 // Absent attributes should be filled with default values.
835 NativeBackendLibsecret backend(42);
837 backend.AddLogin(form_google_);
839 EXPECT_EQ(1u, global_mock_libsecret_items->size());
840 // Remove a string attribute.
841 (*global_mock_libsecret_items)[0]->RemoveAttribute("avatar_url");
842 // Remove an integer attribute.
843 (*global_mock_libsecret_items)[0]->RemoveAttribute("ssl_valid");
845 ScopedVector<autofill::PasswordForm> form_list;
846 backend.GetAutofillableLogins(&form_list);
848 EXPECT_EQ(1u, form_list.size());
849 EXPECT_EQ(GURL(""), form_list[0]->avatar_url);
850 EXPECT_FALSE(form_list[0]->ssl_valid);
853 // TODO(mdm): add more basic tests here at some point.