Commit c3a6c5f3 authored by Andy Paicu's avatar Andy Paicu Committed by Commit Bot

Moved last version cache from the origin-policy throttle to...

... the network service origin policy manager.

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

This also includes a second in memory structure for the actual policy
contents that was not present in the origin policy throttle.

Bug: 950905
Change-Id: I493e66f97c68263e1110d9ecfa724cee0ee8310f
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1627036
Commit-Queue: Andy Paicu <andypaicu@chromium.org>
Auto-Submit: Andy Paicu <andypaicu@chromium.org>
Reviewed-by: default avatarAlex Moshchuk <alexmos@chromium.org>
Reviewed-by: default avatarDaniel Cheng <dcheng@chromium.org>
Reviewed-by: default avatarDaniel Vogelheim <vogelheim@chromium.org>
Reviewed-by: default avatarMatt Menke <mmenke@chromium.org>
Reviewed-by: default avatarCarlos IL <carlosil@chromium.org>
Cr-Commit-Position: refs/heads/master@{#665427}
parent 2e6c0434
......@@ -117,7 +117,8 @@ void OriginPolicyInterstitialPage::CommandReceived(const std::string& command) {
}
void OriginPolicyInterstitialPage::OnProceed() {
content::OriginPolicyAddExceptionFor(request_url());
content::OriginPolicyAddExceptionFor(web_contents()->GetBrowserContext(),
request_url());
web_contents()->GetController().Reload(content::ReloadType::NORMAL, true);
}
......
......@@ -16,7 +16,6 @@
#include "content/browser/frame_host/navigation_handle_impl.h"
#include "content/browser/frame_host/navigation_request.h"
#include "content/browser/storage_partition_impl.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/navigation_handle.h"
#include "content/public/browser/origin_policy_error_reason.h"
......@@ -35,20 +34,15 @@ namespace {
static constexpr const char* kDeletePolicy = "0";
static constexpr const char* kReportTo = "report-to";
static constexpr const char* kPolicy = "policy";
// Marker for (temporarily) exempted origins.
// TODO(vogelheim): Make sure this is outside the value space for policy
// names. A name with a comma in it shouldn't be allowed, but
// I don't think we presently check this anywhere.
static constexpr const char* kExemptedOriginPolicyVersion = "exception,";
} // namespace
namespace content {
// Implement the public "API" from
// content/public/browser/origin_policy_commands.h:
void OriginPolicyAddExceptionFor(const GURL& url) {
OriginPolicyThrottle::AddExceptionFor(url);
void OriginPolicyAddExceptionFor(BrowserContext* browser_context,
const GURL& url) {
OriginPolicyThrottle::AddExceptionFor(browser_context, url);
}
// static
......@@ -87,8 +81,15 @@ OriginPolicyThrottle::MaybeCreateThrottleFor(NavigationHandle* handle) {
}
// static
void OriginPolicyThrottle::AddExceptionFor(const GURL& url) {
GetKnownVersions()[url::Origin::Create(url)] = kExemptedOriginPolicyVersion;
void OriginPolicyThrottle::AddExceptionFor(BrowserContext* browser_context,
const GURL& url) {
DCHECK(browser_context);
StoragePartitionImpl* storage_partition = static_cast<StoragePartitionImpl*>(
BrowserContext::GetStoragePartitionForSite(browser_context, url));
network::mojom::OriginPolicyManager* origin_policy_manager =
storage_partition->GetOriginPolicyManagerForBrowserProcess();
origin_policy_manager->AddExceptionFor(url::Origin::Create(url));
}
OriginPolicyThrottle::~OriginPolicyThrottle() {}
......@@ -114,80 +115,24 @@ OriginPolicyThrottle::WillProcessResponse() {
if (!navigation_handle()->GetResponseHeaders())
return NavigationThrottle::PROCEED;
// TODO(andypaicu):
// This entire logic needs to be moved to OriginPolicyManager with the
// store migration.
// This determines whether and which policy version applies and fetches it.
//
// Inputs are the kSecOriginPolicy HTTP header, and the version
// we've last seen from this particular origin.
//
// - header with kDeletePolicy received: No policy applies, and delete the
// last-known policy for this origin.
// - header received: Use header version and update last-known policy.
// - no header received, last-known version exists: Use last-known version
// - no header, no last-known version: No policy applies.
std::string response_version =
GetRequestedPolicyAndReportGroupFromHeader().policy_version;
bool header_found = !response_version.empty();
url::Origin origin = GetRequestOrigin();
DCHECK(!origin.Serialize().empty());
DCHECK(!origin.opaque());
KnownVersionMap& versions = GetKnownVersions();
auto iter = versions.find(origin);
// Process policy deletion first!
if (header_found && response_version == kDeletePolicy) {
if (iter != versions.end())
versions.erase(iter);
return NavigationThrottle::PROCEED;
}
// Process policy exceptions.
if (iter != versions.end() && iter->second == kExemptedOriginPolicyVersion) {
return NavigationThrottle::PROCEED;
}
// No policy applies to this request?
if (!header_found && iter == versions.end()) {
return NavigationThrottle::PROCEED;
}
std::string header;
navigation_handle()->GetResponseHeaders()->GetNormalizedHeader(
net::HttpRequestHeaders::kSecOriginPolicy, &header);
if (!header_found) {
// TODO(andypaicu):
// This is an absolute hack that will go away when we move the in-memory
// store to the network service OriginPolicyManager. Until then, if we have
// a cached policy version and we receive a request with no header set, we
// build this artificial header to let OriginPolicyManager know where to
// retrieve the policy from.
header = base::StrCat({"policy=", iter->second});
} else if (iter == versions.end()) {
versions.insert(std::make_pair(origin, response_version));
} else {
iter->second = response_version;
}
network::OriginPolicyManager::RetrieveOriginPolicyCallback
origin_policy_manager_done = base::BindOnce(
&OriginPolicyThrottle::OnOriginPolicyManagerRetrieveDone,
base::Unretained(this));
SiteInstance* site_instance = navigation_handle()->GetStartingSiteInstance();
StoragePartitionImpl* storage_partition =
static_cast<StoragePartitionImpl*>(BrowserContext::GetStoragePartition(
site_instance->GetBrowserContext(), site_instance));
network::mojom::OriginPolicyManager* origin_policy_manager =
storage_partition->GetOriginPolicyManagerForBrowserProcess();
origin_policy_manager->RetrieveOriginPolicy(
origin, header, std::move(origin_policy_manager_done));
GetRequestOrigin(), header, std::move(origin_policy_manager_done));
return NavigationThrottle::DEFER;
}
......@@ -196,27 +141,11 @@ const char* OriginPolicyThrottle::GetNameForLogging() {
return "OriginPolicyThrottle";
}
// static
OriginPolicyThrottle::KnownVersionMap&
OriginPolicyThrottle::GetKnownVersionsForTesting() {
return GetKnownVersions();
}
OriginPolicyThrottle::OriginPolicyThrottle(NavigationHandle* handle)
: NavigationThrottle(handle) {}
OriginPolicyThrottle::KnownVersionMap&
OriginPolicyThrottle::GetKnownVersions() {
static base::NoDestructor<KnownVersionMap> map_instance;
return *map_instance;
}
OriginPolicyThrottle::PolicyVersionAndReportTo OriginPolicyThrottle::
GetRequestedPolicyAndReportGroupFromHeaderStringForTesting(
const std::string& header) {
return GetRequestedPolicyAndReportGroupFromHeaderString(header);
}
// TODO(andypaicu): Remove when we have moved reporting logic to the network
// service.
OriginPolicyThrottle::PolicyVersionAndReportTo
OriginPolicyThrottle::GetRequestedPolicyAndReportGroupFromHeader() const {
std::string header;
......@@ -317,30 +246,38 @@ void OriginPolicyThrottle::Report(OriginPolicyErrorReason reason,
const GURL& policy_url) {}
#endif // BUILDFLAG(ENABLE_REPORTING)
bool OriginPolicyThrottle::IsExemptedForTesting(const url::Origin& origin) {
KnownVersionMap& versions = GetKnownVersions();
auto iter = versions.find(origin);
if (iter != versions.end())
return iter->second == kExemptedOriginPolicyVersion;
return false;
}
void OriginPolicyThrottle::OnOriginPolicyManagerRetrieveDone(
const network::mojom::OriginPolicyPtr origin_policy) {
if (origin_policy->state != network::mojom::OriginPolicyState::kLoaded) {
CancelNavigation(OriginPolicyErrorReason::kCannotLoadPolicy,
origin_policy->policy_url);
return;
switch (origin_policy->state) {
case network::mojom::OriginPolicyState::kCannotLoadPolicy:
// TODO(andypaicu): OriginPolicyErrorReason is obsolete and we should use
// network::mojom::OriginPolicyState instead.
CancelNavigation(OriginPolicyErrorReason::kCannotLoadPolicy,
origin_policy->policy_url);
return;
case network::mojom::OriginPolicyState::kInvalidRedirect:
// TODO(andypaicu): OriginPolicyErrorReason is obsolete and we should use
// network::mojom::OriginPolicyState instead.
CancelNavigation(OriginPolicyErrorReason::kPolicyShouldNotRedirect,
origin_policy->policy_url);
return;
case network::mojom::OriginPolicyState::kNoPolicyApplies:
Resume();
return;
case network::mojom::OriginPolicyState::kLoaded:
DCHECK(origin_policy->contents);
static_cast<NavigationHandleImpl*>(navigation_handle())
->navigation_request()
->SetOriginPolicy(origin_policy->contents->raw_policy);
Resume();
return;
default:
NOTREACHED();
}
DCHECK(origin_policy->contents);
// TODO(vogelheim): Determine whether we need to parse or sanity check
// the policy content at this point.
static_cast<NavigationHandleImpl*>(navigation_handle())
->navigation_request()
->SetOriginPolicy(origin_policy->contents->raw_policy);
Resume();
}
} // namespace content
......@@ -12,6 +12,7 @@
#include "base/gtest_prod_util.h"
#include "base/macros.h"
#include "base/memory/scoped_refptr.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/navigation_throttle.h"
#include "services/network/public/mojom/origin_policy_manager.mojom.h"
......@@ -59,7 +60,7 @@ class CONTENT_EXPORT OriginPolicyThrottle : public NavigationThrottle {
// otherwise invalid) policy. This is meant to be called by the security
// interstitial.
// This will exempt the entire origin, rather than only the given URL.
static void AddExceptionFor(const GURL& url);
static void AddExceptionFor(BrowserContext* browser_context, const GURL& url);
~OriginPolicyThrottle() override;
......@@ -70,20 +71,11 @@ class CONTENT_EXPORT OriginPolicyThrottle : public NavigationThrottle {
using KnownVersionMap = std::map<url::Origin, std::string>;
static KnownVersionMap& GetKnownVersionsForTesting();
// TODO(andypaicu): Remove this when we move the store to the network
// service layer.
static PolicyVersionAndReportTo
GetRequestedPolicyAndReportGroupFromHeaderStringForTesting(
const std::string& header);
static bool IsExemptedForTesting(const url::Origin& origin);
private:
explicit OriginPolicyThrottle(NavigationHandle* handle);
static KnownVersionMap& GetKnownVersions();
// Get the policy name and the reporting group from the header string.
// TODO(andypaicu): Remove when we have moved reporting logic to the network
// service.
PolicyVersionAndReportTo GetRequestedPolicyAndReportGroupFromHeader() const;
static PolicyVersionAndReportTo
GetRequestedPolicyAndReportGroupFromHeaderString(const std::string& header);
......
......@@ -4,6 +4,7 @@
#include "content/browser/frame_host/origin_policy_throttle.h"
#include <set>
#include <utility>
#include "base/feature_list.h"
......@@ -40,10 +41,8 @@ class OriginPolicyThrottleTest : public RenderViewHostTestHarness,
features_.InitWithFeatureState(features::kOriginPolicy, GetParam());
RenderViewHostTestHarness::SetUp();
OriginPolicyThrottle::GetKnownVersionsForTesting().clear();
}
void TearDown() override {
OriginPolicyThrottle::GetKnownVersionsForTesting().clear();
nav_handle_.reset();
RenderViewHostTestHarness::TearDown();
}
......@@ -72,20 +71,28 @@ class TestOriginPolicyManager : public network::mojom::OriginPolicyManager {
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();
if (origin_exceptions_.find(origin) == origin_exceptions_.end()) {
result->state = network::mojom::OriginPolicyState::kLoaded;
result->contents = network::mojom::OriginPolicyContents::New();
result->contents->raw_policy = kExampleManifestString;
result->policy_url = origin.GetURL();
} else {
result->state = network::mojom::OriginPolicyState::kNoPolicyApplies;
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);
......@@ -95,7 +102,14 @@ class TestOriginPolicyManager : public network::mojom::OriginPolicyManager {
return ptr;
}
void AddExceptionFor(const url::Origin& origin) override {
origin_exceptions_.insert(origin);
}
private:
std::unique_ptr<mojo::Binding<network::mojom::OriginPolicyManager>> binding_;
std::set<url::Origin> origin_exceptions_;
};
INSTANTIATE_TEST_SUITE_P(OriginPolicyThrottleTests,
......@@ -182,25 +196,10 @@ TEST_P(OriginPolicyThrottleTest, RunRequestEndToEnd) {
->ResetOriginPolicyManagerForBrowserProcessForTesting();
}
TEST_P(OriginPolicyThrottleTest, AddException) {
if (!enabled())
return;
GURL url("https://example.org/bla");
OriginPolicyThrottle::GetKnownVersionsForTesting()[url::Origin::Create(url)] =
"abcd";
OriginPolicyThrottle::AddExceptionFor(url);
EXPECT_TRUE(
OriginPolicyThrottle::IsExemptedForTesting(url::Origin::Create(url)));
}
TEST_P(OriginPolicyThrottleTest, AddExceptionEndToEnd) {
if (!enabled())
return;
OriginPolicyThrottle::AddExceptionFor(GURL("https://example.org/blubb"));
// Start the navigation.
auto navigation = NavigationSimulator::CreateBrowserInitiated(
GURL("https://example.org/bla"), web_contents());
......@@ -210,88 +209,47 @@ TEST_P(OriginPolicyThrottleTest, AddExceptionEndToEnd) {
EXPECT_EQ(NavigationThrottle::PROCEED,
navigation->GetLastThrottleCheckResult().action());
// 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;
test_origin_policy_manager.AddExceptionFor(
url::Origin::Create(GURL("https://example.org/blubb")));
NavigationHandleImpl* nav_handle =
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());
// Fake a response with a policy header.
const char* raw_headers =
"HTTP/1.1 200 OK\nSec-Origin-Policy: policy=policy-1\n\n";
auto headers = base::MakeRefCounted<net::HttpResponseHeaders>(
net::HttpUtil::AssembleRawHeaders(raw_headers));
NavigationHandleImpl* nav_handle =
static_cast<NavigationHandleImpl*>(navigation->GetNavigationHandle());
nav_handle->set_response_headers_for_testing(headers);
navigation->ReadyToCommit();
// Due to the exception, we expect the policy to not defer.
EXPECT_FALSE(navigation->IsDeferred());
// The policy manager has to be called even though this is an exception
// because the throttle has no way of knowing that.
EXPECT_TRUE(navigation->IsDeferred());
OriginPolicyThrottle* policy_throttle = static_cast<OriginPolicyThrottle*>(
nav_handle->GetDeferringThrottleForTesting());
EXPECT_TRUE(policy_throttle);
// Also check that the header policy did not overwrite the exemption:
EXPECT_TRUE(OriginPolicyThrottle::IsExemptedForTesting(
url::Origin::Create(GURL("https://example.org/bla"))));
}
// Wait until the navigation has been allowed to proceed.
navigation->Wait();
TEST(OriginPolicyThrottleTest, ParseHeaders) {
const struct {
const char* header;
const char* policy_version;
const char* report_to;
} testcases[] = {
// The common cases: We expect >99% of headers to look like these:
{"policy=policy", "policy", ""},
{"policy=policy, report-to=endpoint", "policy", "endpoint"},
// Delete a policy. This better work.
{"0", "0", ""},
{"policy=0", "0", ""},
{"policy=\"0\"", "0", ""},
{"policy=0, report-to=endpoint", "0", "endpoint"},
// Order, please!
{"policy=policy, report-to=endpoint", "policy", "endpoint"},
{"report-to=endpoint, policy=policy", "policy", "endpoint"},
// Quoting:
{"policy=\"policy\"", "policy", ""},
{"policy=\"policy\", report-to=endpoint", "policy", "endpoint"},
{"policy=\"policy\", report-to=\"endpoint\"", "policy", "endpoint"},
{"policy=policy, report-to=\"endpoint\"", "policy", "endpoint"},
// Whitespace, and funky but valid syntax:
{" policy = policy ", "policy", ""},
{" policy = \t policy ", "policy", ""},
{" policy \t= \t \"policy\" ", "policy", ""},
{" policy = \" policy \" ", "policy", ""},
{" , policy = policy , report-to=endpoint , ", "policy", "endpoint"},
// Valid policy, invalid report-to:
{"policy=policy, report-to endpoint", "", ""},
{"policy=policy, report-to=here, report-to=there", "", ""},
{"policy=policy, \"report-to\"=endpoint", "", ""},
// Invalid policy, valid report-to:
{"policy=policy1, policy=policy2", "", ""},
{"policy, report-to=r", "", ""},
{"report-to=endpoint", "", "endpoint"},
// Invalid everything:
{"one two three", "", ""},
{"one, two, three", "", ""},
{"policy report-to=endpoint", "", ""},
{"policy=policy report-to=endpoint", "", ""},
// Forward compatibility, ignore unknown keywords:
{"policy=pol, report-to=endpoint, unknown=keyword", "pol", "endpoint"},
{"unknown=keyword, policy=pol, report-to=endpoint", "pol", "endpoint"},
{"policy=pol, unknown=keyword", "pol", ""},
{"policy=policy, report_to=endpoint", "policy", ""},
{"policy=policy, reportto=endpoint", "policy", ""},
};
for (const auto& testcase : testcases) {
SCOPED_TRACE(testcase.header);
const auto result = OriginPolicyThrottle::
GetRequestedPolicyAndReportGroupFromHeaderStringForTesting(
testcase.header);
EXPECT_EQ(result.policy_version, testcase.policy_version);
EXPECT_EQ(result.report_to, testcase.report_to);
}
// At the end of the navigation, the navigation handle should have no policy
// as this origin should be exempted.
EXPECT_EQ("",
nav_handle->navigation_request()->common_params().origin_policy);
static_cast<StoragePartitionImpl*>(
BrowserContext::GetStoragePartition(site_instance->GetBrowserContext(),
site_instance))
->ResetOriginPolicyManagerForBrowserProcessForTesting();
}
} // namespace content
......@@ -11,11 +11,14 @@ class GURL;
namespace content {
class BrowserContext;
// Instruct the Origin Policy throttle to disregard errors for the given URL.
//
// Intended use: This should be called by the browser when the user selects
// "proceed" on the security interstitial page for the given URL.
CONTENT_EXPORT void OriginPolicyAddExceptionFor(const GURL& url);
CONTENT_EXPORT void OriginPolicyAddExceptionFor(BrowserContext* browser_context,
const GURL& url);
} // namespace content
......
......@@ -10,6 +10,7 @@ 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;
......
......@@ -8,11 +8,8 @@
#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 {
......@@ -89,7 +86,6 @@ void OriginPolicyFetcher::OnPolicyRedirect(
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;
}
......@@ -147,20 +143,17 @@ void OriginPolicyFetcher::FetchPolicy(mojom::URLLoaderFactory* factory) {
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));
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_;
// Do not add code after this call as it will destroy this object.
owner_policy_manager_->FetcherDone(this);
owner_policy_manager_->FetcherDone(this, std::move(result),
std::move(callback_));
}
bool OriginPolicyFetcher::IsValidRedirect(
......
......@@ -88,7 +88,7 @@ class COMPONENT_EXPORT(NETWORK_SERVICE) OriginPolicyFetcher {
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.
// which must redirect to the latest origin policy.
bool must_redirect_;
DISALLOW_COPY_AND_ASSIGN(OriginPolicyFetcher);
......
......@@ -53,6 +53,13 @@ class OriginPolicyFetcherTest : public testing::Test {
manager_ = std::make_unique<OriginPolicyManager>(
network_context_->CreateUrlLoaderFactoryForNetworkService());
test_server_.RegisterRequestHandler(base::BindRepeating(
&OriginPolicyFetcherTest::HandleResponse, base::Unretained(this)));
EXPECT_TRUE(test_server_.Start());
test_server_origin_ = url::Origin::Create(test_server_.base_url());
}
const url::Origin& test_server_origin() const { return test_server_origin_; }
......@@ -64,16 +71,6 @@ class OriginPolicyFetcherTest : public testing::Test {
}
protected:
// testing::Test implementation.
void SetUp() override {
test_server_.RegisterRequestHandler(base::BindRepeating(
&OriginPolicyFetcherTest::HandleResponse, base::Unretained(this)));
EXPECT_TRUE(test_server_.Start());
test_server_origin_ = url::Origin::Create(test_server_.base_url());
}
const net::test_server::EmbeddedTestServer& test_server() const {
return test_server_;
}
......
......@@ -7,9 +7,18 @@
#include <memory>
#include <utility>
#include "base/logging.h"
#include "base/optional.h"
#include "net/http/http_util.h"
#include "services/network/origin_policy/origin_policy_constants.h"
#include "services/network/origin_policy/origin_policy_fetcher.h"
namespace {
// Marker for (temporarily) exempted origins. The presence of the "?" guarantees
// that this is not a valid policy as it is not a valid http token.
const char kExemptedOriginPolicyVersion[] = "exception?";
} // namespace
namespace network {
......@@ -28,49 +37,84 @@ void OriginPolicyManager::RetrieveOriginPolicy(
const url::Origin& origin,
const std::string& header_value,
RetrieveOriginPolicyCallback callback) {
DCHECK(origin.GetURL().is_valid());
DCHECK(!origin.opaque());
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));
}
auto iter = latest_version_map_.find(origin);
// Process policy deletion first!
if (header_info.policy_version == kOriginPolicyDeletePolicy) {
if (iter != latest_version_map_.end())
latest_version_map_.erase(iter);
InvokeCallbackWithPolicyState(origin,
mojom::OriginPolicyState::kNoPolicyApplies,
std::move(callback));
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));
}
// Process policy exceptions.
if (iter != latest_version_map_.end() &&
iter->second == kExemptedOriginPolicyVersion) {
InvokeCallbackWithPolicyState(origin,
mojom::OriginPolicyState::kNoPolicyApplies,
std::move(callback));
return;
}
void OriginPolicyManager::StartPolicyFetch(
const url::Origin& origin,
const OriginPolicyHeaderValues& header_info,
RetrieveOriginPolicyCallback callback) {
// No policy applies to this request or invalid header present.
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)));
// If there header has no policy version is present, use cached version, if
// there is one. Otherwise, fail.
if (iter == latest_version_map_.end()) {
InvokeCallbackWithPolicyState(
origin,
header_value.empty() ? mojom::OriginPolicyState::kNoPolicyApplies
: mojom::OriginPolicyState::kCannotLoadPolicy,
std::move(callback));
return;
}
header_info.policy_version = iter->second;
} else if (iter == latest_version_map_.end()) {
latest_version_map_.emplace(origin, header_info.policy_version);
} 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)));
iter->second = header_info.policy_version;
}
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) {
void OriginPolicyManager::AddExceptionFor(const url::Origin& origin) {
latest_version_map_[origin] = kExemptedOriginPolicyVersion;
}
void OriginPolicyManager::FetcherDone(OriginPolicyFetcher* fetcher,
mojom::OriginPolicyPtr origin_policy,
RetrieveOriginPolicyCallback callback) {
std::move(callback).Run(std::move(origin_policy));
auto it = origin_policy_fetchers_.find(fetcher);
DCHECK(it != origin_policy_fetchers_.end());
origin_policy_fetchers_.erase(it);
}
void OriginPolicyManager::RetrieveDefaultOriginPolicy(
const url::Origin& origin,
RetrieveOriginPolicyCallback callback) {
origin_policy_fetchers_.emplace(std::make_unique<OriginPolicyFetcher>(
this, std::string() /* report_to */, origin, url_loader_factory_.get(),
std::move(callback)));
}
// static
const char* OriginPolicyManager::GetExemptedVersionForTesting() {
return kExemptedOriginPolicyVersion;
}
// static
OriginPolicyManager::OriginPolicyHeaderValues
OriginPolicyManager::GetRequestedPolicyAndReportGroupFromHeaderString(
......@@ -102,4 +146,15 @@ OriginPolicyManager::GetRequestedPolicyAndReportGroupFromHeaderString(
return OriginPolicyHeaderValues({policy.value(), report_to.value_or("")});
}
// static
void OriginPolicyManager::InvokeCallbackWithPolicyState(
const url::Origin& origin,
mojom::OriginPolicyState state,
RetrieveOriginPolicyCallback callback) {
mojom::OriginPolicyPtr result = mojom::OriginPolicy::New();
result->state = state;
result->policy_url = OriginPolicyFetcher::GetDefaultPolicyURL(origin);
std::move(callback).Run(std::move(result));
}
} // namespace network
......@@ -5,6 +5,7 @@
#ifndef SERVICES_NETWORK_ORIGIN_POLICY_ORIGIN_POLICY_MANAGER_H_
#define SERVICES_NETWORK_ORIGIN_POLICY_ORIGIN_POLICY_MANAGER_H_
#include <map>
#include <memory>
#include <set>
#include <string>
......@@ -13,12 +14,14 @@
#include "base/containers/unique_ptr_adapters.h"
#include "base/macros.h"
#include "mojo/public/cpp/bindings/binding_set.h"
#include "services/network/origin_policy/origin_policy_fetcher.h"
#include "services/network/origin_policy/origin_policy_constants.h"
#include "services/network/public/mojom/origin_policy_manager.mojom.h"
#include "services/network/public/mojom/url_loader_factory.mojom.h"
namespace network {
class OriginPolicyFetcher;
// 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)
......@@ -44,14 +47,17 @@ class COMPONENT_EXPORT(NETWORK_SERVICE) OriginPolicyManager
// coming through the associated pipe will be served by this object.
void AddBinding(mojom::OriginPolicyManagerRequest request);
// mojom::OriginPolicy
// mojom::OriginPolicyManager
void RetrieveOriginPolicy(const url::Origin& origin,
const std::string& header_value,
RetrieveOriginPolicyCallback callback) override;
void AddExceptionFor(const url::Origin& origin) 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);
void FetcherDone(OriginPolicyFetcher* fetcher,
mojom::OriginPolicyPtr origin_policy,
RetrieveOriginPolicyCallback callback);
// Retrieves an origin's default origin policy by attempting to fetch it
// from "<origin>/.well-known/origin-policy".
......@@ -69,17 +75,28 @@ class COMPONENT_EXPORT(NETWORK_SERVICE) OriginPolicyManager
return GetRequestedPolicyAndReportGroupFromHeaderString(header_value);
}
// Get the version used for exempted policies. For testing purposes only.
static const char* GetExemptedVersionForTesting();
private:
using KnownVersionMap = std::map<url::Origin, std::string>;
// 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);
// Returns an origin policy with the specified state. The contents is empty
// and the `policy_url` is the default policy url for the specified origin.
static void InvokeCallbackWithPolicyState(
const url::Origin& origin,
mojom::OriginPolicyState state,
RetrieveOriginPolicyCallback callback);
// In memory cache of current policy version per origin.
// TODO(andypaicu): clear this when the disk cache is cleaned.
KnownVersionMap latest_version_map_;
// A list of fetchers owned by this object
std::set<std::unique_ptr<OriginPolicyFetcher>, base::UniquePtrComparator>
......
......@@ -81,7 +81,14 @@ struct OriginPolicy {
interface OriginPolicyManager {
// Attempts to retrieve the origin policy for an origin and
// `Sec-Origin-Policy` HTTP header value. Calls back with the result.
// The header_value needs to contain a proper policy version or be empty. An
// invalid header_value will result in a returned empty policy with the state
// of `kCannotLoadPolicy`.
// https://wicg.github.io/origin-policy/#origin-policy-header
RetrieveOriginPolicy(url.mojom.Origin origin, string header_value)
=> (OriginPolicy origin_policy);
// Adds an exception for the specified origin. This means that no policy will
// apply for the specified origin from this point forward.
AddExceptionFor(url.mojom.Origin origin);
};
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