| // 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."); |