blob: 18c062e49da93bf7c89129fef920ea0747424d0e [file]
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "base/command_line.h"
#include "base/test/scoped_feature_list.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "components/network_session_configurator/common/network_switches.h"
#include "content/public/browser/web_contents.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/content_browser_test_utils.h"
#include "net/dns/mock_host_resolver.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "services/network/public/cpp/features.h"
#include "services/network/public/mojom/cross_origin_opener_policy.mojom.h"
// Web platform security features are implemented by content/ and blink/.
// However, since ContentBrowserClientImpl::LogWebFeatureForCurrentPage() is
// currently left blank in content/, metrics logging can't be tested from
// content/. So it is tested from chrome/ instead.
class ChromeWebPlatformSecurityMetricsBrowserTest
: public InProcessBrowserTest {
public:
ChromeWebPlatformSecurityMetricsBrowserTest()
: https_server_(net::EmbeddedTestServer::TYPE_HTTPS),
http_server_(net::EmbeddedTestServer::TYPE_HTTP) {
features_.InitWithFeatures(
{
// Enabled:
network::features::kCrossOriginOpenerPolicy,
network::features::kCrossOriginEmbedderPolicy,
network::features::kCrossOriginOpenerPolicyReporting,
},
{});
}
content::WebContents* web_contents() const {
return browser()->tab_strip_model()->GetActiveWebContents();
}
void set_monitored_feature(blink::mojom::WebFeature feature) {
monitored_feature_ = feature;
}
void LoadIFrame(const GURL& url) {
EXPECT_TRUE(content::ExecJs(web_contents(), content::JsReplace(R"(
new Promise(resolve => {
let iframe = document.createElement("iframe");
iframe.src = $1;
iframe.onload = resolve;
document.body.appendChild(iframe);
});
)",
url)));
}
void ExpectHistogramIncreasedBy(int count) {
expected_count_ += count;
EXPECT_TRUE(content::NavigateToURL(web_contents(), GURL("about:blank")));
histogram_.ExpectBucketCount("Blink.UseCounter.Features",
monitored_feature_, expected_count_);
}
net::EmbeddedTestServer& https_server() { return https_server_; }
net::EmbeddedTestServer& http_server() { return http_server_; }
private:
void SetUpOnMainThread() final {
host_resolver()->AddRule("*", "127.0.0.1");
https_server_.AddDefaultHandlers(GetChromeTestDataDir());
http_server_.AddDefaultHandlers(GetChromeTestDataDir());
https_server_.SetSSLConfig(net::EmbeddedTestServer::CERT_OK);
ASSERT_TRUE(https_server_.Start());
ASSERT_TRUE(http_server_.Start());
EXPECT_TRUE(content::NavigateToURL(web_contents(), GURL("about:blank")));
}
void SetUpCommandLine(base::CommandLine* command_line) final {
InProcessBrowserTest::SetUpCommandLine(command_line);
command_line->AppendSwitch(switches::kIgnoreCertificateErrors);
}
net::EmbeddedTestServer https_server_;
net::EmbeddedTestServer http_server_;
int expected_count_ = 0;
base::HistogramTester histogram_;
blink::mojom::WebFeature monitored_feature_;
base::test::ScopedFeatureList features_;
};
// Check the kCrossOriginOpenerPolicyReporting feature usage. No header => 0
// count.
IN_PROC_BROWSER_TEST_F(ChromeWebPlatformSecurityMetricsBrowserTest,
CrossOriginOpenerPolicyReportingNoHeader) {
set_monitored_feature(
blink::mojom::WebFeature::kCrossOriginOpenerPolicyReporting);
GURL url = https_server().GetURL("a.com", "/title1.html");
EXPECT_TRUE(content::NavigateToURL(web_contents(), url));
ExpectHistogramIncreasedBy(0);
}
// Check the kCrossOriginOpenerPolicyReporting feature usage. COOP-Report-Only +
// HTTP => 0 count.
IN_PROC_BROWSER_TEST_F(ChromeWebPlatformSecurityMetricsBrowserTest,
CrossOriginOpenerPolicyReportingReportOnlyHTTP) {
set_monitored_feature(
blink::mojom::WebFeature::kCrossOriginOpenerPolicyReporting);
GURL url = http_server().GetURL("a.com",
"/set-header?"
"Cross-Origin-Opener-Policy-Report-Only: "
"same-origin; report-to%3d\"a\"");
EXPECT_TRUE(content::NavigateToURL(web_contents(), url));
ExpectHistogramIncreasedBy(0);
}
// Check the kCrossOriginOpenerPolicyReporting feature usage. COOP-Report-Only +
// HTTPS => 1 count.
IN_PROC_BROWSER_TEST_F(ChromeWebPlatformSecurityMetricsBrowserTest,
CrossOriginOpenerPolicyReportingReportOnlyHTTPS) {
set_monitored_feature(
blink::mojom::WebFeature::kCrossOriginOpenerPolicyReporting);
GURL url = https_server().GetURL("a.com",
"/set-header?"
"Cross-Origin-Opener-Policy-Report-Only: "
"same-origin; report-to%3d\"a\"");
EXPECT_TRUE(content::NavigateToURL(web_contents(), url));
ExpectHistogramIncreasedBy(1);
}
// Check the kCrossOriginOpenerPolicyReporting feature usage. COOP + HTPS => 1
// count.
IN_PROC_BROWSER_TEST_F(ChromeWebPlatformSecurityMetricsBrowserTest,
CrossOriginOpenerPolicyReportingCOOPHTTPS) {
set_monitored_feature(
blink::mojom::WebFeature::kCrossOriginOpenerPolicyReporting);
GURL url = https_server().GetURL("a.com",
"/set-header?"
"Cross-Origin-Opener-Policy: "
"same-origin; report-to%3d\"a\"");
EXPECT_TRUE(content::NavigateToURL(web_contents(), url));
ExpectHistogramIncreasedBy(1);
}
// Check the kCrossOriginOpenerPolicyReporting feature usage. COOP + COOP-RO +
// HTTPS => 1 count.
IN_PROC_BROWSER_TEST_F(ChromeWebPlatformSecurityMetricsBrowserTest,
CrossOriginOpenerPolicyReportingCOOPAndReportOnly) {
set_monitored_feature(
blink::mojom::WebFeature::kCrossOriginOpenerPolicyReporting);
GURL url = https_server().GetURL("a.com",
"/set-header?"
"Cross-Origin-Opener-Policy: "
"same-origin; report-to%3d\"a\"&"
"Cross-Origin-Opener-Policy-Report-Only: "
"same-origin; report-to%3d\"a\"");
EXPECT_TRUE(content::NavigateToURL(web_contents(), url));
ExpectHistogramIncreasedBy(1);
}
// Check the kCrossOriginOpenerPolicyReporting feature usage. No report
// endpoints defined => 0 count.
IN_PROC_BROWSER_TEST_F(ChromeWebPlatformSecurityMetricsBrowserTest,
CrossOriginOpenerPolicyReportingNoEndpoint) {
set_monitored_feature(
blink::mojom::WebFeature::kCrossOriginOpenerPolicyReporting);
GURL url = https_server().GetURL(
"a.com",
"/set-header?"
"Cross-Origin-Opener-Policy: same-origin&"
"Cross-Origin-Opener-Policy-Report-Only: same-origin");
EXPECT_TRUE(content::NavigateToURL(web_contents(), url));
ExpectHistogramIncreasedBy(0);
}
// Check the kCrossOriginOpenerPolicyReporting feature usage. Main frame
// (COOP-RO), subframe (COOP-RO) => 1 count.
IN_PROC_BROWSER_TEST_F(ChromeWebPlatformSecurityMetricsBrowserTest,
CrossOriginOpenerPolicyReportingMainFrameAndSubframe) {
set_monitored_feature(
blink::mojom::WebFeature::kCrossOriginOpenerPolicyReporting);
GURL url = https_server().GetURL("a.com",
"/set-header?"
"Cross-Origin-Opener-Policy-Report-Only: "
"same-origin; report-to%3d\"a\"");
EXPECT_TRUE(content::NavigateToURL(web_contents(), url));
LoadIFrame(url);
ExpectHistogramIncreasedBy(1);
}
// Check the kCrossOriginOpenerPolicyReporting feature usage. Main frame
// (no-headers), subframe (COOP-RO) => 0 count.
IN_PROC_BROWSER_TEST_F(ChromeWebPlatformSecurityMetricsBrowserTest,
CrossOriginOpenerPolicyReportingUsageSubframeOnly) {
set_monitored_feature(
blink::mojom::WebFeature::kCrossOriginOpenerPolicyReporting);
GURL main_document_url = https_server().GetURL("a.com", "/title1.html");
GURL sub_document_url =
https_server().GetURL("a.com",
"/set-header?"
"Cross-Origin-Opener-Policy-Report-Only: "
"same-origin; report-to%3d\"a\"");
EXPECT_TRUE(content::NavigateToURL(web_contents(), main_document_url));
LoadIFrame(sub_document_url);
ExpectHistogramIncreasedBy(0);
}
// Check kCrossOriginSubframeWithoutEmbeddingControl reporting. Same-origin
// iframe (no headers) => 0 count.
IN_PROC_BROWSER_TEST_F(ChromeWebPlatformSecurityMetricsBrowserTest,
CrossOriginSubframeWithoutEmbeddingControlSameOrigin) {
set_monitored_feature(
blink::mojom::WebFeature::kCrossOriginSubframeWithoutEmbeddingControl);
GURL url = https_server().GetURL("a.com", "/title1.html");
EXPECT_TRUE(content::NavigateToURL(web_contents(), url));
LoadIFrame(url);
ExpectHistogramIncreasedBy(0);
}
// Check kCrossOriginSubframeWithoutEmbeddingControl reporting. Cross-origin
// iframe (no headers) => 0 count.
IN_PROC_BROWSER_TEST_F(ChromeWebPlatformSecurityMetricsBrowserTest,
CrossOriginSubframeWithoutEmbeddingControlNoHeaders) {
set_monitored_feature(
blink::mojom::WebFeature::kCrossOriginSubframeWithoutEmbeddingControl);
GURL main_document_url = https_server().GetURL("a.com", "/title1.html");
GURL sub_document_url = https_server().GetURL("b.com", "/title1.html");
EXPECT_TRUE(content::NavigateToURL(web_contents(), main_document_url));
LoadIFrame(sub_document_url);
ExpectHistogramIncreasedBy(1);
}
// Check kCrossOriginSubframeWithoutEmbeddingControl reporting. Cross-origin
// iframe (CSP frame-ancestors) => 0 count.
IN_PROC_BROWSER_TEST_F(
ChromeWebPlatformSecurityMetricsBrowserTest,
CrossOriginSubframeWithoutEmbeddingControlFrameAncestors) {
set_monitored_feature(
blink::mojom::WebFeature::kCrossOriginSubframeWithoutEmbeddingControl);
GURL main_document_url = https_server().GetURL("a.com", "/title1.html");
url::Origin main_document_origin = url::Origin::Create(main_document_url);
std::string csp_header = "Content-Security-Policy: frame-ancestors 'self' *;";
GURL sub_document_url =
https_server().GetURL("b.com", "/set-header?" + csp_header);
EXPECT_TRUE(content::NavigateToURL(web_contents(), main_document_url));
LoadIFrame(sub_document_url);
ExpectHistogramIncreasedBy(0);
}
// Check kCrossOriginSubframeWithoutEmbeddingControl reporting. Cross-origin
// iframe (blocked by CSP header) => 0 count.
IN_PROC_BROWSER_TEST_F(ChromeWebPlatformSecurityMetricsBrowserTest,
CrossOriginSubframeWithoutEmbeddingControlNoEmbedding) {
set_monitored_feature(
blink::mojom::WebFeature::kCrossOriginSubframeWithoutEmbeddingControl);
GURL main_document_url = https_server().GetURL("a.com", "/title1.html");
GURL sub_document_url =
https_server().GetURL("b.com",
"/set-header?"
"Content-Security-Policy: frame-ancestors 'self';");
EXPECT_TRUE(content::NavigateToURL(web_contents(), main_document_url));
LoadIFrame(sub_document_url);
ExpectHistogramIncreasedBy(0);
}
// Check kCrossOriginSubframeWithoutEmbeddingControl reporting. Cross-origin
// iframe (other CSP header) => 1 count.
IN_PROC_BROWSER_TEST_F(ChromeWebPlatformSecurityMetricsBrowserTest,
CrossOriginSubframeWithoutEmbeddingControlOtherCSP) {
set_monitored_feature(
blink::mojom::WebFeature::kCrossOriginSubframeWithoutEmbeddingControl);
GURL main_document_url = https_server().GetURL("a.com", "/title1.html");
GURL sub_document_url =
https_server().GetURL("b.com",
"/set-header?"
"Content-Security-Policy: script-src 'self';");
EXPECT_TRUE(content::NavigateToURL(web_contents(), main_document_url));
LoadIFrame(sub_document_url);
ExpectHistogramIncreasedBy(1);
}
// Check kEmbeddedCrossOriginFrameWithoutFrameAncestorsOrXFO feature usage.
// This should increment in cases where a cross-origin frame is embedded which
// does not assert either X-Frame-Options or CSP's frame-ancestors.
IN_PROC_BROWSER_TEST_F(ChromeWebPlatformSecurityMetricsBrowserTest,
EmbeddingOptIn) {
set_monitored_feature(
blink::mojom::WebFeature::
kEmbeddedCrossOriginFrameWithoutFrameAncestorsOrXFO);
GURL main_document_url = https_server().GetURL("a.com", "/title1.html");
struct TestCase {
const char* name;
const char* host;
const char* header;
bool expect_counter;
} cases[] = {{
"Same-origin, no XFO, no frame-ancestors",
"a.com",
nullptr,
false,
},
{
"Cross-origin, no XFO, no frame-ancestors",
"b.com",
nullptr,
true,
},
{
"Same-origin, yes XFO, no frame-ancestors",
"a.com",
"X-Frame-Options: ALLOWALL",
false,
},
{
"Cross-origin, yes XFO, no frame-ancestors",
"b.com",
"X-Frame-Options: ALLOWALL",
false,
},
{
"Same-origin, no XFO, yes frame-ancestors",
"a.com",
"Content-Security-Policy: frame-ancestors *",
false,
},
{
"Cross-origin, no XFO, yes frame-ancestors",
"b.com",
"Content-Security-Policy: frame-ancestors *",
false,
}};
for (auto test : cases) {
SCOPED_TRACE(test.name);
EXPECT_TRUE(content::NavigateToURL(web_contents(), main_document_url));
std::string path = "/set-header?";
if (test.header)
path += test.header;
GURL url = https_server().GetURL(test.host, path);
LoadIFrame(url);
ExpectHistogramIncreasedBy(test.expect_counter ? 1 : 0);
}
}
// TODO(arthursonzogni): Add basic test(s) for the WebFeatures:
// - CrossOriginOpenerPolicySameOrigin
// - CrossOriginOpenerPolicySameOriginAllowPopups
// - CrossOriginEmbedderPolicyRequireCorp
// - CoopAndCoepIsolated
//
// Added by:
// https://chromium-review.googlesource.com/c/chromium/src/+/2122140
//
// In particular, it would be interesting knowing what happens with iframes?
// Are CoopCoepOriginIsolated nested document counted as CoopAndCoepIsolated?
// Not doing it would underestimate the usage metric.