blob: 8cc337adad08783e6f8a168d2c9d13e98e922995 [file] [log] [blame]
// Copyright 2017 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 "components/password_manager/core/browser/http_password_store_migrator.h"
#include <memory>
#include "base/strings/utf_string_conversions.h"
#include "base/test/scoped_task_environment.h"
#include "components/password_manager/core/browser/mock_password_store.h"
#include "components/password_manager/core/browser/stub_password_manager_client.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace password_manager {
namespace {
using autofill::PasswordForm;
using testing::ElementsAre;
using testing::Invoke;
using testing::Pointee;
using testing::SaveArg;
using testing::Unused;
using testing::_;
constexpr char kTestHttpsURL[] = "https://example.org/path";
constexpr char kTestHttpURL[] = "http://example.org/path";
constexpr char kTestSubdomainHttpURL[] = "http://login.example.org/path2";
// Creates a dummy http form with some basic arbitrary values.
PasswordForm CreateTestForm() {
PasswordForm form;
form.origin = GURL(kTestHttpURL);
form.signon_realm = form.origin.GetOrigin().spec();
form.action = GURL("https://example.org/action.html");
form.username_value = base::ASCIIToUTF16("user");
form.password_value = base::ASCIIToUTF16("password");
return form;
}
// Creates a dummy http PSL-matching form with some basic arbitrary values.
PasswordForm CreateTestPSLForm() {
PasswordForm form;
form.origin = GURL(kTestSubdomainHttpURL);
form.signon_realm = form.origin.GetOrigin().spec();
form.action = GURL(kTestSubdomainHttpURL);
form.username_value = base::ASCIIToUTF16("user2");
form.password_value = base::ASCIIToUTF16("password2");
form.is_public_suffix_match = true;
return form;
}
// Creates an Android credential.
PasswordForm CreateAndroidCredential() {
PasswordForm form;
form.username_value = base::ASCIIToUTF16("user3");
form.password_value = base::ASCIIToUTF16("password3");
form.signon_realm = "android://hash@com.example.android/";
form.origin = GURL(form.signon_realm);
form.action = GURL();
form.is_affiliation_based_match = true;
return form;
}
class MockConsumer : public HttpPasswordStoreMigrator::Consumer {
public:
MOCK_METHOD1(ProcessForms,
void(const std::vector<autofill::PasswordForm*>& forms));
void ProcessMigratedForms(
std::vector<std::unique_ptr<autofill::PasswordForm>> forms) override {
std::vector<autofill::PasswordForm*> raw_forms(forms.size());
std::transform(forms.begin(), forms.end(), raw_forms.begin(),
[](const std::unique_ptr<autofill::PasswordForm>& form) {
return form.get();
});
ProcessForms(raw_forms);
}
};
class MockPasswordManagerClient : public StubPasswordManagerClient {
public:
explicit MockPasswordManagerClient(PasswordStore* store) : store_(store) {}
// PasswordManagerClient:
PasswordStore* GetPasswordStore() const override { return store_; }
void PostHSTSQueryForHost(const GURL& gurl,
HSTSCallback callback) const override {
saved_callback_ = std::move(callback);
PostHSTSQueryForHostHelper(gurl);
}
MOCK_CONST_METHOD1(PostHSTSQueryForHostHelper, void(const GURL&));
HSTSCallback hsts_acquire_callback() { return std::move(saved_callback_); }
private:
PasswordStore* store_;
mutable HSTSCallback saved_callback_;
DISALLOW_COPY_AND_ASSIGN(MockPasswordManagerClient);
};
} // namespace
class HttpPasswordStoreMigratorTest : public testing::Test {
public:
HttpPasswordStoreMigratorTest()
: mock_store_(new testing::StrictMock<MockPasswordStore>),
client_(mock_store_.get()) {
mock_store_->Init(syncer::SyncableService::StartSyncFlare(), nullptr);
}
~HttpPasswordStoreMigratorTest() override {
mock_store_->ShutdownOnUIThread();
}
MockConsumer& consumer() { return consumer_; }
MockPasswordStore& store() { return *mock_store_; }
MockPasswordManagerClient& client() { return client_; }
void WaitForPasswordStore() { scoped_task_environment_.RunUntilIdle(); }
protected:
void TestEmptyStore(bool is_hsts);
void TestFullStore(bool is_hsts);
void TestMigratorDeletionByConsumer(bool is_hsts);
private:
base::test::ScopedTaskEnvironment scoped_task_environment_;
MockConsumer consumer_;
scoped_refptr<MockPasswordStore> mock_store_;
MockPasswordManagerClient client_;
DISALLOW_COPY_AND_ASSIGN(HttpPasswordStoreMigratorTest);
};
void HttpPasswordStoreMigratorTest::TestEmptyStore(bool is_hsts) {
PasswordStore::FormDigest form(CreateTestForm());
EXPECT_CALL(store(), GetLogins(form, _));
EXPECT_CALL(client(), PostHSTSQueryForHostHelper(GURL(kTestHttpsURL)))
.Times(1);
HttpPasswordStoreMigrator migrator(GURL(kTestHttpsURL), &client(),
&consumer());
HSTSCallback callback = client().hsts_acquire_callback();
std::move(callback).Run(is_hsts ? HSTSResult::kYes : HSTSResult::kNo);
// We expect a potential call to |RemoveSiteStatsImpl| which is a async task
// posted from |PasswordStore::RemoveSiteStats|. Hence the following lines are
// necessary to ensure |RemoveSiteStatsImpl| gets called when expected.
EXPECT_CALL(store(), RemoveSiteStatsImpl(GURL(kTestHttpURL).GetOrigin()))
.Times(is_hsts);
WaitForPasswordStore();
EXPECT_CALL(consumer(), ProcessForms(std::vector<autofill::PasswordForm*>()));
migrator.OnGetPasswordStoreResults(
std::vector<std::unique_ptr<autofill::PasswordForm>>());
}
void HttpPasswordStoreMigratorTest::TestFullStore(bool is_hsts) {
PasswordStore::FormDigest form_digest(CreateTestForm());
EXPECT_CALL(store(), GetLogins(form_digest, _));
EXPECT_CALL(client(), PostHSTSQueryForHostHelper(GURL(kTestHttpsURL)))
.Times(1);
HttpPasswordStoreMigrator migrator(GURL(kTestHttpsURL), &client(),
&consumer());
HSTSCallback callback = client().hsts_acquire_callback();
std::move(callback).Run(is_hsts ? HSTSResult::kYes : HSTSResult::kNo);
// We expect a potential call to |RemoveSiteStatsImpl| which is a async task
// posted from |PasswordStore::RemoveSiteStats|. Hence the following lines are
// necessary to ensure |RemoveSiteStatsImpl| gets called when expected.
EXPECT_CALL(store(), RemoveSiteStatsImpl(GURL(kTestHttpURL).GetOrigin()))
.Times(is_hsts);
WaitForPasswordStore();
PasswordForm form = CreateTestForm();
PasswordForm psl_form = CreateTestPSLForm();
PasswordForm android_form = CreateAndroidCredential();
PasswordForm expected_form = form;
expected_form.origin = GURL(kTestHttpsURL);
expected_form.signon_realm = expected_form.origin.GetOrigin().spec();
EXPECT_CALL(store(), AddLogin(expected_form));
EXPECT_CALL(store(), RemoveLogin(form)).Times(is_hsts);
EXPECT_CALL(consumer(), ProcessForms(ElementsAre(Pointee(expected_form))));
std::vector<std::unique_ptr<autofill::PasswordForm>> results;
results.push_back(std::make_unique<PasswordForm>(psl_form));
results.push_back(std::make_unique<PasswordForm>(form));
results.push_back(std::make_unique<PasswordForm>(android_form));
migrator.OnGetPasswordStoreResults(std::move(results));
}
// This test checks whether the migration successfully completes even if the
// migrator gets explicitly deleted by its consumer. This test will crash if
// this is not the case.
void HttpPasswordStoreMigratorTest::TestMigratorDeletionByConsumer(
bool is_hsts) {
// Setup expectations on store and client.
EXPECT_CALL(store(), GetLogins(_, _));
EXPECT_CALL(client(), PostHSTSQueryForHostHelper(GURL(kTestHttpsURL)))
.Times(1);
// Construct the migrator, call |OnGetPasswordStoreResults| explicitly and
// manually delete it.
auto migrator = std::make_unique<HttpPasswordStoreMigrator>(
GURL(kTestHttpsURL), &client(), &consumer());
migrator->OnGetPasswordStoreResults(
std::vector<std::unique_ptr<autofill::PasswordForm>>());
EXPECT_CALL(consumer(), ProcessForms(_)).WillOnce(Invoke([&migrator](Unused) {
migrator.reset();
}));
HSTSCallback callback = client().hsts_acquire_callback();
std::move(callback).Run(is_hsts ? HSTSResult::kYes : HSTSResult::kNo);
// We expect a potential call to |RemoveSiteStatsImpl| which is a async task
// posted from |PasswordStore::RemoveSiteStats|. Hence the following lines are
// necessary to ensure |RemoveSiteStatsImpl| gets called when expected.
EXPECT_CALL(store(), RemoveSiteStatsImpl(GURL(kTestHttpURL).GetOrigin()))
.Times(is_hsts);
WaitForPasswordStore();
}
TEST_F(HttpPasswordStoreMigratorTest, EmptyStoreWithHSTS) {
TestEmptyStore(true);
}
TEST_F(HttpPasswordStoreMigratorTest, EmptyStoreWithoutHSTS) {
TestEmptyStore(false);
}
TEST_F(HttpPasswordStoreMigratorTest, FullStoreWithHSTS) {
TestFullStore(true);
}
TEST_F(HttpPasswordStoreMigratorTest, FullStoreWithoutHSTS) {
TestFullStore(false);
}
TEST_F(HttpPasswordStoreMigratorTest, MigratorDeletionByConsumerWithHSTS) {
TestMigratorDeletionByConsumer(true);
}
TEST_F(HttpPasswordStoreMigratorTest, MigratorDeletionByConsumerWithoutHSTS) {
TestMigratorDeletionByConsumer(false);
}
TEST(HttpPasswordStoreMigrator, MigrateHttpFormToHttpsTestSignonRealm) {
const GURL kOrigins[] = {GURL("http://example.org/"),
GURL("http://example.org/path/")};
for (bool origin_has_paths : {true, false}) {
PasswordForm http_html_form;
http_html_form.origin = kOrigins[origin_has_paths];
http_html_form.signon_realm = "http://example.org/";
http_html_form.scheme = PasswordForm::Scheme::SCHEME_HTML;
PasswordForm non_html_empty_realm_form;
non_html_empty_realm_form.origin = kOrigins[origin_has_paths];
non_html_empty_realm_form.signon_realm = "http://example.org/";
non_html_empty_realm_form.scheme = PasswordForm::Scheme::SCHEME_BASIC;
PasswordForm non_html_form;
non_html_form.origin = kOrigins[origin_has_paths];
non_html_form.signon_realm = "http://example.org/realm";
non_html_form.scheme = PasswordForm::Scheme::SCHEME_BASIC;
EXPECT_EQ(HttpPasswordStoreMigrator::MigrateHttpFormToHttps(http_html_form)
.signon_realm,
"https://example.org/");
EXPECT_EQ(HttpPasswordStoreMigrator::MigrateHttpFormToHttps(
non_html_empty_realm_form)
.signon_realm,
"https://example.org/");
EXPECT_EQ(HttpPasswordStoreMigrator::MigrateHttpFormToHttps(non_html_form)
.signon_realm,
"https://example.org/realm");
}
}
} // namespace password_manager