Skip to content

Commit

Permalink
Fetch: Plumb request initiator through passthrough service workers.
Browse files Browse the repository at this point in the history
This CL contains essentially two changes:

1. The request initiator origin is plumbed through service workers
   that do `fetch(evt.request)`.  In addition to plumbing, this
   requires changes to how we validate navigation requests in the
   CorsURLLoaderFactory.
2. Introduces an internal flag to indicate that a request should be
   treated as a main-frame navigation for SameSite cookie computation.
   This is necessary to override the service worker's IsolationInfo when
   a navigation request is passed through.

For more detailed information about these changes please see the
internal design doc at:

https://docs.google.com/document/d/1KZscujuV7bCFEnzJW-0DaCPU-I40RJimQKoCcI0umTQ/edit?usp=sharing

In addition, there is some discussion of these features in the following
spec issues:

whatwg/fetch#1321
whatwg/fetch#1327

Bug: 1115847,1241188
Change-Id: I7e236fa20aeabb705aef40fcf8d5c36da6d2798c
  • Loading branch information
wanderview authored and chromium-wpt-export-bot committed Oct 26, 2021
1 parent 4d23eaf commit 75d3a03
Show file tree
Hide file tree
Showing 9 changed files with 841 additions and 1 deletion.
551 changes: 551 additions & 0 deletions service-workers/service-worker/navigation-headers.https.html

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -90,8 +90,12 @@ self.addEventListener('fetch', function(event) {
var request = event.request;
if (url) {
request = new Request(url, init);
} else if (params['change-request']) {
request = new Request(request, init);
}
fetch(request).then(function(response) {
const response_promise = params['navpreload'] ? event.preloadResponse
: fetch(request);
response_promise.then(function(response) {
var expectedType = params['expected_type'];
if (expectedType && response.type !== expectedType) {
// Resolve a JSON object with a failure instead of rejecting
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Content-Type: text/javascript
Service-Worker-Allowed: /
12 changes: 12 additions & 0 deletions service-workers/service-worker/resources/form-poster.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<!DOCTYPE html>
<meta name="referrer" content="origin">
<form method="POST" id="form"></form>
<script>
function onLoad() {
const params = new URLSearchParams(self.location.search);
const form = document.getElementById('form');
form.action = decodeURIComponent(params.get('target'));
form.submit();
}
self.addEventListener('load', onLoad);
</script>
10 changes: 10 additions & 0 deletions service-workers/service-worker/resources/location-setter.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<!DOCTYPE html>
<meta name="referrer" content="origin">
<script>
function onLoad() {
const params = new URLSearchParams(self.location.search);
const target = decodeURIComponent(params.get('target'));
self.location = target;
}
self.addEventListener('load', onLoad);
</script>
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
def main(request, response):
response.status = (200, b"OK")
response.headers.set(b"Content-Type", b"text/html")
return b"""
<script>
self.addEventListener('load', evt => {
self.parent.postMessage({
origin: '%s',
referer: '%s',
'sec-fetch-site': '%s',
'sec-fetch-mode': '%s',
'sec-fetch-dest': '%s',
});
});
</script>""" % (request.headers.get(
b"origin", b"not set"), request.headers.get(b"referer", b"not set"),
request.headers.get(b"sec-fetch-site", b"not set"),
request.headers.get(b"sec-fetch-mode", b"not set"),
request.headers.get(b"sec-fetch-dest", b"not set"))
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<!DOCTYPE html>
<meta charset="utf-8"/>
<script>
async function onLoad() {
const scope = self.origin + '/cookies/resources/postToParent.py?with-sw';
const script = './fetch-rewrite-worker.js';
const reg = await navigator.serviceWorker.register(script, { scope: scope });
await new Promise(resolve => {
const worker = reg.installing;
worker.addEventListener('statechange', evt => {
if (worker.state === 'activated') {
resolve();
}
});
});
await reg.navigationPreload.enable();
window.opener.postMessage({ type: 'SW-REGISTERED' }, '*');
}
self.addEventListener('load', onLoad);
</script>
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<!DOCTYPE html>
<meta charset="utf-8"/>
<script>
async function onLoad() {
const scope = self.origin + '/cookies/resources/postToParent.py?with-sw';
const reg = await navigator.serviceWorker.getRegistration(scope);
await reg.unregister();
window.opener.postMessage({ type: 'SW-UNREGISTERED' }, '*');
}
self.addEventListener('load', onLoad);
</script>
211 changes: 211 additions & 0 deletions service-workers/service-worker/same-site-cookies.https.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,211 @@
<!DOCTYPE html>
<meta charset="utf-8"/>
<title>Service Worker: Same-site cookie behavior</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/common/get-host-info.sub.js"></script>
<script src="/service-workers/service-worker/resources/test-helpers.sub.js"></script>
<script src="/cookies/resources/cookie-helper.sub.js"></script>
<body>
<script>
'use strict';
async function unregister_service_worker(origin) {
const w = window.open(origin +
'/service-workers/service-worker/resources/same-site-cookies-unregister.html');
try {
await wait_for_message('SW-UNREGISTERED');
} finally {
w.close();
}
}

async function register_service_worker(origin) {
const w = window.open(origin +
'/service-workers/service-worker/resources/same-site-cookies-register.html');
try {
await wait_for_message('SW-REGISTERED');
} finally {
w.close();
}
}

async function run_test(t, origin, navaction, swaction, expected) {
const value = 'COOKIE_VALUE';
await resetSameSiteCookies(origin, value);
const sw_param = swaction === 'no-sw' ? 'no-sw' : 'with-sw';
let action_param = '';
if (swaction === 'fallback') {
action_param = '&ignore';
} else if (swaction !== 'no-sw') {
action_param = '&' + swaction;
}
const navpreload_param = swaction === 'navpreload' ? '&navpreload' : '';
const change_request_param = swaction === 'change-request' ? '&change-request' : '';
const target_string = origin + `/cookies/resources/postToParent.py?` +
`${sw_param}${action_param}`
const target_url = new URL(target_string);
if (navaction === 'window.open') {
const w = window.open(target_url);
t.add_cleanup(() => w.close());
} else if (navaction === 'form post') {
const poster_url =
`./resources/form-poster.html?target=${encodeURIComponent(target_url)}`;
const w = window.open(poster_url);
t.add_cleanup(() => w.close());
}
const result = await wait_for_message('COOKIES');
verifySameSiteCookieState(expected, value, result.data,
DomSameSiteStatus.SAME_SITE);
}

promise_test(async t => {
await register_service_worker(self.origin);
await register_service_worker(SECURE_SUBDOMAIN_ORIGIN);
await register_service_worker(SECURE_CROSS_SITE_ORIGIN);
}, 'Setup service workers');

promise_test(t => {
return run_test(t, self.origin, 'window.open', 'no-sw',
SameSiteStatus.STRICT);
}, 'same-origin, window.open with no service worker');

promise_test(t => {
return run_test(t, self.origin, 'window.open', 'fallback',
SameSiteStatus.STRICT);
}, 'same-origin, window.open with fallback');

promise_test(t => {
return run_test(t, self.origin, 'window.open', 'passthrough',
SameSiteStatus.STRICT);
}, 'same-origin, window.open with passthrough');

promise_test(t => {
return run_test(t, self.origin, 'window.open', 'change-request',
SameSiteStatus.STRICT);
}, 'same-origin, window.open with change-request');

promise_test(t => {
return run_test(t, self.origin, 'window.open', 'navpreload',
SameSiteStatus.STRICT);
}, 'same-origin, window.open with navpreload');

promise_test(t => {
return run_test(t, SECURE_SUBDOMAIN_ORIGIN, 'window.open', 'no-sw',
SameSiteStatus.STRICT);
}, 'same-site, window.open with no service worker');

promise_test(t => {
return run_test(t, SECURE_SUBDOMAIN_ORIGIN, 'window.open', 'fallback',
SameSiteStatus.STRICT);
}, 'same-site, window.open with fallback');

promise_test(t => {
return run_test(t, SECURE_SUBDOMAIN_ORIGIN, 'window.open', 'passthrough',
SameSiteStatus.STRICT);
}, 'same-site, window.open with passthrough');

promise_test(t => {
return run_test(t, SECURE_SUBDOMAIN_ORIGIN, 'window.open', 'change-request',
SameSiteStatus.STRICT);
}, 'same-site, window.open with change-request');

promise_test(t => {
return run_test(t, SECURE_SUBDOMAIN_ORIGIN, 'window.open', 'navpreload',
SameSiteStatus.STRICT);
}, 'same-site, window.open with navpreload');

promise_test(t => {
return run_test(t, SECURE_CROSS_SITE_ORIGIN, 'window.open', 'no-sw',
SameSiteStatus.LAX);
}, 'cross-site, window.open with no service worker');

promise_test(t => {
return run_test(t, SECURE_CROSS_SITE_ORIGIN, 'window.open', 'fallback',
SameSiteStatus.LAX);
}, 'cross-site, window.open with fallback');

promise_test(t => {
return run_test(t, SECURE_CROSS_SITE_ORIGIN, 'window.open', 'passthrough',
SameSiteStatus.LAX);
}, 'cross-site, window.open with passthrough');

promise_test(t => {
return run_test(t, SECURE_CROSS_SITE_ORIGIN, 'window.open', 'change-request',
SameSiteStatus.STRICT);
}, 'cross-site, window.open with change-request');

promise_test(t => {
return run_test(t, SECURE_CROSS_SITE_ORIGIN, 'window.open', 'navpreload',
SameSiteStatus.LAX);
}, 'cross-site, window.open with navpreload');

//
// Form POST tests
//
promise_test(t => {
return run_test(t, self.origin, 'form post', 'no-sw', SameSiteStatus.STRICT);
}, 'same-origin, form post with no service worker');

promise_test(t => {
return run_test(t, self.origin, 'form post', 'fallback',
SameSiteStatus.STRICT);
}, 'same-origin, form post with fallback');

promise_test(t => {
return run_test(t, self.origin, 'form post', 'passthrough',
SameSiteStatus.STRICT);
}, 'same-origin, form post with passthrough');

promise_test(t => {
return run_test(t, self.origin, 'form post', 'change-request',
SameSiteStatus.STRICT);
}, 'same-origin, form post with change-request');

promise_test(t => {
return run_test(t, SECURE_SUBDOMAIN_ORIGIN, 'form post', 'no-sw',
SameSiteStatus.STRICT);
}, 'same-site, form post with no service worker');

promise_test(t => {
return run_test(t, SECURE_SUBDOMAIN_ORIGIN, 'form post', 'fallback',
SameSiteStatus.STRICT);
}, 'same-site, form post with fallback');

promise_test(t => {
return run_test(t, SECURE_SUBDOMAIN_ORIGIN, 'form post', 'passthrough',
SameSiteStatus.STRICT);
}, 'same-site, form post with passthrough');

promise_test(t => {
return run_test(t, SECURE_SUBDOMAIN_ORIGIN, 'form post', 'change-request',
SameSiteStatus.STRICT);
}, 'same-site, form post with change-request');

promise_test(t => {
return run_test(t, SECURE_CROSS_SITE_ORIGIN, 'form post', 'no-sw',
SameSiteStatus.CROSS_SITE);
}, 'cross-site, form post with no service worker');

promise_test(t => {
return run_test(t, SECURE_CROSS_SITE_ORIGIN, 'form post', 'fallback',
SameSiteStatus.CROSS_SITE);
}, 'cross-site, form post with fallback');

promise_test(t => {
return run_test(t, SECURE_CROSS_SITE_ORIGIN, 'form post', 'passthrough',
SameSiteStatus.CROSS_SITE);
}, 'cross-site, form post with passthrough');

promise_test(t => {
return run_test(t, SECURE_CROSS_SITE_ORIGIN, 'form post', 'change-request',
SameSiteStatus.STRICT);
}, 'cross-site, form post with change-request');

promise_test(async t => {
await unregister_service_worker(self.origin);
await unregister_service_worker(SECURE_SUBDOMAIN_ORIGIN);
await unregister_service_worker(SECURE_CROSS_SITE_ORIGIN);
}, 'Cleanup service workers');

</script>
</body>

0 comments on commit 75d3a03

Please sign in to comment.