488 lines
No EOL
22 KiB
JavaScript
488 lines
No EOL
22 KiB
JavaScript
// META: script=helpers.js
|
|
// META: script=/resources/testdriver.js
|
|
// META: script=/resources/testdriver-vendor.js
|
|
// META: timeout=long
|
|
"use strict";
|
|
|
|
// These are secure origins with different relations to the current document.
|
|
const https_origin = 'https://{{host}}:{{ports[https][0]}}';
|
|
const same_site = 'https://{{hosts[][www]}}:{{ports[https][0]}}';
|
|
const cross_site = 'https://{{hosts[alt][]}}:{{ports[https][0]}}';
|
|
const alt_cross_site = 'https://{{hosts[alt][www]}}:{{ports[https][0]}}';
|
|
|
|
const responder_script = 'embedded_responder.js';
|
|
const nested_path = '/storage-access-api/resources/nested-handle-storage-access-headers.py';
|
|
const retry_path = '/storage-access-api/resources/handle-headers-retry.py';
|
|
const non_retry_path = '/storage-access-api/resources/handle-headers-non-retry.py';
|
|
|
|
function makeURL(key, domain, path, params) {
|
|
const request_params = new URLSearchParams(params);
|
|
request_params.append('key', key);
|
|
return domain + path + '?' + request_params.toString();
|
|
}
|
|
|
|
async function grantStorageAccessForEmbedSite(test, origin) {
|
|
const iframe_params = new URLSearchParams([['script', responder_script]]);
|
|
const iframe = await CreateFrame(origin +
|
|
'/storage-access-api/resources/script-with-cookie-header.py?' +
|
|
iframe_params.toString());
|
|
test.add_cleanup( async () => {
|
|
await SetPermissionInFrame(iframe,
|
|
[{ name: 'storage-access' }, 'prompt']);
|
|
iframe.parentNode.removeChild(iframe);
|
|
})
|
|
await SetPermissionInFrame(iframe,
|
|
[{ name: 'storage-access' }, 'granted']);
|
|
}
|
|
|
|
// Sends a request whose headers can be read in cross-site contexts.
|
|
async function sendReadableHeaderRequest(url) {
|
|
return fetch(url, {credentials: 'include', mode: 'no-cors'});
|
|
}
|
|
|
|
// Sends a request `resources/retrieve-storage-access-headers.py` and parses
|
|
// the response as JSON. Will return `undefined` if no headers were set at the
|
|
// given key, or if the headers have already been retrieved from that key.
|
|
async function sendRetrieveRequest(key) {
|
|
const retrieval_path = '/storage-access-api/resources/retrieve-storage-access-headers.py?';
|
|
const request_params = new URLSearchParams([['key', key]]);
|
|
const response = await fetch(retrieval_path + request_params.toString());
|
|
|
|
return response.status !== 200 ? undefined : response.json();
|
|
}
|
|
|
|
// Checks that the values of `actual_headers` match those passed in the
|
|
// `expected_headers` at their respective header keys. Headers with the value
|
|
// of `undefined` in `expected_headers` are expected to be absent from
|
|
// `actual_headers`.
|
|
function assertHeaderValuesMatch(actual_headers, expected_headers) {
|
|
for (const [expected_name, expected_value] of Object.entries(
|
|
expected_headers)) {
|
|
const actual_value = actual_headers[expected_name];
|
|
if (expected_value === undefined) {
|
|
assert_equals(actual_value, undefined);
|
|
} else {
|
|
assert_array_equals(actual_value, expected_value);
|
|
}
|
|
}
|
|
}
|
|
|
|
function addCommonCleanupCallback(test) {
|
|
test.add_cleanup(async () => {
|
|
await test_driver.delete_all_cookies();
|
|
await MaybeSetStorageAccess("*", "*", "allowed");
|
|
});
|
|
}
|
|
|
|
function retriedKey(key) {
|
|
return key + 'active';
|
|
}
|
|
|
|
function redirectedKey(key) {
|
|
return key + 'redirected';
|
|
}
|
|
|
|
promise_test(async (t) => {
|
|
const key = '{{uuid()}}';
|
|
await MaybeSetStorageAccess("*", "*", "blocked");
|
|
addCommonCleanupCallback(t);
|
|
|
|
await fetch(makeURL(key, cross_site, non_retry_path),
|
|
{credentials: 'omit', mode: 'no-cors'});
|
|
const headers = await sendRetrieveRequest(key);
|
|
assertHeaderValuesMatch(headers, {'sec-fetch-storage-access': undefined});
|
|
}, "Sec-Fetch-Storage-Access is omitted when credentials are omitted");
|
|
|
|
promise_test(async (t) => {
|
|
const key = '{{uuid()}}';
|
|
await MaybeSetStorageAccess("*", "*", "blocked");
|
|
addCommonCleanupCallback(t);
|
|
|
|
await sendReadableHeaderRequest(makeURL(key, cross_site, non_retry_path));
|
|
const headers = await sendRetrieveRequest(key);
|
|
assertHeaderValuesMatch(headers, {'sec-fetch-storage-access': ['none']});
|
|
}, "Sec-Fetch-Storage-Access is `none` when unpartitioned cookies are unavailable.");
|
|
|
|
promise_test(async (t) => {
|
|
const key = '{{uuid()}}';
|
|
await MaybeSetStorageAccess("*", "*", "blocked");
|
|
|
|
// Create an iframe and grant it storage access permissions.
|
|
await grantStorageAccessForEmbedSite(t, cross_site);
|
|
// A cross-site request to the same site as the above iframe should have an
|
|
// `inactive` storage access status since a permission grant exists for the
|
|
// context.
|
|
await sendReadableHeaderRequest(makeURL(key, cross_site, non_retry_path));
|
|
const headers = await sendRetrieveRequest(key);
|
|
// We should see the origin header on the inactive case.
|
|
assertHeaderValuesMatch(headers, {'sec-fetch-storage-access': ['inactive'],
|
|
'origin': [https_origin]});
|
|
}, "Sec-Fetch-Storage-Access is `inactive` when unpartitioned cookies are available but not in use.");
|
|
|
|
promise_test(async (t) => {
|
|
const key = '{{uuid()}}';
|
|
await MaybeSetStorageAccess("*", "*", "blocked");
|
|
await SetFirstPartyCookieAndUnsetStorageAccessPermission(cross_site);
|
|
addCommonCleanupCallback(t);
|
|
|
|
await grantStorageAccessForEmbedSite(t, cross_site);
|
|
await sendReadableHeaderRequest(makeURL(key, cross_site, retry_path,
|
|
[['retry-allowed-origin',
|
|
https_origin]]));
|
|
// Retrieve the pre-retry headers.
|
|
const headers = await sendRetrieveRequest(key);
|
|
// Unpartitioned cookie should not be included before the retry.
|
|
assertHeaderValuesMatch(headers, {'sec-fetch-storage-access': ['inactive'],
|
|
'origin': [https_origin], 'cookie': undefined});
|
|
// Retrieve the headers for the retried request.
|
|
const retried_headers = await sendRetrieveRequest(retriedKey(key));
|
|
// The unpartitioned cookie should have been included in the retry.
|
|
assertHeaderValuesMatch(retried_headers, {
|
|
'sec-fetch-storage-access': ['active'],
|
|
'origin': [https_origin],
|
|
'cookie': ['cookie=unpartitioned']
|
|
});
|
|
}, "Sec-Fetch-Storage-Access is `active` after a valid retry with matching explicit allowed-origin.");
|
|
|
|
promise_test(async (t) => {
|
|
const key = '{{uuid()}}';
|
|
await MaybeSetStorageAccess("*", "*", "blocked");
|
|
await SetFirstPartyCookieAndUnsetStorageAccessPermission(cross_site);
|
|
addCommonCleanupCallback(t);
|
|
|
|
await grantStorageAccessForEmbedSite(t, cross_site);
|
|
await sendReadableHeaderRequest(makeURL(key, cross_site, retry_path,
|
|
[['retry-allowed-origin','*']]));
|
|
// Retrieve the pre-retry headers.
|
|
const headers = await sendRetrieveRequest(key);
|
|
assertHeaderValuesMatch(headers, {
|
|
'sec-fetch-storage-access': ['inactive'],
|
|
'origin': [https_origin],
|
|
'cookie': undefined
|
|
});
|
|
// Retrieve the headers for the retried request.
|
|
const retried_headers = await sendRetrieveRequest(retriedKey(key));
|
|
assertHeaderValuesMatch(retried_headers, {
|
|
'sec-fetch-storage-access': ['active'],
|
|
'origin': [https_origin],
|
|
'cookie': ['cookie=unpartitioned']
|
|
});
|
|
}, "Sec-Fetch-Storage-Access is active after retry with wildcard `allowed-origin` value.");
|
|
|
|
promise_test(async (t) => {
|
|
const key = '{{uuid()}}';
|
|
await MaybeSetStorageAccess("*", "*", "blocked");
|
|
addCommonCleanupCallback(t);
|
|
|
|
await grantStorageAccessForEmbedSite(t, cross_site);
|
|
await sendReadableHeaderRequest(
|
|
makeURL(key, cross_site, retry_path,
|
|
[['retry-allowed-origin', '']]));
|
|
|
|
// The server behavior when retrieving a header that was never sent is
|
|
// indistinguishable from its behavior when retrieving a header that was
|
|
// sent but was previously retrieved. To ensure the request to retrieve the
|
|
// post-retry header occurs only because they were never sent, always
|
|
// test its retrieval first.
|
|
const retried_headers = await sendRetrieveRequest(retriedKey(key));
|
|
assert_equals(retried_headers, undefined);
|
|
// Retrieve the pre-retry headers.
|
|
const headers = await sendRetrieveRequest(key);
|
|
assertHeaderValuesMatch(headers, {'sec-fetch-storage-access': ['inactive']});
|
|
}, "'Activate-Storage-Access: retry' is a no-op on a request without an `allowed-origin` value.");
|
|
|
|
promise_test(async (t) => {
|
|
const key = '{{uuid()}}';
|
|
await MaybeSetStorageAccess("*", "*", "blocked");
|
|
addCommonCleanupCallback(t);
|
|
|
|
await grantStorageAccessForEmbedSite(t, cross_site);
|
|
await sendReadableHeaderRequest(makeURL(key, cross_site, retry_path,
|
|
[['retry-allowed-origin',
|
|
same_site]]));
|
|
// Should not be able to retrieve any headers at the post-retry key.
|
|
const retried_headers = await sendRetrieveRequest(retriedKey(key));
|
|
assert_equals(retried_headers, undefined);
|
|
// Retrieve the pre-retry headers.
|
|
const headers = await sendRetrieveRequest(key);
|
|
assertHeaderValuesMatch(headers, {'sec-fetch-storage-access': ['inactive']});
|
|
}, "'Activate-Storage-Access: retry' is a no-op on a request from an origin that does not match its `allowed-origin` value.");
|
|
|
|
promise_test(async (t) => {
|
|
const key = '{{uuid()}}';
|
|
await MaybeSetStorageAccess("*", "*", "blocked");
|
|
addCommonCleanupCallback(t);
|
|
|
|
await sendReadableHeaderRequest(makeURL(key, cross_site, retry_path,
|
|
[['retry-allowed-origin',
|
|
https_origin]]));
|
|
// Should not be able to retrieve any headers at the post-retry key.
|
|
const retried_headers = await sendRetrieveRequest(retriedKey(key));
|
|
assert_equals(retried_headers, undefined);
|
|
// Retrieve the pre-retry headers.
|
|
const headers = await sendRetrieveRequest(key);
|
|
assertHeaderValuesMatch(headers, {'sec-fetch-storage-access': ['none'],
|
|
'origin': undefined});
|
|
}, "Activate-Storage-Access `retry` is a no-op on a request with a `none` Storage Access status.");
|
|
|
|
promise_test(async (t) => {
|
|
const key = '{{uuid()}}';
|
|
await MaybeSetStorageAccess("*", "*", "blocked");
|
|
addCommonCleanupCallback(t);
|
|
|
|
await grantStorageAccessForEmbedSite(t, cross_site);
|
|
const load_header_iframe = await CreateFrame(makeURL(key, cross_site,
|
|
non_retry_path,
|
|
[['load', ''],
|
|
['script', responder_script]]));
|
|
assert_true(await FrameHasStorageAccess(load_header_iframe),
|
|
"frame should have storage access because of the `load` header");
|
|
}, "Activate-Storage-Access `load` header grants storage access to frame.");
|
|
|
|
promise_test(async (t) => {
|
|
const key = '{{uuid()}}';
|
|
await MaybeSetStorageAccess("*", "*", "blocked");
|
|
addCommonCleanupCallback(t);
|
|
|
|
const iframe = await CreateFrame(makeURL(key, cross_site,
|
|
non_retry_path,
|
|
[['script', responder_script]]));
|
|
t.add_cleanup(async () => {
|
|
SetPermissionInFrame(iframe,
|
|
[{ name: 'storage-access' }, 'prompt']);
|
|
});
|
|
await SetPermissionInFrame(iframe,
|
|
[{ name: 'storage-access' }, 'granted']);
|
|
await RequestStorageAccessInFrame(iframe);
|
|
// Create a child iframe with the same source, that causes the server to
|
|
// respond with the `load` header.
|
|
const nested_iframe = await CreateFrameHelper((frame) => {
|
|
// Need a unique `key` on the request or else the server will fail it.
|
|
frame.src = makeURL(key + 'load', cross_site, non_retry_path,
|
|
[['load', ''], ['script', responder_script]]);
|
|
iframe.appendChild(frame);
|
|
}, false);
|
|
// The nested frame will have storage access because of the `load` response.
|
|
assert_true(await FrameHasStorageAccess(nested_iframe));
|
|
}, "Activate-Storage-Access `load` is honored for `active` cases.");
|
|
|
|
promise_test(async (t) => {
|
|
const key = '{{uuid()}}';
|
|
await MaybeSetStorageAccess("*", "*", "blocked");
|
|
addCommonCleanupCallback(t);
|
|
|
|
const load_header_iframe = await CreateFrame(makeURL(key, cross_site,
|
|
non_retry_path,
|
|
[['load', ''],
|
|
['script', responder_script]]));
|
|
assert_false(await FrameHasStorageAccess(load_header_iframe),
|
|
"frame should not have received storage access.");
|
|
}, "Activate-Storage-Access `load` header is a no-op for requests without storage access.");
|
|
|
|
promise_test(async t => {
|
|
const key = '{{uuid()}}';
|
|
await MaybeSetStorageAccess('*', '*', 'blocked');
|
|
addCommonCleanupCallback(t);
|
|
|
|
const iframe_params = new URLSearchParams([['script',
|
|
'embedded_responder.js']]);
|
|
const iframe = await CreateFrame(cross_site + nested_path + '?' +
|
|
iframe_params.toString());
|
|
|
|
// Create a cross-site request within the iframe
|
|
const nested_url_params = new URLSearchParams([['key', key]]);
|
|
const nested_url = https_origin + non_retry_path + '?' +
|
|
nested_url_params.toString();
|
|
await NoCorsFetchFromFrame(iframe, nested_url);
|
|
|
|
const headers = await sendRetrieveRequest(key);
|
|
assertHeaderValuesMatch(headers, {'sec-fetch-storage-access': ['inactive'],
|
|
'origin': [cross_site]});
|
|
}, "Sec-Fetch-Storage-Access is `inactive` for ABA case.");
|
|
|
|
promise_test(async t => {
|
|
const key = '{{uuid()}}';
|
|
await MaybeSetStorageAccess('*', '*', 'blocked');
|
|
await SetFirstPartyCookieAndUnsetStorageAccessPermission(https_origin);
|
|
addCommonCleanupCallback(t);
|
|
|
|
const iframe_params = new URLSearchParams([['script',
|
|
'embedded_responder.js']]);
|
|
const iframe = await CreateFrame(cross_site + nested_path + '?' +
|
|
iframe_params.toString());
|
|
|
|
const nested_url_params = new URLSearchParams([
|
|
['key', key],
|
|
['retry-allowed-origin', cross_site]]);
|
|
const nested_url = https_origin + retry_path +
|
|
'?' + nested_url_params.toString();
|
|
await NoCorsFetchFromFrame(iframe, nested_url);
|
|
const headers = await sendRetrieveRequest(key);
|
|
assertHeaderValuesMatch(headers, {
|
|
'sec-fetch-storage-access': ['inactive'],
|
|
'origin': [cross_site],
|
|
'cookie': undefined
|
|
});
|
|
|
|
// Storage access should have been activated, without the need for a grant,
|
|
// on the ABA case.
|
|
const retried_headers = await sendRetrieveRequest(retriedKey(key));
|
|
assertHeaderValuesMatch(retried_headers, {
|
|
'sec-fetch-storage-access': ['active'],
|
|
'origin': [cross_site],
|
|
'cookie': ['cookie=unpartitioned']
|
|
});
|
|
}, "Storage Access can be activated for ABA cases by retrying.");
|
|
|
|
promise_test(async (t) => {
|
|
const key = '{{uuid()}}';
|
|
await MaybeSetStorageAccess("*", "*", "blocked");
|
|
await SetFirstPartyCookieAndUnsetStorageAccessPermission(cross_site);
|
|
addCommonCleanupCallback(t);
|
|
|
|
await grantStorageAccessForEmbedSite(t, cross_site);
|
|
|
|
// Create a redirect destination that is same origin to the initial
|
|
// request.
|
|
const redirect_url = makeURL(key,
|
|
cross_site,
|
|
retry_path,
|
|
[['redirected', '']]);
|
|
// Send a request instructing the server include the `retry` response,
|
|
// and then redirect when storage access has been activated.
|
|
await sendReadableHeaderRequest(makeURL(key, cross_site, retry_path,
|
|
[['retry-allowed-origin',
|
|
https_origin],
|
|
['once-active-redirect-location',
|
|
redirect_url]]));
|
|
// Confirm the normal retry behavior.
|
|
const headers = await sendRetrieveRequest(key);
|
|
assertHeaderValuesMatch(headers, {'sec-fetch-storage-access': ['inactive'],
|
|
'origin': [https_origin], 'cookie': undefined});
|
|
const retried_headers = await sendRetrieveRequest(retriedKey(key));
|
|
assertHeaderValuesMatch(retried_headers, {
|
|
'sec-fetch-storage-access': ['active'],
|
|
'origin': [https_origin],
|
|
'cookie': ['cookie=unpartitioned']
|
|
});
|
|
// Retrieve the headers for the post-retry redirect request.
|
|
const redirected_headers = await sendRetrieveRequest(redirectedKey(retriedKey(key)));
|
|
assertHeaderValuesMatch(redirected_headers, {
|
|
'sec-fetch-storage-access': ['active'],
|
|
'origin': [https_origin],
|
|
'cookie': ['cookie=unpartitioned']
|
|
});
|
|
}, "Sec-Fetch-Storage-Access maintains value on same-origin redirect.");
|
|
|
|
promise_test(async (t) => {
|
|
const key = '{{uuid()}}';
|
|
await MaybeSetStorageAccess("*", "*", "blocked");
|
|
await SetFirstPartyCookieAndUnsetStorageAccessPermission(cross_site);
|
|
addCommonCleanupCallback(t);
|
|
|
|
await grantStorageAccessForEmbedSite(t, cross_site);
|
|
|
|
// Create a redirect destination that is cross-origin same-site to the
|
|
// initial request.
|
|
const redirect_url = makeURL(key, alt_cross_site, retry_path,
|
|
[['redirected', '']]);
|
|
await sendReadableHeaderRequest(makeURL(key, cross_site, retry_path,
|
|
[['retry-allowed-origin',
|
|
https_origin],
|
|
['once-active-redirect-location',
|
|
redirect_url]]));
|
|
|
|
const headers = await sendRetrieveRequest(key);
|
|
assertHeaderValuesMatch(headers, {
|
|
'sec-fetch-storage-access': ['inactive'],
|
|
'origin': [https_origin],
|
|
'cookie': undefined
|
|
});
|
|
const retried_headers = await sendRetrieveRequest(retriedKey(key));
|
|
assertHeaderValuesMatch(retried_headers, {
|
|
'sec-fetch-storage-access': ['active'],
|
|
'origin': [https_origin],
|
|
'cookie': ['cookie=unpartitioned']
|
|
});
|
|
// Retrieve the headers for the post-retry redirect request.
|
|
const redirected_headers = await sendRetrieveRequest(redirectedKey(key));
|
|
assertHeaderValuesMatch(redirected_headers, {
|
|
'sec-fetch-storage-access': ['inactive'],
|
|
'origin': ['null'],
|
|
'cookie': undefined
|
|
});
|
|
}, "Sec-Fetch-Storage-Access is not 'active' after cross-origin same-site redirection.");
|
|
|
|
promise_test(async (t) => {
|
|
const key = '{{uuid()}}';
|
|
await MaybeSetStorageAccess("*", "*", "blocked");
|
|
await SetFirstPartyCookieAndUnsetStorageAccessPermission(cross_site);
|
|
addCommonCleanupCallback(t);
|
|
await grantStorageAccessForEmbedSite(t, cross_site);
|
|
|
|
// Create a redirect destination that is cross-site to the
|
|
// initial request.
|
|
const redirect_url = makeURL(key, https_origin, retry_path,
|
|
[['redirected', '']]);
|
|
await sendReadableHeaderRequest(makeURL(key, cross_site, retry_path,
|
|
[['retry-allowed-origin',
|
|
https_origin],
|
|
['once-active-redirect-location',
|
|
redirect_url]]));
|
|
|
|
const headers = await sendRetrieveRequest(key);
|
|
assertHeaderValuesMatch(headers, {
|
|
'sec-fetch-storage-access': ['inactive'],
|
|
'origin': [https_origin],
|
|
'cookie': undefined
|
|
});
|
|
const retried_headers = await sendRetrieveRequest(retriedKey(key));
|
|
assertHeaderValuesMatch(retried_headers, {
|
|
'sec-fetch-storage-access': ['active'],
|
|
'origin': [https_origin],
|
|
'cookie': ['cookie=unpartitioned']
|
|
});
|
|
// Retrieve the headers for the post-retry redirect request.
|
|
const redirected_headers = await sendRetrieveRequest(redirectedKey(key));
|
|
// These values will be empty because the frame is now same origin with
|
|
// the top level.
|
|
assertHeaderValuesMatch(redirected_headers, {
|
|
'sec-fetch-storage-access': undefined,
|
|
'origin': ['null'],
|
|
'cookie': undefined
|
|
});
|
|
}, "Sec-Fetch-Storage-Access loses value on a cross-site redirection.");
|
|
|
|
promise_test(async (t) => {
|
|
const key = '{{uuid()}}';
|
|
await MaybeSetStorageAccess("*", "*", "blocked");
|
|
await SetFirstPartyCookieAndUnsetStorageAccessPermission(cross_site);
|
|
addCommonCleanupCallback(t);
|
|
await grantStorageAccessForEmbedSite(t, cross_site);
|
|
|
|
// Create a redirect destination that is cross-origin same-site to the
|
|
// initial request.
|
|
const redirect_url = makeURL(key, https_origin, retry_path, [['redirected', '']]);
|
|
// Send a request that instructs the server to respond with both retry and
|
|
// response headers.
|
|
await sendReadableHeaderRequest(makeURL(key, cross_site, retry_path,
|
|
[['retry-allowed-origin',
|
|
https_origin],
|
|
['redirect-location',
|
|
redirect_url]]));
|
|
// No redirect should have occurred, so a retrieval request for the
|
|
// redirect request should fail.
|
|
const redirected_headers = await sendRetrieveRequest(redirectedKey(key));
|
|
assert_equals(redirected_headers, undefined);
|
|
// Confirm the normal retry behavior.
|
|
const headers = await sendRetrieveRequest(key);
|
|
assertHeaderValuesMatch(headers, {'sec-fetch-storage-access': ['inactive'],
|
|
'origin': [https_origin],
|
|
'cookie': undefined});
|
|
const retried_headers = await sendRetrieveRequest(retriedKey(key));
|
|
assertHeaderValuesMatch(retried_headers, {
|
|
'sec-fetch-storage-access': ['active'],
|
|
'origin': [https_origin],
|
|
'cookie': ['cookie=unpartitioned']
|
|
});
|
|
}, "Activate-Storage-Access retry is handled before any redirects are followed."); |