Commit d63edc1a authored by arthursonzogni's avatar arthursonzogni Committed by Commit Bot

[COOP] reporting: Add origin trial support behind a flag.

Add support for CrossOriginOpenerPolicyReporting experiment for
OriginTrial.

This is put behind the flag CrossOriginOpenerPolicyReportingOriginTrial.
The flag is disabled for now. Once approuved it will be switched to
enabled and will be used as a kill-switch.

Bug: 1116413
Change-Id: I48abfee8e9b9369926a94d03a4c1f4c2afec5f2b
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2362122Reviewed-by: default avatarKinuko Yasuda <kinuko@chromium.org>
Reviewed-by: default avatarCamille Lamy <clamy@chromium.org>
Commit-Queue: Arthur Sonzogni <arthursonzogni@chromium.org>
Cr-Commit-Position: refs/heads/master@{#799692}
parent f21f948b
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
#include "base/test/bind_test_util.h"
#include "base/test/scoped_feature_list.h" #include "base/test/scoped_feature_list.h"
#include "components/network_session_configurator/common/network_switches.h" #include "components/network_session_configurator/common/network_switches.h"
#include "content/browser/frame_host/navigation_request.h" #include "content/browser/frame_host/navigation_request.h"
...@@ -13,6 +14,7 @@ ...@@ -13,6 +14,7 @@
#include "content/public/test/browser_test.h" #include "content/public/test/browser_test.h"
#include "content/public/test/content_browser_test.h" #include "content/public/test/content_browser_test.h"
#include "content/public/test/content_browser_test_utils.h" #include "content/public/test/content_browser_test_utils.h"
#include "content/public/test/url_loader_interceptor.h"
#include "content/shell/browser/shell.h" #include "content/shell/browser/shell.h"
#include "content/test/content_browser_test_utils_internal.h" #include "content/test/content_browser_test_utils_internal.h"
#include "content/test/render_document_feature.h" #include "content/test/render_document_feature.h"
...@@ -20,6 +22,9 @@ ...@@ -20,6 +22,9 @@
#include "net/test/embedded_test_server/default_handlers.h" #include "net/test/embedded_test_server/default_handlers.h"
#include "services/network/public/cpp/cross_origin_opener_policy.h" #include "services/network/public/cpp/cross_origin_opener_policy.h"
#include "services/network/public/cpp/features.h" #include "services/network/public/cpp/features.h"
#include "testing/gmock/include/gmock/gmock.h"
using ::testing::HasSubstr;
namespace content { namespace content {
...@@ -1880,4 +1885,214 @@ static auto kTestParams = ...@@ -1880,4 +1885,214 @@ static auto kTestParams =
INSTANTIATE_TEST_SUITE_P(All, CrossOriginOpenerPolicyBrowserTest, kTestParams); INSTANTIATE_TEST_SUITE_P(All, CrossOriginOpenerPolicyBrowserTest, kTestParams);
INSTANTIATE_TEST_SUITE_P(All, VirtualBrowsingContextGroupTest, kTestParams); INSTANTIATE_TEST_SUITE_P(All, VirtualBrowsingContextGroupTest, kTestParams);
namespace {
// Ensure the CrossOriginOpenerPolicyReporting origin trial is correctly
// implemented.
class CoopReportingOriginTrialBrowserTest : public ContentBrowserTest {
public:
CoopReportingOriginTrialBrowserTest() {
feature_list_.InitWithFeatures(
{
// Enabled
network::features::kCrossOriginOpenerPolicy,
network::features::kCrossOriginEmbedderPolicy,
network::features::kCrossOriginOpenerPolicyAccessReporting,
network::features::kCrossOriginOpenerPolicyReportingOriginTrial,
},
{
// Disabled
network::features::kCrossOriginOpenerPolicyReporting,
});
}
// Origin Trials key generated with:
//
// tools/origin_trials/generate_token.py --expire-days 5000 --version 3
// https://coop.security:9999 CrossOriginOpenerPolicyReporting
static std::string OriginTrialToken() {
return "A5U4dXG9lYhhLSumDmXNObrt5xJ0XVpSfw/"
"w7q+MYzOziNnHfcl1ZShjKjecyEc3E5vDtHV+"
"wiLMbqukLwhs8gIAAABteyJvcmlnaW4iOiAiaHR0cHM6Ly9jb29wLnNlY3VyaXR5Ojk"
"5OTkiLCAiZmVhdHVyZSI6ICJDcm9zc09yaWdpbk9wZW5lclBvbGljeVJlcG9ydGluZy"
"IsICJleHBpcnkiOiAyMDI5NzA4MDA3fQ==";
}
// The OriginTrial token is bound to a given origin. Since the
// EmbeddedTestServer's port changes after every test run, it can't be used.
// As a result, response must be served using a URLLoaderInterceptor.
GURL OriginTrialURL() { return GURL("https://coop.security:9999"); }
WebContentsImpl* web_contents() const {
return static_cast<WebContentsImpl*>(shell()->web_contents());
}
RenderFrameHostImpl* current_frame_host() {
return web_contents()->GetMainFrame();
}
net::EmbeddedTestServer* https_server() { return &https_server_; }
private:
void SetUpOnMainThread() final {
ContentBrowserTest::TearDownOnMainThread();
host_resolver()->AddRule("*", "127.0.0.1");
ASSERT_TRUE(embedded_test_server()->Start());
https_server()->ServeFilesFromSourceDirectory(GetTestDataFilePath());
SetupCrossSiteRedirector(https_server());
net::test_server::RegisterDefaultHandlers(&https_server_);
ASSERT_TRUE(https_server()->Start());
}
void TearDownOnMainThread() final {
ContentBrowserTest::TearDownOnMainThread();
}
void SetUpCommandLine(base::CommandLine* command_line) final {
ContentBrowserTest::SetUpCommandLine(command_line);
command_line->AppendSwitch(switches::kIgnoreCertificateErrors);
}
private:
base::test::ScopedFeatureList feature_list_;
net::EmbeddedTestServer https_server_;
};
} // namespace
IN_PROC_BROWSER_TEST_F(CoopReportingOriginTrialBrowserTest,
CoopStateWithoutToken) {
URLLoaderInterceptor interceptor(base::BindLambdaForTesting(
[&](URLLoaderInterceptor::RequestParams* params) {
if (params->url_request.url != OriginTrialURL())
return false;
URLLoaderInterceptor::WriteResponse(
"HTTP/1.1 200 OK\n"
"Content-type: text/html\n"
"Cross-Origin-Opener-Policy: same-origin; report-to=\"a\"\n"
"Cross-Origin-Opener-Policy-Report-Only: same-origin; "
"report-to=\"b\"\n"
"Cross-Origin-Embedder-Policy: require-corp\n"
"\n",
"", params->client.get());
return true;
}));
EXPECT_TRUE(NavigateToURL(shell(), OriginTrialURL()));
network::CrossOriginOpenerPolicy coop =
current_frame_host()->cross_origin_opener_policy();
EXPECT_EQ(coop.reporting_endpoint, base::nullopt);
EXPECT_EQ(coop.report_only_reporting_endpoint, base::nullopt);
EXPECT_EQ(coop.value,
network::mojom::CrossOriginOpenerPolicyValue::kSameOriginPlusCoep);
EXPECT_EQ(coop.report_only_value,
network::mojom::CrossOriginOpenerPolicyValue::kUnsafeNone);
}
IN_PROC_BROWSER_TEST_F(CoopReportingOriginTrialBrowserTest,
CoopStateWithToken) {
URLLoaderInterceptor interceptor(base::BindLambdaForTesting(
[&](URLLoaderInterceptor::RequestParams* params) {
if (params->url_request.url != OriginTrialURL())
return false;
URLLoaderInterceptor::WriteResponse(
"HTTP/1.1 200 OK\n"
"Content-type: text/html\n"
"Cross-Origin-Opener-Policy: same-origin; report-to=\"a\"\n"
"Cross-Origin-Opener-Policy-Report-Only: same-origin; "
"report-to=\"b\"\n"
"Cross-Origin-Embedder-Policy: require-corp\n"
"Origin-Trial: " +
OriginTrialToken() + "\n\n",
"", params->client.get());
return true;
}));
EXPECT_TRUE(NavigateToURL(shell(), OriginTrialURL()));
network::CrossOriginOpenerPolicy coop =
current_frame_host()->cross_origin_opener_policy();
EXPECT_EQ(coop.reporting_endpoint, "a");
EXPECT_EQ(coop.report_only_reporting_endpoint, "b");
EXPECT_EQ(coop.value,
network::mojom::CrossOriginOpenerPolicyValue::kSameOriginPlusCoep);
EXPECT_EQ(coop.report_only_value,
network::mojom::CrossOriginOpenerPolicyValue::kSameOriginPlusCoep);
}
IN_PROC_BROWSER_TEST_F(CoopReportingOriginTrialBrowserTest,
AccessReportingWithoutToken) {
URLLoaderInterceptor interceptor(base::BindLambdaForTesting(
[&](URLLoaderInterceptor::RequestParams* params) {
if (params->url_request.url != OriginTrialURL())
return false;
URLLoaderInterceptor::WriteResponse(
"HTTP/1.1 200 OK\n"
"Content-type: text/html\n"
"Cross-Origin-Opener-Policy-Report-Only: same-origin; "
"report-to=\"b\"\n"
"Cross-Origin-Embedder-Policy: require-corp\n\n",
"", params->client.get());
return true;
}));
EXPECT_TRUE(NavigateToURL(shell(), OriginTrialURL()));
ShellAddedObserver shell_observer;
GURL openee_url = https_server()->GetURL("a.com", "/title1.html");
EXPECT_TRUE(ExecJs(current_frame_host(),
JsReplace("openee = window.open($1);", openee_url)));
auto* popup =
static_cast<WebContentsImpl*>(shell_observer.GetShell()->web_contents());
WaitForLoadStop(popup);
auto eval = EvalJs(current_frame_host(), R"(
new Promise(resolve => {
let observer = new ReportingObserver(()=>{});
observer.observe();
openee.postMessage("hello");
let reports = observer.takeRecords();
resolve(JSON.stringify(reports));
});
)");
std::string reports = eval.ExtractString();
EXPECT_EQ("[]", reports);
}
IN_PROC_BROWSER_TEST_F(CoopReportingOriginTrialBrowserTest,
AccessReportingWithToken) {
URLLoaderInterceptor interceptor(base::BindLambdaForTesting(
[&](URLLoaderInterceptor::RequestParams* params) {
if (params->url_request.url != OriginTrialURL())
return false;
URLLoaderInterceptor::WriteResponse(
"HTTP/1.1 200 OK\n"
"Content-type: text/html\n"
"Cross-Origin-Opener-Policy-Report-Only: same-origin; "
"report-to=\"b\"\n"
"Cross-Origin-Embedder-Policy: require-corp\n"
"Origin-Trial: " +
OriginTrialToken() + "\n\n",
"", params->client.get());
return true;
}));
EXPECT_TRUE(NavigateToURL(shell(), OriginTrialURL()));
ShellAddedObserver shell_observer;
GURL openee_url = https_server()->GetURL("a.com", "/title1.html");
EXPECT_TRUE(ExecJs(current_frame_host(),
JsReplace("openee = window.open($1);", openee_url)));
auto* popup =
static_cast<WebContentsImpl*>(shell_observer.GetShell()->web_contents());
WaitForLoadStop(popup);
auto eval = EvalJs(current_frame_host(), R"(
new Promise(resolve => {
let observer = new ReportingObserver(()=>{});
observer.observe();
openee.postMessage("hello");
let reports = observer.takeRecords();
resolve(JSON.stringify(reports));
});
)");
std::string reports = eval.ExtractString();
EXPECT_THAT(reports, HasSubstr("coop-access-violation"));
}
} // namespace content } // namespace content
...@@ -7,13 +7,16 @@ ...@@ -7,13 +7,16 @@
#include <utility> #include <utility>
#include "base/feature_list.h" #include "base/feature_list.h"
#include "base/time/time.h"
#include "content/browser/frame_host/frame_tree_node.h" #include "content/browser/frame_host/frame_tree_node.h"
#include "services/network/public/cpp/features.h" #include "services/network/public/cpp/features.h"
#include "services/network/public/cpp/is_potentially_trustworthy.h" #include "services/network/public/cpp/is_potentially_trustworthy.h"
#include "third_party/blink/public/common/origin_trials/trial_token_validator.h"
namespace content { namespace content {
namespace { namespace {
// This function implements the COOP matching algorithm as detailed in [1]. // This function implements the COOP matching algorithm as detailed in [1].
// Note that COEP is also provided since the COOP enum does not have a // Note that COEP is also provided since the COOP enum does not have a
// "same-origin + COEP" value. // "same-origin + COEP" value.
...@@ -91,15 +94,15 @@ CrossOriginOpenerPolicyStatus::~CrossOriginOpenerPolicyStatus() = default; ...@@ -91,15 +94,15 @@ CrossOriginOpenerPolicyStatus::~CrossOriginOpenerPolicyStatus() = default;
base::Optional<network::mojom::BlockedByResponseReason> base::Optional<network::mojom::BlockedByResponseReason>
CrossOriginOpenerPolicyStatus::EnforceCOOP( CrossOriginOpenerPolicyStatus::EnforceCOOP(
network::mojom::ParsedHeaders* parsed_headers, network::mojom::URLResponseHead* response_head,
const url::Origin& response_origin, const url::Origin& response_origin,
const GURL& response_url) { const GURL& response_url) {
SanitizeCoopHeaders(response_origin, parsed_headers); SanitizeCoopHeaders(response_url, response_origin, response_head);
network::mojom::ParsedHeaders* parsed_headers =
response_head->parsed_headers.get();
// Return early if the situation prevents COOP from operating. // Return early if the situation prevents COOP from operating.
if (!frame_tree_node_->IsMainFrame() || if (!frame_tree_node_->IsMainFrame() ||
!base::FeatureList::IsEnabled(
network::features::kCrossOriginOpenerPolicy) ||
response_url.IsAboutBlank()) { response_url.IsAboutBlank()) {
return base::nullopt; return base::nullopt;
} }
...@@ -168,21 +171,54 @@ CrossOriginOpenerPolicyStatus::EnforceCOOP( ...@@ -168,21 +171,54 @@ CrossOriginOpenerPolicyStatus::EnforceCOOP(
// We blank out the COOP headers in a number of situations. // We blank out the COOP headers in a number of situations.
// - When the headers were not sent over HTTPS. // - When the headers were not sent over HTTPS.
// - For subframes. // - For subframes.
// - When the feature is disabled.
// We also strip the "reporting" parts when the reporting feature is disabled
// for the |response_origin|.
void CrossOriginOpenerPolicyStatus::SanitizeCoopHeaders( void CrossOriginOpenerPolicyStatus::SanitizeCoopHeaders(
const GURL& response_url,
const url::Origin& response_origin, const url::Origin& response_origin,
network::mojom::ParsedHeaders* parsed_headers) { network::mojom::URLResponseHead* response_head) {
network::CrossOriginOpenerPolicy& coop = network::CrossOriginOpenerPolicy& coop =
parsed_headers->cross_origin_opener_policy; response_head->parsed_headers->cross_origin_opener_policy;
if (network::IsOriginPotentiallyTrustworthy(response_origin) && if (coop == network::CrossOriginOpenerPolicy())
frame_tree_node_->IsMainFrame())
return; return;
if (coop == network::CrossOriginOpenerPolicy()) if (!base::FeatureList::IsEnabled(
network::features::kCrossOriginOpenerPolicy) ||
// https://html.spec.whatwg.org/multipage#the-cross-origin-opener-policy-header
// ```
// 1. If reservedEnvironment is a non-secure context, then return
// "unsafe-none".
// ```
!network::IsOriginPotentiallyTrustworthy(response_origin) ||
// The COOP header must be ignored outside of the top-level context. It is
// removed as a defensive measure.
!frame_tree_node_->IsMainFrame()) {
coop = network::CrossOriginOpenerPolicy();
if (!network::IsOriginPotentiallyTrustworthy(response_origin))
header_ignored_due_to_insecure_context_ = true;
return; return;
coop = network::CrossOriginOpenerPolicy(); }
if (!network::IsOriginPotentiallyTrustworthy(response_origin)) // The reporting part can be enabled via either a command-line flag or an
header_ignored_due_to_insecure_context_ = true; // origin trial.
bool reporting_enabled = base::FeatureList::IsEnabled(
network::features::kCrossOriginOpenerPolicyReporting);
reporting_enabled |=
base::FeatureList::IsEnabled(
network::features::kCrossOriginOpenerPolicyReportingOriginTrial) &&
blink::TrialTokenValidator().RequestEnablesFeature(
response_url, response_head->headers.get(),
"CrossOriginOpenerPolicyReporting", base::Time::Now());
if (!reporting_enabled) {
coop.reporting_endpoint = base::nullopt;
coop.report_only_reporting_endpoint = base::nullopt;
coop.report_only_value =
network::mojom::CrossOriginOpenerPolicyValue::kUnsafeNone;
}
} }
} // namespace content } // namespace content
...@@ -25,7 +25,7 @@ class CrossOriginOpenerPolicyStatus { ...@@ -25,7 +25,7 @@ class CrossOriginOpenerPolicyStatus {
// Called after receiving a network response. Returns a BlockedByResponse // Called after receiving a network response. Returns a BlockedByResponse
// reason if the navigation should be blocked, nullopt otherwise. // reason if the navigation should be blocked, nullopt otherwise.
base::Optional<network::mojom::BlockedByResponseReason> EnforceCOOP( base::Optional<network::mojom::BlockedByResponseReason> EnforceCOOP(
network::mojom::ParsedHeaders* parsed_headers, network::mojom::URLResponseHead* response_head,
const url::Origin& response_origin, const url::Origin& response_origin,
const GURL& response_url); const GURL& response_url);
...@@ -78,8 +78,9 @@ class CrossOriginOpenerPolicyStatus { ...@@ -78,8 +78,9 @@ class CrossOriginOpenerPolicyStatus {
private: private:
// Make sure COOP is relevant or clear the COOP headers. // Make sure COOP is relevant or clear the COOP headers.
void SanitizeCoopHeaders(const url::Origin& response_origin, void SanitizeCoopHeaders(const GURL& response_url,
network::mojom::ParsedHeaders* parsed_headers); const url::Origin& response_origin,
network::mojom::URLResponseHead* response_head);
// Tracks the FrameTreeNode in which this navigation is taking place. // Tracks the FrameTreeNode in which this navigation is taking place.
const FrameTreeNode* frame_tree_node_; const FrameTreeNode* frame_tree_node_;
......
...@@ -1650,16 +1650,6 @@ NavigationRequest::TakeCoepReporter() { ...@@ -1650,16 +1650,6 @@ NavigationRequest::TakeCoepReporter() {
void NavigationRequest::CreateCoopReporter( void NavigationRequest::CreateCoopReporter(
StoragePartition* storage_partition) { StoragePartition* storage_partition) {
// If the flag for reporting is off, we simply don't create anything.
// Since this is the only place we create COOP reporters this ensure reporting
// is completely off.
// Note that "popup inheritance" also instantiate a reporter, but only if we
// created one here first.
if (!base::FeatureList::IsEnabled(
network::features::kCrossOriginOpenerPolicyReporting)) {
return;
}
// If the main document hasn't specified any network report endpoint(s), // If the main document hasn't specified any network report endpoint(s),
// then it is likely not interested in receiving: // then it is likely not interested in receiving:
// 1. Network reports (for obvious reasons). // 1. Network reports (for obvious reasons).
...@@ -1798,8 +1788,8 @@ void NavigationRequest::OnRequestRedirected( ...@@ -1798,8 +1788,8 @@ void NavigationRequest::OnRequestRedirected(
// from the response URL. // from the response URL.
const base::Optional<network::mojom::BlockedByResponseReason> const base::Optional<network::mojom::BlockedByResponseReason>
coop_requires_blocking = coop_status_.EnforceCOOP( coop_requires_blocking = coop_status_.EnforceCOOP(
response_head_->parsed_headers.get(), response_head_.get(), url::Origin::Create(common_params_->url),
url::Origin::Create(common_params_->url), common_params_->url); common_params_->url);
if (coop_requires_blocking) { if (coop_requires_blocking) {
OnRequestFailedInternal( OnRequestFailedInternal(
network::URLLoaderCompletionStatus(*coop_requires_blocking), network::URLLoaderCompletionStatus(*coop_requires_blocking),
...@@ -2229,8 +2219,8 @@ void NavigationRequest::OnResponseStarted( ...@@ -2229,8 +2219,8 @@ void NavigationRequest::OnResponseStarted(
// from the response URL. // from the response URL.
const base::Optional<network::mojom::BlockedByResponseReason> const base::Optional<network::mojom::BlockedByResponseReason>
coop_requires_blocking = coop_status_.EnforceCOOP( coop_requires_blocking = coop_status_.EnforceCOOP(
response_head_->parsed_headers.get(), response_head_.get(), url::Origin::Create(common_params_->url),
url::Origin::Create(common_params_->url), common_params_->url); common_params_->url);
if (coop_requires_blocking) { if (coop_requires_blocking) {
OnRequestFailedInternal( OnRequestFailedInternal(
network::URLLoaderCompletionStatus(*coop_requires_blocking), network::URLLoaderCompletionStatus(*coop_requires_blocking),
......
...@@ -88,6 +88,14 @@ const base::Feature kProactivelyThrottleLowPriorityRequests{ ...@@ -88,6 +88,14 @@ const base::Feature kProactivelyThrottleLowPriorityRequests{
const base::Feature kCrossOriginOpenerPolicy{"CrossOriginOpenerPolicy", const base::Feature kCrossOriginOpenerPolicy{"CrossOriginOpenerPolicy",
base::FEATURE_ENABLED_BY_DEFAULT}; base::FEATURE_ENABLED_BY_DEFAULT};
// Enables Cross-Origin-Opener-Policy reporting API origin trial. Serves a dual
// purpose:
// 1) Disable the field trial up until it gets approuved.
// 2) Used as a kill-switch during the experiment.
const base::Feature kCrossOriginOpenerPolicyReportingOriginTrial{
"CrossOriginOpenerPolicyReportingOriginTrial",
base::FEATURE_DISABLED_BY_DEFAULT};
// Enables Cross-Origin Opener Policy (COOP) reporting. // Enables Cross-Origin Opener Policy (COOP) reporting.
// https://gist.github.com/annevk/6f2dd8c79c77123f39797f6bdac43f3e // https://gist.github.com/annevk/6f2dd8c79c77123f39797f6bdac43f3e
const base::Feature kCrossOriginOpenerPolicyReporting{ const base::Feature kCrossOriginOpenerPolicyReporting{
......
...@@ -39,6 +39,8 @@ extern const base::Feature kCrossOriginOpenerPolicy; ...@@ -39,6 +39,8 @@ extern const base::Feature kCrossOriginOpenerPolicy;
COMPONENT_EXPORT(NETWORK_CPP) COMPONENT_EXPORT(NETWORK_CPP)
extern const base::Feature kCrossOriginOpenerPolicyReporting; extern const base::Feature kCrossOriginOpenerPolicyReporting;
COMPONENT_EXPORT(NETWORK_CPP) COMPONENT_EXPORT(NETWORK_CPP)
extern const base::Feature kCrossOriginOpenerPolicyReportingOriginTrial;
COMPONENT_EXPORT(NETWORK_CPP)
extern const base::Feature kCrossOriginOpenerPolicyAccessReporting; extern const base::Feature kCrossOriginOpenerPolicyAccessReporting;
COMPONENT_EXPORT(NETWORK_CPP) COMPONENT_EXPORT(NETWORK_CPP)
extern const base::Feature kCrossOriginEmbedderPolicy; extern const base::Feature kCrossOriginEmbedderPolicy;
......
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