0

[Security][Coop] Browsing context switch reporting WPT

This CL adds basic reporting tests for browsing context switches.
It provides a reporting endpoint (report.py), and reusable helpers
within reporting-common.js, allowing future tests.
The helpers provided verify that expected report templates are present
on the expected endpoints, and that no extraneous reports are present.

This CL only convers the cases:
Popup opened from pages with coop :
 Same-origin with report only navigating to *
 Same-origin (without report) navigatin to *-with report
Follow ups will cover redirects (moved to follow up as it had timeout
 issues), other origins and iframe cases.

Bug: 1076456
Change-Id: I7a39d4def20692d8628ce2406569638310684f4f
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2207451
Reviewed-by: Arthur Sonzogni <arthursonzogni@chromium.org>
Commit-Queue: Pâris Meuleman <pmeuleman@chromium.org>
Auto-Submit: Pâris Meuleman <pmeuleman@chromium.org>
Cr-Commit-Position: refs/heads/master@{#776008}
This commit is contained in:
Pâris Meuleman
2020-06-08 12:57:29 +00:00
committed by Commit Bot
parent e42c6966c2
commit a8a2808358
16 changed files with 1002 additions and 7 deletions

@ -0,0 +1,82 @@
<title>Cross-Origin-Opener-Policy: a navigated popup with reporting</title>
<script src=/resources/testharness.js></script>
<script src=/resources/testharnessreport.js></script>
<script src="/common/utils.js"></script> <!-- Use token() to allow running tests in parallel -->
<script src="resources/reporting-common.js"></script>
<script>
// This test does the following:
// 1 - This document has COOP: same-origin-allow-popups; report-to="coop-report-endpoint"
// 2 - Open a popup on a same-origin page without COOP, with the coop-popup-report-endpoint
// 3 - Navigate the popup to a same-origin page with COOP, with the coop-redirect-report-endpoint
// it verifies that the reports are properly send for the browsing context switch
// during the navigation in the popup (step 3). The current document (the opener)
// endpoint should not receive any report as no switch ocurred on 2.
promise_test( async t => {
const popupName = token();
const noCoopChannelName = token();
const coopChannelName = token();
await reportingTest( (resolve) => {
const noCOOP = `resources/coop-coep.py?coop=${encodeURIComponent(`unsafe-none; report-to="${popupReportEndpoint.name}"`)}&coep=&channel=${noCoopChannelName}`;
const coop = `resources/coop-coep.py?coop=${encodeURIComponent(`same-origin; report-to="${redirectReportEndpoint.name}"`)}&coep=&channel=${coopChannelName}`;
const popup = window.open(noCOOP, popupName);
const channel = new BroadcastChannel(coopChannelName);
// Close the popup once the test is complete.
// The browsing context is closed after the navigation hence use the
// broadcast channel to trigger the closure.
t.add_cleanup(() => {
channel.postMessage("close");
});
popup.onload = t.step_func(() => {
assert_equals(popup.name.length, popupName.length, "popup name");
channel.onmessage = t.step_func(event => {
const payload = event.data;
// The name should be empty, but we're checking the length rather than a
// string comparison to "" to keep the random token out of error messages.
assert_equals(payload.name.length, 0, "Popup name after navigation");
assert_false(payload.opener, "Opener after navigation");
assert_true(popup.closed, "Window proxy closed after navigation");
resolve();
});
popup.location = coop;
});
},
popupName,
[
// Reports expected for the navigation from "noCOOP" to "coop"
{
"endpoint": popupReportEndpoint,
"report": {
"body": {
"disposition": "enforce",
"document-uri": RegExp(`coop-coep.py?.*channel=${noCoopChannelName}$`),
"effective-policy": "unsafe-none",
"navigation-uri": RegExp(`coop-coep.py?.*channel=${coopChannelName}$`),
"violation-type": "navigation-from-document"
},
"url": RegExp(`coop-coep.py?.*channel=${noCoopChannelName}$`),
"type": "coop"
}
},
{
"endpoint": redirectReportEndpoint,
"report": {
"body": {
"disposition": "enforce",
"document-uri": RegExp(`coop-coep.py?.*channel=${coopChannelName}$`),
"effective-policy": "same-origin",
"navigation-uri": RegExp(`coop-coep.py?.*channel=${noCoopChannelName}$`),
"violation-type": "navigation-to-document"
},
"url": RegExp(`coop-coep.py?.*channel=${coopChannelName}$`),
"type": "coop"
}
},
]);
}, "Open a popup to a document without COOP, then navigate it to a document with");
verifyRemainingReports();
</script>

@ -0,0 +1,2 @@
Cross-Origin-Opener-Policy: same-origin-allow-popups; report-to="coop-report-endpoint"
report-to: { "group": "coop-report-endpoint", "max_age": 10886400, "endpoints": [{ "url": "https://{{hosts[][www]}}:{{ports[https][0]}}/html/cross-origin-opener-policy/resources/report.py?endpoint=coop-report-endpoint" }] }, { "group": "coop-report-only-endpoint", "max_age": 10886400, "endpoints": [{ "url": "https://{{hosts[][www]}}:{{ports[https][0]}}/html/cross-origin-opener-policy/resources/report.py?endpoint=coop-report-only-endpoint" }]}

@ -0,0 +1,123 @@
<meta name=timeout content=long>
<title>reporting same origin with report-to</title>
<script src=/resources/testharness.js></script>
<script src=/resources/testharnessreport.js></script>
<script src="/common/get-host-info.sub.js"></script>
<script src="resources/common.js"></script>
<script src="resources/reporting-common.js"></script>
<script>
let tests = [
// popup origin, popup COOP, popup COEP, expected opener, expected reports
// Open a same-origin popup with a same-origin COOP and no COEP. Produces two
// reports (one from and one to). Both pages being same origin, the
// next/pervious document urls are available.
[
SAME_ORIGIN,
`same-origin; report-to="${popupReportEndpoint.name}"`,
"",
false,
[
{
"endpoint": reportEndpoint,
"report": {
"body": {
"disposition": "enforce",
"document-uri": `${location.href}`,
"effective-policy": "same-origin-allow-popups",
"navigation-uri": /coop-coep.py?.*channel=CHANNEL_NAME$/, // next document URL
"violation-type": "navigation-from-document"
},
"url": `${location.href}`,
"type": "coop"
}
},
{
"endpoint": popupReportEndpoint,
"report": {
"body": {
"disposition": "enforce",
"document-uri": /coop-coep.py?.*channel=CHANNEL_NAME$/,
"effective-policy": "same-origin",
"navigation-uri": `${location.href}`, // previous documnent url
"violation-type": "navigation-to-document"
},
"url": /coop-coep.py?.*channel=CHANNEL_NAME$/,
"type": "coop"
}
}
]
],
// Open a cross-origin popup with a same-origin-allow-popup COOP and noCOEP.
// Produces two reports (one from and one to). Both pages being cross origin,
// the next/pervious document urls are not available and the initial document
// url/referrer are used instead.
[
CROSS_ORIGIN,
`same-origin-allow-popups; report-to="${popupReportEndpoint.name}"`,
"require-corp",
false,
[
{
"endpoint": reportEndpoint,
"report": {
"body": {
"disposition": "enforce",
"document-uri": `${location.href}`,
"effective-policy": "same-origin-allow-popups",
"navigation-uri": /coop-coep.py?.*channel=CHANNEL_NAME$/, // initial navigation URL
"violation-type": "navigation-from-document"
},
"url": `${location.href}`,
"type": "coop"
}
},
{
"endpoint": popupReportEndpoint,
"report": {
"body": {
"disposition": "enforce",
"document-uri": /coop-coep.py?.*channel=CHANNEL_NAME$/,
"effective-policy": "same-origin-allow-popups",
"navigation-uri": `${location.origin}/`, // referrer (origin, as dictated by the referrer policy)
"violation-type": "navigation-to-document"
},
"url": /coop-coep.py?.*channel=CHANNEL_NAME$/,
"type": "coop"
}
}
]
],
// Open a cross-origin popup with a same-origin COOP and COEP, and no reporting.
// Produces one navigation-from-report for this document (the opener). The
// pages being cross origin, the next/pervious document urls are not available
// and the initial document url/referrer are used instead.
[
CROSS_ORIGIN,
`same-origin`,
"require-corp",
false,
[
{
"endpoint": reportEndpoint,
"report": {
"body": {
"disposition": "enforce",
"document-uri": `${location.href}`,
"effective-policy": "same-origin-allow-popups",
"navigation-uri": /coop-coep.py?.*channel=CHANNEL_NAME$/, // initial navigation URL
"violation-type": "navigation-from-document"
},
"url": `${location.href}`,
"type": "coop"
}
}
]
],
];
runCoopReportingTest(document.title, tests);
</script>

@ -0,0 +1,3 @@
report-to: { "group": "coop-report-endpoint", "max_age": 10886400, "endpoints": [{ "url": "https://{{hosts[][www]}}:{{ports[https][0]}}/html/cross-origin-opener-policy/resources/report.py?endpoint=coop-report-endpoint" }] }, { "group": "coop-report-only-endpoint", "max_age": 10886400, "endpoints": [{ "url": "https://{{hosts[][www]}}:{{ports[https][0]}}/html/cross-origin-opener-policy/resources/report.py?endpoint=coop-report-only-endpoint" }]}
Cross-Origin-Opener-Policy: same-origin-allow-popups; report-to="coop-report-endpoint"
Referrer-Policy: origin

@ -0,0 +1,108 @@
<meta name=timeout content=long>
<title>reporting same origin with report-to</title>
<script src=/resources/testharness.js></script>
<script src=/resources/testharnessreport.js></script>
<script src="/common/get-host-info.sub.js"></script>
<script src="resources/common.js"></script>
<script src="resources/reporting-common.js"></script>
<script>
let tests = [
// popup origin, popup COOP, popup COEP, expected opener, expected reports
// Open and navigate a popup to a same-origin page with the same COOP-COEP
// settings: no browsing context group switch hence no report expected.
[
SAME_ORIGIN,
`same-origin; report-to="${popupReportEndpoint.name}"`,
"require-corp",
true,
[]
],
// Open a same-origin popup with a same-origin COOP but no COEP. Produces two
// reports (one from and one to). The from report has an effective-policy of
// same-origin-plus-coep, both pages being same origin, the entire
// next/pervious document urls are available.
[
SAME_ORIGIN,
`same-origin; report-to="${popupReportEndpoint.name}"`,
"",
false,
[
{
"endpoint": reportEndpoint,
"report": {
"body": {
"disposition": "enforce",
"document-uri": `${location.href}`,
"effective-policy": "same-origin-plus-coep",
"navigation-uri": /coop-coep.py?.*channel=CHANNEL_NAME$/, // next destination url
"violation-type": "navigation-from-document"
},
"url": `${location.href}`,
"type": "coop"
}
},
{
"endpoint": popupReportEndpoint,
"report": {
"body": {
"disposition": "enforce",
"document-uri": /coop-coep.py?.*channel=CHANNEL_NAME$/,
"effective-policy": "same-origin",
"navigation-uri": `${location.href}`, // previous document url
"violation-type": "navigation-to-document"
},
"url": /coop-coep.py?.*channel=CHANNEL_NAME$/,
"type": "coop"
}
}
]
],
// Open a cross-origin popup with a same-origin COOP and COEP. Produces two
// reports (one from and one to). The from report has an effective-policy of
// same-origin-plus-coep, both pages being cross origin, the next/pervious
// document urls are not available and the initial document url/referrer are
// used instead.
[
CROSS_ORIGIN,
`same-origin; report-to="${popupReportEndpoint.name}"`,
"require-corp",
false,
[
{
"endpoint": reportEndpoint,
"report": {
"body": {
"disposition": "enforce",
"document-uri": `${location.href}`,
"effective-policy": "same-origin-plus-coep",
"navigation-uri": /coop-coep.py?.*channel=CHANNEL_NAME$/, // initial navigation url
"violation-type": "navigation-from-document"
},
"url": `${location.href}`,
"type": "coop"
}
},
{
"endpoint": popupReportEndpoint,
"report": {
"body": {
"disposition": "enforce",
"document-uri": /coop-coep.py?.*channel=CHANNEL_NAME$/,
"effective-policy": "same-origin-plus-coep",
"navigation-uri": `${location.origin}/`, // referrer (origin, as dictated by the referrer policy)
"violation-type": "navigation-to-document"
},
"url": /coop-coep.py?.*channel=CHANNEL_NAME$/,
"type": "coop"
}
}
]
],
];
runCoopReportingTest(document.title, tests);
</script>

@ -0,0 +1,4 @@
report-to: { "group": "coop-report-endpoint", "max_age": 10886400, "endpoints": [{ "url": "https://{{hosts[][www]}}:{{ports[https][0]}}/html/cross-origin-opener-policy/resources/report.py?endpoint=coop-report-endpoint" }] }, { "group": "coop-report-only-endpoint", "max_age": 10886400, "endpoints": [{ "url": "https://{{hosts[][www]}}:{{ports[https][0]}}/html/cross-origin-opener-policy/resources/report.py?endpoint=coop-report-only-endpoint" }]}
Cross-Origin-Opener-Policy: same-origin; report-to="coop-report-endpoint"
Cross-Origin-Embedder-Policy: require-corp
Referrer-Policy: origin

@ -0,0 +1,213 @@
<meta name=timeout content=long>
<title>reporting same origin with report-to</title>
<script src=/resources/testharness.js></script>
<script src=/resources/testharnessreport.js></script>
<script src="/common/get-host-info.sub.js"></script>
<script src="resources/common.js"></script>
<script src="resources/reporting-common.js"></script>
<script>
let tests = [
// popup origin, popup COOP, popup COEP, expected opener, expected reports
// Open a popup on a same-origin page, with a compatible COOP.
// This is a sanity check that no report are produced.
[
SAME_ORIGIN,
`same-origin; report-to="${popupReportEndpoint.name}"`,
"",
true,
[]
],
// Open a cross-origin popup with a same-origin COOP. Produces two
// reports (one from and one to). The from report has an effective-policy of
// same-origin (corresponding to the current document), both pages being
// cross origin, the next/pervious document urls are not available and the
// initial document url/referrer are used instead.
[
CROSS_ORIGIN,
`same-origin; report-to="${popupReportEndpoint.name}"`,
"",
false,
[
{
"endpoint": reportEndpoint,
"report": {
"body": {
"disposition": "enforce",
"document-uri": `${location.href}`,
"effective-policy": "same-origin",
"navigation-uri": /coop-coep.py?.*channel=CHANNEL_NAME$/, // initial navigation URL (equal to the next document url in that case)
"violation-type": "navigation-from-document"
},
"url": `${location.href}`,
"type": "coop"
}
},
{
"endpoint": popupReportEndpoint,
"report": {
"body": {
"disposition": "enforce",
"document-uri": /coop-coep.py?.*channel=CHANNEL_NAME$/,
"effective-policy": "same-origin",
"navigation-uri": '', // referrer (empty due to the Referrer Policy)
"violation-type": "navigation-to-document"
},
"url": /coop-coep.py?.*channel=CHANNEL_NAME$/,
"type": "coop"
}
}
]
],
// Open a same-origin popup with a unsafe-none COOP and no COEP. COOP switches
// the browsing context group and hence produces two reports (one from and one
// to). This test verifies that unsafe-none properly sends report.
[
SAME_ORIGIN,
`unsafe-none; report-to="${popupReportEndpoint.name}"`,
"",
false,
[
{
"endpoint": reportEndpoint,
"report": {
"body": {
"disposition": "enforce",
"document-uri": `${location.href}`,
"effective-policy": "same-origin",
"navigation-uri": /coop-coep.py?.*channel=CHANNEL_NAME$/,
"violation-type": "navigation-from-document"
},
"url": `${location.href}`,
"type": "coop"
}
},
{
"endpoint": popupReportEndpoint,
"report": {
"body": {
"disposition": "enforce",
"document-uri": /coop-coep.py?.*channel=CHANNEL_NAME$/,
"effective-policy": "unsafe-none",
"navigation-uri": `${location.href}`,
"violation-type": "navigation-to-document"
},
"url": /coop-coep.py?.*channel=CHANNEL_NAME$/,
"type": "coop"
}
}
]
],
// Open a same-origin popup with a same-origin COOP and COEP. The difference
// of COEP values leads to the browsing context group switch and produces two
// reports. This verifies that the navigation-to-document report has an
// effective-policy of same-origin-plus-coep.
[
SAME_ORIGIN,
`same-origin; report-to="${popupReportEndpoint.name}"`,
"require-corp",
false,
[
{
"endpoint": reportEndpoint,
"report": {
"body": {
"disposition": "enforce",
"document-uri": `${location.href}`,
"effective-policy": "same-origin",
"navigation-uri": /coop-coep.py?.*channel=CHANNEL_NAME$/, // Next document URL
"violation-type": "navigation-from-document"
},
"url": `${location.href}`,
"type": "coop"
}
},
{
"endpoint": popupReportEndpoint,
"report": {
"body": {
"disposition": "enforce",
"document-uri": /coop-coep.py?.*channel=CHANNEL_NAME$/,
"effective-policy": "same-origin-plus-coep",
"navigation-uri": `${location.href}`, // Previous document URL
"violation-type": "navigation-to-document"
},
"url": /coop-coep.py?.*channel=CHANNEL_NAME$/,
"type": "coop"
}
}
]
],
// Open a cross-origin popup with no COOP (but reporting) and no COEP.
// Produces two reports. The pages being cross origin, the next/pervious
// document urls are not available and the initial document url/referrer are
// used instead.
[
CROSS_ORIGIN,
`unsafe-none; report-to="${popupReportEndpoint.name}"`,
"",
false,
[
{
"endpoint": reportEndpoint,
"report": {
"body": {
"disposition": "enforce",
"document-uri": `${location.href}`,
"effective-policy": "same-origin",
"navigation-uri": /coop-coep.py?.*channel=CHANNEL_NAME$/, // initial navigation URL
"violation-type": "navigation-from-document"
},
"url": `${location.href}`,
"type": "coop"
}
},
{
"endpoint": popupReportEndpoint,
"report": {
"body": {
"disposition": "enforce",
"document-uri": /coop-coep.py?.*channel=CHANNEL_NAME$/,
"effective-policy": "unsafe-none",
"navigation-uri": '', // referrer, as per the no-referrer policy.
"violation-type": "navigation-to-document"
},
"url": /coop-coep.py?.*channel=CHANNEL_NAME$/,
"type": "coop"
}
}
]
],
// Open a same-origin popup with no COOP (without reporting) and no COEP.
// Produces one report to this page (opener) endpoint.
// This verifies that the navigated-to-document's COOP report values do not
// impact the navigated-from-document's COOP.
[
SAME_ORIGIN,
"unsafe-none",
"",
false,
[
{
"endpoint": reportEndpoint,
"report": {
"body": {
"disposition": "enforce",
"document-uri": `${location.href}`,
"effective-policy": "same-origin",
"navigation-uri": /coop-coep.py?.*channel=CHANNEL_NAME$/,
"violation-type": "navigation-from-document"
},
"url": `${location.href}`,
"type": "coop"
}
}
]
]
];
runCoopReportingTest(document.title, tests);
</script>

@ -0,0 +1,3 @@
report-to: { "group": "coop-report-endpoint", "max_age": 10886400, "endpoints": [{ "url": "https://{{hosts[][www]}}:{{ports[https][0]}}/html/cross-origin-opener-policy/resources/report.py?endpoint=coop-report-endpoint" }] }, { "group": "coop-report-only-endpoint", "max_age": 10886400, "endpoints": [{ "url": "https://{{hosts[][www]}}:{{ports[https][0]}}/html/cross-origin-opener-policy/resources/report.py?endpoint=coop-report-only-endpoint" }]}
Cross-Origin-Opener-Policy: same-origin; report-to="coop-report-endpoint"
Referrer-Policy: no-referrer

@ -0,0 +1,96 @@
<meta name=timeout content=long>
<title>reporting same origin</title>
<script src=/resources/testharness.js></script>
<script src=/resources/testharnessreport.js></script>
<script src="/common/get-host-info.sub.js"></script>
<script src="resources/common.js"></script>
<script src="resources/reporting-common.js"></script>
<script>
let tests = [
// popup origin, popup COOP, popup COEP, expected opener, expected reports
// Open a cross-origin popup with a same-origin COOP and no COEP. COOP
// switches the browsing context group and hence produces one report.
// This test verifies that the navigated to document properly sends a
// navigation-to report. The navigation-uri is the referrer.
[
CROSS_ORIGIN,
`same-origin; report-to="${popupReportEndpoint.name}"`,
"",
false,
[
{
"endpoint": popupReportEndpoint,
"report": {
"body": {
"disposition": "enforce",
"document-uri": /coop-coep.py?.*channel=CHANNEL_NAME$/,
"effective-policy": "same-origin",
"navigation-uri": `${location.href}`, // referrer
"violation-type": "navigation-to-document"
},
"url": /coop-coep.py?.*channel=CHANNEL_NAME$/,
"type": "coop"
}
}
]
],
// Open a same-origin popup with a unsafe-none COOP and no COEP. COOP switches
// the browsing context group and hence produces one report.
// This test verifies that having different policies on same origin documents
// still properly produces report to the navigated-to-document.
[
SAME_ORIGIN,
`unsafe-none; report-to="${popupReportEndpoint.name}"`,
"",
false,
[
{
"endpoint": popupReportEndpoint,
"report": {
"body": {
"disposition": "enforce",
"document-uri": /coop-coep.py?.*channel=CHANNEL_NAME$/,
"effective-policy": "unsafe-none",
"navigation-uri": `${location.href}`,
"violation-type": "navigation-to-document"
},
"url": /coop-coep.py?.*channel=CHANNEL_NAME$/,
"type": "coop"
}
}
]
],
// Open a cross-origin popup with a unsafe-none COOP (with reporting) and no
// COEP. COOP switches the browsing context group and hence produces one
// reports to the unsafe-none document. This test verifies that unsafe-none
// properly sends report in that configuration.
[
CROSS_ORIGIN,
`unsafe-none; report-to="${popupReportEndpoint.name}"`,
"",
false,
[
{
"endpoint": popupReportEndpoint,
"report": {
"body": {
"disposition": "enforce",
"document-uri": /coop-coep.py?.*channel=CHANNEL_NAME$/,
"effective-policy": "unsafe-none",
"navigation-uri": `${location.href}`, // referrer
"violation-type": "navigation-to-document"
},
"url": /coop-coep.py?.*channel=CHANNEL_NAME$/,
"type": "coop"
}
}
]
]
];
runCoopReportingTest(document.title, tests);
</script>

@ -0,0 +1,123 @@
<meta name=timeout content=long>
<title>reporting same origin with report-to</title>
<script src=/resources/testharness.js></script>
<script src=/resources/testharnessreport.js></script>
<script src="/common/get-host-info.sub.js"></script>
<script src="resources/common.js"></script>
<script src="resources/reporting-common.js"></script>
<script>
let tests = [
// popup origin, popup COOP, popup COEP, expected opener, expected reports
// Open a same-origin popup with a same-origin COOP with reporting and no COEP.
// COOP switches the browsing context group and hence produces two reports
// (one from and one to). This test verifies that unsafe-none (from the opener)
// properly sends a report.
[
SAME_ORIGIN,
`same-origin; report-to="${popupReportEndpoint.name}"`,
"",
false,
[
{
"endpoint": reportEndpoint,
"report": {
"body": {
"disposition": "enforce",
"document-uri": `${location.href}`,
"effective-policy": "unsafe-none",
"navigation-uri": /coop-coep.py?.*channel=CHANNEL_NAME$/, // next document URL
"violation-type": "navigation-from-document"
},
"url": `${location.href}`,
"type": "coop"
}
},
{
"endpoint": popupReportEndpoint,
"report": {
"body": {
"disposition": "enforce",
"document-uri": /coop-coep.py?.*channel=CHANNEL_NAME$/,
"effective-policy": "same-origin",
"navigation-uri": `${location.href}`, // previous document url
"violation-type": "navigation-to-document"
},
"url": /coop-coep.py?.*channel=CHANNEL_NAME$/,
"type": "coop"
}
}
]
],
// Open a same-origin popup with a same-origin COOP (no reporting)and no COEP.
// COOP switches the browsing context group and hence produces one report for
// the navigated from document (this page, the opener). This test differs with
// the previous one as it assert that the navigated to document's COOP reporting
// values do not interfere.
[
SAME_ORIGIN,
`same-origin`,
"",
false,
[
{
"endpoint": reportEndpoint,
"report": {
"body": {
"disposition": "enforce",
"document-uri": `${location.href}`,
"effective-policy": "unsafe-none",
"navigation-uri": /coop-coep.py?.*channel=CHANNEL_NAME$/, // next document URL
"violation-type": "navigation-from-document"
},
"url": `${location.href}`,
"type": "coop"
}
}
]
],
// Open a cross-origin popup with a same-origin COOP and no COEP. COOP switches
// the browsing context group and hence produces two reports.
[
CROSS_ORIGIN,
`same-origin; report-to="${popupReportEndpoint.name}"`,
"",
false,
[
{
"endpoint": reportEndpoint,
"report": {
"body": {
"disposition": "enforce",
"document-uri": `${location.href}`,
"effective-policy": "unsafe-none",
"navigation-uri": /coop-coep.py?.*channel=CHANNEL_NAME$/, // initial navigation url
"violation-type": "navigation-from-document"
},
"url": `${location.href}`,
"type": "coop"
}
},
{
"endpoint": popupReportEndpoint,
"report": {
"body": {
"disposition": "enforce",
"document-uri": /coop-coep.py?.*channel=CHANNEL_NAME$/,
"effective-policy": "same-origin",
"navigation-uri": `${location.href}`, // referrer
"violation-type": "navigation-to-document"
},
"url": /coop-coep.py?.*channel=CHANNEL_NAME$/,
"type": "coop"
}
}
]
]
];
runCoopReportingTest(document.title, tests);
</script>

@ -0,0 +1,2 @@
report-to: { "group": "coop-report-endpoint", "max_age": 10886400, "endpoints": [{ "url": "https://{{hosts[][www]}}:{{ports[https][0]}}/html/cross-origin-opener-policy/resources/report.py?endpoint=coop-report-endpoint" }] }, { "group": "coop-report-only-endpoint", "max_age": 10886400, "endpoints": [{ "url": "https://{{hosts[][www]}}:{{ports[https][0]}}/html/cross-origin-opener-policy/resources/report.py?endpoint=coop-report-only-endpoint" }]}
Cross-Origin-Opener-Policy: unsafe-none; report-to="coop-report-endpoint"

@ -29,11 +29,14 @@ function validate_results(callback, test, w, channelName, hasOpener, openerDOMAc
}
}
function url_test(t, url, channelName, hasOpener, openerDOMAccess) {
function url_test(t, url, channelName, hasOpener, openerDOMAccess, callback) {
if (callback === undefined) {
callback = () => { t.done(); };
}
const bc = new BroadcastChannel(channelName);
bc.onmessage = t.step_func(event => {
const payload = event.data;
validate_results(() => { t.done(); }, t, w, channelName, hasOpener, openerDOMAccess, payload);
validate_results(callback, t, w, channelName, hasOpener, openerDOMAccess, payload);
});
const w = window.open(url, channelName);
@ -46,12 +49,12 @@ function url_test(t, url, channelName, hasOpener, openerDOMAccess) {
});
}
function coop_coep_test(t, host, coop, coep, channelName, hasOpener, openerDOMAccess) {
url_test(t, `${host.origin}/html/cross-origin-opener-policy/resources/coop-coep.py?coop=${encodeURIComponent(coop)}&coep=${coep}&channel=${channelName}`, channelName, hasOpener, openerDOMAccess);
function coop_coep_test(t, host, coop, coep, channelName, hasOpener, openerDOMAccess, callback) {
url_test(t, `${host.origin}/html/cross-origin-opener-policy/resources/coop-coep.py?coop=${encodeURIComponent(coop)}&coep=${coep}&channel=${channelName}`, channelName, hasOpener, openerDOMAccess, callback);
}
function coop_test(t, host, coop, channelName, hasOpener) {
coop_coep_test(t, host, coop, "", channelName, hasOpener);
function coop_test(t, host, coop, channelName, hasOpener, callback) {
coop_coep_test(t, host, coop, "", channelName, hasOpener, undefined /* openerDOMAccess */, callback);
}
function run_coop_tests(documentCOOPValueTitle, testArray) {
@ -59,7 +62,7 @@ function run_coop_tests(documentCOOPValueTitle, testArray) {
async_test(t => {
coop_test(t, test[0], test[1],
`${documentCOOPValueTitle}_to_${test[0].name}_${test[1].replace(/ /g,"-")}`,
test[2]);
test[2], () => { t.done(); });
}, `${documentCOOPValueTitle} document opening popup to ${test[0].origin} with COOP: "${test[1]}"`);
}
}

@ -1,13 +1,42 @@
def get_reporting_group(host, endpoint):
return '\
{{\
"group": "{endpoint}",\
"max_age": 10886400,\
"endpoints":\
[{{\
"url": "https://{host}/html/cross-origin-opener-policy/resources/report.py?endpoint={endpoint}"\
}}]\
}}'.format(host=host, endpoint=endpoint)
def main(request, response):
coop = request.GET.first("coop")
coopReportOnly = request.GET.first("coop-report-only") if "coop-report-only" in request.GET else ""
coep = request.GET.first("coep")
coepReportOnly = request.GET.first("coep-report-only") if "coep-report-only" in request.GET else ""
redirect = request.GET.first("redirect", None)
if coop != "":
response.headers.set("Cross-Origin-Opener-Policy", coop)
if coop != "":
response.headers.set("Cross-Origin-Opener-Policy-Report-Only", coopReportOnly)
if coep != "":
response.headers.set("Cross-Origin-Embedder-Policy", coep)
if coep != "":
response.headers.set("Cross-Origin-Embedder-Policy-Report-Only", coepReportOnly)
if 'cache' in request.GET:
response.headers.set('Cache-Control', 'max-age=3600')
host = request.url_parts[1]
# add all possible reporting endpoints to the report-to header
# Note that this also returns the coop-report-endpoint, as it may override
# the test's endpoints if same-origin.
response.headers.set('report-to',
get_reporting_group(host, "coop-report-endpoint") + ',' +
get_reporting_group(host, "coop-report-only-endpoint") + ',' +
get_reporting_group(host, "coop-redirect-report-endpoint") + ',' +
get_reporting_group(host, "coop-redirect-report-only-endpoint") + ',' +
get_reporting_group(host, "coop-popup-report-endpoint") + ',' +
get_reporting_group(host, "coop-popup-report-only-endpoint") )
if redirect != None:
response.status = 302

@ -0,0 +1,26 @@
import json, uuid
def main(request, response):
response.headers.set('Cache-Control', 'no-cache, no-store, must-revalidate');
key = 0;
if 'endpoint' in request.GET:
key = uuid.uuid5(uuid.NAMESPACE_OID, request.GET['endpoint']).get_urn()
if key == 0:
response.status = 400
return 'invalid endpoint'
if request.method == 'POST':
reports = request.server.stash.take(key) or []
for report in json.loads(request.body):
reports.append(report)
request.server.stash.put(key, reports)
return "done"
if request.method == 'GET':
response.headers.set('Content-Type', 'application/json')
return json.dumps(request.server.stash.take(key) or [])
response.status = 400
return 'invalid method'

@ -0,0 +1,177 @@
// Allows RegExps to be pretty printed when printing unmatched expected reports.
Object.defineProperty(RegExp.prototype, "toJSON", {
value: RegExp.prototype.toString
});
function wait(ms) {
return new Promise(resolve => step_timeout(resolve, ms));
}
async function pollReports(endpoint) {
const res = await fetch(
`resources/report.py?endpoint=${endpoint.name}`,
{cache: 'no-store'});
if (res.status !== 200) {
return;
}
for (const report of await res.json()) {
endpoint.reports.push(report);
}
}
// Recursively check that all members of expectedReport are present or matched
// in report.
// Report may have members not explicitly expected by expectedReport.
function isObjectAsExpected(report, expectedReport) {
if (( report === undefined || report === null
|| expectedReport === undefined || expectedReport === null )
&& report !== expectedReport ) {
return false;
}
if (expectedReport instanceof RegExp && typeof report === "string") {
return expectedReport.test(report);
}
// Perform this check now, as RegExp and strings above have different typeof.
if (typeof report !== typeof expectedReport)
return false;
if (typeof expectedReport === 'object') {
return Object.keys(expectedReport).every(key => {
return isObjectAsExpected(report[key], expectedReport[key]);
});
}
return report == expectedReport;
}
async function checkForExpectedReport(expectedReport) {
return new Promise( async (resolve, reject) => {
const polls = 30;
const waitTime = 100;
for (var i=0; i < polls; ++i) {
pollReports(expectedReport.endpoint);
for (var j=0; j<expectedReport.endpoint.reports.length; ++j){
if (isObjectAsExpected(expectedReport.endpoint.reports[j],
expectedReport.report)){
expectedReport.endpoint.reports.splice(j,1);
resolve();
}
};
await wait(waitTime);
}
reject("No report matched the expected report for endpoint: "
+ expectedReport.endpoint.name
+ ", expected report: " + JSON.stringify(expectedReport.report)
+ ", within available reports: "
+ JSON.stringify(expectedReport.endpoint.reports)
);
});
}
function replaceFromRegexOrString(str, match, value) {
if (str instanceof RegExp) {
return RegExp(str.source.replace(match, value));
}
return str.replace(match, value);
}
// Replace generated values in regexes and strings of an expected report:
// CHANNEL_NAME: the channel name is generated from the test parameters.
function replaceValuesInExpectedReport(expectedReport, channelName) {
if (expectedReport.report.body !== undefined) {
if (expectedReport.report.body["document-uri"] !== undefined) {
expectedReport.report.body["document-uri"] = replaceFromRegexOrString(
expectedReport.report.body["document-uri"], "CHANNEL_NAME",
channelName);
}
if (expectedReport.report.body["navigation-uri"] !== undefined) {
expectedReport.report.body["navigation-uri"] = replaceFromRegexOrString(
expectedReport.report.body["navigation-uri"], "CHANNEL_NAME",
channelName);
}
}
if (expectedReport.report.url !== undefined) {
expectedReport.report.url = replaceFromRegexOrString(
expectedReport.report.url, "CHANNEL_NAME", channelName);
}
return expectedReport;
}
// Run a test (such as coop_coep_test from ./common.js) then check that all
// expected reports are present.
async function reportingTest(testFunction, channelName, expectedReports) {
await new Promise( async resolve => {
testFunction(resolve);
});
expectedReports = Array.from(
expectedReports,
report => replaceValuesInExpectedReport(report, channelName) );
await Promise.all(Array.from(expectedReports, checkForExpectedReport));
}
function coopCoepReportingTest(testName, host, coop, coep, hasOpener,
expectedReports){
const channelName = `${testName.replace(/[ ;"=]/g,"-")}_to_${host.name}_${coop.replace(/[ ;"=]/g,"-")}_${coep}`;
promise_test(async t => {
await reportingTest( (resolve) => {
coop_coep_test(t, host, coop, coep, channelName,
hasOpener, undefined /* openerDOMAccess */, resolve);
}, channelName, expectedReports);
}, `coop reporting test ${channelName}`);
}
// Run an array of reporting tests then verify there's no reports that were not
// expected.
// Tests' elements contain: host, coop, coep, hasOpener, expectedReports.
// See isObjectAsExpected for explanations regarding the matching behavior.
function runCoopReportingTest(testName, tests){
tests.forEach( test => {
coopCoepReportingTest(testName, ...test);
});
verifyRemainingReports();
}
const reportEndpoint = {
name: "coop-report-endpoint",
reports: []
}
const reportOnlyEndpoint = {
name: "coop-report-only-endpoint",
reports: []
}
const popupReportEndpoint = {
name: "coop-popup-report-endpoint",
reports: []
}
const popupReportOnlyEndpoint = {
name: "coop-popup-report-only-endpoint",
reports: []
}
const redirectReportEndpoint = {
name: "coop-redirect-report-endpoint",
reports: []
}
const redirectReportOnlyEndpoint = {
name: "coop-redirect-report-only-endpoint",
reports: []
}
const reportEndpoints = [
reportEndpoint,
reportOnlyEndpoint,
popupReportEndpoint,
popupReportOnlyEndpoint,
redirectReportEndpoint,
redirectReportOnlyEndpoint
]
function verifyRemainingReports() {
promise_test( async t => {
await Promise.all(Array.from(reportEndpoints, (endpoint) => {
return new Promise( async (resolve, reject) => {
await pollReports(endpoint);
if (endpoint.reports.length != 0)
reject( `${endpoint.name} not empty`);
resolve();
});
}));
}, "verify remaining reports");
}