forked from web-platform-tests/wpt
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[Credentialless] Add credentialless reporting
This CL adds support for navigational COEP:credentialless requests to cross-origin-resource-policy reporting. When a navigational response is blocked, COEP:credentialless report will be sent. Bug: 1200849 Change-Id: I3ab8235190597b6292f99afe5daffd059435d369 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2867085 Reviewed-by: Yifan Luo <[email protected]> Reviewed-by: Arthur Sonzogni <[email protected]> Reviewed-by: Kinuko Yasuda <[email protected]> Commit-Queue: Yifan Luo <[email protected]> Cr-Commit-Position: refs/heads/master@{#888454}
- Loading branch information
Showing
5 changed files
with
335 additions
and
10 deletions.
There are no files selected for viewing
136 changes: 136 additions & 0 deletions
136
html/cross-origin-embedder-policy/credentialless/reporting-navigation.tentative.https.html
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,136 @@ | ||
<!doctype html> | ||
<html> | ||
<meta name="timeout" content="long"> | ||
<body> | ||
<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> | ||
const {ORIGIN, REMOTE_ORIGIN} = get_host_info(); | ||
const COEP = '|header(cross-origin-embedder-policy,credentialless)'; | ||
const COEP_RO = | ||
'|header(cross-origin-embedder-policy-report-only,credentialless)'; | ||
const CORP_CROSS_ORIGIN = | ||
'|header(cross-origin-resource-policy,cross-origin)'; | ||
const FRAME_URL = `${ORIGIN}/common/blank.html?pipe=`; | ||
const REMOTE_FRAME_URL = `${REMOTE_ORIGIN}/common/blank.html?pipe=`; | ||
|
||
function checkCorpReport(report, contextUrl, blockedUrl, disposition) { | ||
assert_equals(report.type, 'coep'); | ||
assert_equals(report.url, contextUrl); | ||
assert_equals(report.body.type, 'corp'); | ||
assert_equals(report.body.blockedURL, blockedUrl); | ||
assert_equals(report.body.disposition, disposition); | ||
assert_equals(report.body.destination, 'iframe'); | ||
} | ||
|
||
function checkCoepMismatchReport(report, contextUrl, blockedUrl, disposition) { | ||
assert_equals(report.type, 'coep'); | ||
assert_equals(report.url, contextUrl); | ||
assert_equals(report.body.type, 'navigation'); | ||
assert_equals(report.body.blockedURL, blockedUrl); | ||
assert_equals(report.body.disposition, disposition); | ||
} | ||
|
||
function loadFrame(document, url) { | ||
return new Promise((resolve, reject) => { | ||
const frame = document.createElement('iframe'); | ||
frame.src = url; | ||
frame.onload = () => resolve(frame); | ||
frame.onerror = reject; | ||
document.body.appendChild(frame); | ||
}); | ||
} | ||
|
||
// |parentSuffix| is a suffix for the parent frame URL. | ||
// |targetUrl| is a URL for the target frame. | ||
async function loadFrames(test, parentSuffix, targetUrl) { | ||
const frame = await loadFrame(document, FRAME_URL + parentSuffix); | ||
test.add_cleanup(() => frame.remove()); | ||
// Here we don't need "await". This loading may or may not succeed, and | ||
// we're not interested in the result. | ||
loadFrame(frame.contentDocument, targetUrl); | ||
|
||
return frame; | ||
} | ||
|
||
async function observeReports(global) { | ||
const reports = []; | ||
const observer = new global.ReportingObserver((rs) => { | ||
for (const r of rs) { | ||
reports.push(r.toJSON()); | ||
} | ||
}); | ||
observer.observe(); | ||
|
||
// Wait 1000ms for reports to settle. | ||
await new Promise(r => step_timeout(r, 1000)); | ||
return reports; | ||
} | ||
|
||
function desc(headers) { | ||
return headers === '' ? '(none)' : headers; | ||
} | ||
|
||
// CASES is a list of test case. Each test case consists of: | ||
// parent_headers: the suffix of the URL of the parent frame. | ||
// target_headers: the suffix of the URL of the target frame. | ||
// expected_reports: one of: | ||
// 'CORP': CORP violation | ||
// 'CORP-RO': CORP violation (report only) | ||
// 'NAV': COEP mismatch between the frames. | ||
// 'NAV-RO': COEP mismatch between the frames (report only). | ||
const reportingTest = function( | ||
parent_headers, target_headers, expected_reports) { | ||
// These tests are very slow, so they must be run in parallel using | ||
// async_test. | ||
promise_test_parallel(async t => { | ||
const targetUrl = REMOTE_FRAME_URL + target_headers; | ||
const parent = await loadFrames(t, parent_headers, targetUrl); | ||
const contextUrl = parent.src ? parent.src : 'about:blank'; | ||
const reports = await observeReports(parent.contentWindow); | ||
assert_equals(reports.length, expected_reports.length); | ||
for (let i = 0; i < reports.length; i += 1) { | ||
const report = reports[i]; | ||
switch (expected_reports[i]) { | ||
case 'CORP': | ||
checkCorpReport(report, contextUrl, targetUrl, 'enforce'); | ||
break; | ||
case 'CORP-RO': | ||
checkCorpReport(report, contextUrl, targetUrl, 'reporting'); | ||
break; | ||
case 'NAV': | ||
checkCoepMismatchReport(report, contextUrl, targetUrl, 'enforce'); | ||
break; | ||
case 'NAV-RO': | ||
checkCoepMismatchReport(report, contextUrl, targetUrl, 'reporting'); | ||
break; | ||
default: | ||
assert_unreached( | ||
'Unexpected report exception: ' + expected_reports[i]); | ||
} | ||
} | ||
}, `parent: ${desc(parent_headers)}, target: ${desc(target_headers)}, `); | ||
} | ||
|
||
reportingTest('', '', []); | ||
reportingTest('', COEP, []); | ||
reportingTest(COEP, COEP, ['CORP']); | ||
reportingTest(COEP, '', ['CORP']); | ||
|
||
reportingTest('', CORP_CROSS_ORIGIN, []); | ||
reportingTest(COEP, CORP_CROSS_ORIGIN, ['NAV']); | ||
|
||
reportingTest('', COEP + CORP_CROSS_ORIGIN, []); | ||
reportingTest(COEP, COEP + CORP_CROSS_ORIGIN, []); | ||
|
||
reportingTest(COEP_RO, COEP, ['CORP-RO']); | ||
reportingTest(COEP_RO, '', ['CORP-RO', 'NAV-RO']); | ||
reportingTest(COEP_RO, CORP_CROSS_ORIGIN, ['NAV-RO']); | ||
reportingTest(COEP_RO, COEP + CORP_CROSS_ORIGIN, []); | ||
|
||
reportingTest(COEP, COEP_RO + CORP_CROSS_ORIGIN, ['NAV']); | ||
|
||
</script> | ||
</body></html> |
186 changes: 186 additions & 0 deletions
186
...oss-origin-embedder-policy/credentialless/reporting-subresource-corp.tentative.https.html
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,186 @@ | ||
<!doctype html> | ||
<html> | ||
<meta name="timeout" content="long"> | ||
<body> | ||
<script src="/resources/testharness.js"></script> | ||
<script src="/resources/testharnessreport.js"></script> | ||
<script src="/common/utils.js"></script> | ||
<script src="/common/get-host-info.sub.js"></script> | ||
<script src="/service-workers/service-worker/resources/test-helpers.sub.js"></script> | ||
<script> | ||
const {ORIGIN, REMOTE_ORIGIN} = get_host_info(); | ||
const BASE = "/html/cross-origin-embedder-policy/resources"; | ||
const FRAME_URL = `${ORIGIN}/common/blank.html` + | ||
'?pipe=header(cross-origin-embedder-policy,credentialless)' + | ||
`|header(cross-origin-embedder-policy-report-only,credentialless)`; | ||
const WORKER_URL = `${ORIGIN}${BASE}/reporting-worker.js` + | ||
'?pipe=header(cross-origin-embedder-policy,credentialless)' + | ||
`|header(cross-origin-embedder-policy-report-only,credentialless)`; | ||
const REPORTING_FRAME_URL = `${ORIGIN}${BASE}/reporting-empty-frame.html` + | ||
'?pipe=header(cross-origin-embedder-policy,credentialless)' + | ||
`|header(cross-origin-embedder-policy-report-only,credentialless)`; | ||
|
||
function wait(ms) { | ||
return new Promise(resolve => step_timeout(resolve, ms)); | ||
} | ||
|
||
async function fetchInFrame(t, frameUrl, url) { | ||
const reports = []; | ||
const frame = await with_iframe(frameUrl); | ||
t.add_cleanup(() => frame.remove()); | ||
|
||
const observer = new frame.contentWindow.ReportingObserver((rs) => { | ||
for (const report of rs) { | ||
reports.push(report.toJSON()); | ||
} | ||
}); | ||
observer.observe(); | ||
const init = { mode: 'no-cors', cache: 'no-store' }; | ||
await frame.contentWindow.fetch(url, init).catch(() => {}); | ||
|
||
// Wait 1000ms for reports to settle. | ||
await new Promise(r => step_timeout(r, 1000)); | ||
return reports; | ||
} | ||
|
||
async function fetchInWorker(workerOrPort, url) { | ||
const script = | ||
`fetch('${url}', {mode: 'no-cors', cache: 'no-store'}).catch(() => {});`; | ||
const mc = new MessageChannel(); | ||
workerOrPort.postMessage({script, port: mc.port2}, [mc.port2]); | ||
return (await new Promise(r => mc.port1.onmessage = r)).data; | ||
} | ||
|
||
// We want to test several URLs in various environments (document, | ||
// dedicated worker, shared worker, service worker). As expectations | ||
// are independent of environment except for the context URLs in reports, | ||
// we define ENVIRONMENTS and CASES to reduce the code duplication. | ||
// | ||
// ENVIRONMENTS is a list of dictionaries. Each dictionary consists of: | ||
// - tag: the name of the environment | ||
// - contextUrl: the URL of the environment settings object | ||
// - run: an async function which generates reports | ||
// - test: a testharness Test object | ||
// - url: the URL for a test case (see below) | ||
// | ||
// CASES is a list of test cases. Each test case consists of: | ||
// - name: the name of the test case | ||
// - url: the URL of the test case | ||
// - check: a function to check the results | ||
// - reports: the generated reports | ||
// - url: the URL of the test case | ||
// - contextUrl: the URL of the environment settings object (see | ||
// ENVORONMENTS) | ||
|
||
const ENVIRONMENTS = [{ | ||
tag: 'document', | ||
contextUrl: FRAME_URL, | ||
run: async (test, url) => { | ||
return await fetchInFrame(test, FRAME_URL, url); | ||
}, | ||
}, { | ||
tag: 'dedicated worker', | ||
contextUrl: WORKER_URL, | ||
run: async (test, url) => { | ||
const worker = new Worker(WORKER_URL); | ||
worker.addEventListener('error', test.unreached_func('Worker.onerror')); | ||
test.add_cleanup(() => worker.terminate()); | ||
return await fetchInWorker(worker, url); | ||
}, | ||
}, { | ||
tag: 'shared worker', | ||
contextUrl: WORKER_URL, | ||
run: async (test, url) => { | ||
const worker = new SharedWorker(WORKER_URL); | ||
worker.addEventListener('error', test.unreached_func('Worker.onerror')); | ||
return await fetchInWorker(worker.port, url); | ||
}, | ||
}, { | ||
tag: 'service worker', | ||
contextUrl: WORKER_URL, | ||
run: async (test, url) => { | ||
// Generate a one-time scope for service workeer. | ||
const SCOPE = `${BASE}/${token()}.html`; | ||
const reg = | ||
await service_worker_unregister_and_register(test, WORKER_URL, SCOPE); | ||
test.add_cleanup(() => reg.unregister()); | ||
const worker = reg.installing || reg.waiting || reg.active; | ||
worker.addEventListener('error', test.unreached_func('Worker.onerror')); | ||
return await fetchInWorker(worker, url); | ||
}, | ||
}, { | ||
tag: 'between service worker and page', | ||
contextUrl: REPORTING_FRAME_URL, | ||
run: async (test, url) => { | ||
// Service Worker without COEP. | ||
const WORKER_URL = `${ORIGIN}${BASE}/sw.js`; | ||
const reg = await service_worker_unregister_and_register( | ||
test, WORKER_URL, REPORTING_FRAME_URL); | ||
test.add_cleanup(() => reg.unregister()); | ||
const worker = reg.installing || reg.waiting || reg.active; | ||
worker.addEventListener('error', test.unreached_func('Worker.onerror')); | ||
return await fetchInFrame( | ||
test, REPORTING_FRAME_URL, url); | ||
}, | ||
}]; | ||
|
||
const CASES = [{ | ||
name: 'same-origin', | ||
url: '/common/text-plain.txt', | ||
check: (reports, url, contextUrl) => { | ||
assert_equals(reports.length, 0); | ||
} | ||
}, { | ||
name: 'blocked by CORP: same-origin', | ||
url: `${REMOTE_ORIGIN}${BASE}/nothing-same-origin-corp.txt`, | ||
check: (reports, url, contextUrl) => { | ||
assert_equals(reports.length, 0); | ||
} | ||
}, { | ||
name: 'blocked due to COEP', | ||
url: `${REMOTE_ORIGIN}/common/text-plain.txt`, | ||
check: (reports, contextUrl, url) => { | ||
assert_equals(reports.length, 0); | ||
} | ||
}, { | ||
name: 'blocked during redirect', | ||
url: `${ORIGIN}/common/redirect.py?location=` + | ||
encodeURIComponent(`${REMOTE_ORIGIN}/common/text-plain.txt`), | ||
check: (reports, contextUrl, url) => { | ||
assert_equals(reports.length, 0); | ||
}, | ||
}]; | ||
|
||
for (const env of ENVIRONMENTS) { | ||
for (const testcase of CASES) { | ||
promise_test(async (t) => { | ||
const reports = await env.run(t, testcase.url); | ||
testcase.check(reports, env.contextUrl, testcase.url); | ||
}, `[${env.tag}] ${testcase.name}`); | ||
} | ||
} | ||
|
||
// A test for a non-empty destination. | ||
promise_test(async (t) => { | ||
const reports = []; | ||
const frame = await with_iframe(FRAME_URL); | ||
t.add_cleanup(() => frame.remove()); | ||
|
||
const observer = new frame.contentWindow.ReportingObserver((rs) => { | ||
for (const report of rs) { | ||
reports.push(report.toJSON()); | ||
} | ||
}); | ||
observer.observe(); | ||
const url = `${REMOTE_ORIGIN}/common/utils.js`; | ||
const script = frame.contentDocument.createElement('script'); | ||
script.src = url; | ||
frame.contentDocument.body.appendChild(script); | ||
|
||
// Wait 200ms for reports to settle. | ||
await t.step_timeout(200); | ||
|
||
assert_equals(reports.length, 0); | ||
}, 'destination: script'); | ||
|
||
</script> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
2 changes: 0 additions & 2 deletions
2
html/cross-origin-embedder-policy/resources/reporting-empty-frame.html.headers
This file was deleted.
Oops, something went wrong.