Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / chrome / browser / password_manager / native_backend_libsecret_unittest.cc
blob6b5c21d00e61d0d98c9a601bee9c17a77fe74c4c
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
170 global_mock_libsecret_items->size() != kept_mock_libsecret_items.size();
173 MockSecretValue* mock_secret_item_get_secret(MockSecretItem* self) {
174 return self->value;
177 const gchar* mock_secret_value_get_text(MockSecretValue* value) {
178 return value->password;
181 GHashTable* mock_secret_item_get_attributes(MockSecretItem* self) {
182 // Libsecret backend will make unreference of received attributes, so in
183 // order to save them we need to increase their reference number.
184 g_hash_table_ref(self->attributes);
185 return self->attributes;
188 gboolean mock_secret_item_load_secret_sync(MockSecretItem* self,
189 GCancellable* cancellable,
190 GError** error) {
191 return true;
194 void mock_secret_value_unref(gpointer value) {
197 // Inherit to get access to protected fields.
198 class MockLibsecretLoader : public LibsecretLoader {
199 public:
200 static bool LoadMockLibsecret() {
201 secret_password_store_sync = &mock_secret_password_store_sync;
202 secret_service_search_sync = &mock_secret_service_search_sync;
203 secret_password_clear_sync = &mock_secret_password_clear_sync;
204 secret_item_get_secret =
205 (decltype(&::secret_item_get_secret)) & mock_secret_item_get_secret;
206 secret_value_get_text =
207 (decltype(&::secret_value_get_text)) & mock_secret_value_get_text;
208 secret_item_get_attributes = (decltype(&::secret_item_get_attributes)) &
209 mock_secret_item_get_attributes;
210 secret_item_load_secret_sync = (decltype(&::secret_item_load_secret_sync)) &
211 mock_secret_item_load_secret_sync;
212 secret_value_unref =
213 (decltype(&::secret_value_unref)) & mock_secret_value_unref;
214 libsecret_loaded = true;
215 // Reset the state of the mock library.
216 global_mock_libsecret_items->clear();
217 global_mock_libsecret_reject_local_ids = false;
218 return true;
222 void CheckPasswordChanges(const PasswordStoreChangeList& expected_list,
223 const PasswordStoreChangeList& actual_list) {
224 ASSERT_EQ(expected_list.size(), actual_list.size());
225 for (size_t i = 0; i < expected_list.size(); ++i) {
226 EXPECT_EQ(expected_list[i].type(), actual_list[i].type());
227 EXPECT_EQ(expected_list[i].form(), actual_list[i].form());
231 void VerifiedAdd(NativeBackendLibsecret* backend, const PasswordForm& form) {
232 SCOPED_TRACE("VerifiedAdd");
233 PasswordStoreChangeList changes = backend->AddLogin(form);
234 PasswordStoreChangeList expected(1,
235 PasswordStoreChange(PasswordStoreChange::ADD,
236 form));
237 CheckPasswordChanges(expected, changes);
240 void VerifiedUpdate(NativeBackendLibsecret* backend, const PasswordForm& form) {
241 SCOPED_TRACE("VerifiedUpdate");
242 PasswordStoreChangeList changes;
243 EXPECT_TRUE(backend->UpdateLogin(form, &changes));
244 PasswordStoreChangeList expected(1, PasswordStoreChange(
245 PasswordStoreChange::UPDATE, form));
246 CheckPasswordChanges(expected, changes);
249 void VerifiedRemove(NativeBackendLibsecret* backend, const PasswordForm& form) {
250 SCOPED_TRACE("VerifiedRemove");
251 PasswordStoreChangeList changes;
252 EXPECT_TRUE(backend->RemoveLogin(form, &changes));
253 CheckPasswordChanges(PasswordStoreChangeList(1, PasswordStoreChange(
254 PasswordStoreChange::REMOVE, form)), changes);
257 } // anonymous namespace
259 class NativeBackendLibsecretTest : public testing::Test {
260 protected:
261 enum UpdateType { // Used in CheckPSLUpdate().
262 UPDATE_BY_UPDATELOGIN,
263 UPDATE_BY_ADDLOGIN,
265 enum RemoveBetweenMethod { // Used in CheckRemoveLoginsBetween().
266 CREATED,
267 SYNCED,
270 NativeBackendLibsecretTest() {}
272 void SetUp() override {
273 ASSERT_FALSE(global_mock_libsecret_items);
274 global_mock_libsecret_items = &mock_libsecret_items_;
276 ASSERT_TRUE(MockLibsecretLoader::LoadMockLibsecret());
278 form_google_.origin = GURL("http://www.google.com/");
279 form_google_.action = GURL("http://www.google.com/login");
280 form_google_.username_element = UTF8ToUTF16("user");
281 form_google_.username_value = UTF8ToUTF16("joeschmoe");
282 form_google_.password_element = UTF8ToUTF16("pass");
283 form_google_.password_value = UTF8ToUTF16("seekrit");
284 form_google_.submit_element = UTF8ToUTF16("submit");
285 form_google_.signon_realm = "http://www.google.com/";
286 form_google_.type = PasswordForm::TYPE_GENERATED;
287 form_google_.date_created = base::Time::Now();
288 form_google_.date_synced = base::Time::Now();
289 form_google_.display_name = UTF8ToUTF16("Joe Schmoe");
290 form_google_.icon_url = GURL("http://www.google.com/icon");
291 form_google_.federation_url = GURL("http://www.google.com/federation_url");
292 form_google_.skip_zero_click = true;
293 form_google_.generation_upload_status = PasswordForm::POSITIVE_SIGNAL_SENT;
294 form_google_.form_data.name = UTF8ToUTF16("form_name");
296 form_facebook_.origin = GURL("http://www.facebook.com/");
297 form_facebook_.action = GURL("http://www.facebook.com/login");
298 form_facebook_.username_element = UTF8ToUTF16("user");
299 form_facebook_.username_value = UTF8ToUTF16("a");
300 form_facebook_.password_element = UTF8ToUTF16("password");
301 form_facebook_.password_value = UTF8ToUTF16("b");
302 form_facebook_.submit_element = UTF8ToUTF16("submit");
303 form_facebook_.signon_realm = "http://www.facebook.com/";
304 form_facebook_.date_created = base::Time::Now();
305 form_facebook_.date_synced = base::Time::Now();
306 form_facebook_.display_name = UTF8ToUTF16("Joe Schmoe");
307 form_facebook_.icon_url = GURL("http://www.facebook.com/icon");
308 form_facebook_.federation_url = GURL("http://www.facebook.com/federation");
309 form_facebook_.skip_zero_click = true;
310 form_facebook_.generation_upload_status = PasswordForm::NO_SIGNAL_SENT;
312 form_isc_.origin = GURL("http://www.isc.org/");
313 form_isc_.action = GURL("http://www.isc.org/auth");
314 form_isc_.username_element = UTF8ToUTF16("id");
315 form_isc_.username_value = UTF8ToUTF16("janedoe");
316 form_isc_.password_element = UTF8ToUTF16("passwd");
317 form_isc_.password_value = UTF8ToUTF16("ihazabukkit");
318 form_isc_.submit_element = UTF8ToUTF16("login");
319 form_isc_.signon_realm = "http://www.isc.org/";
320 form_isc_.date_created = base::Time::Now();
321 form_isc_.date_synced = base::Time::Now();
323 other_auth_.origin = GURL("http://www.example.com/");
324 other_auth_.username_value = UTF8ToUTF16("username");
325 other_auth_.password_value = UTF8ToUTF16("pass");
326 other_auth_.signon_realm = "http://www.example.com/Realm";
327 other_auth_.date_created = base::Time::Now();
328 other_auth_.date_synced = base::Time::Now();
331 void TearDown() override {
332 base::ThreadTaskRunnerHandle::Get()->PostTask(
333 FROM_HERE, base::MessageLoop::QuitClosure());
334 base::MessageLoop::current()->Run();
335 ASSERT_TRUE(global_mock_libsecret_items);
336 global_mock_libsecret_items = nullptr;
339 void RunUIThread() { base::MessageLoop::current()->Run(); }
341 void CheckUint32Attribute(const MockSecretItem* item,
342 const std::string& attribute,
343 uint32_t value) {
344 gpointer item_value =
345 g_hash_table_lookup(item->attributes, attribute.c_str());
346 EXPECT_TRUE(item_value) << " in attribute " << attribute;
347 if (item_value) {
348 uint32_t int_value;
349 bool conversion_ok = base::StringToUint((char*)item_value, &int_value);
350 EXPECT_TRUE(conversion_ok);
351 EXPECT_EQ(value, int_value);
355 void CheckStringAttribute(const MockSecretItem* item,
356 const std::string& attribute,
357 const std::string& value) {
358 gpointer item_value =
359 g_hash_table_lookup(item->attributes, attribute.c_str());
360 EXPECT_TRUE(item_value) << " in attribute " << attribute;
361 if (item_value) {
362 EXPECT_EQ(value, static_cast<char*>(item_value));
366 void CheckMockSecretItem(const MockSecretItem* item,
367 const PasswordForm& form,
368 const std::string& app_string) {
369 EXPECT_EQ(UTF16ToUTF8(form.password_value), item->value->password);
370 EXPECT_EQ(22u, g_hash_table_size(item->attributes));
371 CheckStringAttribute(item, "origin_url", form.origin.spec());
372 CheckStringAttribute(item, "action_url", form.action.spec());
373 CheckStringAttribute(item, "username_element",
374 UTF16ToUTF8(form.username_element));
375 CheckStringAttribute(item, "username_value",
376 UTF16ToUTF8(form.username_value));
377 CheckStringAttribute(item, "password_element",
378 UTF16ToUTF8(form.password_element));
379 CheckStringAttribute(item, "submit_element",
380 UTF16ToUTF8(form.submit_element));
381 CheckStringAttribute(item, "signon_realm", form.signon_realm);
382 CheckUint32Attribute(item, "ssl_valid", form.ssl_valid);
383 CheckUint32Attribute(item, "preferred", form.preferred);
384 // We don't check the date created. It varies.
385 CheckUint32Attribute(item, "blacklisted_by_user", form.blacklisted_by_user);
386 CheckUint32Attribute(item, "type", form.type);
387 CheckUint32Attribute(item, "times_used", form.times_used);
388 CheckUint32Attribute(item, "scheme", form.scheme);
389 CheckStringAttribute(
390 item, "date_synced",
391 base::Int64ToString(form.date_synced.ToInternalValue()));
392 CheckStringAttribute(item, "display_name", UTF16ToUTF8(form.display_name));
393 CheckStringAttribute(item, "avatar_url", form.icon_url.spec());
394 CheckStringAttribute(item, "federation_url", form.federation_url.spec());
395 CheckUint32Attribute(item, "skip_zero_click", form.skip_zero_click);
396 CheckUint32Attribute(item, "generation_upload_status",
397 form.generation_upload_status);
398 CheckStringAttribute(item, "application", app_string);
399 autofill::FormData actual;
400 DeserializeFormDataFromBase64String(
401 static_cast<char*>(g_hash_table_lookup(item->attributes, "form_data")),
402 &actual);
403 EXPECT_TRUE(form.form_data.SameFormAs(actual));
406 // Saves |credentials| and then gets logins matching |url| and |scheme|.
407 // Returns true when something is found, and in such case copies the result to
408 // |result| when |result| is not nullptr. (Note that there can be max. 1
409 // result derived from |credentials|.)
410 bool CheckCredentialAvailability(const PasswordForm& credentials,
411 const GURL& url,
412 const PasswordForm::Scheme& scheme,
413 PasswordForm* result) {
414 NativeBackendLibsecret backend(321);
416 VerifiedAdd(&backend, credentials);
418 PasswordForm target_form;
419 target_form.origin = url;
420 target_form.signon_realm = url.spec();
421 if (scheme != PasswordForm::SCHEME_HTML) {
422 // For non-HTML forms, the realm used for authentication
423 // (http://tools.ietf.org/html/rfc1945#section-10.2) is appended to the
424 // signon_realm. Just use a default value for now.
425 target_form.signon_realm.append("Realm");
426 target_form.scheme = scheme;
428 ScopedVector<autofill::PasswordForm> form_list;
429 EXPECT_TRUE(backend.GetLogins(target_form, &form_list));
431 EXPECT_EQ(1u, global_mock_libsecret_items->size());
432 if (!global_mock_libsecret_items->empty())
433 CheckMockSecretItem((*global_mock_libsecret_items)[0], credentials,
434 "chrome-321");
435 global_mock_libsecret_items->clear();
437 if (form_list.empty())
438 return false;
439 EXPECT_EQ(1u, form_list.size());
440 if (result)
441 *result = *form_list[0];
442 return true;
445 // Test that updating does not use PSL matching: Add a www.facebook.com
446 // password, then use PSL matching to get a copy of it for m.facebook.com, and
447 // add that copy as well. Now update the www.facebook.com password -- the
448 // m.facebook.com password should not get updated. Depending on the argument,
449 // the credential update is done via UpdateLogin or AddLogin.
450 void CheckPSLUpdate(UpdateType update_type) {
451 NativeBackendLibsecret backend(321);
453 VerifiedAdd(&backend, form_facebook_);
455 // Get the PSL-matched copy of the saved login for m.facebook.
456 const GURL kMobileURL("http://m.facebook.com/");
457 PasswordForm m_facebook_lookup;
458 m_facebook_lookup.origin = kMobileURL;
459 m_facebook_lookup.signon_realm = kMobileURL.spec();
460 ScopedVector<autofill::PasswordForm> form_list;
461 EXPECT_TRUE(backend.GetLogins(m_facebook_lookup, &form_list));
463 EXPECT_EQ(1u, global_mock_libsecret_items->size());
464 EXPECT_EQ(1u, form_list.size());
465 PasswordForm m_facebook = *form_list[0];
466 form_list.clear();
467 EXPECT_EQ(kMobileURL, m_facebook.origin);
468 EXPECT_EQ(kMobileURL.spec(), m_facebook.signon_realm);
470 // Add the PSL-matched copy to saved logins.
471 VerifiedAdd(&backend, m_facebook);
472 EXPECT_EQ(2u, global_mock_libsecret_items->size());
474 // Update www.facebook.com login.
475 PasswordForm new_facebook(form_facebook_);
476 const base::string16 kOldPassword(form_facebook_.password_value);
477 const base::string16 kNewPassword(UTF8ToUTF16("new_b"));
478 EXPECT_NE(kOldPassword, kNewPassword);
479 new_facebook.password_value = kNewPassword;
480 switch (update_type) {
481 case UPDATE_BY_UPDATELOGIN:
482 VerifiedUpdate(&backend, new_facebook);
483 break;
484 case UPDATE_BY_ADDLOGIN:
485 // This is an overwrite call.
486 backend.AddLogin(new_facebook);
487 break;
490 EXPECT_EQ(2u, global_mock_libsecret_items->size());
492 // Check that m.facebook.com login was not modified by the update.
493 EXPECT_TRUE(backend.GetLogins(m_facebook_lookup, &form_list));
495 // There should be two results -- the exact one, and the PSL-matched one.
496 EXPECT_EQ(2u, form_list.size());
497 size_t index_non_psl = 0;
498 if (!form_list[index_non_psl]->original_signon_realm.empty())
499 index_non_psl = 1;
500 EXPECT_EQ(kMobileURL, form_list[index_non_psl]->origin);
501 EXPECT_EQ(kMobileURL.spec(), form_list[index_non_psl]->signon_realm);
502 EXPECT_EQ(kOldPassword, form_list[index_non_psl]->password_value);
503 form_list.clear();
505 // Check that www.facebook.com login was modified by the update.
506 EXPECT_TRUE(backend.GetLogins(form_facebook_, &form_list));
507 // There should be two results -- the exact one, and the PSL-matched one.
508 EXPECT_EQ(2u, form_list.size());
509 index_non_psl = 0;
510 if (!form_list[index_non_psl]->original_signon_realm.empty())
511 index_non_psl = 1;
512 EXPECT_EQ(form_facebook_.origin, form_list[index_non_psl]->origin);
513 EXPECT_EQ(form_facebook_.signon_realm,
514 form_list[index_non_psl]->signon_realm);
515 EXPECT_EQ(kNewPassword, form_list[index_non_psl]->password_value);
516 form_list.clear();
519 // Checks various types of matching for forms with a non-HTML |scheme|.
520 void CheckMatchingWithScheme(const PasswordForm::Scheme& scheme) {
521 ASSERT_NE(PasswordForm::SCHEME_HTML, scheme);
522 other_auth_.scheme = scheme;
524 // Don't match a non-HTML form with an HTML form.
525 EXPECT_FALSE(
526 CheckCredentialAvailability(other_auth_, GURL("http://www.example.com"),
527 PasswordForm::SCHEME_HTML, nullptr));
528 // Don't match an HTML form with non-HTML auth form.
529 EXPECT_FALSE(CheckCredentialAvailability(
530 form_google_, GURL("http://www.google.com/"), scheme, nullptr));
531 // Don't match two different non-HTML auth forms with different origin.
532 EXPECT_FALSE(CheckCredentialAvailability(
533 other_auth_, GURL("http://first.example.com"), scheme, nullptr));
534 // Do match non-HTML forms from the same origin.
535 EXPECT_TRUE(CheckCredentialAvailability(
536 other_auth_, GURL("http://www.example.com/"), scheme, nullptr));
539 void CheckRemoveLoginsBetween(RemoveBetweenMethod date_to_test) {
540 NativeBackendLibsecret backend(42);
542 base::Time now = base::Time::Now();
543 base::Time next_day = now + base::TimeDelta::FromDays(1);
544 form_google_.date_synced = base::Time();
545 form_isc_.date_synced = base::Time();
546 form_google_.date_created = now;
547 form_isc_.date_created = now;
548 if (date_to_test == CREATED) {
549 form_google_.date_created = now;
550 form_isc_.date_created = next_day;
551 } else {
552 form_google_.date_synced = now;
553 form_isc_.date_synced = next_day;
556 VerifiedAdd(&backend, form_google_);
557 VerifiedAdd(&backend, form_isc_);
559 PasswordStoreChangeList expected_changes;
560 expected_changes.push_back(
561 PasswordStoreChange(PasswordStoreChange::REMOVE, form_google_));
562 PasswordStoreChangeList changes;
563 bool (NativeBackendLibsecret::*method)(
564 base::Time, base::Time, password_manager::PasswordStoreChangeList*) =
565 date_to_test == CREATED
566 ? &NativeBackendLibsecret::RemoveLoginsCreatedBetween
567 : &NativeBackendLibsecret::RemoveLoginsSyncedBetween;
569 EXPECT_TRUE(base::Bind(method, base::Unretained(&backend), base::Time(),
570 next_day, &changes).Run());
571 CheckPasswordChanges(expected_changes, changes);
573 EXPECT_EQ(1u, global_mock_libsecret_items->size());
574 if (!global_mock_libsecret_items->empty() > 0)
575 CheckMockSecretItem((*global_mock_libsecret_items)[0], form_isc_,
576 "chrome-42");
578 // Remove form_isc_.
579 expected_changes.clear();
580 expected_changes.push_back(
581 PasswordStoreChange(PasswordStoreChange::REMOVE, form_isc_));
583 EXPECT_TRUE(base::Bind(method, base::Unretained(&backend), next_day,
584 base::Time(), &changes).Run());
585 CheckPasswordChanges(expected_changes, changes);
587 EXPECT_TRUE(global_mock_libsecret_items->empty());
590 base::MessageLoopForUI message_loop_;
592 // Provide some test forms to avoid having to set them up in each test.
593 PasswordForm form_google_;
594 PasswordForm form_facebook_;
595 PasswordForm form_isc_;
596 PasswordForm other_auth_;
598 ScopedVector<MockSecretItem> mock_libsecret_items_;
601 TEST_F(NativeBackendLibsecretTest, BasicAddLogin) {
602 NativeBackendLibsecret backend(42);
604 VerifiedAdd(&backend, form_google_);
606 EXPECT_EQ(1u, global_mock_libsecret_items->size());
607 if (!global_mock_libsecret_items->empty())
608 CheckMockSecretItem((*global_mock_libsecret_items)[0], form_google_,
609 "chrome-42");
612 TEST_F(NativeBackendLibsecretTest, BasicListLogins) {
613 NativeBackendLibsecret backend(42);
615 VerifiedAdd(&backend, form_google_);
617 ScopedVector<autofill::PasswordForm> form_list;
618 EXPECT_TRUE(backend.GetAutofillableLogins(&form_list));
620 ASSERT_EQ(1u, form_list.size());
621 EXPECT_EQ(form_google_, *form_list[0]);
623 EXPECT_EQ(1u, global_mock_libsecret_items->size());
624 if (!global_mock_libsecret_items->empty())
625 CheckMockSecretItem((*global_mock_libsecret_items)[0], form_google_,
626 "chrome-42");
629 // Save a password for www.facebook.com and see it suggested for m.facebook.com.
630 TEST_F(NativeBackendLibsecretTest, PSLMatchingPositive) {
631 PasswordForm result;
632 const GURL kMobileURL("http://m.facebook.com/");
633 EXPECT_TRUE(CheckCredentialAvailability(form_facebook_, kMobileURL,
634 PasswordForm::SCHEME_HTML, &result));
635 EXPECT_EQ(kMobileURL, result.origin);
636 EXPECT_EQ(kMobileURL.spec(), result.signon_realm);
639 // Save a password for www.facebook.com and see it not suggested for
640 // m-facebook.com.
641 TEST_F(NativeBackendLibsecretTest, PSLMatchingNegativeDomainMismatch) {
642 EXPECT_FALSE(CheckCredentialAvailability(form_facebook_,
643 GURL("http://m-facebook.com/"),
644 PasswordForm::SCHEME_HTML, nullptr));
647 // Test PSL matching is off for domains excluded from it.
648 TEST_F(NativeBackendLibsecretTest, PSLMatchingDisabledDomains) {
649 EXPECT_FALSE(CheckCredentialAvailability(form_google_,
650 GURL("http://one.google.com/"),
651 PasswordForm::SCHEME_HTML, nullptr));
654 // Make sure PSL matches aren't available for non-HTML forms.
655 TEST_F(NativeBackendLibsecretTest, PSLMatchingDisabledForNonHTMLForms) {
656 CheckMatchingWithScheme(PasswordForm::SCHEME_BASIC);
657 CheckMatchingWithScheme(PasswordForm::SCHEME_DIGEST);
658 CheckMatchingWithScheme(PasswordForm::SCHEME_OTHER);
661 TEST_F(NativeBackendLibsecretTest, PSLUpdatingStrictUpdateLogin) {
662 CheckPSLUpdate(UPDATE_BY_UPDATELOGIN);
665 TEST_F(NativeBackendLibsecretTest, PSLUpdatingStrictAddLogin) {
666 // TODO(vabr): if AddLogin becomes no longer valid for existing logins, then
667 // just delete this test.
668 CheckPSLUpdate(UPDATE_BY_ADDLOGIN);
671 TEST_F(NativeBackendLibsecretTest, BasicUpdateLogin) {
672 NativeBackendLibsecret backend(42);
674 VerifiedAdd(&backend, form_google_);
676 PasswordForm new_form_google(form_google_);
677 new_form_google.times_used = 1;
678 new_form_google.action = GURL("http://www.google.com/different/login");
680 EXPECT_EQ(1u, global_mock_libsecret_items->size());
681 if (!global_mock_libsecret_items->empty()) {
682 CheckMockSecretItem((*global_mock_libsecret_items)[0], form_google_,
683 "chrome-42");
686 // Update login
687 VerifiedUpdate(&backend, new_form_google);
689 EXPECT_EQ(1u, global_mock_libsecret_items->size());
690 if (!global_mock_libsecret_items->empty())
691 CheckMockSecretItem((*global_mock_libsecret_items)[0], new_form_google,
692 "chrome-42");
695 TEST_F(NativeBackendLibsecretTest, BasicRemoveLogin) {
696 NativeBackendLibsecret backend(42);
698 VerifiedAdd(&backend, form_google_);
700 EXPECT_EQ(1u, global_mock_libsecret_items->size());
701 if (!global_mock_libsecret_items->empty())
702 CheckMockSecretItem((*global_mock_libsecret_items)[0], form_google_,
703 "chrome-42");
705 VerifiedRemove(&backend, form_google_);
707 EXPECT_TRUE(global_mock_libsecret_items->empty());
710 // Verify fix for http://crbug.com/408783.
711 TEST_F(NativeBackendLibsecretTest, RemoveLoginActionMismatch) {
712 NativeBackendLibsecret backend(42);
714 VerifiedAdd(&backend, form_google_);
716 EXPECT_EQ(1u, global_mock_libsecret_items->size());
717 if (!global_mock_libsecret_items->empty())
718 CheckMockSecretItem((*global_mock_libsecret_items)[0], form_google_,
719 "chrome-42");
721 // Action url match not required for removal.
722 form_google_.action = GURL("https://some.other.url.com/path");
723 VerifiedRemove(&backend, 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 VerifiedAdd(&backend, 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 PasswordStoreChangeList changes;
741 EXPECT_TRUE(backend.RemoveLogin(form_isc_, &changes));
742 CheckPasswordChanges(PasswordStoreChangeList(), changes);
744 // Make sure we can still get the first form back.
745 ScopedVector<autofill::PasswordForm> form_list;
746 EXPECT_TRUE(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 VerifiedAdd(&backend, 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");
770 // Attempt to update a login that doesn't exist.
771 PasswordStoreChangeList changes;
772 EXPECT_TRUE(backend.UpdateLogin(form_isc_, &changes));
773 CheckPasswordChanges(PasswordStoreChangeList(), changes);
775 EXPECT_EQ(1u, global_mock_libsecret_items->size());
776 if (!global_mock_libsecret_items->empty())
777 CheckMockSecretItem((*global_mock_libsecret_items)[0], form_google_,
778 "chrome-42");
781 TEST_F(NativeBackendLibsecretTest, UpdateSameLogin) {
782 NativeBackendLibsecret backend(42);
784 VerifiedAdd(&backend, form_google_);
786 EXPECT_EQ(1u, global_mock_libsecret_items->size());
787 if (!global_mock_libsecret_items->empty()) {
788 CheckMockSecretItem((*global_mock_libsecret_items)[0], form_google_,
789 "chrome-42");
792 // Attempt to update the same login without changing anything.
793 PasswordStoreChangeList changes;
794 EXPECT_TRUE(backend.UpdateLogin(form_google_, &changes));
795 CheckPasswordChanges(PasswordStoreChangeList(), 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");
804 TEST_F(NativeBackendLibsecretTest, AddDuplicateLogin) {
805 NativeBackendLibsecret backend(42);
807 VerifiedAdd(&backend, form_google_);
809 PasswordStoreChangeList expected_changes;
810 expected_changes.push_back(
811 PasswordStoreChange(PasswordStoreChange::REMOVE, form_google_));
812 form_google_.times_used++;
813 form_google_.submit_element = UTF8ToUTF16("submit2");
814 expected_changes.push_back(
815 PasswordStoreChange(PasswordStoreChange::ADD, form_google_));
817 PasswordStoreChangeList actual_changes = backend.AddLogin(form_google_);
818 CheckPasswordChanges(expected_changes, actual_changes);
820 EXPECT_EQ(1u, global_mock_libsecret_items->size());
821 if (!global_mock_libsecret_items->empty())
822 CheckMockSecretItem((*global_mock_libsecret_items)[0], form_google_,
823 "chrome-42");
826 TEST_F(NativeBackendLibsecretTest, AndroidCredentials) {
827 NativeBackendLibsecret backend(42);
828 backend.Init();
830 PasswordForm observed_android_form;
831 observed_android_form.scheme = PasswordForm::SCHEME_HTML;
832 observed_android_form.signon_realm =
833 "android://7x7IDboo8u9YKraUsbmVkuf1-@net.rateflix.app/";
834 PasswordForm saved_android_form = observed_android_form;
835 saved_android_form.username_value = base::UTF8ToUTF16("randomusername");
836 saved_android_form.password_value = base::UTF8ToUTF16("password");
837 saved_android_form.date_created = base::Time::Now();
839 VerifiedAdd(&backend, saved_android_form);
841 ScopedVector<autofill::PasswordForm> form_list;
842 EXPECT_TRUE(backend.GetAutofillableLogins(&form_list));
844 EXPECT_EQ(1u, form_list.size());
845 EXPECT_EQ(saved_android_form, *form_list[0]);
848 TEST_F(NativeBackendLibsecretTest, RemoveLoginsCreatedBetween) {
849 CheckRemoveLoginsBetween(CREATED);
852 TEST_F(NativeBackendLibsecretTest, RemoveLoginsSyncedBetween) {
853 CheckRemoveLoginsBetween(SYNCED);
856 TEST_F(NativeBackendLibsecretTest, SomeKeyringAttributesAreMissing) {
857 // Absent attributes should be filled with default values.
858 NativeBackendLibsecret backend(42);
860 VerifiedAdd(&backend, form_google_);
862 EXPECT_EQ(1u, global_mock_libsecret_items->size());
863 // Remove a string attribute.
864 (*global_mock_libsecret_items)[0]->RemoveAttribute("avatar_url");
865 // Remove an integer attribute.
866 (*global_mock_libsecret_items)[0]->RemoveAttribute("ssl_valid");
868 ScopedVector<autofill::PasswordForm> form_list;
869 EXPECT_TRUE(backend.GetAutofillableLogins(&form_list));
871 EXPECT_EQ(1u, form_list.size());
872 EXPECT_EQ(GURL(""), form_list[0]->icon_url);
873 EXPECT_FALSE(form_list[0]->ssl_valid);
876 TEST_F(NativeBackendLibsecretTest, ReadDuplicateForms) {
877 NativeBackendLibsecret backend(42);
879 // Add 2 slightly different password forms.
880 const char unique_string[] = "unique_unique_string";
881 const char unique_string_replacement[] = "uniKue_unique_string";
882 form_google_.origin =
883 GURL(std::string("http://www.google.com/") + unique_string);
884 VerifiedAdd(&backend, form_google_);
885 form_google_.origin =
886 GURL(std::string("http://www.google.com/") + unique_string_replacement);
887 VerifiedAdd(&backend, form_google_);
889 // Read the raw value back. Change the |unique_string| to
890 // |unique_string_replacement| so the forms become unique.
891 ASSERT_EQ(2u, global_mock_libsecret_items->size());
892 gpointer item_value = g_hash_table_lookup(
893 global_mock_libsecret_items->front()->attributes, "origin_url");
894 ASSERT_TRUE(item_value);
895 char* substr = strstr(static_cast<char*>(item_value), unique_string);
896 ASSERT_TRUE(substr);
897 ASSERT_EQ(strlen(unique_string), strlen(unique_string_replacement));
898 strncpy(substr, unique_string_replacement, strlen(unique_string));
900 // Now test that GetAutofillableLogins returns only one form.
901 ScopedVector<autofill::PasswordForm> form_list;
902 EXPECT_TRUE(backend.GetAutofillableLogins(&form_list));
904 EXPECT_EQ(1u, form_list.size());
905 EXPECT_EQ(form_google_, *form_list[0]);
907 EXPECT_EQ(1u, global_mock_libsecret_items->size());
908 if (!global_mock_libsecret_items->empty()) {
909 CheckMockSecretItem((*global_mock_libsecret_items)[0], form_google_,
910 "chrome-42");
914 // TODO(mdm): add more basic tests here at some point.