Commit 96d488a1 authored by Gemene Narcis's avatar Gemene Narcis Committed by Commit Bot

Changes in HttpCredentialCleaner

This CL introduces a few improvements to HttpCredentialCleaner:
(1) A fix in removing the protocol from signon_realm,
(2) refactoring the unittest to use parameters,
(3) minor style fixes.

More details about (1):
HttpCredentialCleaner removes the protocol (HTTP or HTTPS) form
the signon_realm in order to compare the signon_realm of HTTP
credentials with the signon_realm of HTTPS credentials. Until now,
a GURL was created from the signon_realm of the form and then the
protocol was extracted from that GURL, resulting the signon_realm
excluding protocol. This can cause problems when the auth realm contains
characters that are forbidden in an url. This will lead in creating an
invalid url, and the resulting signon_realm with protocol exluded will
be an empty string.
This CL will avoid conversion from the signon_realm string to the GURL
and use other way to remove the protocol from the signon_realm.

More details about (2):
Unitests for this class were changed from one single test into a
bunch of parametrised tests in order to make debug easier in case
of failing test in the future.

Bug: 871140
Change-Id: Ic606d250f50806ae3a3fa07a480fcb01f5551c97
Reviewed-on: https://chromium-review.googlesource.com/1249104
Commit-Queue: Narcis Gemene <gemene@google.com>
Reviewed-by: default avatarVaclav Brozek <vabr@chromium.org>
Cr-Commit-Position: refs/heads/master@{#594826}
parent 66a3e578
...@@ -5,7 +5,6 @@ ...@@ -5,7 +5,6 @@
#include "components/password_manager/core/browser/http_credentials_cleaner.h" #include "components/password_manager/core/browser/http_credentials_cleaner.h"
#include "base/metrics/histogram_functions.h" #include "base/metrics/histogram_functions.h"
#include "base/strings/string_piece.h"
#include "components/password_manager/core/browser/password_manager_util.h" #include "components/password_manager/core/browser/password_manager_util.h"
#include "url/gurl.h" #include "url/gurl.h"
...@@ -30,11 +29,10 @@ void HttpCredentialCleaner::OnGetPasswordStoreResults( ...@@ -30,11 +29,10 @@ void HttpCredentialCleaner::OnGetPasswordStoreResults(
}); });
for (auto& form : results) { for (auto& form : results) {
// The next signon-realm has the protocol excluded. For example if original FormKey form_key(
// signon_realm is "https://google.com/". After excluding protocol it {std::string(
// becomes "google.com/". password_manager_util::GetSignonRealmWithProtocolExcluded(*form)),
FormKey form_key({GURL(form->signon_realm).GetContent(), form->scheme, form->scheme, form->username_value});
form->username_value});
if (form->origin.SchemeIs(url::kHttpScheme)) { if (form->origin.SchemeIs(url::kHttpScheme)) {
PostHSTSQueryForHostAndNetworkContext( PostHSTSQueryForHostAndNetworkContext(
form->origin, network_context_getter_.Run(), form->origin, network_context_getter_.Run(),
......
...@@ -2,6 +2,8 @@ ...@@ -2,6 +2,8 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
#include "components/password_manager/core/browser/http_credentials_cleaner.h"
#include "base/macros.h" #include "base/macros.h"
#include "base/stl_util.h" #include "base/stl_util.h"
#include "base/strings/utf_string_conversions.h" #include "base/strings/utf_string_conversions.h"
...@@ -17,37 +19,41 @@ ...@@ -17,37 +19,41 @@
namespace password_manager { namespace password_manager {
TEST(HttpCredentialCleaner, ReportHttpMigrationMetrics) { namespace {
enum class HttpCredentialType { kNoMatching, kEquivalent, kConflicting };
enum class HttpCredentialType { kNoMatching, kEquivalent, kConflicting };
struct TestCase { struct TestCase {
bool is_hsts_enabled; bool is_hsts_enabled;
autofill::PasswordForm::Scheme http_form_scheme; autofill::PasswordForm::Scheme http_form_scheme;
bool same_signon_realm; bool same_signon_realm;
bool same_scheme; bool same_scheme;
bool same_username; bool same_username;
bool same_password; bool same_password;
// |expected| == kNoMatching if:
// same_signon_realm & same_scheme & same_username = false
// Otherwise:
// |expected| == kEquivalent if:
// same_signon_realm & same_scheme & same_username & same_password = true
// Otherwise:
// |expected| == kConflicting if:
// same_signon_realm & same_scheme & same_username = true but same_password =
// false
HttpCredentialType expected; HttpCredentialType expected;
}; };
struct Histogram { constexpr static TestCase kCases[] = {
bool test_hsts_enabled;
HttpCredentialType test_type;
std::string histogram_name;
};
constexpr static TestCase cases[] = { {true, autofill::PasswordForm::Scheme::SCHEME_HTML, false, true, true, true,
HttpCredentialType::kNoMatching},
{true, autofill::PasswordForm::Scheme::SCHEME_HTML, false, true, true, {true, autofill::PasswordForm::Scheme::SCHEME_HTML, true, false, true, true,
true, HttpCredentialType::kNoMatching}, HttpCredentialType::kNoMatching},
{true, autofill::PasswordForm::Scheme::SCHEME_HTML, true, false, true, {true, autofill::PasswordForm::Scheme::SCHEME_HTML, true, true, false, true,
true, HttpCredentialType::kNoMatching}, HttpCredentialType::kNoMatching},
{true, autofill::PasswordForm::Scheme::SCHEME_HTML, true, true, false, {true, autofill::PasswordForm::Scheme::SCHEME_HTML, true, true, true, false,
true, HttpCredentialType::kNoMatching}, HttpCredentialType::kConflicting},
{true, autofill::PasswordForm::Scheme::SCHEME_HTML, true, true, true, {true, autofill::PasswordForm::Scheme::SCHEME_HTML, true, true, true, true,
false, HttpCredentialType::kConflicting}, HttpCredentialType::kEquivalent},
{true, autofill::PasswordForm::Scheme::SCHEME_HTML, true, true, true,
true, HttpCredentialType::kEquivalent},
{false, autofill::PasswordForm::Scheme::SCHEME_HTML, false, true, true, {false, autofill::PasswordForm::Scheme::SCHEME_HTML, false, true, true,
true, HttpCredentialType::kNoMatching}, true, HttpCredentialType::kNoMatching},
...@@ -57,8 +63,8 @@ TEST(HttpCredentialCleaner, ReportHttpMigrationMetrics) { ...@@ -57,8 +63,8 @@ TEST(HttpCredentialCleaner, ReportHttpMigrationMetrics) {
true, HttpCredentialType::kNoMatching}, true, HttpCredentialType::kNoMatching},
{false, autofill::PasswordForm::Scheme::SCHEME_HTML, true, true, true, {false, autofill::PasswordForm::Scheme::SCHEME_HTML, true, true, true,
false, HttpCredentialType::kConflicting}, false, HttpCredentialType::kConflicting},
{false, autofill::PasswordForm::Scheme::SCHEME_HTML, true, true, true, {false, autofill::PasswordForm::Scheme::SCHEME_HTML, true, true, true, true,
true, HttpCredentialType::kEquivalent}, HttpCredentialType::kEquivalent},
{true, autofill::PasswordForm::Scheme::SCHEME_BASIC, false, true, true, {true, autofill::PasswordForm::Scheme::SCHEME_BASIC, false, true, true,
true, HttpCredentialType::kNoMatching}, true, HttpCredentialType::kNoMatching},
...@@ -68,8 +74,8 @@ TEST(HttpCredentialCleaner, ReportHttpMigrationMetrics) { ...@@ -68,8 +74,8 @@ TEST(HttpCredentialCleaner, ReportHttpMigrationMetrics) {
true, HttpCredentialType::kNoMatching}, true, HttpCredentialType::kNoMatching},
{true, autofill::PasswordForm::Scheme::SCHEME_BASIC, true, true, true, {true, autofill::PasswordForm::Scheme::SCHEME_BASIC, true, true, true,
false, HttpCredentialType::kConflicting}, false, HttpCredentialType::kConflicting},
{true, autofill::PasswordForm::Scheme::SCHEME_BASIC, true, true, true, {true, autofill::PasswordForm::Scheme::SCHEME_BASIC, true, true, true, true,
true, HttpCredentialType::kEquivalent}, HttpCredentialType::kEquivalent},
{false, autofill::PasswordForm::Scheme::SCHEME_BASIC, false, true, true, {false, autofill::PasswordForm::Scheme::SCHEME_BASIC, false, true, true,
true, HttpCredentialType::kNoMatching}, true, HttpCredentialType::kNoMatching},
...@@ -80,33 +86,42 @@ TEST(HttpCredentialCleaner, ReportHttpMigrationMetrics) { ...@@ -80,33 +86,42 @@ TEST(HttpCredentialCleaner, ReportHttpMigrationMetrics) {
{false, autofill::PasswordForm::Scheme::SCHEME_BASIC, true, true, true, {false, autofill::PasswordForm::Scheme::SCHEME_BASIC, true, true, true,
false, HttpCredentialType::kConflicting}, false, HttpCredentialType::kConflicting},
{false, autofill::PasswordForm::Scheme::SCHEME_BASIC, true, true, true, {false, autofill::PasswordForm::Scheme::SCHEME_BASIC, true, true, true,
true, HttpCredentialType::kEquivalent} true, HttpCredentialType::kEquivalent}};
} // namespace
class HttpCredentialCleanerTest : public ::testing::TestWithParam<TestCase> {
public:
HttpCredentialCleanerTest() = default;
~HttpCredentialCleanerTest() override = default;
protected:
scoped_refptr<TestPasswordStore> store_ =
base::MakeRefCounted<TestPasswordStore>();
DISALLOW_COPY_AND_ASSIGN(HttpCredentialCleanerTest);
};
TEST_P(HttpCredentialCleanerTest, ReportHttpMigrationMetrics) {
struct Histogram {
bool test_hsts_enabled;
HttpCredentialType test_type;
std::string histogram_name;
}; };
const base::string16 username[2] = {base::ASCIIToUTF16("user0"), static const std::string signon_realm[2] = {"https://example.org/realm/",
"https://example.org/"};
static const base::string16 username[2] = {base::ASCIIToUTF16("user0"),
base::ASCIIToUTF16("user1")}; base::ASCIIToUTF16("user1")};
const base::string16 password[2] = {base::ASCIIToUTF16("pass0"),
static const base::string16 password[2] = {base::ASCIIToUTF16("pass0"),
base::ASCIIToUTF16("pass1")}; base::ASCIIToUTF16("pass1")};
std::vector<Histogram> histograms_to_test; base::test::ScopedTaskEnvironment scoped_task_environment;
for (bool test_hsts_enabled : {true, false}) { ASSERT_TRUE(store_->Init(syncer::SyncableService::StartSyncFlare(), nullptr));
std::string suffix = TestCase test = GetParam();
(test_hsts_enabled ? "WithHSTSEnabled" : "HSTSNotEnabled");
histograms_to_test.push_back(
{test_hsts_enabled, HttpCredentialType::kNoMatching,
"PasswordManager.HttpCredentialsWithoutMatchingHttpsCredential." +
suffix});
histograms_to_test.push_back(
{test_hsts_enabled, HttpCredentialType::kEquivalent,
"PasswordManager.HttpCredentialsWithEquivalentHttpsCredential." +
suffix});
histograms_to_test.push_back(
{test_hsts_enabled, HttpCredentialType::kConflicting,
"PasswordManager.HttpCredentialsWithConflictingHttpsCredential." +
suffix});
}
for (const auto& test : cases) {
SCOPED_TRACE(testing::Message() SCOPED_TRACE(testing::Message()
<< "is_hsts_enabled=" << test.is_hsts_enabled << "is_hsts_enabled=" << test.is_hsts_enabled
<< ", http_form_scheme=" << ", http_form_scheme="
...@@ -116,45 +131,34 @@ TEST(HttpCredentialCleaner, ReportHttpMigrationMetrics) { ...@@ -116,45 +131,34 @@ TEST(HttpCredentialCleaner, ReportHttpMigrationMetrics) {
<< ", same_username=" << test.same_username << ", same_username=" << test.same_username
<< ", same_password=" << test.same_password); << ", same_password=" << test.same_password);
base::test::ScopedTaskEnvironment scoped_task_environment;
auto request_context =
base::MakeRefCounted<net::TestURLRequestContextGetter>(
base::ThreadTaskRunnerHandle::Get());
network::mojom::NetworkContextPtr network_context_pipe;
auto network_context = std::make_unique<network::NetworkContext>(
nullptr, mojo::MakeRequest(&network_context_pipe),
request_context->GetURLRequestContext());
auto password_store =
base::MakeRefCounted<password_manager::TestPasswordStore>();
ASSERT_TRUE(password_store->Init(syncer::SyncableService::StartSyncFlare(),
nullptr));
autofill::PasswordForm http_form; autofill::PasswordForm http_form;
http_form.origin = GURL("http://example.org/"); http_form.origin = GURL("http://example.org/");
http_form.signon_realm = http_form.origin.GetOrigin().spec(); http_form.signon_realm = "http://example.org/";
http_form.scheme = test.http_form_scheme; http_form.scheme = test.http_form_scheme;
http_form.username_value = username[1]; http_form.username_value = username[1];
http_form.password_value = password[1]; http_form.password_value = password[1];
password_store->AddLogin(http_form); store_->AddLogin(http_form);
autofill::PasswordForm https_form; autofill::PasswordForm https_form;
https_form.origin = GURL("https://example.org/"); https_form.origin = GURL("https://example.org/");
https_form.signon_realm = signon_realm[test.same_signon_realm];
https_form.username_value = username[test.same_username];
https_form.password_value = password[test.same_password];
https_form.scheme = test.http_form_scheme; https_form.scheme = test.http_form_scheme;
if (!test.same_scheme) { if (!test.same_scheme) {
if (https_form.scheme == autofill::PasswordForm::Scheme::SCHEME_BASIC) https_form.scheme =
https_form.scheme = autofill::PasswordForm::Scheme::SCHEME_HTML; (http_form.scheme == autofill::PasswordForm::Scheme::SCHEME_BASIC
else ? autofill::PasswordForm::Scheme::SCHEME_HTML
https_form.scheme = autofill::PasswordForm::Scheme::SCHEME_BASIC; : autofill::PasswordForm::Scheme::SCHEME_BASIC);
} }
store_->AddLogin(https_form);
https_form.signon_realm = https_form.origin.GetOrigin().spec(); auto request_context = base::MakeRefCounted<net::TestURLRequestContextGetter>(
if (!test.same_signon_realm) base::ThreadTaskRunnerHandle::Get());
https_form.signon_realm += "different/"; network::mojom::NetworkContextPtr network_context_pipe;
auto network_context = std::make_unique<network::NetworkContext>(
https_form.username_value = username[test.same_username]; nullptr, mojo::MakeRequest(&network_context_pipe),
https_form.password_value = password[test.same_password]; request_context->GetURLRequestContext());
password_store->AddLogin(https_form);
if (test.is_hsts_enabled) { if (test.is_hsts_enabled) {
network_context->AddHSTSForTesting( network_context->AddHSTSForTesting(
...@@ -163,9 +167,13 @@ TEST(HttpCredentialCleaner, ReportHttpMigrationMetrics) { ...@@ -163,9 +167,13 @@ TEST(HttpCredentialCleaner, ReportHttpMigrationMetrics) {
} }
scoped_task_environment.RunUntilIdle(); scoped_task_environment.RunUntilIdle();
const TestPasswordStore::PasswordMap passwords_before_cleaning =
store_->stored_passwords();
base::HistogramTester histogram_tester; base::HistogramTester histogram_tester;
password_manager_util::ReportHttpMigrationMetrics( password_manager_util::ReportHttpMigrationMetrics(
password_store, store_,
base::BindLambdaForTesting([&]() -> network::mojom::NetworkContext* { base::BindLambdaForTesting([&]() -> network::mojom::NetworkContext* {
// This needs to be network_context_pipe.get() and // This needs to be network_context_pipe.get() and
// not network_context.get() to make HSTS queries asynchronous, which // not network_context.get() to make HSTS queries asynchronous, which
...@@ -177,6 +185,23 @@ TEST(HttpCredentialCleaner, ReportHttpMigrationMetrics) { ...@@ -177,6 +185,23 @@ TEST(HttpCredentialCleaner, ReportHttpMigrationMetrics) {
})); }));
scoped_task_environment.RunUntilIdle(); scoped_task_environment.RunUntilIdle();
std::vector<Histogram> histograms_to_test;
for (bool test_hsts_enabled : {true, false}) {
std::string suffix =
(test_hsts_enabled ? "WithHSTSEnabled" : "HSTSNotEnabled");
histograms_to_test.push_back(
{test_hsts_enabled, HttpCredentialType::kNoMatching,
"PasswordManager.HttpCredentialsWithoutMatchingHttpsCredential." +
suffix});
histograms_to_test.push_back(
{test_hsts_enabled, HttpCredentialType::kEquivalent,
"PasswordManager.HttpCredentialsWithEquivalentHttpsCredential." +
suffix});
histograms_to_test.push_back(
{test_hsts_enabled, HttpCredentialType::kConflicting,
"PasswordManager.HttpCredentialsWithConflictingHttpsCredential." +
suffix});
}
for (const auto& histogram : histograms_to_test) { for (const auto& histogram : histograms_to_test) {
int sample = int sample =
static_cast<int>(histogram.test_type == test.expected && static_cast<int>(histogram.test_type == test.expected &&
...@@ -184,9 +209,12 @@ TEST(HttpCredentialCleaner, ReportHttpMigrationMetrics) { ...@@ -184,9 +209,12 @@ TEST(HttpCredentialCleaner, ReportHttpMigrationMetrics) {
histogram_tester.ExpectUniqueSample(histogram.histogram_name, sample, 1); histogram_tester.ExpectUniqueSample(histogram.histogram_name, sample, 1);
} }
password_store->ShutdownOnUIThread(); store_->ShutdownOnUIThread();
scoped_task_environment.RunUntilIdle(); scoped_task_environment.RunUntilIdle();
}
} }
INSTANTIATE_TEST_CASE_P(,
HttpCredentialCleanerTest,
::testing::ValuesIn(kCases));
} // namespace password_manager } // namespace password_manager
\ No newline at end of file
...@@ -13,6 +13,7 @@ ...@@ -13,6 +13,7 @@
#include "base/strings/string_piece.h" #include "base/strings/string_piece.h"
#include "components/autofill/core/common/password_form.h" #include "components/autofill/core/common/password_form.h"
#include "components/password_manager/core/browser/password_manager_util.h"
#include "components/password_manager/core/browser/password_store.h" #include "components/password_manager/core/browser/password_store.h"
#include "components/password_manager/core/common/password_manager_pref_names.h" #include "components/password_manager/core/common/password_manager_pref_names.h"
#include "components/prefs/pref_service.h" #include "components/prefs/pref_service.h"
...@@ -198,12 +199,9 @@ void InvalidRealmCredentialCleaner::OnGetPasswordStoreResults( ...@@ -198,12 +199,9 @@ void InvalidRealmCredentialCleaner::OnGetPasswordStoreResults(
// HTTP forms to the expected signon_realm (excluding the protocol). // HTTP forms to the expected signon_realm (excluding the protocol).
std::map<FormKeyForHttpMatch, std::string> http_credentials_map; std::map<FormKeyForHttpMatch, std::string> http_credentials_map;
for (const auto& form : http_forms) { for (const auto& form : http_forms) {
base::StringPiece signon_realm = form->signon_realm; http_credentials_map.emplace(
// Find the web origin in the signon_realm and remove what is before it. GetFormKeyForHttpMatch(*form),
// This will result in removing the protocol ("http://"). password_manager_util::GetSignonRealmWithProtocolExcluded(*form));
signon_realm = signon_realm.substr(
signon_realm.find(form->origin.GetOrigin().GetContent()));
http_credentials_map.emplace(GetFormKeyForHttpMatch(*form), signon_realm);
} }
// Separate HTML and non-HTML HTTPS credentials. // Separate HTML and non-HTML HTTPS credentials.
......
...@@ -243,6 +243,20 @@ void RemoveUselessCredentials( ...@@ -243,6 +243,20 @@ void RemoveUselessCredentials(
base::TimeDelta::FromSeconds(delay_in_seconds)); base::TimeDelta::FromSeconds(delay_in_seconds));
} }
base::StringPiece GetSignonRealmWithProtocolExcluded(const PasswordForm& form) {
base::StringPiece signon_realm_protocol_excluded = form.signon_realm;
// Find the web origin (with protocol excluded) in the signon_realm.
const size_t after_protocol =
signon_realm_protocol_excluded.find(form.origin.GetOrigin().GetContent());
DCHECK_NE(after_protocol, base::StringPiece::npos);
// Keep the string starting with position |after_protocol|.
signon_realm_protocol_excluded =
signon_realm_protocol_excluded.substr(after_protocol);
return signon_realm_protocol_excluded;
}
void FindBestMatches( void FindBestMatches(
std::vector<const PasswordForm*> matches, std::vector<const PasswordForm*> matches,
std::map<base::string16, const PasswordForm*>* best_matches, std::map<base::string16, const PasswordForm*>* best_matches,
......
...@@ -105,6 +105,14 @@ void RemoveUselessCredentials( ...@@ -105,6 +105,14 @@ void RemoveUselessCredentials(
PrefService* prefs, PrefService* prefs,
int delay_in_seconds); int delay_in_seconds);
// Excluding protocol from a signon_realm means to remove from the signon_realm
// what is before the web origin (with the protocol excluded as well). For
// example if the signon_realm is "https://www.google.com/", after
// excluding protocol it becomes "www.google.com/".
// This assumes that the |form|'s origin is a substring of the signon_realm.
base::StringPiece GetSignonRealmWithProtocolExcluded(
const autofill::PasswordForm& form);
// Report metrics about HTTP to HTTPS migration process. This function cannot be // Report metrics about HTTP to HTTPS migration process. This function cannot be
// used on iOS platform because the HSTS query is not supported. // used on iOS platform because the HSTS query is not supported.
// |network_context_getter| should return nullptr if it can't get the network // |network_context_getter| should return nullptr if it can't get the network
......
...@@ -208,6 +208,18 @@ TEST(PasswordManagerUtil, ...@@ -208,6 +208,18 @@ TEST(PasswordManagerUtil,
} }
} }
TEST(PasswordManagerUtil, GetSignonRealmWithProtocolExcluded) {
autofill::PasswordForm http_form;
http_form.origin = GURL("http://www.google.com/page-1/");
http_form.signon_realm = "http://www.google.com/";
EXPECT_EQ(GetSignonRealmWithProtocolExcluded(http_form), "www.google.com/");
autofill::PasswordForm https_form;
https_form.origin = GURL("https://www.google.com/page-1/");
https_form.signon_realm = "https://www.google.com/";
EXPECT_EQ(GetSignonRealmWithProtocolExcluded(https_form), "www.google.com/");
}
TEST(PasswordManagerUtil, FindBestMatches) { TEST(PasswordManagerUtil, FindBestMatches) {
const int kNotFound = -1; const int kNotFound = -1;
struct TestMatch { struct TestMatch {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment