1// Copyright (c) 2012 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5#include <stdarg.h> 6 7#include "base/basictypes.h" 8#include "base/prefs/pref_service.h" 9#include "base/stl_util.h" 10#include "base/strings/string_number_conversions.h" 11#include "base/strings/string_util.h" 12#include "base/strings/stringprintf.h" 13#include "base/strings/utf_string_conversions.h" 14#include "base/time/time.h" 15#include "chrome/browser/password_manager/native_backend_gnome_x.h" 16#include "chrome/test/base/testing_profile.h" 17#include "components/autofill/core/common/password_form.h" 18#include "components/password_manager/core/browser/psl_matching_helper.h" 19#include "components/password_manager/core/common/password_manager_pref_names.h" 20#include "content/public/test/test_browser_thread.h" 21#include "testing/gtest/include/gtest/gtest.h" 22 23using autofill::PasswordForm; 24using base::UTF8ToUTF16; 25using base::UTF16ToUTF8; 26using content::BrowserThread; 27using password_manager::PasswordStoreChange; 28using password_manager::PasswordStoreChangeList; 29 30namespace { 31 32// What follows is a very simple implementation of the subset of the GNOME 33// Keyring API that we actually use. It gets substituted for the real one by 34// MockGnomeKeyringLoader, which hooks into the facility normally used to load 35// the GNOME Keyring library at runtime to avoid a static dependency on it. 36 37struct MockKeyringItem { 38 MockKeyringItem() {} 39 MockKeyringItem(const char* keyring, 40 const std::string& display_name, 41 const std::string& password) 42 : keyring(keyring ? keyring : "login"), 43 display_name(display_name), 44 password(password) {} 45 46 struct ItemAttribute { 47 ItemAttribute() : type(UINT32), value_uint32(0) {} 48 explicit ItemAttribute(uint32_t value) 49 : type(UINT32), value_uint32(value) {} 50 explicit ItemAttribute(const std::string& value) 51 : type(STRING), value_string(value) {} 52 53 bool Equals(const ItemAttribute& x) const { 54 if (type != x.type) return false; 55 return (type == STRING) ? value_string == x.value_string 56 : value_uint32 == x.value_uint32; 57 } 58 59 enum Type { UINT32, STRING } type; 60 uint32_t value_uint32; 61 std::string value_string; 62 }; 63 64 typedef std::map<std::string, ItemAttribute> attribute_map; 65 typedef std::vector<std::pair<std::string, ItemAttribute> > attribute_query; 66 67 bool Matches(const attribute_query& query) const { 68 // The real GNOME Keyring doesn't match empty queries. 69 if (query.empty()) return false; 70 for (size_t i = 0; i < query.size(); ++i) { 71 attribute_map::const_iterator match = attributes.find(query[i].first); 72 if (match == attributes.end()) return false; 73 if (!match->second.Equals(query[i].second)) return false; 74 } 75 return true; 76 } 77 78 std::string keyring; 79 std::string display_name; 80 std::string password; 81 82 attribute_map attributes; 83}; 84 85// The list of all keyring items we have stored. 86std::vector<MockKeyringItem> mock_keyring_items; 87bool mock_keyring_reject_local_ids = false; 88 89bool IsStringAttribute(const GnomeKeyringPasswordSchema* schema, 90 const std::string& name) { 91 for (size_t i = 0; schema->attributes[i].name; ++i) 92 if (name == schema->attributes[i].name) 93 return schema->attributes[i].type == GNOME_KEYRING_ATTRIBUTE_TYPE_STRING; 94 NOTREACHED() << "Requested type of nonexistent attribute"; 95 return false; 96} 97 98gboolean mock_gnome_keyring_is_available() { 99 return true; 100} 101 102gpointer mock_gnome_keyring_store_password( 103 const GnomeKeyringPasswordSchema* schema, 104 const gchar* keyring, 105 const gchar* display_name, 106 const gchar* password, 107 GnomeKeyringOperationDoneCallback callback, 108 gpointer data, 109 GDestroyNotify destroy_data, 110 ...) { 111 mock_keyring_items.push_back( 112 MockKeyringItem(keyring, display_name, password)); 113 MockKeyringItem* item = &mock_keyring_items.back(); 114 const std::string keyring_desc = 115 keyring ? base::StringPrintf("keyring %s", keyring) 116 : std::string("default keyring"); 117 VLOG(1) << "Adding item with origin " << display_name 118 << " to " << keyring_desc; 119 va_list ap; 120 va_start(ap, destroy_data); 121 char* name; 122 while ((name = va_arg(ap, gchar*))) { 123 if (IsStringAttribute(schema, name)) { 124 item->attributes[name] = 125 MockKeyringItem::ItemAttribute(va_arg(ap, gchar*)); 126 VLOG(1) << "Adding item attribute " << name 127 << ", value '" << item->attributes[name].value_string << "'"; 128 } else { 129 item->attributes[name] = 130 MockKeyringItem::ItemAttribute(va_arg(ap, uint32_t)); 131 VLOG(1) << "Adding item attribute " << name 132 << ", value " << item->attributes[name].value_uint32; 133 } 134 } 135 va_end(ap); 136 // As a hack to ease testing migration, make it possible to reject the new 137 // format for the app string. This way we can add them easily to migrate. 138 if (mock_keyring_reject_local_ids) { 139 MockKeyringItem::attribute_map::iterator it = 140 item->attributes.find("application"); 141 if (it != item->attributes.end() && 142 it->second.type == MockKeyringItem::ItemAttribute::STRING && 143 base::StringPiece(it->second.value_string).starts_with("chrome-")) { 144 mock_keyring_items.pop_back(); 145 // GnomeKeyringResult, data 146 callback(GNOME_KEYRING_RESULT_IO_ERROR, data); 147 return NULL; 148 } 149 } 150 // GnomeKeyringResult, data 151 callback(GNOME_KEYRING_RESULT_OK, data); 152 return NULL; 153} 154 155gpointer mock_gnome_keyring_delete_password( 156 const GnomeKeyringPasswordSchema* schema, 157 GnomeKeyringOperationDoneCallback callback, 158 gpointer data, 159 GDestroyNotify destroy_data, 160 ...) { 161 MockKeyringItem::attribute_query query; 162 va_list ap; 163 va_start(ap, destroy_data); 164 char* name; 165 while ((name = va_arg(ap, gchar*))) { 166 if (IsStringAttribute(schema, name)) { 167 query.push_back(make_pair(std::string(name), 168 MockKeyringItem::ItemAttribute(va_arg(ap, gchar*)))); 169 VLOG(1) << "Querying with item attribute " << name 170 << ", value '" << query.back().second.value_string << "'"; 171 } else { 172 query.push_back(make_pair(std::string(name), 173 MockKeyringItem::ItemAttribute(va_arg(ap, uint32_t)))); 174 VLOG(1) << "Querying with item attribute " << name 175 << ", value " << query.back().second.value_uint32; 176 } 177 } 178 va_end(ap); 179 bool deleted = false; 180 for (size_t i = mock_keyring_items.size(); i > 0; --i) { 181 const MockKeyringItem* item = &mock_keyring_items[i - 1]; 182 if (item->Matches(query)) { 183 VLOG(1) << "Deleting item with origin " << item->display_name; 184 mock_keyring_items.erase(mock_keyring_items.begin() + (i - 1)); 185 deleted = true; 186 } 187 } 188 // GnomeKeyringResult, data 189 callback(deleted ? GNOME_KEYRING_RESULT_OK 190 : GNOME_KEYRING_RESULT_NO_MATCH, data); 191 return NULL; 192} 193 194gpointer mock_gnome_keyring_find_items( 195 GnomeKeyringItemType type, 196 GnomeKeyringAttributeList* attributes, 197 GnomeKeyringOperationGetListCallback callback, 198 gpointer data, 199 GDestroyNotify destroy_data) { 200 MockKeyringItem::attribute_query query; 201 for (size_t i = 0; i < attributes->len; ++i) { 202 GnomeKeyringAttribute attribute = 203 g_array_index(attributes, GnomeKeyringAttribute, i); 204 if (attribute.type == GNOME_KEYRING_ATTRIBUTE_TYPE_STRING) { 205 query.push_back( 206 make_pair(std::string(attribute.name), 207 MockKeyringItem::ItemAttribute(attribute.value.string))); 208 VLOG(1) << "Querying with item attribute " << attribute.name 209 << ", value '" << query.back().second.value_string << "'"; 210 } else { 211 query.push_back( 212 make_pair(std::string(attribute.name), 213 MockKeyringItem::ItemAttribute(attribute.value.integer))); 214 VLOG(1) << "Querying with item attribute " << attribute.name << ", value " 215 << query.back().second.value_uint32; 216 } 217 } 218 // Find matches and add them to a list of results. 219 GList* results = NULL; 220 for (size_t i = 0; i < mock_keyring_items.size(); ++i) { 221 const MockKeyringItem* item = &mock_keyring_items[i]; 222 if (item->Matches(query)) { 223 GnomeKeyringFound* found = new GnomeKeyringFound; 224 found->keyring = strdup(item->keyring.c_str()); 225 found->item_id = i; 226 found->attributes = gnome_keyring_attribute_list_new(); 227 for (MockKeyringItem::attribute_map::const_iterator it = 228 item->attributes.begin(); 229 it != item->attributes.end(); 230 ++it) { 231 if (it->second.type == MockKeyringItem::ItemAttribute::STRING) { 232 gnome_keyring_attribute_list_append_string( 233 found->attributes, it->first.c_str(), 234 it->second.value_string.c_str()); 235 } else { 236 gnome_keyring_attribute_list_append_uint32( 237 found->attributes, it->first.c_str(), 238 it->second.value_uint32); 239 } 240 } 241 found->secret = strdup(item->password.c_str()); 242 results = g_list_prepend(results, found); 243 } 244 } 245 // GnomeKeyringResult, GList*, data 246 callback(results ? GNOME_KEYRING_RESULT_OK 247 : GNOME_KEYRING_RESULT_NO_MATCH, results, data); 248 // Now free the list of results. 249 GList* element = g_list_first(results); 250 while (element) { 251 GnomeKeyringFound* found = static_cast<GnomeKeyringFound*>(element->data); 252 free(found->keyring); 253 gnome_keyring_attribute_list_free(found->attributes); 254 free(found->secret); 255 delete found; 256 element = g_list_next(element); 257 } 258 g_list_free(results); 259 return NULL; 260} 261 262const gchar* mock_gnome_keyring_result_to_message(GnomeKeyringResult res) { 263 return "mock keyring simulating failure"; 264} 265 266// Inherit to get access to protected fields. 267class MockGnomeKeyringLoader : public GnomeKeyringLoader { 268 public: 269 static bool LoadMockGnomeKeyring() { 270 if (!LoadGnomeKeyring()) 271 return false; 272#define GNOME_KEYRING_ASSIGN_POINTER(name) \ 273 gnome_keyring_##name = &mock_gnome_keyring_##name; 274 GNOME_KEYRING_FOR_EACH_MOCKED_FUNC(GNOME_KEYRING_ASSIGN_POINTER) 275#undef GNOME_KEYRING_ASSIGN_POINTER 276 keyring_loaded = true; 277 // Reset the state of the mock library. 278 mock_keyring_items.clear(); 279 mock_keyring_reject_local_ids = false; 280 return true; 281 } 282}; 283 284void CheckPasswordChanges(const PasswordStoreChangeList& expected_list, 285 const PasswordStoreChangeList& actual_list) { 286 ASSERT_EQ(expected_list.size(), actual_list.size()); 287 for (size_t i = 0; i < expected_list.size(); ++i) { 288 EXPECT_EQ(expected_list[i].type(), actual_list[i].type()); 289 const PasswordForm& expected = expected_list[i].form(); 290 const PasswordForm& actual = actual_list[i].form(); 291 292 EXPECT_EQ(expected.origin, actual.origin); 293 EXPECT_EQ(expected.password_value, actual.password_value); 294 EXPECT_EQ(expected.action, actual.action); 295 EXPECT_EQ(expected.username_element, actual.username_element); 296 EXPECT_EQ(expected.username_value, actual.username_value); 297 EXPECT_EQ(expected.password_element, actual.password_element); 298 EXPECT_EQ(expected.submit_element, actual.submit_element); 299 EXPECT_EQ(expected.signon_realm, actual.signon_realm); 300 EXPECT_EQ(expected.ssl_valid, actual.ssl_valid); 301 EXPECT_EQ(expected.preferred, actual.preferred); 302 // We don't check the date created. It varies due to bug in the 303 // serialization. Integer seconds are saved instead of microseconds. 304 EXPECT_EQ(expected.blacklisted_by_user, actual.blacklisted_by_user); 305 EXPECT_EQ(expected.type, actual.type); 306 EXPECT_EQ(expected.times_used, actual.times_used); 307 EXPECT_EQ(expected.scheme, actual.scheme); 308 EXPECT_EQ(expected.date_synced, actual.date_synced); 309 EXPECT_EQ(expected.display_name, actual.display_name); 310 EXPECT_EQ(expected.avatar_url, actual.avatar_url); 311 EXPECT_EQ(expected.federation_url, actual.federation_url); 312 EXPECT_EQ(expected.is_zero_click, actual.is_zero_click); 313 } 314} 315 316void CheckPasswordChangesWithResult(const PasswordStoreChangeList* expected, 317 const PasswordStoreChangeList* actual, 318 bool result) { 319 EXPECT_TRUE(result); 320 CheckPasswordChanges(*expected, *actual); 321} 322 323} // anonymous namespace 324 325class NativeBackendGnomeTest : public testing::Test { 326 protected: 327 enum UpdateType { // Used in CheckPSLUpdate(). 328 UPDATE_BY_UPDATELOGIN, 329 UPDATE_BY_ADDLOGIN, 330 }; 331 enum RemoveBetweenMethod { // Used in CheckRemoveLoginsBetween(). 332 CREATED, 333 SYNCED, 334 }; 335 336 NativeBackendGnomeTest() 337 : ui_thread_(BrowserThread::UI, &message_loop_), 338 db_thread_(BrowserThread::DB) { 339 } 340 341 virtual void SetUp() { 342 ASSERT_TRUE(db_thread_.Start()); 343 344 ASSERT_TRUE(MockGnomeKeyringLoader::LoadMockGnomeKeyring()); 345 346 form_google_.origin = GURL("http://www.google.com/"); 347 form_google_.action = GURL("http://www.google.com/login"); 348 form_google_.username_element = UTF8ToUTF16("user"); 349 form_google_.username_value = UTF8ToUTF16("joeschmoe"); 350 form_google_.password_element = UTF8ToUTF16("pass"); 351 form_google_.password_value = UTF8ToUTF16("seekrit"); 352 form_google_.submit_element = UTF8ToUTF16("submit"); 353 form_google_.signon_realm = "http://www.google.com/"; 354 form_google_.type = PasswordForm::TYPE_GENERATED; 355 form_google_.date_created = base::Time::Now(); 356 form_google_.date_synced = base::Time::Now(); 357 form_google_.display_name = UTF8ToUTF16("Joe Schmoe"); 358 form_google_.avatar_url = GURL("http://www.google.com/avatar"); 359 form_google_.federation_url = GURL("http://www.google.com/federation_url"); 360 form_google_.is_zero_click = true; 361 362 form_facebook_.origin = GURL("http://www.facebook.com/"); 363 form_facebook_.action = GURL("http://www.facebook.com/login"); 364 form_facebook_.username_element = UTF8ToUTF16("user"); 365 form_facebook_.username_value = UTF8ToUTF16("a"); 366 form_facebook_.password_element = UTF8ToUTF16("password"); 367 form_facebook_.password_value = UTF8ToUTF16("b"); 368 form_facebook_.submit_element = UTF8ToUTF16("submit"); 369 form_facebook_.signon_realm = "http://www.facebook.com/"; 370 form_facebook_.date_created = base::Time::Now(); 371 form_facebook_.date_synced = base::Time::Now(); 372 form_facebook_.display_name = UTF8ToUTF16("Joe Schmoe"); 373 form_facebook_.avatar_url = GURL("http://www.facebook.com/avatar"); 374 form_facebook_.federation_url = GURL("http://www.facebook.com/federation"); 375 form_facebook_.is_zero_click = true; 376 377 form_isc_.origin = GURL("http://www.isc.org/"); 378 form_isc_.action = GURL("http://www.isc.org/auth"); 379 form_isc_.username_element = UTF8ToUTF16("id"); 380 form_isc_.username_value = UTF8ToUTF16("janedoe"); 381 form_isc_.password_element = UTF8ToUTF16("passwd"); 382 form_isc_.password_value = UTF8ToUTF16("ihazabukkit"); 383 form_isc_.submit_element = UTF8ToUTF16("login"); 384 form_isc_.signon_realm = "http://www.isc.org/"; 385 form_isc_.date_created = base::Time::Now(); 386 form_isc_.date_synced = base::Time::Now(); 387 388 other_auth_.origin = GURL("http://www.example.com/"); 389 other_auth_.username_value = UTF8ToUTF16("username"); 390 other_auth_.password_value = UTF8ToUTF16("pass"); 391 other_auth_.signon_realm = "http://www.example.com/Realm"; 392 other_auth_.date_created = base::Time::Now(); 393 other_auth_.date_synced = base::Time::Now(); 394 } 395 396 virtual void TearDown() { 397 base::MessageLoop::current()->PostTask(FROM_HERE, 398 base::MessageLoop::QuitClosure()); 399 base::MessageLoop::current()->Run(); 400 db_thread_.Stop(); 401 } 402 403 void RunBothThreads() { 404 // First we post a message to the DB thread that will run after all other 405 // messages that have been posted to the DB thread (we don't expect more 406 // to be posted), which posts a message to the UI thread to quit the loop. 407 // That way we can run both loops and be sure that the UI thread loop will 408 // quit so we can get on with the rest of the test. 409 BrowserThread::PostTask(BrowserThread::DB, FROM_HERE, 410 base::Bind(&PostQuitTask, &message_loop_)); 411 base::MessageLoop::current()->Run(); 412 } 413 414 static void PostQuitTask(base::MessageLoop* loop) { 415 loop->PostTask(FROM_HERE, base::MessageLoop::QuitClosure()); 416 } 417 418 void CheckUint32Attribute(const MockKeyringItem* item, 419 const std::string& attribute, 420 uint32_t value) { 421 MockKeyringItem::attribute_map::const_iterator it = 422 item->attributes.find(attribute); 423 EXPECT_NE(item->attributes.end(), it); 424 if (it != item->attributes.end()) { 425 EXPECT_EQ(MockKeyringItem::ItemAttribute::UINT32, it->second.type); 426 EXPECT_EQ(value, it->second.value_uint32); 427 } 428 } 429 430 void CheckStringAttribute(const MockKeyringItem* item, 431 const std::string& attribute, 432 const std::string& value) { 433 MockKeyringItem::attribute_map::const_iterator it = 434 item->attributes.find(attribute); 435 EXPECT_NE(item->attributes.end(), it); 436 if (it != item->attributes.end()) { 437 EXPECT_EQ(MockKeyringItem::ItemAttribute::STRING, it->second.type); 438 EXPECT_EQ(value, it->second.value_string); 439 } 440 } 441 442 void CheckMockKeyringItem(const MockKeyringItem* item, 443 const PasswordForm& form, 444 const std::string& app_string) { 445 // We always add items to the login keyring. 446 EXPECT_EQ("login", item->keyring); 447 EXPECT_EQ(form.origin.spec(), item->display_name); 448 EXPECT_EQ(UTF16ToUTF8(form.password_value), item->password); 449 EXPECT_EQ(20u, item->attributes.size()); 450 CheckStringAttribute(item, "origin_url", form.origin.spec()); 451 CheckStringAttribute(item, "action_url", form.action.spec()); 452 CheckStringAttribute(item, "username_element", 453 UTF16ToUTF8(form.username_element)); 454 CheckStringAttribute(item, "username_value", 455 UTF16ToUTF8(form.username_value)); 456 CheckStringAttribute(item, "password_element", 457 UTF16ToUTF8(form.password_element)); 458 CheckStringAttribute(item, "submit_element", 459 UTF16ToUTF8(form.submit_element)); 460 CheckStringAttribute(item, "signon_realm", form.signon_realm); 461 CheckUint32Attribute(item, "ssl_valid", form.ssl_valid); 462 CheckUint32Attribute(item, "preferred", form.preferred); 463 // We don't check the date created. It varies. 464 CheckUint32Attribute(item, "blacklisted_by_user", form.blacklisted_by_user); 465 CheckUint32Attribute(item, "type", form.type); 466 CheckUint32Attribute(item, "times_used", form.times_used); 467 CheckUint32Attribute(item, "scheme", form.scheme); 468 CheckStringAttribute(item, "date_synced", base::Int64ToString( 469 form.date_synced.ToInternalValue())); 470 CheckStringAttribute(item, "display_name", UTF16ToUTF8(form.display_name)); 471 CheckStringAttribute(item, "avatar_url", form.avatar_url.spec()); 472 CheckStringAttribute(item, "federation_url", form.federation_url.spec()); 473 CheckUint32Attribute(item, "is_zero_click", form.is_zero_click); 474 CheckStringAttribute(item, "application", app_string); 475 } 476 477 // Saves |credentials| and then gets logins matching |url| and |scheme|. 478 // Returns true when something is found, and in such case copies the result to 479 // |result| when |result| is not NULL. (Note that there can be max. 1 result, 480 // derived from |credentials|.) 481 bool CheckCredentialAvailability(const PasswordForm& credentials, 482 const GURL& url, 483 const PasswordForm::Scheme& scheme, 484 PasswordForm* result) { 485 NativeBackendGnome backend(321); 486 backend.Init(); 487 488 BrowserThread::PostTask( 489 BrowserThread::DB, 490 FROM_HERE, 491 base::Bind(base::IgnoreResult(&NativeBackendGnome::AddLogin), 492 base::Unretained(&backend), 493 credentials)); 494 495 PasswordForm target_form; 496 target_form.origin = url; 497 target_form.signon_realm = url.spec(); 498 if (scheme != PasswordForm::SCHEME_HTML) { 499 // For non-HTML forms, the realm used for authentication 500 // (http://tools.ietf.org/html/rfc1945#section-10.2) is appended to the 501 // signon_realm. Just use a default value for now. 502 target_form.signon_realm.append("Realm"); 503 target_form.scheme = scheme; 504 } 505 std::vector<PasswordForm*> form_list; 506 BrowserThread::PostTask( 507 BrowserThread::DB, 508 FROM_HERE, 509 base::Bind(base::IgnoreResult(&NativeBackendGnome::GetLogins), 510 base::Unretained(&backend), 511 target_form, 512 &form_list)); 513 514 RunBothThreads(); 515 516 EXPECT_EQ(1u, mock_keyring_items.size()); 517 if (mock_keyring_items.size() > 0) 518 CheckMockKeyringItem(&mock_keyring_items[0], credentials, "chrome-321"); 519 mock_keyring_items.clear(); 520 521 if (form_list.empty()) 522 return false; 523 EXPECT_EQ(1u, form_list.size()); 524 if (result) 525 *result = *form_list[0]; 526 STLDeleteElements(&form_list); 527 return true; 528 } 529 530 // Test that updating does not use PSL matching: Add a www.facebook.com 531 // password, then use PSL matching to get a copy of it for m.facebook.com, and 532 // add that copy as well. Now update the www.facebook.com password -- the 533 // m.facebook.com password should not get updated. Depending on the argument, 534 // the credential update is done via UpdateLogin or AddLogin. 535 void CheckPSLUpdate(UpdateType update_type) { 536 NativeBackendGnome backend(321); 537 backend.Init(); 538 539 // Add |form_facebook_| to saved logins. 540 BrowserThread::PostTask( 541 BrowserThread::DB, 542 FROM_HERE, 543 base::Bind(base::IgnoreResult(&NativeBackendGnome::AddLogin), 544 base::Unretained(&backend), 545 form_facebook_)); 546 547 // Get the PSL-matched copy of the saved login for m.facebook. 548 const GURL kMobileURL("http://m.facebook.com/"); 549 PasswordForm m_facebook_lookup; 550 m_facebook_lookup.origin = kMobileURL; 551 m_facebook_lookup.signon_realm = kMobileURL.spec(); 552 std::vector<PasswordForm*> form_list; 553 BrowserThread::PostTask( 554 BrowserThread::DB, 555 FROM_HERE, 556 base::Bind(base::IgnoreResult(&NativeBackendGnome::GetLogins), 557 base::Unretained(&backend), 558 m_facebook_lookup, 559 &form_list)); 560 RunBothThreads(); 561 EXPECT_EQ(1u, mock_keyring_items.size()); 562 EXPECT_EQ(1u, form_list.size()); 563 PasswordForm m_facebook = *form_list[0]; 564 STLDeleteElements(&form_list); 565 EXPECT_EQ(kMobileURL, m_facebook.origin); 566 EXPECT_EQ(kMobileURL.spec(), m_facebook.signon_realm); 567 568 // Add the PSL-matched copy to saved logins. 569 BrowserThread::PostTask( 570 BrowserThread::DB, 571 FROM_HERE, 572 base::Bind(base::IgnoreResult(&NativeBackendGnome::AddLogin), 573 base::Unretained(&backend), 574 m_facebook)); 575 RunBothThreads(); 576 EXPECT_EQ(2u, mock_keyring_items.size()); 577 578 // Update www.facebook.com login. 579 PasswordForm new_facebook(form_facebook_); 580 const base::string16 kOldPassword(form_facebook_.password_value); 581 const base::string16 kNewPassword(UTF8ToUTF16("new_b")); 582 EXPECT_NE(kOldPassword, kNewPassword); 583 new_facebook.password_value = kNewPassword; 584 switch (update_type) { 585 case UPDATE_BY_UPDATELOGIN: 586 BrowserThread::PostTask( 587 BrowserThread::DB, 588 FROM_HERE, 589 base::Bind(base::IgnoreResult(&NativeBackendGnome::UpdateLogin), 590 base::Unretained(&backend), 591 new_facebook, 592 base::Owned(new PasswordStoreChangeList))); 593 break; 594 case UPDATE_BY_ADDLOGIN: 595 BrowserThread::PostTask( 596 BrowserThread::DB, 597 FROM_HERE, 598 base::Bind(base::IgnoreResult(&NativeBackendGnome::AddLogin), 599 base::Unretained(&backend), 600 new_facebook)); 601 break; 602 } 603 604 RunBothThreads(); 605 EXPECT_EQ(2u, mock_keyring_items.size()); 606 607 // Check that m.facebook.com login was not modified by the update. 608 BrowserThread::PostTask( 609 BrowserThread::DB, 610 FROM_HERE, 611 base::Bind(base::IgnoreResult(&NativeBackendGnome::GetLogins), 612 base::Unretained(&backend), 613 m_facebook_lookup, 614 &form_list)); 615 RunBothThreads(); 616 // There should be two results -- the exact one, and the PSL-matched one. 617 EXPECT_EQ(2u, form_list.size()); 618 size_t index_non_psl = 0; 619 if (!form_list[index_non_psl]->original_signon_realm.empty()) 620 index_non_psl = 1; 621 EXPECT_EQ(kMobileURL, form_list[index_non_psl]->origin); 622 EXPECT_EQ(kMobileURL.spec(), form_list[index_non_psl]->signon_realm); 623 EXPECT_EQ(kOldPassword, form_list[index_non_psl]->password_value); 624 STLDeleteElements(&form_list); 625 626 // Check that www.facebook.com login was modified by the update. 627 BrowserThread::PostTask( 628 BrowserThread::DB, 629 FROM_HERE, 630 base::Bind(base::IgnoreResult(&NativeBackendGnome::GetLogins), 631 base::Unretained(&backend), 632 form_facebook_, 633 &form_list)); 634 RunBothThreads(); 635 // There should be two results -- the exact one, and the PSL-matched one. 636 EXPECT_EQ(2u, form_list.size()); 637 index_non_psl = 0; 638 if (!form_list[index_non_psl]->original_signon_realm.empty()) 639 index_non_psl = 1; 640 EXPECT_EQ(form_facebook_.origin, form_list[index_non_psl]->origin); 641 EXPECT_EQ(form_facebook_.signon_realm, 642 form_list[index_non_psl]->signon_realm); 643 EXPECT_EQ(kNewPassword, form_list[index_non_psl]->password_value); 644 STLDeleteElements(&form_list); 645 } 646 647 void CheckMatchingWithScheme(const PasswordForm::Scheme& scheme) { 648 other_auth_.scheme = scheme; 649 650 // Don't match a non-HTML form with an HTML form. 651 EXPECT_FALSE(CheckCredentialAvailability( 652 other_auth_, GURL("http://www.example.com"), 653 PasswordForm::SCHEME_HTML, NULL)); 654 // Don't match an HTML form with non-HTML auth form. 655 EXPECT_FALSE(CheckCredentialAvailability( 656 form_google_, GURL("http://www.google.com/"), scheme, NULL)); 657 // Don't match two different non-HTML auth forms with different origin. 658 EXPECT_FALSE(CheckCredentialAvailability( 659 other_auth_, GURL("http://first.example.com"), scheme, NULL)); 660 // Do match non-HTML forms from the same origin. 661 EXPECT_TRUE(CheckCredentialAvailability( 662 other_auth_, GURL("http://www.example.com/"), scheme, NULL)); 663 } 664 665 void CheckRemoveLoginsBetween(RemoveBetweenMethod date_to_test) { 666 NativeBackendGnome backend(42); 667 backend.Init(); 668 669 form_google_.date_synced = base::Time(); 670 form_isc_.date_synced = base::Time(); 671 form_google_.date_created = base::Time(); 672 form_isc_.date_created = base::Time(); 673 base::Time now = base::Time::Now(); 674 base::Time next_day = now + base::TimeDelta::FromDays(1); 675 if (date_to_test == CREATED) { 676 // crbug/374132. Remove the next line once it's fixed. 677 next_day = base::Time::FromTimeT(next_day.ToTimeT()); 678 form_google_.date_created = now; 679 form_isc_.date_created = next_day; 680 } else { 681 form_google_.date_synced = now; 682 form_isc_.date_synced = next_day; 683 } 684 685 BrowserThread::PostTask( 686 BrowserThread::DB, 687 FROM_HERE, 688 base::Bind(base::IgnoreResult(&NativeBackendGnome::AddLogin), 689 base::Unretained(&backend), 690 form_google_)); 691 BrowserThread::PostTask( 692 BrowserThread::DB, 693 FROM_HERE, 694 base::Bind(base::IgnoreResult(&NativeBackendGnome::AddLogin), 695 base::Unretained(&backend), 696 form_isc_)); 697 698 PasswordStoreChangeList expected_changes; 699 expected_changes.push_back( 700 PasswordStoreChange(PasswordStoreChange::REMOVE, form_google_)); 701 PasswordStoreChangeList changes; 702 bool (NativeBackendGnome::*method)( 703 base::Time, base::Time, password_manager::PasswordStoreChangeList*) = 704 date_to_test == CREATED 705 ? &NativeBackendGnome::RemoveLoginsCreatedBetween 706 : &NativeBackendGnome::RemoveLoginsSyncedBetween; 707 BrowserThread::PostTaskAndReplyWithResult( 708 BrowserThread::DB, 709 FROM_HERE, 710 base::Bind(method, 711 base::Unretained(&backend), 712 base::Time(), 713 next_day, 714 &changes), 715 base::Bind( 716 &CheckPasswordChangesWithResult, &expected_changes, &changes)); 717 RunBothThreads(); 718 719 EXPECT_EQ(1u, mock_keyring_items.size()); 720 if (mock_keyring_items.size() > 0) 721 CheckMockKeyringItem(&mock_keyring_items[0], form_isc_, "chrome-42"); 722 723 // Remove form_isc_. 724 expected_changes.clear(); 725 expected_changes.push_back( 726 PasswordStoreChange(PasswordStoreChange::REMOVE, form_isc_)); 727 BrowserThread::PostTaskAndReplyWithResult( 728 BrowserThread::DB, 729 FROM_HERE, 730 base::Bind(method, 731 base::Unretained(&backend), 732 next_day, 733 base::Time(), 734 &changes), 735 base::Bind( 736 &CheckPasswordChangesWithResult, &expected_changes, &changes)); 737 RunBothThreads(); 738 739 EXPECT_EQ(0u, mock_keyring_items.size()); 740 } 741 742 base::MessageLoopForUI message_loop_; 743 content::TestBrowserThread ui_thread_; 744 content::TestBrowserThread db_thread_; 745 746 // Provide some test forms to avoid having to set them up in each test. 747 PasswordForm form_google_; 748 PasswordForm form_facebook_; 749 PasswordForm form_isc_; 750 PasswordForm other_auth_; 751}; 752 753TEST_F(NativeBackendGnomeTest, BasicAddLogin) { 754 NativeBackendGnome backend(42); 755 backend.Init(); 756 757 BrowserThread::PostTask( 758 BrowserThread::DB, FROM_HERE, 759 base::Bind(base::IgnoreResult(&NativeBackendGnome::AddLogin), 760 base::Unretained(&backend), form_google_)); 761 762 RunBothThreads(); 763 764 EXPECT_EQ(1u, mock_keyring_items.size()); 765 if (mock_keyring_items.size() > 0) 766 CheckMockKeyringItem(&mock_keyring_items[0], form_google_, "chrome-42"); 767} 768 769TEST_F(NativeBackendGnomeTest, BasicListLogins) { 770 NativeBackendGnome backend(42); 771 backend.Init(); 772 773 BrowserThread::PostTask( 774 BrowserThread::DB, FROM_HERE, 775 base::Bind(base::IgnoreResult( &NativeBackendGnome::AddLogin), 776 base::Unretained(&backend), form_google_)); 777 778 std::vector<PasswordForm*> form_list; 779 BrowserThread::PostTask( 780 BrowserThread::DB, FROM_HERE, 781 base::Bind( 782 base::IgnoreResult(&NativeBackendGnome::GetAutofillableLogins), 783 base::Unretained(&backend), &form_list)); 784 785 RunBothThreads(); 786 787 // Quick check that we got something back. 788 EXPECT_EQ(1u, form_list.size()); 789 STLDeleteElements(&form_list); 790 791 EXPECT_EQ(1u, mock_keyring_items.size()); 792 if (mock_keyring_items.size() > 0) 793 CheckMockKeyringItem(&mock_keyring_items[0], form_google_, "chrome-42"); 794} 795 796// Save a password for www.facebook.com and see it suggested for m.facebook.com. 797TEST_F(NativeBackendGnomeTest, PSLMatchingPositive) { 798 PasswordForm result; 799 const GURL kMobileURL("http://m.facebook.com/"); 800 EXPECT_TRUE(CheckCredentialAvailability( 801 form_facebook_, kMobileURL, PasswordForm::SCHEME_HTML, &result)); 802 EXPECT_EQ(kMobileURL, result.origin); 803 EXPECT_EQ(kMobileURL.spec(), result.signon_realm); 804} 805 806// Save a password for www.facebook.com and see it not suggested for 807// m-facebook.com. 808TEST_F(NativeBackendGnomeTest, PSLMatchingNegativeDomainMismatch) { 809 EXPECT_FALSE(CheckCredentialAvailability( 810 form_facebook_, GURL("http://m-facebook.com/"), 811 PasswordForm::SCHEME_HTML, NULL)); 812} 813 814// Test PSL matching is off for domains excluded from it. 815TEST_F(NativeBackendGnomeTest, PSLMatchingDisabledDomains) { 816 EXPECT_FALSE(CheckCredentialAvailability( 817 form_google_, GURL("http://one.google.com/"), 818 PasswordForm::SCHEME_HTML, NULL)); 819} 820 821// Make sure PSL matches aren't available for non-HTML forms. 822TEST_F(NativeBackendGnomeTest, PSLMatchingDisabledForNonHTMLForms) { 823 CheckMatchingWithScheme(PasswordForm::SCHEME_BASIC); 824 CheckMatchingWithScheme(PasswordForm::SCHEME_DIGEST); 825 CheckMatchingWithScheme(PasswordForm::SCHEME_OTHER); 826 827} 828 829TEST_F(NativeBackendGnomeTest, PSLUpdatingStrictUpdateLogin) { 830 CheckPSLUpdate(UPDATE_BY_UPDATELOGIN); 831} 832 833TEST_F(NativeBackendGnomeTest, PSLUpdatingStrictAddLogin) { 834 // TODO(vabr): if AddLogin becomes no longer valid for existing logins, then 835 // just delete this test. 836 CheckPSLUpdate(UPDATE_BY_ADDLOGIN); 837} 838 839TEST_F(NativeBackendGnomeTest, BasicUpdateLogin) { 840 NativeBackendGnome backend(42); 841 backend.Init(); 842 843 // First add google login. 844 BrowserThread::PostTask( 845 BrowserThread::DB, FROM_HERE, 846 base::Bind(base::IgnoreResult(&NativeBackendGnome::AddLogin), 847 base::Unretained(&backend), form_google_)); 848 849 RunBothThreads(); 850 851 PasswordForm new_form_google(form_google_); 852 new_form_google.times_used = 1; 853 new_form_google.action = GURL("http://www.google.com/different/login"); 854 855 EXPECT_EQ(1u, mock_keyring_items.size()); 856 if (mock_keyring_items.size() > 0) 857 CheckMockKeyringItem(&mock_keyring_items[0], form_google_, "chrome-42"); 858 859 // Update login 860 PasswordStoreChangeList changes; 861 BrowserThread::PostTask( 862 BrowserThread::DB, FROM_HERE, 863 base::Bind(base::IgnoreResult(&NativeBackendGnome::UpdateLogin), 864 base::Unretained(&backend), 865 new_form_google, 866 base::Unretained(&changes))); 867 868 RunBothThreads(); 869 870 ASSERT_EQ(1u, changes.size()); 871 EXPECT_EQ(PasswordStoreChange::UPDATE, changes.front().type()); 872 EXPECT_EQ(new_form_google, changes.front().form()); 873 EXPECT_EQ(1u, mock_keyring_items.size()); 874 if (mock_keyring_items.size() > 0) 875 CheckMockKeyringItem(&mock_keyring_items[0], new_form_google, "chrome-42"); 876} 877 878TEST_F(NativeBackendGnomeTest, BasicRemoveLogin) { 879 NativeBackendGnome backend(42); 880 backend.Init(); 881 882 BrowserThread::PostTask( 883 BrowserThread::DB, FROM_HERE, 884 base::Bind(base::IgnoreResult(&NativeBackendGnome::AddLogin), 885 base::Unretained(&backend), form_google_)); 886 887 RunBothThreads(); 888 889 EXPECT_EQ(1u, mock_keyring_items.size()); 890 if (mock_keyring_items.size() > 0) 891 CheckMockKeyringItem(&mock_keyring_items[0], form_google_, "chrome-42"); 892 893 BrowserThread::PostTask( 894 BrowserThread::DB, FROM_HERE, 895 base::Bind(base::IgnoreResult(&NativeBackendGnome::RemoveLogin), 896 base::Unretained(&backend), form_google_)); 897 898 RunBothThreads(); 899 900 EXPECT_EQ(0u, mock_keyring_items.size()); 901} 902 903// Verify fix for http://crbug.com/408783. 904TEST_F(NativeBackendGnomeTest, RemoveLoginActionMismatch) { 905 NativeBackendGnome backend(42); 906 backend.Init(); 907 908 BrowserThread::PostTask( 909 BrowserThread::DB, FROM_HERE, 910 base::Bind(base::IgnoreResult(&NativeBackendGnome::AddLogin), 911 base::Unretained(&backend), form_google_)); 912 913 RunBothThreads(); 914 915 EXPECT_EQ(1u, mock_keyring_items.size()); 916 if (mock_keyring_items.size() > 0) 917 CheckMockKeyringItem(&mock_keyring_items[0], form_google_, "chrome-42"); 918 919 // Action url match not required for removal. 920 form_google_.action = GURL("https://some.other.url.com/path"); 921 922 BrowserThread::PostTask( 923 BrowserThread::DB, FROM_HERE, 924 base::Bind(base::IgnoreResult(&NativeBackendGnome::RemoveLogin), 925 base::Unretained(&backend), form_google_)); 926 927 RunBothThreads(); 928 929 EXPECT_EQ(0u, mock_keyring_items.size()); 930} 931 932TEST_F(NativeBackendGnomeTest, RemoveNonexistentLogin) { 933 NativeBackendGnome backend(42); 934 backend.Init(); 935 936 // First add an unrelated login. 937 BrowserThread::PostTask( 938 BrowserThread::DB, FROM_HERE, 939 base::Bind(base::IgnoreResult(&NativeBackendGnome::AddLogin), 940 base::Unretained(&backend), form_google_)); 941 942 RunBothThreads(); 943 944 EXPECT_EQ(1u, mock_keyring_items.size()); 945 if (mock_keyring_items.size() > 0) 946 CheckMockKeyringItem(&mock_keyring_items[0], form_google_, "chrome-42"); 947 948 // Attempt to remove a login that doesn't exist. 949 BrowserThread::PostTask( 950 BrowserThread::DB, FROM_HERE, 951 base::Bind(base::IgnoreResult(&NativeBackendGnome::RemoveLogin), 952 base::Unretained(&backend), form_isc_)); 953 954 // Make sure we can still get the first form back. 955 std::vector<PasswordForm*> form_list; 956 BrowserThread::PostTask( 957 BrowserThread::DB, FROM_HERE, 958 base::Bind( 959 base::IgnoreResult(&NativeBackendGnome::GetAutofillableLogins), 960 base::Unretained(&backend), &form_list)); 961 962 RunBothThreads(); 963 964 // Quick check that we got something back. 965 EXPECT_EQ(1u, form_list.size()); 966 STLDeleteElements(&form_list); 967 968 EXPECT_EQ(1u, mock_keyring_items.size()); 969 if (mock_keyring_items.size() > 0) 970 CheckMockKeyringItem(&mock_keyring_items[0], form_google_, "chrome-42"); 971} 972 973TEST_F(NativeBackendGnomeTest, UpdateNonexistentLogin) { 974 NativeBackendGnome backend(42); 975 backend.Init(); 976 977 // First add an unrelated login. 978 BrowserThread::PostTask( 979 BrowserThread::DB, FROM_HERE, 980 base::Bind(base::IgnoreResult(&NativeBackendGnome::AddLogin), 981 base::Unretained(&backend), form_google_)); 982 983 RunBothThreads(); 984 985 EXPECT_EQ(1u, mock_keyring_items.size()); 986 if (mock_keyring_items.size() > 0) 987 CheckMockKeyringItem(&mock_keyring_items[0], form_google_, "chrome-42"); 988 989 // Attempt to update a login that doesn't exist. 990 PasswordStoreChangeList changes; 991 BrowserThread::PostTask( 992 BrowserThread::DB, FROM_HERE, 993 base::Bind(base::IgnoreResult(&NativeBackendGnome::UpdateLogin), 994 base::Unretained(&backend), 995 form_isc_, 996 base::Unretained(&changes))); 997 998 RunBothThreads(); 999 1000 EXPECT_EQ(PasswordStoreChangeList(), changes); 1001 EXPECT_EQ(1u, mock_keyring_items.size()); 1002 if (mock_keyring_items.size() > 0) 1003 CheckMockKeyringItem(&mock_keyring_items[0], form_google_, "chrome-42"); 1004} 1005 1006TEST_F(NativeBackendGnomeTest, AddDuplicateLogin) { 1007 NativeBackendGnome backend(42); 1008 backend.Init(); 1009 1010 PasswordStoreChangeList changes; 1011 changes.push_back(PasswordStoreChange(PasswordStoreChange::ADD, 1012 form_google_)); 1013 BrowserThread::PostTaskAndReplyWithResult( 1014 BrowserThread::DB, FROM_HERE, 1015 base::Bind(&NativeBackendGnome::AddLogin, 1016 base::Unretained(&backend), form_google_), 1017 base::Bind(&CheckPasswordChanges, changes)); 1018 1019 changes.clear(); 1020 changes.push_back(PasswordStoreChange(PasswordStoreChange::REMOVE, 1021 form_google_)); 1022 form_google_.times_used++; 1023 changes.push_back(PasswordStoreChange(PasswordStoreChange::ADD, 1024 form_google_)); 1025 1026 BrowserThread::PostTaskAndReplyWithResult( 1027 BrowserThread::DB, FROM_HERE, 1028 base::Bind(&NativeBackendGnome::AddLogin, 1029 base::Unretained(&backend), form_google_), 1030 base::Bind(&CheckPasswordChanges, changes)); 1031 1032 RunBothThreads(); 1033 1034 EXPECT_EQ(1u, mock_keyring_items.size()); 1035 if (mock_keyring_items.size() > 0) 1036 CheckMockKeyringItem(&mock_keyring_items[0], form_google_, "chrome-42"); 1037} 1038 1039TEST_F(NativeBackendGnomeTest, ListLoginsAppends) { 1040 NativeBackendGnome backend(42); 1041 backend.Init(); 1042 1043 BrowserThread::PostTask( 1044 BrowserThread::DB, FROM_HERE, 1045 base::Bind(base::IgnoreResult(&NativeBackendGnome::AddLogin), 1046 base::Unretained(&backend), form_google_)); 1047 1048 // Send the same request twice with the same list both times. 1049 std::vector<PasswordForm*> form_list; 1050 BrowserThread::PostTask( 1051 BrowserThread::DB, FROM_HERE, 1052 base::Bind( 1053 base::IgnoreResult(&NativeBackendGnome::GetAutofillableLogins), 1054 base::Unretained(&backend), &form_list)); 1055 BrowserThread::PostTask( 1056 BrowserThread::DB, FROM_HERE, 1057 base::Bind( 1058 base::IgnoreResult(&NativeBackendGnome::GetAutofillableLogins), 1059 base::Unretained(&backend), &form_list)); 1060 1061 RunBothThreads(); 1062 1063 // Quick check that we got two results back. 1064 EXPECT_EQ(2u, form_list.size()); 1065 STLDeleteElements(&form_list); 1066 1067 EXPECT_EQ(1u, mock_keyring_items.size()); 1068 if (mock_keyring_items.size() > 0) 1069 CheckMockKeyringItem(&mock_keyring_items[0], form_google_, "chrome-42"); 1070} 1071 1072TEST_F(NativeBackendGnomeTest, RemoveLoginsCreatedBetween) { 1073 CheckRemoveLoginsBetween(CREATED); 1074} 1075 1076TEST_F(NativeBackendGnomeTest, RemoveLoginsSyncedBetween) { 1077 CheckRemoveLoginsBetween(SYNCED); 1078} 1079 1080// TODO(mdm): add more basic tests here at some point. 1081