Commit 200bd3bd authored by Andy Paicu's avatar Andy Paicu Committed by Commit Bot

Move over origin policy fetching logic to the network service

This is step 3 of https://docs.google.com/document/d/1heiIgNdO7kbaU9BLOPO4wZ9maHScB87cGT5lyjeBVAM/edit#heading=h.4en9va43fgfj

Bug: 950905
Change-Id: I756b531abbab7510384097da1a2b5c003ad1812d
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1602643
Auto-Submit: Andy Paicu <andypaicu@chromium.org>
Commit-Queue: Andy Paicu <andypaicu@chromium.org>
Reviewed-by: default avatarAlex Moshchuk <alexmos@chromium.org>
Reviewed-by: default avatarMatt Menke <mmenke@chromium.org>
Reviewed-by: default avatarMike West <mkwst@chromium.org>
Reviewed-by: default avatarDaniel Vogelheim <vogelheim@chromium.org>
Reviewed-by: default avatarDaniel Cheng <dcheng@chromium.org>
Cr-Commit-Position: refs/heads/master@{#662593}
parent 39fc6575
...@@ -13,20 +13,13 @@ ...@@ -13,20 +13,13 @@
#include "base/macros.h" #include "base/macros.h"
#include "base/memory/scoped_refptr.h" #include "base/memory/scoped_refptr.h"
#include "content/public/browser/navigation_throttle.h" #include "content/public/browser/navigation_throttle.h"
#include "services/network/public/mojom/url_loader_factory.mojom.h" #include "services/network/public/mojom/origin_policy_manager.mojom.h"
class GURL; class GURL;
namespace url { namespace url {
class Origin; class Origin;
} }
namespace net {
struct RedirectInfo;
} // namespace net
namespace network {
struct ResourceResponseHead;
class SimpleURLLoader;
} // namespace network
namespace content { namespace content {
class NavigationHandle; class NavigationHandle;
...@@ -77,12 +70,8 @@ class CONTENT_EXPORT OriginPolicyThrottle : public NavigationThrottle { ...@@ -77,12 +70,8 @@ class CONTENT_EXPORT OriginPolicyThrottle : public NavigationThrottle {
using KnownVersionMap = std::map<url::Origin, std::string>; using KnownVersionMap = std::map<url::Origin, std::string>;
static KnownVersionMap& GetKnownVersionsForTesting(); static KnownVersionMap& GetKnownVersionsForTesting();
void InjectPolicyForTesting(const std::string& policy_content); // TODO(andypaicu): Remove this when we move the store to the network
// service layer.
void SetURLLoaderFactoryForTesting(
std::unique_ptr<network::mojom::URLLoaderFactory>
url_loader_factory_for_testing);
static PolicyVersionAndReportTo static PolicyVersionAndReportTo
GetRequestedPolicyAndReportGroupFromHeaderStringForTesting( GetRequestedPolicyAndReportGroupFromHeaderStringForTesting(
const std::string& header); const std::string& header);
...@@ -90,12 +79,6 @@ class CONTENT_EXPORT OriginPolicyThrottle : public NavigationThrottle { ...@@ -90,12 +79,6 @@ class CONTENT_EXPORT OriginPolicyThrottle : public NavigationThrottle {
static bool IsExemptedForTesting(const url::Origin& origin); static bool IsExemptedForTesting(const url::Origin& origin);
private: private:
using FetchCallback = base::OnceCallback<void(std::unique_ptr<std::string>)>;
using RedirectCallback =
base::RepeatingCallback<void(const net::RedirectInfo&,
const network::ResourceResponseHead&,
std::vector<std::string>*)>;
explicit OriginPolicyThrottle(NavigationHandle* handle); explicit OriginPolicyThrottle(NavigationHandle* handle);
static KnownVersionMap& GetKnownVersions(); static KnownVersionMap& GetKnownVersions();
...@@ -106,25 +89,13 @@ class CONTENT_EXPORT OriginPolicyThrottle : public NavigationThrottle { ...@@ -106,25 +89,13 @@ class CONTENT_EXPORT OriginPolicyThrottle : public NavigationThrottle {
GetRequestedPolicyAndReportGroupFromHeaderString(const std::string& header); GetRequestedPolicyAndReportGroupFromHeaderString(const std::string& header);
const url::Origin GetRequestOrigin() const; const url::Origin GetRequestOrigin() const;
const GURL GetPolicyURL(const std::string& version) const;
void FetchPolicy(const GURL& url, void CancelNavigation(OriginPolicyErrorReason reason, const GURL& policy_url);
FetchCallback done,
RedirectCallback redirect); void Report(OriginPolicyErrorReason reason, const GURL& policy_url);
void OnTheGloriousPolicyHasArrived(
std::unique_ptr<std::string> policy_content); void OnOriginPolicyManagerRetrieveDone(
void OnRedirect(const net::RedirectInfo& redirect_info, const network::mojom::OriginPolicyPtr origin_policy);
const network::ResourceResponseHead& response_head,
std::vector<std::string>* to_be_removed_headers);
void CancelNavigation(OriginPolicyErrorReason reason);
void Report(OriginPolicyErrorReason reason);
// We may need the SimpleURLLoader to download the policy. The loader must
// be kept alive while the load is ongoing.
std::unique_ptr<network::SimpleURLLoader> url_loader_;
std::unique_ptr<network::mojom::URLLoaderFactory>
url_loader_factory_for_testing_;
DISALLOW_COPY_AND_ASSIGN(OriginPolicyThrottle); DISALLOW_COPY_AND_ASSIGN(OriginPolicyThrottle);
}; };
......
...@@ -4,19 +4,29 @@ ...@@ -4,19 +4,29 @@
#include "content/browser/frame_host/origin_policy_throttle.h" #include "content/browser/frame_host/origin_policy_throttle.h"
#include <utility>
#include "base/feature_list.h" #include "base/feature_list.h"
#include "base/macros.h" #include "base/macros.h"
#include "base/test/scoped_feature_list.h" #include "base/test/scoped_feature_list.h"
#include "content/browser/frame_host/navigation_handle_impl.h" #include "content/browser/frame_host/navigation_handle_impl.h"
#include "content/browser/storage_partition_impl.h"
#include "content/browser/web_contents/web_contents_impl.h" #include "content/browser/web_contents/web_contents_impl.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/storage_partition.h"
#include "content/public/common/content_features.h" #include "content/public/common/content_features.h"
#include "content/public/test/mock_navigation_handle.h" #include "content/public/test/mock_navigation_handle.h"
#include "content/public/test/navigation_simulator.h" #include "content/public/test/navigation_simulator.h"
#include "content/public/test/test_renderer_host.h" #include "content/public/test/test_renderer_host.h"
#include "net/http/http_util.h" #include "net/http/http_util.h"
#include "services/network/test/test_url_loader_factory.h" #include "services/network/test/test_url_loader_factory.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h" #include "testing/gtest/include/gtest/gtest.h"
namespace {
constexpr const char kExampleManifestString[] = "{}";
}
namespace content { namespace content {
class OriginPolicyThrottleTest : public RenderViewHostTestHarness, class OriginPolicyThrottleTest : public RenderViewHostTestHarness,
...@@ -56,6 +66,38 @@ class OriginPolicyThrottleTest : public RenderViewHostTestHarness, ...@@ -56,6 +66,38 @@ class OriginPolicyThrottleTest : public RenderViewHostTestHarness,
base::test::ScopedFeatureList features_; base::test::ScopedFeatureList features_;
}; };
class TestOriginPolicyManager : public network::mojom::OriginPolicyManager {
public:
void RetrieveOriginPolicy(const url::Origin& origin,
const std::string& header_value,
RetrieveOriginPolicyCallback callback) override {
auto result = network::mojom::OriginPolicy::New();
result->state = network::mojom::OriginPolicyState::kLoaded;
result->contents = network::mojom::OriginPolicyContents::New();
result->contents->raw_policy = kExampleManifestString;
result->policy_url = origin.GetURL();
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(&TestOriginPolicyManager::RunCallback,
base::Unretained(this), std::move(callback),
std::move(result)));
}
void RunCallback(RetrieveOriginPolicyCallback callback,
network::mojom::OriginPolicyPtr result) {
std::move(callback).Run(std::move(result));
}
network::mojom::OriginPolicyManagerPtr GetPtr() {
network::mojom::OriginPolicyManagerPtr ptr;
auto request = mojo::MakeRequest(&ptr);
binding_ =
std::make_unique<mojo::Binding<network::mojom::OriginPolicyManager>>(
this, std::move(request));
return ptr;
}
std::unique_ptr<mojo::Binding<network::mojom::OriginPolicyManager>> binding_;
};
INSTANTIATE_TEST_SUITE_P(OriginPolicyThrottleTests, INSTANTIATE_TEST_SUITE_P(OriginPolicyThrottleTests,
OriginPolicyThrottleTest, OriginPolicyThrottleTest,
testing::Bool()); testing::Bool());
...@@ -107,29 +149,37 @@ TEST_P(OriginPolicyThrottleTest, RunRequestEndToEnd) { ...@@ -107,29 +149,37 @@ TEST_P(OriginPolicyThrottleTest, RunRequestEndToEnd) {
"HTTP/1.1 200 OK\nSec-Origin-Policy: policy=policy-1\n\n"; "HTTP/1.1 200 OK\nSec-Origin-Policy: policy=policy-1\n\n";
auto headers = base::MakeRefCounted<net::HttpResponseHeaders>( auto headers = base::MakeRefCounted<net::HttpResponseHeaders>(
net::HttpUtil::AssembleRawHeaders(raw_headers)); net::HttpUtil::AssembleRawHeaders(raw_headers));
// We set a test origin policy manager as during unit tests we can't reach
// the network service to retrieve a valid origin policy manager.
TestOriginPolicyManager test_origin_policy_manager;
NavigationHandleImpl* nav_handle = NavigationHandleImpl* nav_handle =
static_cast<NavigationHandleImpl*>(navigation->GetNavigationHandle()); static_cast<NavigationHandleImpl*>(navigation->GetNavigationHandle());
SiteInstance* site_instance = nav_handle->GetStartingSiteInstance();
static_cast<StoragePartitionImpl*>(
BrowserContext::GetStoragePartition(site_instance->GetBrowserContext(),
site_instance))
->SetOriginPolicyManagerForBrowserProcessForTesting(
test_origin_policy_manager.GetPtr());
nav_handle->set_response_headers_for_testing(headers); nav_handle->set_response_headers_for_testing(headers);
navigation->ReadyToCommit(); navigation->ReadyToCommit();
EXPECT_TRUE(navigation->IsDeferred()); EXPECT_TRUE(navigation->IsDeferred());
// Set TestURLLoaderFactory because we could not make actual network requests
// in this unit tests, but this test would make.
OriginPolicyThrottle* policy_throttle = static_cast<OriginPolicyThrottle*>( OriginPolicyThrottle* policy_throttle = static_cast<OriginPolicyThrottle*>(
nav_handle->GetDeferringThrottleForTesting()); nav_handle->GetDeferringThrottleForTesting());
EXPECT_TRUE(policy_throttle); EXPECT_TRUE(policy_throttle);
policy_throttle->SetURLLoaderFactoryForTesting(
std::make_unique<network::TestURLLoaderFactory>());
// For the purpose of this unit test we don't care about policy content, // Wait until the navigation has been allowed to proceed.
// only that it's non-empty. We check whether the throttle will pass it on. navigation->Wait();
const char* policy = "{}";
policy_throttle->InjectPolicyForTesting(policy);
// At the end of the navigation, the navigation handle should have a copy // At the end of the navigation, the navigation handle should have a copy
// of the origin policy. // of the origin policy.
EXPECT_EQ(policy, EXPECT_EQ(kExampleManifestString,
nav_handle->navigation_request()->common_params().origin_policy); nav_handle->navigation_request()->common_params().origin_policy);
static_cast<StoragePartitionImpl*>(
BrowserContext::GetStoragePartition(site_instance->GetBrowserContext(),
site_instance))
->ResetOriginPolicyManagerForBrowserProcessForTesting();
} }
TEST_P(OriginPolicyThrottleTest, AddException) { TEST_P(OriginPolicyThrottleTest, AddException) {
......
...@@ -1403,6 +1403,8 @@ void StoragePartitionImpl::FlushNetworkInterfaceForTesting() { ...@@ -1403,6 +1403,8 @@ void StoragePartitionImpl::FlushNetworkInterfaceForTesting() {
url_loader_factory_for_browser_process_.FlushForTesting(); url_loader_factory_for_browser_process_.FlushForTesting();
if (cookie_manager_for_browser_process_) if (cookie_manager_for_browser_process_)
cookie_manager_for_browser_process_.FlushForTesting(); cookie_manager_for_browser_process_.FlushForTesting();
if (origin_policy_manager_for_browser_process_)
origin_policy_manager_for_browser_process_.FlushForTesting();
} }
void StoragePartitionImpl::WaitForDeletionTasksForTesting() { void StoragePartitionImpl::WaitForDeletionTasksForTesting() {
...@@ -1513,4 +1515,25 @@ StoragePartitionImpl::GetURLLoaderFactoryForBrowserProcessInternal() { ...@@ -1513,4 +1515,25 @@ StoragePartitionImpl::GetURLLoaderFactoryForBrowserProcessInternal() {
return url_loader_factory_for_browser_process_.get(); return url_loader_factory_for_browser_process_.get();
} }
network::mojom::OriginPolicyManager*
StoragePartitionImpl::GetOriginPolicyManagerForBrowserProcess() {
if (!origin_policy_manager_for_browser_process_ ||
origin_policy_manager_for_browser_process_.encountered_error()) {
GetNetworkContext()->GetOriginPolicyManager(
mojo::MakeRequest(&origin_policy_manager_for_browser_process_));
}
return origin_policy_manager_for_browser_process_.get();
}
void StoragePartitionImpl::SetOriginPolicyManagerForBrowserProcessForTesting(
network::mojom::OriginPolicyManagerPtr test_origin_policy_manager) {
origin_policy_manager_for_browser_process_ =
std::move(test_origin_policy_manager);
}
void StoragePartitionImpl::
ResetOriginPolicyManagerForBrowserProcessForTesting() {
origin_policy_manager_for_browser_process_ = nullptr;
}
} // namespace content } // namespace content
...@@ -147,7 +147,6 @@ class CONTENT_EXPORT StoragePartitionImpl ...@@ -147,7 +147,6 @@ class CONTENT_EXPORT StoragePartitionImpl
void ClearBluetoothAllowedDevicesMapForTesting() override; void ClearBluetoothAllowedDevicesMapForTesting() override;
void FlushNetworkInterfaceForTesting() override; void FlushNetworkInterfaceForTesting() override;
void WaitForDeletionTasksForTesting() override; void WaitForDeletionTasksForTesting() override;
BackgroundFetchContext* GetBackgroundFetchContext(); BackgroundFetchContext* GetBackgroundFetchContext();
PaymentAppContextImpl* GetPaymentAppContext(); PaymentAppContextImpl* GetPaymentAppContext();
BroadcastChannelProvider* GetBroadcastChannelProvider(); BroadcastChannelProvider* GetBroadcastChannelProvider();
...@@ -210,6 +209,15 @@ class CONTENT_EXPORT StoragePartitionImpl ...@@ -210,6 +209,15 @@ class CONTENT_EXPORT StoragePartitionImpl
return site_for_service_worker_; return site_for_service_worker_;
} }
// Use the network context to retrieve the origin policy manager.
network::mojom::OriginPolicyManager*
GetOriginPolicyManagerForBrowserProcess();
// Override the origin policy manager for testing use only.
void SetOriginPolicyManagerForBrowserProcessForTesting(
network::mojom::OriginPolicyManagerPtr test_origin_policy_manager);
void ResetOriginPolicyManagerForBrowserProcessForTesting();
private: private:
class DataDeletionHelper; class DataDeletionHelper;
class QuotaManagedDataDeletionHelper; class QuotaManagedDataDeletionHelper;
...@@ -378,6 +386,8 @@ class CONTENT_EXPORT StoragePartitionImpl ...@@ -378,6 +386,8 @@ class CONTENT_EXPORT StoragePartitionImpl
network::mojom::URLLoaderFactoryPtr url_loader_factory_for_browser_process_; network::mojom::URLLoaderFactoryPtr url_loader_factory_for_browser_process_;
bool is_test_url_loader_factory_for_browser_process_ = false; bool is_test_url_loader_factory_for_browser_process_ = false;
network::mojom::CookieManagerPtr cookie_manager_for_browser_process_; network::mojom::CookieManagerPtr cookie_manager_for_browser_process_;
network::mojom::OriginPolicyManagerPtr
origin_policy_manager_for_browser_process_;
// When the network service is disabled, a NetworkContext is created on the IO // When the network service is disabled, a NetworkContext is created on the IO
// thread that wraps access to the URLRequestContext. // thread that wraps access to the URLRequestContext.
......
...@@ -83,6 +83,9 @@ jumbo_component("network_service") { ...@@ -83,6 +83,9 @@ jumbo_component("network_service") {
"network_service_proxy_delegate.h", "network_service_proxy_delegate.h",
"network_usage_accumulator.cc", "network_usage_accumulator.cc",
"network_usage_accumulator.h", "network_usage_accumulator.h",
"origin_policy/origin_policy_constants.h",
"origin_policy/origin_policy_fetcher.cc",
"origin_policy/origin_policy_fetcher.h",
"origin_policy/origin_policy_manager.cc", "origin_policy/origin_policy_manager.cc",
"origin_policy/origin_policy_manager.h", "origin_policy/origin_policy_manager.h",
"p2p/socket.cc", "p2p/socket.cc",
...@@ -308,6 +311,7 @@ source_set("tests") { ...@@ -308,6 +311,7 @@ source_set("tests") {
"network_service_proxy_delegate_unittest.cc", "network_service_proxy_delegate_unittest.cc",
"network_service_unittest.cc", "network_service_unittest.cc",
"network_usage_accumulator_unittest.cc", "network_usage_accumulator_unittest.cc",
"origin_policy/origin_policy_fetcher_unittest.cc",
"origin_policy/origin_policy_manager_unittest.cc", "origin_policy/origin_policy_manager_unittest.cc",
"p2p/socket_tcp_server_unittest.cc", "p2p/socket_tcp_server_unittest.cc",
"p2p/socket_tcp_unittest.cc", "p2p/socket_tcp_unittest.cc",
......
...@@ -532,6 +532,9 @@ NetworkContext::NetworkContext( ...@@ -532,6 +532,9 @@ NetworkContext::NetworkContext(
resource_scheduler_ = resource_scheduler_ =
std::make_unique<ResourceScheduler>(enable_resource_scheduler_); std::make_unique<ResourceScheduler>(enable_resource_scheduler_);
origin_policy_manager_ = std::make_unique<OriginPolicyManager>(
CreateUrlLoaderFactoryForNetworkService());
InitializeCorsParams(); InitializeCorsParams();
} }
...@@ -560,6 +563,9 @@ NetworkContext::NetworkContext( ...@@ -560,6 +563,9 @@ NetworkContext::NetworkContext(
resource_scheduler_ = resource_scheduler_ =
std::make_unique<ResourceScheduler>(enable_resource_scheduler_); std::make_unique<ResourceScheduler>(enable_resource_scheduler_);
origin_policy_manager_ = std::make_unique<OriginPolicyManager>(
CreateUrlLoaderFactoryForNetworkService());
InitializeCorsParams(); InitializeCorsParams();
} }
...@@ -590,6 +596,9 @@ NetworkContext::NetworkContext( ...@@ -590,6 +596,9 @@ NetworkContext::NetworkContext(
for (const auto& key : cors_exempt_header_list) for (const auto& key : cors_exempt_header_list)
cors_exempt_header_list_.insert(key); cors_exempt_header_list_.insert(key);
origin_policy_manager_ = std::make_unique<OriginPolicyManager>(
CreateUrlLoaderFactoryForNetworkService());
} }
NetworkContext::~NetworkContext() { NetworkContext::~NetworkContext() {
...@@ -679,11 +688,11 @@ void NetworkContext::SetClient(mojom::NetworkContextClientPtr client) { ...@@ -679,11 +688,11 @@ void NetworkContext::SetClient(mojom::NetworkContextClientPtr client) {
void NetworkContext::CreateURLLoaderFactory( void NetworkContext::CreateURLLoaderFactory(
mojom::URLLoaderFactoryRequest request, mojom::URLLoaderFactoryRequest request,
mojom::URLLoaderFactoryParamsPtr params) { mojom::URLLoaderFactoryParamsPtr params) {
scoped_refptr<ResourceSchedulerClient> resource_scheduler_client = scoped_refptr<ResourceSchedulerClient> resource_scheduler_client;
base::MakeRefCounted<ResourceSchedulerClient>( resource_scheduler_client = base::MakeRefCounted<ResourceSchedulerClient>(
params->process_id, ++current_resource_scheduler_client_id_, params->process_id, ++current_resource_scheduler_client_id_,
resource_scheduler_.get(), resource_scheduler_.get(),
url_request_context_->network_quality_estimator()); url_request_context_->network_quality_estimator());
CreateURLLoaderFactory(std::move(request), std::move(params), CreateURLLoaderFactory(std::move(request), std::move(params),
std::move(resource_scheduler_client)); std::move(resource_scheduler_client));
} }
...@@ -2311,7 +2320,17 @@ void NetworkContext::InitializeCorsParams() { ...@@ -2311,7 +2320,17 @@ void NetworkContext::InitializeCorsParams() {
void NetworkContext::GetOriginPolicyManager( void NetworkContext::GetOriginPolicyManager(
mojom::OriginPolicyManagerRequest request) { mojom::OriginPolicyManagerRequest request) {
origin_policy_manager_.AddBinding(std::move(request)); origin_policy_manager_->AddBinding(std::move(request));
}
mojom::URLLoaderFactoryPtr
NetworkContext::CreateUrlLoaderFactoryForNetworkService() {
auto url_loader_factory_params = mojom::URLLoaderFactoryParams::New();
url_loader_factory_params->process_id = network::mojom::kBrowserProcessId;
mojom::URLLoaderFactoryPtr url_loader_factory;
CreateURLLoaderFactory(mojo::MakeRequest(&url_loader_factory),
std::move(url_loader_factory_params));
return url_loader_factory;
} }
} // namespace network } // namespace network
...@@ -405,6 +405,10 @@ class COMPONENT_EXPORT(NETWORK_SERVICE) NetworkContext ...@@ -405,6 +405,10 @@ class COMPONENT_EXPORT(NETWORK_SERVICE) NetworkContext
// Gather active URLLoader count. // Gather active URLLoader count.
size_t GatherActiveLoaderCount(); size_t GatherActiveLoaderCount();
// Creates a new url loader factory bound to this network context. For use
// inside the network service.
mojom::URLLoaderFactoryPtr CreateUrlLoaderFactoryForNetworkService();
private: private:
class ContextNetworkDelegate; class ContextNetworkDelegate;
...@@ -604,7 +608,7 @@ class COMPONENT_EXPORT(NETWORK_SERVICE) NetworkContext ...@@ -604,7 +608,7 @@ class COMPONENT_EXPORT(NETWORK_SERVICE) NetworkContext
std::unique_ptr<domain_reliability::DomainReliabilityMonitor> std::unique_ptr<domain_reliability::DomainReliabilityMonitor>
domain_reliability_monitor_; domain_reliability_monitor_;
OriginPolicyManager origin_policy_manager_; std::unique_ptr<OriginPolicyManager> origin_policy_manager_;
DISALLOW_COPY_AND_ASSIGN(NetworkContext); DISALLOW_COPY_AND_ASSIGN(NetworkContext);
}; };
......
// Copyright 2019 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 SERVICES_NETWORK_ORIGIN_POLICY_ORIGIN_POLICY_CONSTANTS_H_
#define SERVICES_NETWORK_ORIGIN_POLICY_ORIGIN_POLICY_CONSTANTS_H_
namespace network {
const char kOriginPolicyWellKnown[] = "/.well-known/origin-policy";
const char kOriginPolicyDeletePolicy[] = "0";
const char kOriginPolicyReportTo[] = "report-to";
const char kOriginPolicyPolicy[] = "policy";
// Maximum policy size (implementation-defined limit in bytes).
// (Limit copied from network::SimpleURLLoader::kMaxBoundedStringDownloadSize.)
static const size_t kOriginPolicyMaxPolicySize = 1024 * 1024;
} // namespace network
#endif // SERVICES_NETWORK_ORIGIN_POLICY_ORIGIN_POLICY_CONSTANTS_H_
// Copyright 2019 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 "services/network/origin_policy/origin_policy_fetcher.h"
#include <utility>
#include "base/strings/strcat.h"
#include "net/base/load_flags.h"
#include "net/http/http_util.h"
#include "services/network/origin_policy/origin_policy_constants.h"
#include "services/network/origin_policy/origin_policy_manager.h"
#include "services/network/public/cpp/resource_response.h"
#include "services/network/public/cpp/simple_url_loader.h"
#include "services/network/public/mojom/url_loader_factory.mojom.h"
namespace network {
OriginPolicyFetcher::OriginPolicyFetcher(
OriginPolicyManager* owner_policy_manager,
const std::string& policy_version,
const std::string& report_to,
const url::Origin& origin,
mojom::URLLoaderFactory* factory,
mojom::OriginPolicyManager::RetrieveOriginPolicyCallback callback)
: owner_policy_manager_(owner_policy_manager),
report_to_(report_to),
fetch_url_(GetPolicyURL(policy_version, origin)),
callback_(std::move(callback)),
must_redirect_(false) {
DCHECK(callback_);
DCHECK(!policy_version.empty());
FetchPolicy(factory);
}
OriginPolicyFetcher::OriginPolicyFetcher(
OriginPolicyManager* owner_policy_manager,
const std::string& report_to,
const url::Origin& origin,
mojom::URLLoaderFactory* factory,
mojom::OriginPolicyManager::RetrieveOriginPolicyCallback callback)
: owner_policy_manager_(owner_policy_manager),
report_to_(report_to),
fetch_url_(GetDefaultPolicyURL(origin)),
callback_(std::move(callback)),
must_redirect_(true) {
DCHECK(callback_);
FetchPolicy(factory);
}
OriginPolicyFetcher::~OriginPolicyFetcher() {}
// static
GURL OriginPolicyFetcher::GetPolicyURL(const std::string& version,
const url::Origin& origin) {
// TODO(vogelheim): update this check when the origin policy spec is updated
// to clearly specify the proper sanitization of a version.
if (!net::HttpUtil::IsToken(version))
return GURL();
return GURL(
base::StrCat({origin.Serialize(), kOriginPolicyWellKnown, "/", version}));
}
// static
GURL OriginPolicyFetcher::GetDefaultPolicyURL(const url::Origin& origin) {
return GURL(base::StrCat({origin.Serialize(), kOriginPolicyWellKnown}));
}
bool OriginPolicyFetcher::IsValidRedirectForTesting(
const net::RedirectInfo& redirect_info) const {
return IsValidRedirect(redirect_info);
}
void OriginPolicyFetcher::OnPolicyHasArrived(
std::unique_ptr<std::string> policy_content) {
// Fail hard if the policy could not be loaded.
if (!policy_content || must_redirect_) {
Report(mojom::OriginPolicyState::kCannotLoadPolicy);
WorkDone(nullptr, mojom::OriginPolicyState::kCannotLoadPolicy);
} else {
WorkDone(std::move(policy_content), mojom::OriginPolicyState::kLoaded);
}
}
void OriginPolicyFetcher::OnPolicyRedirect(
const net::RedirectInfo& redirect_info,
const network::ResourceResponseHead& response_head,
std::vector<std::string>* to_be_removed_headers) {
if (IsValidRedirect(redirect_info)) {
must_redirect_ = false;
// TODO(andypaicu): should we callback with the original url or the new url?
fetch_url_ = redirect_info.new_url;
return;
}
// Fail hard if the policy response follows an invalid redirect.
Report(mojom::OriginPolicyState::kInvalidRedirect);
WorkDone(nullptr, mojom::OriginPolicyState::kInvalidRedirect);
}
void OriginPolicyFetcher::FetchPolicy(mojom::URLLoaderFactory* factory) {
// Create the traffic annotation
net::NetworkTrafficAnnotationTag traffic_annotation =
net::DefineNetworkTrafficAnnotation("origin_policy_loader", R"(
semantics {
sender: "Origin Policy URL Loader Throttle"
description:
"Fetches the Origin Policy with a given version from an origin."
trigger:
"In case the Origin Policy with a given version does not "
"exist in the cache, it is fetched from the origin at a "
"well-known location."
data:
"None, the URL itself contains the origin and Origin Policy "
"version."
destination: OTHER
}
policy {
cookies_allowed: NO
setting: "This feature cannot be disabled by settings. Server "
"opt-in or out of this mechanism."
policy_exception_justification:
"Not implemented, considered not useful."})");
FetchCallback done = base::BindOnce(&OriginPolicyFetcher::OnPolicyHasArrived,
base::Unretained(this));
RedirectCallback redirect = base::BindRepeating(
&OriginPolicyFetcher::OnPolicyRedirect, base::Unretained(this));
// Create and configure the SimpleURLLoader for the policy.
std::unique_ptr<ResourceRequest> policy_request =
std::make_unique<ResourceRequest>();
policy_request->url = fetch_url_;
policy_request->request_initiator = url::Origin::Create(fetch_url_);
policy_request->allow_credentials = false;
url_loader_ =
SimpleURLLoader::Create(std::move(policy_request), traffic_annotation);
url_loader_->SetOnRedirectCallback(std::move(redirect));
// Start the download, and pass the callback for when we're finished.
url_loader_->DownloadToString(factory, std::move(done),
kOriginPolicyMaxPolicySize);
}
void OriginPolicyFetcher::WorkDone(std::unique_ptr<std::string> policy_content,
mojom::OriginPolicyState state) {
if (callback_) {
auto result = mojom::OriginPolicy::New();
result->state = state;
if (policy_content) {
result->contents = mojom::OriginPolicyContents::New();
result->contents->raw_policy = *policy_content;
}
result->policy_url = fetch_url_;
std::move(callback_).Run(std::move(result));
}
// Do not add code after this call as it will destroy this object.
owner_policy_manager_->FetcherDone(this);
}
bool OriginPolicyFetcher::IsValidRedirect(
const net::RedirectInfo& redirect_info) const {
if (!must_redirect_)
return false;
if (!redirect_info.new_url.is_valid())
return false;
// If the url is correctly built, the filename portion of the URL is the
// policy version.
std::string new_version = redirect_info.new_url.ExtractFileName();
if (new_version.empty())
return false;
// The specified redirect url must be correctly built according to origin
// policy url rules.
return redirect_info.new_url ==
GetPolicyURL(new_version, url::Origin::Create(fetch_url_));
}
// TODO(andypaicu): use report_to implement this function
void OriginPolicyFetcher::Report(mojom::OriginPolicyState error_state) {}
} // namespace network
// Copyright 2019 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 SERVICES_NETWORK_ORIGIN_POLICY_ORIGIN_POLICY_FETCHER_H_
#define SERVICES_NETWORK_ORIGIN_POLICY_ORIGIN_POLICY_FETCHER_H_
#include <memory>
#include <string>
#include <vector>
#include "net/url_request/redirect_info.h"
#include "services/network/public/cpp/simple_url_loader.h"
#include "services/network/public/mojom/origin_policy_manager.mojom.h"
#include "services/network/public/mojom/url_loader_factory.mojom.h"
#include "url/origin.h"
namespace network {
class OriginPolicyManager;
struct ResourceResponseHead;
class COMPONENT_EXPORT(NETWORK_SERVICE) OriginPolicyFetcher {
public:
// Constructs a fetcher that attempts to retrieve the policy of the specified
// origin using the specified policy_version.
OriginPolicyFetcher(
OriginPolicyManager* owner_policy_manager,
const std::string& policy_version,
const std::string& report_to,
const url::Origin& origin,
mojom::URLLoaderFactory* factory,
mojom::OriginPolicyManager::RetrieveOriginPolicyCallback callback);
// Constructs a fetcher that attempts to retrieve the current policy for
// the specified origin by fetching from /.well-known/origin-policy
// Spec: https://wicg.github.io/origin-policy/#origin-policy-well-known
OriginPolicyFetcher(
OriginPolicyManager* owner_policy_manager,
const std::string& report_to,
const url::Origin& origin,
mojom::URLLoaderFactory* factory,
mojom::OriginPolicyManager::RetrieveOriginPolicyCallback callback);
~OriginPolicyFetcher();
static GURL GetPolicyURL(const std::string& version,
const url::Origin& origin);
static GURL GetDefaultPolicyURL(const url::Origin& origin);
// ForTesting methods.
bool IsValidRedirectForTesting(const net::RedirectInfo& redirect_info) const;
private:
using FetchCallback = base::OnceCallback<void(std::unique_ptr<std::string>)>;
using RedirectCallback =
base::RepeatingCallback<void(const net::RedirectInfo&,
const ResourceResponseHead&,
std::vector<std::string>*)>;
void OnPolicyHasArrived(std::unique_ptr<std::string> policy_content);
void OnPolicyRedirect(const net::RedirectInfo& redirect_info,
const network::ResourceResponseHead& response_head,
std::vector<std::string>* to_be_removed_headers);
void FetchPolicy(mojom::URLLoaderFactory* factory);
void WorkDone(std::unique_ptr<std::string> policy_content,
mojom::OriginPolicyState state);
bool IsValidRedirect(const net::RedirectInfo& redirect_info) const;
void Report(mojom::OriginPolicyState error_state);
// The owner of this object. When it is destroyed, this is destroyed too.
OriginPolicyManager* const owner_policy_manager_;
// We may need the SimpleURLLoader to download the policy. The loader must
// be kept alive while the load is ongoing.
std::unique_ptr<network::SimpleURLLoader> url_loader_;
// Used to determine the reporting group in case of a failure.
std::string report_to_;
// Used for testing if a redirect is valid.
GURL fetch_url_;
// Called back with policy fetch result.
mojom::OriginPolicyManager::RetrieveOriginPolicyCallback callback_;
// Will be true if we started a fetch at <origin>/well-known/origin-policy
// which will redirect to the latest origin policy.
bool must_redirect_;
DISALLOW_COPY_AND_ASSIGN(OriginPolicyFetcher);
};
} // namespace network
#endif // SERVICES_NETWORK_ORIGIN_POLICY_ORIGIN_POLICY_FETCHER_H_
...@@ -7,9 +7,15 @@ ...@@ -7,9 +7,15 @@
#include <memory> #include <memory>
#include <utility> #include <utility>
#include "base/optional.h"
#include "net/http/http_util.h"
#include "services/network/origin_policy/origin_policy_constants.h"
namespace network { namespace network {
OriginPolicyManager::OriginPolicyManager() {} OriginPolicyManager::OriginPolicyManager(
mojom::URLLoaderFactoryPtr url_loader_factory)
: url_loader_factory_(std::move(url_loader_factory)) {}
OriginPolicyManager::~OriginPolicyManager() {} OriginPolicyManager::~OriginPolicyManager() {}
...@@ -18,4 +24,82 @@ void OriginPolicyManager::AddBinding( ...@@ -18,4 +24,82 @@ void OriginPolicyManager::AddBinding(
bindings_.AddBinding(this, std::move(request)); bindings_.AddBinding(this, std::move(request));
} }
void OriginPolicyManager::RetrieveOriginPolicy(
const url::Origin& origin,
const std::string& header_value,
RetrieveOriginPolicyCallback callback) {
OriginPolicyHeaderValues header_info =
GetRequestedPolicyAndReportGroupFromHeaderString(header_value);
if (header_info.policy_version.empty()) {
if (callback) {
auto result = mojom::OriginPolicy::New();
result->state = mojom::OriginPolicyState::kCannotLoadPolicy;
std::move(callback).Run(std::move(result));
}
return;
}
// Here we might check the cache and only then start the fetch.
StartPolicyFetch(origin, header_info, std::move(callback));
}
void OriginPolicyManager::RetrieveDefaultOriginPolicy(
const url::Origin& origin,
RetrieveOriginPolicyCallback callback) {
// Here we might check the cache and only then start the fetch.
StartPolicyFetch(origin, OriginPolicyHeaderValues(), std::move(callback));
}
void OriginPolicyManager::StartPolicyFetch(
const url::Origin& origin,
const OriginPolicyHeaderValues& header_info,
RetrieveOriginPolicyCallback callback) {
if (header_info.policy_version.empty()) {
origin_policy_fetchers_.emplace(std::make_unique<OriginPolicyFetcher>(
this, header_info.report_to, origin, url_loader_factory_.get(),
std::move(callback)));
} else {
origin_policy_fetchers_.emplace(std::make_unique<OriginPolicyFetcher>(
this, header_info.policy_version, header_info.report_to, origin,
url_loader_factory_.get(), std::move(callback)));
}
}
void OriginPolicyManager::FetcherDone(OriginPolicyFetcher* fetcher) {
auto it = origin_policy_fetchers_.find(fetcher);
DCHECK(it != origin_policy_fetchers_.end());
origin_policy_fetchers_.erase(it);
}
// static
OriginPolicyManager::OriginPolicyHeaderValues
OriginPolicyManager::GetRequestedPolicyAndReportGroupFromHeaderString(
const std::string& header_value) {
if (net::HttpUtil::TrimLWS(header_value) == kOriginPolicyDeletePolicy)
return OriginPolicyHeaderValues({kOriginPolicyDeletePolicy, ""});
base::Optional<std::string> policy;
base::Optional<std::string> report_to;
bool valid = true;
net::HttpUtil::NameValuePairsIterator iter(header_value.cbegin(),
header_value.cend(), ',');
while (iter.GetNext()) {
std::string token_value = net::HttpUtil::TrimLWS(iter.value()).as_string();
bool is_token = net::HttpUtil::IsToken(token_value);
if (iter.name() == kOriginPolicyPolicy) {
valid &= is_token && !policy.has_value();
policy = token_value;
} else if (iter.name() == kOriginPolicyReportTo) {
valid &= is_token && !report_to.has_value();
report_to = token_value;
}
}
valid &= iter.valid();
valid &= (policy.has_value() && policy->find('.') == std::string::npos);
if (!valid)
return OriginPolicyHeaderValues();
return OriginPolicyHeaderValues({policy.value(), report_to.value_or("")});
}
} // namespace network } // namespace network
...@@ -5,28 +5,94 @@ ...@@ -5,28 +5,94 @@
#ifndef SERVICES_NETWORK_ORIGIN_POLICY_ORIGIN_POLICY_MANAGER_H_ #ifndef SERVICES_NETWORK_ORIGIN_POLICY_ORIGIN_POLICY_MANAGER_H_
#define SERVICES_NETWORK_ORIGIN_POLICY_ORIGIN_POLICY_MANAGER_H_ #define SERVICES_NETWORK_ORIGIN_POLICY_ORIGIN_POLICY_MANAGER_H_
#include <memory>
#include <set>
#include <string> #include <string>
#include "base/component_export.h"
#include "base/containers/unique_ptr_adapters.h"
#include "base/macros.h"
#include "mojo/public/cpp/bindings/binding_set.h" #include "mojo/public/cpp/bindings/binding_set.h"
#include "services/network/origin_policy/origin_policy_fetcher.h"
#include "services/network/public/mojom/origin_policy_manager.mojom.h" #include "services/network/public/mojom/origin_policy_manager.mojom.h"
#include "services/network/public/mojom/url_loader_factory.mojom.h"
namespace network { namespace network {
// The OriginPolicyManager is the entry point for all Origin Policy related
// API calls. Spec: https://wicg.github.io/origin-policy/
// A client will likely call AddBinding (or use the NetworkContext function)
// and then do mojom::OriginPolicy related operations, like retrieving a policy
// (which could potentially trigger a fetch), or adding an exception.
class COMPONENT_EXPORT(NETWORK_SERVICE) OriginPolicyManager class COMPONENT_EXPORT(NETWORK_SERVICE) OriginPolicyManager
: public mojom::OriginPolicyManager { : public mojom::OriginPolicyManager {
public: public:
OriginPolicyManager(); // Represents a parsed `Sec-Origin-Policy` header.
// Spec: https://wicg.github.io/origin-policy/#origin-policy-header
struct OriginPolicyHeaderValues {
// The policy version that is parsed from the `policy=` parameter.
std::string policy_version;
// The report group to send reports to if an error occurs. Uses the
// reporting API. Parsed from the `report-to=` parameter.
std::string report_to;
};
explicit OriginPolicyManager(mojom::URLLoaderFactoryPtr url_loader_factory);
~OriginPolicyManager() override; ~OriginPolicyManager() override;
// Bind a request to this object. Mojo messages // Bind a request to this object. Mojo messages
// coming through the associated pipe will be served by this object. // coming through the associated pipe will be served by this object.
void AddBinding(mojom::OriginPolicyManagerRequest request); void AddBinding(mojom::OriginPolicyManagerRequest request);
// mojom::OriginPolicy
void RetrieveOriginPolicy(const url::Origin& origin,
const std::string& header_value,
RetrieveOriginPolicyCallback callback) override;
// To be called by fetcher when it has finished its work.
// This removes the fetcher which results in the fetcher being destroyed.
void FetcherDone(OriginPolicyFetcher* fetcher);
// Retrieves an origin's default origin policy by attempting to fetch it
// from "<origin>/.well-known/origin-policy".
void RetrieveDefaultOriginPolicy(const url::Origin& origin,
RetrieveOriginPolicyCallback callback);
// ForTesting methods // ForTesting methods
mojo::BindingSet<mojom::OriginPolicyManager>& GetBindingsForTesting() { mojo::BindingSet<mojom::OriginPolicyManager>& GetBindingsForTesting() {
return bindings_; return bindings_;
} }
static OriginPolicyHeaderValues
GetRequestedPolicyAndReportGroupFromHeaderStringForTesting(
const std::string& header_value) {
return GetRequestedPolicyAndReportGroupFromHeaderString(header_value);
}
private: private:
// Parses a header and returns the result. If a parsed result does not contain
// a non-empty policy version it means the `header_value` is invalid.
static OriginPolicyHeaderValues
GetRequestedPolicyAndReportGroupFromHeaderString(
const std::string& header_value);
// Will start a fetch based on the provided origin and info.
void StartPolicyFetch(const url::Origin& origin,
const OriginPolicyHeaderValues& header_info,
RetrieveOriginPolicyCallback callback);
// A list of fetchers owned by this object
std::set<std::unique_ptr<OriginPolicyFetcher>, base::UniquePtrComparator>
origin_policy_fetchers_;
// Used for fetching requests
mojom::URLLoaderFactoryPtr url_loader_factory_;
// This object's set of bindings.
// This MUST be below origin_policy_fetchers_ to ensure it is destroyed before
// it. Otherwise it's possible that un-invoked OnceCallbacks owned by members
// of origin_policy_fetchers_ will be destroyed before the beinding they are
// on is destroyed.
mojo::BindingSet<mojom::OriginPolicyManager> bindings_; mojo::BindingSet<mojom::OriginPolicyManager> bindings_;
DISALLOW_COPY_AND_ASSIGN(OriginPolicyManager); DISALLOW_COPY_AND_ASSIGN(OriginPolicyManager);
......
...@@ -17,7 +17,7 @@ enum OriginPolicyState { ...@@ -17,7 +17,7 @@ enum OriginPolicyState {
kCannotLoadPolicy, kCannotLoadPolicy,
// An invalid redirect has been encountered. The only valid redirect is if // An invalid redirect has been encountered. The only valid redirect is if
// we requested the default "/.well-known/origin-policy", to which the // we requested the default "/.well-known/origin-policy", to which the
// server MUST response with a redirect to the latest origin policy. Any // server MUST respond with a redirect to the latest origin policy. Any
// other redirect (or more than 1 redirect) is invalid. // other redirect (or more than 1 redirect) is invalid.
// https://wicg.github.io/origin-policy/#origin-policy-well-known // https://wicg.github.io/origin-policy/#origin-policy-well-known
kInvalidRedirect, kInvalidRedirect,
...@@ -34,7 +34,7 @@ struct OriginPolicyContents { ...@@ -34,7 +34,7 @@ struct OriginPolicyContents {
// The feature policy that is dictated by the origin policy. Each feature // The feature policy that is dictated by the origin policy. Each feature
// is one member of the array. // is one member of the array.
// https://w3c.github.io/webappsec-feature-policy/ // https://w3c.github.io/webappsec-feature-policy/
array<string> features; // array<string> features;
// These two fields together represent the CSP that should be applied to the // These two fields together represent the CSP that should be applied to the
// origin, based on the origin policy. // origin, based on the origin policy.
...@@ -43,12 +43,19 @@ struct OriginPolicyContents { ...@@ -43,12 +43,19 @@ struct OriginPolicyContents {
// The "enforced" portion of the CSP. This CSP is to be treated as having // The "enforced" portion of the CSP. This CSP is to be treated as having
// an "enforced" disposition. // an "enforced" disposition.
// https://w3c.github.io/webappsec-csp/#policy-disposition // https://w3c.github.io/webappsec-csp/#policy-disposition
array<string> content_security_policies; // array<string> content_security_policies;
// The "report-only" portion of the CSP. This CSP is to be treated as having // The "report-only" portion of the CSP. This CSP is to be treated as having
// a "report" disposition. // a "report" disposition.
// https://w3c.github.io/webappsec-csp/#policy-disposition // https://w3c.github.io/webappsec-csp/#policy-disposition
array<string> content_security_policies_report_only; // array<string> content_security_policies_report_only;
// TODO(andypaicu): remove this when the policy is parsed in the network
// service.
// For now the raw contents is the only available part. When the policy file
// will be parsed in the network service the other (commented) fields will be
// properly populated.
string raw_policy;
}; };
// Represents the result of retrieving an origin policy. // Represents the result of retrieving an origin policy.
...@@ -72,4 +79,9 @@ struct OriginPolicy { ...@@ -72,4 +79,9 @@ struct OriginPolicy {
// An interface for handling all origin policy related operations. // An interface for handling all origin policy related operations.
// https://wicg.github.io/origin-policy/ // https://wicg.github.io/origin-policy/
interface OriginPolicyManager { interface OriginPolicyManager {
// Attempts to retrieve the origin policy for an origin and
// `Sec-Origin-Policy` HTTP header value. Calls back with the result.
// https://wicg.github.io/origin-policy/#origin-policy-header
RetrieveOriginPolicy(url.mojom.Origin origin, string header_value)
=> (OriginPolicy origin_policy);
}; };
...@@ -179,7 +179,7 @@ Refer to README.md for content description and update process. ...@@ -179,7 +179,7 @@ Refer to README.md for content description and update process.
<item id="omnibox_zerosuggest_experimental" hash_code="3813491" type="0" content_hash_code="22929259" os_list="linux,windows" file_path="components/omnibox/browser/contextual_suggestions_service.cc"/> <item id="omnibox_zerosuggest_experimental" hash_code="3813491" type="0" content_hash_code="22929259" os_list="linux,windows" file_path="components/omnibox/browser/contextual_suggestions_service.cc"/>
<item id="one_google_bar_service" hash_code="78917933" type="0" content_hash_code="46527252" os_list="linux,windows" file_path="chrome/browser/search/one_google_bar/one_google_bar_loader_impl.cc"/> <item id="one_google_bar_service" hash_code="78917933" type="0" content_hash_code="46527252" os_list="linux,windows" file_path="chrome/browser/search/one_google_bar/one_google_bar_loader_impl.cc"/>
<item id="open_search" hash_code="107267424" type="0" content_hash_code="83025542" os_list="linux,windows" file_path="components/search_engines/template_url_fetcher.cc"/> <item id="open_search" hash_code="107267424" type="0" content_hash_code="83025542" os_list="linux,windows" file_path="components/search_engines/template_url_fetcher.cc"/>
<item id="origin_policy_loader" hash_code="6483617" type="0" content_hash_code="20680909" os_list="linux,windows" file_path="content/browser/frame_host/origin_policy_throttle.cc"/> <item id="origin_policy_loader" hash_code="6483617" type="0" content_hash_code="20680909" os_list="linux,windows" file_path="services/network/origin_policy/origin_policy_fetcher.cc"/>
<item id="parallel_download_job" hash_code="135118587" type="0" content_hash_code="105330419" os_list="linux,windows" file_path="components/download/internal/common/parallel_download_job.cc"/> <item id="parallel_download_job" hash_code="135118587" type="0" content_hash_code="105330419" os_list="linux,windows" file_path="components/download/internal/common/parallel_download_job.cc"/>
<item id="password_protection_request" hash_code="66322287" type="0" content_hash_code="25596947" os_list="linux,windows" file_path="components/safe_browsing/password_protection/password_protection_request.cc"/> <item id="password_protection_request" hash_code="66322287" type="0" content_hash_code="25596947" os_list="linux,windows" file_path="components/safe_browsing/password_protection/password_protection_request.cc"/>
<item id="password_requirements_spec_fetch" hash_code="69585116" type="0" content_hash_code="5591260" os_list="linux,windows" file_path="components/password_manager/core/browser/generation/password_requirements_spec_fetcher_impl.cc"/> <item id="password_requirements_spec_fetch" hash_code="69585116" type="0" content_hash_code="5591260" os_list="linux,windows" file_path="components/password_manager/core/browser/generation/password_requirements_spec_fetcher_impl.cc"/>
......
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