Commit 8dd3ab36 authored by Dominic Battre's avatar Dominic Battre Committed by Commit Bot

Implement a service that delivers password generation requirements


Bug: 846694
Change-Id: Iecf328d8a43c96dd6772b48d0f0f80760a8a51e1
Reviewed-on: https://chromium-review.googlesource.com/1093096
Commit-Queue: Dominic Battré <battre@chromium.org>
Reviewed-by: default avatarJochen Eisinger <jochen@chromium.org>
Reviewed-by: default avatarVaclav Brozek <vabr@chromium.org>
Cr-Commit-Position: refs/heads/master@{#566399}
parent 421558d4
......@@ -16,6 +16,8 @@ static_library("browser") {
"form_submission_tracker_util.h",
"password_manager_internals_service_factory.cc",
"password_manager_internals_service_factory.h",
"password_requirements_service_factory.cc",
"password_requirements_service_factory.h",
]
public_deps = [
......
......@@ -3,6 +3,7 @@ include_rules = [
"+components/autofill/content/browser",
"+components/autofill/content/common",
"+components/keyed_service/content",
"+components/keyed_service/core",
"+content/public/browser",
"+net",
"+services/service_manager/public/cpp",
......
// Copyright 2018 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/content/browser/password_requirements_service_factory.h"
#include <memory>
#include "base/memory/singleton.h"
#include "components/autofill/core/browser/password_requirements_spec_fetcher.h"
#include "components/autofill/core/browser/password_requirements_spec_fetcher_impl.h"
#include "components/keyed_service/content/browser_context_dependency_manager.h"
#include "components/keyed_service/core/keyed_service.h"
#include "components/password_manager/core/browser/password_requirements_service.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/storage_partition.h"
namespace password_manager {
// static
PasswordRequirementsServiceFactory*
PasswordRequirementsServiceFactory::GetInstance() {
return base::Singleton<PasswordRequirementsServiceFactory>::get();
}
// static
PasswordRequirementsService*
PasswordRequirementsServiceFactory::GetForBrowserContext(
content::BrowserContext* context) {
return static_cast<PasswordRequirementsService*>(
GetInstance()->GetServiceForBrowserContext(context, true /* create */));
}
PasswordRequirementsServiceFactory::PasswordRequirementsServiceFactory()
: BrowserContextKeyedServiceFactory(
"PasswordRequirementsServiceFactory",
BrowserContextDependencyManager::GetInstance()) {}
PasswordRequirementsServiceFactory::~PasswordRequirementsServiceFactory() {}
KeyedService* PasswordRequirementsServiceFactory::BuildServiceInstanceFor(
content::BrowserContext* context) const {
if (context->IsOffTheRecord())
return nullptr;
// TODO(crbug.com/846694): These should become finch parameters.
const int kVersion = 1;
const size_t kPrefixLength = 0;
const int kTimeout = 5000;
std::unique_ptr<autofill::PasswordRequirementsSpecFetcher> fetcher =
std::make_unique<autofill::PasswordRequirementsSpecFetcherImpl>(
content::BrowserContext::GetDefaultStoragePartition(context)
->GetURLLoaderFactoryForBrowserProcess(),
kVersion, kPrefixLength, kTimeout);
return new PasswordRequirementsService(std::move(fetcher));
}
} // namespace password_manager
// Copyright 2018 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.
#ifndef COMPONENTS_PASSWORD_MANAGER_CONTENT_BROWSER_PASSWORD_REQUIREMENTS_SERVICE_FACTORY_H_
#define COMPONENTS_PASSWORD_MANAGER_CONTENT_BROWSER_PASSWORD_REQUIREMENTS_SERVICE_FACTORY_H_
#include "base/macros.h"
#include "components/keyed_service/content/browser_context_keyed_service_factory.h"
namespace base {
template <typename T>
struct DefaultSingletonTraits;
}
namespace content {
class BrowserContext;
}
namespace password_manager {
class PasswordRequirementsService;
class PasswordRequirementsServiceFactory
: public BrowserContextKeyedServiceFactory {
public:
static PasswordRequirementsServiceFactory* GetInstance();
// Returns the PasswordRequirementsService associated with |context|.
// This may be nullptr for an incognito |context|.
static PasswordRequirementsService* GetForBrowserContext(
content::BrowserContext* context);
private:
friend struct base::DefaultSingletonTraits<
PasswordRequirementsServiceFactory>;
PasswordRequirementsServiceFactory();
~PasswordRequirementsServiceFactory() override;
// BrowserContextKeyedServiceFactory overrides:
KeyedService* BuildServiceInstanceFor(
content::BrowserContext* context) const override;
DISALLOW_COPY_AND_ASSIGN(PasswordRequirementsServiceFactory);
};
} // namespace password_manager
#endif // COMPONENTS_PASSWORD_MANAGER_CONTENT_BROWSER_PASSWORD_REQUIREMENTS_SERVICE_FACTORY_H_
......@@ -116,6 +116,8 @@ static_library("browser") {
"password_manager_metrics_util.h",
"password_manager_util.cc",
"password_manager_util.h",
"password_requirements_service.cc",
"password_requirements_service.h",
"password_reuse_defines.h",
"password_store.cc",
"password_store.h",
......@@ -384,6 +386,7 @@ source_set("unit_tests") {
"password_manager_metrics_recorder_unittest.cc",
"password_manager_unittest.cc",
"password_manager_util_unittest.cc",
"password_requirements_service_unittest.cc",
"password_store_default_unittest.cc",
"password_store_origin_unittest.h",
"password_store_unittest.cc",
......
// Copyright 2018 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/password_requirements_service.h"
#include "base/bind.h"
#include "base/logging.h"
namespace {
constexpr size_t kCacheSizeForDomainKeyedSpecs = 200;
constexpr size_t kCacheSizeForSignatureKeyedSpecs = 500;
} // namespace
namespace password_manager {
PasswordRequirementsService::PasswordRequirementsService(
std::unique_ptr<autofill::PasswordRequirementsSpecFetcher> fetcher)
: specs_for_domains_(kCacheSizeForDomainKeyedSpecs),
specs_for_signatures_(kCacheSizeForSignatureKeyedSpecs),
fetcher_(std::move(fetcher)) {
DCHECK(fetcher_);
}
PasswordRequirementsService::~PasswordRequirementsService() = default;
autofill::PasswordRequirementsSpec PasswordRequirementsService::GetSpec(
const GURL& main_frame_domain,
autofill::FormSignature form_signature,
autofill::FieldSignature field_signature) {
autofill::PasswordRequirementsSpec result;
auto iter_by_signature = specs_for_signatures_.Get(
std::make_pair(form_signature, field_signature));
bool found_item_by_signature =
iter_by_signature != specs_for_signatures_.end();
if (found_item_by_signature) {
result = iter_by_signature->second;
}
auto iter_by_domain = specs_for_domains_.Get(main_frame_domain);
if (iter_by_domain != specs_for_domains_.end()) {
const autofill::PasswordRequirementsSpec& spec = iter_by_domain->second;
if (!found_item_by_signature) {
// If nothing was found by signature, |spec| can be adopted.
result = spec;
} else if (spec.has_priority() && (!result.has_priority() ||
spec.priority() > result.priority())) {
// If something was found by signature, override with |spec| only in case
// the priority of |spec| exceeds the priority of the data found by
// signature.
result = spec;
}
}
return result;
}
void PasswordRequirementsService::PrefetchSpec(const GURL& main_frame_domain) {
// Using base::Unretained(this) is safe here because the
// PasswordRequirementsService owns fetcher_. If |this| is deleted, so is
// the |fetcher_|, and no callback can happen.
fetcher_->Fetch(
main_frame_domain,
base::BindOnce(&PasswordRequirementsService::OnFetchedRequirements,
base::Unretained(this), main_frame_domain));
}
void PasswordRequirementsService::OnFetchedRequirements(
const GURL& main_frame_domain,
const autofill::PasswordRequirementsSpec& spec) {
specs_for_domains_.Put(main_frame_domain, spec);
}
void PasswordRequirementsService::AddSpec(
autofill::FormSignature form_signature,
autofill::FieldSignature field_signature,
const autofill::PasswordRequirementsSpec& spec) {
specs_for_signatures_.Put(std::make_pair(form_signature, field_signature),
spec);
}
} // namespace password_manager
// Copyright 2018 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.
#ifndef COMPONENTS_PASSWORD_MANAGER_CORE_BROWSER_PASSWORD_REQUIREMENTS_SERVICE_H_
#define COMPONENTS_PASSWORD_MANAGER_CORE_BROWSER_PASSWORD_REQUIREMENTS_SERVICE_H_
#include <memory>
#include <utility>
#include "base/containers/mru_cache.h"
#include "components/autofill/core/browser/password_requirements_spec_fetcher.h"
#include "components/autofill/core/browser/proto/password_requirements.pb.h"
#include "components/autofill/core/common/signatures_util.h"
#include "components/keyed_service/core/keyed_service.h"
#include "url/gurl.h"
namespace autofill {
class PasswordRequirementsSpec;
}
namespace password_manager {
// A service that fetches, stores and returns requirements for generating a
// random password on a specific form and site.
class PasswordRequirementsService : public KeyedService {
public:
explicit PasswordRequirementsService(
std::unique_ptr<autofill::PasswordRequirementsSpecFetcher> fetcher);
~PasswordRequirementsService() override;
// Returns the password requirements for a field that appears on a site
// with domain |main_frame_domain| and has the specified |form_signature|
// and |field_signature|.
//
// This function returns synchronously and only returns results if these
// have been retrieved via the Add/Prefetch methods below and the data is
// still in the cache.
autofill::PasswordRequirementsSpec GetSpec(
const GURL& main_frame_domain,
autofill::FormSignature form_signature,
autofill::FieldSignature field_signature);
// Triggers a fetch for password requirements for the domain passed in
// |main_frame_domain| and stores it into the MRU cache.
void PrefetchSpec(const GURL& main_frame_domain);
// Stores the password requirements for the field identified via
// |form_signature| and |field_signature| in the MRU cache.
void AddSpec(autofill::FormSignature form_signature,
autofill::FieldSignature field_signature,
const autofill::PasswordRequirementsSpec& spec);
private:
void OnFetchedRequirements(const GURL& main_frame_domain,
const autofill::PasswordRequirementsSpec& spec);
using FullSignature =
std::pair<autofill::FormSignature, autofill::FieldSignature>;
base::MRUCache<GURL, autofill::PasswordRequirementsSpec> specs_for_domains_;
base::MRUCache<FullSignature, autofill::PasswordRequirementsSpec>
specs_for_signatures_;
std::unique_ptr<autofill::PasswordRequirementsSpecFetcher> fetcher_;
DISALLOW_COPY_AND_ASSIGN(PasswordRequirementsService);
};
} // namespace password_manager
#endif // COMPONENTS_PASSWORD_MANAGER_CORE_BROWSER_PASSWORD_REQUIREMENTS_SERVICE_H_
// Copyright 2018 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/password_requirements_service.h"
#include <map>
#include "base/logging.h"
#include "base/test/bind_test_util.h"
#include "components/autofill/core/browser/password_requirements_spec_fetcher.h"
#include "components/autofill/core/browser/proto/password_requirements.pb.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace password_manager {
namespace {
class MockPasswordRequirementsSpecFetcher
: public autofill::PasswordRequirementsSpecFetcher {
public:
MockPasswordRequirementsSpecFetcher() = default;
~MockPasswordRequirementsSpecFetcher() override = default;
void Fetch(GURL origin, FetchCallback callback) override {
auto iter = data_to_return_.find(origin);
std::move(callback).Run(iter != data_to_return_.end()
? iter->second
: autofill::PasswordRequirementsSpec());
}
void SetDataToReturn(const GURL& origin,
const autofill::PasswordRequirementsSpec& spec) {
data_to_return_[origin] = spec;
}
private:
std::map<GURL, autofill::PasswordRequirementsSpec> data_to_return_;
};
class PasswordRequirementsServiceTest : public testing::Test {
public:
PasswordRequirementsServiceTest()
: test_origin_("http://www.example.com"),
// Ownership is passed to the |service_| below.
fetcher_ptr_(new MockPasswordRequirementsSpecFetcher()),
service_(std::unique_ptr<MockPasswordRequirementsSpecFetcher>(
fetcher_ptr_)) {}
~PasswordRequirementsServiceTest() override = default;
protected:
// Prepopulated test data.
GURL test_origin_;
autofill::FormSignature test_form_signature_ = 123;
autofill::FieldSignature test_field_signature_ = 22;
// Weak pointer.
MockPasswordRequirementsSpecFetcher* fetcher_ptr_;
PasswordRequirementsService service_;
};
TEST_F(PasswordRequirementsServiceTest, ExerciseEverything) {
// The following specs are names according to the following scheme:
// spec_l${max_length value}_p${priority value}
// values of 0 imply that no value is specified.
// It would be possible to test the behavior with fewer instances than below
// but these are chosen to be representative of what we expect the server
// to send with regards to priorities.
autofill::PasswordRequirementsSpec spec_l0_p0; // empty spec.
autofill::PasswordRequirementsSpec spec_l7_p0;
spec_l7_p0.set_max_length(7u);
autofill::PasswordRequirementsSpec spec_l8_p10;
spec_l8_p10.set_max_length(8u);
spec_l8_p10.set_priority(10);
autofill::PasswordRequirementsSpec spec_l9_p20;
spec_l9_p20.set_max_length(9u);
spec_l9_p20.set_priority(20);
autofill::PasswordRequirementsSpec spec_l10_p30;
spec_l10_p30.set_max_length(10u);
spec_l10_p30.set_priority(30);
struct {
const char* test_name;
autofill::PasswordRequirementsSpec* spec_for_signature = nullptr;
autofill::PasswordRequirementsSpec* spec_for_domain = nullptr;
autofill::PasswordRequirementsSpec* expected;
} tests[] = {
{
.test_name = "No data prefechted", .expected = &spec_l0_p0,
},
{
.test_name = "Only domain wide spec",
.spec_for_domain = &spec_l7_p0,
.expected = &spec_l7_p0,
},
{
.test_name = "Only signature based spec",
.spec_for_signature = &spec_l7_p0,
.expected = &spec_l7_p0,
},
{
.test_name = "Domain spec can override spec based on signature",
.spec_for_signature = &spec_l8_p10,
.spec_for_domain = &spec_l9_p20,
.expected = &spec_l9_p20, // priority 20 trumps priority 10.
},
{
.test_name = "Signature spec can override spec based on domain",
.spec_for_signature = &spec_l10_p30,
.spec_for_domain = &spec_l9_p20,
.expected = &spec_l10_p30, // priority 30 trumps priority 20.
},
{
.test_name = "Dealing with unset priority in domain",
.spec_for_signature = &spec_l8_p10,
.spec_for_domain = &spec_l7_p0, // No prioritiy specified.
.expected = &spec_l8_p10,
},
{
.test_name = "Dealing with unset priority in signature",
.spec_for_signature = &spec_l7_p0, // No prioritiy specified.
.spec_for_domain = &spec_l8_p10,
.expected = &spec_l8_p10,
},
};
for (const auto& test : tests) {
SCOPED_TRACE(test.test_name);
// Populate the service with data.
if (test.spec_for_domain) {
fetcher_ptr_->SetDataToReturn(test_origin_, *(test.spec_for_domain));
service_.PrefetchSpec(test_origin_);
}
if (test.spec_for_signature) {
service_.AddSpec(test_form_signature_, test_field_signature_,
*(test.spec_for_signature));
}
// Perform lookup.
auto result = service_.GetSpec(test_origin_, test_form_signature_,
test_field_signature_);
// Validate answer.
EXPECT_EQ(test.expected->has_priority(), result.has_priority());
if (test.expected->has_priority()) {
EXPECT_EQ(test.expected->priority(), result.priority());
}
EXPECT_EQ(test.expected->has_max_length(), result.has_max_length());
if (test.expected->has_max_length()) {
EXPECT_EQ(test.expected->max_length(), result.max_length());
}
}
}
} // namespace
} // namespace password_manager
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