blob: 67cd326c6b933e78f5504a2cedb3ea7673c47e72 [file] [log] [blame]
// 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 <stddef.h>
#include <memory>
#include <utility>
#include "base/bind.h"
#include "base/files/scoped_temp_dir.h"
#include "base/run_loop.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/metrics/histogram_tester.h"
#include "base/test/scoped_task_environment.h"
#include "base/time/time.h"
#include "components/os_crypt/os_crypt_mocker.h"
#include "components/password_manager/core/browser/android_affiliation/affiliated_match_helper.h"
#include "components/password_manager/core/browser/android_affiliation/affiliation_service.h"
#include "components/password_manager/core/browser/android_affiliation/mock_affiliated_match_helper.h"
#include "components/password_manager/core/browser/password_manager_test_utils.h"
#include "components/password_manager/core/browser/password_reuse_detector.h"
#include "components/password_manager/core/browser/password_store_consumer.h"
#include "components/password_manager/core/browser/password_store_default.h"
#include "components/password_manager/core/browser/password_store_signin_notifier.h"
#include "components/password_manager/core/common/password_manager_pref_names.h"
#include "components/prefs/pref_registry_simple.h"
#include "components/prefs/testing_pref_service.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
using autofill::PasswordForm;
using base::WaitableEvent;
using testing::_;
using testing::DoAll;
using testing::WithArg;
namespace password_manager {
namespace {
constexpr const char kTestWebRealm1[] = "https://one.example.com/";
constexpr const char kTestWebOrigin1[] = "https://one.example.com/origin";
constexpr const char kTestWebRealm2[] = "https://two.example.com/";
constexpr const char kTestWebOrigin2[] = "https://two.example.com/origin";
constexpr const char kTestWebRealm3[] = "https://three.example.com/";
constexpr const char kTestWebOrigin3[] = "https://three.example.com/origin";
constexpr const char kTestWebRealm5[] = "https://five.example.com/";
constexpr const char kTestWebOrigin5[] = "https://five.example.com/origin";
constexpr const char kTestPSLMatchingWebRealm[] = "https://psl.example.com/";
constexpr const char kTestPSLMatchingWebOrigin[] =
"https://psl.example.com/origin";
constexpr const char kTestUnrelatedWebRealm[] = "https://notexample.com/";
constexpr const char kTestUnrelatedWebOrigin[] = "https:/notexample.com/origin";
constexpr const char kTestSameOrganizationNameWebRealm[] =
"https://example.appspot.com/";
constexpr const char kTestSameOrganizationNameWebOrigin[] =
"https://example.appspot.com/origin";
constexpr const char kTestInsecureWebRealm[] = "http://one.example.com/";
constexpr const char kTestInsecureWebOrigin[] = "http://one.example.com/origin";
constexpr const char kTestAndroidRealm1[] =
"android://hash@com.example.android/";
constexpr const char kTestAndroidRealm2[] =
"android://hash@com.example.two.android/";
constexpr const char kTestAndroidRealm3[] =
"android://hash@com.example.three.android/";
constexpr const char kTestUnrelatedAndroidRealm[] =
"android://hash@com.notexample.android/";
constexpr const char kTestAndroidName1[] = "Example Android App 1";
constexpr const char kTestAndroidIconURL1[] = "https://example.com/icon_1.png";
constexpr const char kTestAndroidName2[] = "Example Android App 2";
constexpr const char kTestAndroidIconURL2[] = "https://example.com/icon_2.png";
class MockPasswordStoreConsumer : public PasswordStoreConsumer {
public:
MockPasswordStoreConsumer() = default;
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);
}
private:
DISALLOW_COPY_AND_ASSIGN(MockPasswordStoreConsumer);
};
class MockPasswordStoreSigninNotifier : public PasswordStoreSigninNotifier {
public:
MOCK_METHOD1(SubscribeToSigninEvents, void(PasswordStore* store));
MOCK_METHOD0(UnsubscribeFromSigninEvents, void());
};
class StartSyncFlareMock {
public:
StartSyncFlareMock() = default;
MOCK_METHOD1(StartSyncFlare, void(syncer::ModelType));
private:
DISALLOW_COPY_AND_ASSIGN(StartSyncFlareMock);
};
} // namespace
class PasswordStoreTest : public testing::Test {
protected:
PasswordStoreTest()
: scoped_task_environment_(
base::test::ScopedTaskEnvironment::MainThreadType::UI) {}
void SetUp() override {
ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
// Mock OSCrypt. There is a call to OSCrypt on initializling
// PasswordReuseDetector, so it should be mocked.
OSCryptMocker::SetUp();
}
void TearDown() override {
OSCryptMocker::TearDown();
}
void WaitForPasswordStore() { scoped_task_environment_.RunUntilIdle(); }
base::FilePath test_login_db_file_path() const {
return temp_dir_.GetPath().Append(FILE_PATH_LITERAL("login_test"));
}
private:
base::ScopedTempDir temp_dir_;
base::test::ScopedTaskEnvironment scoped_task_environment_;
DISALLOW_COPY_AND_ASSIGN(PasswordStoreTest);
};
base::Optional<PasswordHashData> GetPasswordFromPref(
const std::string& username,
bool is_gaia_password,
PrefService* prefs) {
HashPasswordManager hash_password_manager;
hash_password_manager.set_prefs(prefs);
return hash_password_manager.RetrievePasswordHash(username, is_gaia_password);
}
TEST_F(PasswordStoreTest, IgnoreOldWwwGoogleLogins) {
scoped_refptr<PasswordStoreDefault> store(new PasswordStoreDefault(
std::make_unique<LoginDatabase>(test_login_db_file_path())));
store->Init(syncer::SyncableService::StartSyncFlare(), nullptr);
const time_t cutoff = 1325376000; // 00:00 Jan 1 2012 UTC
static const PasswordFormData form_data[] = {
// A form on https://www.google.com/ older than the cutoff. Will be
// ignored.
{PasswordForm::SCHEME_HTML, "https://www.google.com",
"https://www.google.com/origin", "https://www.google.com/action",
L"submit_element", L"username_element", L"password_element",
L"username_value_1", L"", true, cutoff - 1},
// A form on https://www.google.com/ older than the cutoff. Will be
// ignored.
{PasswordForm::SCHEME_HTML, "https://www.google.com",
"https://www.google.com/origin", "https://www.google.com/action",
L"submit_element", L"username_element", L"password_element",
L"username_value_2", L"", true, cutoff - 1},
// A form on https://www.google.com/ newer than the cutoff.
{PasswordForm::SCHEME_HTML, "https://www.google.com",
"https://www.google.com/origin", "https://www.google.com/action",
L"submit_element", L"username_element", L"password_element",
L"username_value_3", L"", true, cutoff + 1},
// A form on https://accounts.google.com/ older than the cutoff.
{PasswordForm::SCHEME_HTML, "https://accounts.google.com",
"https://accounts.google.com/origin",
"https://accounts.google.com/action", L"submit_element",
L"username_element", L"password_element", L"username_value", L"", true,
cutoff - 1},
// A form on http://bar.example.com/ older than the cutoff.
{PasswordForm::SCHEME_HTML, "http://bar.example.com",
"http://bar.example.com/origin", "http://bar.example.com/action",
L"submit_element", L"username_element", L"password_element",
L"username_value", L"", true, cutoff - 1},
};
// Build the forms vector and add the forms to the store.
std::vector<std::unique_ptr<PasswordForm>> all_forms;
for (size_t i = 0; i < base::size(form_data); ++i) {
all_forms.push_back(FillPasswordFormWithData(form_data[i]));
store->AddLogin(*all_forms.back());
}
// We expect to get back only the "recent" www.google.com login.
// Theoretically these should never actually exist since there are no longer
// any login forms on www.google.com to save, but we technically allow them.
// We should not get back the older saved password though.
const PasswordStore::FormDigest www_google = {
PasswordForm::SCHEME_HTML, "https://www.google.com", GURL()};
std::vector<std::unique_ptr<PasswordForm>> www_google_expected;
www_google_expected.push_back(std::make_unique<PasswordForm>(*all_forms[2]));
// We should still get the accounts.google.com login even though it's older
// than our cutoff - this is the new location of all Google login forms.
const PasswordStore::FormDigest accounts_google = {
PasswordForm::SCHEME_HTML, "https://accounts.google.com", GURL()};
std::vector<std::unique_ptr<PasswordForm>> accounts_google_expected;
accounts_google_expected.push_back(
std::make_unique<PasswordForm>(*all_forms[3]));
// Same thing for a generic saved login.
const PasswordStore::FormDigest bar_example = {
PasswordForm::SCHEME_HTML, "http://bar.example.com", GURL()};
std::vector<std::unique_ptr<PasswordForm>> bar_example_expected;
bar_example_expected.push_back(std::make_unique<PasswordForm>(*all_forms[4]));
MockPasswordStoreConsumer consumer;
testing::InSequence s;
EXPECT_CALL(consumer,
OnGetPasswordStoreResultsConstRef(
UnorderedPasswordFormElementsAre(&www_google_expected)))
.RetiresOnSaturation();
EXPECT_CALL(consumer,
OnGetPasswordStoreResultsConstRef(
UnorderedPasswordFormElementsAre(&accounts_google_expected)))
.RetiresOnSaturation();
EXPECT_CALL(consumer,
OnGetPasswordStoreResultsConstRef(
UnorderedPasswordFormElementsAre(&bar_example_expected)))
.RetiresOnSaturation();
store->GetLogins(www_google, &consumer);
store->GetLogins(accounts_google, &consumer);
store->GetLogins(bar_example, &consumer);
WaitForPasswordStore();
store->ShutdownOnUIThread();
}
TEST_F(PasswordStoreTest, StartSyncFlare) {
scoped_refptr<PasswordStoreDefault> store(new PasswordStoreDefault(
std::make_unique<LoginDatabase>(test_login_db_file_path())));
StartSyncFlareMock mock;
store->Init(
base::Bind(&StartSyncFlareMock::StartSyncFlare, base::Unretained(&mock)),
nullptr);
PasswordForm form;
form.origin = GURL("http://accounts.google.com/LoginAuth");
form.signon_realm = "http://accounts.google.com/";
EXPECT_CALL(mock, StartSyncFlare(syncer::PASSWORDS));
store->AddLogin(form);
WaitForPasswordStore();
store->ShutdownOnUIThread();
}
TEST_F(PasswordStoreTest, UpdateLoginPrimaryKeyFields) {
/* clang-format off */
static const PasswordFormData kTestCredentials[] = {
// The old credential.
{PasswordForm::SCHEME_HTML,
kTestWebRealm1,
kTestWebOrigin1,
"", L"", L"username_element_1", L"password_element_1",
L"username_value_1",
L"", true, 1},
// The new credential with different values for all primary key fields.
{PasswordForm::SCHEME_HTML,
kTestWebRealm2,
kTestWebOrigin2,
"", L"", L"username_element_2", L"password_element_2",
L"username_value_2",
L"", true, 1}};
/* clang-format on */
scoped_refptr<PasswordStoreDefault> store(new PasswordStoreDefault(
std::make_unique<LoginDatabase>(test_login_db_file_path())));
store->Init(syncer::SyncableService::StartSyncFlare(), nullptr);
std::unique_ptr<PasswordForm> old_form(
FillPasswordFormWithData(kTestCredentials[0]));
store->AddLogin(*old_form);
WaitForPasswordStore();
MockPasswordStoreObserver mock_observer;
store->AddObserver(&mock_observer);
std::unique_ptr<PasswordForm> new_form(
FillPasswordFormWithData(kTestCredentials[1]));
EXPECT_CALL(mock_observer, OnLoginsChanged(testing::SizeIs(2u)));
PasswordForm old_primary_key;
old_primary_key.signon_realm = old_form->signon_realm;
old_primary_key.origin = old_form->origin;
old_primary_key.username_element = old_form->username_element;
old_primary_key.username_value = old_form->username_value;
old_primary_key.password_element = old_form->password_element;
store->UpdateLoginWithPrimaryKey(*new_form, old_primary_key);
WaitForPasswordStore();
testing::Mock::VerifyAndClearExpectations(&mock_observer);
MockPasswordStoreConsumer mock_consumer;
std::vector<std::unique_ptr<PasswordForm>> expected_forms;
expected_forms.push_back(std::move(new_form));
EXPECT_CALL(mock_consumer,
OnGetPasswordStoreResultsConstRef(
UnorderedPasswordFormElementsAre(&expected_forms)));
store->GetAutofillableLogins(&mock_consumer);
WaitForPasswordStore();
store->RemoveObserver(&mock_observer);
store->ShutdownOnUIThread();
}
// Verify that RemoveLoginsCreatedBetween() fires the completion callback after
// deletions have been performed and notifications have been sent out. Whether
// the correct logins are removed or not is verified in detail in other tests.
TEST_F(PasswordStoreTest, RemoveLoginsCreatedBetweenCallbackIsCalled) {
/* clang-format off */
static const PasswordFormData kTestCredential =
{PasswordForm::SCHEME_HTML,
kTestWebRealm1,
kTestWebOrigin1,
"", L"", L"username_element_1", L"password_element_1",
L"username_value_1",
L"", true, 1};
/* clang-format on */
scoped_refptr<PasswordStoreDefault> store(new PasswordStoreDefault(
std::make_unique<LoginDatabase>(test_login_db_file_path())));
store->Init(syncer::SyncableService::StartSyncFlare(), nullptr);
std::unique_ptr<PasswordForm> test_form(
FillPasswordFormWithData(kTestCredential));
store->AddLogin(*test_form);
WaitForPasswordStore();
MockPasswordStoreObserver mock_observer;
store->AddObserver(&mock_observer);
EXPECT_CALL(mock_observer, OnLoginsChanged(testing::SizeIs(1u)));
base::RunLoop run_loop;
store->RemoveLoginsCreatedBetween(base::Time::FromDoubleT(0),
base::Time::FromDoubleT(2),
run_loop.QuitClosure());
run_loop.Run();
testing::Mock::VerifyAndClearExpectations(&mock_observer);
store->RemoveObserver(&mock_observer);
store->ShutdownOnUIThread();
}
// When no Android applications are actually affiliated with the realm of the
// observed form, GetLoginsWithAffiliations() should still return the exact and
// PSL matching results, but not any stored Android credentials.
TEST_F(PasswordStoreTest, GetLoginsWithoutAffiliations) {
/* clang-format off */
static const PasswordFormData kTestCredentials[] = {
// Credential that is an exact match of the observed form.
{PasswordForm::SCHEME_HTML,
kTestWebRealm1,
kTestWebOrigin1,
"", L"", L"", L"",
L"username_value_1",
L"", true, 1},
// Credential that is a PSL match of the observed form.
{PasswordForm::SCHEME_HTML,
kTestPSLMatchingWebRealm,
kTestPSLMatchingWebOrigin,
"", L"", L"", L"",
L"username_value_2",
L"", true, 1},
// Credential for an unrelated Android application.
{PasswordForm::SCHEME_HTML,
kTestUnrelatedAndroidRealm,
"", "", L"", L"", L"",
L"username_value_3",
L"", true, 1}};
/* clang-format on */
scoped_refptr<PasswordStoreDefault> store(new PasswordStoreDefault(
std::make_unique<LoginDatabase>(test_login_db_file_path())));
store->Init(syncer::SyncableService::StartSyncFlare(), nullptr);
std::vector<std::unique_ptr<PasswordForm>> all_credentials;
for (size_t i = 0; i < base::size(kTestCredentials); ++i) {
all_credentials.push_back(FillPasswordFormWithData(kTestCredentials[i]));
store->AddLogin(*all_credentials.back());
}
PasswordStore::FormDigest observed_form = {
PasswordForm::SCHEME_HTML, kTestWebRealm1, GURL(kTestWebOrigin1)};
std::vector<std::unique_ptr<PasswordForm>> expected_results;
expected_results.push_back(
std::make_unique<PasswordForm>(*all_credentials[0]));
expected_results.push_back(
std::make_unique<PasswordForm>(*all_credentials[1]));
for (const auto& result : expected_results) {
if (result->signon_realm != observed_form.signon_realm)
result->is_public_suffix_match = true;
}
std::vector<std::string> no_affiliated_android_realms;
auto mock_helper = std::make_unique<MockAffiliatedMatchHelper>();
mock_helper->ExpectCallToGetAffiliatedAndroidRealms(
observed_form, no_affiliated_android_realms);
store->SetAffiliatedMatchHelper(std::move(mock_helper));
MockPasswordStoreConsumer mock_consumer;
EXPECT_CALL(mock_consumer,
OnGetPasswordStoreResultsConstRef(
UnorderedPasswordFormElementsAre(&expected_results)));
store->GetLogins(observed_form, &mock_consumer);
WaitForPasswordStore();
store->ShutdownOnUIThread();
}
// There are 3 Android applications affiliated with the realm of the observed
// form, with the PasswordStore having credentials for two of these (even two
// credentials for one). GetLoginsWithAffiliations() should return the exact,
// and PSL matching credentials, and the credentials for these two Android
// applications, but not for the unaffiliated Android application.
TEST_F(PasswordStoreTest, GetLoginsWithAffiliations) {
static constexpr const struct {
PasswordFormData form_data;
bool use_federated_login;
} kTestCredentials[] = {
// Credential that is an exact match of the observed form.
{
{PasswordForm::SCHEME_HTML, kTestWebRealm1, kTestWebOrigin1, "", L"",
L"", L"", L"username_value_1", L"", true, 1},
false,
},
// Credential that is a PSL match of the observed form.
{
{PasswordForm::SCHEME_HTML, kTestPSLMatchingWebRealm,
kTestPSLMatchingWebOrigin, "", L"", L"", L"", L"username_value_2",
L"", true, 1},
false,
},
// Credential for an Android application affiliated with the realm of the
// observed from.
{
{PasswordForm::SCHEME_HTML, kTestAndroidRealm1, "", "", L"", L"", L"",
L"username_value_3", L"", true, 1},
false,
},
// Second credential for the same Android application.
{
{PasswordForm::SCHEME_HTML, kTestAndroidRealm1, "", "", L"", L"", L"",
L"username_value_3b", L"", true, 1},
false,
},
// Third credential for the same application which is username-only.
{
{PasswordForm::SCHEME_USERNAME_ONLY, kTestAndroidRealm1, "", "", L"",
L"", L"", L"username_value_3c", L"", true, 1},
false,
},
// Credential for another Android application affiliated with the realm
// of the observed from.
{
{PasswordForm::SCHEME_HTML, kTestAndroidRealm2, "", "", L"", L"", L"",
L"username_value_4", L"", true, 1},
false,
},
// Federated credential for this second Android application.
{
{PasswordForm::SCHEME_HTML, kTestAndroidRealm2, "", "", L"", L"", L"",
L"username_value_4b", L"", true, 1},
true,
},
// Credential for an unrelated Android application.
{
{PasswordForm::SCHEME_HTML, kTestUnrelatedAndroidRealm, "", "", L"",
L"", L"", L"username_value_5", L"", true, 1},
false,
}};
scoped_refptr<PasswordStoreDefault> store(new PasswordStoreDefault(
std::make_unique<LoginDatabase>(test_login_db_file_path())));
store->Init(syncer::SyncableService::StartSyncFlare(), nullptr);
std::vector<std::unique_ptr<PasswordForm>> all_credentials;
for (const auto& i : kTestCredentials) {
all_credentials.push_back(
FillPasswordFormWithData(i.form_data, i.use_federated_login));
store->AddLogin(*all_credentials.back());
}
PasswordStore::FormDigest observed_form = {
PasswordForm::SCHEME_HTML, kTestWebRealm1, GURL(kTestWebOrigin1)};
std::vector<std::unique_ptr<PasswordForm>> expected_results;
expected_results.push_back(
std::make_unique<PasswordForm>(*all_credentials[0]));
expected_results.push_back(
std::make_unique<PasswordForm>(*all_credentials[1]));
expected_results.push_back(
std::make_unique<PasswordForm>(*all_credentials[2]));
expected_results.push_back(
std::make_unique<PasswordForm>(*all_credentials[3]));
expected_results.push_back(
std::make_unique<PasswordForm>(*all_credentials[5]));
expected_results.push_back(
std::make_unique<PasswordForm>(*all_credentials[6]));
for (const auto& result : expected_results) {
if (result->signon_realm != observed_form.signon_realm &&
!IsValidAndroidFacetURI(result->signon_realm))
result->is_public_suffix_match = true;
if (IsValidAndroidFacetURI(result->signon_realm))
result->is_affiliation_based_match = true;
}
std::vector<std::string> affiliated_android_realms;
affiliated_android_realms.push_back(kTestAndroidRealm1);
affiliated_android_realms.push_back(kTestAndroidRealm2);
affiliated_android_realms.push_back(kTestAndroidRealm3);
auto mock_helper = std::make_unique<MockAffiliatedMatchHelper>();
mock_helper->ExpectCallToGetAffiliatedAndroidRealms(
observed_form, affiliated_android_realms);
store->SetAffiliatedMatchHelper(std::move(mock_helper));
MockPasswordStoreConsumer mock_consumer;
EXPECT_CALL(mock_consumer,
OnGetPasswordStoreResultsConstRef(
UnorderedPasswordFormElementsAre(&expected_results)));
store->GetLogins(observed_form, &mock_consumer);
WaitForPasswordStore();
store->ShutdownOnUIThread();
}
// When the password stored for an Android application is updated, credentials
// with the same username stored for affiliated web sites should also be updated
// automatically.
TEST_F(PasswordStoreTest, UpdatePasswordsStoredForAffiliatedWebsites) {
const wchar_t kTestUsername[] = L"username_value_1";
const wchar_t kTestOtherUsername[] = L"username_value_2";
const wchar_t kTestOldPassword[] = L"old_password_value";
const wchar_t kTestNewPassword[] = L"new_password_value";
const wchar_t kTestOtherPassword[] = L"other_password_value";
/* clang-format off */
static const PasswordFormData kTestCredentials[] = {
// The credential for the Android application that will be updated
// explicitly so it can be tested if the changes are correctly propagated
// to affiliated Web credentials.
{PasswordForm::SCHEME_HTML,
kTestAndroidRealm1,
"", "", L"", L"", L"",
kTestUsername,
kTestOldPassword, true, 2},
// --- Positive samples --- Credentials that the password update should be
// automatically propagated to.
// Credential for an affiliated web site with the same username.
{PasswordForm::SCHEME_HTML,
kTestWebRealm1,
kTestWebOrigin1,
"", L"", L"", L"",
kTestUsername,
kTestOldPassword, true, 1},
// Credential for another affiliated web site with the same username.
// Although the password is different than the current/old password for
// the Android application, it should be updated regardless.
{PasswordForm::SCHEME_HTML,
kTestWebRealm2,
kTestWebOrigin2,
"", L"", L"", L"",
kTestUsername,
kTestOtherPassword, true, 1},
// --- Negative samples --- Credentials that the password update should
// not be propagated to.
// Credential for another affiliated web site, but one that already has
// the new password.
{PasswordForm::SCHEME_HTML,
kTestWebRealm3,
kTestWebOrigin3,
"", L"", L"", L"",
kTestUsername,
kTestNewPassword, true, 1},
// Credential for the HTTP version of an affiliated web site.
{PasswordForm::SCHEME_HTML,
kTestInsecureWebRealm,
kTestInsecureWebOrigin,
"", L"", L"", L"",
kTestUsername,
kTestOldPassword, true, 1},
// Credential for an affiliated web site, but with a different username.
{PasswordForm::SCHEME_HTML,
kTestWebRealm1,
kTestWebOrigin1,
"", L"", L"", L"",
kTestOtherUsername,
kTestOldPassword, true, 1},
// Credential for a web site that is a PSL match to a web sites affiliated
// with the Android application.
{PasswordForm::SCHEME_HTML,
kTestPSLMatchingWebRealm,
kTestPSLMatchingWebOrigin,
"poisoned", L"poisoned", L"", L"",
kTestUsername,
kTestOldPassword, true, 1},
// Credential for an unrelated web site.
{PasswordForm::SCHEME_HTML,
kTestUnrelatedWebRealm,
kTestUnrelatedWebOrigin,
"", L"", L"", L"",
kTestUsername,
kTestOldPassword, true, 1},
// Credential for an affiliated Android application.
{PasswordForm::SCHEME_HTML,
kTestAndroidRealm2,
"", "", L"", L"", L"",
kTestUsername,
kTestOldPassword, true, 1},
// Credential for an unrelated Android application.
{PasswordForm::SCHEME_HTML,
kTestUnrelatedAndroidRealm,
"", "", L"", L"", L"",
kTestUsername,
kTestOldPassword, true, 1},
// Credential for an affiliated web site with the same username, but one
// that was updated at the same time via Sync as the Android credential.
{PasswordForm::SCHEME_HTML,
kTestWebRealm5,
kTestWebOrigin5,
"", L"", L"", L"",
kTestUsername,
kTestOtherPassword, true, 2}};
/* clang-format on */
// The number of positive samples in |kTestCredentials|.
const size_t kExpectedNumberOfPropagatedUpdates = 2u;
const bool kFalseTrue[] = {false, true};
for (bool propagation_enabled : kFalseTrue) {
for (bool test_remove_and_add_login : kFalseTrue) {
SCOPED_TRACE(testing::Message("propagation_enabled: ")
<< propagation_enabled);
SCOPED_TRACE(testing::Message("test_remove_and_add_login: ")
<< test_remove_and_add_login);
scoped_refptr<PasswordStoreDefault> store(new PasswordStoreDefault(
std::make_unique<LoginDatabase>(test_login_db_file_path())));
store->Init(syncer::SyncableService::StartSyncFlare(), nullptr);
store->RemoveLoginsCreatedBetween(base::Time(), base::Time::Max(),
base::Closure());
// Set up the initial test data set.
std::vector<std::unique_ptr<PasswordForm>> all_credentials;
for (size_t i = 0; i < base::size(kTestCredentials); ++i) {
all_credentials.push_back(
FillPasswordFormWithData(kTestCredentials[i]));
all_credentials.back()->date_synced =
all_credentials.back()->date_created;
store->AddLogin(*all_credentials.back());
}
WaitForPasswordStore();
// The helper must be injected after the initial test data is set up,
// otherwise it will already start propagating updates as new Android
// credentials are added.
store->enable_propagating_password_changes_to_web_credentials(
propagation_enabled);
// Calculate how the correctly updated test data set should look like.
size_t expected_number_of_propageted_updates =
propagation_enabled ? kExpectedNumberOfPropagatedUpdates : 0u;
std::vector<std::unique_ptr<PasswordForm>>
expected_credentials_after_update;
for (size_t i = 0; i < all_credentials.size(); ++i) {
expected_credentials_after_update.push_back(
std::make_unique<PasswordForm>(*all_credentials[i]));
if (i < 1 + expected_number_of_propageted_updates) {
expected_credentials_after_update.back()->password_value =
base::WideToUTF16(kTestNewPassword);
}
}
if (propagation_enabled) {
std::vector<std::string> affiliated_web_realms;
affiliated_web_realms.push_back(kTestWebRealm1);
affiliated_web_realms.push_back(kTestWebRealm2);
affiliated_web_realms.push_back(kTestWebRealm3);
affiliated_web_realms.push_back(kTestWebRealm5);
auto mock_helper = std::make_unique<MockAffiliatedMatchHelper>();
mock_helper->ExpectCallToGetAffiliatedWebRealms(
PasswordStore::FormDigest(*expected_credentials_after_update[0]),
affiliated_web_realms);
store->SetAffiliatedMatchHelper(std::move(mock_helper));
}
// Explicitly update the Android credential, wait until things calm down,
// then query all passwords and expect that:
// 1.) The positive samples in |kTestCredentials| have the new password,
// but the negative samples do not.
// 2.) Change notifications are sent about the updates. Note that as the
// test interacts with the (Update|Add)LoginSync methods, only the
// derived changes should trigger notifications, the first one would
// normally be trigger by Sync.
MockPasswordStoreObserver mock_observer;
store->AddObserver(&mock_observer);
if (propagation_enabled) {
EXPECT_CALL(mock_observer, OnLoginsChanged(testing::SizeIs(
expected_number_of_propageted_updates)));
}
if (test_remove_and_add_login) {
store->ScheduleTask(
base::BindOnce(IgnoreResult(&PasswordStore::RemoveLoginSync), store,
*all_credentials[0]));
store->ScheduleTask(
base::BindOnce(IgnoreResult(&PasswordStore::AddLoginSync), store,
*expected_credentials_after_update[0]));
} else {
store->ScheduleTask(
base::BindOnce(IgnoreResult(&PasswordStore::UpdateLoginSync), store,
*expected_credentials_after_update[0]));
}
WaitForPasswordStore();
store->RemoveObserver(&mock_observer);
MockPasswordStoreConsumer mock_consumer;
EXPECT_CALL(mock_consumer, OnGetPasswordStoreResultsConstRef(
UnorderedPasswordFormElementsAre(
&expected_credentials_after_update)));
store->GetAutofillableLogins(&mock_consumer);
WaitForPasswordStore();
store->ShutdownOnUIThread();
store = nullptr;
// Finish processing so that the database isn't locked next iteration.
WaitForPasswordStore();
}
}
}
TEST_F(PasswordStoreTest, GetLoginsWithAffiliationAndBrandingInformation) {
static const PasswordFormData kTestCredentials[] = {
{PasswordForm::SCHEME_HTML, kTestAndroidRealm1, "", "", L"", L"", L"",
L"username_value_1", L"", true, 1},
{PasswordForm::SCHEME_HTML, kTestAndroidRealm2, "", "", L"", L"", L"",
L"username_value_2", L"", true, 1},
{PasswordForm::SCHEME_HTML, kTestAndroidRealm3, "", "", L"", L"", L"",
L"username_value_3", L"", true, 1},
{PasswordForm::SCHEME_HTML, kTestWebRealm1, kTestWebOrigin1, "", L"", L"",
L"", L"username_value_4", L"", true, 1}};
for (bool blacklisted : {false, true}) {
SCOPED_TRACE(testing::Message("use blacklisted logins: ") << blacklisted);
scoped_refptr<PasswordStoreDefault> store(new PasswordStoreDefault(
std::make_unique<LoginDatabase>(test_login_db_file_path())));
store->Init(syncer::SyncableService::StartSyncFlare(), nullptr);
store->RemoveLoginsCreatedBetween(base::Time(), base::Time::Max(),
base::Closure());
std::vector<std::unique_ptr<PasswordForm>> all_credentials;
for (const auto& test_credential : kTestCredentials) {
all_credentials.push_back(FillPasswordFormWithData(test_credential));
all_credentials.back()->blacklisted_by_user = blacklisted;
store->AddLogin(*all_credentials.back());
}
MockPasswordStoreConsumer mock_consumer;
std::vector<std::unique_ptr<PasswordForm>> expected_results;
for (const auto& credential : all_credentials)
expected_results.push_back(std::make_unique<PasswordForm>(*credential));
std::vector<MockAffiliatedMatchHelper::AffiliationAndBrandingInformation>
affiliation_info_for_results = {
{kTestWebRealm1, kTestAndroidName1, GURL(kTestAndroidIconURL1)},
{kTestWebRealm2, kTestAndroidName2, GURL(kTestAndroidIconURL2)},
{/* Pretend affiliation or branding info is unavailable. */},
{/* Pretend affiliation or branding info is unavailable. */}};
auto mock_helper = std::make_unique<MockAffiliatedMatchHelper>();
mock_helper->ExpectCallToInjectAffiliationAndBrandingInformation(
affiliation_info_for_results);
store->SetAffiliatedMatchHelper(std::move(mock_helper));
for (size_t i = 0; i < expected_results.size(); ++i) {
expected_results[i]->affiliated_web_realm =
affiliation_info_for_results[i].affiliated_web_realm;
expected_results[i]->app_display_name =
affiliation_info_for_results[i].app_display_name;
expected_results[i]->app_icon_url =
affiliation_info_for_results[i].app_icon_url;
}
EXPECT_CALL(mock_consumer,
OnGetPasswordStoreResultsConstRef(
UnorderedPasswordFormElementsAre(&expected_results)));
if (blacklisted) {
store->GetBlacklistLoginsWithAffiliationAndBrandingInformation(
&mock_consumer);
} else {
store->GetAutofillableLoginsWithAffiliationAndBrandingInformation(
&mock_consumer);
}
// Since GetAutofillableLoginsWithAffiliationAndBrandingInformation
// schedules a request for affiliation information to UI thread, don't
// shutdown UI thread until there are no tasks in the UI queue.
WaitForPasswordStore();
store->ShutdownOnUIThread();
store = nullptr;
// Finish processing so that the database isn't locked next iteration.
WaitForPasswordStore();
}
}
TEST_F(PasswordStoreTest, GetAllLoginsWithAffiliationAndBrandingInformation) {
static constexpr PasswordFormData kTestCredentials[] = {
{PasswordForm::SCHEME_HTML, kTestAndroidRealm1, "", "", L"", L"", L"",
L"username_value_1", L"", true, 1},
{PasswordForm::SCHEME_HTML, kTestAndroidRealm2, "", "", L"", L"", L"",
L"username_value_2", L"", true, 1},
{PasswordForm::SCHEME_HTML, kTestAndroidRealm3, "", "", L"", L"", L"",
L"username_value_3", L"", true, 1},
{PasswordForm::SCHEME_HTML, kTestWebRealm1, kTestWebOrigin1, "", L"", L"",
L"", L"username_value_4", L"", true, 1},
// A PasswordFormData with nullptr as the username_value will be converted
// in a blacklisted PasswordForm in FillPasswordFormWithData().
{PasswordForm::SCHEME_HTML, kTestWebRealm2, kTestWebOrigin2, "", L"", L"",
L"", nullptr, L"", true, 1},
{PasswordForm::SCHEME_HTML, kTestWebRealm3, kTestWebOrigin3, "", L"", L"",
L"", nullptr, L"", true, 1}};
auto store = base::MakeRefCounted<PasswordStoreDefault>(
std::make_unique<LoginDatabase>(test_login_db_file_path()));
store->Init(syncer::SyncableService::StartSyncFlare(), nullptr);
std::vector<std::unique_ptr<PasswordForm>> all_credentials;
for (const auto& test_credential : kTestCredentials) {
all_credentials.push_back(FillPasswordFormWithData(test_credential));
store->AddLogin(*all_credentials.back());
}
MockPasswordStoreConsumer mock_consumer;
std::vector<std::unique_ptr<PasswordForm>> expected_results;
for (const auto& credential : all_credentials)
expected_results.push_back(std::make_unique<PasswordForm>(*credential));
std::vector<MockAffiliatedMatchHelper::AffiliationAndBrandingInformation>
affiliation_info_for_results = {
{kTestWebRealm1, kTestAndroidName1, GURL(kTestAndroidIconURL1)},
{kTestWebRealm2, kTestAndroidName2, GURL(kTestAndroidIconURL2)},
{/* Pretend affiliation or branding info is unavailable. */},
{/* Pretend affiliation or branding info is unavailable. */},
{/* Pretend affiliation or branding info is unavailable. */},
{/* Pretend affiliation or branding info is unavailable. */}};
auto mock_helper = std::make_unique<MockAffiliatedMatchHelper>();
mock_helper->ExpectCallToInjectAffiliationAndBrandingInformation(
affiliation_info_for_results);
store->SetAffiliatedMatchHelper(std::move(mock_helper));
for (size_t i = 0; i < expected_results.size(); ++i) {
expected_results[i]->affiliated_web_realm =
affiliation_info_for_results[i].affiliated_web_realm;
expected_results[i]->app_display_name =
affiliation_info_for_results[i].app_display_name;
expected_results[i]->app_icon_url =
affiliation_info_for_results[i].app_icon_url;
}
EXPECT_CALL(mock_consumer,
OnGetPasswordStoreResultsConstRef(
UnorderedPasswordFormElementsAre(&expected_results)));
store->GetAllLoginsWithAffiliationAndBrandingInformation(&mock_consumer);
// Since GetAutofillableLoginsWithAffiliationAndBrandingInformation
// schedules a request for affiliation information to UI thread, don't
// shutdown UI thread until there are no tasks in the UI queue.
WaitForPasswordStore();
store->ShutdownOnUIThread();
}
TEST_F(PasswordStoreTest, GetLoginsForSameOrganizationName) {
static constexpr const PasswordFormData kSameOrganizationCredentials[] = {
// Credential that is an exact match of the observed form.
{PasswordForm::SCHEME_HTML, kTestWebRealm1, kTestWebOrigin1, "", L"", L"",
L"", L"username_value_1", L"", true, 1},
// Credential that is a PSL match of the observed form.
{PasswordForm::SCHEME_HTML, kTestPSLMatchingWebRealm,
kTestPSLMatchingWebOrigin, "", L"", L"", L"", L"username_value_2", L"",
true, 1},
// Credential for the HTTP version of the observed form. (Should not be
// filled, but returned as part of same-organization-name matches).
{PasswordForm::SCHEME_HTML, kTestInsecureWebRealm, kTestInsecureWebOrigin,
"", L"", L"", L"", L"username_value_3", L"", true, 1},
// Credential for a signon realm with a different TLD, but same
// organization identifying name.
{PasswordForm::SCHEME_HTML, kTestSameOrganizationNameWebRealm,
kTestSameOrganizationNameWebOrigin, "", L"", L"", L"",
L"username_value_4", L"", true, 1},
};
static constexpr const PasswordFormData kNotSameOrganizationCredentials[] = {
// Unrelated Web credential.
{PasswordForm::SCHEME_HTML, kTestUnrelatedWebRealm,
kTestUnrelatedWebOrigin, "", L"", L"", L"", L"username_value_5", L"",
true, 1},
// Credential for an affiliated Android application.
{PasswordForm::SCHEME_HTML, kTestAndroidRealm1, "", "", L"", L"", L"",
L"username_value_6", L"", true, 1}};
scoped_refptr<PasswordStoreDefault> store(new PasswordStoreDefault(
std::make_unique<LoginDatabase>(test_login_db_file_path())));
store->Init(syncer::SyncableService::StartSyncFlare(), nullptr);
std::vector<std::unique_ptr<PasswordForm>> expected_results;
for (const auto& form_data : kSameOrganizationCredentials) {
expected_results.push_back(FillPasswordFormWithData(form_data));
store->AddLogin(*expected_results.back());
}
for (const auto& form_data : kNotSameOrganizationCredentials) {
store->AddLogin(*FillPasswordFormWithData(form_data));
}
const std::string observed_form_realm = kTestWebRealm1;
MockPasswordStoreConsumer mock_consumer;
EXPECT_CALL(mock_consumer,
OnGetPasswordStoreResultsConstRef(
UnorderedPasswordFormElementsAre(&expected_results)));
store->GetLoginsForSameOrganizationName(observed_form_realm, &mock_consumer);
WaitForPasswordStore();
store->ShutdownOnUIThread();
}
#if defined(SYNC_PASSWORD_REUSE_DETECTION_ENABLED)
TEST_F(PasswordStoreTest, CheckPasswordReuse) {
static constexpr PasswordFormData kTestCredentials[] = {
{PasswordForm::SCHEME_HTML, "https://www.google.com",
"https://www.google.com", "", L"", L"", L"", L"", L"password", true, 1},
{PasswordForm::SCHEME_HTML, "https://facebook.com",
"https://facebook.com", "", L"", L"", L"", L"", L"topsecret", true, 1}};
scoped_refptr<PasswordStoreDefault> store(new PasswordStoreDefault(
std::make_unique<LoginDatabase>(test_login_db_file_path())));
store->Init(syncer::SyncableService::StartSyncFlare(), nullptr);
for (const auto& test_credentials : kTestCredentials) {
auto credentials = FillPasswordFormWithData(test_credentials);
store->AddLogin(*credentials);
}
struct {
const wchar_t* input;
const char* domain;
const size_t reused_password_len; // Set to 0 if no reuse is expected.
const char* reuse_domain;
} kReuseTestData[] = {
{L"12345password", "https://evil.com", strlen("password"), "google.com"},
{L"1234567890", "https://evil.com", 0, nullptr},
{L"topsecret", "https://m.facebook.com", 0, nullptr},
};
for (const auto& test_data : kReuseTestData) {
MockPasswordReuseDetectorConsumer mock_consumer;
if (test_data.reused_password_len != 0) {
EXPECT_CALL(
mock_consumer,
OnReuseFound(test_data.reused_password_len, Matches(base::nullopt),
std::vector<std::string>({test_data.reuse_domain}), 2));
} else {
EXPECT_CALL(mock_consumer, OnReuseFound(_, _, _, _)).Times(0);
}
store->CheckReuse(base::WideToUTF16(test_data.input), test_data.domain,
&mock_consumer);
WaitForPasswordStore();
}
store->ShutdownOnUIThread();
}
TEST_F(PasswordStoreTest, SavingClearingProtectedPassword) {
scoped_refptr<PasswordStoreDefault> store(new PasswordStoreDefault(
std::make_unique<LoginDatabase>(test_login_db_file_path())));
TestingPrefServiceSimple prefs;
prefs.registry()->RegisterListPref(prefs::kPasswordHashDataList,
PrefRegistry::NO_REGISTRATION_FLAGS);
ASSERT_FALSE(prefs.HasPrefPath(prefs::kSyncPasswordHash));
store->Init(syncer::SyncableService::StartSyncFlare(), &prefs);
const base::string16 sync_password = base::ASCIIToUTF16("password");
const base::string16 input = base::ASCIIToUTF16("123password");
store->SaveGaiaPasswordHash(
"sync_username", sync_password,
metrics_util::SyncPasswordHashChange::SAVED_ON_CHROME_SIGNIN);
WaitForPasswordStore();
EXPECT_TRUE(prefs.HasPrefPath(prefs::kPasswordHashDataList));
base::Optional<PasswordHashData> sync_password_hash =
GetPasswordFromPref("sync_username", /*is_gaia_password=*/true, &prefs);
ASSERT_TRUE(sync_password_hash.has_value());
// Check that sync password reuse is found.
MockPasswordReuseDetectorConsumer mock_consumer;
EXPECT_CALL(mock_consumer,
OnReuseFound(sync_password.size(), Matches(sync_password_hash),
std::vector<std::string>(), 0));
store->CheckReuse(input, "https://facebook.com", &mock_consumer);
WaitForPasswordStore();
testing::Mock::VerifyAndClearExpectations(&mock_consumer);
// Save a non-sync Gaia password this time.
const base::string16 gaia_password = base::ASCIIToUTF16("3password");
store->SaveGaiaPasswordHash(
"other_gaia_username", gaia_password,
metrics_util::SyncPasswordHashChange::NOT_SYNC_PASSWORD_CHANGE);
base::Optional<PasswordHashData> gaia_password_hash = GetPasswordFromPref(
"other_gaia_username", /*is_gaia_password=*/true, &prefs);
ASSERT_TRUE(gaia_password_hash.has_value());
// Check that Gaia password reuse is found.
EXPECT_CALL(mock_consumer,
OnReuseFound(gaia_password.size(), Matches(gaia_password_hash),
std::vector<std::string>(), 0));
store->CheckReuse(input, "https://example.com", &mock_consumer);
WaitForPasswordStore();
testing::Mock::VerifyAndClearExpectations(&mock_consumer);
// Check that no sync password reuse is found after clearing the password
// hash.
store->ClearGaiaPasswordHash("sync_username");
EXPECT_EQ(1u, prefs.GetList(prefs::kPasswordHashDataList)->GetList().size());
EXPECT_CALL(mock_consumer, OnReuseFound(_, _, _, _)).Times(1);
store->CheckReuse(input, "https://facebook.com", &mock_consumer);
WaitForPasswordStore();
testing::Mock::VerifyAndClearExpectations(&mock_consumer);
// Check that no Gaia password reuse is found after clearing all Gaia
// password hash.
store->ClearAllGaiaPasswordHash();
EXPECT_EQ(0u, prefs.GetList(prefs::kPasswordHashDataList)->GetList().size());
EXPECT_CALL(mock_consumer, OnReuseFound(_, _, _, _)).Times(0);
store->CheckReuse(input, "https://facebook.com", &mock_consumer);
WaitForPasswordStore();
testing::Mock::VerifyAndClearExpectations(&mock_consumer);
// Save a enterprise password this time.
const base::string16 enterprise_password = base::ASCIIToUTF16("23password");
store->SaveEnterprisePasswordHash("enterprise_username", enterprise_password);
base::Optional<PasswordHashData> enterprise_password_hash =
GetPasswordFromPref("enterprise_username", /*is_gaia_password=*/false,
&prefs);
ASSERT_TRUE(enterprise_password_hash.has_value());
// Check that enterprise password reuse is found.
EXPECT_CALL(mock_consumer, OnReuseFound(enterprise_password.size(),
Matches(enterprise_password_hash),
std::vector<std::string>(), 0));
store->CheckReuse(input, "https://example.com", &mock_consumer);
WaitForPasswordStore();
testing::Mock::VerifyAndClearExpectations(&mock_consumer);
// Check that no enterprise password reuse is found after clearing the
// password hash.
store->ClearAllEnterprisePasswordHash();
EXPECT_EQ(0u, prefs.GetList(prefs::kPasswordHashDataList)->GetList().size());
EXPECT_CALL(mock_consumer, OnReuseFound(_, _, _, _)).Times(0);
store->CheckReuse(input, "https://example.com", &mock_consumer);
WaitForPasswordStore();
testing::Mock::VerifyAndClearExpectations(&mock_consumer);
store->ShutdownOnUIThread();
}
TEST_F(PasswordStoreTest, SubscriptionAndUnsubscriptionFromSignInEvents) {
scoped_refptr<PasswordStoreDefault> store(new PasswordStoreDefault(
std::make_unique<LoginDatabase>(test_login_db_file_path())));
std::unique_ptr<MockPasswordStoreSigninNotifier> notifier =
std::make_unique<MockPasswordStoreSigninNotifier>();
MockPasswordStoreSigninNotifier* notifier_weak = notifier.get();
// Check that |store| is subscribed to sign-in events.
EXPECT_CALL(*notifier_weak, SubscribeToSigninEvents(store.get()));
store->SetPasswordStoreSigninNotifier(std::move(notifier));
testing::Mock::VerifyAndClearExpectations(store.get());
// Check that |store| is unsubscribed from sign-in events on shutdown.
EXPECT_CALL(*notifier_weak, UnsubscribeFromSigninEvents());
store->ShutdownOnUIThread();
}
TEST_F(PasswordStoreTest, ReportMetricsForAdvancedProtection) {
scoped_refptr<PasswordStoreDefault> store(new PasswordStoreDefault(
std::make_unique<LoginDatabase>(test_login_db_file_path())));
TestingPrefServiceSimple prefs;
prefs.registry()->RegisterListPref(prefs::kPasswordHashDataList,
PrefRegistry::NO_REGISTRATION_FLAGS);
ASSERT_FALSE(prefs.HasPrefPath(prefs::kSyncPasswordHash));
store->Init(syncer::SyncableService::StartSyncFlare(), &prefs);
base::HistogramTester histogram_tester;
store->ReportMetrics("sync_username", false, true);
std::string name =
"PasswordManager.IsSyncPasswordHashSavedForAdvancedProtectionUser";
histogram_tester.ExpectBucketCount(
name, metrics_util::IsSyncPasswordHashSaved::NOT_SAVED, 1);
// Save password.
const base::string16 sync_password = base::ASCIIToUTF16("password");
const base::string16 input = base::ASCIIToUTF16("123password");
store->SaveGaiaPasswordHash(
"sync_username", sync_password,
metrics_util::SyncPasswordHashChange::SAVED_ON_CHROME_SIGNIN);
WaitForPasswordStore();
store->ReportMetrics("sync_username", false, true);
histogram_tester.ExpectBucketCount(
name, metrics_util::IsSyncPasswordHashSaved::SAVED_VIA_LIST_PREF, 1);
store->ShutdownOnUIThread();
}
#endif
} // namespace password_manager