blob: 724ea9ba210d906809e9c7a20c276b707153c7cd [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/callback_helpers.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_feature_list.h"
#include "base/test/task_environment.h"
#include "base/time/time.h"
#include "build/build_config.h"
#include "components/autofill/core/common/signatures.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/android_affiliation_service.h"
#include "components/password_manager/core/browser/android_affiliation/mock_affiliated_match_helper.h"
#include "components/password_manager/core/browser/compromised_credentials_consumer.h"
#include "components/password_manager/core/browser/form_parsing/form_parser.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.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_features.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 base::WaitableEvent;
using testing::_;
using testing::DoAll;
using testing::ElementsAre;
using testing::ElementsAreArray;
using testing::IsEmpty;
using testing::UnorderedElementsAre;
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 kTestUnrelatedWebRealm2[] = "https://notexample2.com/";
constexpr const char kTestUnrelatedWebOrigin2[] =
"https:/notexample2.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";
constexpr const time_t kTestLastUsageTime = 1546300800; // 00:00 Jan 1 2019 UTC
class MockCompromisedCredentialsConsumer
: public CompromisedCredentialsConsumer {
public:
MockCompromisedCredentialsConsumer() = default;
MOCK_METHOD1(OnGetCompromisedCredentials,
void(std::vector<CompromisedCredentials>));
private:
DISALLOW_COPY_AND_ASSIGN(MockCompromisedCredentialsConsumer);
};
class MockPasswordStoreConsumer : public PasswordStoreConsumer {
public:
MockPasswordStoreConsumer() = default;
MOCK_METHOD1(OnGetPasswordStoreResultsConstRef,
void(const std::vector<std::unique_ptr<PasswordForm>>&));
MOCK_METHOD1(OnGetAllFieldInfo, void(const std::vector<FieldInfo>));
// 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);
};
struct MockDatabaseCompromisedCredentialsObserver
: PasswordStore::DatabaseCompromisedCredentialsObserver {
MOCK_METHOD0(OnCompromisedCredentialsChanged, void());
};
class MockPasswordStoreSigninNotifier : public PasswordStoreSigninNotifier {
public:
MOCK_METHOD(void,
SubscribeToSigninEvents,
(PasswordStore * store),
(override));
MOCK_METHOD(void, UnsubscribeFromSigninEvents, (), (override));
};
} // namespace
class PasswordStoreTest : public testing::Test {
protected:
PasswordStoreTest() = default;
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();
feature_list_.InitAndEnableFeature(
features::kPasswordReuseDetectionEnabled);
}
void TearDown() override { OSCryptMocker::TearDown(); }
void WaitForPasswordStore() { task_environment_.RunUntilIdle(); }
base::FilePath test_login_db_file_path() const {
return temp_dir_.GetPath().Append(FILE_PATH_LITERAL("login_test"));
}
scoped_refptr<PasswordStoreDefault> CreatePasswordStore() {
return new PasswordStoreDefault(std::make_unique<LoginDatabase>(
test_login_db_file_path(), password_manager::IsAccountStore(false)));
}
private:
base::ScopedTempDir temp_dir_;
base::test::TaskEnvironment task_environment_{
base::test::TaskEnvironment::MainThreadType::UI};
base::test::ScopedFeatureList feature_list_;
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 = CreatePasswordStore();
store->Init(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::kHtml, "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"", kTestLastUsageTime, cutoff - 1},
// A form on https://www.google.com/ older than the cutoff. Will be
// ignored.
{PasswordForm::Scheme::kHtml, "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"", kTestLastUsageTime, cutoff - 1},
// A form on https://www.google.com/ newer than the cutoff.
{PasswordForm::Scheme::kHtml, "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"", kTestLastUsageTime, cutoff + 1},
// A form on https://accounts.google.com/ older than the cutoff.
{PasswordForm::Scheme::kHtml, "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"",
kTestLastUsageTime, cutoff - 1},
// A form on http://bar.example.com/ older than the cutoff.
{PasswordForm::Scheme::kHtml, "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"", kTestLastUsageTime, cutoff - 1},
};
// Build the forms vector and add the forms to the store.
std::vector<std::unique_ptr<PasswordForm>> all_forms;
for (const auto& data : form_data) {
all_forms.push_back(FillPasswordFormWithData(data));
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::kHtml, "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::kHtml, "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::kHtml, "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, UpdateLoginPrimaryKeyFields) {
/* clang-format off */
static const PasswordFormData kTestCredentials[] = {
// The old credential.
{PasswordForm::Scheme::kHtml,
kTestWebRealm1,
kTestWebOrigin1,
"", L"", L"username_element_1", L"password_element_1",
L"username_value_1",
L"", kTestLastUsageTime, 1},
// The new credential with different values for all primary key fields.
{PasswordForm::Scheme::kHtml,
kTestWebRealm2,
kTestWebOrigin2,
"", L"", L"username_element_2", L"password_element_2",
L"username_value_2",
L"", kTestLastUsageTime, 1}};
/* clang-format on */
scoped_refptr<PasswordStoreDefault> store = CreatePasswordStore();
store->Init(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.url = old_form->url;
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::kHtml,
kTestWebRealm1,
kTestWebOrigin1,
"", L"", L"username_element_1", L"password_element_1",
L"username_value_1",
L"", kTestLastUsageTime, 1};
/* clang-format on */
scoped_refptr<PasswordStoreDefault> store = CreatePasswordStore();
store->Init(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();
}
// Verify that when a login is removed that the corresponding row is also
// removed from the compromised credentials table.
TEST_F(PasswordStoreTest, CompromisedCredentialsObserverOnRemoveLogin) {
base::test::ScopedFeatureList feature_list;
feature_list.InitAndEnableFeature(password_manager::features::kPasswordCheck);
CompromisedCredentials compromised_credentials = {
kTestWebRealm1, base::ASCIIToUTF16("username_value_1"),
base::Time::FromTimeT(1), CompromiseType::kLeaked};
scoped_refptr<PasswordStoreDefault> store = CreatePasswordStore();
store->Init(nullptr);
store->AddCompromisedCredentials(compromised_credentials);
/* clang-format off */
static const PasswordFormData kTestCredential =
{PasswordForm::Scheme::kHtml,
kTestWebRealm1,
kTestWebOrigin1,
"", L"", L"username_element_1", L"password_element_1",
L"username_value_1",
L"", kTestLastUsageTime, 1};
/* clang-format on */
std::unique_ptr<PasswordForm> test_form(
FillPasswordFormWithData(kTestCredential));
store->AddLogin(*test_form);
WaitForPasswordStore();
MockCompromisedCredentialsConsumer consumer;
base::RunLoop run_loop;
store->RemoveLoginsCreatedBetween(base::Time::FromDoubleT(0),
base::Time::FromDoubleT(2),
run_loop.QuitClosure());
run_loop.Run();
EXPECT_CALL(consumer, OnGetCompromisedCredentials(testing::IsEmpty()));
store->GetAllCompromisedCredentials(&consumer);
WaitForPasswordStore();
store->ShutdownOnUIThread();
}
// Verify that when a login password is updated that the corresponding row is
// removed from the compromised credentials table.
TEST_F(PasswordStoreTest, CompromisedCredentialsObserverOnLoginUpdated) {
base::test::ScopedFeatureList feature_list;
feature_list.InitAndEnableFeature(password_manager::features::kPasswordCheck);
CompromisedCredentials compromised_credentials = {
kTestWebRealm1, base::ASCIIToUTF16("username_value_1"),
base::Time::FromTimeT(1), CompromiseType::kLeaked};
scoped_refptr<PasswordStoreDefault> store = CreatePasswordStore();
store->Init(nullptr);
store->AddCompromisedCredentials(compromised_credentials);
/* clang-format off */
PasswordFormData kTestCredential =
{PasswordForm::Scheme::kHtml,
kTestWebRealm1,
kTestWebOrigin1,
"", L"", L"username_element_1", L"password_element_1",
L"username_value_1",
L"password_value_1", kTestLastUsageTime, 1};
/* clang-format on */
std::unique_ptr<PasswordForm> test_form(
FillPasswordFormWithData(kTestCredential));
store->AddLogin(*test_form);
WaitForPasswordStore();
MockCompromisedCredentialsConsumer consumer;
kTestCredential.password_value = L"password_value_2";
std::unique_ptr<PasswordForm> test_form_2(
FillPasswordFormWithData(kTestCredential));
store->UpdateLogin(*test_form_2);
WaitForPasswordStore();
EXPECT_CALL(consumer, OnGetCompromisedCredentials(testing::IsEmpty()));
store->GetAllCompromisedCredentials(&consumer);
WaitForPasswordStore();
store->ShutdownOnUIThread();
}
// Verify that when a login password is added with the password changed that the
// corresponding row is removed from the compromised credentials table.
TEST_F(PasswordStoreTest, CompromisedCredentialsObserverOnLoginAdded) {
base::test::ScopedFeatureList feature_list;
feature_list.InitAndEnableFeature(password_manager::features::kPasswordCheck);
CompromisedCredentials compromised_credentials = {
kTestWebRealm1, base::ASCIIToUTF16("username_value_1"),
base::Time::FromTimeT(1), CompromiseType::kLeaked};
scoped_refptr<PasswordStoreDefault> store = CreatePasswordStore();
store->Init(nullptr);
store->AddCompromisedCredentials(compromised_credentials);
/* clang-format off */
PasswordFormData kTestCredential =
{PasswordForm::Scheme::kHtml,
kTestWebRealm1,
kTestWebOrigin1,
"", L"", L"username_element_1", L"password_element_1",
L"username_value_1",
L"password_value_1", kTestLastUsageTime, 1};
/* clang-format on */
std::unique_ptr<PasswordForm> test_form(
FillPasswordFormWithData(kTestCredential));
store->AddLogin(*test_form);
WaitForPasswordStore();
MockCompromisedCredentialsConsumer consumer;
kTestCredential.password_value = L"password_value_2";
std::unique_ptr<PasswordForm> test_form_2(
FillPasswordFormWithData(kTestCredential));
store->AddLogin(*test_form_2);
WaitForPasswordStore();
EXPECT_CALL(consumer, OnGetCompromisedCredentials(testing::IsEmpty()));
store->GetAllCompromisedCredentials(&consumer);
WaitForPasswordStore();
store->ShutdownOnUIThread();
}
TEST_F(PasswordStoreTest,
CompromisedPasswordObserverOnCompromisedCredentialAdded) {
base::test::ScopedFeatureList feature_list;
feature_list.InitAndEnableFeature(features::kPasswordCheck);
MockDatabaseCompromisedCredentialsObserver observer;
CompromisedCredentials compromised_credentials = {
kTestWebRealm1, base::ASCIIToUTF16("username_value_1"),
base::Time::FromTimeT(1), CompromiseType::kLeaked};
scoped_refptr<PasswordStoreDefault> store = CreatePasswordStore();
store->Init(nullptr);
store->AddDatabaseCompromisedCredentialsObserver(&observer);
// Expect a notification after adding a credential.
EXPECT_CALL(observer, OnCompromisedCredentialsChanged);
store->AddCompromisedCredentials(compromised_credentials);
WaitForPasswordStore();
// Adding the same credential should not result in another notification.
EXPECT_CALL(observer, OnCompromisedCredentialsChanged).Times(0);
store->AddCompromisedCredentials(compromised_credentials);
WaitForPasswordStore();
store->ShutdownOnUIThread();
}
TEST_F(PasswordStoreTest,
CompromisedPasswordObserverOnCompromisedCredentialRemoved) {
base::test::ScopedFeatureList feature_list;
feature_list.InitAndEnableFeature(features::kPasswordCheck);
MockDatabaseCompromisedCredentialsObserver observer;
CompromisedCredentials compromised_credentials = {
kTestWebRealm1, base::ASCIIToUTF16("username_value_1"),
base::Time::FromTimeT(1), CompromiseType::kLeaked};
scoped_refptr<PasswordStoreDefault> store = CreatePasswordStore();
store->Init(nullptr);
store->AddCompromisedCredentials(compromised_credentials);
WaitForPasswordStore();
store->AddDatabaseCompromisedCredentialsObserver(&observer);
// Expect a notification after removing a credential.
EXPECT_CALL(observer, OnCompromisedCredentialsChanged);
store->RemoveCompromisedCredentials(
compromised_credentials.signon_realm, compromised_credentials.username,
RemoveCompromisedCredentialsReason::kRemove);
WaitForPasswordStore();
// Removing the same credential should not result in another notification.
EXPECT_CALL(observer, OnCompromisedCredentialsChanged).Times(0);
store->RemoveCompromisedCredentials(
compromised_credentials.signon_realm, compromised_credentials.username,
RemoveCompromisedCredentialsReason::kRemove);
WaitForPasswordStore();
store->ShutdownOnUIThread();
}
TEST_F(PasswordStoreTest,
CompromisedPasswordObserverOnCompromisedCredentialRemovedByTime) {
base::test::ScopedFeatureList feature_list;
feature_list.InitAndEnableFeature(features::kPasswordCheck);
MockDatabaseCompromisedCredentialsObserver observer;
CompromisedCredentials compromised_credentials = {
kTestWebRealm1, base::ASCIIToUTF16("username_value_1"),
base::Time::FromTimeT(1), CompromiseType::kLeaked};
scoped_refptr<PasswordStoreDefault> store = CreatePasswordStore();
store->Init(nullptr);
store->AddCompromisedCredentials(compromised_credentials);
WaitForPasswordStore();
store->AddDatabaseCompromisedCredentialsObserver(&observer);
// Expect a notification after removing all credential.
EXPECT_CALL(observer, OnCompromisedCredentialsChanged);
store->RemoveCompromisedCredentialsByUrlAndTime(
base::NullCallback(), base::Time::Min(), base::Time::Max(),
base::NullCallback());
WaitForPasswordStore();
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::kHtml,
kTestWebRealm1,
kTestWebOrigin1,
"", L"", L"", L"",
L"username_value_1",
L"", kTestLastUsageTime, 1},
// Credential that is a PSL match of the observed form.
{PasswordForm::Scheme::kHtml,
kTestPSLMatchingWebRealm,
kTestPSLMatchingWebOrigin,
"", L"", L"", L"",
L"username_value_2",
L"", kTestLastUsageTime, 1},
// Credential for an unrelated Android application.
{PasswordForm::Scheme::kHtml,
kTestUnrelatedAndroidRealm,
"", "", L"", L"", L"",
L"username_value_3",
L"", kTestLastUsageTime, 1}};
/* clang-format on */
scoped_refptr<PasswordStoreDefault> store = CreatePasswordStore();
store->Init(nullptr);
std::vector<std::unique_ptr<PasswordForm>> all_credentials;
for (const auto& credential : kTestCredentials) {
all_credentials.push_back(FillPasswordFormWithData(credential));
store->AddLogin(*all_credentials.back());
}
PasswordStore::FormDigest observed_form = {
PasswordForm::Scheme::kHtml, 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::kHtml, kTestWebRealm1, kTestWebOrigin1, "",
L"", L"", L"", L"username_value_1", L"", kTestLastUsageTime, 1},
false,
},
// Credential that is a PSL match of the observed form.
{
{PasswordForm::Scheme::kHtml, 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::kHtml, kTestAndroidRealm1, "", "", L"", L"",
L"", L"username_value_3", L"", kTestLastUsageTime, 1},
false,
},
// Second credential for the same Android application.
{
{PasswordForm::Scheme::kHtml, kTestAndroidRealm1, "", "", L"", L"",
L"", L"username_value_3b", L"", kTestLastUsageTime, 1},
false,
},
// Third credential for the same application which is username-only.
{
{PasswordForm::Scheme::kUsernameOnly, kTestAndroidRealm1, "", "", L"",
L"", L"", L"username_value_3c", L"", kTestLastUsageTime, 1},
false,
},
// Credential for another Android application affiliated with the realm
// of the observed from.
{
{PasswordForm::Scheme::kHtml, kTestAndroidRealm2, "", "", L"", L"",
L"", L"username_value_4", L"", kTestLastUsageTime, 1},
false,
},
// Federated credential for this second Android application.
{
{PasswordForm::Scheme::kHtml, kTestAndroidRealm2, "", "", L"", L"",
L"", L"username_value_4b", L"", kTestLastUsageTime, 1},
true,
},
// Credential for an unrelated Android application.
{
{PasswordForm::Scheme::kHtml, kTestUnrelatedAndroidRealm, "", "", L"",
L"", L"", L"username_value_5", L"", kTestLastUsageTime, 1},
false,
}};
scoped_refptr<PasswordStoreDefault> store = CreatePasswordStore();
store->Init(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::kHtml, 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::kHtml,
kTestAndroidRealm1,
"", "", L"", L"", L"",
kTestUsername,
kTestOldPassword, kTestLastUsageTime, 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::kHtml,
kTestWebRealm1,
kTestWebOrigin1,
"", L"", L"", L"",
kTestUsername,
kTestOldPassword, kTestLastUsageTime, 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::kHtml,
kTestWebRealm2,
kTestWebOrigin2,
"", L"", L"", L"",
kTestUsername,
kTestOtherPassword,kTestLastUsageTime, 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::kHtml,
kTestWebRealm3,
kTestWebOrigin3,
"", L"", L"", L"",
kTestUsername,
kTestNewPassword, kTestLastUsageTime, 1},
// Credential for the HTTP version of an affiliated web site.
{PasswordForm::Scheme::kHtml,
kTestInsecureWebRealm,
kTestInsecureWebOrigin,
"", L"", L"", L"",
kTestUsername,
kTestOldPassword, kTestLastUsageTime, 1},
// Credential for an affiliated web site, but with a different username.
{PasswordForm::Scheme::kHtml,
kTestWebRealm1,
kTestWebOrigin1,
"", L"", L"", L"",
kTestOtherUsername,
kTestOldPassword, kTestLastUsageTime, 1},
// Credential for a web site that is a PSL match to a web sites affiliated
// with the Android application.
{PasswordForm::Scheme::kHtml,
kTestPSLMatchingWebRealm,
kTestPSLMatchingWebOrigin,
"poisoned", L"poisoned", L"", L"",
kTestUsername,
kTestOldPassword, kTestLastUsageTime, 1},
// Credential for an unrelated web site.
{PasswordForm::Scheme::kHtml,
kTestUnrelatedWebRealm,
kTestUnrelatedWebOrigin,
"", L"", L"", L"",
kTestUsername,
kTestOldPassword, kTestLastUsageTime, 1},
// Credential for an affiliated Android application.
{PasswordForm::Scheme::kHtml,
kTestAndroidRealm2,
"", "", L"", L"", L"",
kTestUsername,
kTestOldPassword, kTestLastUsageTime, 1},
// Credential for an unrelated Android application.
{PasswordForm::Scheme::kHtml,
kTestUnrelatedAndroidRealm,
"", "", L"", L"", L"",
kTestUsername,
kTestOldPassword, kTestLastUsageTime, 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::kHtml,
kTestWebRealm5,
kTestWebOrigin5,
"", L"", L"", L"",
kTestUsername,
kTestOtherPassword, kTestLastUsageTime, 2}};
/* clang-format on */
// The number of positive samples in |kTestCredentials|.
const size_t kExpectedNumberOfPropagatedUpdates = 2u;
const bool kFalseTrue[] = {false, true};
for (bool test_remove_and_add_login : kFalseTrue) {
SCOPED_TRACE(testing::Message("test_remove_and_add_login: ")
<< test_remove_and_add_login);
scoped_refptr<PasswordStoreDefault> store = CreatePasswordStore();
store->Init(nullptr);
store->RemoveLoginsCreatedBetween(base::Time(), base::Time::Max(),
base::NullCallback());
// Set up the initial test data set.
std::vector<std::unique_ptr<PasswordForm>> all_credentials;
for (const auto& credential : kTestCredentials) {
all_credentials.push_back(FillPasswordFormWithData(credential));
all_credentials.back()->date_synced =
all_credentials.back()->date_created;
store->AddLogin(*all_credentials.back());
}
WaitForPasswordStore();
// Calculate how the correctly updated test data set should look like.
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 + kExpectedNumberOfPropagatedUpdates) {
expected_credentials_after_update.back()->password_value =
base::WideToUTF16(kTestNewPassword);
}
}
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);
EXPECT_CALL(
mock_observer,
OnLoginsChanged(testing::SizeIs(kExpectedNumberOfPropagatedUpdates)));
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], /*error=*/nullptr));
} else {
store->ScheduleTask(base::BindOnce(
IgnoreResult(&PasswordStore::UpdateLoginSync), store,
*expected_credentials_after_update[0], /*error=*/nullptr));
}
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, GetAllLogins) {
static constexpr PasswordFormData kTestCredentials[] = {
{PasswordForm::Scheme::kHtml, kTestAndroidRealm1, "", "", L"", L"", L"",
L"username_value_1", L"", kTestLastUsageTime, 1},
{PasswordForm::Scheme::kHtml, kTestAndroidRealm2, "", "", L"", L"", L"",
L"username_value_2", L"", kTestLastUsageTime, 1},
{PasswordForm::Scheme::kHtml, kTestAndroidRealm3, "", "", L"", L"", L"",
L"username_value_3", L"", kTestLastUsageTime, 1},
{PasswordForm::Scheme::kHtml, kTestWebRealm1, kTestWebOrigin1, "", L"",
L"", L"", L"username_value_4", L"", kTestLastUsageTime, 1},
// A PasswordFormData with nullptr as the username_value will be converted
// in a blacklisted PasswordForm in FillPasswordFormWithData().
{PasswordForm::Scheme::kHtml, kTestWebRealm2, kTestWebOrigin2, "", L"",
L"", L"", nullptr, L"", kTestLastUsageTime, 1},
{PasswordForm::Scheme::kHtml, kTestWebRealm3, kTestWebOrigin3, "", L"",
L"", L"", nullptr, L"", kTestLastUsageTime, 1}};
scoped_refptr<PasswordStoreDefault> store = CreatePasswordStore();
store->Init(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));
EXPECT_CALL(mock_consumer,
OnGetPasswordStoreResultsConstRef(
UnorderedPasswordFormElementsAre(&expected_results)));
store->GetAllLogins(&mock_consumer);
WaitForPasswordStore();
store->ShutdownOnUIThread();
}
// Tests if all credentials in the store with a specific password are
// successfully transferred to the consumer.
TEST_F(PasswordStoreTest, GetLogisByPassword) {
static constexpr wchar_t tested_password[] = L"duplicated_password";
static constexpr wchar_t another_tested_password[] = L"some_other_password";
static constexpr wchar_t untested_password[] = L"and_another_password";
// The first, third and forth credentials use the same password, but the forth
// is blacklisted.
static constexpr PasswordFormData kTestCredentials[] = {
// Has the specified password:
{PasswordForm::Scheme::kHtml, kTestAndroidRealm1, "", "", L"", L"", L"",
L"username_value_1", tested_password, kTestLastUsageTime, 1},
// Has another password:
{PasswordForm::Scheme::kHtml, kTestAndroidRealm2, "", "", L"", L"", L"",
L"username_value_2", another_tested_password, kTestLastUsageTime, 1},
// Has the specified password:
{PasswordForm::Scheme::kHtml, kTestAndroidRealm3, "", "", L"", L"", L"",
L"username_value_3", tested_password, kTestLastUsageTime, 1},
// Has a third password:
{PasswordForm::Scheme::kHtml, kTestWebRealm1, kTestWebOrigin1, "", L"",
L"", L"", L"username_value_4", untested_password, kTestLastUsageTime, 1},
// A PasswordFormData with nullptr as the username_value will be converted
// in a blacklisted PasswordForm in FillPasswordFormWithData().
// Has the specified password, but is blacklisted.
{PasswordForm::Scheme::kHtml, kTestWebRealm3, kTestWebOrigin3, "", L"",
L"", L"", nullptr, tested_password, kTestLastUsageTime, 1}};
scoped_refptr<PasswordStoreDefault> store = CreatePasswordStore();
store->Init(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;
expected_results.push_back(
std::make_unique<PasswordForm>(*all_credentials[0]));
expected_results.push_back(
std::make_unique<PasswordForm>(*all_credentials[2]));
EXPECT_CALL(mock_consumer,
OnGetPasswordStoreResultsConstRef(
UnorderedPasswordFormElementsAre(&expected_results)));
store->GetLoginsByPassword(base::WideToUTF16(tested_password),
&mock_consumer);
WaitForPasswordStore();
// Tries to find all credentials with |another_tested_password|.
expected_results.clear();
expected_results.push_back(
std::make_unique<PasswordForm>(*all_credentials[1]));
EXPECT_CALL(mock_consumer,
OnGetPasswordStoreResultsConstRef(
UnorderedPasswordFormElementsAre(&expected_results)));
store->GetLoginsByPassword(base::WideToUTF16(another_tested_password),
&mock_consumer);
WaitForPasswordStore();
store->ShutdownOnUIThread();
}
TEST_F(PasswordStoreTest, GetAllLoginsWithAffiliationAndBrandingInformation) {
static constexpr PasswordFormData kTestCredentials[] = {
{PasswordForm::Scheme::kHtml, kTestAndroidRealm1, "", "", L"", L"", L"",
L"username_value_1", L"", kTestLastUsageTime, 1},
{PasswordForm::Scheme::kHtml, kTestAndroidRealm2, "", "", L"", L"", L"",
L"username_value_2", L"", kTestLastUsageTime, 1},
{PasswordForm::Scheme::kHtml, kTestAndroidRealm3, "", "", L"", L"", L"",
L"username_value_3", L"", kTestLastUsageTime, 1},
{PasswordForm::Scheme::kHtml, kTestWebRealm1, kTestWebOrigin1, "", L"",
L"", L"", L"username_value_4", L"", kTestLastUsageTime, 1},
// A PasswordFormData with nullptr as the username_value will be converted
// in a blacklisted PasswordForm in FillPasswordFormWithData().
{PasswordForm::Scheme::kHtml, kTestWebRealm2, kTestWebOrigin2, "", L"",
L"", L"", nullptr, L"", kTestLastUsageTime, 1},
{PasswordForm::Scheme::kHtml, kTestWebRealm3, kTestWebOrigin3, "", L"",
L"", L"", nullptr, L"", kTestLastUsageTime, 1}};
scoped_refptr<PasswordStoreDefault> store = CreatePasswordStore();
store->Init(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, Unblacklisting) {
static const PasswordFormData kTestCredentials[] = {
// A PasswordFormData with nullptr as the username_value will be converted
// in a blacklisted PasswordForm in FillPasswordFormWithData().
// Blacklisted entry for the observed domain.
{PasswordForm::Scheme::kHtml, kTestWebRealm1, kTestWebOrigin1, "", L"",
L"", L"", nullptr, L"", kTestLastUsageTime, 1},
// Blacklisted entry for a PSL match of the observed form.
{PasswordForm::Scheme::kHtml, kTestPSLMatchingWebRealm,
kTestPSLMatchingWebOrigin, "", L"", L"", L"", nullptr, L"",
kTestLastUsageTime, 1},
// Blacklisted entry for another domain
{PasswordForm::Scheme::kHtml, kTestUnrelatedWebRealm,
kTestUnrelatedWebOrigin, "", L"", L"", L"", nullptr, L"",
kTestLastUsageTime, 1},
// Non-blacklisted for the observed domain with a username.
{PasswordForm::Scheme::kHtml, kTestWebRealm1, kTestWebOrigin1, "", L"",
L"", L"", L"username", L"", kTestLastUsageTime, 1},
// Non-blacklisted for the observed domain without a username.
{PasswordForm::Scheme::kHtml, kTestWebRealm1, kTestWebOrigin1, "", L"",
L"", L"username_element", L"", L"", kTestLastUsageTime, 1},
// Non-blacklisted entry for a PSL match of the observed form.
{PasswordForm::Scheme::kHtml, kTestPSLMatchingWebRealm,
kTestPSLMatchingWebOrigin, "", L"", L"", L"", L"username", L"",
kTestLastUsageTime, 1},
// Non-blacklisted entry for another domain
{PasswordForm::Scheme::kHtml, kTestUnrelatedWebRealm2,
kTestUnrelatedWebOrigin2, "", L"", L"", L"", L"username", L"",
kTestLastUsageTime, 1}};
scoped_refptr<PasswordStoreDefault> store = CreatePasswordStore();
store->Init(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());
}
WaitForPasswordStore();
MockPasswordStoreObserver mock_observer;
store->AddObserver(&mock_observer);
// Only the related non-PSL match should be deleted.
EXPECT_CALL(mock_observer, OnLoginsChanged(testing::SizeIs(1u)));
base::RunLoop run_loop;
PasswordStore::FormDigest observed_form_digest = {
PasswordForm::Scheme::kHtml, kTestWebRealm1, GURL(kTestWebOrigin1)};
store->Unblacklist(observed_form_digest, run_loop.QuitClosure());
run_loop.Run();
testing::Mock::VerifyAndClearExpectations(&mock_observer);
// Unblacklisting will delete only the first credential. It should leave the
// PSL match as well as the unrelated blacklisting entry and all
// non-blacklisting entries.
all_credentials.erase(all_credentials.begin());
MockPasswordStoreConsumer mock_consumer;
EXPECT_CALL(mock_consumer,
OnGetPasswordStoreResultsConstRef(
UnorderedPasswordFormElementsAre(&all_credentials)));
store->GetAllLogins(&mock_consumer);
WaitForPasswordStore();
store->ShutdownOnUIThread();
}
TEST_F(PasswordStoreTest, CheckPasswordReuse) {
static constexpr PasswordFormData kTestCredentials[] = {
{PasswordForm::Scheme::kHtml, "https://www.google.com",
"https://www.google.com", "", L"", L"", L"", L"username1", L"password",
kTestLastUsageTime, 1},
{PasswordForm::Scheme::kHtml, "https://facebook.com",
"https://facebook.com", "", L"", L"", L"", L"username2", L"topsecret",
kTestLastUsageTime, 1}};
scoped_refptr<PasswordStoreDefault> store = CreatePasswordStore();
store->Init(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) {
const std::vector<MatchingReusedCredential> credentials = {
{"https://www.google.com", base::ASCIIToUTF16("username1"),
PasswordForm::Store::kProfileStore}};
EXPECT_CALL(mock_consumer,
OnReuseCheckDone(true, test_data.reused_password_len,
Matches(base::nullopt),
ElementsAreArray(credentials), 2));
} else {
EXPECT_CALL(mock_consumer, OnReuseCheckDone(false, _, _, _, _));
}
store->CheckReuse(base::WideToUTF16(test_data.input), test_data.domain,
&mock_consumer);
WaitForPasswordStore();
}
store->ShutdownOnUIThread();
}
TEST_F(PasswordStoreTest, SavingClearingProtectedPassword) {
scoped_refptr<PasswordStoreDefault> store = CreatePasswordStore();
TestingPrefServiceSimple prefs;
prefs.registry()->RegisterListPref(prefs::kPasswordHashDataList,
PrefRegistry::NO_REGISTRATION_FLAGS);
ASSERT_FALSE(prefs.HasPrefPath(prefs::kSyncPasswordHash));
store->Init(&prefs);
const base::string16 sync_password = base::ASCIIToUTF16("password");
const base::string16 input = base::ASCIIToUTF16("123password");
store->SaveGaiaPasswordHash(
"sync_username", sync_password,
/*is_primary_account=*/true,
metrics_util::GaiaPasswordHashChange::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,
OnReuseCheckDone(true, sync_password.size(),
Matches(sync_password_hash), IsEmpty(), 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,
/*is_primary_account=*/false,
GaiaPasswordHashChange::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,
OnReuseCheckDone(true, gaia_password.size(),
Matches(gaia_password_hash), IsEmpty(), 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, OnReuseCheckDone(true, _, _, _, _)).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, OnReuseCheckDone(false, _, _, _, _));
store->CheckReuse(input, "https://example.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, OnReuseCheckDone(true, enterprise_password.size(),
Matches(enterprise_password_hash),
IsEmpty(), 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, OnReuseCheckDone(false, _, _, _, _));
store->CheckReuse(input, "https://example.com", &mock_consumer);
WaitForPasswordStore();
testing::Mock::VerifyAndClearExpectations(&mock_consumer);
// Save a Gmail password this time.
const base::string16 gmail_password = base::ASCIIToUTF16("gmailpass");
store->SaveGaiaPasswordHash("username@gmail.com", gmail_password,
/*is_primary_account=*/false,
GaiaPasswordHashChange::NOT_SYNC_PASSWORD_CHANGE);
WaitForPasswordStore();
EXPECT_TRUE(prefs.HasPrefPath(prefs::kPasswordHashDataList));
base::Optional<PasswordHashData> gmail_password_hash = GetPasswordFromPref(
"username@gmail.com", /*is_gaia_password=*/true, &prefs);
ASSERT_TRUE(gmail_password_hash.has_value());
// Check that gmail password reuse is found.
EXPECT_CALL(mock_consumer,
OnReuseCheckDone(true, gmail_password.size(),
Matches(gmail_password_hash), IsEmpty(), 0));
store->CheckReuse(gmail_password, "https://example.com", &mock_consumer);
WaitForPasswordStore();
testing::Mock::VerifyAndClearExpectations(&mock_consumer);
// Also save another non-sync Gaia password this time.
const base::string16 non_sync_gaia_password = base::ASCIIToUTF16("3password");
store->SaveGaiaPasswordHash("non_sync_gaia_password@gsuite.com",
non_sync_gaia_password,
/*is_primary_account=*/false,
GaiaPasswordHashChange::NOT_SYNC_PASSWORD_CHANGE);
base::Optional<PasswordHashData> non_sync_gaia_password_hash =
GetPasswordFromPref("non_sync_gaia_password@gsuite.com",
/*is_gaia_password=*/true, &prefs);
ASSERT_TRUE(non_sync_gaia_password_hash.has_value());
EXPECT_EQ(2u, prefs.GetList(prefs::kPasswordHashDataList)->GetList().size());
// Check that no non-gmail password reuse is found after clearing the
// password hash.
store->ClearAllNonGmailPasswordHash();
EXPECT_EQ(1u, prefs.GetList(prefs::kPasswordHashDataList)->GetList().size());
EXPECT_CALL(mock_consumer, OnReuseCheckDone(false, _, _, _, _));
store->CheckReuse(non_sync_gaia_password, "https://example.com",
&mock_consumer);
WaitForPasswordStore();
testing::Mock::VerifyAndClearExpectations(&mock_consumer);
EXPECT_CALL(mock_consumer,
OnReuseCheckDone(true, gmail_password.size(),
Matches(gmail_password_hash), IsEmpty(), 0))
.Times(1);
store->CheckReuse(gmail_password, "https://example.com", &mock_consumer);
WaitForPasswordStore();
testing::Mock::VerifyAndClearExpectations(&mock_consumer);
store->ShutdownOnUIThread();
}
TEST_F(PasswordStoreTest, SubscriptionAndUnsubscriptionFromSignInEvents) {
scoped_refptr<PasswordStoreDefault> store = CreatePasswordStore();
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 = CreatePasswordStore();
TestingPrefServiceSimple prefs;
prefs.registry()->RegisterListPref(prefs::kPasswordHashDataList,
PrefRegistry::NO_REGISTRATION_FLAGS);
ASSERT_FALSE(prefs.HasPrefPath(prefs::kSyncPasswordHash));
store->Init(&prefs);
// Hash does not exist yet.
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,
/*is_primary_account=*/true,
GaiaPasswordHashChange::SAVED_ON_CHROME_SIGNIN);
WaitForPasswordStore();
store->ReportMetrics("sync_username", false, true);
histogram_tester.ExpectBucketCount(
name, metrics_util::IsSyncPasswordHashSaved::SAVED_VIA_LIST_PREF, 1);
histogram_tester.ExpectBucketCount(
"PasswordManager.SyncPasswordHashChange",
GaiaPasswordHashChange::SAVED_ON_CHROME_SIGNIN, 1);
store->ShutdownOnUIThread();
}
TEST_F(PasswordStoreTest, ReportMetricsForNonSyncPassword) {
scoped_refptr<PasswordStoreDefault> store = CreatePasswordStore();
TestingPrefServiceSimple prefs;
prefs.registry()->RegisterListPref(prefs::kPasswordHashDataList,
PrefRegistry::NO_REGISTRATION_FLAGS);
ASSERT_FALSE(prefs.HasPrefPath(prefs::kSyncPasswordHash));
store->Init(&prefs);
// Hash does not exist yet.
base::HistogramTester histogram_tester;
store->ReportMetrics("not_sync_username",
/*custom_passphrase_sync_enabled=*/false,
/*is_under_advanced_protection=*/true);
std::string name =
"PasswordManager.IsSyncPasswordHashSavedForAdvancedProtectionUser";
histogram_tester.ExpectBucketCount(
name, metrics_util::IsSyncPasswordHashSaved::NOT_SAVED, 1);
// Save password.
const base::string16 not_sync_password = base::ASCIIToUTF16("password");
const base::string16 input = base::ASCIIToUTF16("123password");
store->SaveGaiaPasswordHash("not_sync_username", not_sync_password,
/*is_primary_account=*/false,
GaiaPasswordHashChange::NOT_SYNC_PASSWORD_CHANGE);
WaitForPasswordStore();
store->ReportMetrics("not_sync_username",
/*custom_passphrase_sync_enabled=*/false,
/*is_under_advanced_protection=*/true);
// Check that the non sync hash password was saved.
histogram_tester.ExpectBucketCount(
name, metrics_util::IsSyncPasswordHashSaved::SAVED_VIA_LIST_PREF, 1);
histogram_tester.ExpectBucketCount(
"PasswordManager.NonSyncPasswordHashChange",
GaiaPasswordHashChange::NOT_SYNC_PASSWORD_CHANGE, 1);
store->ShutdownOnUIThread();
}
TEST_F(PasswordStoreTest, GetAllCompromisedCredentials) {
base::test::ScopedFeatureList feature_list;
feature_list.InitAndEnableFeature(password_manager::features::kPasswordCheck);
CompromisedCredentials compromised_credentials = {
"https://example.com/", base::ASCIIToUTF16("username"),
base::Time::FromTimeT(1), CompromiseType::kLeaked};
CompromisedCredentials compromised_credentials2 = {
"https://2.example.com/", base::ASCIIToUTF16("username2"),
base::Time::FromTimeT(2), CompromiseType::kLeaked};
scoped_refptr<PasswordStoreDefault> store = CreatePasswordStore();
store->Init(nullptr);
store->AddCompromisedCredentials(compromised_credentials);
store->AddCompromisedCredentials(compromised_credentials2);
MockCompromisedCredentialsConsumer consumer;
EXPECT_CALL(consumer,
OnGetCompromisedCredentials(UnorderedElementsAre(
compromised_credentials, compromised_credentials2)));
store->GetAllCompromisedCredentials(&consumer);
WaitForPasswordStore();
testing::Mock::VerifyAndClearExpectations(&consumer);
store->RemoveCompromisedCredentials(
compromised_credentials.signon_realm, compromised_credentials.username,
RemoveCompromisedCredentialsReason::kRemove);
EXPECT_CALL(consumer, OnGetCompromisedCredentials(
UnorderedElementsAre(compromised_credentials2)));
store->GetAllCompromisedCredentials(&consumer);
WaitForPasswordStore();
store->ShutdownOnUIThread();
}
// Test GetMatchingCompromisedCredentials when affiliation service isn't
// available.
TEST_F(PasswordStoreTest, GetMatchingCompromisedWithoutAffiliations) {
base::test::ScopedFeatureList feature_list;
feature_list.InitAndEnableFeature(password_manager::features::kPasswordCheck);
scoped_refptr<PasswordStoreDefault> store = CreatePasswordStore();
store->Init(nullptr);
CompromisedCredentials credentials1 = {
kTestWebRealm1, base::ASCIIToUTF16("username_value"),
base::Time::FromTimeT(1), CompromiseType::kLeaked};
CompromisedCredentials credentials2 = {
kTestWebRealm2, base::ASCIIToUTF16("username_value"),
base::Time::FromTimeT(2), CompromiseType::kLeaked};
for (const auto& credentials : {credentials1, credentials2})
store->AddCompromisedCredentials(credentials);
MockCompromisedCredentialsConsumer consumer;
EXPECT_CALL(consumer,
OnGetCompromisedCredentials(UnorderedElementsAre(credentials1)));
store->GetMatchingCompromisedCredentials(kTestWebRealm1, &consumer);
WaitForPasswordStore();
store->ShutdownOnUIThread();
}
// Test GetMatchingCompromisedCredentials with some matching Android
// credentials.
TEST_F(PasswordStoreTest, GetMatchingCompromisedWithAffiliations) {
base::test::ScopedFeatureList feature_list;
feature_list.InitAndEnableFeature(password_manager::features::kPasswordCheck);
scoped_refptr<PasswordStoreDefault> store = CreatePasswordStore();
store->Init(nullptr);
CompromisedCredentials credentials1 = {
kTestWebRealm1, base::ASCIIToUTF16("username_value"),
base::Time::FromTimeT(1), CompromiseType::kLeaked};
CompromisedCredentials credentials2 = {
kTestAndroidRealm1, base::ASCIIToUTF16("username_value_1"),
base::Time::FromTimeT(2), CompromiseType::kPhished};
CompromisedCredentials credentials3 = {
kTestWebRealm2, base::ASCIIToUTF16("username_value_2"),
base::Time::FromTimeT(3), CompromiseType::kLeaked};
for (const auto& credentials : {credentials1, credentials2, credentials3})
store->AddCompromisedCredentials(credentials);
PasswordStore::FormDigest observed_form = {
PasswordForm::Scheme::kHtml, kTestWebRealm1, GURL(kTestWebRealm1)};
std::vector<std::string> affiliated_android_realms = {kTestAndroidRealm1};
auto mock_helper = std::make_unique<MockAffiliatedMatchHelper>();
mock_helper->ExpectCallToGetAffiliatedAndroidRealms(
observed_form, affiliated_android_realms);
store->SetAffiliatedMatchHelper(std::move(mock_helper));
MockCompromisedCredentialsConsumer consumer;
EXPECT_CALL(consumer, OnGetCompromisedCredentials(
UnorderedElementsAre(credentials1, credentials2)));
store->GetMatchingCompromisedCredentials(kTestWebRealm1, &consumer);
WaitForPasswordStore();
store->ShutdownOnUIThread();
}
TEST_F(PasswordStoreTest, RemovePhishedCredentialsByCompromiseType) {
base::test::ScopedFeatureList feature_list;
feature_list.InitAndEnableFeature(password_manager::features::kPasswordCheck);
CompromisedCredentials leaked_credentials = {
"https://example.com/", base::ASCIIToUTF16("username"),
base::Time::FromTimeT(100), CompromiseType::kLeaked};
CompromisedCredentials phished_credentials = {
"https://example.com/", base::ASCIIToUTF16("username"),
base::Time::FromTimeT(200), CompromiseType::kPhished};
scoped_refptr<PasswordStoreDefault> store = CreatePasswordStore();
store->Init(nullptr);
store->AddCompromisedCredentials(leaked_credentials);
store->AddCompromisedCredentials(phished_credentials);
MockCompromisedCredentialsConsumer consumer;
EXPECT_CALL(consumer, OnGetCompromisedCredentials(UnorderedElementsAre(
leaked_credentials, phished_credentials)));
store->GetAllCompromisedCredentials(&consumer);
WaitForPasswordStore();
testing::Mock::VerifyAndClearExpectations(&consumer);
store->RemoveCompromisedCredentialsByCompromiseType(
"https://example.com/", base::ASCIIToUTF16("username"),
CompromiseType::kPhished,
RemoveCompromisedCredentialsReason::kMarkSiteAsLegitimate);
WaitForPasswordStore();
EXPECT_CALL(consumer,
OnGetCompromisedCredentials(ElementsAre(leaked_credentials)));
store->GetAllCompromisedCredentials(&consumer);
WaitForPasswordStore();
store->ShutdownOnUIThread();
}
TEST_F(PasswordStoreTest, RemoveLeakedCredentialsByCompromiseType) {
base::test::ScopedFeatureList feature_list;
feature_list.InitAndEnableFeature(password_manager::features::kPasswordCheck);
CompromisedCredentials leaked_credentials = {
"https://example.com/", base::ASCIIToUTF16("username"),
base::Time::FromTimeT(100), CompromiseType::kLeaked};
CompromisedCredentials phished_credentials = {
"https://example.com/", base::ASCIIToUTF16("username"),
base::Time::FromTimeT(200), CompromiseType::kPhished};
scoped_refptr<PasswordStoreDefault> store = CreatePasswordStore();
store->Init(nullptr);
store->AddCompromisedCredentials(leaked_credentials);
store->AddCompromisedCredentials(phished_credentials);
MockCompromisedCredentialsConsumer consumer;
EXPECT_CALL(consumer, OnGetCompromisedCredentials(UnorderedElementsAre(
leaked_credentials, phished_credentials)));
store->GetAllCompromisedCredentials(&consumer);
WaitForPasswordStore();
testing::Mock::VerifyAndClearExpectations(&consumer);
store->RemoveCompromisedCredentialsByCompromiseType(
"https://example.com/", base::ASCIIToUTF16("username"),
CompromiseType::kLeaked,
RemoveCompromisedCredentialsReason::kMarkSiteAsLegitimate);
EXPECT_CALL(consumer,
OnGetCompromisedCredentials(ElementsAre(phished_credentials)));
store->GetAllCompromisedCredentials(&consumer);
WaitForPasswordStore();
store->ShutdownOnUIThread();
}
TEST_F(PasswordStoreTest, RemoveCompromisedCredentialsCreatedBetween) {
base::test::ScopedFeatureList feature_list;
feature_list.InitAndEnableFeature(password_manager::features::kPasswordCheck);
CompromisedCredentials compromised_credentials1 = {
"https://example1.com/", base::ASCIIToUTF16("username1"),
base::Time::FromTimeT(100), CompromiseType::kLeaked};
CompromisedCredentials compromised_credentials2 = {
"https://2.example.com/", base::ASCIIToUTF16("username2"),
base::Time::FromTimeT(200), CompromiseType::kLeaked};
CompromisedCredentials compromised_credentials3 = {
"https://example3.com/", base::ASCIIToUTF16("username3"),
base::Time::FromTimeT(300), CompromiseType::kLeaked};
scoped_refptr<PasswordStoreDefault> store = CreatePasswordStore();
store->Init(nullptr);
store->AddCompromisedCredentials(compromised_credentials1);
store->AddCompromisedCredentials(compromised_credentials2);
store->AddCompromisedCredentials(compromised_credentials3);
MockCompromisedCredentialsConsumer consumer;
EXPECT_CALL(consumer, OnGetCompromisedCredentials(UnorderedElementsAre(
compromised_credentials1, compromised_credentials2,
compromised_credentials3)));
store->GetAllCompromisedCredentials(&consumer);
WaitForPasswordStore();
testing::Mock::VerifyAndClearExpectations(&consumer);
store->RemoveCompromisedCredentialsByUrlAndTime(
base::BindRepeating(std::not_equal_to<GURL>(),
GURL(compromised_credentials3.signon_realm)),
base::Time::FromTimeT(150), base::Time::FromTimeT(350),
base::NullCallback());
EXPECT_CALL(consumer,
OnGetCompromisedCredentials(UnorderedElementsAre(
compromised_credentials1, compromised_credentials3)));
store->GetAllCompromisedCredentials(&consumer);
WaitForPasswordStore();
store->ShutdownOnUIThread();
}
// Test that updating a password in the store deletes the corresponding
// compromised record synchronously.
TEST_F(PasswordStoreTest, RemoveCompromisedCredentialsSyncOnUpdate) {
base::test::ScopedFeatureList feature_list;
feature_list.InitAndEnableFeature(password_manager::features::kPasswordCheck);
scoped_refptr<PasswordStoreDefault> store = CreatePasswordStore();
store->Init(nullptr);
CompromisedCredentials compromised_credentials = {
kTestWebRealm1, base::ASCIIToUTF16("username1"),
base::Time::FromTimeT(100), CompromiseType::kLeaked};
constexpr PasswordFormData kTestCredential = {PasswordForm::Scheme::kHtml,
kTestWebRealm1,
kTestWebOrigin1,
"",
L"",
L"username_element_1",
L"password_element_1",
L"username1",
L"12345",
10,
5};
std::unique_ptr<PasswordForm> form(FillPasswordFormWithData(kTestCredential));
store->AddCompromisedCredentials(compromised_credentials);
store->AddLogin(*form);
WaitForPasswordStore();
// Update the password value and immediately get the compromised passwords.
form->password_value = base::ASCIIToUTF16("new_password");
store->UpdateLogin(*form);
MockCompromisedCredentialsConsumer consumer;
store->GetAllCompromisedCredentials(&consumer);
EXPECT_CALL(consumer, OnGetCompromisedCredentials(IsEmpty()));
WaitForPasswordStore();
store->ShutdownOnUIThread();
}
// Test that deleting a password in the store deletes the corresponding
// compromised record synchronously.
TEST_F(PasswordStoreTest, RemoveCompromisedCredentialsSyncOnDelete) {
base::test::ScopedFeatureList feature_list;
feature_list.InitAndEnableFeature(password_manager::features::kPasswordCheck);
scoped_refptr<PasswordStoreDefault> store = CreatePasswordStore();
store->Init(nullptr);
CompromisedCredentials compromised_credentials = {
kTestWebRealm1, base::ASCIIToUTF16("username1"),
base::Time::FromTimeT(100), CompromiseType::kLeaked};
constexpr PasswordFormData kTestCredential = {PasswordForm::Scheme::kHtml,
kTestWebRealm1,
kTestWebOrigin1,
"",
L"",
L"username_element_1",
L"password_element_1",
L"username1",
L"12345",
10,
5};
std::unique_ptr<PasswordForm> form(FillPasswordFormWithData(kTestCredential));
store->AddCompromisedCredentials(compromised_credentials);
store->AddLogin(*form);
WaitForPasswordStore();
// Delete the password and immediately get the compromised passwords.
store->RemoveLogin(*form);
MockCompromisedCredentialsConsumer consumer;
store->GetAllCompromisedCredentials(&consumer);
EXPECT_CALL(consumer, OnGetCompromisedCredentials(IsEmpty()));
WaitForPasswordStore();
store->ShutdownOnUIThread();
}
#if !defined(OS_ANDROID)
// TODO(https://crbug.com/1051914): Enable on Android after making local
// heuristics reliable.
TEST_F(PasswordStoreTest, GetAllFieldInfo) {
FieldInfo field_info1{autofill::FormSignature(1001),
autofill::FieldSignature(1), autofill::USERNAME,
base::Time::FromTimeT(1)};
FieldInfo field_info2{autofill::FormSignature(1002),
autofill::FieldSignature(10), autofill::PASSWORD,
base::Time::FromTimeT(2)};
scoped_refptr<PasswordStoreDefault> store = CreatePasswordStore();
store->Init(nullptr);
store->AddFieldInfo(field_info1);
store->AddFieldInfo(field_info2);
MockPasswordStoreConsumer consumer;
EXPECT_CALL(consumer, OnGetAllFieldInfo(
UnorderedElementsAre(field_info1, field_info2)));
store->GetAllFieldInfo(&consumer);
WaitForPasswordStore();
store->ShutdownOnUIThread();
}
TEST_F(PasswordStoreTest, RemoveFieldInfo) {
FieldInfo field_info1{autofill::FormSignature(1001),
autofill::FieldSignature(1), autofill::USERNAME,
base::Time::FromTimeT(100)};
FieldInfo field_info2{autofill::FormSignature(1002),
autofill::FieldSignature(10), autofill::PASSWORD,
base::Time::FromTimeT(200)};
FieldInfo field_info3{autofill::FormSignature(1003),
autofill::FieldSignature(11), autofill::PASSWORD,
base::Time::FromTimeT(300)};
scoped_refptr<PasswordStoreDefault> store = CreatePasswordStore();
store->Init(nullptr);
store->AddFieldInfo(field_info1);
store->AddFieldInfo(field_info2);
store->AddFieldInfo(field_info3);
MockPasswordStoreConsumer consumer;
EXPECT_CALL(consumer, OnGetAllFieldInfo(UnorderedElementsAre(
field_info1, field_info2, field_info3)));
store->GetAllFieldInfo(&consumer);
WaitForPasswordStore();
testing::Mock::VerifyAndClearExpectations(&consumer);
store->RemoveFieldInfoByTime(base::Time::FromTimeT(150),
base::Time::FromTimeT(250),
base::NullCallback());
EXPECT_CALL(consumer, OnGetAllFieldInfo(
UnorderedElementsAre(field_info1, field_info3)));
store->GetAllFieldInfo(&consumer);
WaitForPasswordStore();
store->ShutdownOnUIThread();
}
#endif // !defined(OS_ANDROID)
} // namespace password_manager