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) { ...@@ -117,7 +117,8 @@ void OriginPolicyInterstitialPage::CommandReceived(const std::string& command) {
} }
void OriginPolicyInterstitialPage::OnProceed() { void OriginPolicyInterstitialPage::OnProceed() {
content::OriginPolicyAddExceptionFor(request_url()); content::OriginPolicyAddExceptionFor(web_contents()->GetBrowserContext(),
request_url());
web_contents()->GetController().Reload(content::ReloadType::NORMAL, true); web_contents()->GetController().Reload(content::ReloadType::NORMAL, true);
} }
......
...@@ -16,7 +16,6 @@ ...@@ -16,7 +16,6 @@
#include "content/browser/frame_host/navigation_handle_impl.h" #include "content/browser/frame_host/navigation_handle_impl.h"
#include "content/browser/frame_host/navigation_request.h" #include "content/browser/frame_host/navigation_request.h"
#include "content/browser/storage_partition_impl.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/browser_thread.h"
#include "content/public/browser/navigation_handle.h" #include "content/public/browser/navigation_handle.h"
#include "content/public/browser/origin_policy_error_reason.h" #include "content/public/browser/origin_policy_error_reason.h"
...@@ -35,20 +34,15 @@ namespace { ...@@ -35,20 +34,15 @@ namespace {
static constexpr const char* kDeletePolicy = "0"; static constexpr const char* kDeletePolicy = "0";
static constexpr const char* kReportTo = "report-to"; static constexpr const char* kReportTo = "report-to";
static constexpr const char* kPolicy = "policy"; 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
namespace content { namespace content {
// Implement the public "API" from // Implement the public "API" from
// content/public/browser/origin_policy_commands.h: // content/public/browser/origin_policy_commands.h:
void OriginPolicyAddExceptionFor(const GURL& url) { void OriginPolicyAddExceptionFor(BrowserContext* browser_context,
OriginPolicyThrottle::AddExceptionFor(url); const GURL& url) {
OriginPolicyThrottle::AddExceptionFor(browser_context, url);
} }
// static // static
...@@ -87,8 +81,15 @@ OriginPolicyThrottle::MaybeCreateThrottleFor(NavigationHandle* handle) { ...@@ -87,8 +81,15 @@ OriginPolicyThrottle::MaybeCreateThrottleFor(NavigationHandle* handle) {
} }
// static // static
void OriginPolicyThrottle::AddExceptionFor(const GURL& url) { void OriginPolicyThrottle::AddExceptionFor(BrowserContext* browser_context,
GetKnownVersions()[url::Origin::Create(url)] = kExemptedOriginPolicyVersion; 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() {} OriginPolicyThrottle::~OriginPolicyThrottle() {}
...@@ -114,80 +115,24 @@ OriginPolicyThrottle::WillProcessResponse() { ...@@ -114,80 +115,24 @@ OriginPolicyThrottle::WillProcessResponse() {
if (!navigation_handle()->GetResponseHeaders()) if (!navigation_handle()->GetResponseHeaders())
return NavigationThrottle::PROCEED; 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; std::string header;
navigation_handle()->GetResponseHeaders()->GetNormalizedHeader( navigation_handle()->GetResponseHeaders()->GetNormalizedHeader(
net::HttpRequestHeaders::kSecOriginPolicy, &header); 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 network::OriginPolicyManager::RetrieveOriginPolicyCallback
origin_policy_manager_done = base::BindOnce( origin_policy_manager_done = base::BindOnce(
&OriginPolicyThrottle::OnOriginPolicyManagerRetrieveDone, &OriginPolicyThrottle::OnOriginPolicyManagerRetrieveDone,
base::Unretained(this)); base::Unretained(this));
SiteInstance* site_instance = navigation_handle()->GetStartingSiteInstance(); SiteInstance* site_instance = navigation_handle()->GetStartingSiteInstance();
StoragePartitionImpl* storage_partition = StoragePartitionImpl* storage_partition =
static_cast<StoragePartitionImpl*>(BrowserContext::GetStoragePartition( static_cast<StoragePartitionImpl*>(BrowserContext::GetStoragePartition(
site_instance->GetBrowserContext(), site_instance)); site_instance->GetBrowserContext(), site_instance));
network::mojom::OriginPolicyManager* origin_policy_manager = network::mojom::OriginPolicyManager* origin_policy_manager =
storage_partition->GetOriginPolicyManagerForBrowserProcess(); storage_partition->GetOriginPolicyManagerForBrowserProcess();
origin_policy_manager->RetrieveOriginPolicy( origin_policy_manager->RetrieveOriginPolicy(
origin, header, std::move(origin_policy_manager_done)); GetRequestOrigin(), header, std::move(origin_policy_manager_done));
return NavigationThrottle::DEFER; return NavigationThrottle::DEFER;
} }
...@@ -196,27 +141,11 @@ const char* OriginPolicyThrottle::GetNameForLogging() { ...@@ -196,27 +141,11 @@ const char* OriginPolicyThrottle::GetNameForLogging() {
return "OriginPolicyThrottle"; return "OriginPolicyThrottle";
} }
// static
OriginPolicyThrottle::KnownVersionMap&
OriginPolicyThrottle::GetKnownVersionsForTesting() {
return GetKnownVersions();
}
OriginPolicyThrottle::OriginPolicyThrottle(NavigationHandle* handle) OriginPolicyThrottle::OriginPolicyThrottle(NavigationHandle* handle)
: NavigationThrottle(handle) {} : NavigationThrottle(handle) {}
OriginPolicyThrottle::KnownVersionMap& // TODO(andypaicu): Remove when we have moved reporting logic to the network
OriginPolicyThrottle::GetKnownVersions() { // service.
static base::NoDestructor<KnownVersionMap> map_instance;
return *map_instance;
}
OriginPolicyThrottle::PolicyVersionAndReportTo OriginPolicyThrottle::
GetRequestedPolicyAndReportGroupFromHeaderStringForTesting(
const std::string& header) {
return GetRequestedPolicyAndReportGroupFromHeaderString(header);
}
OriginPolicyThrottle::PolicyVersionAndReportTo OriginPolicyThrottle::PolicyVersionAndReportTo
OriginPolicyThrottle::GetRequestedPolicyAndReportGroupFromHeader() const { OriginPolicyThrottle::GetRequestedPolicyAndReportGroupFromHeader() const {
std::string header; std::string header;
...@@ -317,30 +246,38 @@ void OriginPolicyThrottle::Report(OriginPolicyErrorReason reason, ...@@ -317,30 +246,38 @@ void OriginPolicyThrottle::Report(OriginPolicyErrorReason reason,
const GURL& policy_url) {} const GURL& policy_url) {}
#endif // BUILDFLAG(ENABLE_REPORTING) #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( void OriginPolicyThrottle::OnOriginPolicyManagerRetrieveDone(
const network::mojom::OriginPolicyPtr origin_policy) { const network::mojom::OriginPolicyPtr origin_policy) {
if (origin_policy->state != network::mojom::OriginPolicyState::kLoaded) { 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, CancelNavigation(OriginPolicyErrorReason::kCannotLoadPolicy,
origin_policy->policy_url); origin_policy->policy_url);
return; 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); 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()) static_cast<NavigationHandleImpl*>(navigation_handle())
->navigation_request() ->navigation_request()
->SetOriginPolicy(origin_policy->contents->raw_policy); ->SetOriginPolicy(origin_policy->contents->raw_policy);
Resume(); Resume();
return;
default:
NOTREACHED();
}
} }
} // namespace content } // namespace content
...@@ -12,6 +12,7 @@ ...@@ -12,6 +12,7 @@
#include "base/gtest_prod_util.h" #include "base/gtest_prod_util.h"
#include "base/macros.h" #include "base/macros.h"
#include "base/memory/scoped_refptr.h" #include "base/memory/scoped_refptr.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/navigation_throttle.h" #include "content/public/browser/navigation_throttle.h"
#include "services/network/public/mojom/origin_policy_manager.mojom.h" #include "services/network/public/mojom/origin_policy_manager.mojom.h"
...@@ -59,7 +60,7 @@ class CONTENT_EXPORT OriginPolicyThrottle : public NavigationThrottle { ...@@ -59,7 +60,7 @@ class CONTENT_EXPORT OriginPolicyThrottle : public NavigationThrottle {
// otherwise invalid) policy. This is meant to be called by the security // otherwise invalid) policy. This is meant to be called by the security
// interstitial. // interstitial.
// This will exempt the entire origin, rather than only the given URL. // 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; ~OriginPolicyThrottle() override;
...@@ -70,20 +71,11 @@ class CONTENT_EXPORT OriginPolicyThrottle : public NavigationThrottle { ...@@ -70,20 +71,11 @@ 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();
// 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: private:
explicit OriginPolicyThrottle(NavigationHandle* handle); explicit OriginPolicyThrottle(NavigationHandle* handle);
static KnownVersionMap& GetKnownVersions(); // TODO(andypaicu): Remove when we have moved reporting logic to the network
// service.
// Get the policy name and the reporting group from the header string.
PolicyVersionAndReportTo GetRequestedPolicyAndReportGroupFromHeader() const; PolicyVersionAndReportTo GetRequestedPolicyAndReportGroupFromHeader() const;
static PolicyVersionAndReportTo static PolicyVersionAndReportTo
GetRequestedPolicyAndReportGroupFromHeaderString(const std::string& header); GetRequestedPolicyAndReportGroupFromHeaderString(const std::string& header);
......
...@@ -4,6 +4,7 @@ ...@@ -4,6 +4,7 @@
#include "content/browser/frame_host/origin_policy_throttle.h" #include "content/browser/frame_host/origin_policy_throttle.h"
#include <set>
#include <utility> #include <utility>
#include "base/feature_list.h" #include "base/feature_list.h"
...@@ -40,10 +41,8 @@ class OriginPolicyThrottleTest : public RenderViewHostTestHarness, ...@@ -40,10 +41,8 @@ class OriginPolicyThrottleTest : public RenderViewHostTestHarness,
features_.InitWithFeatureState(features::kOriginPolicy, GetParam()); features_.InitWithFeatureState(features::kOriginPolicy, GetParam());
RenderViewHostTestHarness::SetUp(); RenderViewHostTestHarness::SetUp();
OriginPolicyThrottle::GetKnownVersionsForTesting().clear();
} }
void TearDown() override { void TearDown() override {
OriginPolicyThrottle::GetKnownVersionsForTesting().clear();
nav_handle_.reset(); nav_handle_.reset();
RenderViewHostTestHarness::TearDown(); RenderViewHostTestHarness::TearDown();
} }
...@@ -72,20 +71,28 @@ class TestOriginPolicyManager : public network::mojom::OriginPolicyManager { ...@@ -72,20 +71,28 @@ class TestOriginPolicyManager : public network::mojom::OriginPolicyManager {
const std::string& header_value, const std::string& header_value,
RetrieveOriginPolicyCallback callback) override { RetrieveOriginPolicyCallback callback) override {
auto result = network::mojom::OriginPolicy::New(); auto result = network::mojom::OriginPolicy::New();
if (origin_exceptions_.find(origin) == origin_exceptions_.end()) {
result->state = network::mojom::OriginPolicyState::kLoaded; result->state = network::mojom::OriginPolicyState::kLoaded;
result->contents = network::mojom::OriginPolicyContents::New(); result->contents = network::mojom::OriginPolicyContents::New();
result->contents->raw_policy = kExampleManifestString; result->contents->raw_policy = kExampleManifestString;
result->policy_url = origin.GetURL(); result->policy_url = origin.GetURL();
} else {
result->state = network::mojom::OriginPolicyState::kNoPolicyApplies;
result->policy_url = origin.GetURL();
}
base::ThreadTaskRunnerHandle::Get()->PostTask( base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(&TestOriginPolicyManager::RunCallback, FROM_HERE, base::BindOnce(&TestOriginPolicyManager::RunCallback,
base::Unretained(this), std::move(callback), base::Unretained(this), std::move(callback),
std::move(result))); std::move(result)));
} }
void RunCallback(RetrieveOriginPolicyCallback callback, void RunCallback(RetrieveOriginPolicyCallback callback,
network::mojom::OriginPolicyPtr result) { network::mojom::OriginPolicyPtr result) {
std::move(callback).Run(std::move(result)); std::move(callback).Run(std::move(result));
} }
network::mojom::OriginPolicyManagerPtr GetPtr() { network::mojom::OriginPolicyManagerPtr GetPtr() {
network::mojom::OriginPolicyManagerPtr ptr; network::mojom::OriginPolicyManagerPtr ptr;
auto request = mojo::MakeRequest(&ptr); auto request = mojo::MakeRequest(&ptr);
...@@ -95,7 +102,14 @@ class TestOriginPolicyManager : public network::mojom::OriginPolicyManager { ...@@ -95,7 +102,14 @@ class TestOriginPolicyManager : public network::mojom::OriginPolicyManager {
return ptr; return ptr;
} }
void AddExceptionFor(const url::Origin& origin) override {
origin_exceptions_.insert(origin);
}
private:
std::unique_ptr<mojo::Binding<network::mojom::OriginPolicyManager>> binding_; std::unique_ptr<mojo::Binding<network::mojom::OriginPolicyManager>> binding_;
std::set<url::Origin> origin_exceptions_;
}; };
INSTANTIATE_TEST_SUITE_P(OriginPolicyThrottleTests, INSTANTIATE_TEST_SUITE_P(OriginPolicyThrottleTests,
...@@ -182,25 +196,10 @@ TEST_P(OriginPolicyThrottleTest, RunRequestEndToEnd) { ...@@ -182,25 +196,10 @@ TEST_P(OriginPolicyThrottleTest, RunRequestEndToEnd) {
->ResetOriginPolicyManagerForBrowserProcessForTesting(); ->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) { TEST_P(OriginPolicyThrottleTest, AddExceptionEndToEnd) {
if (!enabled()) if (!enabled())
return; return;
OriginPolicyThrottle::AddExceptionFor(GURL("https://example.org/blubb"));
// Start the navigation. // Start the navigation.
auto navigation = NavigationSimulator::CreateBrowserInitiated( auto navigation = NavigationSimulator::CreateBrowserInitiated(
GURL("https://example.org/bla"), web_contents()); GURL("https://example.org/bla"), web_contents());
...@@ -210,88 +209,47 @@ TEST_P(OriginPolicyThrottleTest, AddExceptionEndToEnd) { ...@@ -210,88 +209,47 @@ TEST_P(OriginPolicyThrottleTest, AddExceptionEndToEnd) {
EXPECT_EQ(NavigationThrottle::PROCEED, EXPECT_EQ(NavigationThrottle::PROCEED,
navigation->GetLastThrottleCheckResult().action()); 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. // Fake a response with a policy header.
const char* raw_headers = const char* raw_headers =
"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));
NavigationHandleImpl* nav_handle =
static_cast<NavigationHandleImpl*>(navigation->GetNavigationHandle());
nav_handle->set_response_headers_for_testing(headers); nav_handle->set_response_headers_for_testing(headers);
navigation->ReadyToCommit(); navigation->ReadyToCommit();
// Due to the exception, we expect the policy to not defer. // The policy manager has to be called even though this is an exception
EXPECT_FALSE(navigation->IsDeferred()); // 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: // Wait until the navigation has been allowed to proceed.
EXPECT_TRUE(OriginPolicyThrottle::IsExemptedForTesting( navigation->Wait();
url::Origin::Create(GURL("https://example.org/bla"))));
}
TEST(OriginPolicyThrottleTest, ParseHeaders) { // At the end of the navigation, the navigation handle should have no policy
const struct { // as this origin should be exempted.
const char* header; EXPECT_EQ("",
const char* policy_version; nav_handle->navigation_request()->common_params().origin_policy);
const char* report_to;
} testcases[] = { static_cast<StoragePartitionImpl*>(
// The common cases: We expect >99% of headers to look like these: BrowserContext::GetStoragePartition(site_instance->GetBrowserContext(),
{"policy=policy", "policy", ""}, site_instance))
{"policy=policy, report-to=endpoint", "policy", "endpoint"}, ->ResetOriginPolicyManagerForBrowserProcessForTesting();
// 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);
}
} }
} // namespace content } // namespace content
...@@ -11,11 +11,14 @@ class GURL; ...@@ -11,11 +11,14 @@ class GURL;
namespace content { namespace content {
class BrowserContext;
// Instruct the Origin Policy throttle to disregard errors for the given URL. // 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 // Intended use: This should be called by the browser when the user selects
// "proceed" on the security interstitial page for the given URL. // "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 } // namespace content
......
...@@ -10,6 +10,7 @@ const char kOriginPolicyWellKnown[] = "/.well-known/origin-policy"; ...@@ -10,6 +10,7 @@ const char kOriginPolicyWellKnown[] = "/.well-known/origin-policy";
const char kOriginPolicyDeletePolicy[] = "0"; const char kOriginPolicyDeletePolicy[] = "0";
const char kOriginPolicyReportTo[] = "report-to"; const char kOriginPolicyReportTo[] = "report-to";
const char kOriginPolicyPolicy[] = "policy"; const char kOriginPolicyPolicy[] = "policy";
// Maximum policy size (implementation-defined limit in bytes). // Maximum policy size (implementation-defined limit in bytes).
// (Limit copied from network::SimpleURLLoader::kMaxBoundedStringDownloadSize.) // (Limit copied from network::SimpleURLLoader::kMaxBoundedStringDownloadSize.)
static const size_t kOriginPolicyMaxPolicySize = 1024 * 1024; static const size_t kOriginPolicyMaxPolicySize = 1024 * 1024;
......
...@@ -8,11 +8,8 @@ ...@@ -8,11 +8,8 @@
#include "base/strings/strcat.h" #include "base/strings/strcat.h"
#include "net/base/load_flags.h" #include "net/base/load_flags.h"
#include "net/http/http_util.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/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/cpp/simple_url_loader.h"
#include "services/network/public/mojom/url_loader_factory.mojom.h"
namespace network { namespace network {
...@@ -89,7 +86,6 @@ void OriginPolicyFetcher::OnPolicyRedirect( ...@@ -89,7 +86,6 @@ void OriginPolicyFetcher::OnPolicyRedirect(
std::vector<std::string>* to_be_removed_headers) { std::vector<std::string>* to_be_removed_headers) {
if (IsValidRedirect(redirect_info)) { if (IsValidRedirect(redirect_info)) {
must_redirect_ = false; must_redirect_ = false;
// TODO(andypaicu): should we callback with the original url or the new url?
fetch_url_ = redirect_info.new_url; fetch_url_ = redirect_info.new_url;
return; return;
} }
...@@ -147,7 +143,6 @@ void OriginPolicyFetcher::FetchPolicy(mojom::URLLoaderFactory* factory) { ...@@ -147,7 +143,6 @@ void OriginPolicyFetcher::FetchPolicy(mojom::URLLoaderFactory* factory) {
void OriginPolicyFetcher::WorkDone(std::unique_ptr<std::string> policy_content, void OriginPolicyFetcher::WorkDone(std::unique_ptr<std::string> policy_content,
mojom::OriginPolicyState state) { mojom::OriginPolicyState state) {
if (callback_) {
auto result = mojom::OriginPolicy::New(); auto result = mojom::OriginPolicy::New();
result->state = state; result->state = state;
if (policy_content) { if (policy_content) {
...@@ -156,11 +151,9 @@ void OriginPolicyFetcher::WorkDone(std::unique_ptr<std::string> policy_content, ...@@ -156,11 +151,9 @@ void OriginPolicyFetcher::WorkDone(std::unique_ptr<std::string> policy_content,
} }
result->policy_url = fetch_url_; 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. // 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( bool OriginPolicyFetcher::IsValidRedirect(
......
...@@ -88,7 +88,7 @@ class COMPONENT_EXPORT(NETWORK_SERVICE) OriginPolicyFetcher { ...@@ -88,7 +88,7 @@ class COMPONENT_EXPORT(NETWORK_SERVICE) OriginPolicyFetcher {
mojom::OriginPolicyManager::RetrieveOriginPolicyCallback callback_; mojom::OriginPolicyManager::RetrieveOriginPolicyCallback callback_;
// Will be true if we started a fetch at <origin>/well-known/origin-policy // 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_; bool must_redirect_;
DISALLOW_COPY_AND_ASSIGN(OriginPolicyFetcher); DISALLOW_COPY_AND_ASSIGN(OriginPolicyFetcher);
......
...@@ -53,6 +53,13 @@ class OriginPolicyFetcherTest : public testing::Test { ...@@ -53,6 +53,13 @@ class OriginPolicyFetcherTest : public testing::Test {
manager_ = std::make_unique<OriginPolicyManager>( manager_ = std::make_unique<OriginPolicyManager>(
network_context_->CreateUrlLoaderFactoryForNetworkService()); 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_; } const url::Origin& test_server_origin() const { return test_server_origin_; }
...@@ -64,16 +71,6 @@ class OriginPolicyFetcherTest : public testing::Test { ...@@ -64,16 +71,6 @@ class OriginPolicyFetcherTest : public testing::Test {
} }
protected: 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 { const net::test_server::EmbeddedTestServer& test_server() const {
return test_server_; return test_server_;
} }
......
...@@ -7,9 +7,18 @@ ...@@ -7,9 +7,18 @@
#include <memory> #include <memory>
#include <utility> #include <utility>
#include "base/logging.h"
#include "base/optional.h" #include "base/optional.h"
#include "net/http/http_util.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 { namespace network {
...@@ -28,47 +37,82 @@ void OriginPolicyManager::RetrieveOriginPolicy( ...@@ -28,47 +37,82 @@ void OriginPolicyManager::RetrieveOriginPolicy(
const url::Origin& origin, const url::Origin& origin,
const std::string& header_value, const std::string& header_value,
RetrieveOriginPolicyCallback callback) { RetrieveOriginPolicyCallback callback) {
DCHECK(origin.GetURL().is_valid());
DCHECK(!origin.opaque());
OriginPolicyHeaderValues header_info = OriginPolicyHeaderValues header_info =
GetRequestedPolicyAndReportGroupFromHeaderString(header_value); GetRequestedPolicyAndReportGroupFromHeaderString(header_value);
if (header_info.policy_version.empty()) {
if (callback) { auto iter = latest_version_map_.find(origin);
auto result = mojom::OriginPolicy::New();
result->state = mojom::OriginPolicyState::kCannotLoadPolicy; // Process policy deletion first!
std::move(callback).Run(std::move(result)); 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;
}
// Process policy exceptions.
if (iter != latest_version_map_.end() &&
iter->second == kExemptedOriginPolicyVersion) {
InvokeCallbackWithPolicyState(origin,
mojom::OriginPolicyState::kNoPolicyApplies,
std::move(callback));
return;
} }
// No policy applies to this request or invalid header present.
if (header_info.policy_version.empty()) {
// 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; return;
} }
header_info.policy_version = iter->second;
} else if (iter == latest_version_map_.end()) {
latest_version_map_.emplace(origin, header_info.policy_version);
} else {
iter->second = header_info.policy_version;
}
// Here we might check the cache and only then start the fetch. origin_policy_fetchers_.emplace(std::make_unique<OriginPolicyFetcher>(
StartPolicyFetch(origin, header_info, std::move(callback)); this, header_info.policy_version, header_info.report_to, origin,
url_loader_factory_.get(), std::move(callback)));
} }
void OriginPolicyManager::RetrieveDefaultOriginPolicy( void OriginPolicyManager::AddExceptionFor(const url::Origin& origin) {
const url::Origin& origin, latest_version_map_[origin] = kExemptedOriginPolicyVersion;
}
void OriginPolicyManager::FetcherDone(OriginPolicyFetcher* fetcher,
mojom::OriginPolicyPtr origin_policy,
RetrieveOriginPolicyCallback callback) { RetrieveOriginPolicyCallback callback) {
// Here we might check the cache and only then start the fetch. std::move(callback).Run(std::move(origin_policy));
StartPolicyFetch(origin, OriginPolicyHeaderValues(), std::move(callback));
auto it = origin_policy_fetchers_.find(fetcher);
DCHECK(it != origin_policy_fetchers_.end());
origin_policy_fetchers_.erase(it);
} }
void OriginPolicyManager::StartPolicyFetch( void OriginPolicyManager::RetrieveDefaultOriginPolicy(
const url::Origin& origin, const url::Origin& origin,
const OriginPolicyHeaderValues& header_info,
RetrieveOriginPolicyCallback callback) { RetrieveOriginPolicyCallback callback) {
if (header_info.policy_version.empty()) {
origin_policy_fetchers_.emplace(std::make_unique<OriginPolicyFetcher>( origin_policy_fetchers_.emplace(std::make_unique<OriginPolicyFetcher>(
this, header_info.report_to, origin, url_loader_factory_.get(), this, std::string() /* report_to */, origin, url_loader_factory_.get(),
std::move(callback))); 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) { // static
auto it = origin_policy_fetchers_.find(fetcher); const char* OriginPolicyManager::GetExemptedVersionForTesting() {
DCHECK(it != origin_policy_fetchers_.end()); return kExemptedOriginPolicyVersion;
origin_policy_fetchers_.erase(it);
} }
// static // static
...@@ -102,4 +146,15 @@ OriginPolicyManager::GetRequestedPolicyAndReportGroupFromHeaderString( ...@@ -102,4 +146,15 @@ OriginPolicyManager::GetRequestedPolicyAndReportGroupFromHeaderString(
return OriginPolicyHeaderValues({policy.value(), report_to.value_or("")}); 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 } // namespace network
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
#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 <map>
#include <memory> #include <memory>
#include <set> #include <set>
#include <string> #include <string>
...@@ -13,12 +14,14 @@ ...@@ -13,12 +14,14 @@
#include "base/containers/unique_ptr_adapters.h" #include "base/containers/unique_ptr_adapters.h"
#include "base/macros.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/origin_policy/origin_policy_constants.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" #include "services/network/public/mojom/url_loader_factory.mojom.h"
namespace network { namespace network {
class OriginPolicyFetcher;
// The OriginPolicyManager is the entry point for all Origin Policy related // The OriginPolicyManager is the entry point for all Origin Policy related
// API calls. Spec: https://wicg.github.io/origin-policy/ // API calls. Spec: https://wicg.github.io/origin-policy/
// A client will likely call AddBinding (or use the NetworkContext function) // A client will likely call AddBinding (or use the NetworkContext function)
...@@ -44,14 +47,17 @@ class COMPONENT_EXPORT(NETWORK_SERVICE) OriginPolicyManager ...@@ -44,14 +47,17 @@ class COMPONENT_EXPORT(NETWORK_SERVICE) OriginPolicyManager
// 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 // mojom::OriginPolicyManager
void RetrieveOriginPolicy(const url::Origin& origin, void RetrieveOriginPolicy(const url::Origin& origin,
const std::string& header_value, const std::string& header_value,
RetrieveOriginPolicyCallback callback) override; RetrieveOriginPolicyCallback callback) override;
void AddExceptionFor(const url::Origin& origin) override;
// To be called by fetcher when it has finished its work. // To be called by fetcher when it has finished its work.
// This removes the fetcher which results in the fetcher being destroyed. // 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 // Retrieves an origin's default origin policy by attempting to fetch it
// from "<origin>/.well-known/origin-policy". // from "<origin>/.well-known/origin-policy".
...@@ -69,18 +75,29 @@ class COMPONENT_EXPORT(NETWORK_SERVICE) OriginPolicyManager ...@@ -69,18 +75,29 @@ class COMPONENT_EXPORT(NETWORK_SERVICE) OriginPolicyManager
return GetRequestedPolicyAndReportGroupFromHeaderString(header_value); return GetRequestedPolicyAndReportGroupFromHeaderString(header_value);
} }
// Get the version used for exempted policies. For testing purposes only.
static const char* GetExemptedVersionForTesting();
private: private:
using KnownVersionMap = std::map<url::Origin, std::string>;
// Parses a header and returns the result. If a parsed result does not contain // 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. // a non-empty policy version it means the `header_value` is invalid.
static OriginPolicyHeaderValues static OriginPolicyHeaderValues
GetRequestedPolicyAndReportGroupFromHeaderString( GetRequestedPolicyAndReportGroupFromHeaderString(
const std::string& header_value); const std::string& header_value);
// Will start a fetch based on the provided origin and info. // Returns an origin policy with the specified state. The contents is empty
void StartPolicyFetch(const url::Origin& origin, // and the `policy_url` is the default policy url for the specified origin.
const OriginPolicyHeaderValues& header_info, static void InvokeCallbackWithPolicyState(
const url::Origin& origin,
mojom::OriginPolicyState state,
RetrieveOriginPolicyCallback callback); 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 // A list of fetchers owned by this object
std::set<std::unique_ptr<OriginPolicyFetcher>, base::UniquePtrComparator> std::set<std::unique_ptr<OriginPolicyFetcher>, base::UniquePtrComparator>
origin_policy_fetchers_; origin_policy_fetchers_;
......
...@@ -81,7 +81,14 @@ struct OriginPolicy { ...@@ -81,7 +81,14 @@ struct OriginPolicy {
interface OriginPolicyManager { interface OriginPolicyManager {
// Attempts to retrieve the origin policy for an origin and // Attempts to retrieve the origin policy for an origin and
// `Sec-Origin-Policy` HTTP header value. Calls back with the result. // `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 // https://wicg.github.io/origin-policy/#origin-policy-header
RetrieveOriginPolicy(url.mojom.Origin origin, string header_value) RetrieveOriginPolicy(url.mojom.Origin origin, string header_value)
=> (OriginPolicy origin_policy); => (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