Index: chrome/browser/password_manager/password_store_mac_unittest.cc |
diff --git a/chrome/browser/password_manager/password_store_mac_unittest.cc b/chrome/browser/password_manager/password_store_mac_unittest.cc |
deleted file mode 100644 |
index 02241aba111d201eb4b7eb22ab2e745c0e2eb01b..0000000000000000000000000000000000000000 |
--- a/chrome/browser/password_manager/password_store_mac_unittest.cc |
+++ /dev/null |
@@ -1,2113 +0,0 @@ |
-// Copyright (c) 2012 The Chromium Authors. All rights reserved. |
-// Use of this source code is governed by a BSD-style license that can be |
-// found in the LICENSE file. |
- |
-#include "chrome/browser/password_manager/password_store_mac.h" |
- |
-#include <stddef.h> |
- |
-#include <string> |
- |
-#include "base/files/scoped_temp_dir.h" |
-#include "base/macros.h" |
-#include "base/memory/ptr_util.h" |
-#include "base/message_loop/message_loop.h" |
-#include "base/run_loop.h" |
-#include "base/scoped_observer.h" |
-#include "base/stl_util.h" |
-#include "base/strings/string_util.h" |
-#include "base/strings/utf_string_conversions.h" |
-#include "base/synchronization/waitable_event.h" |
-#include "base/test/histogram_tester.h" |
-#include "base/threading/thread_task_runner_handle.h" |
-#include "chrome/browser/password_manager/password_store_mac_internal.h" |
-#include "chrome/common/chrome_paths.h" |
-#include "components/os_crypt/os_crypt_mocker.h" |
-#include "components/password_manager/core/browser/login_database.h" |
-#include "components/password_manager/core/browser/password_manager_test_utils.h" |
-#include "components/password_manager/core/browser/password_manager_util.h" |
-#include "components/password_manager/core/browser/password_store_consumer.h" |
-#include "components/password_manager/core/browser/password_store_origin_unittest.h" |
-#include "content/public/browser/browser_thread.h" |
-#include "content/public/test/test_browser_thread_bundle.h" |
-#include "content/public/test/test_utils.h" |
-#include "crypto/mock_apple_keychain.h" |
-#include "testing/gmock/include/gmock/gmock.h" |
-#include "testing/gtest/include/gtest/gtest.h" |
-#include "url/origin.h" |
- |
-using autofill::PasswordForm; |
-using base::ASCIIToUTF16; |
-using base::WideToUTF16; |
-using content::BrowserThread; |
-using crypto::MockAppleKeychain; |
-using internal_keychain_helpers::FormsMatchForMerge; |
-using internal_keychain_helpers::STRICT_FORM_MATCH; |
-using password_manager::CreatePasswordFormFromDataForTesting; |
-using password_manager::LoginDatabase; |
-using password_manager::PasswordFormData; |
-using password_manager::PasswordStore; |
-using password_manager::PasswordStoreChange; |
-using password_manager::PasswordStoreChangeList; |
-using password_manager::PasswordStoreConsumer; |
-using testing::_; |
-using testing::DoAll; |
-using testing::Invoke; |
-using testing::IsEmpty; |
-using testing::SizeIs; |
-using testing::WithArg; |
- |
-namespace { |
- |
-ACTION(QuitUIMessageLoop) { |
- DCHECK_CURRENTLY_ON(BrowserThread::UI); |
- base::MessageLoop::current()->QuitWhenIdle(); |
-} |
- |
-// From the mock's argument #0 of type const std::vector<PasswordForm*>& takes |
-// the first form and copies it to the form pointed to by |target_form_ptr|. |
-ACTION_P(SaveACopyOfFirstForm, target_form_ptr) { |
- ASSERT_FALSE(arg0.empty()); |
- *target_form_ptr = *arg0[0]; |
-} |
- |
-void Noop() {} |
- |
-class MockPasswordStoreConsumer : public PasswordStoreConsumer { |
- public: |
- MOCK_METHOD1(OnGetPasswordStoreResultsConstRef, |
- void(const std::vector<std::unique_ptr<PasswordForm>>&)); |
- |
- // GMock cannot mock methods with move-only args. |
- void OnGetPasswordStoreResults( |
- std::vector<std::unique_ptr<PasswordForm>> results) override { |
- OnGetPasswordStoreResultsConstRef(results); |
- } |
-}; |
- |
-// A LoginDatabase that simulates an Init() method that takes a long time. |
-class SlowToInitLoginDatabase : public password_manager::LoginDatabase { |
- public: |
- // Creates an instance whose Init() method will block until |event| is |
- // signaled. |event| must outlive |this|. |
- SlowToInitLoginDatabase(const base::FilePath& db_path, |
- base::WaitableEvent* event) |
- : password_manager::LoginDatabase(db_path), event_(event) {} |
- ~SlowToInitLoginDatabase() override {} |
- |
- // LoginDatabase: |
- bool Init() override { |
- event_->Wait(); |
- return password_manager::LoginDatabase::Init(); |
- } |
- |
- private: |
- base::WaitableEvent* event_; |
- |
- DISALLOW_COPY_AND_ASSIGN(SlowToInitLoginDatabase); |
-}; |
- |
-#pragma mark - |
- |
-// Macro to simplify calling CheckFormsAgainstExpectations with a useful label. |
-#define CHECK_FORMS(forms, expectations, i) \ |
- CheckFormsAgainstExpectations(forms, expectations, #forms, i) |
- |
-// Ensures that the data in |forms| match |expectations|, causing test failures |
-// for any discrepencies. |
-// TODO(stuartmorgan): This is current order-dependent; ideally it shouldn't |
-// matter if |forms| and |expectations| are scrambled. |
-void CheckFormsAgainstExpectations( |
- const std::vector<std::unique_ptr<PasswordForm>>& forms, |
- const std::vector<PasswordFormData*>& expectations, |
- |
- const char* forms_label, |
- unsigned int test_number) { |
- EXPECT_EQ(expectations.size(), forms.size()) << forms_label << " in test " |
- << test_number; |
- if (expectations.size() != forms.size()) |
- return; |
- |
- for (unsigned int i = 0; i < expectations.size(); ++i) { |
- SCOPED_TRACE(testing::Message() << forms_label << " in test " << test_number |
- << ", item " << i); |
- PasswordForm* form = forms[i].get(); |
- PasswordFormData* expectation = expectations[i]; |
- EXPECT_EQ(expectation->scheme, form->scheme); |
- EXPECT_EQ(std::string(expectation->signon_realm), form->signon_realm); |
- EXPECT_EQ(GURL(expectation->origin), form->origin); |
- EXPECT_EQ(GURL(expectation->action), form->action); |
- EXPECT_EQ(WideToUTF16(expectation->submit_element), form->submit_element); |
- EXPECT_EQ(WideToUTF16(expectation->username_element), |
- form->username_element); |
- EXPECT_EQ(WideToUTF16(expectation->password_element), |
- form->password_element); |
- if (expectation->username_value) { |
- EXPECT_EQ(WideToUTF16(expectation->username_value), form->username_value); |
- EXPECT_EQ(WideToUTF16(expectation->username_value), form->display_name); |
- EXPECT_TRUE(form->skip_zero_click); |
- if (expectation->password_value && |
- wcscmp(expectation->password_value, |
- password_manager::kTestingFederatedLoginMarker) == 0) { |
- EXPECT_TRUE(form->password_value.empty()); |
- EXPECT_EQ( |
- url::Origin(GURL(password_manager::kTestingFederationUrlSpec)), |
- form->federation_origin); |
- } else { |
- EXPECT_EQ(WideToUTF16(expectation->password_value), |
- form->password_value); |
- EXPECT_TRUE(form->federation_origin.unique()); |
- } |
- } else { |
- EXPECT_TRUE(form->blacklisted_by_user); |
- } |
- EXPECT_EQ(expectation->preferred, form->preferred); |
- EXPECT_DOUBLE_EQ(expectation->creation_time, |
- form->date_created.ToDoubleT()); |
- base::Time created = base::Time::FromDoubleT(expectation->creation_time); |
- EXPECT_EQ( |
- created + base::TimeDelta::FromDays( |
- password_manager::kTestingDaysAfterPasswordsAreSynced), |
- form->date_synced); |
- EXPECT_EQ(GURL(password_manager::kTestingIconUrlSpec), form->icon_url); |
- } |
-} |
- |
-PasswordStoreChangeList AddChangeForForm(const PasswordForm& form) { |
- return PasswordStoreChangeList( |
- 1, PasswordStoreChange(PasswordStoreChange::ADD, form)); |
-} |
- |
-class PasswordStoreMacTestDelegate { |
- public: |
- PasswordStoreMacTestDelegate(); |
- ~PasswordStoreMacTestDelegate(); |
- |
- PasswordStoreMac* store() { return store_.get(); } |
- |
- static void FinishAsyncProcessing(); |
- |
- private: |
- void Initialize(); |
- |
- void ClosePasswordStore(); |
- |
- base::FilePath test_login_db_file_path() const; |
- |
- base::MessageLoopForUI message_loop_; |
- base::ScopedTempDir db_dir_; |
- std::unique_ptr<LoginDatabase> login_db_; |
- scoped_refptr<PasswordStoreMac> store_; |
- |
- DISALLOW_COPY_AND_ASSIGN(PasswordStoreMacTestDelegate); |
-}; |
- |
-PasswordStoreMacTestDelegate::PasswordStoreMacTestDelegate() { |
- Initialize(); |
-} |
- |
-PasswordStoreMacTestDelegate::~PasswordStoreMacTestDelegate() { |
- ClosePasswordStore(); |
-} |
- |
-void PasswordStoreMacTestDelegate::FinishAsyncProcessing() { |
- base::RunLoop().RunUntilIdle(); |
-} |
- |
-void PasswordStoreMacTestDelegate::Initialize() { |
- ASSERT_TRUE(db_dir_.CreateUniqueTempDir()); |
- |
- // Ensure that LoginDatabase will use the mock keychain if it needs to |
- // encrypt/decrypt a password. |
- OSCryptMocker::SetUpWithSingleton(); |
- login_db_.reset(new LoginDatabase(test_login_db_file_path())); |
- ASSERT_TRUE(login_db_->Init()); |
- |
- // Create and initialize the password store. |
- store_ = new PasswordStoreMac(base::ThreadTaskRunnerHandle::Get(), |
- base::ThreadTaskRunnerHandle::Get(), |
- base::WrapUnique(new MockAppleKeychain)); |
- store_->set_login_metadata_db(login_db_.get()); |
- store_->login_metadata_db()->set_clear_password_values(false); |
-} |
- |
-void PasswordStoreMacTestDelegate::ClosePasswordStore() { |
- store_->ShutdownOnUIThread(); |
- FinishAsyncProcessing(); |
- OSCryptMocker::TearDown(); |
-} |
- |
-base::FilePath PasswordStoreMacTestDelegate::test_login_db_file_path() const { |
- return db_dir_.GetPath().Append(FILE_PATH_LITERAL("login.db")); |
-} |
- |
-} // namespace |
- |
-namespace password_manager { |
- |
-INSTANTIATE_TYPED_TEST_CASE_P(Mac, |
- PasswordStoreOriginTest, |
- PasswordStoreMacTestDelegate); |
- |
-} // namespace password_manager |
- |
-#pragma mark - |
- |
-class PasswordStoreMacInternalsTest : public testing::Test { |
- public: |
- void SetUp() override { |
- MockAppleKeychain::KeychainTestData test_data[] = { |
- // Basic HTML form. |
- {kSecAuthenticationTypeHTMLForm, "some.domain.com", |
- kSecProtocolTypeHTTP, NULL, 0, NULL, "20020601171500Z", "joe_user", |
- "sekrit", false}, |
- // HTML form with path. |
- {kSecAuthenticationTypeHTMLForm, "some.domain.com", |
- kSecProtocolTypeHTTP, "/insecure.html", 0, NULL, "19991231235959Z", |
- "joe_user", "sekrit", false}, |
- // Secure HTML form with path. |
- {kSecAuthenticationTypeHTMLForm, "some.domain.com", |
- kSecProtocolTypeHTTPS, "/secure.html", 0, NULL, "20100908070605Z", |
- "secure_user", "password", false}, |
- // True negative item. |
- {kSecAuthenticationTypeHTMLForm, "dont.remember.com", |
- kSecProtocolTypeHTTP, NULL, 0, NULL, "20000101000000Z", "", "", true}, |
- // De-facto negative item, type one. |
- {kSecAuthenticationTypeHTMLForm, "dont.remember.com", |
- kSecProtocolTypeHTTP, NULL, 0, NULL, "20000101000000Z", |
- "Password Not Stored", "", false}, |
- // De-facto negative item, type two. |
- {kSecAuthenticationTypeHTMLForm, "dont.remember.com", |
- kSecProtocolTypeHTTPS, NULL, 0, NULL, "20000101000000Z", |
- "Password Not Stored", " ", false}, |
- // HTTP auth basic, with port and path. |
- {kSecAuthenticationTypeHTTPBasic, "some.domain.com", |
- kSecProtocolTypeHTTP, "/insecure.html", 4567, "low_security", |
- "19980330100000Z", "basic_auth_user", "basic", false}, |
- // HTTP auth digest, secure. |
- {kSecAuthenticationTypeHTTPDigest, "some.domain.com", |
- kSecProtocolTypeHTTPS, NULL, 0, "high_security", "19980330100000Z", |
- "digest_auth_user", "digest", false}, |
- // An FTP password with an invalid date, for edge-case testing. |
- {kSecAuthenticationTypeDefault, "a.server.com", kSecProtocolTypeFTP, |
- NULL, 0, NULL, "20010203040", "abc", "123", false}, |
- // Password for an Android application. |
- {kSecAuthenticationTypeHTMLForm, "android://hash@com.domain.some/", |
- kSecProtocolTypeHTTPS, "", 0, NULL, "20150515141312Z", "joe_user", |
- "secret", false}, |
- }; |
- |
- keychain_ = new MockAppleKeychain(); |
- |
- for (unsigned int i = 0; i < arraysize(test_data); ++i) { |
- keychain_->AddTestItem(test_data[i]); |
- } |
- } |
- |
- void TearDown() override { |
- ExpectCreatesAndFreesBalanced(); |
- ExpectCreatorCodesSet(); |
- delete keychain_; |
- } |
- |
- protected: |
- // Causes a test failure unless everything returned from keychain_'s |
- // ItemCopyAttributesAndData, SearchCreateFromAttributes, and SearchCopyNext |
- // was correctly freed. |
- void ExpectCreatesAndFreesBalanced() { |
- EXPECT_EQ(0, keychain_->UnfreedSearchCount()); |
- EXPECT_EQ(0, keychain_->UnfreedKeychainItemCount()); |
- EXPECT_EQ(0, keychain_->UnfreedAttributeDataCount()); |
- } |
- |
- // Causes a test failure unless any Keychain items added during the test have |
- // their creator code set. |
- void ExpectCreatorCodesSet() { |
- EXPECT_TRUE(keychain_->CreatorCodesSetForAddedItems()); |
- } |
- |
- MockAppleKeychain* keychain_; |
-}; |
- |
-#pragma mark - |
- |
-TEST_F(PasswordStoreMacInternalsTest, TestKeychainToFormTranslation) { |
- typedef struct { |
- const PasswordForm::Scheme scheme; |
- const char* signon_realm; |
- const char* origin; |
- const wchar_t* username; // Set to NULL to check for a blacklist entry. |
- const wchar_t* password; |
- const int creation_year; |
- const int creation_month; |
- const int creation_day; |
- const int creation_hour; |
- const int creation_minute; |
- const int creation_second; |
- } TestExpectations; |
- |
- TestExpectations expected[] = { |
- {PasswordForm::SCHEME_HTML, "http://some.domain.com/", |
- "http://some.domain.com/", L"joe_user", L"sekrit", 2002, 6, 1, 17, 15, |
- 0}, |
- {PasswordForm::SCHEME_HTML, "http://some.domain.com/", |
- "http://some.domain.com/insecure.html", L"joe_user", L"sekrit", 1999, 12, |
- 31, 23, 59, 59}, |
- {PasswordForm::SCHEME_HTML, "https://some.domain.com/", |
- "https://some.domain.com/secure.html", L"secure_user", L"password", 2010, |
- 9, 8, 7, 6, 5}, |
- {PasswordForm::SCHEME_HTML, "http://dont.remember.com/", |
- "http://dont.remember.com/", NULL, NULL, 2000, 1, 1, 0, 0, 0}, |
- {PasswordForm::SCHEME_HTML, "http://dont.remember.com/", |
- "http://dont.remember.com/", NULL, NULL, 2000, 1, 1, 0, 0, 0}, |
- {PasswordForm::SCHEME_HTML, "https://dont.remember.com/", |
- "https://dont.remember.com/", NULL, NULL, 2000, 1, 1, 0, 0, 0}, |
- {PasswordForm::SCHEME_BASIC, "http://some.domain.com:4567/low_security", |
- "http://some.domain.com:4567/insecure.html", L"basic_auth_user", |
- L"basic", 1998, 03, 30, 10, 00, 00}, |
- {PasswordForm::SCHEME_DIGEST, "https://some.domain.com/high_security", |
- "https://some.domain.com/", L"digest_auth_user", L"digest", 1998, 3, 30, |
- 10, 0, 0}, |
- // This one gives us an invalid date, which we will treat as a "NULL" date |
- // which is 1601. |
- {PasswordForm::SCHEME_OTHER, "http://a.server.com/", |
- "http://a.server.com/", L"abc", L"123", 1601, 1, 1, 0, 0, 0}, |
- {PasswordForm::SCHEME_HTML, "android://hash@com.domain.some/", "", |
- L"joe_user", L"secret", 2015, 5, 15, 14, 13, 12}, |
- }; |
- |
- for (unsigned int i = 0; i < arraysize(expected); ++i) { |
- SCOPED_TRACE(testing::Message("In iteration ") << i); |
- // Create our fake KeychainItemRef; see MockAppleKeychain docs. |
- SecKeychainItemRef keychain_item = |
- reinterpret_cast<SecKeychainItemRef>(i + 1); |
- PasswordForm form; |
- bool parsed = internal_keychain_helpers::FillPasswordFormFromKeychainItem( |
- *keychain_, keychain_item, &form, true); |
- |
- EXPECT_TRUE(parsed); |
- |
- EXPECT_EQ(expected[i].scheme, form.scheme); |
- EXPECT_EQ(GURL(expected[i].origin), form.origin); |
- EXPECT_EQ(std::string(expected[i].signon_realm), form.signon_realm); |
- if (expected[i].username) { |
- EXPECT_EQ(WideToUTF16(expected[i].username), form.username_value); |
- EXPECT_EQ(WideToUTF16(expected[i].password), form.password_value); |
- EXPECT_FALSE(form.blacklisted_by_user); |
- } else { |
- EXPECT_TRUE(form.blacklisted_by_user); |
- } |
- base::Time::Exploded exploded_time; |
- form.date_created.UTCExplode(&exploded_time); |
- EXPECT_EQ(expected[i].creation_year, exploded_time.year); |
- EXPECT_EQ(expected[i].creation_month, exploded_time.month); |
- EXPECT_EQ(expected[i].creation_day, exploded_time.day_of_month); |
- EXPECT_EQ(expected[i].creation_hour, exploded_time.hour); |
- EXPECT_EQ(expected[i].creation_minute, exploded_time.minute); |
- EXPECT_EQ(expected[i].creation_second, exploded_time.second); |
- } |
- |
- { |
- // Use an invalid ref, to make sure errors are reported. |
- SecKeychainItemRef keychain_item = reinterpret_cast<SecKeychainItemRef>(99); |
- PasswordForm form; |
- bool parsed = internal_keychain_helpers::FillPasswordFormFromKeychainItem( |
- *keychain_, keychain_item, &form, true); |
- EXPECT_FALSE(parsed); |
- } |
-} |
- |
-TEST_F(PasswordStoreMacInternalsTest, TestKeychainSearch) { |
- struct TestDataAndExpectation { |
- const PasswordFormData data; |
- const size_t expected_fill_matches; |
- const size_t expected_merge_matches; |
- }; |
- // Most fields are left blank because we don't care about them for searching. |
- TestDataAndExpectation test_data[] = { |
- // An HTML form we've seen. |
- {{PasswordForm::SCHEME_HTML, "http://some.domain.com/", NULL, NULL, NULL, |
- NULL, NULL, L"joe_user", NULL, false, 0}, |
- 2, |
- 2}, |
- {{PasswordForm::SCHEME_HTML, "http://some.domain.com/", NULL, NULL, NULL, |
- NULL, NULL, L"wrong_user", NULL, false, 0}, |
- 2, |
- 0}, |
- // An HTML form we haven't seen |
- {{PasswordForm::SCHEME_HTML, "http://www.unseendomain.com/", NULL, NULL, |
- NULL, NULL, NULL, L"joe_user", NULL, false, 0}, |
- 0, |
- 0}, |
- // Basic auth that should match. |
- {{PasswordForm::SCHEME_BASIC, "http://some.domain.com:4567/low_security", |
- NULL, NULL, NULL, NULL, NULL, L"basic_auth_user", NULL, false, 0}, |
- 1, |
- 1}, |
- // Basic auth with the wrong port. |
- {{PasswordForm::SCHEME_BASIC, "http://some.domain.com:1111/low_security", |
- NULL, NULL, NULL, NULL, NULL, L"basic_auth_user", NULL, false, 0}, |
- 0, |
- 0}, |
- // Digest auth we've saved under https, visited with http. |
- {{PasswordForm::SCHEME_DIGEST, "http://some.domain.com/high_security", |
- NULL, NULL, NULL, NULL, NULL, L"digest_auth_user", NULL, false, 0}, |
- 0, |
- 0}, |
- // Digest auth that should match. |
- {{PasswordForm::SCHEME_DIGEST, "https://some.domain.com/high_security", |
- NULL, NULL, NULL, NULL, NULL, L"wrong_user", NULL, false, 0}, |
- 1, |
- 0}, |
- // Digest auth with the wrong domain. |
- {{PasswordForm::SCHEME_DIGEST, "https://some.domain.com/other_domain", |
- NULL, NULL, NULL, NULL, NULL, L"digest_auth_user", NULL, false, 0}, |
- 0, |
- 0}, |
- // Android credentials (both legacy ones with origin, and without). |
- {{PasswordForm::SCHEME_HTML, "android://hash@com.domain.some/", |
- "android://hash@com.domain.some/", NULL, NULL, NULL, NULL, L"joe_user", |
- NULL, false, 0}, |
- 1, |
- 1}, |
- {{PasswordForm::SCHEME_HTML, "android://hash@com.domain.some/", NULL, |
- NULL, NULL, NULL, NULL, L"joe_user", NULL, false, 0}, |
- 1, |
- 1}, |
- // Federated logins do not have a corresponding Keychain entry, and should |
- // not match the username/password stored for the same application. Note |
- // that it will match for filling, however, because that part does not |
- // know |
- // that it is a federated login. |
- {{PasswordForm::SCHEME_HTML, "android://hash@com.domain.some/", NULL, |
- NULL, NULL, NULL, NULL, L"joe_user", |
- password_manager::kTestingFederatedLoginMarker, false, 0}, |
- 1, |
- 0}, |
- /// Garbage forms should have no matches. |
- {{PasswordForm::SCHEME_HTML, "foo/bar/baz", NULL, NULL, NULL, NULL, NULL, |
- NULL, NULL, false, 0}, |
- 0, |
- 0}, |
- }; |
- |
- MacKeychainPasswordFormAdapter keychain_adapter(keychain_); |
- MacKeychainPasswordFormAdapter owned_keychain_adapter(keychain_); |
- owned_keychain_adapter.SetFindsOnlyOwnedItems(true); |
- for (unsigned int i = 0; i < arraysize(test_data); ++i) { |
- std::unique_ptr<PasswordForm> query_form = |
- CreatePasswordFormFromDataForTesting(test_data[i].data); |
- |
- // Check matches treating the form as a fill target. |
- std::vector<std::unique_ptr<PasswordForm>> matching_items = |
- keychain_adapter.PasswordsFillingForm(query_form->signon_realm, |
- query_form->scheme); |
- EXPECT_EQ(test_data[i].expected_fill_matches, matching_items.size()); |
- |
- // Check matches treating the form as a merging target. |
- EXPECT_EQ(test_data[i].expected_merge_matches > 0, |
- keychain_adapter.HasPasswordsMergeableWithForm(*query_form)); |
- std::vector<SecKeychainItemRef> keychain_items; |
- std::vector<internal_keychain_helpers::ItemFormPair> item_form_pairs = |
- internal_keychain_helpers:: |
- ExtractAllKeychainItemAttributesIntoPasswordForms(&keychain_items, |
- *keychain_); |
- matching_items = |
- internal_keychain_helpers::ExtractPasswordsMergeableWithForm( |
- *keychain_, item_form_pairs, *query_form); |
- EXPECT_EQ(test_data[i].expected_merge_matches, matching_items.size()); |
- for (std::vector<SecKeychainItemRef>::iterator i = keychain_items.begin(); |
- i != keychain_items.end(); ++i) { |
- keychain_->Free(*i); |
- } |
- |
- // None of the pre-seeded items are owned by us, so none should match an |
- // owned-passwords-only search. |
- matching_items = owned_keychain_adapter.PasswordsFillingForm( |
- query_form->signon_realm, query_form->scheme); |
- EXPECT_EQ(0U, matching_items.size()); |
- } |
-} |
- |
-// Changes just the origin path of |form|. |
-static void SetPasswordFormPath(PasswordForm* form, const char* path) { |
- GURL::Replacements replacement; |
- std::string new_value(path); |
- replacement.SetPathStr(new_value); |
- form->origin = form->origin.ReplaceComponents(replacement); |
-} |
- |
-// Changes just the signon_realm port of |form|. |
-static void SetPasswordFormPort(PasswordForm* form, const char* port) { |
- GURL::Replacements replacement; |
- std::string new_value(port); |
- replacement.SetPortStr(new_value); |
- GURL signon_gurl = GURL(form->signon_realm); |
- form->signon_realm = signon_gurl.ReplaceComponents(replacement).spec(); |
-} |
- |
-// Changes just the signon_ream auth realm of |form|. |
-static void SetPasswordFormRealm(PasswordForm* form, const char* realm) { |
- GURL::Replacements replacement; |
- std::string new_value(realm); |
- replacement.SetPathStr(new_value); |
- GURL signon_gurl = GURL(form->signon_realm); |
- form->signon_realm = signon_gurl.ReplaceComponents(replacement).spec(); |
-} |
- |
-TEST_F(PasswordStoreMacInternalsTest, TestKeychainExactSearch) { |
- MacKeychainPasswordFormAdapter keychain_adapter(keychain_); |
- |
- PasswordFormData base_form_data[] = { |
- {PasswordForm::SCHEME_HTML, "http://some.domain.com/", |
- "http://some.domain.com/insecure.html", NULL, NULL, NULL, NULL, |
- L"joe_user", NULL, true, 0}, |
- {PasswordForm::SCHEME_BASIC, "http://some.domain.com:4567/low_security", |
- "http://some.domain.com:4567/insecure.html", NULL, NULL, NULL, NULL, |
- L"basic_auth_user", NULL, true, 0}, |
- {PasswordForm::SCHEME_DIGEST, "https://some.domain.com/high_security", |
- "https://some.domain.com", NULL, NULL, NULL, NULL, L"digest_auth_user", |
- NULL, true, 0}, |
- }; |
- |
- for (unsigned int i = 0; i < arraysize(base_form_data); ++i) { |
- // Create a base form and make sure we find a match. |
- std::unique_ptr<PasswordForm> base_form = |
- CreatePasswordFormFromDataForTesting(base_form_data[i]); |
- EXPECT_TRUE(keychain_adapter.HasPasswordsMergeableWithForm(*base_form)); |
- EXPECT_TRUE(keychain_adapter.HasPasswordExactlyMatchingForm(*base_form)); |
- |
- // Make sure that the matching isn't looser than it should be by checking |
- // that slightly altered forms don't match. |
- std::vector<std::unique_ptr<PasswordForm>> modified_forms; |
- |
- modified_forms.push_back(base::MakeUnique<PasswordForm>(*base_form)); |
- modified_forms.back()->username_value = ASCIIToUTF16("wrong_user"); |
- |
- modified_forms.push_back(base::MakeUnique<PasswordForm>(*base_form)); |
- SetPasswordFormPath(modified_forms.back().get(), "elsewhere.html"); |
- |
- modified_forms.push_back(base::MakeUnique<PasswordForm>(*base_form)); |
- modified_forms.back()->scheme = PasswordForm::SCHEME_OTHER; |
- |
- modified_forms.push_back(base::MakeUnique<PasswordForm>(*base_form)); |
- SetPasswordFormPort(modified_forms.back().get(), "1234"); |
- |
- modified_forms.push_back(base::MakeUnique<PasswordForm>(*base_form)); |
- modified_forms.back()->blacklisted_by_user = true; |
- |
- if (base_form->scheme == PasswordForm::SCHEME_BASIC || |
- base_form->scheme == PasswordForm::SCHEME_DIGEST) { |
- modified_forms.push_back(base::MakeUnique<PasswordForm>(*base_form)); |
- SetPasswordFormRealm(modified_forms.back().get(), "incorrect"); |
- } |
- |
- for (unsigned int j = 0; j < modified_forms.size(); ++j) { |
- bool match = |
- keychain_adapter.HasPasswordExactlyMatchingForm(*modified_forms[j]); |
- EXPECT_FALSE(match) << "In modified version " << j << " of base form " |
- << i; |
- } |
- } |
-} |
- |
-TEST_F(PasswordStoreMacInternalsTest, TestKeychainAdd) { |
- struct TestDataAndExpectation { |
- PasswordFormData data; |
- bool should_succeed; |
- }; |
- TestDataAndExpectation test_data[] = { |
- // Test a variety of scheme/port/protocol/path variations. |
- {{PasswordForm::SCHEME_HTML, "http://web.site.com/", |
- "http://web.site.com/path/to/page.html", NULL, NULL, NULL, NULL, |
- L"anonymous", L"knock-knock", false, 0}, |
- true}, |
- {{PasswordForm::SCHEME_HTML, "https://web.site.com/", |
- "https://web.site.com/", NULL, NULL, NULL, NULL, L"admin", L"p4ssw0rd", |
- false, 0}, |
- true}, |
- {{PasswordForm::SCHEME_BASIC, "http://a.site.com:2222/therealm", |
- "http://a.site.com:2222/", NULL, NULL, NULL, NULL, L"username", |
- L"password", false, 0}, |
- true}, |
- {{PasswordForm::SCHEME_DIGEST, "https://digest.site.com/differentrealm", |
- "https://digest.site.com/secure.html", NULL, NULL, NULL, NULL, |
- L"testname", L"testpass", false, 0}, |
- true}, |
- // Test that Android credentials can be stored. Also check the legacy form |
- // when |origin| was still filled with the Android URI (and not left |
- // empty). |
- {{PasswordForm::SCHEME_HTML, "android://hash@com.example.alpha/", "", |
- NULL, NULL, NULL, NULL, L"joe_user", L"password", false, 0}, |
- true}, |
- {{PasswordForm::SCHEME_HTML, "android://hash@com.example.beta/", |
- "android://hash@com.example.beta/", NULL, NULL, NULL, NULL, |
- L"jane_user", L"password2", false, 0}, |
- true}, |
- // Make sure that garbage forms are rejected. |
- {{PasswordForm::SCHEME_HTML, "gobbledygook", "gobbledygook", NULL, NULL, |
- NULL, NULL, L"anonymous", L"knock-knock", false, 0}, |
- false}, |
- // Test that failing to update a duplicate (forced using the magic failure |
- // password; see MockAppleKeychain::ItemModifyAttributesAndData) is |
- // reported. |
- {{PasswordForm::SCHEME_HTML, "http://some.domain.com", |
- "http://some.domain.com/insecure.html", NULL, NULL, NULL, NULL, |
- L"joe_user", L"fail_me", false, 0}, |
- false}, |
- }; |
- |
- MacKeychainPasswordFormAdapter owned_keychain_adapter(keychain_); |
- owned_keychain_adapter.SetFindsOnlyOwnedItems(true); |
- |
- for (unsigned int i = 0; i < arraysize(test_data); ++i) { |
- std::unique_ptr<PasswordForm> in_form = |
- CreatePasswordFormFromDataForTesting(test_data[i].data); |
- bool add_succeeded = owned_keychain_adapter.AddPassword(*in_form); |
- EXPECT_EQ(test_data[i].should_succeed, add_succeeded); |
- if (add_succeeded) { |
- EXPECT_TRUE( |
- owned_keychain_adapter.HasPasswordsMergeableWithForm(*in_form)); |
- EXPECT_TRUE( |
- owned_keychain_adapter.HasPasswordExactlyMatchingForm(*in_form)); |
- } |
- } |
- |
- // Test that adding duplicate item updates the existing item. |
- // TODO(engedy): Add a test to verify that updating Android credentials work. |
- // See: https://crbug.com/476851. |
- { |
- PasswordFormData data = {PasswordForm::SCHEME_HTML, |
- "http://some.domain.com", |
- "http://some.domain.com/insecure.html", |
- NULL, |
- NULL, |
- NULL, |
- NULL, |
- L"joe_user", |
- L"updated_password", |
- false, |
- 0}; |
- std::unique_ptr<PasswordForm> update_form = |
- CreatePasswordFormFromDataForTesting(data); |
- MacKeychainPasswordFormAdapter keychain_adapter(keychain_); |
- EXPECT_TRUE(keychain_adapter.AddPassword(*update_form)); |
- SecKeychainItemRef keychain_item = reinterpret_cast<SecKeychainItemRef>(2); |
- PasswordForm stored_form; |
- internal_keychain_helpers::FillPasswordFormFromKeychainItem( |
- *keychain_, keychain_item, &stored_form, true); |
- EXPECT_EQ(update_form->password_value, stored_form.password_value); |
- } |
-} |
- |
-TEST_F(PasswordStoreMacInternalsTest, TestKeychainRemove) { |
- struct TestDataAndExpectation { |
- PasswordFormData data; |
- bool should_succeed; |
- }; |
- TestDataAndExpectation test_data[] = { |
- // Test deletion of an item that we add. |
- {{PasswordForm::SCHEME_HTML, "http://web.site.com/", |
- "http://web.site.com/path/to/page.html", NULL, NULL, NULL, NULL, |
- L"anonymous", L"knock-knock", false, 0}, |
- true}, |
- // Test that Android credentials can be removed. Also check the legacy |
- // case when |origin| was still filled with the Android URI (and not left |
- // empty). |
- {{PasswordForm::SCHEME_HTML, "android://hash@com.example.alpha/", "", |
- NULL, NULL, NULL, NULL, L"joe_user", L"secret", false, 0}, |
- true}, |
- {{PasswordForm::SCHEME_HTML, "android://hash@com.example.beta/", |
- "android://hash@com.example.beta/", NULL, NULL, NULL, NULL, |
- L"jane_user", L"secret", false, 0}, |
- true}, |
- // Make sure we don't delete items we don't own. |
- {{PasswordForm::SCHEME_HTML, "http://some.domain.com/", |
- "http://some.domain.com/insecure.html", NULL, NULL, NULL, NULL, |
- L"joe_user", NULL, true, 0}, |
- false}, |
- }; |
- |
- MacKeychainPasswordFormAdapter owned_keychain_adapter(keychain_); |
- owned_keychain_adapter.SetFindsOnlyOwnedItems(true); |
- |
- // Add our test items (except the last one) so that we can delete them. |
- for (unsigned int i = 0; i + 1 < arraysize(test_data); ++i) { |
- std::unique_ptr<PasswordForm> add_form = |
- CreatePasswordFormFromDataForTesting(test_data[i].data); |
- EXPECT_TRUE(owned_keychain_adapter.AddPassword(*add_form)); |
- } |
- |
- for (unsigned int i = 0; i < arraysize(test_data); ++i) { |
- std::unique_ptr<PasswordForm> form = |
- CreatePasswordFormFromDataForTesting(test_data[i].data); |
- EXPECT_EQ(test_data[i].should_succeed, |
- owned_keychain_adapter.RemovePassword(*form)); |
- |
- MacKeychainPasswordFormAdapter keychain_adapter(keychain_); |
- bool match = keychain_adapter.HasPasswordExactlyMatchingForm(*form); |
- EXPECT_EQ(test_data[i].should_succeed, !match); |
- } |
-} |
- |
-TEST_F(PasswordStoreMacInternalsTest, TestFormMatch) { |
- PasswordForm base_form; |
- base_form.signon_realm = std::string("http://some.domain.com/"); |
- base_form.origin = GURL("http://some.domain.com/page.html"); |
- base_form.username_value = ASCIIToUTF16("joe_user"); |
- |
- { |
- // Check that everything unimportant can be changed. |
- PasswordForm different_form(base_form); |
- different_form.username_element = ASCIIToUTF16("username"); |
- different_form.submit_element = ASCIIToUTF16("submit"); |
- different_form.username_element = ASCIIToUTF16("password"); |
- different_form.password_value = ASCIIToUTF16("sekrit"); |
- different_form.action = GURL("http://some.domain.com/action.cgi"); |
- different_form.preferred = true; |
- different_form.date_created = base::Time::Now(); |
- EXPECT_TRUE( |
- FormsMatchForMerge(base_form, different_form, STRICT_FORM_MATCH)); |
- |
- // Check that path differences don't prevent a match. |
- base_form.origin = GURL("http://some.domain.com/other_page.html"); |
- EXPECT_TRUE( |
- FormsMatchForMerge(base_form, different_form, STRICT_FORM_MATCH)); |
- } |
- |
- // Check that any one primary key changing is enough to prevent matching. |
- { |
- PasswordForm different_form(base_form); |
- different_form.scheme = PasswordForm::SCHEME_DIGEST; |
- EXPECT_FALSE( |
- FormsMatchForMerge(base_form, different_form, STRICT_FORM_MATCH)); |
- } |
- { |
- PasswordForm different_form(base_form); |
- different_form.signon_realm = std::string("http://some.domain.com:8080/"); |
- EXPECT_FALSE( |
- FormsMatchForMerge(base_form, different_form, STRICT_FORM_MATCH)); |
- } |
- { |
- PasswordForm different_form(base_form); |
- different_form.username_value = ASCIIToUTF16("john.doe"); |
- EXPECT_FALSE( |
- FormsMatchForMerge(base_form, different_form, STRICT_FORM_MATCH)); |
- } |
- { |
- PasswordForm different_form(base_form); |
- different_form.blacklisted_by_user = true; |
- EXPECT_FALSE( |
- FormsMatchForMerge(base_form, different_form, STRICT_FORM_MATCH)); |
- } |
- |
- // Blacklist forms should *never* match for merging, even when identical |
- // (and certainly not when only one is a blacklist entry). |
- { |
- PasswordForm form_a(base_form); |
- form_a.blacklisted_by_user = true; |
- PasswordForm form_b(form_a); |
- EXPECT_FALSE(FormsMatchForMerge(form_a, form_b, STRICT_FORM_MATCH)); |
- } |
- |
- // Federated login forms should never match for merging either. |
- { |
- PasswordForm form_b(base_form); |
- form_b.federation_origin = |
- url::Origin(GURL(password_manager::kTestingFederationUrlSpec)); |
- EXPECT_FALSE(FormsMatchForMerge(base_form, form_b, STRICT_FORM_MATCH)); |
- EXPECT_FALSE(FormsMatchForMerge(form_b, base_form, STRICT_FORM_MATCH)); |
- EXPECT_FALSE(FormsMatchForMerge(form_b, form_b, STRICT_FORM_MATCH)); |
- } |
-} |
- |
-TEST_F(PasswordStoreMacInternalsTest, TestFormMerge) { |
- // Set up a bunch of test data to use in varying combinations. |
- PasswordFormData keychain_user_1 = {PasswordForm::SCHEME_HTML, |
- "http://some.domain.com/", |
- "http://some.domain.com/", |
- "", |
- L"", |
- L"", |
- L"", |
- L"joe_user", |
- L"sekrit", |
- false, |
- 1010101010}; |
- PasswordFormData keychain_user_1_with_path = { |
- PasswordForm::SCHEME_HTML, |
- "http://some.domain.com/", |
- "http://some.domain.com/page.html", |
- "", |
- L"", |
- L"", |
- L"", |
- L"joe_user", |
- L"otherpassword", |
- false, |
- 1010101010}; |
- PasswordFormData keychain_user_2 = {PasswordForm::SCHEME_HTML, |
- "http://some.domain.com/", |
- "http://some.domain.com/", |
- "", |
- L"", |
- L"", |
- L"", |
- L"john.doe", |
- L"sesame", |
- false, |
- 958739876}; |
- PasswordFormData keychain_blacklist = {PasswordForm::SCHEME_HTML, |
- "http://some.domain.com/", |
- "http://some.domain.com/", |
- "", |
- L"", |
- L"", |
- L"", |
- NULL, |
- NULL, |
- false, |
- 1010101010}; |
- PasswordFormData keychain_android = {PasswordForm::SCHEME_HTML, |
- "android://hash@com.domain.some/", |
- "", |
- "", |
- L"", |
- L"", |
- L"", |
- L"joe_user", |
- L"secret", |
- false, |
- 1234567890}; |
- |
- PasswordFormData db_user_1 = {PasswordForm::SCHEME_HTML, |
- "http://some.domain.com/", |
- "http://some.domain.com/", |
- "http://some.domain.com/action.cgi", |
- L"submit", |
- L"username", |
- L"password", |
- L"joe_user", |
- L"", |
- true, |
- 1212121212}; |
- PasswordFormData db_user_1_with_path = { |
- PasswordForm::SCHEME_HTML, |
- "http://some.domain.com/", |
- "http://some.domain.com/page.html", |
- "http://some.domain.com/handlepage.cgi", |
- L"submit", |
- L"username", |
- L"password", |
- L"joe_user", |
- L"", |
- true, |
- 1234567890}; |
- PasswordFormData db_user_3_with_path = { |
- PasswordForm::SCHEME_HTML, |
- "http://some.domain.com/", |
- "http://some.domain.com/page.html", |
- "http://some.domain.com/handlepage.cgi", |
- L"submit", |
- L"username", |
- L"password", |
- L"second-account", |
- L"", |
- true, |
- 1240000000}; |
- PasswordFormData database_blacklist_with_path = { |
- PasswordForm::SCHEME_HTML, |
- "http://some.domain.com/", |
- "http://some.domain.com/path.html", |
- "http://some.domain.com/action.cgi", |
- L"submit", |
- L"username", |
- L"password", |
- NULL, |
- NULL, |
- true, |
- 1212121212}; |
- PasswordFormData db_android = {PasswordForm::SCHEME_HTML, |
- "android://hash@com.domain.some/", |
- "android://hash@com.domain.some/", |
- "", |
- L"", |
- L"", |
- L"", |
- L"joe_user", |
- L"", |
- false, |
- 1234567890}; |
- PasswordFormData db_federated = { |
- PasswordForm::SCHEME_HTML, |
- "android://hash@com.domain.some/", |
- "android://hash@com.domain.some/", |
- "", |
- L"", |
- L"", |
- L"", |
- L"joe_user", |
- password_manager::kTestingFederatedLoginMarker, |
- false, |
- 3434343434}; |
- |
- PasswordFormData merged_user_1 = {PasswordForm::SCHEME_HTML, |
- "http://some.domain.com/", |
- "http://some.domain.com/", |
- "http://some.domain.com/action.cgi", |
- L"submit", |
- L"username", |
- L"password", |
- L"joe_user", |
- L"sekrit", |
- true, |
- 1212121212}; |
- PasswordFormData merged_user_1_with_db_path = { |
- PasswordForm::SCHEME_HTML, |
- "http://some.domain.com/", |
- "http://some.domain.com/page.html", |
- "http://some.domain.com/handlepage.cgi", |
- L"submit", |
- L"username", |
- L"password", |
- L"joe_user", |
- L"sekrit", |
- true, |
- 1234567890}; |
- PasswordFormData merged_user_1_with_both_paths = { |
- PasswordForm::SCHEME_HTML, |
- "http://some.domain.com/", |
- "http://some.domain.com/page.html", |
- "http://some.domain.com/handlepage.cgi", |
- L"submit", |
- L"username", |
- L"password", |
- L"joe_user", |
- L"otherpassword", |
- true, |
- 1234567890}; |
- PasswordFormData merged_android = {PasswordForm::SCHEME_HTML, |
- "android://hash@com.domain.some/", |
- "android://hash@com.domain.some/", |
- "", |
- L"", |
- L"", |
- L"", |
- L"joe_user", |
- L"secret", |
- false, |
- 1234567890}; |
- |
- // Build up the big multi-dimensional array of data sets that will actually |
- // drive the test. Use vectors rather than arrays so that initialization is |
- // simple. |
- enum { |
- KEYCHAIN_INPUT = 0, |
- DATABASE_INPUT, |
- MERGE_OUTPUT, |
- KEYCHAIN_OUTPUT, |
- DATABASE_OUTPUT, |
- MERGE_IO_ARRAY_COUNT // termination marker |
- }; |
- const unsigned int kTestCount = 5; |
- std::vector<std::vector<std::vector<PasswordFormData*>>> test_data( |
- MERGE_IO_ARRAY_COUNT, std::vector<std::vector<PasswordFormData*>>( |
- kTestCount, std::vector<PasswordFormData*>())); |
- unsigned int current_test = 0; |
- |
- // Test a merge with a few accounts in both systems, with partial overlap. |
- CHECK(current_test < kTestCount); |
- test_data[KEYCHAIN_INPUT][current_test].push_back(&keychain_user_1); |
- test_data[KEYCHAIN_INPUT][current_test].push_back(&keychain_user_2); |
- test_data[DATABASE_INPUT][current_test].push_back(&db_user_1); |
- test_data[DATABASE_INPUT][current_test].push_back(&db_user_1_with_path); |
- test_data[DATABASE_INPUT][current_test].push_back(&db_user_3_with_path); |
- test_data[MERGE_OUTPUT][current_test].push_back(&merged_user_1); |
- test_data[MERGE_OUTPUT][current_test].push_back(&merged_user_1_with_db_path); |
- test_data[KEYCHAIN_OUTPUT][current_test].push_back(&keychain_user_2); |
- test_data[DATABASE_OUTPUT][current_test].push_back(&db_user_3_with_path); |
- |
- // Test a merge where Chrome has a blacklist entry, and the keychain has |
- // a stored account. |
- ++current_test; |
- CHECK(current_test < kTestCount); |
- test_data[KEYCHAIN_INPUT][current_test].push_back(&keychain_user_1); |
- test_data[DATABASE_INPUT][current_test].push_back( |
- &database_blacklist_with_path); |
- // We expect both to be present because a blacklist could be specific to a |
- // subpath, and we want access to the password on other paths. |
- test_data[MERGE_OUTPUT][current_test].push_back( |
- &database_blacklist_with_path); |
- test_data[KEYCHAIN_OUTPUT][current_test].push_back(&keychain_user_1); |
- |
- // Test a merge where Chrome has an account, and Keychain has a blacklist |
- // (from another browser) and the Chrome password data. |
- ++current_test; |
- CHECK(current_test < kTestCount); |
- test_data[KEYCHAIN_INPUT][current_test].push_back(&keychain_blacklist); |
- test_data[KEYCHAIN_INPUT][current_test].push_back(&keychain_user_1); |
- test_data[DATABASE_INPUT][current_test].push_back(&db_user_1); |
- test_data[MERGE_OUTPUT][current_test].push_back(&merged_user_1); |
- test_data[KEYCHAIN_OUTPUT][current_test].push_back(&keychain_blacklist); |
- |
- // Test that matches are done using exact path when possible. |
- ++current_test; |
- CHECK(current_test < kTestCount); |
- test_data[KEYCHAIN_INPUT][current_test].push_back(&keychain_user_1); |
- test_data[KEYCHAIN_INPUT][current_test].push_back(&keychain_user_1_with_path); |
- test_data[DATABASE_INPUT][current_test].push_back(&db_user_1); |
- test_data[DATABASE_INPUT][current_test].push_back(&db_user_1_with_path); |
- test_data[MERGE_OUTPUT][current_test].push_back(&merged_user_1); |
- test_data[MERGE_OUTPUT][current_test].push_back( |
- &merged_user_1_with_both_paths); |
- |
- // Test that Android credentails are matched correctly and that federated |
- // credentials are not tried to be matched with a Keychain item. |
- ++current_test; |
- CHECK(current_test < kTestCount); |
- test_data[KEYCHAIN_INPUT][current_test].push_back(&keychain_android); |
- test_data[DATABASE_INPUT][current_test].push_back(&db_federated); |
- test_data[DATABASE_INPUT][current_test].push_back(&db_android); |
- test_data[MERGE_OUTPUT][current_test].push_back(&db_federated); |
- test_data[MERGE_OUTPUT][current_test].push_back(&merged_android); |
- |
- for (unsigned int test_case = 0; test_case <= current_test; ++test_case) { |
- std::vector<std::unique_ptr<PasswordForm>> keychain_forms; |
- for (std::vector<PasswordFormData*>::iterator i = |
- test_data[KEYCHAIN_INPUT][test_case].begin(); |
- i != test_data[KEYCHAIN_INPUT][test_case].end(); ++i) { |
- keychain_forms.push_back(CreatePasswordFormFromDataForTesting(*(*i))); |
- } |
- std::vector<std::unique_ptr<PasswordForm>> database_forms; |
- for (std::vector<PasswordFormData*>::iterator i = |
- test_data[DATABASE_INPUT][test_case].begin(); |
- i != test_data[DATABASE_INPUT][test_case].end(); ++i) { |
- database_forms.push_back(CreatePasswordFormFromDataForTesting(*(*i))); |
- } |
- |
- std::vector<std::unique_ptr<PasswordForm>> merged_forms; |
- internal_keychain_helpers::MergePasswordForms( |
- &keychain_forms, &database_forms, &merged_forms); |
- |
- CHECK_FORMS(keychain_forms, test_data[KEYCHAIN_OUTPUT][test_case], |
- test_case); |
- CHECK_FORMS(database_forms, test_data[DATABASE_OUTPUT][test_case], |
- test_case); |
- CHECK_FORMS(merged_forms, test_data[MERGE_OUTPUT][test_case], test_case); |
- } |
-} |
- |
-TEST_F(PasswordStoreMacInternalsTest, TestPasswordBulkLookup) { |
- PasswordFormData db_data[] = { |
- {PasswordForm::SCHEME_HTML, "http://some.domain.com/", |
- "http://some.domain.com/", "http://some.domain.com/action.cgi", |
- L"submit", L"username", L"password", L"joe_user", L"", true, 1212121212}, |
- {PasswordForm::SCHEME_HTML, "http://some.domain.com/", |
- "http://some.domain.com/page.html", |
- "http://some.domain.com/handlepage.cgi", L"submit", L"username", |
- L"password", L"joe_user", L"", true, 1234567890}, |
- {PasswordForm::SCHEME_HTML, "http://some.domain.com/", |
- "http://some.domain.com/page.html", |
- "http://some.domain.com/handlepage.cgi", L"submit", L"username", |
- L"password", L"second-account", L"", true, 1240000000}, |
- {PasswordForm::SCHEME_HTML, "http://dont.remember.com/", |
- "http://dont.remember.com/", "http://dont.remember.com/handlepage.cgi", |
- L"submit", L"username", L"password", L"joe_user", L"", true, 1240000000}, |
- {PasswordForm::SCHEME_HTML, "http://some.domain.com/", |
- "http://some.domain.com/path.html", "http://some.domain.com/action.cgi", |
- L"submit", L"username", L"password", NULL, NULL, true, 1212121212}, |
- }; |
- std::vector<std::unique_ptr<PasswordForm>> database_forms; |
- for (const PasswordFormData& form_data : db_data) { |
- database_forms.push_back(CreatePasswordFormFromDataForTesting(form_data)); |
- } |
- std::vector<std::unique_ptr<PasswordForm>> merged_forms; |
- internal_keychain_helpers::GetPasswordsForForms(*keychain_, &database_forms, |
- &merged_forms); |
- EXPECT_EQ(2U, database_forms.size()); |
- ASSERT_EQ(3U, merged_forms.size()); |
- EXPECT_EQ(ASCIIToUTF16("sekrit"), merged_forms[0]->password_value); |
- EXPECT_EQ(ASCIIToUTF16("sekrit"), merged_forms[1]->password_value); |
- EXPECT_TRUE(merged_forms[2]->blacklisted_by_user); |
-} |
- |
-TEST_F(PasswordStoreMacInternalsTest, TestBlacklistedFiltering) { |
- PasswordFormData db_data[] = { |
- {PasswordForm::SCHEME_HTML, "http://dont.remember.com/", |
- "http://dont.remember.com/", "http://dont.remember.com/handlepage.cgi", |
- L"submit", L"username", L"password", L"joe_user", L"non_empty_password", |
- true, 1240000000}, |
- {PasswordForm::SCHEME_HTML, "https://dont.remember.com/", |
- "https://dont.remember.com/", |
- "https://dont.remember.com/handlepage_secure.cgi", L"submit", |
- L"username", L"password", L"joe_user", L"non_empty_password", true, |
- 1240000000}, |
- }; |
- std::vector<std::unique_ptr<PasswordForm>> database_forms; |
- for (const PasswordFormData& form_data : db_data) { |
- database_forms.push_back(CreatePasswordFormFromDataForTesting(form_data)); |
- } |
- std::vector<std::unique_ptr<PasswordForm>> merged_forms; |
- internal_keychain_helpers::GetPasswordsForForms(*keychain_, &database_forms, |
- &merged_forms); |
- EXPECT_EQ(2U, database_forms.size()); |
- ASSERT_EQ(0U, merged_forms.size()); |
-} |
- |
-TEST_F(PasswordStoreMacInternalsTest, TestFillPasswordFormFromKeychainItem) { |
- // When |extract_password_data| is false, the password field must be empty, |
- // and |blacklisted_by_user| must be false. |
- SecKeychainItemRef keychain_item = reinterpret_cast<SecKeychainItemRef>(1); |
- PasswordForm form_without_extracted_password; |
- bool parsed = internal_keychain_helpers::FillPasswordFormFromKeychainItem( |
- *keychain_, keychain_item, &form_without_extracted_password, |
- false); // Do not extract password. |
- EXPECT_TRUE(parsed); |
- ASSERT_TRUE(form_without_extracted_password.password_value.empty()); |
- ASSERT_FALSE(form_without_extracted_password.blacklisted_by_user); |
- |
- // When |extract_password_data| is true and the keychain entry has a non-empty |
- // password, the password field must be non-empty, and the value of |
- // |blacklisted_by_user| must be false. |
- keychain_item = reinterpret_cast<SecKeychainItemRef>(1); |
- PasswordForm form_with_extracted_password; |
- parsed = internal_keychain_helpers::FillPasswordFormFromKeychainItem( |
- *keychain_, keychain_item, &form_with_extracted_password, |
- true); // Extract password. |
- EXPECT_TRUE(parsed); |
- ASSERT_EQ(ASCIIToUTF16("sekrit"), |
- form_with_extracted_password.password_value); |
- ASSERT_FALSE(form_with_extracted_password.blacklisted_by_user); |
- |
- // When |extract_password_data| is true and the keychain entry has an empty |
- // username and password (""), the password field must be empty, and the value |
- // of |blacklisted_by_user| must be true. |
- keychain_item = reinterpret_cast<SecKeychainItemRef>(4); |
- PasswordForm negative_form; |
- parsed = internal_keychain_helpers::FillPasswordFormFromKeychainItem( |
- *keychain_, keychain_item, &negative_form, |
- true); // Extract password. |
- EXPECT_TRUE(parsed); |
- ASSERT_TRUE(negative_form.username_value.empty()); |
- ASSERT_TRUE(negative_form.password_value.empty()); |
- ASSERT_TRUE(negative_form.blacklisted_by_user); |
- |
- // When |extract_password_data| is true and the keychain entry has an empty |
- // password (""), the password field must be empty (""), and the value of |
- // |blacklisted_by_user| must be true. |
- keychain_item = reinterpret_cast<SecKeychainItemRef>(5); |
- PasswordForm form_with_empty_password_a; |
- parsed = internal_keychain_helpers::FillPasswordFormFromKeychainItem( |
- *keychain_, keychain_item, &form_with_empty_password_a, |
- true); // Extract password. |
- EXPECT_TRUE(parsed); |
- ASSERT_TRUE(form_with_empty_password_a.password_value.empty()); |
- ASSERT_TRUE(form_with_empty_password_a.blacklisted_by_user); |
- |
- // When |extract_password_data| is true and the keychain entry has a single |
- // space password (" "), the password field must be a single space (" "), and |
- // the value of |blacklisted_by_user| must be true. |
- keychain_item = reinterpret_cast<SecKeychainItemRef>(6); |
- PasswordForm form_with_empty_password_b; |
- parsed = internal_keychain_helpers::FillPasswordFormFromKeychainItem( |
- *keychain_, keychain_item, &form_with_empty_password_b, |
- true); // Extract password. |
- EXPECT_TRUE(parsed); |
- ASSERT_EQ(ASCIIToUTF16(" "), form_with_empty_password_b.password_value); |
- ASSERT_TRUE(form_with_empty_password_b.blacklisted_by_user); |
-} |
- |
-TEST_F(PasswordStoreMacInternalsTest, TestPasswordGetAll) { |
- MacKeychainPasswordFormAdapter keychain_adapter(keychain_); |
- MacKeychainPasswordFormAdapter owned_keychain_adapter(keychain_); |
- owned_keychain_adapter.SetFindsOnlyOwnedItems(true); |
- |
- // Add a few passwords of various types so that we own some. |
- PasswordFormData owned_password_data[] = { |
- {PasswordForm::SCHEME_HTML, "http://web.site.com/", |
- "http://web.site.com/path/to/page.html", NULL, NULL, NULL, NULL, |
- L"anonymous", L"knock-knock", false, 0}, |
- {PasswordForm::SCHEME_BASIC, "http://a.site.com:2222/therealm", |
- "http://a.site.com:2222/", NULL, NULL, NULL, NULL, L"username", |
- L"password", false, 0}, |
- {PasswordForm::SCHEME_DIGEST, "https://digest.site.com/differentrealm", |
- "https://digest.site.com/secure.html", NULL, NULL, NULL, NULL, |
- L"testname", L"testpass", false, 0}, |
- }; |
- for (unsigned int i = 0; i < arraysize(owned_password_data); ++i) { |
- std::unique_ptr<PasswordForm> form = |
- CreatePasswordFormFromDataForTesting(owned_password_data[i]); |
- owned_keychain_adapter.AddPassword(*form); |
- } |
- |
- std::vector<std::unique_ptr<PasswordForm>> all_passwords = |
- keychain_adapter.GetAllPasswordFormPasswords(); |
- EXPECT_EQ(9 + arraysize(owned_password_data), all_passwords.size()); |
- |
- std::vector<std::unique_ptr<PasswordForm>> owned_passwords = |
- owned_keychain_adapter.GetAllPasswordFormPasswords(); |
- EXPECT_EQ(arraysize(owned_password_data), owned_passwords.size()); |
-} |
- |
-#pragma mark - |
- |
-class PasswordStoreMacTest : public testing::Test { |
- public: |
- PasswordStoreMacTest() = default; |
- |
- void SetUp() override { |
- ASSERT_TRUE(db_dir_.CreateUniqueTempDir()); |
- histogram_tester_.reset(new base::HistogramTester); |
- |
- // Ensure that LoginDatabase will use the mock keychain if it needs to |
- // encrypt/decrypt a password. |
- OSCryptMocker::SetUpWithSingleton(); |
- login_db_.reset( |
- new password_manager::LoginDatabase(test_login_db_file_path())); |
- thread_.reset(new base::Thread("Chrome_PasswordStore_Thread")); |
- ASSERT_TRUE(thread_->Start()); |
- ASSERT_TRUE(thread_->task_runner()->PostTask( |
- FROM_HERE, base::Bind(&PasswordStoreMacTest::InitLoginDatabase, |
- base::Unretained(login_db_.get())))); |
- CreateAndInitPasswordStore(login_db_.get()); |
- // Make sure deferred initialization is performed before some tests start |
- // accessing the |login_db| directly. |
- FinishAsyncProcessing(); |
- } |
- |
- void TearDown() override { |
- ClosePasswordStore(); |
- thread_.reset(); |
- login_db_.reset(); |
- // Whatever a test did, PasswordStoreMac stores only empty password values |
- // in LoginDatabase. The empty valus do not require encryption and therefore |
- // OSCrypt shouldn't call the Keychain. The histogram doesn't cover the |
- // internet passwords. |
- if (histogram_tester_) { |
- histogram_tester_->ExpectTotalCount("OSX.Keychain.Access", 0); |
- } |
- OSCryptMocker::TearDown(); |
- } |
- |
- static void InitLoginDatabase(password_manager::LoginDatabase* login_db) { |
- ASSERT_TRUE(login_db->Init()); |
- } |
- |
- void CreateAndInitPasswordStore(password_manager::LoginDatabase* login_db) { |
- store_ = new PasswordStoreMac( |
- base::ThreadTaskRunnerHandle::Get(), nullptr, |
- base::WrapUnique<AppleKeychain>(new MockAppleKeychain)); |
- ASSERT_TRUE(thread_->task_runner()->PostTask( |
- FROM_HERE, base::Bind(&PasswordStoreMac::InitWithTaskRunner, store_, |
- thread_->task_runner()))); |
- |
- ASSERT_TRUE(thread_->task_runner()->PostTask( |
- FROM_HERE, base::Bind(&PasswordStoreMac::set_login_metadata_db, store_, |
- base::Unretained(login_db)))); |
- } |
- |
- void ClosePasswordStore() { |
- if (!store_) |
- return; |
- |
- store_->ShutdownOnUIThread(); |
- store_ = nullptr; |
- } |
- |
- // Verifies that the given |form| can be properly stored so that it can be |
- // retrieved by FillMatchingLogins() and GetAutofillableLogins(), and then it |
- // can be properly removed. |
- void VerifyCredentialLifecycle(const PasswordForm& form) { |
- // Run everything twice to make sure no garbage is left behind that would |
- // prevent storing the form a second time. |
- for (size_t iteration = 0; iteration < 2; ++iteration) { |
- SCOPED_TRACE(testing::Message("Iteration: ") << iteration); |
- |
- MockPasswordStoreConsumer mock_consumer; |
- EXPECT_CALL(mock_consumer, OnGetPasswordStoreResultsConstRef(IsEmpty())) |
- .WillOnce(QuitUIMessageLoop()); |
- store()->GetAutofillableLogins(&mock_consumer); |
- base::RunLoop().Run(); |
- ::testing::Mock::VerifyAndClearExpectations(&mock_consumer); |
- |
- store()->AddLogin(form); |
- FinishAsyncProcessing(); |
- |
- PasswordForm returned_form; |
- EXPECT_CALL(mock_consumer, OnGetPasswordStoreResultsConstRef(SizeIs(1u))) |
- .WillOnce( |
- DoAll(SaveACopyOfFirstForm(&returned_form), QuitUIMessageLoop())); |
- |
- // The query operations will also do some housekeeping: they will remove |
- // dangling credentials in the LoginDatabase without a matching Keychain |
- // item when one is expected. If the logic that stores the Keychain item |
- // is incorrect, this will wipe the newly added form before the second |
- // query. |
- store()->GetAutofillableLogins(&mock_consumer); |
- base::RunLoop().Run(); |
- ::testing::Mock::VerifyAndClearExpectations(&mock_consumer); |
- EXPECT_EQ(form, returned_form); |
- |
- PasswordStore::FormDigest query_form(form); |
- EXPECT_CALL(mock_consumer, OnGetPasswordStoreResultsConstRef(SizeIs(1u))) |
- .WillOnce( |
- DoAll(SaveACopyOfFirstForm(&returned_form), QuitUIMessageLoop())); |
- store()->GetLogins(query_form, &mock_consumer); |
- base::RunLoop().Run(); |
- ::testing::Mock::VerifyAndClearExpectations(&mock_consumer); |
- EXPECT_EQ(form, returned_form); |
- |
- store()->RemoveLogin(form); |
- } |
- } |
- |
- base::FilePath test_login_db_file_path() const { |
- return db_dir_.GetPath().Append(FILE_PATH_LITERAL("login.db")); |
- } |
- |
- password_manager::LoginDatabase* login_db() const { |
- return store_->login_metadata_db(); |
- } |
- |
- MockAppleKeychain* keychain() { |
- return static_cast<MockAppleKeychain*>(store_->keychain()); |
- } |
- |
- void FinishAsyncProcessing() { |
- scoped_refptr<content::MessageLoopRunner> runner = |
- new content::MessageLoopRunner; |
- ASSERT_TRUE(thread_->task_runner()->PostTaskAndReply( |
- FROM_HERE, base::Bind(&Noop), runner->QuitClosure())); |
- runner->Run(); |
- } |
- |
- PasswordStoreMac* store() { return store_.get(); } |
- |
- protected: |
- content::TestBrowserThreadBundle test_browser_thread_bundle_; |
- // Thread that the synchronous methods are run on. |
- std::unique_ptr<base::Thread> thread_; |
- |
- base::ScopedTempDir db_dir_; |
- std::unique_ptr<password_manager::LoginDatabase> login_db_; |
- scoped_refptr<PasswordStoreMac> store_; |
- std::unique_ptr<base::HistogramTester> histogram_tester_; |
-}; |
- |
-TEST_F(PasswordStoreMacTest, TestStoreUpdate) { |
- // Insert a password into both the database and the keychain. |
- // This is done manually, rather than through store_->AddLogin, because the |
- // Mock Keychain isn't smart enough to be able to support update generically, |
- // so some.domain.com triggers special handling to test it that make inserting |
- // fail. |
- PasswordFormData joint_data = {PasswordForm::SCHEME_HTML, |
- "http://some.domain.com/", |
- "http://some.domain.com/insecure.html", |
- "login.cgi", |
- L"username", |
- L"password", |
- L"submit", |
- L"joe_user", |
- L"sekrit", |
- true, |
- 1}; |
- std::unique_ptr<PasswordForm> joint_form = |
- CreatePasswordFormFromDataForTesting(joint_data); |
- EXPECT_EQ(AddChangeForForm(*joint_form), login_db()->AddLogin(*joint_form)); |
- MockAppleKeychain::KeychainTestData joint_keychain_data = { |
- kSecAuthenticationTypeHTMLForm, |
- "some.domain.com", |
- kSecProtocolTypeHTTP, |
- "/insecure.html", |
- 0, |
- NULL, |
- "20020601171500Z", |
- "joe_user", |
- "sekrit", |
- false}; |
- keychain()->AddTestItem(joint_keychain_data); |
- |
- // Insert a password into the keychain only. |
- MockAppleKeychain::KeychainTestData keychain_only_data = { |
- kSecAuthenticationTypeHTMLForm, |
- "keychain.only.com", |
- kSecProtocolTypeHTTP, |
- NULL, |
- 0, |
- NULL, |
- "20020601171500Z", |
- "keychain", |
- "only", |
- false}; |
- keychain()->AddTestItem(keychain_only_data); |
- |
- struct UpdateData { |
- PasswordFormData form_data; |
- const char* password; // NULL indicates no entry should be present. |
- }; |
- |
- // Make a series of update calls. |
- UpdateData updates[] = { |
- // Update the keychain+db passwords (the normal password update case). |
- { |
- {PasswordForm::SCHEME_HTML, "http://some.domain.com/", |
- "http://some.domain.com/insecure.html", "login.cgi", L"username", |
- L"password", L"submit", L"joe_user", L"53krit", true, 2}, |
- "53krit", |
- }, |
- // Update the keychain-only password; this simulates the initial use of a |
- // password stored by another browsers. |
- { |
- {PasswordForm::SCHEME_HTML, "http://keychain.only.com/", |
- "http://keychain.only.com/login.html", "login.cgi", L"username", |
- L"password", L"submit", L"keychain", L"only", true, 2}, |
- "only", |
- }, |
- // Update a password that doesn't exist in either location. This tests the |
- // case where a form is filled, then the stored login is removed, then the |
- // form is submitted. |
- { |
- {PasswordForm::SCHEME_HTML, "http://different.com/", |
- "http://different.com/index.html", "login.cgi", L"username", |
- L"password", L"submit", L"abc", L"123", true, 2}, |
- NULL, |
- }, |
- }; |
- for (unsigned int i = 0; i < arraysize(updates); ++i) { |
- std::unique_ptr<PasswordForm> form = |
- CreatePasswordFormFromDataForTesting(updates[i].form_data); |
- store_->UpdateLogin(*form); |
- } |
- |
- FinishAsyncProcessing(); |
- |
- MacKeychainPasswordFormAdapter keychain_adapter(keychain()); |
- for (unsigned int i = 0; i < arraysize(updates); ++i) { |
- SCOPED_TRACE(testing::Message("iteration ") << i); |
- std::unique_ptr<PasswordForm> query_form = |
- CreatePasswordFormFromDataForTesting(updates[i].form_data); |
- |
- std::vector<std::unique_ptr<PasswordForm>> matching_items = |
- keychain_adapter.PasswordsFillingForm(query_form->signon_realm, |
- query_form->scheme); |
- if (updates[i].password) { |
- EXPECT_GT(matching_items.size(), 0U); |
- if (matching_items.size() >= 1) |
- EXPECT_EQ(ASCIIToUTF16(updates[i].password), |
- matching_items[0]->password_value); |
- } else { |
- EXPECT_EQ(0U, matching_items.size()); |
- } |
- |
- std::vector<std::unique_ptr<PasswordForm>> matching_db_items; |
- EXPECT_TRUE(login_db()->GetLogins(PasswordStore::FormDigest(*query_form), |
- &matching_db_items)); |
- EXPECT_EQ(updates[i].password ? 1U : 0U, matching_db_items.size()); |
- } |
-} |
- |
-TEST_F(PasswordStoreMacTest, TestDBKeychainAssociation) { |
- // Tests that association between the keychain and login database parts of a |
- // password added by fuzzy (PSL) matching works. |
- // 1. Add a password for www.facebook.com |
- // 2. Get a password for m.facebook.com. This fuzzy matches and returns the |
- // www.facebook.com password. |
- // 3. Add the returned password for m.facebook.com. |
- // 4. Remove both passwords. |
- // -> check: that both are gone from the login DB and the keychain |
- // This test should in particular ensure that we don't keep passwords in the |
- // keychain just before we think we still have other (fuzzy-)matching entries |
- // for them in the login database. (For example, here if we deleted the |
- // www.facebook.com password from the login database, we should not be blocked |
- // from deleting it from the keystore just becaus the m.facebook.com password |
- // fuzzy-matches the www.facebook.com one.) |
- |
- // 1. Add a password for www.facebook.com |
- PasswordFormData www_form_data = {PasswordForm::SCHEME_HTML, |
- "http://www.facebook.com/", |
- "http://www.facebook.com/index.html", |
- "login", |
- L"username", |
- L"password", |
- L"submit", |
- L"joe_user", |
- L"sekrit", |
- true, |
- 1}; |
- std::unique_ptr<PasswordForm> www_form = |
- CreatePasswordFormFromDataForTesting(www_form_data); |
- EXPECT_EQ(AddChangeForForm(*www_form), login_db()->AddLogin(*www_form)); |
- MacKeychainPasswordFormAdapter owned_keychain_adapter(keychain()); |
- owned_keychain_adapter.SetFindsOnlyOwnedItems(true); |
- owned_keychain_adapter.AddPassword(*www_form); |
- |
- // 2. Get a password for m.facebook.com. |
- PasswordForm m_form(*www_form); |
- m_form.signon_realm = "http://m.facebook.com"; |
- m_form.origin = GURL("http://m.facebook.com/index.html"); |
- |
- MockPasswordStoreConsumer consumer; |
- store_->GetLogins(PasswordStore::FormDigest(m_form), &consumer); |
- PasswordForm returned_form; |
- EXPECT_CALL(consumer, OnGetPasswordStoreResultsConstRef(SizeIs(1u))) |
- .WillOnce( |
- DoAll(SaveACopyOfFirstForm(&returned_form), QuitUIMessageLoop())); |
- base::RunLoop().Run(); |
- |
- // 3. Add the returned password for m.facebook.com. |
- returned_form.signon_realm = "http://m.facebook.com"; |
- returned_form.origin = GURL("http://m.facebook.com/index.html"); |
- EXPECT_EQ(AddChangeForForm(returned_form), |
- login_db()->AddLogin(returned_form)); |
- owned_keychain_adapter.AddPassword(m_form); |
- |
- // 4. Remove both passwords. |
- store_->RemoveLogin(*www_form); |
- store_->RemoveLogin(m_form); |
- FinishAsyncProcessing(); |
- |
- // No trace of www.facebook.com. |
- std::vector<std::unique_ptr<PasswordForm>> matching_items = |
- owned_keychain_adapter.PasswordsFillingForm(www_form->signon_realm, |
- www_form->scheme); |
- EXPECT_EQ(0u, matching_items.size()); |
- |
- std::vector<std::unique_ptr<PasswordForm>> matching_db_items; |
- EXPECT_TRUE(login_db()->GetLogins(PasswordStore::FormDigest(*www_form), |
- &matching_db_items)); |
- EXPECT_EQ(0u, matching_db_items.size()); |
- |
- // No trace of m.facebook.com. |
- matching_items = owned_keychain_adapter.PasswordsFillingForm( |
- m_form.signon_realm, m_form.scheme); |
- EXPECT_EQ(0u, matching_items.size()); |
- |
- EXPECT_TRUE(login_db()->GetLogins(PasswordStore::FormDigest(m_form), |
- &matching_db_items)); |
- EXPECT_EQ(0u, matching_db_items.size()); |
-} |
- |
-namespace { |
- |
-class PasswordsChangeObserver |
- : public password_manager::PasswordStore::Observer { |
- public: |
- explicit PasswordsChangeObserver(PasswordStoreMac* store) : observer_(this) { |
- observer_.Add(store); |
- } |
- |
- void WaitAndVerify(PasswordStoreMacTest* test) { |
- test->FinishAsyncProcessing(); |
- ::testing::Mock::VerifyAndClearExpectations(this); |
- } |
- |
- // password_manager::PasswordStore::Observer: |
- MOCK_METHOD1(OnLoginsChanged, |
- void(const password_manager::PasswordStoreChangeList& changes)); |
- |
- private: |
- ScopedObserver<password_manager::PasswordStore, PasswordsChangeObserver> |
- observer_; |
-}; |
- |
-password_manager::PasswordStoreChangeList GetAddChangeList( |
- const PasswordForm& form) { |
- password_manager::PasswordStoreChange change( |
- password_manager::PasswordStoreChange::ADD, form); |
- return password_manager::PasswordStoreChangeList(1, change); |
-} |
- |
-// Tests RemoveLoginsCreatedBetween or RemoveLoginsSyncedBetween depending on |
-// |check_created|. |
-void CheckRemoveLoginsBetween(PasswordStoreMacTest* test, bool check_created) { |
- PasswordFormData www_form_data_facebook = { |
- PasswordForm::SCHEME_HTML, |
- "http://www.facebook.com/", |
- "http://www.facebook.com/index.html", |
- "login", |
- L"submit", |
- L"username", |
- L"password", |
- L"joe_user", |
- L"sekrit", |
- true, |
- 0}; |
- // The old form doesn't have elements names. |
- PasswordFormData www_form_data_facebook_old = { |
- PasswordForm::SCHEME_HTML, |
- "http://www.facebook.com/", |
- "http://www.facebook.com/index.html", |
- "login", |
- L"", |
- L"", |
- L"", |
- L"joe_user", |
- L"oldsekrit", |
- true, |
- 0}; |
- PasswordFormData www_form_data_other = {PasswordForm::SCHEME_HTML, |
- "http://different.com/", |
- "http://different.com/index.html", |
- "login", |
- L"submit", |
- L"username", |
- L"password", |
- L"different_joe_user", |
- L"sekrit", |
- true, |
- 0}; |
- std::unique_ptr<PasswordForm> form_facebook = |
- CreatePasswordFormFromDataForTesting(www_form_data_facebook); |
- std::unique_ptr<PasswordForm> form_facebook_old = |
- CreatePasswordFormFromDataForTesting(www_form_data_facebook_old); |
- std::unique_ptr<PasswordForm> form_other = |
- CreatePasswordFormFromDataForTesting(www_form_data_other); |
- base::Time now = base::Time::Now(); |
- base::Time next_day = now + base::TimeDelta::FromDays(1); |
- if (check_created) { |
- form_facebook_old->date_created = now; |
- form_facebook->date_created = next_day; |
- form_other->date_created = next_day; |
- } else { |
- form_facebook_old->date_synced = now; |
- form_facebook->date_synced = next_day; |
- form_other->date_synced = next_day; |
- } |
- |
- PasswordsChangeObserver observer(test->store()); |
- test->store()->AddLogin(*form_facebook_old); |
- test->store()->AddLogin(*form_facebook); |
- test->store()->AddLogin(*form_other); |
- EXPECT_CALL(observer, OnLoginsChanged(GetAddChangeList(*form_facebook_old))); |
- EXPECT_CALL(observer, OnLoginsChanged(GetAddChangeList(*form_facebook))); |
- EXPECT_CALL(observer, OnLoginsChanged(GetAddChangeList(*form_other))); |
- observer.WaitAndVerify(test); |
- |
- // Check the keychain content. |
- MacKeychainPasswordFormAdapter owned_keychain_adapter(test->keychain()); |
- owned_keychain_adapter.SetFindsOnlyOwnedItems(false); |
- std::vector<std::unique_ptr<PasswordForm>> matching_items( |
- owned_keychain_adapter.PasswordsFillingForm(form_facebook->signon_realm, |
- form_facebook->scheme)); |
- EXPECT_EQ(1u, matching_items.size()); |
- matching_items = owned_keychain_adapter.PasswordsFillingForm( |
- form_other->signon_realm, form_other->scheme); |
- EXPECT_EQ(1u, matching_items.size()); |
- |
- // Remove facebook. |
- if (check_created) { |
- test->store()->RemoveLoginsCreatedBetween(base::Time(), next_day, |
- base::Closure()); |
- } else { |
- test->store()->RemoveLoginsSyncedBetween(base::Time(), next_day); |
- } |
- password_manager::PasswordStoreChangeList list; |
- form_facebook_old->password_value.clear(); |
- form_facebook->password_value.clear(); |
- list.push_back(password_manager::PasswordStoreChange( |
- password_manager::PasswordStoreChange::REMOVE, *form_facebook_old)); |
- list.push_back(password_manager::PasswordStoreChange( |
- password_manager::PasswordStoreChange::REMOVE, *form_facebook)); |
- EXPECT_CALL(observer, OnLoginsChanged(list)); |
- list.clear(); |
- observer.WaitAndVerify(test); |
- |
- matching_items = owned_keychain_adapter.PasswordsFillingForm( |
- form_facebook->signon_realm, form_facebook->scheme); |
- EXPECT_EQ(0u, matching_items.size()); |
- matching_items = owned_keychain_adapter.PasswordsFillingForm( |
- form_other->signon_realm, form_other->scheme); |
- EXPECT_EQ(1u, matching_items.size()); |
- |
- // Remove form_other. |
- if (check_created) { |
- test->store()->RemoveLoginsCreatedBetween(next_day, base::Time(), |
- base::Closure()); |
- } else { |
- test->store()->RemoveLoginsSyncedBetween(next_day, base::Time()); |
- } |
- form_other->password_value.clear(); |
- list.push_back(password_manager::PasswordStoreChange( |
- password_manager::PasswordStoreChange::REMOVE, *form_other)); |
- EXPECT_CALL(observer, OnLoginsChanged(list)); |
- observer.WaitAndVerify(test); |
- matching_items = owned_keychain_adapter.PasswordsFillingForm( |
- form_other->signon_realm, form_other->scheme); |
- EXPECT_EQ(0u, matching_items.size()); |
-} |
- |
-} // namespace |
- |
-TEST_F(PasswordStoreMacTest, TestRemoveLoginsCreatedBetween) { |
- CheckRemoveLoginsBetween(this, true); |
-} |
- |
-TEST_F(PasswordStoreMacTest, TestRemoveLoginsSyncedBetween) { |
- CheckRemoveLoginsBetween(this, false); |
-} |
- |
-TEST_F(PasswordStoreMacTest, TestDisableAutoSignInForOrigins) { |
- PasswordFormData www_form_data_facebook = { |
- PasswordForm::SCHEME_HTML, |
- "http://www.facebook.com/", |
- "http://www.facebook.com/index.html", |
- "login", |
- L"submit", |
- L"username", |
- L"password", |
- L"joe_user", |
- L"sekrit", |
- true, |
- 0}; |
- std::unique_ptr<PasswordForm> form_facebook = |
- CreatePasswordFormFromDataForTesting(www_form_data_facebook); |
- form_facebook->skip_zero_click = false; |
- |
- PasswordFormData www_form_data_google = { |
- PasswordForm::SCHEME_HTML, |
- "http://www.google.com/", |
- "http://www.google.com/foo/bar/index.html", |
- "login", |
- L"submit", |
- L"username", |
- L"password", |
- L"joe_user", |
- L"sekrit", |
- true, |
- 0}; |
- std::unique_ptr<PasswordForm> form_google = |
- CreatePasswordFormFromDataForTesting(www_form_data_google); |
- form_google->skip_zero_click = false; |
- |
- // Add the zero-clickable forms to the database. |
- PasswordsChangeObserver observer(store()); |
- store()->AddLogin(*form_facebook); |
- store()->AddLogin(*form_google); |
- EXPECT_CALL(observer, OnLoginsChanged(GetAddChangeList(*form_facebook))); |
- EXPECT_CALL(observer, OnLoginsChanged(GetAddChangeList(*form_google))); |
- observer.WaitAndVerify(this); |
- |
- std::vector<std::unique_ptr<PasswordForm>> forms; |
- EXPECT_TRUE(login_db()->GetAutoSignInLogins(&forms)); |
- EXPECT_EQ(2u, forms.size()); |
- EXPECT_FALSE(forms[0]->skip_zero_click); |
- EXPECT_FALSE(forms[1]->skip_zero_click); |
- |
- store()->DisableAutoSignInForOrigins( |
- base::Bind(static_cast<bool (*)(const GURL&, const GURL&)>(operator==), |
- form_google->origin), |
- base::Closure()); |
- FinishAsyncProcessing(); |
- |
- EXPECT_TRUE(login_db()->GetAutoSignInLogins(&forms)); |
- EXPECT_EQ(1u, forms.size()); |
- EXPECT_EQ(form_facebook->origin, forms[0]->origin); |
-} |
- |
-TEST_F(PasswordStoreMacTest, TestRemoveLoginsMultiProfile) { |
- // Make sure that RemoveLoginsCreatedBetween does affect only the correct |
- // profile. |
- |
- // Add a third-party password. |
- MockAppleKeychain::KeychainTestData keychain_data = { |
- kSecAuthenticationTypeHTMLForm, |
- "some.domain.com", |
- kSecProtocolTypeHTTP, |
- "/insecure.html", |
- 0, |
- NULL, |
- "20020601171500Z", |
- "joe_user", |
- "sekrit", |
- false}; |
- keychain()->AddTestItem(keychain_data); |
- |
- // Add a password through the adapter. It has the "Chrome" creator tag. |
- // However, it's not referenced by the password database. |
- MacKeychainPasswordFormAdapter owned_keychain_adapter(keychain()); |
- owned_keychain_adapter.SetFindsOnlyOwnedItems(true); |
- PasswordFormData www_form_data1 = {PasswordForm::SCHEME_HTML, |
- "http://www.facebook.com/", |
- "http://www.facebook.com/index.html", |
- "login", |
- L"username", |
- L"password", |
- L"submit", |
- L"joe_user", |
- L"sekrit", |
- true, |
- 1}; |
- std::unique_ptr<PasswordForm> www_form = |
- CreatePasswordFormFromDataForTesting(www_form_data1); |
- EXPECT_TRUE(owned_keychain_adapter.AddPassword(*www_form)); |
- |
- // Add a password from the current profile. |
- PasswordFormData www_form_data2 = {PasswordForm::SCHEME_HTML, |
- "http://www.facebook.com/", |
- "http://www.facebook.com/index.html", |
- "login", |
- L"username", |
- L"password", |
- L"submit", |
- L"not_joe_user", |
- L"12345", |
- true, |
- 1}; |
- www_form = CreatePasswordFormFromDataForTesting(www_form_data2); |
- store_->AddLogin(*www_form); |
- FinishAsyncProcessing(); |
- |
- std::vector<std::unique_ptr<PasswordForm>> matching_items; |
- EXPECT_TRUE(login_db()->GetLogins(PasswordStore::FormDigest(*www_form), |
- &matching_items)); |
- EXPECT_EQ(1u, matching_items.size()); |
- |
- store_->RemoveLoginsCreatedBetween(base::Time(), base::Time(), |
- base::Closure()); |
- FinishAsyncProcessing(); |
- |
- // Check the second facebook form is gone. |
- EXPECT_TRUE(login_db()->GetLogins(PasswordStore::FormDigest(*www_form), |
- &matching_items)); |
- EXPECT_EQ(0u, matching_items.size()); |
- |
- // Check the first facebook form is still there. |
- std::vector<std::unique_ptr<PasswordForm>> matching_keychain_items; |
- matching_keychain_items = owned_keychain_adapter.PasswordsFillingForm( |
- www_form->signon_realm, www_form->scheme); |
- ASSERT_EQ(1u, matching_keychain_items.size()); |
- EXPECT_EQ(ASCIIToUTF16("joe_user"), |
- matching_keychain_items[0]->username_value); |
- |
- // Check the third-party password is still there. |
- owned_keychain_adapter.SetFindsOnlyOwnedItems(false); |
- matching_keychain_items = owned_keychain_adapter.PasswordsFillingForm( |
- "http://some.domain.com/insecure.html", PasswordForm::SCHEME_HTML); |
- ASSERT_EQ(1u, matching_keychain_items.size()); |
-} |
- |
-// Add a facebook form to the store but not to the keychain. The form is to be |
-// implicitly deleted. However, the observers shouldn't get notified about |
-// deletion of non-existent forms like m.facebook.com. |
-TEST_F(PasswordStoreMacTest, SilentlyRemoveOrphanedForm) { |
- testing::StrictMock<password_manager::MockPasswordStoreObserver> |
- mock_observer; |
- store()->AddObserver(&mock_observer); |
- |
- // 1. Add a password for www.facebook.com to the LoginDatabase. |
- PasswordFormData www_form_data = {PasswordForm::SCHEME_HTML, |
- "http://www.facebook.com/", |
- "http://www.facebook.com/index.html", |
- "login", |
- L"username", |
- L"password", |
- L"submit", |
- L"joe_user", |
- L"", |
- true, |
- 1}; |
- std::unique_ptr<PasswordForm> www_form( |
- CreatePasswordFormFromDataForTesting(www_form_data)); |
- EXPECT_EQ(AddChangeForForm(*www_form), login_db()->AddLogin(*www_form)); |
- |
- // 2. Get a PSL-matched password for m.facebook.com. The observer isn't |
- // notified because the form isn't in the database. |
- PasswordForm m_form(*www_form); |
- m_form.signon_realm = "http://m.facebook.com"; |
- m_form.origin = GURL("http://m.facebook.com/index.html"); |
- |
- MockPasswordStoreConsumer consumer; |
- ON_CALL(consumer, OnGetPasswordStoreResultsConstRef(_)) |
- .WillByDefault(QuitUIMessageLoop()); |
- EXPECT_CALL(mock_observer, OnLoginsChanged(_)).Times(0); |
- // The PSL-matched form isn't returned because there is no actual password in |
- // the keychain. |
- EXPECT_CALL(consumer, OnGetPasswordStoreResultsConstRef(IsEmpty())); |
- store_->GetLogins(PasswordStore::FormDigest(m_form), &consumer); |
- base::RunLoop().Run(); |
- std::vector<std::unique_ptr<PasswordForm>> all_forms; |
- EXPECT_TRUE(login_db()->GetAutofillableLogins(&all_forms)); |
- EXPECT_EQ(1u, all_forms.size()); |
- ::testing::Mock::VerifyAndClearExpectations(&mock_observer); |
- |
- // 3. Get a password for www.facebook.com. The form is implicitly removed and |
- // the observer is notified. |
- password_manager::PasswordStoreChangeList list; |
- list.push_back(password_manager::PasswordStoreChange( |
- password_manager::PasswordStoreChange::REMOVE, *www_form)); |
- EXPECT_CALL(mock_observer, OnLoginsChanged(list)); |
- EXPECT_CALL(consumer, OnGetPasswordStoreResultsConstRef(IsEmpty())); |
- store_->GetLogins(PasswordStore::FormDigest(*www_form), &consumer); |
- base::RunLoop().Run(); |
- EXPECT_TRUE(login_db()->GetAutofillableLogins(&all_forms)); |
- EXPECT_EQ(0u, all_forms.size()); |
-} |
- |
-// Verify that Android app passwords can be stored, retrieved, and deleted. |
-// Regression test for http://crbug.com/455551 |
-TEST_F(PasswordStoreMacTest, StoringAndRetrievingAndroidCredentials) { |
- PasswordForm form; |
- form.signon_realm = "android://7x7IDboo8u9YKraUsbmVkuf1@net.rateflix.app/"; |
- form.username_value = base::UTF8ToUTF16("randomusername"); |
- form.password_value = base::UTF8ToUTF16("password"); |
- |
- VerifyCredentialLifecycle(form); |
-} |
- |
-// Verify that federated credentials can be stored, retrieved and deleted. |
-TEST_F(PasswordStoreMacTest, StoringAndRetrievingFederatedCredentials) { |
- PasswordForm form; |
- form.signon_realm = "android://7x7IDboo8u9YKraUsbmVkuf1@net.rateflix.app/"; |
- form.federation_origin = |
- url::Origin(GURL(password_manager::kTestingFederationUrlSpec)); |
- form.username_value = base::UTF8ToUTF16("randomusername"); |
- form.password_value = base::UTF8ToUTF16(""); // No password. |
- |
- VerifyCredentialLifecycle(form); |
-} |
- |
-void CheckMigrationResult(PasswordStoreMac::MigrationResult expected_result, |
- PasswordStoreMac::MigrationResult result) { |
- EXPECT_EQ(expected_result, result); |
- QuitUIMessageLoop(); |
-} |
- |
-// Import the passwords from the Keychain to LoginDatabase. |
-TEST_F(PasswordStoreMacTest, ImportFromKeychain) { |
- PasswordForm form1; |
- form1.origin = GURL("http://accounts.google.com/LoginAuth"); |
- form1.signon_realm = "http://accounts.google.com/"; |
- form1.username_value = ASCIIToUTF16("my_username"); |
- form1.password_value = ASCIIToUTF16("my_password"); |
- |
- PasswordForm form2; |
- form2.origin = GURL("http://facebook.com/Login"); |
- form2.signon_realm = "http://facebook.com/"; |
- form2.username_value = ASCIIToUTF16("my_username"); |
- form2.password_value = ASCIIToUTF16("my_password"); |
- |
- PasswordForm blacklisted_form; |
- blacklisted_form.origin = GURL("http://badsite.com/Login"); |
- blacklisted_form.signon_realm = "http://badsite.com/"; |
- blacklisted_form.blacklisted_by_user = true; |
- |
- store()->AddLogin(form1); |
- store()->AddLogin(form2); |
- store()->AddLogin(blacklisted_form); |
- FinishAsyncProcessing(); |
- |
- ASSERT_TRUE(base::PostTaskAndReplyWithResult( |
- thread_->task_runner().get(), FROM_HERE, |
- base::Bind(&PasswordStoreMac::ImportFromKeychain, login_db(), keychain()), |
- base::Bind(&CheckMigrationResult, PasswordStoreMac::MIGRATION_OK))); |
- FinishAsyncProcessing(); |
- |
- // The password should be stored in the database by now. |
- std::vector<std::unique_ptr<PasswordForm>> matching_items; |
- EXPECT_TRUE( |
- login_db()->GetLogins(PasswordStore::FormDigest(form1), &matching_items)); |
- ASSERT_EQ(1u, matching_items.size()); |
- EXPECT_EQ(form1, *matching_items[0]); |
- |
- EXPECT_TRUE( |
- login_db()->GetLogins(PasswordStore::FormDigest(form2), &matching_items)); |
- ASSERT_EQ(1u, matching_items.size()); |
- EXPECT_EQ(form2, *matching_items[0]); |
- |
- EXPECT_TRUE(login_db()->GetLogins(PasswordStore::FormDigest(blacklisted_form), |
- &matching_items)); |
- ASSERT_EQ(1u, matching_items.size()); |
- EXPECT_EQ(blacklisted_form, *matching_items[0]); |
- |
- // The passwords are encrypted using a key from the Keychain. |
- EXPECT_TRUE( |
- histogram_tester_->GetHistogramSamplesSinceCreation("OSX.Keychain.Access") |
- ->TotalCount()); |
- histogram_tester_.reset(); |
-} |
- |
-// Import a federated credential while the Keychain is locked. |
-TEST_F(PasswordStoreMacTest, ImportFederatedFromLockedKeychain) { |
- keychain()->set_locked(true); |
- PasswordForm form1; |
- form1.origin = GURL("http://example.com/Login"); |
- form1.signon_realm = "http://example.com/"; |
- form1.username_value = ASCIIToUTF16("my_username"); |
- form1.federation_origin = url::Origin(GURL("https://accounts.google.com/")); |
- |
- store()->AddLogin(form1); |
- FinishAsyncProcessing(); |
- ASSERT_TRUE(base::PostTaskAndReplyWithResult( |
- thread_->task_runner().get(), FROM_HERE, |
- base::Bind(&PasswordStoreMac::ImportFromKeychain, login_db(), keychain()), |
- base::Bind(&CheckMigrationResult, PasswordStoreMac::MIGRATION_OK))); |
- FinishAsyncProcessing(); |
- |
- std::vector<std::unique_ptr<PasswordForm>> matching_items; |
- EXPECT_TRUE( |
- login_db()->GetLogins(PasswordStore::FormDigest(form1), &matching_items)); |
- ASSERT_EQ(1u, matching_items.size()); |
- EXPECT_EQ(form1, *matching_items[0]); |
-} |
- |
-// Try to import while the Keychain is locked but the encryption key had been |
-// read earlier. |
-TEST_F(PasswordStoreMacTest, ImportFromLockedKeychainError) { |
- PasswordForm form1; |
- form1.origin = GURL("http://accounts.google.com/LoginAuth"); |
- form1.signon_realm = "http://accounts.google.com/"; |
- form1.username_value = ASCIIToUTF16("my_username"); |
- form1.password_value = ASCIIToUTF16("my_password"); |
- store()->AddLogin(form1); |
- FinishAsyncProcessing(); |
- |
- // Add a second keychain item matching the Database entry. |
- PasswordForm form2 = form1; |
- form2.origin = GURL("http://accounts.google.com/Login"); |
- form2.password_value = ASCIIToUTF16("1234"); |
- MacKeychainPasswordFormAdapter adapter(keychain()); |
- EXPECT_TRUE(adapter.AddPassword(form2)); |
- |
- keychain()->set_locked(true); |
- ASSERT_TRUE(base::PostTaskAndReplyWithResult( |
- thread_->task_runner().get(), FROM_HERE, |
- base::Bind(&PasswordStoreMac::ImportFromKeychain, login_db(), keychain()), |
- base::Bind(&CheckMigrationResult, PasswordStoreMac::MIGRATION_PARTIAL))); |
- FinishAsyncProcessing(); |
- |
- std::vector<std::unique_ptr<PasswordForm>> matching_items; |
- EXPECT_TRUE( |
- login_db()->GetLogins(PasswordStore::FormDigest(form1), &matching_items)); |
- EXPECT_EQ(0u, matching_items.size()); |
- |
- histogram_tester_->ExpectUniqueSample( |
- "PasswordManager.KeychainMigration.NumPasswordsOnFailure", 1, 1); |
- histogram_tester_->ExpectUniqueSample( |
- "PasswordManager.KeychainMigration.NumFailedPasswords", 1, 1); |
- // Don't test the encryption key access. |
- histogram_tester_.reset(); |
-} |
- |
-// Delete the Chrome-owned password from the Keychain. |
-TEST_F(PasswordStoreMacTest, CleanUpKeychain) { |
- MockAppleKeychain::KeychainTestData data1 = { kSecAuthenticationTypeHTMLForm, |
- "some.domain.com", kSecProtocolTypeHTTP, NULL, 0, NULL, "20020601171500Z", |
- "joe_user", "sekrit", false}; |
- keychain()->AddTestItem(data1); |
- |
- MacKeychainPasswordFormAdapter keychain_adapter(keychain()); |
- PasswordFormData data2 = { PasswordForm::SCHEME_HTML, "http://web.site.com/", |
- "http://web.site.com/path/to/page.html", NULL, NULL, NULL, NULL, |
- L"anonymous", L"knock-knock", false, 0 }; |
- keychain_adapter.AddPassword(*CreatePasswordFormFromDataForTesting(data2)); |
- std::vector<std::unique_ptr<PasswordForm>> passwords = |
- keychain_adapter.GetAllPasswordFormPasswords(); |
- EXPECT_EQ(2u, passwords.size()); |
- |
- // Delete everyhting but only the Chrome-owned item should be affected. |
- PasswordStoreMac::CleanUpKeychain(keychain(), passwords); |
- passwords = keychain_adapter.GetAllPasswordFormPasswords(); |
- ASSERT_EQ(1u, passwords.size()); |
- EXPECT_EQ("http://some.domain.com/", passwords[0]->signon_realm); |
- EXPECT_EQ(ASCIIToUTF16("sekrit"), passwords[0]->password_value); |
-} |