| 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);
|
| -}
|
|
|