From 26a029d407be480d791972afb5975cf62c9360a6 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Fri, 19 Apr 2024 02:47:55 +0200 Subject: Adding upstream version 124.0.1. Signed-off-by: Daniel Baumann --- .../tests/fetch/private-network-access/META.yml | 7 + .../tests/fetch/private-network-access/README.md | 10 + .../anchor.tentative.https.window.js | 191 +++++ .../anchor.tentative.window.js | 95 +++ ...no-preflight-required.tentative.https.window.js | 91 +++ ...ame-subresource-fetch.tentative.https.window.js | 330 ++++++++ .../fenced-frame.tentative.https.window.js | 150 ++++ ...-from-treat-as-public.tentative.https.window.js | 80 ++ .../fetch.tentative.https.window.js | 271 +++++++ .../fetch.tentative.window.js | 183 +++++ .../iframe.tentative.https.window.js | 267 +++++++ .../iframe.tentative.window.js | 110 +++ .../mixed-content-fetch.tentative.https.window.js | 278 +++++++ .../nested-worker.tentative.https.window.js | 36 + .../nested-worker.tentative.window.js | 36 + .../preflight-cache.https.tentative.window.js | 88 +++ .../redirect.tentative.https.window.js | 640 +++++++++++++++ .../private-network-access/resources/anchor.html | 16 + .../private-network-access/resources/executor.html | 9 + .../resources/fenced-frame-fetcher.https.html | 25 + .../fenced-frame-fetcher.https.html.headers | 1 + ...-frame-private-network-access-target.https.html | 8 + .../fenced-frame-private-network-access.https.html | 14 + ...frame-private-network-access.https.html.headers | 1 + .../private-network-access/resources/fetcher.html | 21 + .../private-network-access/resources/fetcher.js | 20 + .../resources/iframed-no-preflight-received.html | 7 + .../private-network-access/resources/iframed.html | 7 + .../private-network-access/resources/iframer.html | 9 + .../resources/no-preflight-received.html | 6 + .../resources/open-to-existing-window.html | 12 + .../private-network-access/resources/openee.html | 8 + .../private-network-access/resources/opener.html | 11 + .../private-network-access/resources/preflight.py | 191 +++++ .../resources/service-worker-bridge.html | 155 ++++ .../resources/service-worker.js | 18 + .../resources/shared-fetcher.js | 23 + .../resources/shared-worker-blob-fetcher.html | 50 ++ .../resources/shared-worker-fetcher.html | 19 + .../resources/socket-opener.html | 15 + .../resources/support.sub.js | 857 +++++++++++++++++++++ .../resources/worker-blob-fetcher.html | 45 ++ .../resources/worker-fetcher.html | 18 + .../resources/worker-fetcher.js | 11 + .../resources/xhr-sender.html | 33 + ...rker-background-fetch.tentative.https.window.js | 143 ++++ .../service-worker-fetch.tentative.https.window.js | 235 ++++++ ...service-worker-update.tentative.https.window.js | 106 +++ .../service-worker.tentative.https.window.js | 84 ++ ...red-worker-blob-fetch.tentative.https.window.js | 168 ++++ .../shared-worker-blob-fetch.tentative.window.js | 173 +++++ .../shared-worker-fetch.tentative.https.window.js | 167 ++++ .../shared-worker-fetch.tentative.window.js | 154 ++++ .../shared-worker.tentative.https.window.js | 34 + .../shared-worker.tentative.window.js | 34 + .../websocket.tentative.https.window.js | 40 + .../websocket.tentative.window.js | 40 + .../window-open-existing.tentative.https.window.js | 209 +++++ .../window-open-existing.tentative.window.js | 95 +++ .../window-open.tentative.https.window.js | 205 +++++ .../window-open.tentative.window.js | 95 +++ .../worker-blob-fetch.tentative.window.js | 155 ++++ .../worker-fetch.tentative.https.window.js | 151 ++++ .../worker-fetch.tentative.window.js | 154 ++++ .../worker.tentative.https.window.js | 37 + .../worker.tentative.window.js | 37 + ...-from-treat-as-public.tentative.https.window.js | 83 ++ .../xhr.https.tentative.window.js | 142 ++++ .../private-network-access/xhr.tentative.window.js | 195 +++++ 69 files changed, 7409 insertions(+) create mode 100644 testing/web-platform/tests/fetch/private-network-access/META.yml create mode 100644 testing/web-platform/tests/fetch/private-network-access/README.md create mode 100644 testing/web-platform/tests/fetch/private-network-access/anchor.tentative.https.window.js create mode 100644 testing/web-platform/tests/fetch/private-network-access/anchor.tentative.window.js create mode 100644 testing/web-platform/tests/fetch/private-network-access/fenced-frame-no-preflight-required.tentative.https.window.js create mode 100644 testing/web-platform/tests/fetch/private-network-access/fenced-frame-subresource-fetch.tentative.https.window.js create mode 100644 testing/web-platform/tests/fetch/private-network-access/fenced-frame.tentative.https.window.js create mode 100644 testing/web-platform/tests/fetch/private-network-access/fetch-from-treat-as-public.tentative.https.window.js create mode 100644 testing/web-platform/tests/fetch/private-network-access/fetch.tentative.https.window.js create mode 100644 testing/web-platform/tests/fetch/private-network-access/fetch.tentative.window.js create mode 100644 testing/web-platform/tests/fetch/private-network-access/iframe.tentative.https.window.js create mode 100644 testing/web-platform/tests/fetch/private-network-access/iframe.tentative.window.js create mode 100644 testing/web-platform/tests/fetch/private-network-access/mixed-content-fetch.tentative.https.window.js create mode 100644 testing/web-platform/tests/fetch/private-network-access/nested-worker.tentative.https.window.js create mode 100644 testing/web-platform/tests/fetch/private-network-access/nested-worker.tentative.window.js create mode 100644 testing/web-platform/tests/fetch/private-network-access/preflight-cache.https.tentative.window.js create mode 100644 testing/web-platform/tests/fetch/private-network-access/redirect.tentative.https.window.js create mode 100644 testing/web-platform/tests/fetch/private-network-access/resources/anchor.html create mode 100644 testing/web-platform/tests/fetch/private-network-access/resources/executor.html create mode 100644 testing/web-platform/tests/fetch/private-network-access/resources/fenced-frame-fetcher.https.html create mode 100644 testing/web-platform/tests/fetch/private-network-access/resources/fenced-frame-fetcher.https.html.headers create mode 100644 testing/web-platform/tests/fetch/private-network-access/resources/fenced-frame-private-network-access-target.https.html create mode 100644 testing/web-platform/tests/fetch/private-network-access/resources/fenced-frame-private-network-access.https.html create mode 100644 testing/web-platform/tests/fetch/private-network-access/resources/fenced-frame-private-network-access.https.html.headers create mode 100644 testing/web-platform/tests/fetch/private-network-access/resources/fetcher.html create mode 100644 testing/web-platform/tests/fetch/private-network-access/resources/fetcher.js create mode 100644 testing/web-platform/tests/fetch/private-network-access/resources/iframed-no-preflight-received.html create mode 100644 testing/web-platform/tests/fetch/private-network-access/resources/iframed.html create mode 100644 testing/web-platform/tests/fetch/private-network-access/resources/iframer.html create mode 100644 testing/web-platform/tests/fetch/private-network-access/resources/no-preflight-received.html create mode 100644 testing/web-platform/tests/fetch/private-network-access/resources/open-to-existing-window.html create mode 100644 testing/web-platform/tests/fetch/private-network-access/resources/openee.html create mode 100644 testing/web-platform/tests/fetch/private-network-access/resources/opener.html create mode 100644 testing/web-platform/tests/fetch/private-network-access/resources/preflight.py create mode 100644 testing/web-platform/tests/fetch/private-network-access/resources/service-worker-bridge.html create mode 100644 testing/web-platform/tests/fetch/private-network-access/resources/service-worker.js create mode 100644 testing/web-platform/tests/fetch/private-network-access/resources/shared-fetcher.js create mode 100644 testing/web-platform/tests/fetch/private-network-access/resources/shared-worker-blob-fetcher.html create mode 100644 testing/web-platform/tests/fetch/private-network-access/resources/shared-worker-fetcher.html create mode 100644 testing/web-platform/tests/fetch/private-network-access/resources/socket-opener.html create mode 100644 testing/web-platform/tests/fetch/private-network-access/resources/support.sub.js create mode 100644 testing/web-platform/tests/fetch/private-network-access/resources/worker-blob-fetcher.html create mode 100644 testing/web-platform/tests/fetch/private-network-access/resources/worker-fetcher.html create mode 100644 testing/web-platform/tests/fetch/private-network-access/resources/worker-fetcher.js create mode 100644 testing/web-platform/tests/fetch/private-network-access/resources/xhr-sender.html create mode 100644 testing/web-platform/tests/fetch/private-network-access/service-worker-background-fetch.tentative.https.window.js create mode 100644 testing/web-platform/tests/fetch/private-network-access/service-worker-fetch.tentative.https.window.js create mode 100644 testing/web-platform/tests/fetch/private-network-access/service-worker-update.tentative.https.window.js create mode 100644 testing/web-platform/tests/fetch/private-network-access/service-worker.tentative.https.window.js create mode 100644 testing/web-platform/tests/fetch/private-network-access/shared-worker-blob-fetch.tentative.https.window.js create mode 100644 testing/web-platform/tests/fetch/private-network-access/shared-worker-blob-fetch.tentative.window.js create mode 100644 testing/web-platform/tests/fetch/private-network-access/shared-worker-fetch.tentative.https.window.js create mode 100644 testing/web-platform/tests/fetch/private-network-access/shared-worker-fetch.tentative.window.js create mode 100644 testing/web-platform/tests/fetch/private-network-access/shared-worker.tentative.https.window.js create mode 100644 testing/web-platform/tests/fetch/private-network-access/shared-worker.tentative.window.js create mode 100644 testing/web-platform/tests/fetch/private-network-access/websocket.tentative.https.window.js create mode 100644 testing/web-platform/tests/fetch/private-network-access/websocket.tentative.window.js create mode 100644 testing/web-platform/tests/fetch/private-network-access/window-open-existing.tentative.https.window.js create mode 100644 testing/web-platform/tests/fetch/private-network-access/window-open-existing.tentative.window.js create mode 100644 testing/web-platform/tests/fetch/private-network-access/window-open.tentative.https.window.js create mode 100644 testing/web-platform/tests/fetch/private-network-access/window-open.tentative.window.js create mode 100644 testing/web-platform/tests/fetch/private-network-access/worker-blob-fetch.tentative.window.js create mode 100644 testing/web-platform/tests/fetch/private-network-access/worker-fetch.tentative.https.window.js create mode 100644 testing/web-platform/tests/fetch/private-network-access/worker-fetch.tentative.window.js create mode 100644 testing/web-platform/tests/fetch/private-network-access/worker.tentative.https.window.js create mode 100644 testing/web-platform/tests/fetch/private-network-access/worker.tentative.window.js create mode 100644 testing/web-platform/tests/fetch/private-network-access/xhr-from-treat-as-public.tentative.https.window.js create mode 100644 testing/web-platform/tests/fetch/private-network-access/xhr.https.tentative.window.js create mode 100644 testing/web-platform/tests/fetch/private-network-access/xhr.tentative.window.js (limited to 'testing/web-platform/tests/fetch/private-network-access') diff --git a/testing/web-platform/tests/fetch/private-network-access/META.yml b/testing/web-platform/tests/fetch/private-network-access/META.yml new file mode 100644 index 0000000000..944ce6f14a --- /dev/null +++ b/testing/web-platform/tests/fetch/private-network-access/META.yml @@ -0,0 +1,7 @@ +spec: https://wicg.github.io/private-network-access/ +suggested_reviewers: + - letitz + - lyf + - hemeryar + - camillelamy + - mikewest diff --git a/testing/web-platform/tests/fetch/private-network-access/README.md b/testing/web-platform/tests/fetch/private-network-access/README.md new file mode 100644 index 0000000000..a69aab4872 --- /dev/null +++ b/testing/web-platform/tests/fetch/private-network-access/README.md @@ -0,0 +1,10 @@ +# Private Network Access tests + +This directory contains tests for Private Network Access' integration with +the Fetch specification. + +See also: + +* [The specification](https://wicg.github.io/private-network-access/) +* [The repository](https://github.com/WICG/private-network-access/) +* [Open issues](https://github.com/WICG/private-network-access/issues/) diff --git a/testing/web-platform/tests/fetch/private-network-access/anchor.tentative.https.window.js b/testing/web-platform/tests/fetch/private-network-access/anchor.tentative.https.window.js new file mode 100644 index 0000000000..4e860ad381 --- /dev/null +++ b/testing/web-platform/tests/fetch/private-network-access/anchor.tentative.https.window.js @@ -0,0 +1,191 @@ +// META: script=/common/subset-tests-by-key.js +// META: script=/common/dispatcher/dispatcher.js +// META: script=/common/utils.js +// META: script=resources/support.sub.js +// META: timeout=long +// META: variant=?include=from-local +// META: variant=?include=from-private +// META: variant=?include=from-public +// META: variant=?include=from-treat-as-public +// +// These tests verify that secure contexts can navigate to less-public address +// spaces via an anchor link iff the target server responds affirmatively to +// preflight requests. + +setup(() => { + assert_true(window.isSecureContext); +}); + +// Source: secure local context. +// +// All fetches unaffected by Private Network Access. + +subsetTestByKey("from-local", promise_test_parallel, t => anchorTest(t, { + source: { server: Server.HTTPS_LOCAL }, + target: { server: Server.HTTPS_LOCAL }, + expected: NavigationTestResult.SUCCESS, +}), "local to local: no preflight required."); + +subsetTestByKey("from-local", promise_test_parallel, t => anchorTest(t, { + source: { server: Server.HTTPS_LOCAL }, + target: { server: Server.HTTPS_PRIVATE }, + expected: NavigationTestResult.SUCCESS, +}), "local to private: no preflight required."); + +subsetTestByKey("from-local", promise_test_parallel, t => anchorTest(t, { + source: { server: Server.HTTPS_LOCAL }, + target: { server: Server.HTTPS_PUBLIC }, + expected: NavigationTestResult.SUCCESS, +}), "local to public: no preflight required."); + +// Generates tests of preflight behavior for a single (source, target) pair. +// +// Scenarios: +// +// - preflight response has non-2xx HTTP code +// - preflight response is missing CORS headers +// - preflight response is missing the PNA-specific `Access-Control` header +// - success +// +function makePreflightTests({ + key, + sourceName, + sourceServer, + sourceTreatAsPublic, + targetName, + targetServer, +}) { + const prefix = + `${sourceName} to ${targetName}: `; + + const source = { + server: sourceServer, + treatAsPublic: sourceTreatAsPublic, + }; + + promise_test_parallel(t => anchorTest(t, { + source, + target: { + server: targetServer, + behavior: { preflight: PreflightBehavior.failure() }, + }, + expected: NavigationTestResult.FAILURE, + }), prefix + "failed preflight."); + + promise_test_parallel(t => anchorTest(t, { + source, + target: { + server: targetServer, + behavior: { preflight: PreflightBehavior.noCorsHeader(token()) }, + }, + expected: NavigationTestResult.FAILURE, + }), prefix + "missing CORS headers."); + + promise_test_parallel(t => anchorTest(t, { + source, + target: { + server: targetServer, + behavior: { preflight: PreflightBehavior.noPnaHeader(token()) }, + }, + expected: NavigationTestResult.FAILURE, + }), prefix + "missing PNA header."); + + promise_test_parallel(t => anchorTest(t, { + source, + target: { + server: targetServer, + behavior: { preflight: PreflightBehavior.navigation(token()) }, + }, + expected: NavigationTestResult.SUCCESS, + }), prefix + "success."); +} + +// Source: private secure context. +// +// Fetches to the local address space require a successful preflight response +// carrying a PNA-specific header. + +subsetTestByKey('from-private', makePreflightTests, { + sourceServer: Server.HTTPS_PRIVATE, + sourceName: 'private', + targetServer: Server.HTTPS_LOCAL, + targetName: 'local', +}); + +subsetTestByKey("from-private", promise_test_parallel, t => anchorTest(t, { + source: { server: Server.HTTPS_PRIVATE }, + target: { server: Server.HTTPS_PRIVATE }, + expected: NavigationTestResult.SUCCESS, +}), "private to private: no preflight required."); + +subsetTestByKey("from-private", promise_test_parallel, t => anchorTest(t, { + source: { server: Server.HTTPS_PRIVATE }, + target: { server: Server.HTTPS_PUBLIC }, + expected: NavigationTestResult.SUCCESS, +}), "private to public: no preflight required."); + +// Source: public secure context. +// +// Fetches to the local and private address spaces require a successful +// preflight response carrying a PNA-specific header. + +subsetTestByKey('from-public', makePreflightTests, { + sourceServer: Server.HTTPS_PUBLIC, + sourceName: "public", + targetServer: Server.HTTPS_LOCAL, + targetName: "local", +}); + +subsetTestByKey('from-public', makePreflightTests, { + sourceServer: Server.HTTPS_PUBLIC, + sourceName: "public", + targetServer: Server.HTTPS_PRIVATE, + targetName: "private", +}); + +subsetTestByKey("from-public", promise_test_parallel, t => anchorTest(t, { + source: { server: Server.HTTPS_PUBLIC }, + target: { server: Server.HTTPS_PUBLIC }, + expected: NavigationTestResult.SUCCESS, +}), "public to public: no preflight required."); + +// The following tests verify that `CSP: treat-as-public-address` makes +// documents behave as if they had been served from a public IP address. + +subsetTestByKey('from-treat-as-public', makePreflightTests, { + sourceServer: Server.HTTPS_LOCAL, + sourceTreatAsPublic: true, + sourceName: "treat-as-public-address", + targetServer: Server.OTHER_HTTPS_LOCAL, + targetName: "local", +}); + +subsetTestByKey("from-treat-as-public", promise_test_parallel, + t => anchorTest(t, { + source: { + server: Server.HTTPS_LOCAL, + treatAsPublic: true, + }, + target: {server: Server.HTTPS_LOCAL}, + expected: NavigationTestResult.SUCCESS, + }), + 'treat-as-public-address to local (same-origin): no preflight required.'); + +subsetTestByKey('from-treat-as-public', makePreflightTests, { + sourceServer: Server.HTTPS_LOCAL, + sourceTreatAsPublic: true, + sourceName: 'treat-as-public-address', + targetServer: Server.HTTPS_PRIVATE, + targetName: 'private', +}); + +subsetTestByKey("from-treat-as-public", promise_test_parallel, + t => anchorTest(t, { + source: { + server: Server.HTTPS_LOCAL, + treatAsPublic: true, + }, + target: {server: Server.HTTPS_PUBLIC}, + expected: NavigationTestResult.SUCCESS, + }), + 'treat-as-public-address to public: no preflight required.'); diff --git a/testing/web-platform/tests/fetch/private-network-access/anchor.tentative.window.js b/testing/web-platform/tests/fetch/private-network-access/anchor.tentative.window.js new file mode 100644 index 0000000000..cb53865808 --- /dev/null +++ b/testing/web-platform/tests/fetch/private-network-access/anchor.tentative.window.js @@ -0,0 +1,95 @@ +// META: script=/common/dispatcher/dispatcher.js +// META: script=/common/utils.js +// META: script=resources/support.sub.js +// META: timeout=long +// +// Spec: https://wicg.github.io/private-network-access/ +// +// These tests verify that non-secure contexts cannot open a new window via an +// anchor link to less-public address spaces. + +setup(() => { + // Making sure we are in a non secure context, as expected. + assert_false(window.isSecureContext); +}); + +promise_test_parallel(t => anchorTest(t, { + source: { server: Server.HTTP_LOCAL }, + target: { server: Server.HTTP_LOCAL }, + expected: NavigationTestResult.SUCCESS, +}), "local to local: no preflight required."); + +promise_test_parallel(t => anchorTest(t, { + source: { server: Server.HTTP_LOCAL }, + target: { server: Server.HTTP_PRIVATE }, + expected: NavigationTestResult.SUCCESS, +}), "local to private: no preflight required."); + +promise_test_parallel(t => anchorTest(t, { + source: { server: Server.HTTP_LOCAL }, + target: { server: Server.HTTP_PUBLIC }, + expected: NavigationTestResult.SUCCESS, +}), "local to public: no preflight required."); + +promise_test_parallel(t => anchorTest(t, { + source: { server: Server.HTTP_PRIVATE }, + target: { server: Server.HTTP_LOCAL }, + expected: NavigationTestResult.FAILURE, +}), "private to local: failure."); + +promise_test_parallel(t => anchorTest(t, { + source: { server: Server.HTTP_PRIVATE }, + target: { server: Server.HTTP_PRIVATE }, + expected: NavigationTestResult.SUCCESS, +}), "private to private: no preflight required."); + +promise_test_parallel(t => anchorTest(t, { + source: { server: Server.HTTP_PRIVATE }, + target: { server: Server.HTTP_PUBLIC }, + expected: NavigationTestResult.SUCCESS, +}), "private to public: no preflight required."); + +promise_test_parallel(t => anchorTest(t, { + source: { server: Server.HTTP_PUBLIC }, + target: { server: Server.HTTP_LOCAL }, + expected: NavigationTestResult.FAILURE, +}), "public to local: failure."); + +promise_test_parallel(t => anchorTest(t, { + source: { server: Server.HTTP_PUBLIC }, + target: { server: Server.HTTP_PRIVATE }, + expected: NavigationTestResult.FAILURE, +}), "public to private: failure."); + +promise_test_parallel(t => anchorTest(t, { + source: { server: Server.HTTP_PUBLIC }, + target: { server: Server.HTTP_PUBLIC }, + expected: NavigationTestResult.SUCCESS, +}), "public to public: no preflight required."); + +promise_test_parallel(t => anchorTest(t, { + source: { + server: Server.HTTP_LOCAL, + treatAsPublic: true, + }, + target: { server: Server.HTTP_LOCAL }, + expected: NavigationTestResult.FAILURE, +}), "treat-as-public-address to local: failure."); + +promise_test_parallel(t => anchorTest(t, { + source: { + server: Server.HTTP_LOCAL, + treatAsPublic: true, + }, + target: { server: Server.HTTP_PRIVATE }, + expected: NavigationTestResult.FAILURE, +}), "treat-as-public-address to private: failure."); + +promise_test_parallel(t => anchorTest(t, { + source: { + server: Server.HTTP_LOCAL, + treatAsPublic: true, + }, + target: { server: Server.HTTP_PUBLIC }, + expected: NavigationTestResult.SUCCESS, +}), "treat-as-public-address to public: no preflight required."); diff --git a/testing/web-platform/tests/fetch/private-network-access/fenced-frame-no-preflight-required.tentative.https.window.js b/testing/web-platform/tests/fetch/private-network-access/fenced-frame-no-preflight-required.tentative.https.window.js new file mode 100644 index 0000000000..33e94d57f1 --- /dev/null +++ b/testing/web-platform/tests/fetch/private-network-access/fenced-frame-no-preflight-required.tentative.https.window.js @@ -0,0 +1,91 @@ +// META: script=/common/dispatcher/dispatcher.js +// META: script=/common/utils.js +// META: script=resources/support.sub.js +// META: script=/fenced-frame/resources/utils.js +// META: timeout=long +// +// Spec: https://wicg.github.io/private-network-access/#integration-fetch +// +// These tests verify that contexts can navigate fenced frames to more-public or +// same address spaces without private network access preflight request header. + +setup(() => { + assert_true(window.isSecureContext); +}); + +// Source: secure local context. +// +// All fetches unaffected by Private Network Access. + +promise_test( + t => fencedFrameTest(t, { + source: {server: Server.HTTPS_LOCAL}, + target: {server: Server.HTTPS_LOCAL}, + expected: FrameTestResult.SUCCESS, + }), + 'local to local: no preflight required.'); + +promise_test( + t => fencedFrameTest(t, { + source: {server: Server.HTTPS_LOCAL}, + target: {server: Server.HTTPS_PRIVATE}, + expected: FrameTestResult.SUCCESS, + }), + 'local to private: no preflight required.'); + +promise_test( + t => fencedFrameTest(t, { + source: {server: Server.HTTPS_LOCAL}, + target: {server: Server.HTTPS_PUBLIC}, + expected: FrameTestResult.SUCCESS, + }), + 'local to public: no preflight required.'); + +promise_test( + t => fencedFrameTest(t, { + source: {server: Server.HTTPS_PRIVATE}, + target: {server: Server.HTTPS_PRIVATE}, + expected: FrameTestResult.SUCCESS, + }), + 'private to private: no preflight required.'); + +promise_test( + t => fencedFrameTest(t, { + source: {server: Server.HTTPS_PRIVATE}, + target: {server: Server.HTTPS_PUBLIC}, + expected: FrameTestResult.SUCCESS, + }), + 'private to public: no preflight required.'); + +promise_test( + t => fencedFrameTest(t, { + source: {server: Server.HTTPS_PUBLIC}, + target: {server: Server.HTTPS_PUBLIC}, + expected: FrameTestResult.SUCCESS, + }), + 'public to public: no preflight required.'); + +promise_test( + t => fencedFrameTest(t, { + source: { + server: Server.HTTPS_LOCAL, + treatAsPublic: true, + }, + target: {server: Server.HTTPS_PUBLIC}, + expected: FrameTestResult.SUCCESS, + }), + 'treat-as-public-address to public: no preflight required.'); + +promise_test( + t => fencedFrameTest(t, { + source: { + server: Server.HTTPS_LOCAL, + treatAsPublic: true, + }, + target: { + server: Server.HTTPS_PUBLIC, + behavior: {preflight: PreflightBehavior.optionalSuccess(token())} + }, + expected: FrameTestResult.SUCCESS, + }), + 'treat-as-public-address to local: optional preflight'); diff --git a/testing/web-platform/tests/fetch/private-network-access/fenced-frame-subresource-fetch.tentative.https.window.js b/testing/web-platform/tests/fetch/private-network-access/fenced-frame-subresource-fetch.tentative.https.window.js new file mode 100644 index 0000000000..2dff325e3e --- /dev/null +++ b/testing/web-platform/tests/fetch/private-network-access/fenced-frame-subresource-fetch.tentative.https.window.js @@ -0,0 +1,330 @@ +// META: script=/common/subset-tests-by-key.js +// META: script=/common/utils.js +// META: script=resources/support.sub.js +// META: script=/fenced-frame/resources/utils.js +// META: variant=?include=baseline +// META: variant=?include=from-local +// META: variant=?include=from-private +// META: variant=?include=from-public +// META: timeout=long +// +// Spec: https://wicg.github.io/private-network-access/#integration-fetch +// +// These tests verify that secure contexts can fetch subresources in fenced +// frames from all address spaces, provided that the target server, if more +// private than the initiator, respond affirmatively to preflight requests. +// + +setup(() => { + // Making sure we are in a secure context, as expected. + assert_true(window.isSecureContext); +}); + +// Source: secure local context. +// +// All fetches unaffected by Private Network Access. + +subsetTestByKey( + 'from-local', promise_test, t => fencedFrameFetchTest(t, { + source: {server: Server.HTTPS_LOCAL}, + target: {server: Server.HTTPS_LOCAL}, + fetchOptions: {method: 'GET', mode: 'cors'}, + expected: FetchTestResult.SUCCESS, + }), + 'local to local: no preflight required.'); + +subsetTestByKey( + 'from-local', promise_test, + t => fencedFrameFetchTest(t, { + source: {server: Server.HTTPS_LOCAL}, + target: { + server: Server.HTTPS_PRIVATE, + behavior: {response: ResponseBehavior.allowCrossOrigin()}, + }, + fetchOptions: {method: 'GET', mode: 'cors'}, + expected: FetchTestResult.SUCCESS, + }), + 'local to private: no preflight required.'); + + +subsetTestByKey( + 'from-local', promise_test, + t => fencedFrameFetchTest(t, { + source: {server: Server.HTTPS_LOCAL}, + target: { + server: Server.HTTPS_PUBLIC, + behavior: {response: ResponseBehavior.allowCrossOrigin()}, + }, + fetchOptions: {method: 'GET', mode: 'cors'}, + expected: FetchTestResult.SUCCESS, + }), + 'local to public: no preflight required.'); + +// Strictly speaking, the following two tests do not exercise PNA-specific +// logic, but they serve as a baseline for comparison, ensuring that non-PNA +// preflight requests are sent and handled as expected. + +subsetTestByKey( + 'baseline', promise_test, + t => fencedFrameFetchTest(t, { + source: {server: Server.HTTPS_LOCAL}, + target: { + server: Server.HTTPS_PUBLIC, + behavior: { + preflight: PreflightBehavior.failure(), + response: ResponseBehavior.allowCrossOrigin(), + }, + }, + fetchOptions: {method: 'PUT', mode: 'cors'}, + expected: FetchTestResult.FAILURE, + }), + 'local to public: PUT preflight failure.'); + +subsetTestByKey( + 'baseline', promise_test, + t => fencedFrameFetchTest(t, { + source: {server: Server.HTTPS_LOCAL}, + target: { + server: Server.HTTPS_PUBLIC, + behavior: { + preflight: PreflightBehavior.success(token()), + response: ResponseBehavior.allowCrossOrigin(), + } + }, + fetchOptions: {method: 'PUT', mode: 'cors'}, + expected: FetchTestResult.SUCCESS, + }), + 'local to public: PUT preflight success.'); + +// Generates tests of preflight behavior for a single (source, target) pair. +// +// Scenarios: +// +// - cors mode: +// - preflight response has non-2xx HTTP code +// - preflight response is missing CORS headers +// - preflight response is missing the PNA-specific `Access-Control` header +// - final response is missing CORS headers +// - success +// - success with PUT method (non-"simple" request) +// - no-cors mode: +// - preflight response has non-2xx HTTP code +// - preflight response is missing CORS headers +// - preflight response is missing the PNA-specific `Access-Control` header +// - success +// +function makePreflightTests({ + subsetKey, + source, + sourceDescription, + targetServer, + targetDescription, +}) { + const prefix = `${sourceDescription} to ${targetDescription}: `; + + subsetTestByKey( + subsetKey, promise_test, + t => fencedFrameFetchTest(t, { + source, + target: { + server: targetServer, + behavior: { + preflight: PreflightBehavior.failure(), + response: ResponseBehavior.allowCrossOrigin(), + }, + }, + fetchOptions: {method: 'GET', mode: 'cors'}, + expected: FetchTestResult.FAILURE, + }), + prefix + 'failed preflight.'); + + subsetTestByKey( + subsetKey, promise_test, + t => fencedFrameFetchTest(t, { + source, + target: { + server: targetServer, + behavior: { + preflight: PreflightBehavior.noCorsHeader(token()), + response: ResponseBehavior.allowCrossOrigin(), + }, + }, + fetchOptions: {method: 'GET', mode: 'cors'}, + expected: FetchTestResult.FAILURE, + }), + prefix + 'missing CORS headers on preflight response.'); + + subsetTestByKey( + subsetKey, promise_test, + t => fencedFrameFetchTest(t, { + source, + target: { + server: targetServer, + behavior: { + preflight: PreflightBehavior.noPnaHeader(token()), + response: ResponseBehavior.allowCrossOrigin(), + }, + }, + fetchOptions: {method: 'GET', mode: 'cors'}, + expected: FetchTestResult.FAILURE, + }), + prefix + 'missing PNA header on preflight response.'); + + subsetTestByKey( + subsetKey, promise_test, + t => fencedFrameFetchTest(t, { + source, + target: { + server: targetServer, + behavior: {preflight: PreflightBehavior.success(token())}, + }, + fetchOptions: {method: 'GET', mode: 'cors'}, + expected: FetchTestResult.FAILURE, + }), + prefix + 'missing CORS headers on final response.'); + + subsetTestByKey( + subsetKey, promise_test, + t => fencedFrameFetchTest(t, { + source, + target: { + server: targetServer, + behavior: { + preflight: PreflightBehavior.success(token()), + response: ResponseBehavior.allowCrossOrigin(), + }, + }, + fetchOptions: {method: 'GET', mode: 'cors'}, + expected: FetchTestResult.SUCCESS, + }), + prefix + 'success.'); + + subsetTestByKey( + subsetKey, promise_test, + t => fencedFrameFetchTest(t, { + source, + target: { + server: targetServer, + behavior: { + preflight: PreflightBehavior.success(token()), + response: ResponseBehavior.allowCrossOrigin(), + }, + }, + fetchOptions: {method: 'PUT', mode: 'cors'}, + expected: FetchTestResult.SUCCESS, + }), + prefix + 'PUT success.'); + + subsetTestByKey( + subsetKey, promise_test, t => fencedFrameFetchTest(t, { + source, + target: {server: targetServer}, + fetchOptions: {method: 'GET', mode: 'no-cors'}, + expected: FetchTestResult.FAILURE, + }), + prefix + 'no-CORS mode failed preflight.'); + + subsetTestByKey( + subsetKey, promise_test, + t => fencedFrameFetchTest(t, { + source, + target: { + server: targetServer, + behavior: {preflight: PreflightBehavior.noCorsHeader(token())}, + }, + fetchOptions: {method: 'GET', mode: 'no-cors'}, + expected: FetchTestResult.FAILURE, + }), + prefix + 'no-CORS mode missing CORS headers on preflight response.'); + + subsetTestByKey( + subsetKey, promise_test, + t => fencedFrameFetchTest(t, { + source, + target: { + server: targetServer, + behavior: {preflight: PreflightBehavior.noPnaHeader(token())}, + }, + fetchOptions: {method: 'GET', mode: 'no-cors'}, + expected: FetchTestResult.FAILURE, + }), + prefix + 'no-CORS mode missing PNA header on preflight response.'); + + subsetTestByKey( + subsetKey, promise_test, + t => fencedFrameFetchTest(t, { + source, + target: { + server: targetServer, + behavior: {preflight: PreflightBehavior.success(token())}, + }, + fetchOptions: {method: 'GET', mode: 'no-cors'}, + expected: FetchTestResult.OPAQUE, + }), + prefix + 'no-CORS mode success.'); +} + +// Source: private secure context. +// +// Fetches to the local address space require a successful preflight response +// carrying a PNA-specific header. + +makePreflightTests({ + subsetKey: 'from-private', + source: {server: Server.HTTPS_PRIVATE}, + sourceDescription: 'private', + targetServer: Server.HTTPS_LOCAL, + targetDescription: 'local', +}); + +subsetTestByKey( + 'from-private', promise_test, t => fencedFrameFetchTest(t, { + source: {server: Server.HTTPS_PRIVATE}, + target: {server: Server.HTTPS_PRIVATE}, + fetchOptions: {method: 'GET', mode: 'cors'}, + expected: FetchTestResult.SUCCESS, + }), + 'private to private: no preflight required.'); + +subsetTestByKey( + 'from-private', promise_test, + t => fencedFrameFetchTest(t, { + source: {server: Server.HTTPS_PRIVATE}, + target: { + server: Server.HTTPS_PRIVATE, + behavior: {response: ResponseBehavior.allowCrossOrigin()}, + }, + fetchOptions: {method: 'GET', mode: 'cors'}, + expected: FetchTestResult.SUCCESS, + }), + 'private to public: no preflight required.'); + +// Source: public secure context. +// +// Fetches to the local and private address spaces require a successful +// preflight response carrying a PNA-specific header. + +makePreflightTests({ + subsetKey: 'from-public', + source: {server: Server.HTTPS_PUBLIC}, + sourceDescription: 'public', + targetServer: Server.HTTPS_LOCAL, + targetDescription: 'local', +}); + +makePreflightTests({ + subsetKey: 'from-public', + source: {server: Server.HTTPS_PUBLIC}, + sourceDescription: 'public', + targetServer: Server.HTTPS_PRIVATE, + targetDescription: 'private', +}); + +subsetTestByKey( + 'from-public', promise_test, t => fencedFrameFetchTest(t, { + source: {server: Server.HTTPS_PUBLIC}, + target: {server: Server.HTTPS_PUBLIC}, + fetchOptions: {method: 'GET', mode: 'cors'}, + expected: FetchTestResult.SUCCESS, + }), + 'public to public: no preflight required.'); diff --git a/testing/web-platform/tests/fetch/private-network-access/fenced-frame.tentative.https.window.js b/testing/web-platform/tests/fetch/private-network-access/fenced-frame.tentative.https.window.js new file mode 100644 index 0000000000..370cc9fbe9 --- /dev/null +++ b/testing/web-platform/tests/fetch/private-network-access/fenced-frame.tentative.https.window.js @@ -0,0 +1,150 @@ +// META: script=/common/dispatcher/dispatcher.js +// META: script=/common/utils.js +// META: script=resources/support.sub.js +// META: script=/fenced-frame/resources/utils.js +// META: timeout=long +// +// Spec: https://wicg.github.io/private-network-access/#integration-fetch +// +// These tests verify that contexts can navigate fenced frames to less-public +// address spaces iff the target server responds affirmatively to preflight +// requests. + +setup(() => { + assert_true(window.isSecureContext); +}); + +// Generates tests of preflight behavior for a single (source, target) pair. +// +// Scenarios: +// +// - parent navigates child: +// - preflight response has non-2xx HTTP code +// - preflight response is missing CORS headers +// - preflight response is missing the PNA-specific `Access-Control` header +// - preflight response has the required PNA related headers, but still fails +// because of the limitation of fenced frame that subjects to PNA checks. +// +function makePreflightTests({ + sourceName, + sourceServer, + sourceTreatAsPublic, + targetName, + targetServer, +}) { + const prefix = `${sourceName} to ${targetName}: `; + + const source = { + server: sourceServer, + treatAsPublic: sourceTreatAsPublic, + }; + + promise_test_parallel( + t => fencedFrameTest(t, { + source, + target: { + server: targetServer, + behavior: {preflight: PreflightBehavior.failure()}, + }, + expected: FrameTestResult.FAILURE, + }), + prefix + 'failed preflight.'); + + promise_test_parallel( + t => fencedFrameTest(t, { + source, + target: { + server: targetServer, + behavior: {preflight: PreflightBehavior.noCorsHeader(token())}, + }, + expected: FrameTestResult.FAILURE, + }), + prefix + 'missing CORS headers.'); + + promise_test_parallel( + t => fencedFrameTest(t, { + source, + target: { + server: targetServer, + behavior: {preflight: PreflightBehavior.noPnaHeader(token())}, + }, + expected: FrameTestResult.FAILURE, + }), + prefix + 'missing PNA header.'); + + promise_test_parallel( + t => fencedFrameTest(t, { + source, + target: { + server: targetServer, + behavior: { + preflight: PreflightBehavior.success(token()), + response: ResponseBehavior.allowCrossOrigin() + }, + }, + expected: FrameTestResult.FAILURE, + }), + prefix + 'failed because fenced frames are incompatible with PNA.'); +} + +// Source: private secure context. +// +// Fetches to the local address space require a successful preflight response +// carrying a PNA-specific header. + +makePreflightTests({ + sourceServer: Server.HTTPS_PRIVATE, + sourceName: 'private', + targetServer: Server.HTTPS_LOCAL, + targetName: 'local', +}); + +// Source: public secure context. +// +// Fetches to the local and private address spaces require a successful +// preflight response carrying a PNA-specific header. + +makePreflightTests({ + sourceServer: Server.HTTPS_PUBLIC, + sourceName: 'public', + targetServer: Server.HTTPS_LOCAL, + targetName: 'local', +}); + +makePreflightTests({ + sourceServer: Server.HTTPS_PUBLIC, + sourceName: 'public', + targetServer: Server.HTTPS_PRIVATE, + targetName: 'private', +}); + +// The following tests verify that `CSP: treat-as-public-address` makes +// documents behave as if they had been served from a public IP address. + +makePreflightTests({ + sourceServer: Server.HTTPS_LOCAL, + sourceTreatAsPublic: true, + sourceName: 'treat-as-public-address', + targetServer: Server.OTHER_HTTPS_LOCAL, + targetName: 'local', +}); + +promise_test_parallel( + t => fencedFrameTest(t, { + source: { + server: Server.HTTPS_LOCAL, + treatAsPublic: true, + }, + target: {server: Server.HTTPS_LOCAL}, + expected: FrameTestResult.FAILURE, + }), + 'treat-as-public-address to local (same-origin): fenced frame embedder ' + + 'initiated navigation has opaque origin.'); + +makePreflightTests({ + sourceServer: Server.HTTPS_LOCAL, + sourceTreatAsPublic: true, + sourceName: 'treat-as-public-address', + targetServer: Server.HTTPS_PRIVATE, + targetName: 'private', +}); diff --git a/testing/web-platform/tests/fetch/private-network-access/fetch-from-treat-as-public.tentative.https.window.js b/testing/web-platform/tests/fetch/private-network-access/fetch-from-treat-as-public.tentative.https.window.js new file mode 100644 index 0000000000..084e03282f --- /dev/null +++ b/testing/web-platform/tests/fetch/private-network-access/fetch-from-treat-as-public.tentative.https.window.js @@ -0,0 +1,80 @@ +// META: script=/common/utils.js +// META: script=resources/support.sub.js +// +// Spec: https://wicg.github.io/private-network-access/#integration-fetch +// +// These tests verify that documents fetched from the `local` or `private` +// address space yet carrying the `treat-as-public-address` CSP directive are +// treated as if they had been fetched from the `public` address space. + +promise_test(t => fetchTest(t, { + source: { + server: Server.HTTPS_LOCAL, + treatAsPublic: true, + }, + target: { + server: Server.OTHER_HTTPS_LOCAL, + preflight: PreflightBehavior.noPnaHeader(token()), + }, + expected: FetchTestResult.FAILURE, +}), "treat-as-public-address to local: failed preflight."); + +promise_test(t => fetchTest(t, { + source: { + server: Server.HTTPS_LOCAL, + treatAsPublic: true, + }, + target: { + server: Server.OTHER_HTTPS_LOCAL, + behavior: { + preflight: PreflightBehavior.success(token()), + response: ResponseBehavior.allowCrossOrigin(), + }, + }, + expected: FetchTestResult.SUCCESS, +}), "treat-as-public-address to local: success."); + +promise_test(t => fetchTest(t, { + source: { + server: Server.HTTPS_LOCAL, + treatAsPublic: true, + }, + target: { server: Server.HTTPS_LOCAL }, + expected: FetchTestResult.SUCCESS, +}), "treat-as-public-address to local (same-origin): no preflight required."); + +promise_test(t => fetchTest(t, { + source: { + server: Server.HTTPS_LOCAL, + treatAsPublic: true, + }, + target: { server: Server.HTTPS_PRIVATE }, + expected: FetchTestResult.FAILURE, +}), "treat-as-public-address to private: failed preflight."); + +promise_test(t => fetchTest(t, { + source: { + server: Server.HTTPS_LOCAL, + treatAsPublic: true, + }, + target: { + server: Server.HTTPS_PRIVATE, + behavior: { + preflight: PreflightBehavior.optionalSuccess(token()), + response: ResponseBehavior.allowCrossOrigin(), + }, + }, + expected: FetchTestResult.SUCCESS, +}), "treat-as-public-address to private: success."); + +promise_test(t => fetchTest(t, { + source: { + server: Server.HTTPS_LOCAL, + treatAsPublic: true, + }, + target: { + server: Server.HTTPS_PUBLIC, + behavior: { response: ResponseBehavior.allowCrossOrigin() }, + }, + expected: FetchTestResult.SUCCESS, +}), "treat-as-public-address to public: no preflight required."); diff --git a/testing/web-platform/tests/fetch/private-network-access/fetch.tentative.https.window.js b/testing/web-platform/tests/fetch/private-network-access/fetch.tentative.https.window.js new file mode 100644 index 0000000000..606443dc14 --- /dev/null +++ b/testing/web-platform/tests/fetch/private-network-access/fetch.tentative.https.window.js @@ -0,0 +1,271 @@ +// META: script=/common/subset-tests-by-key.js +// META: script=/common/utils.js +// META: script=resources/support.sub.js +// META: variant=?include=baseline +// META: variant=?include=from-local +// META: variant=?include=from-private +// META: variant=?include=from-public +// +// Spec: https://wicg.github.io/private-network-access/#integration-fetch +// +// These tests verify that secure contexts can fetch subresources from all +// address spaces, provided that the target server, if more private than the +// initiator, respond affirmatively to preflight requests. +// +// This file covers only those tests that must execute in a secure context. +// Other tests are defined in: fetch.window.js + +setup(() => { + // Making sure we are in a secure context, as expected. + assert_true(window.isSecureContext); +}); + +// Source: secure local context. +// +// All fetches unaffected by Private Network Access. + +subsetTestByKey("from-local", promise_test, t => fetchTest(t, { + source: { server: Server.HTTPS_LOCAL }, + target: { server: Server.HTTPS_LOCAL }, + expected: FetchTestResult.SUCCESS, +}), "local to local: no preflight required."); + +subsetTestByKey("from-local", promise_test, t => fetchTest(t, { + source: { server: Server.HTTPS_LOCAL }, + target: { + server: Server.HTTPS_PRIVATE, + behavior: { response: ResponseBehavior.allowCrossOrigin() }, + }, + expected: FetchTestResult.SUCCESS, +}), "local to private: no preflight required."); + + +subsetTestByKey("from-local", promise_test, t => fetchTest(t, { + source: { server: Server.HTTPS_LOCAL }, + target: { + server: Server.HTTPS_PUBLIC, + behavior: { response: ResponseBehavior.allowCrossOrigin() }, + }, + expected: FetchTestResult.SUCCESS, +}), "local to public: no preflight required."); + +// Strictly speaking, the following two tests do not exercise PNA-specific +// logic, but they serve as a baseline for comparison, ensuring that non-PNA +// preflight requests are sent and handled as expected. + +subsetTestByKey("baseline", promise_test, t => fetchTest(t, { + source: { server: Server.HTTPS_LOCAL }, + target: { + server: Server.HTTPS_PUBLIC, + behavior: { + preflight: PreflightBehavior.failure(), + response: ResponseBehavior.allowCrossOrigin(), + }, + }, + fetchOptions: { method: "PUT" }, + expected: FetchTestResult.FAILURE, +}), "local to public: PUT preflight failure."); + +subsetTestByKey("baseline", promise_test, t => fetchTest(t, { + source: { server: Server.HTTPS_LOCAL }, + target: { + server: Server.HTTPS_PUBLIC, + behavior: { + preflight: PreflightBehavior.success(token()), + response: ResponseBehavior.allowCrossOrigin(), + } + }, + fetchOptions: { method: "PUT" }, + expected: FetchTestResult.SUCCESS, +}), "local to public: PUT preflight success."); + +// Generates tests of preflight behavior for a single (source, target) pair. +// +// Scenarios: +// +// - cors mode: +// - preflight response has non-2xx HTTP code +// - preflight response is missing CORS headers +// - preflight response is missing the PNA-specific `Access-Control` header +// - final response is missing CORS headers +// - success +// - success with PUT method (non-"simple" request) +// - no-cors mode: +// - preflight response has non-2xx HTTP code +// - preflight response is missing CORS headers +// - preflight response is missing the PNA-specific `Access-Control` header +// - success +// +function makePreflightTests({ + subsetKey, + source, + sourceDescription, + targetServer, + targetDescription, +}) { + const prefix = + `${sourceDescription} to ${targetDescription}: `; + + subsetTestByKey(subsetKey, promise_test, t => fetchTest(t, { + source, + target: { + server: targetServer, + behavior: { + preflight: PreflightBehavior.failure(), + response: ResponseBehavior.allowCrossOrigin(), + }, + }, + expected: FetchTestResult.FAILURE, + }), prefix + "failed preflight."); + + subsetTestByKey(subsetKey, promise_test, t => fetchTest(t, { + source, + target: { + server: targetServer, + behavior: { + preflight: PreflightBehavior.noCorsHeader(token()), + response: ResponseBehavior.allowCrossOrigin(), + }, + }, + expected: FetchTestResult.FAILURE, + }), prefix + "missing CORS headers on preflight response."); + + subsetTestByKey(subsetKey, promise_test, t => fetchTest(t, { + source, + target: { + server: targetServer, + behavior: { + preflight: PreflightBehavior.noPnaHeader(token()), + response: ResponseBehavior.allowCrossOrigin(), + }, + }, + expected: FetchTestResult.FAILURE, + }), prefix + "missing PNA header on preflight response."); + + subsetTestByKey(subsetKey, promise_test, t => fetchTest(t, { + source, + target: { + server: targetServer, + behavior: { preflight: PreflightBehavior.success(token()) }, + }, + expected: FetchTestResult.FAILURE, + }), prefix + "missing CORS headers on final response."); + + subsetTestByKey(subsetKey, promise_test, t => fetchTest(t, { + source, + target: { + server: targetServer, + behavior: { + preflight: PreflightBehavior.success(token()), + response: ResponseBehavior.allowCrossOrigin(), + }, + }, + expected: FetchTestResult.SUCCESS, + }), prefix + "success."); + + subsetTestByKey(subsetKey, promise_test, t => fetchTest(t, { + source, + target: { + server: targetServer, + behavior: { + preflight: PreflightBehavior.success(token()), + response: ResponseBehavior.allowCrossOrigin(), + }, + }, + fetchOptions: { method: "PUT" }, + expected: FetchTestResult.SUCCESS, + }), prefix + "PUT success."); + + subsetTestByKey(subsetKey, promise_test, t => fetchTest(t, { + source, + target: { server: targetServer }, + fetchOptions: { mode: "no-cors" }, + expected: FetchTestResult.FAILURE, + }), prefix + "no-CORS mode failed preflight."); + + subsetTestByKey(subsetKey, promise_test, t => fetchTest(t, { + source, + target: { + server: targetServer, + behavior: { preflight: PreflightBehavior.noCorsHeader(token()) }, + }, + fetchOptions: { mode: "no-cors" }, + expected: FetchTestResult.FAILURE, + }), prefix + "no-CORS mode missing CORS headers on preflight response."); + + subsetTestByKey(subsetKey, promise_test, t => fetchTest(t, { + source, + target: { + server: targetServer, + behavior: { preflight: PreflightBehavior.noPnaHeader(token()) }, + }, + fetchOptions: { mode: "no-cors" }, + expected: FetchTestResult.FAILURE, + }), prefix + "no-CORS mode missing PNA header on preflight response."); + + subsetTestByKey(subsetKey, promise_test, t => fetchTest(t, { + source, + target: { + server: targetServer, + behavior: { preflight: PreflightBehavior.success(token()) }, + }, + fetchOptions: { mode: "no-cors" }, + expected: FetchTestResult.OPAQUE, + }), prefix + "no-CORS mode success."); +} + +// Source: private secure context. +// +// Fetches to the local address space require a successful preflight response +// carrying a PNA-specific header. + +makePreflightTests({ + subsetKey: "from-private", + source: { server: Server.HTTPS_PRIVATE }, + sourceDescription: "private", + targetServer: Server.HTTPS_LOCAL, + targetDescription: "local", +}); + +subsetTestByKey("from-private", promise_test, t => fetchTest(t, { + source: { server: Server.HTTPS_PRIVATE }, + target: { server: Server.HTTPS_PRIVATE }, + expected: FetchTestResult.SUCCESS, +}), "private to private: no preflight required."); + +subsetTestByKey("from-private", promise_test, t => fetchTest(t, { + source: { server: Server.HTTPS_PRIVATE }, + target: { + server: Server.HTTPS_PUBLIC, + behavior: { response: ResponseBehavior.allowCrossOrigin() }, + }, + expected: FetchTestResult.SUCCESS, +}), "private to public: no preflight required."); + +// Source: public secure context. +// +// Fetches to the local and private address spaces require a successful +// preflight response carrying a PNA-specific header. + +makePreflightTests({ + subsetKey: "from-public", + source: { server: Server.HTTPS_PUBLIC }, + sourceDescription: "public", + targetServer: Server.HTTPS_LOCAL, + targetDescription: "local", +}); + +makePreflightTests({ + subsetKey: "from-public", + source: { server: Server.HTTPS_PUBLIC }, + sourceDescription: "public", + targetServer: Server.HTTPS_PRIVATE, + targetDescription: "private", +}); + +subsetTestByKey("from-public", promise_test, t => fetchTest(t, { + source: { server: Server.HTTPS_PUBLIC }, + target: { server: Server.HTTPS_PUBLIC }, + expected: FetchTestResult.SUCCESS, +}), "public to public: no preflight required."); + diff --git a/testing/web-platform/tests/fetch/private-network-access/fetch.tentative.window.js b/testing/web-platform/tests/fetch/private-network-access/fetch.tentative.window.js new file mode 100644 index 0000000000..8ee54c9056 --- /dev/null +++ b/testing/web-platform/tests/fetch/private-network-access/fetch.tentative.window.js @@ -0,0 +1,183 @@ +// META: script=/common/utils.js +// META: script=resources/support.sub.js +// +// Spec: https://wicg.github.io/private-network-access/#integration-fetch +// +// These tests verify that non-secure contexts cannot fetch subresources from +// less-public address spaces, and can fetch them otherwise. +// +// This file covers only those tests that must execute in a non secure context. +// Other tests are defined in: fetch.https.window.js + +setup(() => { + // Making sure we are in a non secure context, as expected. + assert_false(window.isSecureContext); +}); + +promise_test(t => fetchTest(t, { + source: { server: Server.HTTP_LOCAL }, + target: { server: Server.HTTP_LOCAL }, + expected: FetchTestResult.SUCCESS, +}), "local to local: no preflight required."); + +promise_test(t => fetchTest(t, { + source: { server: Server.HTTP_LOCAL }, + target: { + server: Server.HTTP_PRIVATE, + behavior: { response: ResponseBehavior.allowCrossOrigin() }, + }, + expected: FetchTestResult.SUCCESS, +}), "local to private: no preflight required."); + +promise_test(t => fetchTest(t, { + source: { server: Server.HTTP_LOCAL }, + target: { + server: Server.HTTP_PUBLIC, + behavior: { response: ResponseBehavior.allowCrossOrigin() }, + }, + expected: FetchTestResult.SUCCESS, +}), "local to public: no preflight required."); + +promise_test(t => fetchTest(t, { + source: { server: Server.HTTP_PRIVATE }, + target: { + server: Server.HTTP_LOCAL, + behavior: { + preflight: PreflightBehavior.optionalSuccess(token()), + response: ResponseBehavior.allowCrossOrigin(), + }, + }, + expected: FetchTestResult.FAILURE, +}), "private to local: failure."); + +promise_test(t => fetchTest(t, { + source: { server: Server.HTTP_PRIVATE }, + target: { server: Server.HTTP_PRIVATE }, + expected: FetchTestResult.SUCCESS, +}), "private to private: no preflight required."); + +promise_test(t => fetchTest(t, { + source: { server: Server.HTTP_PRIVATE }, + target: { + server: Server.HTTP_PUBLIC, + behavior: { response: ResponseBehavior.allowCrossOrigin() }, + }, + expected: FetchTestResult.SUCCESS, +}), "private to public: no preflight required."); + +promise_test(t => fetchTest(t, { + source: { server: Server.HTTP_PUBLIC }, + target: { + server: Server.HTTP_LOCAL, + behavior: { + preflight: PreflightBehavior.optionalSuccess(token()), + response: ResponseBehavior.allowCrossOrigin(), + }, + }, + expected: FetchTestResult.FAILURE, +}), "public to local: failure."); + +promise_test(t => fetchTest(t, { + source: { server: Server.HTTP_PUBLIC }, + target: { + server: Server.HTTP_PRIVATE, + behavior: { + preflight: PreflightBehavior.optionalSuccess(token()), + response: ResponseBehavior.allowCrossOrigin(), + }, + }, + expected: FetchTestResult.FAILURE, +}), "public to private: failure."); + +promise_test(t => fetchTest(t, { + source: { server: Server.HTTP_PUBLIC }, + target: { server: Server.HTTP_PUBLIC }, + expected: FetchTestResult.SUCCESS, +}), "public to public: no preflight required."); + +// These tests verify that documents fetched from the `local` address space yet +// carrying the `treat-as-public-address` CSP directive are treated as if they +// had been fetched from the `public` address space. + +promise_test(t => fetchTest(t, { + source: { + server: Server.HTTP_LOCAL, + treatAsPublic: true, + }, + target: { + server: Server.HTTP_LOCAL, + behavior: { + preflight: PreflightBehavior.success(token()), + response: ResponseBehavior.allowCrossOrigin(), + }, + }, + expected: FetchTestResult.FAILURE, +}), "treat-as-public-address to local: failure."); + +promise_test(t => fetchTest(t, { + source: { + server: Server.HTTP_LOCAL, + treatAsPublic: true, + }, + target: { + server: Server.HTTP_PRIVATE, + behavior: { + preflight: PreflightBehavior.success(token()), + response: ResponseBehavior.allowCrossOrigin(), + }, + }, + expected: FetchTestResult.FAILURE, +}), "treat-as-public-address to private: failure."); + +promise_test(t => fetchTest(t, { + source: { + server: Server.HTTP_LOCAL, + treatAsPublic: true, + }, + target: { + server: Server.HTTP_PUBLIC, + behavior: { response: ResponseBehavior.allowCrossOrigin() }, + }, + expected: FetchTestResult.SUCCESS, +}), "treat-as-public-address to public: no preflight required."); + +// These tests verify that HTTPS iframes embedded in an HTTP top-level document +// cannot fetch subresources from less-public address spaces. Indeed, even +// though the iframes have HTTPS origins, they are non-secure contexts because +// their parent is a non-secure context. + +promise_test(t => fetchTest(t, { + source: { server: Server.HTTPS_PRIVATE }, + target: { + server: Server.HTTPS_LOCAL, + behavior: { + preflight: PreflightBehavior.success(token()), + response: ResponseBehavior.allowCrossOrigin(), + }, + }, + expected: FetchTestResult.FAILURE, +}), "private https to local: failure."); + +promise_test(t => fetchTest(t, { + source: { server: Server.HTTPS_PUBLIC }, + target: { + server: Server.HTTPS_LOCAL, + behavior: { + preflight: PreflightBehavior.success(token()), + response: ResponseBehavior.allowCrossOrigin(), + }, + }, + expected: FetchTestResult.FAILURE, +}), "public https to local: failure."); + +promise_test(t => fetchTest(t, { + source: { server: Server.HTTPS_PUBLIC }, + target: { + server: Server.HTTPS_PRIVATE, + behavior: { + preflight: PreflightBehavior.success(token()), + response: ResponseBehavior.allowCrossOrigin(), + }, + }, + expected: FetchTestResult.FAILURE, +}), "public https to private: failure."); diff --git a/testing/web-platform/tests/fetch/private-network-access/iframe.tentative.https.window.js b/testing/web-platform/tests/fetch/private-network-access/iframe.tentative.https.window.js new file mode 100644 index 0000000000..1e00c0af41 --- /dev/null +++ b/testing/web-platform/tests/fetch/private-network-access/iframe.tentative.https.window.js @@ -0,0 +1,267 @@ +// META: script=/common/subset-tests-by-key.js +// META: script=/common/dispatcher/dispatcher.js +// META: script=/common/utils.js +// META: script=resources/support.sub.js +// META: timeout=long +// META: variant=?include=from-local +// META: variant=?include=from-private +// META: variant=?include=from-public +// META: variant=?include=from-treat-as-public +// META: variant=?include=grandparent +// +// Spec: https://wicg.github.io/private-network-access/#integration-fetch +// +// These tests verify that secure contexts can navigate iframes to less-public +// address spaces iff the target server responds affirmatively to preflight +// requests. +// +// This file covers only those tests that must execute in a secure context. +// Other tests are defined in: iframe.tentative.window.js + +setup(() => { + assert_true(window.isSecureContext); +}); + +// Source: secure local context. +// +// All fetches unaffected by Private Network Access. + +subsetTestByKey("from-local", promise_test_parallel, t => iframeTest(t, { + source: { server: Server.HTTPS_LOCAL }, + target: { server: Server.HTTPS_LOCAL }, + expected: FrameTestResult.SUCCESS, +}), "local to local: no preflight required."); + +subsetTestByKey("from-local", promise_test_parallel, t => iframeTest(t, { + source: { server: Server.HTTPS_LOCAL }, + target: { server: Server.HTTPS_PRIVATE }, + expected: FrameTestResult.SUCCESS, +}), "local to private: no preflight required."); + +subsetTestByKey("from-local", promise_test_parallel, t => iframeTest(t, { + source: { server: Server.HTTPS_LOCAL }, + target: { server: Server.HTTPS_PUBLIC }, + expected: FrameTestResult.SUCCESS, +}), "local to public: no preflight required."); + +// Generates tests of preflight behavior for a single (source, target) pair. +// +// Scenarios: +// +// - parent navigates child: +// - preflight response has non-2xx HTTP code +// - preflight response is missing CORS headers +// - preflight response is missing the PNA-specific `Access-Control` header +// - success +// +function makePreflightTests({ + key, + sourceName, + sourceServer, + sourceTreatAsPublic, + targetName, + targetServer, +}) { + const prefix = + `${sourceName} to ${targetName}: `; + + const source = { + server: sourceServer, + treatAsPublic: sourceTreatAsPublic, + }; + + promise_test_parallel(t => iframeTest(t, { + source, + target: { + server: targetServer, + behavior: { preflight: PreflightBehavior.failure() }, + }, + expected: FrameTestResult.FAILURE, + }), prefix + "failed preflight."); + + promise_test_parallel(t => iframeTest(t, { + source, + target: { + server: targetServer, + behavior: { preflight: PreflightBehavior.noCorsHeader(token()) }, + }, + expected: FrameTestResult.FAILURE, + }), prefix + "missing CORS headers."); + + promise_test_parallel(t => iframeTest(t, { + source, + target: { + server: targetServer, + behavior: { preflight: PreflightBehavior.noPnaHeader(token()) }, + }, + expected: FrameTestResult.FAILURE, + }), prefix + "missing PNA header."); + + promise_test_parallel(t => iframeTest(t, { + source, + target: { + server: targetServer, + behavior: { preflight: PreflightBehavior.navigation(token()) }, + }, + expected: FrameTestResult.SUCCESS, + }), prefix + "success."); +} + +// Source: private secure context. +// +// Fetches to the local address space require a successful preflight response +// carrying a PNA-specific header. + +subsetTestByKey('from-private', makePreflightTests, { + sourceServer: Server.HTTPS_PRIVATE, + sourceName: 'private', + targetServer: Server.HTTPS_LOCAL, + targetName: 'local', +}); + +subsetTestByKey("from-private", promise_test_parallel, t => iframeTest(t, { + source: { server: Server.HTTPS_PRIVATE }, + target: { server: Server.HTTPS_PRIVATE }, + expected: FrameTestResult.SUCCESS, +}), "private to private: no preflight required."); + +subsetTestByKey("from-private", promise_test_parallel, t => iframeTest(t, { + source: { server: Server.HTTPS_PRIVATE }, + target: { server: Server.HTTPS_PUBLIC }, + expected: FrameTestResult.SUCCESS, +}), "private to public: no preflight required."); + +// Source: public secure context. +// +// Fetches to the local and private address spaces require a successful +// preflight response carrying a PNA-specific header. + +subsetTestByKey('from-public', makePreflightTests, { + sourceServer: Server.HTTPS_PUBLIC, + sourceName: "public", + targetServer: Server.HTTPS_LOCAL, + targetName: "local", +}); + +subsetTestByKey('from-public', makePreflightTests, { + sourceServer: Server.HTTPS_PUBLIC, + sourceName: "public", + targetServer: Server.HTTPS_PRIVATE, + targetName: "private", +}); + +subsetTestByKey("from-public", promise_test_parallel, t => iframeTest(t, { + source: { server: Server.HTTPS_PUBLIC }, + target: { server: Server.HTTPS_PUBLIC }, + expected: FrameTestResult.SUCCESS, +}), "public to public: no preflight required."); + +// The following tests verify that `CSP: treat-as-public-address` makes +// documents behave as if they had been served from a public IP address. + +subsetTestByKey('from-treat-as-public', makePreflightTests, { + sourceServer: Server.HTTPS_LOCAL, + sourceTreatAsPublic: true, + sourceName: "treat-as-public-address", + targetServer: Server.OTHER_HTTPS_LOCAL, + targetName: "local", +}); + +subsetTestByKey( + 'from-treat-as-public', promise_test_parallel, + t => iframeTest(t, { + source: { + server: Server.HTTPS_LOCAL, + treatAsPublic: true, + }, + target: {server: Server.HTTPS_LOCAL}, + expected: FrameTestResult.SUCCESS, + }), + 'treat-as-public-address to local (same-origin): no preflight required.' +); + +subsetTestByKey('from-treat-as-public', makePreflightTests, { + sourceServer: Server.HTTPS_LOCAL, + sourceTreatAsPublic: true, + sourceName: "treat-as-public-address", + targetServer: Server.HTTPS_PRIVATE, + targetName: "private", +}); + +subsetTestByKey( + 'from-treat-as-public', promise_test_parallel, + t => iframeTest(t, { + source: { + server: Server.HTTPS_LOCAL, + treatAsPublic: true, + }, + target: {server: Server.HTTPS_PUBLIC}, + expected: FrameTestResult.SUCCESS, + }), + 'treat-as-public-address to public: no preflight required.' +); + +subsetTestByKey( + 'from-treat-as-public', promise_test_parallel, + t => iframeTest(t, { + source: { + server: Server.HTTPS_LOCAL, + treatAsPublic: true, + }, + target: { + server: Server.HTTPS_PUBLIC, + behavior: {preflight: PreflightBehavior.optionalSuccess(token())} + }, + expected: FrameTestResult.SUCCESS, + }), + 'treat-as-public-address to local: optional preflight' +); + +// The following tests verify that when a grandparent frame navigates its +// grandchild, the IP address space of the grandparent is compared against the +// IP address space of the response. Indeed, the navigation initiator in this +// case is the grandparent, not the parent. + +subsetTestByKey('grandparent', iframeGrandparentTest, { + name: 'local to local, grandparent navigates: no preflight required.', + grandparentServer: Server.HTTPS_LOCAL, + child: {server: Server.HTTPS_PUBLIC}, + grandchild: {server: Server.OTHER_HTTPS_LOCAL}, + expected: FrameTestResult.SUCCESS, +}); + +subsetTestByKey('grandparent', iframeGrandparentTest, { + name: "local to local (same-origin), grandparent navigates: no preflight required.", + grandparentServer: Server.HTTPS_LOCAL, + child: { server: Server.HTTPS_PUBLIC }, + grandchild: { server: Server.HTTPS_LOCAL }, + expected: FrameTestResult.SUCCESS, +}); + +subsetTestByKey('grandparent', iframeGrandparentTest, { + name: "public to local, grandparent navigates: failure.", + grandparentServer: Server.HTTPS_PUBLIC, + child: { + server: Server.HTTPS_LOCAL, + behavior: { preflight: PreflightBehavior.navigation(token()) }, + }, + grandchild: { + server: Server.HTTPS_LOCAL, + behavior: { preflight: PreflightBehavior.failure() }, + }, + expected: FrameTestResult.FAILURE, +}); + +subsetTestByKey('grandparent', iframeGrandparentTest, { + name: "public to local, grandparent navigates: success.", + grandparentServer: Server.HTTPS_PUBLIC, + child: { + server: Server.HTTPS_LOCAL, + behavior: { preflight: PreflightBehavior.navigation(token()) }, + }, + grandchild: { + server: Server.HTTPS_LOCAL, + behavior: { preflight: PreflightBehavior.navigation(token()) }, + }, + expected: FrameTestResult.SUCCESS, +}); diff --git a/testing/web-platform/tests/fetch/private-network-access/iframe.tentative.window.js b/testing/web-platform/tests/fetch/private-network-access/iframe.tentative.window.js new file mode 100644 index 0000000000..441e0884d2 --- /dev/null +++ b/testing/web-platform/tests/fetch/private-network-access/iframe.tentative.window.js @@ -0,0 +1,110 @@ +// META: script=/common/dispatcher/dispatcher.js +// META: script=/common/utils.js +// META: script=resources/support.sub.js +// +// Spec: https://wicg.github.io/private-network-access/#integration-fetch +// +// These tests verify that non-secure contexts cannot navigate iframes to +// less-public address spaces, and can navigate them otherwise. +// +// This file covers only those tests that must execute in a non secure context. +// Other tests are defined in: iframe.tentative.https.window.js + +setup(() => { + // Making sure we are in a non secure context, as expected. + assert_false(window.isSecureContext); +}); + +promise_test_parallel(t => iframeTest(t, { + source: { server: Server.HTTP_LOCAL }, + target: { server: Server.HTTP_LOCAL }, + expected: FrameTestResult.SUCCESS, +}), "local to local: no preflight required."); + +promise_test_parallel(t => iframeTest(t, { + source: { server: Server.HTTP_LOCAL }, + target: { server: Server.HTTP_PRIVATE }, + expected: FrameTestResult.SUCCESS, +}), "local to private: no preflight required."); + +promise_test_parallel(t => iframeTest(t, { + source: { server: Server.HTTP_LOCAL }, + target: { server: Server.HTTP_PUBLIC }, + expected: FrameTestResult.SUCCESS, +}), "local to public: no preflight required."); + +promise_test_parallel(t => iframeTest(t, { + source: { server: Server.HTTP_PRIVATE }, + target: { server: Server.HTTP_LOCAL }, + expected: FrameTestResult.FAILURE, +}), "private to local: failure."); + +promise_test_parallel(t => iframeTest(t, { + source: { server: Server.HTTP_PRIVATE }, + target: { server: Server.HTTP_PRIVATE }, + expected: FrameTestResult.SUCCESS, +}), "private to private: no preflight required."); + +promise_test_parallel(t => iframeTest(t, { + source: { server: Server.HTTP_PRIVATE }, + target: { server: Server.HTTP_PUBLIC }, + expected: FrameTestResult.SUCCESS, +}), "private to public: no preflight required."); + +promise_test_parallel(t => iframeTest(t, { + source: { server: Server.HTTP_PUBLIC }, + target: { server: Server.HTTP_LOCAL }, + expected: FrameTestResult.FAILURE, +}), "public to local: failure."); + +promise_test_parallel(t => iframeTest(t, { + source: { server: Server.HTTP_PUBLIC }, + target: { server: Server.HTTP_PRIVATE }, + expected: FrameTestResult.FAILURE, +}), "public to private: failure."); + +promise_test_parallel(t => iframeTest(t, { + source: { server: Server.HTTP_PUBLIC }, + target: { server: Server.HTTP_PUBLIC }, + expected: FrameTestResult.SUCCESS, +}), "public to public: no preflight required."); + +promise_test_parallel(t => iframeTest(t, { + source: { + server: Server.HTTP_LOCAL, + treatAsPublic: true, + }, + target: { server: Server.HTTP_LOCAL }, + expected: FrameTestResult.FAILURE, +}), "treat-as-public-address to local: failure."); + +promise_test_parallel(t => iframeTest(t, { + source: { + server: Server.HTTP_LOCAL, + treatAsPublic: true, + }, + target: { server: Server.HTTP_PRIVATE }, + expected: FrameTestResult.FAILURE, +}), "treat-as-public-address to private: failure."); + +promise_test_parallel(t => iframeTest(t, { + source: { + server: Server.HTTP_LOCAL, + treatAsPublic: true, + }, + target: { server: Server.HTTP_PUBLIC }, + expected: FrameTestResult.SUCCESS, +}), "treat-as-public-address to public: no preflight required."); + +// The following test verifies that when a grandparent frame navigates its +// grandchild, the IP address space of the grandparent is compared against the +// IP address space of the response. Indeed, the navigation initiator in this +// case is the grandparent, not the parent. + +iframeGrandparentTest({ + name: "local to local, grandparent navigates: success.", + grandparentServer: Server.HTTP_LOCAL, + child: { server: Server.HTTP_PUBLIC }, + grandchild: { server: Server.HTTP_LOCAL }, + expected: FrameTestResult.SUCCESS, +}); diff --git a/testing/web-platform/tests/fetch/private-network-access/mixed-content-fetch.tentative.https.window.js b/testing/web-platform/tests/fetch/private-network-access/mixed-content-fetch.tentative.https.window.js new file mode 100644 index 0000000000..dbae5193b5 --- /dev/null +++ b/testing/web-platform/tests/fetch/private-network-access/mixed-content-fetch.tentative.https.window.js @@ -0,0 +1,278 @@ +// META: script=/common/utils.js +// META: script=resources/support.sub.js +// META: timeout=long +// +// Spec: https://wicg.github.io/private-network-access +// +// These tests verify that secure contexts can fetch non-secure subresources +// from more private address spaces, avoiding mixed context checks, as long as +// they specify a valid `targetAddressSpace` fetch option that matches the +// target server's address space. + +setup(() => { + // Making sure we are in a secure context, as expected. + assert_true(window.isSecureContext); +}); + +// Given `addressSpace`, returns the other three possible IP address spaces. +function otherAddressSpaces(addressSpace) { + switch (addressSpace) { + case "local": return ["unknown", "private", "public"]; + case "private": return ["unknown", "local", "public"]; + case "public": return ["unknown", "local", "private"]; + } +} + +// Generates tests of `targetAddressSpace` for the given (source, target) +// address space pair, expecting fetches to succeed iff `targetAddressSpace` is +// correct. +// +// Scenarios exercised: +// +// - cors mode: +// - missing targetAddressSpace option +// - incorrect targetAddressSpace option (x3, see `otherAddressSpaces()`) +// - failed preflight +// - success +// - success with PUT method (non-"simple" request) +// - no-cors mode: +// - success +// +function makeTests({ source, target }) { + const sourceServer = Server.get("https", source); + const targetServer = Server.get("http", target); + + const makeTest = ({ + fetchOptions, + targetBehavior, + name, + expected + }) => { + promise_test_parallel(t => fetchTest(t, { + source: { server: sourceServer }, + target: { + server: targetServer, + behavior: targetBehavior, + }, + fetchOptions, + expected, + }), `${sourceServer.name} to ${targetServer.name}: ${name}.`); + }; + + makeTest({ + name: "missing targetAddressSpace", + targetBehavior: { + preflight: PreflightBehavior.success(token()), + response: ResponseBehavior.allowCrossOrigin(), + }, + expected: FetchTestResult.FAILURE, + }); + + const correctAddressSpace = targetServer.addressSpace; + + for (const targetAddressSpace of otherAddressSpaces(correctAddressSpace)) { + makeTest({ + name: `wrong targetAddressSpace "${targetAddressSpace}"`, + targetBehavior: { + preflight: PreflightBehavior.success(token()), + response: ResponseBehavior.allowCrossOrigin(), + }, + fetchOptions: { targetAddressSpace }, + expected: FetchTestResult.FAILURE, + }); + } + + makeTest({ + name: "failed preflight", + targetBehavior: { + preflight: PreflightBehavior.failure(), + response: ResponseBehavior.allowCrossOrigin(), + }, + fetchOptions: { targetAddressSpace: correctAddressSpace }, + expected: FetchTestResult.FAILURE, + }); + + makeTest({ + name: "success", + targetBehavior: { + preflight: PreflightBehavior.success(token()), + response: ResponseBehavior.allowCrossOrigin(), + }, + fetchOptions: { targetAddressSpace: correctAddressSpace }, + expected: FetchTestResult.SUCCESS, + }); + + makeTest({ + name: "PUT success", + targetBehavior: { + preflight: PreflightBehavior.success(token()), + response: ResponseBehavior.allowCrossOrigin(), + }, + fetchOptions: { + targetAddressSpace: correctAddressSpace, + method: "PUT", + }, + expected: FetchTestResult.SUCCESS, + }); + + makeTest({ + name: "no-cors success", + targetBehavior: { + preflight: PreflightBehavior.success(token()), + response: ResponseBehavior.allowCrossOrigin(), + }, + fetchOptions: { + targetAddressSpace: correctAddressSpace, + mode: "no-cors", + }, + expected: FetchTestResult.OPAQUE, + }); +} + +// Generates tests for the given (source, target) address space pair expecting +// that `targetAddressSpace` cannot be used to bypass mixed content. +// +// Scenarios exercised: +// +// - wrong `targetAddressSpace` (x3, see `otherAddressSpaces()`) +// - correct `targetAddressSpace` +// +function makeNoBypassTests({ source, target }) { + const sourceServer = Server.get("https", source); + const targetServer = Server.get("http", target); + + const prefix = `${sourceServer.name} to ${targetServer.name}: `; + + const correctAddressSpace = targetServer.addressSpace; + for (const targetAddressSpace of otherAddressSpaces(correctAddressSpace)) { + promise_test_parallel(t => fetchTest(t, { + source: { server: sourceServer }, + target: { + server: targetServer, + behavior: { + preflight: PreflightBehavior.success(token()), + response: ResponseBehavior.allowCrossOrigin(), + }, + }, + fetchOptions: { targetAddressSpace }, + expected: FetchTestResult.FAILURE, + }), prefix + `wrong targetAddressSpace "${targetAddressSpace}".`); + } + + promise_test_parallel(t => fetchTest(t, { + source: { server: sourceServer }, + target: { + server: targetServer, + behavior: { + preflight: PreflightBehavior.success(token()), + response: ResponseBehavior.allowCrossOrigin(), + }, + }, + fetchOptions: { targetAddressSpace: correctAddressSpace }, + expected: FetchTestResult.FAILURE, + }), prefix + 'not a private network request.'); +} + +// Source: local secure context. +// +// Fetches to the local and private address spaces cannot use +// `targetAddressSpace` to bypass mixed content, as they are not otherwise +// blocked by Private Network Access. + +makeNoBypassTests({ source: "local", target: "local" }); +makeNoBypassTests({ source: "local", target: "private" }); +makeNoBypassTests({ source: "local", target: "public" }); + +// Source: private secure context. +// +// Fetches to the local address space requires the right `targetAddressSpace` +// option, as well as a successful preflight response carrying a PNA-specific +// header. +// +// Fetches to the private address space cannot use `targetAddressSpace` to +// bypass mixed content, as they are not otherwise blocked by Private Network +// Access. + +makeTests({ source: "private", target: "local" }); + +makeNoBypassTests({ source: "private", target: "private" }); +makeNoBypassTests({ source: "private", target: "public" }); + +// Source: public secure context. +// +// Fetches to the local and private address spaces require the right +// `targetAddressSpace` option, as well as a successful preflight response +// carrying a PNA-specific header. + +makeTests({ source: "public", target: "local" }); +makeTests({ source: "public", target: "private" }); + +makeNoBypassTests({ source: "public", target: "public" }); + +// These tests verify that documents fetched from the `local` address space yet +// carrying the `treat-as-public-address` CSP directive are treated as if they +// had been fetched from the `public` address space. + +promise_test_parallel(t => fetchTest(t, { + source: { + server: Server.HTTPS_LOCAL, + treatAsPublic: true, + }, + target: { + server: Server.HTTP_LOCAL, + behavior: { + preflight: PreflightBehavior.optionalSuccess(token()), + response: ResponseBehavior.allowCrossOrigin(), + }, + }, + fetchOptions: { targetAddressSpace: "private" }, + expected: FetchTestResult.FAILURE, +}), 'https-treat-as-public to http-local: wrong targetAddressSpace "private".'); + +promise_test_parallel(t => fetchTest(t, { + source: { + server: Server.HTTPS_LOCAL, + treatAsPublic: true, + }, + target: { + server: Server.HTTP_LOCAL, + behavior: { + preflight: PreflightBehavior.optionalSuccess(token()), + response: ResponseBehavior.allowCrossOrigin(), + }, + }, + fetchOptions: { targetAddressSpace: "local" }, + expected: FetchTestResult.SUCCESS, +}), "https-treat-as-public to http-local: success."); + +promise_test_parallel(t => fetchTest(t, { + source: { + server: Server.HTTPS_LOCAL, + treatAsPublic: true, + }, + target: { + server: Server.HTTP_PRIVATE, + behavior: { + preflight: PreflightBehavior.success(token()), + response: ResponseBehavior.allowCrossOrigin(), + }, + }, + fetchOptions: { targetAddressSpace: "local" }, + expected: FetchTestResult.FAILURE, +}), 'https-treat-as-public to http-private: wrong targetAddressSpace "local".'); + +promise_test_parallel(t => fetchTest(t, { + source: { + server: Server.HTTPS_LOCAL, + treatAsPublic: true, + }, + target: { + server: Server.HTTP_PRIVATE, + behavior: { + preflight: PreflightBehavior.success(token()), + response: ResponseBehavior.allowCrossOrigin(), + }, + }, + fetchOptions: { targetAddressSpace: "private" }, + expected: FetchTestResult.SUCCESS, +}), "https-treat-as-public to http-private: success."); diff --git a/testing/web-platform/tests/fetch/private-network-access/nested-worker.tentative.https.window.js b/testing/web-platform/tests/fetch/private-network-access/nested-worker.tentative.https.window.js new file mode 100644 index 0000000000..3eeb435bad --- /dev/null +++ b/testing/web-platform/tests/fetch/private-network-access/nested-worker.tentative.https.window.js @@ -0,0 +1,36 @@ +// META: script=/common/utils.js +// META: script=resources/support.sub.js +// +// Spec: https://wicg.github.io/private-network-access/#integration-fetch +// +// These tests check that initial `Worker` script fetches from within worker +// scopes are subject to Private Network Access checks, just like a worker +// script fetches from within document scopes (for non-nested workers). The +// latter are tested in: worker.https.window.js +// +// This file covers only those tests that must execute in a secure context. +// Other tests are defined in: nested-worker.window.js + +promise_test(t => nestedWorkerScriptTest(t, { + source: { + server: Server.HTTPS_LOCAL, + treatAsPublic: true, + }, + target: { server: Server.HTTPS_LOCAL }, + expected: WorkerScriptTestResult.FAILURE, +}), "treat-as-public to local: failure."); + +promise_test(t => nestedWorkerScriptTest(t, { + source: { + server: Server.HTTPS_PRIVATE, + treatAsPublic: true, + }, + target: { server: Server.HTTPS_PRIVATE }, + expected: WorkerScriptTestResult.FAILURE, +}), "treat-as-public to private: failure."); + +promise_test(t => nestedWorkerScriptTest(t, { + source: { server: Server.HTTPS_PUBLIC }, + target: { server: Server.HTTPS_PUBLIC }, + expected: WorkerScriptTestResult.SUCCESS, +}), "public to public: success."); diff --git a/testing/web-platform/tests/fetch/private-network-access/nested-worker.tentative.window.js b/testing/web-platform/tests/fetch/private-network-access/nested-worker.tentative.window.js new file mode 100644 index 0000000000..6d246e1c76 --- /dev/null +++ b/testing/web-platform/tests/fetch/private-network-access/nested-worker.tentative.window.js @@ -0,0 +1,36 @@ +// META: script=/common/utils.js +// META: script=resources/support.sub.js +// +// Spec: https://wicg.github.io/private-network-access/#integration-fetch +// +// These tests check that initial `Worker` script fetches from within worker +// scopes are subject to Private Network Access checks, just like a worker +// script fetches from within document scopes (for non-nested workers). The +// latter are tested in: worker.window.js +// +// This file covers only those tests that must execute in a non secure context. +// Other tests are defined in: nested-worker.https.window.js + +promise_test(t => nestedWorkerScriptTest(t, { + source: { + server: Server.HTTP_LOCAL, + treatAsPublic: true, + }, + target: { server: Server.HTTP_LOCAL }, + expected: WorkerScriptTestResult.FAILURE, +}), "treat-as-public to local: failure."); + +promise_test(t => nestedWorkerScriptTest(t, { + source: { + server: Server.HTTP_PRIVATE, + treatAsPublic: true, + }, + target: { server: Server.HTTP_PRIVATE }, + expected: WorkerScriptTestResult.FAILURE, +}), "treat-as-public to private: failure."); + +promise_test(t => nestedWorkerScriptTest(t, { + source: { server: Server.HTTP_PUBLIC }, + target: { server: Server.HTTP_PUBLIC }, + expected: WorkerScriptTestResult.SUCCESS, +}), "public to public: success."); diff --git a/testing/web-platform/tests/fetch/private-network-access/preflight-cache.https.tentative.window.js b/testing/web-platform/tests/fetch/private-network-access/preflight-cache.https.tentative.window.js new file mode 100644 index 0000000000..87dbf501f6 --- /dev/null +++ b/testing/web-platform/tests/fetch/private-network-access/preflight-cache.https.tentative.window.js @@ -0,0 +1,88 @@ +// META: script=/common/utils.js +// META: script=resources/support.sub.js +// +// Spec: https://wicg.github.io/private-network-access/#cors-preflight +// +// These tests verify that PNA preflight responses are cached. +// +// TODO(https://crbug.com/1268312): We cannot currently test that cache +// entries are keyed by target IP address space because that requires +// loading the same URL from different IP address spaces, and the WPT +// framework does not allow that. +promise_test(async t => { + let uuid = token(); + await fetchTest(t, { + source: { server: Server.HTTPS_PRIVATE }, + target: { + server: Server.HTTPS_LOCAL, + behavior: { + preflight: PreflightBehavior.singlePreflight(uuid), + response: ResponseBehavior.allowCrossOrigin(), + }, + }, + expected: FetchTestResult.SUCCESS, + }); + await fetchTest(t, { + source: { server: Server.HTTPS_PRIVATE }, + target: { + server: Server.HTTPS_LOCAL, + behavior: { + preflight: PreflightBehavior.singlePreflight(uuid), + response: ResponseBehavior.allowCrossOrigin(), + }, + }, + expected: FetchTestResult.SUCCESS, + }); +}, "private to local: success."); + +promise_test(async t => { + let uuid = token(); + await fetchTest(t, { + source: { server: Server.HTTPS_PUBLIC }, + target: { + server: Server.HTTPS_LOCAL, + behavior: { + preflight: PreflightBehavior.singlePreflight(uuid), + response: ResponseBehavior.allowCrossOrigin(), + }, + }, + expected: FetchTestResult.SUCCESS, + }); + await fetchTest(t, { + source: { server: Server.HTTPS_PUBLIC }, + target: { + server: Server.HTTPS_LOCAL, + behavior: { + preflight: PreflightBehavior.singlePreflight(uuid), + response: ResponseBehavior.allowCrossOrigin(), + }, + }, + expected: FetchTestResult.SUCCESS, + }); +}, "public to local: success."); + +promise_test(async t => { + let uuid = token(); + await fetchTest(t, { + source: { server: Server.HTTPS_PUBLIC }, + target: { + server: Server.HTTPS_PRIVATE, + behavior: { + preflight: PreflightBehavior.singlePreflight(uuid), + response: ResponseBehavior.allowCrossOrigin(), + }, + }, + expected: FetchTestResult.SUCCESS, + }); + await fetchTest(t, { + source: { server: Server.HTTPS_PUBLIC }, + target: { + server: Server.HTTPS_PRIVATE, + behavior: { + preflight: PreflightBehavior.singlePreflight(uuid), + response: ResponseBehavior.allowCrossOrigin(), + }, + }, + expected: FetchTestResult.SUCCESS, + }); +}, "public to private: success."); \ No newline at end of file diff --git a/testing/web-platform/tests/fetch/private-network-access/redirect.tentative.https.window.js b/testing/web-platform/tests/fetch/private-network-access/redirect.tentative.https.window.js new file mode 100644 index 0000000000..efbd8f31f9 --- /dev/null +++ b/testing/web-platform/tests/fetch/private-network-access/redirect.tentative.https.window.js @@ -0,0 +1,640 @@ +// META: script=/common/utils.js +// META: script=resources/support.sub.js +// +// Spec: https://wicg.github.io/private-network-access/#integration-fetch +// +// This test verifies that Private Network Access checks are applied to all +// the endpoints in a redirect chain, relative to the same client context. + +// local -> private -> public +// +// Request 1 (local -> private): no preflight. +// Request 2 (local -> public): no preflight. + +promise_test(t => fetchTest(t, { + source: { server: Server.HTTPS_LOCAL }, + target: { + server: Server.HTTPS_PRIVATE, + behavior: { + response: ResponseBehavior.allowCrossOrigin(), + redirect: preflightUrl({ + server: Server.HTTPS_PUBLIC, + behavior: { response: ResponseBehavior.allowCrossOrigin() }, + }), + } + }, + expected: FetchTestResult.SUCCESS, +}), "local to private to public: success."); + +// local -> private -> local +// +// Request 1 (local -> private): no preflight. +// Request 2 (local -> local): no preflight. +// +// This checks that the client for the second request is still the initial +// context, not the redirector. + +promise_test(t => fetchTest(t, { + source: { server: Server.HTTPS_LOCAL }, + target: { + server: Server.HTTPS_PRIVATE, + behavior: { + response: ResponseBehavior.allowCrossOrigin(), + redirect: preflightUrl({ + server: Server.HTTPS_LOCAL, + behavior: { response: ResponseBehavior.allowCrossOrigin() }, + }), + } + }, + expected: FetchTestResult.SUCCESS, +}), "local to private to local: success."); + +// private -> private -> local +// +// Request 1 (private -> private): no preflight. +// Request 2 (private -> local): preflight required. +// +// This verifies that PNA checks are applied after redirects. + +promise_test(t => fetchTest(t, { + source: { server: Server.HTTPS_PRIVATE }, + target: { + server: Server.HTTPS_PRIVATE, + behavior: { + redirect: preflightUrl({ + server: Server.HTTPS_LOCAL, + behavior: { response: ResponseBehavior.allowCrossOrigin() }, + }), + } + }, + expected: FetchTestResult.FAILURE, +}), "private to private to local: failed preflight."); + +promise_test(t => fetchTest(t, { + source: { server: Server.HTTPS_PRIVATE }, + target: { + server: Server.HTTPS_PRIVATE, + behavior: { + redirect: preflightUrl({ + server: Server.HTTPS_LOCAL, + behavior: { + preflight: PreflightBehavior.success(token()), + response: ResponseBehavior.allowCrossOrigin(), + }, + }), + } + }, + expected: FetchTestResult.SUCCESS, +}), "private to private to local: success."); + +promise_test(t => fetchTest(t, { + source: { server: Server.HTTPS_PRIVATE }, + target: { + server: Server.HTTPS_PRIVATE, + behavior: { + redirect: preflightUrl({ + server: Server.HTTPS_LOCAL, + behavior: { preflight: PreflightBehavior.success(token()) }, + }), + } + }, + fetchOptions: { mode: "no-cors" }, + expected: FetchTestResult.OPAQUE, +}), "private to private to local: no-cors success."); + +// private -> local -> private +// +// Request 1 (private -> local): preflight required. +// Request 2 (private -> private): no preflight. +// +// This verifies that PNA checks are applied independently to every step in a +// redirect chain. + +promise_test(t => fetchTest(t, { + source: { server: Server.HTTPS_PRIVATE }, + target: { + server: Server.HTTPS_LOCAL, + behavior: { + response: ResponseBehavior.allowCrossOrigin(), + redirect: preflightUrl({ + server: Server.HTTPS_PRIVATE, + }), + } + }, + expected: FetchTestResult.FAILURE, +}), "private to local to private: failed preflight."); + +promise_test(t => fetchTest(t, { + source: { server: Server.HTTPS_PRIVATE }, + target: { + server: Server.HTTPS_LOCAL, + behavior: { + preflight: PreflightBehavior.success(token()), + response: ResponseBehavior.allowCrossOrigin(), + redirect: preflightUrl({ + server: Server.HTTPS_PRIVATE, + behavior: { response: ResponseBehavior.allowCrossOrigin() }, + }), + } + }, + expected: FetchTestResult.SUCCESS, +}), "private to local to private: success."); + +promise_test(t => fetchTest(t, { + source: { server: Server.HTTPS_PRIVATE }, + target: { + server: Server.HTTPS_LOCAL, + behavior: { + preflight: PreflightBehavior.success(token()), + redirect: preflightUrl({ server: Server.HTTPS_PRIVATE }), + } + }, + fetchOptions: { mode: "no-cors" }, + expected: FetchTestResult.OPAQUE, +}), "private to local to private: no-cors success."); + +// public -> private -> local +// +// Request 1 (public -> private): preflight required. +// Request 2 (public -> local): preflight required. +// +// This verifies that PNA checks are applied to every step in a redirect chain. + +promise_test(t => fetchTest(t, { + source: { server: Server.HTTPS_PUBLIC }, + target: { + server: Server.HTTPS_PRIVATE, + behavior: { + response: ResponseBehavior.allowCrossOrigin(), + redirect: preflightUrl({ + server: Server.HTTPS_LOCAL, + behavior: { + preflight: PreflightBehavior.success(token()), + response: ResponseBehavior.allowCrossOrigin(), + }, + }), + } + }, + expected: FetchTestResult.FAILURE, +}), "public to private to local: failed first preflight."); + +promise_test(t => fetchTest(t, { + source: { server: Server.HTTPS_PUBLIC }, + target: { + server: Server.HTTPS_PRIVATE, + behavior: { + preflight: PreflightBehavior.success(token()), + response: ResponseBehavior.allowCrossOrigin(), + redirect: preflightUrl({ + server: Server.HTTPS_LOCAL, + behavior: { + response: ResponseBehavior.allowCrossOrigin(), + }, + }), + } + }, + expected: FetchTestResult.FAILURE, +}), "public to private to local: failed second preflight."); + +promise_test(t => fetchTest(t, { + source: { server: Server.HTTPS_PUBLIC }, + target: { + server: Server.HTTPS_PRIVATE, + behavior: { + preflight: PreflightBehavior.success(token()), + response: ResponseBehavior.allowCrossOrigin(), + redirect: preflightUrl({ + server: Server.HTTPS_LOCAL, + behavior: { + preflight: PreflightBehavior.success(token()), + response: ResponseBehavior.allowCrossOrigin(), + }, + }), + } + }, + expected: FetchTestResult.SUCCESS, +}), "public to private to local: success."); + +promise_test(t => fetchTest(t, { + source: { server: Server.HTTPS_PUBLIC }, + target: { + server: Server.HTTPS_PRIVATE, + behavior: { + preflight: PreflightBehavior.success(token()), + redirect: preflightUrl({ + server: Server.HTTPS_LOCAL, + behavior: { preflight: PreflightBehavior.success(token()) }, + }), + } + }, + fetchOptions: { mode: "no-cors" }, + expected: FetchTestResult.OPAQUE, +}), "public to private to local: no-cors success."); + +// treat-as-public -> local -> private + +// Request 1 (treat-as-public -> local): preflight required. +// Request 2 (treat-as-public -> private): preflight required. + +// This verifies that PNA checks are applied to every step in a redirect chain. + +promise_test(t => fetchTest(t, { + source: { + server: Server.HTTPS_LOCAL, + treatAsPublic: true, + }, + target: { + server: Server.OTHER_HTTPS_LOCAL, + behavior: { + redirect: preflightUrl({ + server: Server.HTTPS_PRIVATE, + behavior: { + preflight: PreflightBehavior.success(token()), + response: ResponseBehavior.allowCrossOrigin(), + }, + }), + response: ResponseBehavior.allowCrossOrigin(), + } + }, + expected: FetchTestResult.FAILURE, +}), "treat-as-public to local to private: failed first preflight."); + +promise_test(t => fetchTest(t, { + source: { + server: Server.HTTPS_LOCAL, + treatAsPublic: true, + }, + target: { + server: Server.OTHER_HTTPS_LOCAL, + behavior: { + preflight: PreflightBehavior.success(token()), + redirect: preflightUrl({ + server: Server.HTTPS_PRIVATE, + behavior: { + preflight: PreflightBehavior.noPnaHeader(token()), + response: ResponseBehavior.allowCrossOrigin(), + }, + }), + response: ResponseBehavior.allowCrossOrigin(), + } + }, + expected: FetchTestResult.FAILURE, +}), "treat-as-public to local to private: failed second preflight."); + +promise_test(t => fetchTest(t, { + source: { + server: Server.HTTPS_LOCAL, + treatAsPublic: true, + }, + target: { + server: Server.OTHER_HTTPS_LOCAL, + behavior: { + preflight: PreflightBehavior.success(token()), + redirect: preflightUrl({ + server: Server.HTTPS_PRIVATE, + behavior: { + preflight: PreflightBehavior.success(token()), + response: ResponseBehavior.allowCrossOrigin(), + }, + }), + response: ResponseBehavior.allowCrossOrigin(), + } + }, + expected: FetchTestResult.SUCCESS, +}), "treat-as-public to local to private: success."); + +promise_test(t => fetchTest(t, { + source: { + server: Server.HTTPS_LOCAL, + treatAsPublic: true, + }, + target: { + server: Server.OTHER_HTTPS_LOCAL, + behavior: { + redirect: preflightUrl({ + server: Server.HTTPS_PRIVATE, + behavior: { preflight: PreflightBehavior.success(token()) }, + }), + } + }, + fetchOptions: { mode: "no-cors" }, + expected: FetchTestResult.FAILURE, +}), "treat-as-public to local to private: no-cors failed first preflight."); + +promise_test(t => fetchTest(t, { + source: { + server: Server.HTTPS_LOCAL, + treatAsPublic: true, + }, + target: { + server: Server.OTHER_HTTPS_LOCAL, + behavior: { + preflight: PreflightBehavior.success(token()), + redirect: preflightUrl({ server: Server.HTTPS_PRIVATE }), + } + }, + fetchOptions: { mode: "no-cors" }, + expected: FetchTestResult.FAILURE, +}), "treat-as-public to local to private: no-cors failed second preflight."); + +promise_test(t => fetchTest(t, { + source: { + server: Server.HTTPS_LOCAL, + treatAsPublic: true, + }, + target: { + server: Server.OTHER_HTTPS_LOCAL, + behavior: { + preflight: PreflightBehavior.success(token()), + redirect: preflightUrl({ + server: Server.HTTPS_PRIVATE, + behavior: { preflight: PreflightBehavior.success(token()) }, + }), + } + }, + fetchOptions: { mode: "no-cors" }, + expected: FetchTestResult.OPAQUE, +}), "treat-as-public to local to private: no-cors success."); + +// treat-as-public -> local (same-origin) -> private + +// Request 1 (treat-as-public -> local (same-origin)): no preflight required. +// Request 2 (treat-as-public -> private): preflight required. + +// This verifies that PNA checks are applied only to the second step in a +// redirect chain if the first step is same-origin and the origin is potentially +// trustworthy. + +promise_test(t => fetchTest(t, { + source: { + server: Server.HTTPS_LOCAL, + treatAsPublic: true, + }, + target: { + server: Server.HTTPS_LOCAL, + behavior: { + redirect: preflightUrl({ + server: Server.HTTPS_PRIVATE, + behavior: { + preflight: PreflightBehavior.noPnaHeader(token()), + response: ResponseBehavior.allowCrossOrigin(), + }, + }), + } + }, + expected: FetchTestResult.FAILURE, +}), "treat-as-public to local (same-origin) to private: failed second preflight."); + +promise_test(t => fetchTest(t, { + source: { + server: Server.HTTPS_LOCAL, + treatAsPublic: true, + }, + target: { + server: Server.HTTPS_LOCAL, + behavior: { + redirect: preflightUrl({ + server: Server.HTTPS_PRIVATE, + behavior: { + preflight: PreflightBehavior.success(token()), + response: ResponseBehavior.allowCrossOrigin(), + }, + }), + } + }, + expected: FetchTestResult.SUCCESS, +}), "treat-as-public to local (same-origin) to private: success."); + +promise_test(t => fetchTest(t, { + source: { + server: Server.HTTPS_LOCAL, + treatAsPublic: true, + }, + target: { + server: Server.HTTPS_LOCAL, + behavior: { + redirect: preflightUrl({ server: Server.HTTPS_PRIVATE }), + } + }, + fetchOptions: { mode: "no-cors" }, + expected: FetchTestResult.FAILURE, +}), "treat-as-public to local (same-origin) to private: no-cors failed second preflight."); + +promise_test(t => fetchTest(t, { + source: { + server: Server.HTTPS_LOCAL, + treatAsPublic: true, + }, + target: { + server: Server.HTTPS_LOCAL, + behavior: { + redirect: preflightUrl({ + server: Server.HTTPS_PRIVATE, + behavior: { preflight: PreflightBehavior.success(token()) }, + }), + } + }, + fetchOptions: { mode: "no-cors" }, + expected: FetchTestResult.OPAQUE, +}), "treat-as-public to local (same-origin) to private: no-cors success."); + +// treat-as-public -> private -> local + +// Request 1 (treat-as-public -> private): preflight required. +// Request 2 (treat-as-public -> local): preflight required. + +// This verifies that PNA checks are applied to every step in a redirect chain. + +promise_test(t => fetchTest(t, { + source: { + server: Server.HTTPS_LOCAL, + treatAsPublic: true, + }, + target: { + server: Server.HTTPS_PRIVATE, + behavior: { + preflight: PreflightBehavior.noPnaHeader(token()), + response: ResponseBehavior.allowCrossOrigin(), + redirect: preflightUrl({ + server: Server.OTHER_HTTPS_LOCAL, + behavior: { + preflight: PreflightBehavior.success(token()), + response: ResponseBehavior.allowCrossOrigin(), + }, + }), + } + }, + expected: FetchTestResult.FAILURE, +}), "treat-as-public to private to local: failed first preflight."); + +promise_test(t => fetchTest(t, { + source: { + server: Server.HTTPS_LOCAL, + treatAsPublic: true, + }, + target: { + server: Server.HTTPS_PRIVATE, + behavior: { + preflight: PreflightBehavior.success(token()), + response: ResponseBehavior.allowCrossOrigin(), + redirect: preflightUrl({ + server: Server.OTHER_HTTPS_LOCAL, + behavior: { response: ResponseBehavior.allowCrossOrigin() }, + }), + } + }, + expected: FetchTestResult.FAILURE, +}), "treat-as-public to private to local: failed second preflight."); + +promise_test(t => fetchTest(t, { + source: { + server: Server.HTTPS_LOCAL, + treatAsPublic: true, + }, + target: { + server: Server.HTTPS_PRIVATE, + behavior: { + preflight: PreflightBehavior.success(token()), + response: ResponseBehavior.allowCrossOrigin(), + redirect: preflightUrl({ + server: Server.OTHER_HTTPS_LOCAL, + behavior: { + preflight: PreflightBehavior.success(token()), + response: ResponseBehavior.allowCrossOrigin(), + }, + }), + } + }, + expected: FetchTestResult.SUCCESS, +}), "treat-as-public to private to local: success."); + +promise_test(t => fetchTest(t, { + source: { + server: Server.HTTPS_LOCAL, + treatAsPublic: true, + }, + target: { + server: Server.HTTPS_PRIVATE, + behavior: { + redirect: preflightUrl({ + server: Server.OTHER_HTTPS_LOCAL, + behavior: { preflight: PreflightBehavior.success(token()) }, + }), + } + }, + fetchOptions: { mode: "no-cors" }, + expected: FetchTestResult.FAILURE, +}), "treat-as-public to private to local: no-cors failed first preflight."); + +promise_test(t => fetchTest(t, { + source: { + server: Server.HTTPS_LOCAL, + treatAsPublic: true, + }, + target: { + server: Server.HTTPS_PRIVATE, + behavior: { + preflight: PreflightBehavior.success(token()), + redirect: preflightUrl({ server: Server.OTHER_HTTPS_LOCAL }), + } + }, + fetchOptions: { mode: "no-cors" }, + expected: FetchTestResult.FAILURE, +}), "treat-as-public to private to local: no-cors failed second preflight."); + +promise_test(t => fetchTest(t, { + source: { + server: Server.HTTPS_LOCAL, + treatAsPublic: true, + }, + target: { + server: Server.HTTPS_PRIVATE, + behavior: { + preflight: PreflightBehavior.success(token()), + redirect: preflightUrl({ + server: Server.OTHER_HTTPS_LOCAL, + behavior: { preflight: PreflightBehavior.success(token()) }, + }), + } + }, + fetchOptions: { mode: "no-cors" }, + expected: FetchTestResult.OPAQUE, +}), "treat-as-public to private to local: no-cors success."); + +// treat-as-public -> private -> local (same-origin) + +// Request 1 (treat-as-public -> private): preflight required. +// Request 2 (treat-as-public -> local (same-origin)): no preflight required. + +// This verifies that PNA checks are only applied to the first step in a +// redirect chain if the second step is same-origin and the origin is +// potentially trustworthy. + +promise_test(t => fetchTest(t, { + source: { + server: Server.HTTPS_LOCAL, + treatAsPublic: true, + }, + target: { + server: Server.HTTPS_PRIVATE, + behavior: { + preflight: PreflightBehavior.noPnaHeader(token()), + response: ResponseBehavior.allowCrossOrigin(), + redirect: preflightUrl({ + server: Server.HTTPS_LOCAL, + behavior: { response: ResponseBehavior.allowCrossOrigin() }, + }), + } + }, + expected: FetchTestResult.FAILURE, +}), "treat-as-public to private to local (same-origin): failed first preflight."); + +promise_test(t => fetchTest(t, { + source: { + server: Server.HTTPS_LOCAL, + treatAsPublic: true, + }, + target: { + server: Server.HTTPS_PRIVATE, + behavior: { + preflight: PreflightBehavior.success(token()), + response: ResponseBehavior.allowCrossOrigin(), + redirect: preflightUrl({ + server: Server.HTTPS_LOCAL, + behavior: { response: ResponseBehavior.allowCrossOrigin() }, + }), + } + }, + expected: FetchTestResult.SUCCESS, +}), "treat-as-public to private to local (same-origin): success."); + +promise_test(t => fetchTest(t, { + source: { + server: Server.HTTPS_LOCAL, + treatAsPublic: true, + }, + target: { + server: Server.HTTPS_PRIVATE, + behavior: { + redirect: preflightUrl({ server: Server.HTTPS_LOCAL }), + } + }, + fetchOptions: { mode: "no-cors" }, + expected: FetchTestResult.FAILURE, +}), "treat-as-public to private to local (same-origin): no-cors failed first preflight."); + +promise_test(t => fetchTest(t, { + source: { + server: Server.HTTPS_LOCAL, + treatAsPublic: true, + }, + target: { + server: Server.HTTPS_PRIVATE, + behavior: { + preflight: PreflightBehavior.success(token()), + redirect: preflightUrl({ server: Server.HTTPS_LOCAL }), + } + }, + fetchOptions: { mode: "no-cors" }, + expected: FetchTestResult.OPAQUE, +}), "treat-as-public to private to local (same-origin): no-cors success."); diff --git a/testing/web-platform/tests/fetch/private-network-access/resources/anchor.html b/testing/web-platform/tests/fetch/private-network-access/resources/anchor.html new file mode 100644 index 0000000000..0780b3fa50 --- /dev/null +++ b/testing/web-platform/tests/fetch/private-network-access/resources/anchor.html @@ -0,0 +1,16 @@ + + +Anchor + + diff --git a/testing/web-platform/tests/fetch/private-network-access/resources/executor.html b/testing/web-platform/tests/fetch/private-network-access/resources/executor.html new file mode 100644 index 0000000000..d71212951c --- /dev/null +++ b/testing/web-platform/tests/fetch/private-network-access/resources/executor.html @@ -0,0 +1,9 @@ + + +Executor + + + diff --git a/testing/web-platform/tests/fetch/private-network-access/resources/fenced-frame-fetcher.https.html b/testing/web-platform/tests/fetch/private-network-access/resources/fenced-frame-fetcher.https.html new file mode 100644 index 0000000000..b14601dba5 --- /dev/null +++ b/testing/web-platform/tests/fetch/private-network-access/resources/fenced-frame-fetcher.https.html @@ -0,0 +1,25 @@ + + + +Fetcher + \ No newline at end of file diff --git a/testing/web-platform/tests/fetch/private-network-access/resources/fenced-frame-fetcher.https.html.headers b/testing/web-platform/tests/fetch/private-network-access/resources/fenced-frame-fetcher.https.html.headers new file mode 100644 index 0000000000..6247f6d632 --- /dev/null +++ b/testing/web-platform/tests/fetch/private-network-access/resources/fenced-frame-fetcher.https.html.headers @@ -0,0 +1 @@ +Supports-Loading-Mode: fenced-frame \ No newline at end of file diff --git a/testing/web-platform/tests/fetch/private-network-access/resources/fenced-frame-private-network-access-target.https.html b/testing/web-platform/tests/fetch/private-network-access/resources/fenced-frame-private-network-access-target.https.html new file mode 100644 index 0000000000..2b55e056f3 --- /dev/null +++ b/testing/web-platform/tests/fetch/private-network-access/resources/fenced-frame-private-network-access-target.https.html @@ -0,0 +1,8 @@ + + + +Fenced frame target + diff --git a/testing/web-platform/tests/fetch/private-network-access/resources/fenced-frame-private-network-access.https.html b/testing/web-platform/tests/fetch/private-network-access/resources/fenced-frame-private-network-access.https.html new file mode 100644 index 0000000000..98f118432e --- /dev/null +++ b/testing/web-platform/tests/fetch/private-network-access/resources/fenced-frame-private-network-access.https.html @@ -0,0 +1,14 @@ + + + + + +Fenced frame + + diff --git a/testing/web-platform/tests/fetch/private-network-access/resources/fenced-frame-private-network-access.https.html.headers b/testing/web-platform/tests/fetch/private-network-access/resources/fenced-frame-private-network-access.https.html.headers new file mode 100644 index 0000000000..6247f6d632 --- /dev/null +++ b/testing/web-platform/tests/fetch/private-network-access/resources/fenced-frame-private-network-access.https.html.headers @@ -0,0 +1 @@ +Supports-Loading-Mode: fenced-frame \ No newline at end of file diff --git a/testing/web-platform/tests/fetch/private-network-access/resources/fetcher.html b/testing/web-platform/tests/fetch/private-network-access/resources/fetcher.html new file mode 100644 index 0000000000..000a5cc25b --- /dev/null +++ b/testing/web-platform/tests/fetch/private-network-access/resources/fetcher.html @@ -0,0 +1,21 @@ + + +Fetcher + diff --git a/testing/web-platform/tests/fetch/private-network-access/resources/fetcher.js b/testing/web-platform/tests/fetch/private-network-access/resources/fetcher.js new file mode 100644 index 0000000000..3a1859876d --- /dev/null +++ b/testing/web-platform/tests/fetch/private-network-access/resources/fetcher.js @@ -0,0 +1,20 @@ +async function doFetch(url) { + const response = await fetch(url); + const body = await response.text(); + return { + status: response.status, + body, + }; +} + +async function fetchAndPost(url) { + try { + const message = await doFetch(url); + self.postMessage(message); + } catch(e) { + self.postMessage({ error: e.name }); + } +} + +const url = new URL(self.location.href).searchParams.get("url"); +fetchAndPost(url); diff --git a/testing/web-platform/tests/fetch/private-network-access/resources/iframed-no-preflight-received.html b/testing/web-platform/tests/fetch/private-network-access/resources/iframed-no-preflight-received.html new file mode 100644 index 0000000000..20b5150d44 --- /dev/null +++ b/testing/web-platform/tests/fetch/private-network-access/resources/iframed-no-preflight-received.html @@ -0,0 +1,7 @@ + + +Iframed + diff --git a/testing/web-platform/tests/fetch/private-network-access/resources/iframed.html b/testing/web-platform/tests/fetch/private-network-access/resources/iframed.html new file mode 100644 index 0000000000..c889c2882a --- /dev/null +++ b/testing/web-platform/tests/fetch/private-network-access/resources/iframed.html @@ -0,0 +1,7 @@ + + +Iframed + diff --git a/testing/web-platform/tests/fetch/private-network-access/resources/iframer.html b/testing/web-platform/tests/fetch/private-network-access/resources/iframer.html new file mode 100644 index 0000000000..304cc54ae4 --- /dev/null +++ b/testing/web-platform/tests/fetch/private-network-access/resources/iframer.html @@ -0,0 +1,9 @@ + + +Iframer + + diff --git a/testing/web-platform/tests/fetch/private-network-access/resources/no-preflight-received.html b/testing/web-platform/tests/fetch/private-network-access/resources/no-preflight-received.html new file mode 100644 index 0000000000..5ee533e182 --- /dev/null +++ b/testing/web-platform/tests/fetch/private-network-access/resources/no-preflight-received.html @@ -0,0 +1,6 @@ + + +No preflight received + diff --git a/testing/web-platform/tests/fetch/private-network-access/resources/open-to-existing-window.html b/testing/web-platform/tests/fetch/private-network-access/resources/open-to-existing-window.html new file mode 100644 index 0000000000..6460024bc8 --- /dev/null +++ b/testing/web-platform/tests/fetch/private-network-access/resources/open-to-existing-window.html @@ -0,0 +1,12 @@ + + +Opener to an existing window + + diff --git a/testing/web-platform/tests/fetch/private-network-access/resources/openee.html b/testing/web-platform/tests/fetch/private-network-access/resources/openee.html new file mode 100644 index 0000000000..8f0a859cb3 --- /dev/null +++ b/testing/web-platform/tests/fetch/private-network-access/resources/openee.html @@ -0,0 +1,8 @@ + + +Openee + diff --git a/testing/web-platform/tests/fetch/private-network-access/resources/opener.html b/testing/web-platform/tests/fetch/private-network-access/resources/opener.html new file mode 100644 index 0000000000..78b66c6db7 --- /dev/null +++ b/testing/web-platform/tests/fetch/private-network-access/resources/opener.html @@ -0,0 +1,11 @@ + + +Opener + + diff --git a/testing/web-platform/tests/fetch/private-network-access/resources/preflight.py b/testing/web-platform/tests/fetch/private-network-access/resources/preflight.py new file mode 100644 index 0000000000..4467663239 --- /dev/null +++ b/testing/web-platform/tests/fetch/private-network-access/resources/preflight.py @@ -0,0 +1,191 @@ +# This endpoint responds to both preflight requests and the subsequent requests. +# +# Its behavior can be configured with various search/GET parameters, all of +# which are optional: +# +# - treat-as-public-once: Must be a valid UUID if set. +# If set, then this endpoint expects to receive a non-preflight request first, +# for which it sets the `Content-Security-Policy: treat-as-public-address` +# response header. This allows testing "DNS rebinding", where a URL first +# resolves to the public IP address space, then a non-public IP address space. +# - preflight-uuid: Must be a valid UUID if set, distinct from the value of the +# `treat-as-public-once` parameter if both are set. +# If set, then this endpoint expects to receive a preflight request first +# followed by a regular request, as in the regular CORS protocol. If the +# `treat-as-public-once` header is also set, it takes precedence: this +# endpoint expects to receive a non-preflight request first, then a preflight +# request, then finally a regular request. +# If unset, then this endpoint expects to receive no preflight request, only +# a regular (non-OPTIONS) request. +# - preflight-headers: Valid values are: +# - cors: this endpoint responds with valid CORS headers to preflights. These +# should be sufficient for non-PNA preflight requests to succeed, but not +# for PNA-specific preflight requests. +# - cors+pna: this endpoint responds with valid CORS and PNA headers to +# preflights. These should be sufficient for both non-PNA preflight +# requests and PNA-specific preflight requests to succeed. +# - cors+pna+sw: this endpoint responds with valid CORS and PNA headers and +# "Access-Control-Allow-Headers: Service-Worker" to preflights. These should +# be sufficient for both non-PNA preflight requests and PNA-specific +# preflight requests to succeed. This allows the main request to fetch a +# service worker script. +# - unspecified, or any other value: this endpoint responds with no CORS or +# PNA headers. Preflight requests should fail. +# - final-headers: Valid values are: +# - cors: this endpoint responds with valid CORS headers to CORS-enabled +# non-preflight requests. These should be sufficient for non-preflighted +# CORS-enabled requests to succeed. +# - unspecified: this endpoint responds with no CORS headers to non-preflight +# requests. This should fail CORS-enabled requests, but be sufficient for +# no-CORS requests. +# +# The following parameters only affect non-preflight responses: +# +# - redirect: If set, the response code is set to 301 and the `Location` +# response header is set to this value. +# - mime-type: If set, the `Content-Type` response header is set to this value. +# - file: Specifies a path (relative to this file's directory) to a file. If +# set, the response body is copied from this file. +# - random-js-prefix: If set to any value, the response body is prefixed with +# a Javascript comment line containing a random value. This is useful in +# service worker tests, since service workers are only updated if the new +# script is not byte-for-byte identical with the old script. +# - body: If set and `file` is not, the response body is set to this value. +# + +import os +import random + +from wptserve.utils import isomorphic_encode + +_ACAO = ("Access-Control-Allow-Origin", "*") +_ACAPN = ("Access-Control-Allow-Private-Network", "true") +_ACAH = ("Access-Control-Allow-Headers", "Service-Worker") + +def _get_response_headers(method, mode, origin): + acam = ("Access-Control-Allow-Methods", method) + + if mode == b"cors": + return [acam, _ACAO] + + if mode == b"cors+pna": + return [acam, _ACAO, _ACAPN] + + if mode == b"cors+pna+sw": + return [acam, _ACAO, _ACAPN, _ACAH] + + if mode == b"navigation": + return [ + acam, + ("Access-Control-Allow-Origin", origin), + ("Access-Control-Allow-Credentials", "true"), + _ACAPN, + ] + + return [] + +def _get_expect_single_preflight(request): + return request.GET.get(b"expect-single-preflight") + +def _is_preflight_optional(request): + return request.GET.get(b"is-preflight-optional") or \ + request.GET.get(b"file-if-no-preflight-received") + +def _get_preflight_uuid(request): + return request.GET.get(b"preflight-uuid") + +def _is_loaded_in_fenced_frame(request): + return request.GET.get(b"is-loaded-in-fenced-frame") + +def _should_treat_as_public_once(request): + uuid = request.GET.get(b"treat-as-public-once") + if uuid is None: + # If the search parameter is not given, never treat as public. + return False + + # If the parameter is given, we treat the request as public only if the UUID + # has never been seen and stashed. + result = request.server.stash.take(uuid) is None + request.server.stash.put(uuid, "") + return result + +def _handle_preflight_request(request, response): + if _should_treat_as_public_once(request): + return (400, [], "received preflight for first treat-as-public request") + + uuid = _get_preflight_uuid(request) + if uuid is None: + return (400, [], "missing `preflight-uuid` param from preflight URL") + + value = request.server.stash.take(uuid) + request.server.stash.put(uuid, "preflight") + if _get_expect_single_preflight(request) and value is not None: + return (400, [], "received duplicated preflight") + + method = request.headers.get("Access-Control-Request-Method") + mode = request.GET.get(b"preflight-headers") + origin = request.headers.get("Origin") + headers = _get_response_headers(method, mode, origin) + + return (headers, "preflight") + +def _final_response_body(request, missing_preflight): + file_name = None + if missing_preflight and not request.GET.get(b"is-preflight-optional"): + file_name = request.GET.get(b"file-if-no-preflight-received") + if file_name is None: + file_name = request.GET.get(b"file") + if file_name is None: + return request.GET.get(b"body") or "success" + + prefix = b"" + if request.GET.get(b"random-js-prefix"): + value = random.randint(0, 1000000000) + prefix = isomorphic_encode("// Random value: {}\n\n".format(value)) + + path = os.path.join(os.path.dirname(isomorphic_encode(__file__)), file_name) + with open(path, 'rb') as f: + contents = f.read() + + return prefix + contents + +def _handle_final_request(request, response): + missing_preflight = False + if _should_treat_as_public_once(request): + headers = [("Content-Security-Policy", "treat-as-public-address"),] + else: + uuid = _get_preflight_uuid(request) + if uuid is not None: + missing_preflight = request.server.stash.take(uuid) is None + if missing_preflight and not _is_preflight_optional(request): + return (405, [], "no preflight received") + request.server.stash.put(uuid, "final") + + mode = request.GET.get(b"final-headers") + origin = request.headers.get("Origin") + headers = _get_response_headers(request.method, mode, origin) + + redirect = request.GET.get(b"redirect") + if redirect is not None: + headers.append(("Location", redirect)) + return (301, headers, b"") + + mime_type = request.GET.get(b"mime-type") + if mime_type is not None: + headers.append(("Content-Type", mime_type),) + + if _is_loaded_in_fenced_frame(request): + headers.append(("Supports-Loading-Mode", "fenced-frame")) + + body = _final_response_body(request, missing_preflight) + return (headers, body) + +def main(request, response): + try: + if request.method == "OPTIONS": + return _handle_preflight_request(request, response) + else: + return _handle_final_request(request, response) + except BaseException as e: + # Surface exceptions to the client, where they show up as assertion errors. + return (500, [("X-exception", str(e))], "exception: {}".format(e)) diff --git a/testing/web-platform/tests/fetch/private-network-access/resources/service-worker-bridge.html b/testing/web-platform/tests/fetch/private-network-access/resources/service-worker-bridge.html new file mode 100644 index 0000000000..816de535fe --- /dev/null +++ b/testing/web-platform/tests/fetch/private-network-access/resources/service-worker-bridge.html @@ -0,0 +1,155 @@ + + +ServiceWorker Bridge + + + diff --git a/testing/web-platform/tests/fetch/private-network-access/resources/service-worker.js b/testing/web-platform/tests/fetch/private-network-access/resources/service-worker.js new file mode 100644 index 0000000000..bca71ad910 --- /dev/null +++ b/testing/web-platform/tests/fetch/private-network-access/resources/service-worker.js @@ -0,0 +1,18 @@ +self.addEventListener("install", () => { + // Skip waiting before replacing the previously-active service worker, if any. + // This allows the bridge script to notice the controller change and query + // the install time via fetch. + self.skipWaiting(); +}); + +self.addEventListener("activate", (event) => { + // Claim all clients so that the bridge script notices the activation. + event.waitUntil(self.clients.claim()); +}); + +self.addEventListener("fetch", (event) => { + const url = new URL(event.request.url).searchParams.get("proxied-url"); + if (url) { + event.respondWith(fetch(url)); + } +}); diff --git a/testing/web-platform/tests/fetch/private-network-access/resources/shared-fetcher.js b/testing/web-platform/tests/fetch/private-network-access/resources/shared-fetcher.js new file mode 100644 index 0000000000..30bde1e054 --- /dev/null +++ b/testing/web-platform/tests/fetch/private-network-access/resources/shared-fetcher.js @@ -0,0 +1,23 @@ +async function doFetch(url) { + const response = await fetch(url); + const body = await response.text(); + return { + status: response.status, + body, + }; +} + +async function fetchAndPost(url, port) { + try { + const message = await doFetch(url); + port.postMessage(message); + } catch(e) { + port.postMessage({ error: e.name }); + } +} + +const url = new URL(self.location.href).searchParams.get("url"); + +self.addEventListener("connect", async (evt) => { + await fetchAndPost(url, evt.ports[0]); +}); diff --git a/testing/web-platform/tests/fetch/private-network-access/resources/shared-worker-blob-fetcher.html b/testing/web-platform/tests/fetch/private-network-access/resources/shared-worker-blob-fetcher.html new file mode 100644 index 0000000000..a79869b2f9 --- /dev/null +++ b/testing/web-platform/tests/fetch/private-network-access/resources/shared-worker-blob-fetcher.html @@ -0,0 +1,50 @@ + + +SharedWorker Blob Fetcher + diff --git a/testing/web-platform/tests/fetch/private-network-access/resources/shared-worker-fetcher.html b/testing/web-platform/tests/fetch/private-network-access/resources/shared-worker-fetcher.html new file mode 100644 index 0000000000..4af4b1f239 --- /dev/null +++ b/testing/web-platform/tests/fetch/private-network-access/resources/shared-worker-fetcher.html @@ -0,0 +1,19 @@ + + +SharedWorker Fetcher + diff --git a/testing/web-platform/tests/fetch/private-network-access/resources/socket-opener.html b/testing/web-platform/tests/fetch/private-network-access/resources/socket-opener.html new file mode 100644 index 0000000000..48d27216be --- /dev/null +++ b/testing/web-platform/tests/fetch/private-network-access/resources/socket-opener.html @@ -0,0 +1,15 @@ + + +WebSocket Opener + diff --git a/testing/web-platform/tests/fetch/private-network-access/resources/support.sub.js b/testing/web-platform/tests/fetch/private-network-access/resources/support.sub.js new file mode 100644 index 0000000000..46a9d9e076 --- /dev/null +++ b/testing/web-platform/tests/fetch/private-network-access/resources/support.sub.js @@ -0,0 +1,857 @@ +// Creates a new iframe in `doc`, calls `func` on it and appends it as a child +// of `doc`. +// Returns a promise that resolves to the iframe once loaded (successfully or +// not). +// The iframe is removed from `doc` once test `t` is done running. +// +// NOTE: There exists no interoperable way to check whether an iframe failed to +// load, so this should only be used when the iframe is expected to load. It +// also means we cannot wire the iframe's `error` event to a promise +// rejection. See: https://github.com/whatwg/html/issues/125 +function appendIframeWith(t, doc, func) { + return new Promise(resolve => { + const child = doc.createElement("iframe"); + t.add_cleanup(() => child.remove()); + + child.addEventListener("load", () => resolve(child), { once: true }); + func(child); + doc.body.appendChild(child); + }); +} + +// Appends a child iframe to `doc` sourced from `src`. +// +// See `appendIframeWith()` for more details. +function appendIframe(t, doc, src) { + return appendIframeWith(t, doc, child => { child.src = src; }); +} + +// Registers an event listener that will resolve this promise when this +// window receives a message posted to it. +// +// `options` has the following shape: +// +// { +// source: If specified, this function waits for the first message from the +// given source only, ignoring other messages. +// +// filter: If specified, this function calls `filter` on each incoming +// message, and resolves iff it returns true. +// } +// +function futureMessage(options) { + return new Promise(resolve => { + window.addEventListener("message", (e) => { + if (options?.source && options.source !== e.source) { + return; + } + + if (options?.filter && !options.filter(e.data)) { + return; + } + + resolve(e.data); + }); + }); +}; + +// Like `promise_test()`, but executes tests in parallel like `async_test()`. +// +// Cribbed from COEP tests. +function promise_test_parallel(promise, description) { + async_test(test => { + promise(test) + .then(() => test.done()) + .catch(test.step_func(error => { throw error; })); + }, description); +}; + +async function postMessageAndAwaitReply(target, message) { + const reply = futureMessage({ source: target }); + target.postMessage(message, "*"); + return await reply; +} + +// Maps protocol (without the trailing colon) and address space to port. +const SERVER_PORTS = { + "http": { + "local": {{ports[http][0]}}, + "private": {{ports[http-private][0]}}, + "public": {{ports[http-public][0]}}, + }, + "https": { + "local": {{ports[https][0]}}, + "other-local": {{ports[https][1]}}, + "private": {{ports[https-private][0]}}, + "public": {{ports[https-public][0]}}, + }, + "ws": { + "local": {{ports[ws][0]}}, + }, + "wss": { + "local": {{ports[wss][0]}}, + }, +}; + +// A `Server` is a web server accessible by tests. It has the following shape: +// +// { +// addressSpace: the IP address space of the server ("local", "private" or +// "public"), +// name: a human-readable name for the server, +// port: the port on which the server listens for connections, +// protocol: the protocol (including trailing colon) spoken by the server, +// } +// +// Constants below define the available servers, which can also be accessed +// programmatically with `get()`. +class Server { + // Maps the given `protocol` (without a trailing colon) and `addressSpace` to + // a server. Returns null if no such server exists. + static get(protocol, addressSpace) { + const ports = SERVER_PORTS[protocol]; + if (ports === undefined) { + return null; + } + + const port = ports[addressSpace]; + if (port === undefined) { + return null; + } + + return { + addressSpace, + name: `${protocol}-${addressSpace}`, + port, + protocol: protocol + ':', + }; + } + + static HTTP_LOCAL = Server.get("http", "local"); + static HTTP_PRIVATE = Server.get("http", "private"); + static HTTP_PUBLIC = Server.get("http", "public"); + static HTTPS_LOCAL = Server.get("https", "local"); + static OTHER_HTTPS_LOCAL = Server.get("https", "other-local"); + static HTTPS_PRIVATE = Server.get("https", "private"); + static HTTPS_PUBLIC = Server.get("https", "public"); + static WS_LOCAL = Server.get("ws", "local"); + static WSS_LOCAL = Server.get("wss", "local"); +}; + +// Resolves a URL relative to the current location, returning an absolute URL. +// +// `url` specifies the relative URL, e.g. "foo.html" or "http://foo.example". +// `options`, if defined, should have the following shape: +// +// { +// // Optional. Overrides the protocol of the returned URL. +// protocol, +// +// // Optional. Overrides the port of the returned URL. +// port, +// +// // Extra headers. +// headers, +// +// // Extra search params. +// searchParams, +// } +// +function resolveUrl(url, options) { + const result = new URL(url, window.location); + if (options === undefined) { + return result; + } + + const { port, protocol, headers, searchParams } = options; + if (port !== undefined) { + result.port = port; + } + if (protocol !== undefined) { + result.protocol = protocol; + } + if (headers !== undefined) { + const pipes = []; + for (key in headers) { + pipes.push(`header(${key},${headers[key]})`); + } + result.searchParams.append("pipe", pipes.join("|")); + } + if (searchParams !== undefined) { + for (key in searchParams) { + result.searchParams.append(key, searchParams[key]); + } + } + + return result; +} + +// Computes options to pass to `resolveUrl()` for a source document's URL. +// +// `server` identifies the server from which to load the document. +// `treatAsPublic`, if set to true, specifies that the source document should +// be artificially placed in the `public` address space using CSP. +function sourceResolveOptions({ server, treatAsPublic }) { + const options = {...server}; + if (treatAsPublic) { + options.headers = { "Content-Security-Policy": "treat-as-public-address" }; + } + return options; +} + +// Computes the URL of a preflight handler configured with the given options. +// +// `server` identifies the server from which to load the resource. +// `behavior` specifies the behavior of the target server. It may contain: +// - `preflight`: The result of calling one of `PreflightBehavior`'s methods. +// - `response`: The result of calling one of `ResponseBehavior`'s methods. +// - `redirect`: A URL to which the target should redirect GET requests. +function preflightUrl({ server, behavior }) { + assert_not_equals(server, undefined, 'server'); + const options = {...server}; + if (behavior) { + const { preflight, response, redirect } = behavior; + options.searchParams = { + ...preflight, + ...response, + }; + if (redirect !== undefined) { + options.searchParams.redirect = redirect; + } + } + + return resolveUrl("resources/preflight.py", options); +} + +// Methods generate behavior specifications for how `resources/preflight.py` +// should behave upon receiving a preflight request. +const PreflightBehavior = { + // The preflight response should fail with a non-2xx code. + failure: () => ({}), + + // The preflight response should be missing CORS headers. + // `uuid` should be a UUID that uniquely identifies the preflight request. + noCorsHeader: (uuid) => ({ + "preflight-uuid": uuid, + }), + + // The preflight response should be missing PNA headers. + // `uuid` should be a UUID that uniquely identifies the preflight request. + noPnaHeader: (uuid) => ({ + "preflight-uuid": uuid, + "preflight-headers": "cors", + }), + + // The preflight response should succeed. + // `uuid` should be a UUID that uniquely identifies the preflight request. + success: (uuid) => ({ + "preflight-uuid": uuid, + "preflight-headers": "cors+pna", + }), + + optionalSuccess: (uuid) => ({ + "preflight-uuid": uuid, + "preflight-headers": "cors+pna", + "is-preflight-optional": true, + }), + + // The preflight response should succeed and allow service-worker header. + // `uuid` should be a UUID that uniquely identifies the preflight request. + serviceWorkerSuccess: (uuid) => ({ + "preflight-uuid": uuid, + "preflight-headers": "cors+pna+sw", + }), + + // The preflight response should succeed only if it is the first preflight. + // `uuid` should be a UUID that uniquely identifies the preflight request. + singlePreflight: (uuid) => ({ + "preflight-uuid": uuid, + "preflight-headers": "cors+pna", + "expect-single-preflight": true, + }), + + // The preflight response should succeed and allow origins and headers for + // navigations. + navigation: (uuid) => ({ + "preflight-uuid": uuid, + "preflight-headers": "navigation", + }), +}; + +// Methods generate behavior specifications for how `resources/preflight.py` +// should behave upon receiving a regular (non-preflight) request. +const ResponseBehavior = { + // The response should succeed without CORS headers. + default: () => ({}), + + // The response should succeed with CORS headers. + allowCrossOrigin: () => ({ "final-headers": "cors" }), +}; + +const FetchTestResult = { + SUCCESS: { + ok: true, + body: "success", + }, + OPAQUE: { + ok: false, + type: "opaque", + body: "", + }, + FAILURE: { + error: "TypeError: Failed to fetch", + }, +}; + +// Runs a fetch test. Tries to fetch a given subresource from a given document. +// +// Main argument shape: +// +// { +// // Optional. Passed to `sourceResolveOptions()`. +// source, +// +// // Optional. Passed to `preflightUrl()`. +// target, +// +// // Optional. Passed to `fetch()`. +// fetchOptions, +// +// // Required. One of the values in `FetchTestResult`. +// expected, +// } +// +async function fetchTest(t, { source, target, fetchOptions, expected }) { + const sourceUrl = + resolveUrl("resources/fetcher.html", sourceResolveOptions(source)); + + const targetUrl = preflightUrl(target); + + const iframe = await appendIframe(t, document, sourceUrl); + const reply = futureMessage({ source: iframe.contentWindow }); + + const message = { + url: targetUrl.href, + options: fetchOptions, + }; + iframe.contentWindow.postMessage(message, "*"); + + const { error, ok, type, body } = await reply; + + assert_equals(error, expected.error, "error"); + + assert_equals(ok, expected.ok, "response ok"); + assert_equals(body, expected.body, "response body"); + + if (expected.type !== undefined) { + assert_equals(type, expected.type, "response type"); + } +} + +// Similar to `fetchTest`, but replaced iframes with fenced frames. +async function fencedFrameFetchTest(t, { source, target, fetchOptions, expected }) { + const fetcher_url = + resolveUrl("resources/fenced-frame-fetcher.https.html", sourceResolveOptions(source)); + + const target_url = preflightUrl(target); + target_url.searchParams.set("is-loaded-in-fenced-frame", true); + + fetcher_url.searchParams.set("mode", fetchOptions.mode); + fetcher_url.searchParams.set("method", fetchOptions.method); + fetcher_url.searchParams.set("url", target_url); + + const error_token = token(); + const ok_token = token(); + const body_token = token(); + const type_token = token(); + const source_url = generateURL(fetcher_url, [error_token, ok_token, body_token, type_token]); + + const urn = await generateURNFromFledge(source_url, []); + attachFencedFrame(urn); + + const error = await nextValueFromServer(error_token); + const ok = await nextValueFromServer(ok_token); + const body = await nextValueFromServer(body_token); + const type = await nextValueFromServer(type_token); + + assert_equals(error, expected.error || "" , "error"); + assert_equals(body, expected.body || "", "response body"); + assert_equals(ok, expected.ok !== undefined ? expected.ok.toString() : "", "response ok"); + if (expected.type !== undefined) { + assert_equals(type, expected.type, "response type"); + } +} + +const XhrTestResult = { + SUCCESS: { + loaded: true, + status: 200, + body: "success", + }, + FAILURE: { + loaded: false, + status: 0, + }, +}; + +// Runs an XHR test. Tries to fetch a given subresource from a given document. +// +// Main argument shape: +// +// { +// // Optional. Passed to `sourceResolveOptions()`. +// source, +// +// // Optional. Passed to `preflightUrl()`. +// target, +// +// // Optional. Method to use when sending the request. Defaults to "GET". +// method, +// +// // Required. One of the values in `XhrTestResult`. +// expected, +// } +// +async function xhrTest(t, { source, target, method, expected }) { + const sourceUrl = + resolveUrl("resources/xhr-sender.html", sourceResolveOptions(source)); + + const targetUrl = preflightUrl(target); + + const iframe = await appendIframe(t, document, sourceUrl); + const reply = futureMessage(); + + const message = { + url: targetUrl.href, + method: method, + }; + iframe.contentWindow.postMessage(message, "*"); + + const { loaded, status, body } = await reply; + + assert_equals(loaded, expected.loaded, "response loaded"); + assert_equals(status, expected.status, "response status"); + assert_equals(body, expected.body, "response body"); +} + +const FrameTestResult = { + SUCCESS: "loaded", + FAILURE: "timeout", +}; + +async function iframeTest(t, { source, target, expected }) { + // Allows running tests in parallel. + const uuid = token(); + + const targetUrl = preflightUrl(target); + targetUrl.searchParams.set("file", "iframed.html"); + targetUrl.searchParams.set("iframe-uuid", uuid); + targetUrl.searchParams.set( + "file-if-no-preflight-received", + "iframed-no-preflight-received.html", + ); + + const sourceUrl = + resolveUrl("resources/iframer.html", sourceResolveOptions(source)); + sourceUrl.searchParams.set("url", targetUrl); + + const messagePromise = futureMessage({ + filter: (data) => data.uuid === uuid, + }); + const iframe = await appendIframe(t, document, sourceUrl); + + // The grandchild frame posts a message iff it loads successfully. + // There exists no interoperable way to check whether an iframe failed to + // load, so we use a timeout. + // See: https://github.com/whatwg/html/issues/125 + const result = await Promise.race([ + messagePromise.then((data) => data.message), + new Promise((resolve) => { + t.step_timeout(() => resolve("timeout"), 2000 /* ms */); + }), + ]); + + assert_equals(result, expected); +} + +const NavigationTestResult = { + SUCCESS: "success", + FAILURE: "timeout", +}; + +async function windowOpenTest(t, { source, target, expected }) { + const targetUrl = preflightUrl(target); + targetUrl.searchParams.set("file", "openee.html"); + targetUrl.searchParams.set( + "file-if-no-preflight-received", + "no-preflight-received.html", + ); + + const sourceUrl = + resolveUrl("resources/opener.html", sourceResolveOptions(source)); + sourceUrl.searchParams.set("url", targetUrl); + + const iframe = await appendIframe(t, document, sourceUrl); + const reply = futureMessage({ source: iframe.contentWindow }); + + iframe.contentWindow.postMessage({ url: targetUrl.href }, "*"); + + const result = await Promise.race([ + reply, + new Promise((resolve) => { + t.step_timeout(() => resolve("timeout"), 10000 /* ms */); + }), + ]); + + assert_equals(result, expected); +} + +async function windowOpenExistingTest(t, { source, target, expected }) { + const targetUrl = preflightUrl(target); + targetUrl.searchParams.set("file", "openee.html"); + targetUrl.searchParams.set( + "file-if-no-preflight-received", + "no-preflight-received.html", + ); + + const sourceUrl = resolveUrl( + 'resources/open-to-existing-window.html', sourceResolveOptions(source)); + sourceUrl.searchParams.set("url", targetUrl); + sourceUrl.searchParams.set("token", token()); + + const iframe = await appendIframe(t, document, sourceUrl); + const reply = futureMessage({ source: iframe.contentWindow }); + + iframe.contentWindow.postMessage({ url: targetUrl.href }, "*"); + + const result = await Promise.race([ + reply, + new Promise((resolve) => { + t.step_timeout(() => resolve("timeout"), 10000 /* ms */); + }), + ]); + + assert_equals(result, expected); +} + +async function anchorTest(t, { source, target, expected }) { + const targetUrl = preflightUrl(target); + targetUrl.searchParams.set("file", "openee.html"); + targetUrl.searchParams.set( + "file-if-no-preflight-received", + "no-preflight-received.html", + ); + + const sourceUrl = + resolveUrl("resources/anchor.html", sourceResolveOptions(source)); + sourceUrl.searchParams.set("url", targetUrl); + + const iframe = await appendIframe(t, document, sourceUrl); + const reply = futureMessage({ source: iframe.contentWindow }); + + iframe.contentWindow.postMessage({ url: targetUrl.href }, "*"); + + const result = await Promise.race([ + reply, + new Promise((resolve) => { + t.step_timeout(() => resolve("timeout"), 10000 /* ms */); + }), + ]); + + assert_equals(result, expected); +} + +// Similar to `iframeTest`, but replaced iframes with fenced frames. +async function fencedFrameTest(t, { source, target, expected }) { + // Allows running tests in parallel. + const target_url = preflightUrl(target); + target_url.searchParams.set("file", "fenced-frame-private-network-access-target.https.html"); + target_url.searchParams.set("is-loaded-in-fenced-frame", true); + + const frame_loaded_key = token(); + const child_frame_target = generateURL(target_url, [frame_loaded_key]); + + const source_url = + resolveUrl("resources/fenced-frame-private-network-access.https.html", sourceResolveOptions(source)); + source_url.searchParams.set("fenced_frame_url", child_frame_target); + + const urn = await generateURNFromFledge(source_url, []); + attachFencedFrame(urn); + + // The grandchild fenced frame writes a value to the server iff it loads + // successfully. + const result = (expected == FrameTestResult.SUCCESS) ? + await nextValueFromServer(frame_loaded_key) : + await Promise.race([ + nextValueFromServer(frame_loaded_key), + new Promise((resolve) => { + t.step_timeout(() => resolve("timeout"), 10000 /* ms */); + }), + ]); + + assert_equals(result, expected); +} + +const iframeGrandparentTest = ({ + name, + grandparentServer, + child, + grandchild, + expected, +}) => promise_test_parallel(async (t) => { + // Allows running tests in parallel. + const grandparentUuid = token(); + const childUuid = token(); + const grandchildUuid = token(); + + const grandparentUrl = + resolveUrl("resources/executor.html", grandparentServer); + grandparentUrl.searchParams.set("executor-uuid", grandparentUuid); + + const childUrl = preflightUrl(child); + childUrl.searchParams.set("file", "executor.html"); + childUrl.searchParams.set("executor-uuid", childUuid); + + const grandchildUrl = preflightUrl(grandchild); + grandchildUrl.searchParams.set("file", "iframed.html"); + grandchildUrl.searchParams.set("iframe-uuid", grandchildUuid); + + const iframe = await appendIframe(t, document, grandparentUrl); + + const addChild = (url) => new Promise((resolve) => { + const child = document.createElement("iframe"); + child.src = url; + child.addEventListener("load", () => resolve(), { once: true }); + document.body.appendChild(child); + }); + + const grandparentCtx = new RemoteContext(grandparentUuid); + await grandparentCtx.execute_script(addChild, [childUrl]); + + // Add a blank grandchild frame inside the child. + // Apply a timeout to this step so that failures at this step do not block the + // execution of other tests. + const childCtx = new RemoteContext(childUuid); + await Promise.race([ + childCtx.execute_script(addChild, ["about:blank"]), + new Promise((resolve, reject) => t.step_timeout( + () => reject("timeout adding grandchild"), + 2000 /* ms */ + )), + ]); + + const messagePromise = futureMessage({ + filter: (data) => data.uuid === grandchildUuid, + }); + await grandparentCtx.execute_script((url) => { + const child = window.frames[0]; + const grandchild = child.frames[0]; + grandchild.location = url; + }, [grandchildUrl]); + + // The great-grandchild frame posts a message iff it loads successfully. + // There exists no interoperable way to check whether an iframe failed to + // load, so we use a timeout. + // See: https://github.com/whatwg/html/issues/125 + const result = await Promise.race([ + messagePromise.then((data) => data.message), + new Promise((resolve) => { + t.step_timeout(() => resolve("timeout"), 2000 /* ms */); + }), + ]); + + assert_equals(result, expected); +}, name); + +const WebsocketTestResult = { + SUCCESS: "open", + + // The code is a best guess. It is not yet entirely specified, so it may need + // to be changed in the future based on implementation experience. + FAILURE: "close: code 1006", +}; + +// Runs a websocket test. Attempts to open a websocket from `source` (in an +// iframe) to `target`, then checks that the result is as `expected`. +// +// Argument shape: +// +// { +// // Required. Passed to `sourceResolveOptions()`. +// source, +// +// // Required. +// target: { +// // Required. Target server. +// server, +// } +// +// // Required. Should be one of the values in `WebsocketTestResult`. +// expected, +// } +// +async function websocketTest(t, { source, target, expected }) { + const sourceUrl = + resolveUrl("resources/socket-opener.html", sourceResolveOptions(source)); + + const targetUrl = resolveUrl("/echo", target.server); + + const iframe = await appendIframe(t, document, sourceUrl); + + const reply = futureMessage(); + iframe.contentWindow.postMessage(targetUrl.href, "*"); + + assert_equals(await reply, expected); +} + +const WorkerScriptTestResult = { + SUCCESS: { loaded: true }, + FAILURE: { error: "unknown error" }, +}; + +function workerScriptUrl(target) { + const url = preflightUrl(target); + + url.searchParams.append("body", "postMessage({ loaded: true })") + url.searchParams.append("mime-type", "application/javascript") + + return url; +} + +async function workerScriptTest(t, { source, target, expected }) { + const sourceUrl = + resolveUrl("resources/worker-fetcher.html", sourceResolveOptions(source)); + + const targetUrl = workerScriptUrl(target); + + const iframe = await appendIframe(t, document, sourceUrl); + const reply = futureMessage(); + + iframe.contentWindow.postMessage({ url: targetUrl.href }, "*"); + + const { error, loaded } = await reply; + + assert_equals(error, expected.error, "worker error"); + assert_equals(loaded, expected.loaded, "response loaded"); +} + +async function nestedWorkerScriptTest(t, { source, target, expected }) { + const targetUrl = workerScriptUrl(target); + + const sourceUrl = resolveUrl( + "resources/worker-fetcher.js", sourceResolveOptions(source)); + sourceUrl.searchParams.append("url", targetUrl); + + // Iframe must be same-origin with the parent worker. + const iframeUrl = new URL("worker-fetcher.html", sourceUrl); + + const iframe = await appendIframe(t, document, iframeUrl); + const reply = futureMessage(); + + iframe.contentWindow.postMessage({ url: sourceUrl.href }, "*"); + + const { error, loaded } = await reply; + + assert_equals(error, expected.error, "worker error"); + assert_equals(loaded, expected.loaded, "response loaded"); +} + +async function sharedWorkerScriptTest(t, { source, target, expected }) { + const sourceUrl = resolveUrl("resources/shared-worker-fetcher.html", + sourceResolveOptions(source)); + const targetUrl = preflightUrl(target); + targetUrl.searchParams.append( + "body", "onconnect = (e) => e.ports[0].postMessage({ loaded: true })") + targetUrl.searchParams.append("mime-type", "application/javascript") + + const iframe = await appendIframe(t, document, sourceUrl); + const reply = futureMessage(); + + iframe.contentWindow.postMessage({ url: targetUrl.href }, "*"); + + const { error, loaded } = await reply; + + assert_equals(error, expected.error, "worker error"); + assert_equals(loaded, expected.loaded, "response loaded"); +} + +// Results that may be expected in tests. +const WorkerFetchTestResult = { + SUCCESS: { status: 200, body: "success" }, + FAILURE: { error: "TypeError" }, +}; + +async function workerFetchTest(t, { source, target, expected }) { + const targetUrl = preflightUrl(target); + + const sourceUrl = + resolveUrl("resources/fetcher.js", sourceResolveOptions(source)); + sourceUrl.searchParams.append("url", targetUrl.href); + + const fetcherUrl = new URL("worker-fetcher.html", sourceUrl); + + const reply = futureMessage(); + const iframe = await appendIframe(t, document, fetcherUrl); + + iframe.contentWindow.postMessage({ url: sourceUrl.href }, "*"); + + const { error, status, body } = await reply; + assert_equals(error, expected.error, "fetch error"); + assert_equals(status, expected.status, "response status"); + assert_equals(body, expected.body, "response body"); +} + +async function workerBlobFetchTest(t, { source, target, expected }) { + const targetUrl = preflightUrl(target); + + const fetcherUrl = resolveUrl( + 'resources/worker-blob-fetcher.html', sourceResolveOptions(source)); + + const reply = futureMessage(); + const iframe = await appendIframe(t, document, fetcherUrl); + + iframe.contentWindow.postMessage({ url: targetUrl.href }, "*"); + + const { error, status, body } = await reply; + assert_equals(error, expected.error, "fetch error"); + assert_equals(status, expected.status, "response status"); + assert_equals(body, expected.body, "response body"); +} + +async function sharedWorkerFetchTest(t, { source, target, expected }) { + const targetUrl = preflightUrl(target); + + const sourceUrl = + resolveUrl("resources/shared-fetcher.js", sourceResolveOptions(source)); + sourceUrl.searchParams.append("url", targetUrl.href); + + const fetcherUrl = new URL("shared-worker-fetcher.html", sourceUrl); + + const reply = futureMessage(); + const iframe = await appendIframe(t, document, fetcherUrl); + + iframe.contentWindow.postMessage({ url: sourceUrl.href }, "*"); + + const { error, status, body } = await reply; + assert_equals(error, expected.error, "fetch error"); + assert_equals(status, expected.status, "response status"); + assert_equals(body, expected.body, "response body"); +} + +async function sharedWorkerBlobFetchTest(t, { source, target, expected }) { + const targetUrl = preflightUrl(target); + + const fetcherUrl = resolveUrl( + 'resources/shared-worker-blob-fetcher.html', + sourceResolveOptions(source)); + + const reply = futureMessage(); + const iframe = await appendIframe(t, document, fetcherUrl); + + iframe.contentWindow.postMessage({ url: targetUrl.href }, "*"); + + const { error, status, body } = await reply; + assert_equals(error, expected.error, "fetch error"); + assert_equals(status, expected.status, "response status"); + assert_equals(body, expected.body, "response body"); +} diff --git a/testing/web-platform/tests/fetch/private-network-access/resources/worker-blob-fetcher.html b/testing/web-platform/tests/fetch/private-network-access/resources/worker-blob-fetcher.html new file mode 100644 index 0000000000..5a50271e11 --- /dev/null +++ b/testing/web-platform/tests/fetch/private-network-access/resources/worker-blob-fetcher.html @@ -0,0 +1,45 @@ + + +Worker Blob Fetcher + diff --git a/testing/web-platform/tests/fetch/private-network-access/resources/worker-fetcher.html b/testing/web-platform/tests/fetch/private-network-access/resources/worker-fetcher.html new file mode 100644 index 0000000000..bd155a532b --- /dev/null +++ b/testing/web-platform/tests/fetch/private-network-access/resources/worker-fetcher.html @@ -0,0 +1,18 @@ + + +Worker Fetcher + diff --git a/testing/web-platform/tests/fetch/private-network-access/resources/worker-fetcher.js b/testing/web-platform/tests/fetch/private-network-access/resources/worker-fetcher.js new file mode 100644 index 0000000000..aab49afe6f --- /dev/null +++ b/testing/web-platform/tests/fetch/private-network-access/resources/worker-fetcher.js @@ -0,0 +1,11 @@ +const url = new URL(self.location).searchParams.get("url"); +const worker = new Worker(url); + +// Relay messages from the worker to the parent frame. +worker.addEventListener("message", (evt) => { + self.postMessage(evt.data); +}); + +worker.addEventListener("error", (evt) => { + self.postMessage({ error: evt.message || "unknown error" }); +}); diff --git a/testing/web-platform/tests/fetch/private-network-access/resources/xhr-sender.html b/testing/web-platform/tests/fetch/private-network-access/resources/xhr-sender.html new file mode 100644 index 0000000000..b131fa41f9 --- /dev/null +++ b/testing/web-platform/tests/fetch/private-network-access/resources/xhr-sender.html @@ -0,0 +1,33 @@ + + +XHR Sender + diff --git a/testing/web-platform/tests/fetch/private-network-access/service-worker-background-fetch.tentative.https.window.js b/testing/web-platform/tests/fetch/private-network-access/service-worker-background-fetch.tentative.https.window.js new file mode 100644 index 0000000000..8d1028cc5e --- /dev/null +++ b/testing/web-platform/tests/fetch/private-network-access/service-worker-background-fetch.tentative.https.window.js @@ -0,0 +1,143 @@ +// META: script=/common/utils.js +// META: script=resources/support.sub.js +// META: timeout=long +// +// Spec: https://wicg.github.io/private-network-access/#integration-fetch +// Spec: https://wicg.github.io/background-fetch/ +// +// These tests check that background fetches from within `ServiceWorker` scripts +// are not subject to Private Network Access checks. + +// Results that may be expected in tests. +const TestResult = { + SUCCESS: { ok: true, body: "success", result: "success", failureReason: "" }, +}; + +async function makeTest(t, { source, target, expected }) { + const scriptUrl = + resolveUrl("resources/service-worker.js", sourceResolveOptions(source)); + + const bridgeUrl = new URL("service-worker-bridge.html", scriptUrl); + + const targetUrl = preflightUrl(target); + + const iframe = await appendIframe(t, document, bridgeUrl); + + const request = (message) => { + const reply = futureMessage(); + iframe.contentWindow.postMessage(message, "*"); + return reply; + }; + + { + const { error, loaded } = await request({ + action: "register", + url: scriptUrl.href, + }); + + assert_equals(error, undefined, "register error"); + assert_true(loaded, "response loaded"); + } + + { + const { error, state } = await request({ + action: "set-permission", + name: "background-fetch", + state: "granted", + }); + + assert_equals(error, undefined, "set permission error"); + assert_equals(state, "granted", "permission state"); + } + + { + const { error, result, failureReason, ok, body } = await request({ + action: "background-fetch", + url: targetUrl.href, + }); + + assert_equals(error, expected.error, "error"); + assert_equals(failureReason, expected.failureReason, "fetch failure reason"); + assert_equals(result, expected.result, "fetch result"); + assert_equals(ok, expected.ok, "response ok"); + assert_equals(body, expected.body, "response body"); + } +} + +promise_test(t => makeTest(t, { + source: { server: Server.HTTPS_LOCAL }, + target: { server: Server.HTTPS_LOCAL }, + expected: TestResult.SUCCESS, +}), "local to local: success."); + +promise_test(t => makeTest(t, { + source: { server: Server.HTTPS_PRIVATE }, + target: { + server: Server.HTTPS_LOCAL, + behavior: { response: ResponseBehavior.allowCrossOrigin() }, + }, + expected: TestResult.SUCCESS, +}), "private to local: success."); + +promise_test(t => makeTest(t, { + source: { server: Server.HTTPS_PRIVATE }, + target: { server: Server.HTTPS_PRIVATE }, + expected: TestResult.SUCCESS, +}), "private to private: success."); + +promise_test(t => makeTest(t, { + source: { server: Server.HTTPS_PUBLIC }, + target: { + server: Server.HTTPS_LOCAL, + behavior: { response: ResponseBehavior.allowCrossOrigin() }, + }, + expected: TestResult.SUCCESS, +}), "public to local: success."); + +promise_test(t => makeTest(t, { + source: { server: Server.HTTPS_PUBLIC }, + target: { + server: Server.HTTPS_PRIVATE, + behavior: { response: ResponseBehavior.allowCrossOrigin() }, + }, + expected: TestResult.SUCCESS, +}), "public to private: success."); + +promise_test(t => makeTest(t, { + source: { server: Server.HTTPS_PUBLIC }, + target: { server: Server.HTTPS_PUBLIC }, + expected: TestResult.SUCCESS, +}), "public to public: success."); + +promise_test(t => makeTest(t, { + source: { + server: Server.HTTPS_LOCAL, + treatAsPublic: true, + }, + target: { server: Server.HTTPS_LOCAL }, + expected: TestResult.SUCCESS, +}), "treat-as-public to local: success."); + +promise_test(t => makeTest(t, { + source: { + server: Server.HTTPS_LOCAL, + treatAsPublic: true, + }, + target: { + server: Server.HTTPS_PRIVATE, + behavior: { response: ResponseBehavior.allowCrossOrigin() }, + }, + expected: TestResult.SUCCESS, +}), "treat-as-public to private: success."); + +promise_test(t => makeTest(t, { + source: { + server: Server.HTTPS_LOCAL, + treatAsPublic: true, + }, + target: { + server: Server.HTTPS_PUBLIC, + behavior: { response: ResponseBehavior.allowCrossOrigin() }, + }, + expected: TestResult.SUCCESS, +}), "treat-as-public to public: success."); diff --git a/testing/web-platform/tests/fetch/private-network-access/service-worker-fetch.tentative.https.window.js b/testing/web-platform/tests/fetch/private-network-access/service-worker-fetch.tentative.https.window.js new file mode 100644 index 0000000000..cb6d1f79b0 --- /dev/null +++ b/testing/web-platform/tests/fetch/private-network-access/service-worker-fetch.tentative.https.window.js @@ -0,0 +1,235 @@ +// META: script=/common/utils.js +// META: script=resources/support.sub.js +// META: script=/common/subset-tests.js +// META: variant=?1-8 +// META: variant=?9-last +// +// Spec: https://wicg.github.io/private-network-access/#integration-fetch +// +// These tests check that fetches from within `ServiceWorker` scripts are +// subject to Private Network Access checks, just like fetches from within +// documents. + +// Results that may be expected in tests. +const TestResult = { + SUCCESS: { ok: true, body: "success" }, + FAILURE: { error: "TypeError" }, +}; + +async function makeTest(t, { source, target, expected }) { + const bridgeUrl = resolveUrl( + "resources/service-worker-bridge.html", + sourceResolveOptions({ server: source.server })); + + const scriptUrl = + resolveUrl("resources/service-worker.js", sourceResolveOptions(source)); + + const realTargetUrl = preflightUrl(target); + + // Fetch a URL within the service worker's scope, but tell it which URL to + // really fetch. + const targetUrl = new URL("service-worker-proxy", scriptUrl); + targetUrl.searchParams.append("proxied-url", realTargetUrl.href); + + const iframe = await appendIframe(t, document, bridgeUrl); + + const request = (message) => { + const reply = futureMessage(); + iframe.contentWindow.postMessage(message, "*"); + return reply; + }; + + { + const { error, loaded } = await request({ + action: "register", + url: scriptUrl.href, + }); + + assert_equals(error, undefined, "register error"); + assert_true(loaded, "response loaded"); + } + + try { + const { controlled, numControllerChanges } = await request({ + action: "wait", + numControllerChanges: 1, + }); + + assert_equals(numControllerChanges, 1, "controller change"); + assert_true(controlled, "bridge script is controlled"); + + const { error, ok, body } = await request({ + action: "fetch", + url: targetUrl.href, + }); + + assert_equals(error, expected.error, "fetch error"); + assert_equals(ok, expected.ok, "response ok"); + assert_equals(body, expected.body, "response body"); + } finally { + // Always unregister the service worker. + const { error, unregistered } = await request({ + action: "unregister", + scope: new URL("./", scriptUrl).href, + }); + + assert_equals(error, undefined, "unregister error"); + assert_true(unregistered, "unregistered"); + } +} + +subsetTest(promise_test, t => makeTest(t, { + source: { server: Server.HTTPS_LOCAL }, + target: { server: Server.HTTPS_LOCAL }, + expected: TestResult.SUCCESS, +}), "local to local: success."); + +subsetTest(promise_test, t => makeTest(t, { + source: { server: Server.HTTPS_PRIVATE }, + target: { + server: Server.HTTPS_LOCAL, + behavior: { response: ResponseBehavior.allowCrossOrigin() }, + }, + expected: TestResult.FAILURE, +}), "private to local: failed preflight."); + +subsetTest(promise_test, t => makeTest(t, { + source: { server: Server.HTTPS_PRIVATE }, + target: { + server: Server.HTTPS_LOCAL, + behavior: { + preflight: PreflightBehavior.success(token()), + response: ResponseBehavior.allowCrossOrigin(), + }, + }, + expected: TestResult.SUCCESS, +}), "private to local: success."); + +subsetTest(promise_test, t => makeTest(t, { + source: { server: Server.HTTPS_PRIVATE }, + target: { server: Server.HTTPS_PRIVATE }, + expected: TestResult.SUCCESS, +}), "private to private: success."); + +subsetTest(promise_test, t => makeTest(t, { + source: { server: Server.HTTPS_PUBLIC }, + target: { + server: Server.HTTPS_LOCAL, + behavior: { response: ResponseBehavior.allowCrossOrigin() }, + }, + expected: TestResult.FAILURE, +}), "public to local: failed preflight."); + +subsetTest(promise_test, t => makeTest(t, { + source: { server: Server.HTTPS_PUBLIC }, + target: { + server: Server.HTTPS_LOCAL, + behavior: { + preflight: PreflightBehavior.success(token()), + response: ResponseBehavior.allowCrossOrigin(), + }, + }, + expected: TestResult.SUCCESS, +}), "public to local: success."); + +subsetTest(promise_test, t => makeTest(t, { + source: { server: Server.HTTPS_PUBLIC }, + target: { + server: Server.HTTPS_PRIVATE, + behavior: { response: ResponseBehavior.allowCrossOrigin() }, + }, + expected: TestResult.FAILURE, +}), "public to private: failed preflight."); + +subsetTest(promise_test, t => makeTest(t, { + source: { server: Server.HTTPS_PUBLIC }, + target: { + server: Server.HTTPS_PRIVATE, + behavior: { + preflight: PreflightBehavior.success(token()), + response: ResponseBehavior.allowCrossOrigin(), + }, + }, + expected: TestResult.SUCCESS, +}), "public to private: success."); + +subsetTest(promise_test, t => makeTest(t, { + source: { server: Server.HTTPS_PUBLIC }, + target: { server: Server.HTTPS_PUBLIC }, + expected: TestResult.SUCCESS, +}), "public to public: success."); + +subsetTest(promise_test, t => makeTest(t, { + source: { + server: Server.HTTPS_LOCAL, + treatAsPublic: true, + }, + target: { + server: Server.OTHER_HTTPS_LOCAL, + behavior: { response: ResponseBehavior.allowCrossOrigin() }, + }, + expected: TestResult.FAILURE, +}), "treat-as-public to local: failed preflight."); + +subsetTest(promise_test, t => makeTest(t, { + source: { + server: Server.HTTPS_LOCAL, + treatAsPublic: true, + }, + target: { + server: Server.OTHER_HTTPS_LOCAL, + behavior: { + preflight: PreflightBehavior.success(token()), + response: ResponseBehavior.allowCrossOrigin(), + }, + }, + expected: TestResult.SUCCESS, +}), "treat-as-public to local: success."); + +subsetTest(promise_test, t => makeTest(t, { + source: { + server: Server.HTTPS_LOCAL, + treatAsPublic: true, + }, + target: { server: Server.HTTPS_LOCAL }, + expected: TestResult.SUCCESS, +}), "treat-as-public to local (same-origin): no preflight required."); + +subsetTest(promise_test, t => makeTest(t, { + source: { + server: Server.HTTPS_LOCAL, + treatAsPublic: true, + }, + target: { + server: Server.HTTPS_PRIVATE, + behavior: { response: ResponseBehavior.allowCrossOrigin() }, + }, + expected: TestResult.FAILURE, +}), "treat-as-public to private: failed preflight."); + +subsetTest(promise_test, t => makeTest(t, { + source: { + server: Server.HTTPS_LOCAL, + treatAsPublic: true, + }, + target: { + server: Server.HTTPS_PRIVATE, + behavior: { + preflight: PreflightBehavior.success(token()), + response: ResponseBehavior.allowCrossOrigin(), + }, + }, + expected: TestResult.SUCCESS, +}), "treat-as-public to private: success."); + +subsetTest(promise_test, t => makeTest(t, { + source: { + server: Server.HTTPS_LOCAL, + treatAsPublic: true, + }, + target: { + server: Server.HTTPS_PUBLIC, + behavior: { response: ResponseBehavior.allowCrossOrigin() }, + }, + expected: TestResult.SUCCESS, +}), "treat-as-public to public: success."); diff --git a/testing/web-platform/tests/fetch/private-network-access/service-worker-update.tentative.https.window.js b/testing/web-platform/tests/fetch/private-network-access/service-worker-update.tentative.https.window.js new file mode 100644 index 0000000000..4882d235bb --- /dev/null +++ b/testing/web-platform/tests/fetch/private-network-access/service-worker-update.tentative.https.window.js @@ -0,0 +1,106 @@ +// META: script=/common/utils.js +// META: script=resources/support.sub.js +// +// Spec: https://wicg.github.io/private-network-access/#integration-fetch +// +// These tests check that `ServiceWorker` script update fetches are exempt from +// Private Network Access checks because they are always same-origin and the +// origin is potentially trustworthy. The client of the fetch, for PNA purposes, +// is taken to be the previous script. +// +// The tests is carried out by instantiating a service worker from a resource +// that carries the `Content-Security-Policy: treat-as-public-address` header, +// such that the registration is placed in the public IP address space. When +// the script is fetched for an update, the client is thus considered public, +// yet the same-origin fetch observes that the server's IP endpoint is not +// necessarily in the public IP address space. +// +// See also: worker.https.window.js + +// Results that may be expected in tests. +const TestResult = { + SUCCESS: { updated: true }, + FAILURE: { error: "TypeError" }, +}; + +async function makeTest(t, { target, expected }) { + // The bridge must be same-origin with the service worker script. + const bridgeUrl = resolveUrl( + "resources/service-worker-bridge.html", + sourceResolveOptions({ server: target.server })); + + const scriptUrl = preflightUrl(target); + scriptUrl.searchParams.append("treat-as-public-once", token()); + scriptUrl.searchParams.append("mime-type", "application/javascript"); + scriptUrl.searchParams.append("file", "service-worker.js"); + scriptUrl.searchParams.append("random-js-prefix", true); + + const iframe = await appendIframe(t, document, bridgeUrl); + + const request = (message) => { + const reply = futureMessage(); + iframe.contentWindow.postMessage(message, "*"); + return reply; + }; + + { + const { error, loaded } = await request({ + action: "register", + url: scriptUrl.href, + }); + + assert_equals(error, undefined, "register error"); + assert_true(loaded, "response loaded"); + } + + try { + let { controlled, numControllerChanges } = await request({ + action: "wait", + numControllerChanges: 1, + }); + + assert_equals(numControllerChanges, 1, "controller change"); + assert_true(controlled, "bridge script is controlled"); + + const { error, updated } = await request({ action: "update" }); + + assert_equals(error, expected.error, "update error"); + assert_equals(updated, expected.updated, "registration updated"); + + // Stop here if we do not expect the update to succeed. + if (!expected.updated) { + return; + } + + ({ controlled, numControllerChanges } = await request({ + action: "wait", + numControllerChanges: 2, + })); + + assert_equals(numControllerChanges, 2, "controller change"); + assert_true(controlled, "bridge script still controlled"); + } finally { + const { error, unregistered } = await request({ + action: "unregister", + scope: new URL("./", scriptUrl).href, + }); + + assert_equals(error, undefined, "unregister error"); + assert_true(unregistered, "unregistered"); + } +} + +promise_test(t => makeTest(t, { + target: { server: Server.HTTPS_LOCAL }, + expected: TestResult.SUCCESS, +}), "update public to local: success."); + +promise_test(t => makeTest(t, { + target: { server: Server.HTTPS_PRIVATE }, + expected: TestResult.SUCCESS, +}), "update public to private: success."); + +promise_test(t => makeTest(t, { + target: { server: Server.HTTPS_PUBLIC }, + expected: TestResult.SUCCESS, +}), "update public to public: success."); diff --git a/testing/web-platform/tests/fetch/private-network-access/service-worker.tentative.https.window.js b/testing/web-platform/tests/fetch/private-network-access/service-worker.tentative.https.window.js new file mode 100644 index 0000000000..046f662a12 --- /dev/null +++ b/testing/web-platform/tests/fetch/private-network-access/service-worker.tentative.https.window.js @@ -0,0 +1,84 @@ +// META: script=/common/utils.js +// META: script=resources/support.sub.js +// +// Spec: https://wicg.github.io/private-network-access/#integration-fetch +// +// These tests check that initial `ServiceWorker` script fetches are exempt from +// Private Network Access checks because they are always same-origin and the +// origin is potentially trustworthy. +// +// See also: worker.https.window.js + +// Results that may be expected in tests. +const TestResult = { + SUCCESS: { + register: { loaded: true }, + unregister: { unregistered: true }, + }, + FAILURE: { + register: { error: "TypeError" }, + unregister: { unregistered: false, error: "no registration" }, + }, +}; + +async function makeTest(t, { source, target, expected }) { + const sourceUrl = resolveUrl("resources/service-worker-bridge.html", + sourceResolveOptions(source)); + + const targetUrl = preflightUrl(target); + targetUrl.searchParams.append("body", "undefined"); + targetUrl.searchParams.append("mime-type", "application/javascript"); + + const scope = resolveUrl(`resources/${token()}`, {...target.server}).href; + + const iframe = await appendIframe(t, document, sourceUrl); + + { + const reply = futureMessage(); + const message = { + action: "register", + url: targetUrl.href, + options: { scope }, + }; + iframe.contentWindow.postMessage(message, "*"); + + const { error, loaded } = await reply; + + assert_equals(error, expected.register.error, "register error"); + assert_equals(loaded, expected.register.loaded, "response loaded"); + } + + { + const reply = futureMessage(); + iframe.contentWindow.postMessage({ action: "unregister", scope }, "*"); + + const { error, unregistered } = await reply; + assert_equals(error, expected.unregister.error, "unregister error"); + assert_equals( + unregistered, expected.unregister.unregistered, "worker unregistered"); + } +} + +promise_test(t => makeTest(t, { + source: { + server: Server.HTTPS_LOCAL, + treatAsPublic: true, + }, + target: { server: Server.HTTPS_LOCAL }, + expected: TestResult.SUCCESS, +}), "treat-as-public to local: success."); + +promise_test(t => makeTest(t, { + source: { + server: Server.HTTPS_PRIVATE, + treatAsPublic: true, + }, + target: { server: Server.HTTPS_PRIVATE }, + expected: TestResult.SUCCESS, +}), "treat-as-public to private: success."); + +promise_test(t => makeTest(t, { + source: { server: Server.HTTPS_PUBLIC }, + target: { server: Server.HTTPS_PUBLIC }, + expected: TestResult.SUCCESS, +}), "public to public: success."); diff --git a/testing/web-platform/tests/fetch/private-network-access/shared-worker-blob-fetch.tentative.https.window.js b/testing/web-platform/tests/fetch/private-network-access/shared-worker-blob-fetch.tentative.https.window.js new file mode 100644 index 0000000000..269abb7edc --- /dev/null +++ b/testing/web-platform/tests/fetch/private-network-access/shared-worker-blob-fetch.tentative.https.window.js @@ -0,0 +1,168 @@ +// META: script=/common/utils.js +// META: script=resources/support.sub.js +// +// Spec: https://wicg.github.io/private-network-access/#integration-fetch +// +// These tests check that fetches from within `SharedWorker` scripts that are +// loaded from blob URLs are subject to Private Network Access checks, just like +// fetches from within documents. +// +// This file covers only those tests that must execute in a secure context. +// Other tests are defined in: shared-worker-blob-fetch.window.js + +promise_test(t => sharedWorkerBlobFetchTest(t, { + source: { server: Server.HTTPS_LOCAL }, + target: { server: Server.HTTPS_LOCAL }, + expected: WorkerFetchTestResult.SUCCESS, +}), "local to local: success."); + +promise_test(t => sharedWorkerBlobFetchTest(t, { + source: { server: Server.HTTPS_PRIVATE }, + target: { + server: Server.HTTPS_LOCAL, + behavior: { response: ResponseBehavior.allowCrossOrigin() }, + }, + expected: WorkerFetchTestResult.FAILURE, +}), "private to local: failed preflight."); + +promise_test(t => sharedWorkerBlobFetchTest(t, { + source: { server: Server.HTTPS_PRIVATE }, + target: { + server: Server.HTTPS_LOCAL, + behavior: { + preflight: PreflightBehavior.success(token()), + response: ResponseBehavior.allowCrossOrigin(), + }, + }, + expected: WorkerFetchTestResult.SUCCESS, +}), "private to local: success."); + +promise_test(t => sharedWorkerBlobFetchTest(t, { + source: { server: Server.HTTPS_PRIVATE }, + target: { server: Server.HTTPS_PRIVATE }, + expected: WorkerFetchTestResult.SUCCESS, +}), "private to private: success."); + +promise_test(t => sharedWorkerBlobFetchTest(t, { + source: { server: Server.HTTPS_PUBLIC }, + target: { + server: Server.HTTPS_LOCAL, + behavior: { response: ResponseBehavior.allowCrossOrigin() }, + }, + expected: WorkerFetchTestResult.FAILURE, +}), "public to local: failed preflight."); + +promise_test(t => sharedWorkerBlobFetchTest(t, { + source: { server: Server.HTTPS_PUBLIC }, + target: { + server: Server.HTTPS_LOCAL, + behavior: { + preflight: PreflightBehavior.success(token()), + response: ResponseBehavior.allowCrossOrigin(), + }, + }, + expected: WorkerFetchTestResult.SUCCESS, +}), "public to local: success."); + +promise_test(t => sharedWorkerBlobFetchTest(t, { + source: { server: Server.HTTPS_PUBLIC }, + target: { + server: Server.HTTPS_PRIVATE, + behavior: { response: ResponseBehavior.allowCrossOrigin() }, + }, + expected: WorkerFetchTestResult.FAILURE, +}), "public to private: failed preflight."); + +promise_test(t => sharedWorkerBlobFetchTest(t, { + source: { server: Server.HTTPS_PUBLIC }, + target: { + server: Server.HTTPS_PRIVATE, + behavior: { + preflight: PreflightBehavior.success(token()), + response: ResponseBehavior.allowCrossOrigin(), + }, + }, + expected: WorkerFetchTestResult.SUCCESS, +}), "public to private: success."); + +promise_test(t => sharedWorkerBlobFetchTest(t, { + source: { server: Server.HTTPS_PUBLIC }, + target: { server: Server.HTTPS_PUBLIC }, + expected: WorkerFetchTestResult.SUCCESS, +}), "public to public: success."); + +promise_test(t => sharedWorkerBlobFetchTest(t, { + source: { + server: Server.HTTPS_LOCAL, + treatAsPublic: true, + }, + target: { + server: Server.OTHER_HTTPS_LOCAL, + behavior: { response: ResponseBehavior.allowCrossOrigin() }, + }, + expected: WorkerFetchTestResult.FAILURE, +}), "treat-as-public to local: failed preflight."); + +promise_test(t => sharedWorkerBlobFetchTest(t, { + source: { + server: Server.HTTPS_LOCAL, + treatAsPublic: true, + }, + target: { + server: Server.OTHER_HTTPS_LOCAL, + behavior: { + preflight: PreflightBehavior.success(token()), + response: ResponseBehavior.allowCrossOrigin(), + }, + }, + expected: WorkerFetchTestResult.SUCCESS, +}), "treat-as-public to local: success."); + +promise_test(t => sharedWorkerBlobFetchTest(t, { + source: { + server: Server.HTTPS_LOCAL, + treatAsPublic: true, + }, + target: { server: Server.HTTPS_LOCAL }, + expected: WorkerFetchTestResult.SUCCESS, +}), "treat-as-public to local (same-origin): no preflight required."); + +promise_test(t => sharedWorkerBlobFetchTest(t, { + source: { + server: Server.HTTPS_LOCAL, + treatAsPublic: true, + }, + target: { + server: Server.HTTPS_PRIVATE, + behavior: { response: ResponseBehavior.allowCrossOrigin() }, + }, + expected: WorkerFetchTestResult.FAILURE, +}), "treat-as-public to private: failed preflight."); + +promise_test(t => sharedWorkerBlobFetchTest(t, { + source: { + server: Server.HTTPS_LOCAL, + treatAsPublic: true, + }, + target: { + server: Server.HTTPS_PRIVATE, + behavior: { + preflight: PreflightBehavior.success(token()), + response: ResponseBehavior.allowCrossOrigin(), + }, + }, + expected: WorkerFetchTestResult.SUCCESS, +}), "treat-as-public to private: success."); + +promise_test(t => sharedWorkerBlobFetchTest(t, { + source: { + server: Server.HTTPS_LOCAL, + treatAsPublic: true, + }, + target: { + server: Server.HTTPS_PUBLIC, + behavior: { response: ResponseBehavior.allowCrossOrigin() }, + }, + expected: WorkerFetchTestResult.SUCCESS, +}), "treat-as-public to public: success."); + diff --git a/testing/web-platform/tests/fetch/private-network-access/shared-worker-blob-fetch.tentative.window.js b/testing/web-platform/tests/fetch/private-network-access/shared-worker-blob-fetch.tentative.window.js new file mode 100644 index 0000000000..d430ea7383 --- /dev/null +++ b/testing/web-platform/tests/fetch/private-network-access/shared-worker-blob-fetch.tentative.window.js @@ -0,0 +1,173 @@ +// META: script=/common/utils.js +// META: script=resources/support.sub.js +// +// Spec: https://wicg.github.io/private-network-access/#integration-fetch +// +// These tests check that fetches from within `SharedWorker` scripts that are +// loaded from blob URLs are subject to Private Network Access checks, just like +// fetches from within documents. +// +// This file covers only those tests that must execute in a non-secure context. +// Other tests are defined in: shared-worker-blob-fetch.https.window.js + +promise_test(t => sharedWorkerBlobFetchTest(t, { + source: { server: Server.HTTP_LOCAL }, + target: { server: Server.HTTP_LOCAL }, + expected: WorkerFetchTestResult.SUCCESS, +}), "local to local: success."); + +promise_test(t => sharedWorkerBlobFetchTest(t, { + source: { server: Server.HTTP_PRIVATE }, + target: { + server: Server.HTTP_LOCAL, + behavior: { + preflight: PreflightBehavior.optionalSuccess(token()), + response: ResponseBehavior.allowCrossOrigin(), + }, + }, + expected: WorkerFetchTestResult.FAILURE, +}), "private to local: failure."); + +promise_test(t => sharedWorkerBlobFetchTest(t, { + source: { server: Server.HTTP_PRIVATE }, + target: { server: Server.HTTP_PRIVATE }, + expected: WorkerFetchTestResult.SUCCESS, +}), "private to private: success."); + +promise_test(t => sharedWorkerBlobFetchTest(t, { + source: { server: Server.HTTP_PUBLIC }, + target: { + server: Server.HTTP_LOCAL, + behavior: { + preflight: PreflightBehavior.optionalSuccess(token()), + response: ResponseBehavior.allowCrossOrigin(), + }, + }, + expected: WorkerFetchTestResult.FAILURE, +}), "public to local: failure."); + +promise_test(t => sharedWorkerBlobFetchTest(t, { + source: { server: Server.HTTP_PUBLIC }, + target: { + server: Server.HTTP_PRIVATE, + behavior: { + preflight: PreflightBehavior.optionalSuccess(token()), + response: ResponseBehavior.allowCrossOrigin(), + }, + }, + expected: WorkerFetchTestResult.FAILURE, +}), "public to private: failure."); + +promise_test(t => sharedWorkerBlobFetchTest(t, { + source: { server: Server.HTTP_PUBLIC }, + target: { server: Server.HTTP_PUBLIC }, + expected: WorkerFetchTestResult.SUCCESS, +}), "public to public: success."); + +promise_test(t => sharedWorkerBlobFetchTest(t, { + source: { + server: Server.HTTP_LOCAL, + treatAsPublic: true, + }, + target: { + server: Server.HTTP_LOCAL, + behavior: { preflight: PreflightBehavior.optionalSuccess(token()) }, + }, + expected: WorkerFetchTestResult.FAILURE, +}), "treat-as-public to local: failure."); + +promise_test(t => sharedWorkerBlobFetchTest(t, { + source: { + server: Server.HTTP_LOCAL, + treatAsPublic: true, + }, + target: { + server: Server.HTTP_PRIVATE, + behavior: { + preflight: PreflightBehavior.optionalSuccess(token()), + response: ResponseBehavior.allowCrossOrigin(), + }, + }, + expected: WorkerFetchTestResult.FAILURE, +}), "treat-as-public to private: failure."); + +promise_test(t => sharedWorkerBlobFetchTest(t, { + source: { + server: Server.HTTP_LOCAL, + treatAsPublic: true, + }, + target: { + server: Server.HTTP_PUBLIC, + behavior: { response: ResponseBehavior.allowCrossOrigin() }, + }, + expected: WorkerFetchTestResult.SUCCESS, +}), "treat-as-public to public: success."); + +// The following tests verify that workers served over HTTPS are not allowed to +// make private network requests because they are not secure contexts. + +promise_test(t => sharedWorkerBlobFetchTest(t, { + source: { server: Server.HTTPS_LOCAL }, + target: { + server: Server.HTTP_LOCAL, + behavior: { response: ResponseBehavior.allowCrossOrigin() }, + }, + expected: WorkerFetchTestResult.SUCCESS, +}), "local https to local: success."); + +promise_test(t => sharedWorkerBlobFetchTest(t, { + source: { server: Server.HTTPS_PUBLIC }, + target: { + server: Server.HTTP_LOCAL, + behavior: { + preflight: PreflightBehavior.optionalSuccess(token()), + response: ResponseBehavior.allowCrossOrigin(), + }, + }, + expected: WorkerFetchTestResult.FAILURE, +}), "private https to local: failure."); + +promise_test(t => sharedWorkerBlobFetchTest(t, { + source: { server: Server.HTTPS_PUBLIC }, + target: { + server: Server.HTTP_LOCAL, + behavior: { + preflight: PreflightBehavior.optionalSuccess(token()), + response: ResponseBehavior.allowCrossOrigin(), + }, + }, + expected: WorkerFetchTestResult.FAILURE, +}), "public https to local: failure."); + +promise_test(t => sharedWorkerBlobFetchTest(t, { + source: { server: Server.HTTPS_LOCAL }, + target: { + server: Server.HTTPS_LOCAL, + behavior: { response: ResponseBehavior.allowCrossOrigin() }, + }, + expected: WorkerFetchTestResult.SUCCESS, +}), "local https to local https: success."); + +promise_test(t => sharedWorkerBlobFetchTest(t, { + source: { server: Server.HTTPS_PRIVATE }, + target: { + server: Server.HTTPS_LOCAL, + behavior: { + preflight: PreflightBehavior.optionalSuccess(token()), + response: ResponseBehavior.allowCrossOrigin(), + }, + }, + expected: WorkerFetchTestResult.FAILURE, +}), "private https to local https: failure."); + +promise_test(t => sharedWorkerBlobFetchTest(t, { + source: { server: Server.HTTPS_PUBLIC }, + target: { + server: Server.HTTPS_LOCAL, + behavior: { + preflight: PreflightBehavior.optionalSuccess(token()), + response: ResponseBehavior.allowCrossOrigin(), + }, + }, + expected: WorkerFetchTestResult.FAILURE, +}), "public https to local https: failure."); diff --git a/testing/web-platform/tests/fetch/private-network-access/shared-worker-fetch.tentative.https.window.js b/testing/web-platform/tests/fetch/private-network-access/shared-worker-fetch.tentative.https.window.js new file mode 100644 index 0000000000..e5f2b94920 --- /dev/null +++ b/testing/web-platform/tests/fetch/private-network-access/shared-worker-fetch.tentative.https.window.js @@ -0,0 +1,167 @@ +// META: script=/common/utils.js +// META: script=resources/support.sub.js +// +// Spec: https://wicg.github.io/private-network-access/#integration-fetch +// +// These tests check that fetches from within `SharedWorker` scripts are subject +// to Private Network Access checks, just like fetches from within documents. +// +// This file covers only those tests that must execute in a secure context. +// Other tests are defined in: shared-worker-fetch.window.js + +promise_test(t => sharedWorkerFetchTest(t, { + source: { server: Server.HTTPS_LOCAL }, + target: { server: Server.HTTPS_LOCAL }, + expected: WorkerFetchTestResult.SUCCESS, +}), "local to local: success."); + +promise_test(t => sharedWorkerFetchTest(t, { + source: { server: Server.HTTPS_PRIVATE }, + target: { + server: Server.HTTPS_LOCAL, + behavior: { response: ResponseBehavior.allowCrossOrigin() }, + }, + expected: WorkerFetchTestResult.FAILURE, +}), "private to local: failure."); + +promise_test(t => sharedWorkerFetchTest(t, { + source: { server: Server.HTTPS_PRIVATE }, + target: { + server: Server.HTTPS_LOCAL, + behavior: { + preflight: PreflightBehavior.success(token()), + response: ResponseBehavior.allowCrossOrigin(), + }, + }, + expected: WorkerFetchTestResult.SUCCESS, +}), "private to local: success."); + +promise_test(t => sharedWorkerFetchTest(t, { + source: { server: Server.HTTPS_PRIVATE }, + target: { server: Server.HTTPS_PRIVATE }, + expected: WorkerFetchTestResult.SUCCESS, +}), "private to private: success."); + +promise_test(t => sharedWorkerFetchTest(t, { + source: { server: Server.HTTPS_PUBLIC }, + target: { + server: Server.HTTPS_LOCAL, + behavior: { response: ResponseBehavior.allowCrossOrigin() }, + }, + expected: WorkerFetchTestResult.FAILURE, +}), "public to local: failed preflight."); + +promise_test(t => sharedWorkerFetchTest(t, { + source: { server: Server.HTTPS_PUBLIC }, + target: { + server: Server.HTTPS_LOCAL, + behavior: { + preflight: PreflightBehavior.success(token()), + response: ResponseBehavior.allowCrossOrigin(), + }, + }, + expected: WorkerFetchTestResult.SUCCESS, +}), "public to local: success."); + +promise_test(t => sharedWorkerFetchTest(t, { + source: { server: Server.HTTPS_PUBLIC }, + target: { + server: Server.HTTPS_PRIVATE, + behavior: { response: ResponseBehavior.allowCrossOrigin() }, + }, + expected: WorkerFetchTestResult.FAILURE, +}), "public to private: failed preflight."); + +promise_test(t => sharedWorkerFetchTest(t, { + source: { server: Server.HTTPS_PUBLIC }, + target: { + server: Server.HTTPS_PRIVATE, + behavior: { + preflight: PreflightBehavior.success(token()), + response: ResponseBehavior.allowCrossOrigin(), + }, + }, + expected: WorkerFetchTestResult.SUCCESS, +}), "public to private: success."); + +promise_test(t => sharedWorkerFetchTest(t, { + source: { server: Server.HTTPS_PUBLIC }, + target: { server: Server.HTTPS_PUBLIC }, + expected: WorkerFetchTestResult.SUCCESS, +}), "public to public: success."); + +promise_test(t => sharedWorkerFetchTest(t, { + source: { + server: Server.HTTPS_LOCAL, + treatAsPublic: true, + }, + target: { + server: Server.OTHER_HTTPS_LOCAL, + behavior: { response: ResponseBehavior.allowCrossOrigin() }, + }, + expected: WorkerFetchTestResult.FAILURE, +}), "treat-as-public to local: failed preflight."); + +promise_test(t => sharedWorkerFetchTest(t, { + source: { + server: Server.HTTPS_LOCAL, + treatAsPublic: true, + }, + target: { + server: Server.OTHER_HTTPS_LOCAL, + behavior: { + preflight: PreflightBehavior.success(token()), + response: ResponseBehavior.allowCrossOrigin(), + }, + }, + expected: WorkerFetchTestResult.SUCCESS, +}), "treat-as-public to local: success."); + +promise_test(t => sharedWorkerFetchTest(t, { + source: { + server: Server.HTTPS_LOCAL, + treatAsPublic: true, + }, + target: { server: Server.HTTPS_LOCAL }, + expected: WorkerFetchTestResult.SUCCESS, +}), "treat-as-public to local (same-origin): no preflight required."); + +promise_test(t => sharedWorkerFetchTest(t, { + source: { + server: Server.HTTPS_LOCAL, + treatAsPublic: true, + }, + target: { + server: Server.HTTPS_PRIVATE, + behavior: { response: ResponseBehavior.allowCrossOrigin() }, + }, + expected: WorkerFetchTestResult.FAILURE, +}), "treat-as-public to private: failed preflight."); + +promise_test(t => sharedWorkerFetchTest(t, { + source: { + server: Server.HTTPS_LOCAL, + treatAsPublic: true, + }, + target: { + server: Server.HTTPS_PRIVATE, + behavior: { + preflight: PreflightBehavior.success(token()), + response: ResponseBehavior.allowCrossOrigin(), + }, + }, + expected: WorkerFetchTestResult.SUCCESS, +}), "treat-as-public to private: success."); + +promise_test(t => sharedWorkerFetchTest(t, { + source: { + server: Server.HTTPS_LOCAL, + treatAsPublic: true, + }, + target: { + server: Server.HTTPS_PUBLIC, + behavior: { response: ResponseBehavior.allowCrossOrigin() }, + }, + expected: WorkerFetchTestResult.SUCCESS, +}), "treat-as-public to public: success."); + diff --git a/testing/web-platform/tests/fetch/private-network-access/shared-worker-fetch.tentative.window.js b/testing/web-platform/tests/fetch/private-network-access/shared-worker-fetch.tentative.window.js new file mode 100644 index 0000000000..9bc1a89bea --- /dev/null +++ b/testing/web-platform/tests/fetch/private-network-access/shared-worker-fetch.tentative.window.js @@ -0,0 +1,154 @@ +// META: script=/common/utils.js +// META: script=resources/support.sub.js +// +// Spec: https://wicg.github.io/private-network-access/#integration-fetch +// +// These tests check that fetches from within `SharedWorker` scripts are subject +// to Private Network Access checks, just like fetches from within documents. +// +// This file covers only those tests that must execute in a non-secure context. +// Other tests are defined in: shared-worker-fetch.https.window.js + +promise_test(t => sharedWorkerFetchTest(t, { + source: { server: Server.HTTP_LOCAL }, + target: { server: Server.HTTP_LOCAL }, + expected: WorkerFetchTestResult.SUCCESS, +}), "local to local: success."); + +promise_test(t => sharedWorkerFetchTest(t, { + source: { server: Server.HTTP_PRIVATE }, + target: { + server: Server.HTTP_LOCAL, + behavior: { + preflight: PreflightBehavior.optionalSuccess(token()), + response: ResponseBehavior.allowCrossOrigin(), + }, + }, + expected: WorkerFetchTestResult.FAILURE, +}), "private to local: failure."); + +promise_test(t => sharedWorkerFetchTest(t, { + source: { server: Server.HTTP_PRIVATE }, + target: { server: Server.HTTP_PRIVATE }, + expected: WorkerFetchTestResult.SUCCESS, +}), "private to private: success."); + +promise_test(t => sharedWorkerFetchTest(t, { + source: { server: Server.HTTP_PUBLIC }, + target: { + server: Server.HTTP_LOCAL, + behavior: { + preflight: PreflightBehavior.optionalSuccess(token()), + response: ResponseBehavior.allowCrossOrigin(), + }, + }, + expected: WorkerFetchTestResult.FAILURE, +}), "public to local: failure."); + +promise_test(t => sharedWorkerFetchTest(t, { + source: { server: Server.HTTP_PUBLIC }, + target: { + server: Server.HTTP_PRIVATE, + behavior: { + preflight: PreflightBehavior.optionalSuccess(token()), + response: ResponseBehavior.allowCrossOrigin(), + }, + }, + expected: WorkerFetchTestResult.FAILURE, +}), "public to private: failure."); + +promise_test(t => sharedWorkerFetchTest(t, { + source: { server: Server.HTTP_PUBLIC }, + target: { server: Server.HTTP_PUBLIC }, + expected: WorkerFetchTestResult.SUCCESS, +}), "public to public: success."); + +promise_test(t => sharedWorkerFetchTest(t, { + source: { + server: Server.HTTP_LOCAL, + treatAsPublic: true, + }, + target: { + server: Server.HTTP_LOCAL, + behavior: { preflight: PreflightBehavior.optionalSuccess(token()) }, + }, + expected: WorkerFetchTestResult.FAILURE, +}), "treat-as-public to local: failure."); + +promise_test(t => sharedWorkerFetchTest(t, { + source: { + server: Server.HTTP_LOCAL, + treatAsPublic: true, + }, + target: { + server: Server.HTTP_PRIVATE, + behavior: { + preflight: PreflightBehavior.optionalSuccess(token()), + response: ResponseBehavior.allowCrossOrigin(), + }, + }, + expected: WorkerFetchTestResult.FAILURE, +}), "treat-as-public to private: failure."); + +promise_test(t => sharedWorkerFetchTest(t, { + source: { + server: Server.HTTP_LOCAL, + treatAsPublic: true, + }, + target: { + server: Server.HTTP_PUBLIC, + behavior: { response: ResponseBehavior.allowCrossOrigin() }, + }, + expected: WorkerFetchTestResult.SUCCESS, +}), "treat-as-public to public: success."); + +// The following tests verify that workers served over HTTPS are not allowed to +// make private network requests because they are not secure contexts. + +promise_test(t => sharedWorkerFetchTest(t, { + source: { server: Server.HTTPS_LOCAL }, + target: { + server: Server.HTTP_LOCAL, + behavior: { + preflight: PreflightBehavior.optionalSuccess(token()), + response: ResponseBehavior.allowCrossOrigin(), + }, + }, + expected: WorkerFetchTestResult.SUCCESS, +}), "local https to local: success."); + +promise_test(t => sharedWorkerFetchTest(t, { + source: { server: Server.HTTPS_PRIVATE }, + target: { + server: Server.HTTP_LOCAL, + behavior: { + preflight: PreflightBehavior.optionalSuccess(token()), + response: ResponseBehavior.allowCrossOrigin(), + }, + }, + expected: WorkerFetchTestResult.FAILURE, +}), "private https to local: failure."); + +promise_test(t => sharedWorkerFetchTest(t, { + source: { server: Server.HTTPS_PUBLIC }, + target: { + server: Server.HTTP_LOCAL, + behavior: { + preflight: PreflightBehavior.optionalSuccess(token()), + response: ResponseBehavior.allowCrossOrigin(), + }, + }, + expected: WorkerFetchTestResult.FAILURE, +}), "public https to local: failure."); + +promise_test(t => sharedWorkerFetchTest(t, { + source: { server: Server.HTTPS_PUBLIC }, + target: { + server: Server.HTTP_PRIVATE, + behavior: { + preflight: PreflightBehavior.optionalSuccess(token()), + response: ResponseBehavior.allowCrossOrigin(), + }, + }, + expected: WorkerFetchTestResult.FAILURE, +}), "public https to private: failure."); diff --git a/testing/web-platform/tests/fetch/private-network-access/shared-worker.tentative.https.window.js b/testing/web-platform/tests/fetch/private-network-access/shared-worker.tentative.https.window.js new file mode 100644 index 0000000000..24ae108782 --- /dev/null +++ b/testing/web-platform/tests/fetch/private-network-access/shared-worker.tentative.https.window.js @@ -0,0 +1,34 @@ +// META: script=/common/utils.js +// META: script=resources/support.sub.js +// +// Spec: https://wicg.github.io/private-network-access/#integration-fetch +// +// These tests mirror `Worker` tests, except using `SharedWorker`. +// See also: worker.https.window.js +// +// This file covers only those tests that must execute in a secure context. +// Other tests are defined in: shared-worker.window.js + +promise_test(t => sharedWorkerScriptTest(t, { + source: { + server: Server.HTTPS_LOCAL, + treatAsPublic: true, + }, + target: { server: Server.HTTPS_LOCAL }, + expected: WorkerScriptTestResult.SUCCESS, +}), "treat-as-public to local: success."); + +promise_test(t => sharedWorkerScriptTest(t, { + source: { + server: Server.HTTPS_PRIVATE, + treatAsPublic: true, + }, + target: { server: Server.HTTPS_PRIVATE }, + expected: WorkerScriptTestResult.SUCCESS, +}), "treat-as-public to private: success."); + +promise_test(t => sharedWorkerScriptTest(t, { + source: { server: Server.HTTPS_PUBLIC }, + target: { server: Server.HTTPS_PUBLIC }, + expected: WorkerScriptTestResult.SUCCESS, +}), "public to public: success."); diff --git a/testing/web-platform/tests/fetch/private-network-access/shared-worker.tentative.window.js b/testing/web-platform/tests/fetch/private-network-access/shared-worker.tentative.window.js new file mode 100644 index 0000000000..ffa8a360c7 --- /dev/null +++ b/testing/web-platform/tests/fetch/private-network-access/shared-worker.tentative.window.js @@ -0,0 +1,34 @@ +// META: script=/common/utils.js +// META: script=resources/support.sub.js +// +// Spec: https://wicg.github.io/private-network-access/#integration-fetch +// +// These tests mirror `Worker` tests, except using `SharedWorker`. +// See also: shared-worker.window.js +// +// This file covers only those tests that must execute in a non secure context. +// Other tests are defined in: shared-worker.https.window.js + +promise_test(t => sharedWorkerScriptTest(t, { + source: { + server: Server.HTTP_LOCAL, + treatAsPublic: true, + }, + target: { server: Server.HTTP_LOCAL }, + expected: WorkerScriptTestResult.FAILURE, +}), "treat-as-public to local: failure."); + +promise_test(t => sharedWorkerScriptTest(t, { + source: { + server: Server.HTTP_PRIVATE, + treatAsPublic: true, + }, + target: { server: Server.HTTP_PRIVATE }, + expected: WorkerScriptTestResult.FAILURE, +}), "treat-as-public to private: failure."); + +promise_test(t => sharedWorkerScriptTest(t, { + source: { server: Server.HTTP_PUBLIC }, + target: { server: Server.HTTP_PUBLIC }, + expected: WorkerScriptTestResult.SUCCESS, +}), "public to public: success."); diff --git a/testing/web-platform/tests/fetch/private-network-access/websocket.tentative.https.window.js b/testing/web-platform/tests/fetch/private-network-access/websocket.tentative.https.window.js new file mode 100644 index 0000000000..0731896098 --- /dev/null +++ b/testing/web-platform/tests/fetch/private-network-access/websocket.tentative.https.window.js @@ -0,0 +1,40 @@ +// META: script=resources/support.sub.js +// +// Spec: https://wicg.github.io/private-network-access/#integration-fetch +// +// These tests verify that websocket connections behave similarly to fetches. +// +// This file covers only those tests that must execute in a secure context. +// Other tests are defined in: websocket.https.window.js + +setup(() => { + // Making sure we are in a secure context, as expected. + assert_true(window.isSecureContext); +}); + +promise_test(t => websocketTest(t, { + source: { server: Server.HTTPS_LOCAL }, + target: { server: Server.WSS_LOCAL }, + expected: WebsocketTestResult.SUCCESS, +}), "local to local: websocket success."); + +promise_test(t => websocketTest(t, { + source: { server: Server.HTTPS_PRIVATE }, + target: { server: Server.WSS_LOCAL }, + expected: WebsocketTestResult.SUCCESS, +}), "private to local: websocket success."); + +promise_test(t => websocketTest(t, { + source: { server: Server.HTTPS_PUBLIC }, + target: { server: Server.WSS_LOCAL }, + expected: WebsocketTestResult.SUCCESS, +}), "public to local: websocket success."); + +promise_test(t => websocketTest(t, { + source: { + server: Server.HTTPS_LOCAL, + treatAsPublic: true, + }, + target: { server: Server.WSS_LOCAL }, + expected: WebsocketTestResult.SUCCESS, +}), "treat-as-public to local: websocket success."); diff --git a/testing/web-platform/tests/fetch/private-network-access/websocket.tentative.window.js b/testing/web-platform/tests/fetch/private-network-access/websocket.tentative.window.js new file mode 100644 index 0000000000..a44cfaedec --- /dev/null +++ b/testing/web-platform/tests/fetch/private-network-access/websocket.tentative.window.js @@ -0,0 +1,40 @@ +// META: script=resources/support.sub.js +// +// Spec: https://wicg.github.io/private-network-access/#integration-fetch + +// These tests verify that websocket connections behave similarly to fetches. +// +// This file covers only those tests that must execute in a non secure context. +// Other tests are defined in: websocket.https.window.js + +setup(() => { + // Making sure we are in a non secure context, as expected. + assert_false(window.isSecureContext); +}); + +promise_test(t => websocketTest(t, { + source: { server: Server.HTTP_LOCAL }, + target: { server: Server.WS_LOCAL }, + expected: WebsocketTestResult.SUCCESS, +}), "local to local: websocket success."); + +promise_test(t => websocketTest(t, { + source: { server: Server.HTTP_PRIVATE }, + target: { server: Server.WS_LOCAL }, + expected: WebsocketTestResult.FAILURE, +}), "private to local: websocket failure."); + +promise_test(t => websocketTest(t, { + source: { server: Server.HTTP_PUBLIC }, + target: { server: Server.WS_LOCAL }, + expected: WebsocketTestResult.FAILURE, +}), "public to local: websocket failure."); + +promise_test(t => websocketTest(t, { + source: { + server: Server.HTTP_LOCAL, + treatAsPublic: true, + }, + target: { server: Server.WS_LOCAL }, + expected: WebsocketTestResult.FAILURE, +}), "treat-as-public to local: websocket failure."); diff --git a/testing/web-platform/tests/fetch/private-network-access/window-open-existing.tentative.https.window.js b/testing/web-platform/tests/fetch/private-network-access/window-open-existing.tentative.https.window.js new file mode 100644 index 0000000000..6a2a624fc8 --- /dev/null +++ b/testing/web-platform/tests/fetch/private-network-access/window-open-existing.tentative.https.window.js @@ -0,0 +1,209 @@ +// META: script=/common/subset-tests-by-key.js +// META: script=/common/dispatcher/dispatcher.js +// META: script=/common/utils.js +// META: script=resources/support.sub.js +// META: timeout=long +// META: variant=?include=from-local +// META: variant=?include=from-private +// META: variant=?include=from-public +// META: variant=?include=from-treat-as-public +// +// These tests verify that secure contexts can navigate to less-public address +// spaces via window.open to an existing window iff the target server responds +// affirmatively to preflight requests. + +setup(() => { + assert_true(window.isSecureContext); +}); + +// Source: secure local context. +// +// All fetches unaffected by Private Network Access. + +subsetTestByKey( + 'from-local', promise_test_parallel, + t => windowOpenExistingTest(t, { + source: {server: Server.HTTPS_LOCAL}, + target: {server: Server.HTTPS_LOCAL}, + expected: NavigationTestResult.SUCCESS, + }), + 'local to local: no preflight required.'); + +subsetTestByKey( + 'from-local', promise_test_parallel, + t => windowOpenExistingTest(t, { + source: {server: Server.HTTPS_LOCAL}, + target: {server: Server.HTTPS_PRIVATE}, + expected: NavigationTestResult.SUCCESS, + }), + 'local to private: no preflight required.'); + +subsetTestByKey( + 'from-local', promise_test_parallel, + t => windowOpenExistingTest(t, { + source: {server: Server.HTTPS_LOCAL}, + target: {server: Server.HTTPS_PUBLIC}, + expected: NavigationTestResult.SUCCESS, + }), + 'local to public: no preflight required.'); + +// Generates tests of preflight behavior for a single (source, target) pair. +// +// Scenarios: +// +// - preflight response has non-2xx HTTP code +// - preflight response is missing CORS headers +// - preflight response is missing the PNA-specific `Access-Control` header +// - success +// +function makePreflightTests({ + key, + sourceName, + sourceServer, + sourceTreatAsPublic, + targetName, + targetServer, +}) { + const prefix = + `${sourceName} to ${targetName}: `; + + const source = { + server: sourceServer, + treatAsPublic: sourceTreatAsPublic, + }; + + promise_test_parallel(t => windowOpenExistingTest(t, { + source, + target: { + server: targetServer, + behavior: { preflight: PreflightBehavior.failure() }, + }, + expected: NavigationTestResult.FAILURE, + }), prefix + "failed preflight."); + + promise_test_parallel(t => windowOpenExistingTest(t, { + source, + target: { + server: targetServer, + behavior: { preflight: PreflightBehavior.noCorsHeader(token()) }, + }, + expected: NavigationTestResult.FAILURE, + }), prefix + "missing CORS headers."); + + promise_test_parallel(t => windowOpenExistingTest(t, { + source, + target: { + server: targetServer, + behavior: { preflight: PreflightBehavior.noPnaHeader(token()) }, + }, + expected: NavigationTestResult.FAILURE, + }), prefix + "missing PNA header."); + + promise_test_parallel(t => windowOpenExistingTest(t, { + source, + target: { + server: targetServer, + behavior: { preflight: PreflightBehavior.navigation(token()) }, + }, + expected: NavigationTestResult.SUCCESS, + }), prefix + "success."); +} + +// Source: private secure context. +// +// Navigating to the local address space require a successful preflight response +// carrying a PNA-specific header. + +subsetTestByKey('from-private', makePreflightTests, { + sourceServer: Server.HTTPS_PRIVATE, + sourceName: 'private', + targetServer: Server.HTTPS_LOCAL, + targetName: 'local', +}); + +subsetTestByKey( + 'from-private', promise_test_parallel, + t => windowOpenExistingTest(t, { + source: {server: Server.HTTPS_PRIVATE}, + target: {server: Server.HTTPS_PRIVATE}, + expected: NavigationTestResult.SUCCESS, + }), + 'private to private: no preflight required.'); + +subsetTestByKey( + 'from-private', promise_test_parallel, + t => windowOpenExistingTest(t, { + source: {server: Server.HTTPS_PRIVATE}, + target: {server: Server.HTTPS_PUBLIC}, + expected: NavigationTestResult.SUCCESS, + }), + 'private to public: no preflight required.'); + +// Source: public secure context. +// +// Navigating to the local and private address spaces require a successful +// preflight response carrying a PNA-specific header. + +subsetTestByKey('from-public', makePreflightTests, { + sourceServer: Server.HTTPS_PUBLIC, + sourceName: "public", + targetServer: Server.HTTPS_LOCAL, + targetName: "local", +}); + +subsetTestByKey('from-public', makePreflightTests, { + sourceServer: Server.HTTPS_PUBLIC, + sourceName: "public", + targetServer: Server.HTTPS_PRIVATE, + targetName: "private", +}); + +subsetTestByKey( + 'from-public', promise_test_parallel, + t => windowOpenExistingTest(t, { + source: {server: Server.HTTPS_PUBLIC}, + target: {server: Server.HTTPS_PUBLIC}, + expected: NavigationTestResult.SUCCESS, + }), + 'public to public: no preflight required.'); + +// The following tests verify that `CSP: treat-as-public-address` makes +// documents behave as if they had been served from a public IP address. + +subsetTestByKey('from-treat-as-public', makePreflightTests, { + sourceServer: Server.HTTPS_LOCAL, + sourceTreatAsPublic: true, + sourceName: "treat-as-public-address", + targetServer: Server.OTHER_HTTPS_LOCAL, + targetName: "local", +}); + +subsetTestByKey("from-treat-as-public", promise_test_parallel, + t => windowOpenExistingTest(t, { + source: { + server: Server.HTTPS_LOCAL, + treatAsPublic: true, + }, + target: {server: Server.HTTPS_LOCAL}, + expected: NavigationTestResult.SUCCESS, + }), + 'treat-as-public-address to local (same-origin): no preflight required.'); + +subsetTestByKey('from-treat-as-public', makePreflightTests, { + sourceServer: Server.HTTPS_LOCAL, + sourceTreatAsPublic: true, + sourceName: 'treat-as-public-address', + targetServer: Server.HTTPS_PRIVATE, + targetName: 'private', +}); + +subsetTestByKey("from-treat-as-public", promise_test_parallel, + t => windowOpenExistingTest(t, { + source: { + server: Server.HTTPS_LOCAL, + treatAsPublic: true, + }, + target: {server: Server.HTTPS_PUBLIC}, + expected: NavigationTestResult.SUCCESS, + }), + 'treat-as-public-address to public: no preflight required.'); diff --git a/testing/web-platform/tests/fetch/private-network-access/window-open-existing.tentative.window.js b/testing/web-platform/tests/fetch/private-network-access/window-open-existing.tentative.window.js new file mode 100644 index 0000000000..5a6cd4c5cf --- /dev/null +++ b/testing/web-platform/tests/fetch/private-network-access/window-open-existing.tentative.window.js @@ -0,0 +1,95 @@ +// META: script=/common/dispatcher/dispatcher.js +// META: script=/common/utils.js +// META: script=resources/support.sub.js +// META: timeout=long +// +// Spec: https://wicg.github.io/private-network-access/ +// +// These tests verify that non-secure contexts cannot navigate to less-public +// address spaces via window.open to an existing window. + +setup(() => { + // Making sure we are in a non secure context, as expected. + assert_false(window.isSecureContext); +}); + +promise_test_parallel(t => windowOpenExistingTest(t, { + source: { server: Server.HTTP_LOCAL }, + target: { server: Server.HTTP_LOCAL }, + expected: NavigationTestResult.SUCCESS, +}), "local to local: no preflight required."); + +promise_test_parallel(t => windowOpenExistingTest(t, { + source: { server: Server.HTTP_LOCAL }, + target: { server: Server.HTTP_PRIVATE }, + expected: NavigationTestResult.SUCCESS, +}), "local to private: no preflight required."); + +promise_test_parallel(t => windowOpenExistingTest(t, { + source: { server: Server.HTTP_LOCAL }, + target: { server: Server.HTTP_PUBLIC }, + expected: NavigationTestResult.SUCCESS, +}), "local to public: no preflight required."); + +promise_test_parallel(t => windowOpenExistingTest(t, { + source: { server: Server.HTTP_PRIVATE }, + target: { server: Server.HTTP_LOCAL }, + expected: NavigationTestResult.FAILURE, +}), "private to local: failure."); + +promise_test_parallel(t => windowOpenExistingTest(t, { + source: { server: Server.HTTP_PRIVATE }, + target: { server: Server.HTTP_PRIVATE }, + expected: NavigationTestResult.SUCCESS, +}), "private to private: no preflight required."); + +promise_test_parallel(t => windowOpenExistingTest(t, { + source: { server: Server.HTTP_PRIVATE }, + target: { server: Server.HTTP_PUBLIC }, + expected: NavigationTestResult.SUCCESS, +}), "private to public: no preflight required."); + +promise_test_parallel(t => windowOpenExistingTest(t, { + source: { server: Server.HTTP_PUBLIC }, + target: { server: Server.HTTP_LOCAL }, + expected: NavigationTestResult.FAILURE, +}), "public to local: failure."); + +promise_test_parallel(t => windowOpenExistingTest(t, { + source: { server: Server.HTTP_PUBLIC }, + target: { server: Server.HTTP_PRIVATE }, + expected: NavigationTestResult.FAILURE, +}), "public to private: failure."); + +promise_test_parallel(t => windowOpenExistingTest(t, { + source: { server: Server.HTTP_PUBLIC }, + target: { server: Server.HTTP_PUBLIC }, + expected: NavigationTestResult.SUCCESS, +}), "public to public: no preflight required."); + +promise_test_parallel(t => windowOpenExistingTest(t, { + source: { + server: Server.HTTP_LOCAL, + treatAsPublic: true, + }, + target: { server: Server.HTTP_LOCAL }, + expected: NavigationTestResult.FAILURE, +}), "treat-as-public-address to local: failure."); + +promise_test_parallel(t => windowOpenExistingTest(t, { + source: { + server: Server.HTTP_LOCAL, + treatAsPublic: true, + }, + target: { server: Server.HTTP_PRIVATE }, + expected: NavigationTestResult.FAILURE, +}), "treat-as-public-address to private: failure."); + +promise_test_parallel(t => windowOpenExistingTest(t, { + source: { + server: Server.HTTP_LOCAL, + treatAsPublic: true, + }, + target: { server: Server.HTTP_PUBLIC }, + expected: NavigationTestResult.SUCCESS, +}), "treat-as-public-address to public: no preflight required."); diff --git a/testing/web-platform/tests/fetch/private-network-access/window-open.tentative.https.window.js b/testing/web-platform/tests/fetch/private-network-access/window-open.tentative.https.window.js new file mode 100644 index 0000000000..6793d1f3b4 --- /dev/null +++ b/testing/web-platform/tests/fetch/private-network-access/window-open.tentative.https.window.js @@ -0,0 +1,205 @@ +// META: script=/common/subset-tests-by-key.js +// META: script=/common/dispatcher/dispatcher.js +// META: script=/common/utils.js +// META: script=resources/support.sub.js +// META: timeout=long +// META: variant=?include=from-local +// META: variant=?include=from-private +// META: variant=?include=from-public +// META: variant=?include=from-treat-as-public +// +// These tests verify that secure contexts can navigate to less-public address +// spaces via window.open iff the target server responds affirmatively to +// preflight requests. + +setup(() => { + assert_true(window.isSecureContext); +}); + +// Source: secure local context. +// +// All fetches unaffected by Private Network Access. + +subsetTestByKey("from-local", promise_test_parallel, t => windowOpenTest(t, { + source: { server: Server.HTTPS_LOCAL }, + target: { server: Server.HTTPS_LOCAL }, + expected: NavigationTestResult.SUCCESS, +}), "local to local: no preflight required."); + +subsetTestByKey("from-local", promise_test_parallel, t => windowOpenTest(t, { + source: { server: Server.HTTPS_LOCAL }, + target: { server: Server.HTTPS_PRIVATE }, + expected: NavigationTestResult.SUCCESS, +}), "local to private: no preflight required."); + +subsetTestByKey("from-local", promise_test_parallel, t => windowOpenTest(t, { + source: { server: Server.HTTPS_LOCAL }, + target: { server: Server.HTTPS_PUBLIC }, + expected: NavigationTestResult.SUCCESS, +}), "local to public: no preflight required."); + +// Generates tests of preflight behavior for a single (source, target) pair. +// +// Scenarios: +// +// - preflight response has non-2xx HTTP code +// - preflight response is missing CORS headers +// - preflight response is missing the PNA-specific `Access-Control` header +// - success +// +function makePreflightTests({ + key, + sourceName, + sourceServer, + sourceTreatAsPublic, + targetName, + targetServer, +}) { + const prefix = + `${sourceName} to ${targetName}: `; + + const source = { + server: sourceServer, + treatAsPublic: sourceTreatAsPublic, + }; + + promise_test_parallel(t => windowOpenTest(t, { + source, + target: { + server: targetServer, + behavior: { preflight: PreflightBehavior.failure() }, + }, + expected: NavigationTestResult.FAILURE, + }), prefix + "failed preflight."); + + promise_test_parallel(t => windowOpenTest(t, { + source, + target: { + server: targetServer, + behavior: { preflight: PreflightBehavior.noCorsHeader(token()) }, + }, + expected: NavigationTestResult.FAILURE, + }), prefix + "missing CORS headers."); + + promise_test_parallel(t => windowOpenTest(t, { + source, + target: { + server: targetServer, + behavior: { preflight: PreflightBehavior.noPnaHeader(token()) }, + }, + expected: NavigationTestResult.FAILURE, + }), prefix + "missing PNA header."); + + promise_test_parallel(t => windowOpenTest(t, { + source, + target: { + server: targetServer, + behavior: { preflight: PreflightBehavior.navigation(token()) }, + }, + expected: NavigationTestResult.SUCCESS, + }), prefix + "success."); +} + +// Source: private secure context. +// +// Fetches to the local address space require a successful preflight response +// carrying a PNA-specific header. + +subsetTestByKey('from-private', makePreflightTests, { + sourceServer: Server.HTTPS_PRIVATE, + sourceName: 'private', + targetServer: Server.HTTPS_LOCAL, + targetName: 'local', +}); + +subsetTestByKey("from-private", promise_test_parallel, t => windowOpenTest(t, { + source: { server: Server.HTTPS_PRIVATE }, + target: { server: Server.HTTPS_PRIVATE }, + expected: NavigationTestResult.SUCCESS, +}), "private to private: no preflight required."); + +subsetTestByKey("from-private", promise_test_parallel, t => windowOpenTest(t, { + source: { server: Server.HTTPS_PRIVATE }, + target: { server: Server.HTTPS_PUBLIC }, + expected: NavigationTestResult.SUCCESS, +}), "private to public: no preflight required."); + +// Source: public secure context. +// +// Fetches to the local and private address spaces require a successful +// preflight response carrying a PNA-specific header. + +subsetTestByKey('from-public', makePreflightTests, { + sourceServer: Server.HTTPS_PUBLIC, + sourceName: "public", + targetServer: Server.HTTPS_LOCAL, + targetName: "local", +}); + +subsetTestByKey('from-public', makePreflightTests, { + sourceServer: Server.HTTPS_PUBLIC, + sourceName: "public", + targetServer: Server.HTTPS_PRIVATE, + targetName: "private", +}); + +subsetTestByKey("from-public", promise_test_parallel, t => windowOpenTest(t, { + source: { server: Server.HTTPS_PUBLIC }, + target: { server: Server.HTTPS_PUBLIC }, + expected: NavigationTestResult.SUCCESS, +}), "public to public: no preflight required."); + +// The following tests verify that `CSP: treat-as-public-address` makes +// documents behave as if they had been served from a public IP address. + +subsetTestByKey('from-treat-as-public', makePreflightTests, { + sourceServer: Server.HTTPS_LOCAL, + sourceTreatAsPublic: true, + sourceName: "treat-as-public-address", + targetServer: Server.OTHER_HTTPS_LOCAL, + targetName: "local", +}); + +subsetTestByKey("from-treat-as-public", promise_test_parallel, + t => windowOpenTest(t, { + source: { + server: Server.HTTPS_LOCAL, + treatAsPublic: true, + }, + target: {server: Server.HTTPS_LOCAL}, + expected: NavigationTestResult.SUCCESS, + }), + 'treat-as-public-address to local (same-origin): no preflight required.'); + +subsetTestByKey('from-treat-as-public', makePreflightTests, { + sourceServer: Server.HTTPS_LOCAL, + sourceTreatAsPublic: true, + sourceName: 'treat-as-public-address', + targetServer: Server.HTTPS_PRIVATE, + targetName: 'private', +}); + +subsetTestByKey("from-treat-as-public", promise_test_parallel, + t => windowOpenTest(t, { + source: { + server: Server.HTTPS_LOCAL, + treatAsPublic: true, + }, + target: {server: Server.HTTPS_PUBLIC}, + expected: NavigationTestResult.SUCCESS, + }), + 'treat-as-public-address to public: no preflight required.'); + +promise_test_parallel( + t => windowOpenTest(t, { + source: { + server: Server.HTTPS_LOCAL, + treatAsPublic: true, + }, + target: { + server: Server.HTTPS_PUBLIC, + behavior: {preflight: PreflightBehavior.optionalSuccess(token())} + }, + expected: NavigationTestResult.SUCCESS, + }), + 'treat-as-public-address to local: optional preflight'); diff --git a/testing/web-platform/tests/fetch/private-network-access/window-open.tentative.window.js b/testing/web-platform/tests/fetch/private-network-access/window-open.tentative.window.js new file mode 100644 index 0000000000..5e2313d60a --- /dev/null +++ b/testing/web-platform/tests/fetch/private-network-access/window-open.tentative.window.js @@ -0,0 +1,95 @@ +// META: script=/common/dispatcher/dispatcher.js +// META: script=/common/utils.js +// META: script=resources/support.sub.js +// META: timeout=long +// +// Spec: https://wicg.github.io/private-network-access/ +// +// These tests verify that non-secure contexts cannot open a new window to +// less-public address spaces. + +setup(() => { + // Making sure we are in a non secure context, as expected. + assert_false(window.isSecureContext); +}); + +promise_test_parallel(t => windowOpenTest(t, { + source: { server: Server.HTTP_LOCAL }, + target: { server: Server.HTTP_LOCAL }, + expected: NavigationTestResult.SUCCESS, +}), "local to local: no preflight required."); + +promise_test_parallel(t => windowOpenTest(t, { + source: { server: Server.HTTP_LOCAL }, + target: { server: Server.HTTP_PRIVATE }, + expected: NavigationTestResult.SUCCESS, +}), "local to private: no preflight required."); + +promise_test_parallel(t => windowOpenTest(t, { + source: { server: Server.HTTP_LOCAL }, + target: { server: Server.HTTP_PUBLIC }, + expected: NavigationTestResult.SUCCESS, +}), "local to public: no preflight required."); + +promise_test_parallel(t => windowOpenTest(t, { + source: { server: Server.HTTP_PRIVATE }, + target: { server: Server.HTTP_LOCAL }, + expected: NavigationTestResult.FAILURE, +}), "private to local: failure."); + +promise_test_parallel(t => windowOpenTest(t, { + source: { server: Server.HTTP_PRIVATE }, + target: { server: Server.HTTP_PRIVATE }, + expected: NavigationTestResult.SUCCESS, +}), "private to private: no preflight required."); + +promise_test_parallel(t => windowOpenTest(t, { + source: { server: Server.HTTP_PRIVATE }, + target: { server: Server.HTTP_PUBLIC }, + expected: NavigationTestResult.SUCCESS, +}), "private to public: no preflight required."); + +promise_test_parallel(t => windowOpenTest(t, { + source: { server: Server.HTTP_PUBLIC }, + target: { server: Server.HTTP_LOCAL }, + expected: NavigationTestResult.FAILURE, +}), "public to local: failure."); + +promise_test_parallel(t => windowOpenTest(t, { + source: { server: Server.HTTP_PUBLIC }, + target: { server: Server.HTTP_PRIVATE }, + expected: NavigationTestResult.FAILURE, +}), "public to private: failure."); + +promise_test_parallel(t => windowOpenTest(t, { + source: { server: Server.HTTP_PUBLIC }, + target: { server: Server.HTTP_PUBLIC }, + expected: NavigationTestResult.SUCCESS, +}), "public to public: no preflight required."); + +promise_test_parallel(t => windowOpenTest(t, { + source: { + server: Server.HTTP_LOCAL, + treatAsPublic: true, + }, + target: { server: Server.HTTP_LOCAL }, + expected: NavigationTestResult.FAILURE, +}), "treat-as-public-address to local: failure."); + +promise_test_parallel(t => windowOpenTest(t, { + source: { + server: Server.HTTP_LOCAL, + treatAsPublic: true, + }, + target: { server: Server.HTTP_PRIVATE }, + expected: NavigationTestResult.FAILURE, +}), "treat-as-public-address to private: failure."); + +promise_test_parallel(t => windowOpenTest(t, { + source: { + server: Server.HTTP_LOCAL, + treatAsPublic: true, + }, + target: { server: Server.HTTP_PUBLIC }, + expected: NavigationTestResult.SUCCESS, +}), "treat-as-public-address to public: no preflight required."); diff --git a/testing/web-platform/tests/fetch/private-network-access/worker-blob-fetch.tentative.window.js b/testing/web-platform/tests/fetch/private-network-access/worker-blob-fetch.tentative.window.js new file mode 100644 index 0000000000..e119746b8a --- /dev/null +++ b/testing/web-platform/tests/fetch/private-network-access/worker-blob-fetch.tentative.window.js @@ -0,0 +1,155 @@ +// META: script=/common/utils.js +// META: script=resources/support.sub.js +// +// Spec: https://wicg.github.io/private-network-access/#integration-fetch +// +// These tests check that fetches from within `Worker` scripts loaded from blob +// URLs are subject to Private Network Access checks, just like fetches from +// within documents. +// +// This file covers only those tests that must execute in a non-secure context. +// Other tests are defined in: worker-blob-fetch.https.window.js + +promise_test(t => workerBlobFetchTest(t, { + source: { server: Server.HTTP_LOCAL }, + target: { server: Server.HTTP_LOCAL }, + expected: WorkerFetchTestResult.SUCCESS, +}), "local to local: success."); + +promise_test(t => workerBlobFetchTest(t, { + source: { server: Server.HTTP_PRIVATE }, + target: { + server: Server.HTTP_LOCAL, + behavior: { + preflight: PreflightBehavior.optionalSuccess(token()), + response: ResponseBehavior.allowCrossOrigin(), + }, + }, + expected: WorkerFetchTestResult.FAILURE, +}), "private to local: failure."); + +promise_test(t => workerBlobFetchTest(t, { + source: { server: Server.HTTP_PRIVATE }, + target: { server: Server.HTTP_PRIVATE }, + expected: WorkerFetchTestResult.SUCCESS, +}), "private to private: success."); + +promise_test(t => workerBlobFetchTest(t, { + source: { server: Server.HTTP_PUBLIC }, + target: { + server: Server.HTTP_LOCAL, + behavior: { + preflight: PreflightBehavior.optionalSuccess(token()), + response: ResponseBehavior.allowCrossOrigin(), + }, + }, + expected: WorkerFetchTestResult.FAILURE, +}), "public to local: failure."); + +promise_test(t => workerBlobFetchTest(t, { + source: { server: Server.HTTP_PUBLIC }, + target: { + server: Server.HTTP_PRIVATE, + behavior: { + preflight: PreflightBehavior.optionalSuccess(token()), + response: ResponseBehavior.allowCrossOrigin(), + }, + }, + expected: WorkerFetchTestResult.FAILURE, +}), "public to private: failure."); + +promise_test(t => workerBlobFetchTest(t, { + source: { server: Server.HTTP_PUBLIC }, + target: { server: Server.HTTP_PUBLIC }, + expected: WorkerFetchTestResult.SUCCESS, +}), "public to public: success."); + +promise_test(t => workerBlobFetchTest(t, { + source: { + server: Server.HTTP_LOCAL, + treatAsPublic: true, + }, + target: { + server: Server.HTTP_LOCAL, + behavior: { preflight: PreflightBehavior.optionalSuccess(token()) }, + }, + expected: WorkerFetchTestResult.FAILURE, +}), "treat-as-public to local: failure."); + +promise_test(t => workerBlobFetchTest(t, { + source: { + server: Server.HTTP_LOCAL, + treatAsPublic: true, + }, + target: { + server: Server.HTTP_PRIVATE, + behavior: { + preflight: PreflightBehavior.optionalSuccess(token()), + response: ResponseBehavior.allowCrossOrigin(), + }, + }, + expected: WorkerFetchTestResult.FAILURE, +}), "treat-as-public to private: failure."); + +promise_test(t => workerBlobFetchTest(t, { + source: { + server: Server.HTTP_LOCAL, + treatAsPublic: true, + }, + target: { + server: Server.HTTP_PUBLIC, + behavior: { response: ResponseBehavior.allowCrossOrigin() }, + }, + expected: WorkerFetchTestResult.SUCCESS, +}), "treat-as-public to public: success."); + +// The following tests verify that workers served over HTTPS are not allowed to +// make private network requests because they are not secure contexts. + +promise_test(t => workerBlobFetchTest(t, { + source: { server: Server.HTTPS_LOCAL }, + target: { + server: Server.HTTPS_LOCAL, + behavior: { + preflight: PreflightBehavior.optionalSuccess(token()), + response: ResponseBehavior.allowCrossOrigin(), + }, + }, + expected: WorkerFetchTestResult.SUCCESS, +}), "local https to local https: success."); + +promise_test(t => workerBlobFetchTest(t, { + source: { server: Server.HTTPS_PRIVATE }, + target: { + server: Server.HTTPS_LOCAL, + behavior: { + preflight: PreflightBehavior.optionalSuccess(token()), + response: ResponseBehavior.allowCrossOrigin(), + }, + }, + expected: WorkerFetchTestResult.FAILURE, +}), "private https to local https: failure."); + +promise_test(t => workerBlobFetchTest(t, { + source: { server: Server.HTTPS_PUBLIC }, + target: { + server: Server.HTTPS_PRIVATE, + behavior: { + preflight: PreflightBehavior.optionalSuccess(token()), + response: ResponseBehavior.allowCrossOrigin(), + }, + }, + expected: WorkerFetchTestResult.FAILURE, +}), "public https to private https: failure."); + +promise_test(t => workerBlobFetchTest(t, { + source: { server: Server.HTTPS_PUBLIC }, + target: { + server: Server.HTTPS_LOCAL, + behavior: { + preflight: PreflightBehavior.optionalSuccess(token()), + response: ResponseBehavior.allowCrossOrigin(), + }, + }, + expected: WorkerFetchTestResult.FAILURE, +}), "public https to local https: failure."); diff --git a/testing/web-platform/tests/fetch/private-network-access/worker-fetch.tentative.https.window.js b/testing/web-platform/tests/fetch/private-network-access/worker-fetch.tentative.https.window.js new file mode 100644 index 0000000000..89e0c3cf1f --- /dev/null +++ b/testing/web-platform/tests/fetch/private-network-access/worker-fetch.tentative.https.window.js @@ -0,0 +1,151 @@ +// META: script=/common/utils.js +// META: script=resources/support.sub.js +// +// Spec: https://wicg.github.io/private-network-access/#integration-fetch +// +// These tests check that fetches from within `Worker` scripts are subject to +// Private Network Access checks, just like fetches from within documents. +// +// This file covers only those tests that must execute in a secure context. +// Other tests are defined in: worker-fetch.window.js + +promise_test(t => workerFetchTest(t, { + source: { server: Server.HTTPS_LOCAL }, + target: { server: Server.HTTPS_LOCAL }, + expected: WorkerFetchTestResult.SUCCESS, +}), "local to local: success."); + +promise_test(t => workerFetchTest(t, { + source: { server: Server.HTTPS_PRIVATE }, + target: { + server: Server.HTTPS_LOCAL, + behavior: { response: ResponseBehavior.allowCrossOrigin() }, + }, + expected: WorkerFetchTestResult.FAILURE, +}), "private to local: failed preflight."); + +promise_test(t => workerFetchTest(t, { + source: { server: Server.HTTPS_PRIVATE }, + target: { + server: Server.HTTPS_LOCAL, + behavior: { + preflight: PreflightBehavior.success(token()), + response: ResponseBehavior.allowCrossOrigin(), + }, + }, + expected: WorkerFetchTestResult.SUCCESS, +}), "private to local: success."); + +promise_test(t => workerFetchTest(t, { + source: { server: Server.HTTPS_PRIVATE }, + target: { server: Server.HTTPS_PRIVATE }, + expected: WorkerFetchTestResult.SUCCESS, +}), "private to private: success."); + +promise_test(t => workerFetchTest(t, { + source: { server: Server.HTTPS_PUBLIC }, + target: { + server: Server.HTTPS_LOCAL, + behavior: { response: ResponseBehavior.allowCrossOrigin() }, + }, + expected: WorkerFetchTestResult.FAILURE, +}), "public to local: failed preflight."); + +promise_test(t => workerFetchTest(t, { + source: { server: Server.HTTPS_PUBLIC }, + target: { + server: Server.HTTPS_LOCAL, + behavior: { + preflight: PreflightBehavior.success(token()), + response: ResponseBehavior.allowCrossOrigin(), + }, + }, + expected: WorkerFetchTestResult.SUCCESS, +}), "public to local: success."); + +promise_test(t => workerFetchTest(t, { + source: { server: Server.HTTPS_PUBLIC }, + target: { + server: Server.HTTPS_PRIVATE, + behavior: { response: ResponseBehavior.allowCrossOrigin() }, + }, + expected: WorkerFetchTestResult.FAILURE, +}), "public to private: failed preflight."); + +promise_test(t => workerFetchTest(t, { + source: { server: Server.HTTPS_PUBLIC }, + target: { + server: Server.HTTPS_PRIVATE, + behavior: { + preflight: PreflightBehavior.success(token()), + response: ResponseBehavior.allowCrossOrigin(), + }, + }, + expected: WorkerFetchTestResult.SUCCESS, +}), "public to private: success."); + +promise_test(t => workerFetchTest(t, { + source: { server: Server.HTTPS_PUBLIC }, + target: { server: Server.HTTPS_PUBLIC }, + expected: WorkerFetchTestResult.SUCCESS, +}), "public to public: success."); + +promise_test(t => workerFetchTest(t, { + source: { + server: Server.HTTPS_LOCAL, + treatAsPublic: true, + }, + target: { server: Server.HTTPS_LOCAL }, + expected: WorkerFetchTestResult.FAILURE, +}), "treat-as-public to local: failed preflight."); + +promise_test(t => workerFetchTest(t, { + source: { + server: Server.HTTPS_LOCAL, + treatAsPublic: true, + }, + target: { + server: Server.HTTPS_LOCAL, + behavior: { preflight: PreflightBehavior.optionalSuccess(token()) }, + }, + expected: WorkerFetchTestResult.SUCCESS, +}), "treat-as-public to local: success."); + +promise_test(t => workerFetchTest(t, { + source: { + server: Server.HTTPS_LOCAL, + treatAsPublic: true, + }, + target: { + server: Server.HTTPS_PRIVATE, + behavior: { response: ResponseBehavior.allowCrossOrigin() }, + }, + expected: WorkerFetchTestResult.FAILURE, +}), "treat-as-public to private: failed preflight."); + +promise_test(t => workerFetchTest(t, { + source: { + server: Server.HTTPS_LOCAL, + treatAsPublic: true, + }, + target: { + server: Server.HTTPS_PRIVATE, + behavior: { + preflight: PreflightBehavior.success(token()), + response: ResponseBehavior.allowCrossOrigin(), + }, + }, + expected: WorkerFetchTestResult.SUCCESS, +}), "treat-as-public to private: success."); + +promise_test(t => workerFetchTest(t, { + source: { + server: Server.HTTPS_LOCAL, + treatAsPublic: true, + }, + target: { + server: Server.HTTPS_PUBLIC, + behavior: { response: ResponseBehavior.allowCrossOrigin() }, + }, + expected: WorkerFetchTestResult.SUCCESS, +}), "treat-as-public to public: success."); diff --git a/testing/web-platform/tests/fetch/private-network-access/worker-fetch.tentative.window.js b/testing/web-platform/tests/fetch/private-network-access/worker-fetch.tentative.window.js new file mode 100644 index 0000000000..4d6b12f067 --- /dev/null +++ b/testing/web-platform/tests/fetch/private-network-access/worker-fetch.tentative.window.js @@ -0,0 +1,154 @@ +// META: script=/common/utils.js +// META: script=resources/support.sub.js +// +// Spec: https://wicg.github.io/private-network-access/#integration-fetch +// +// These tests check that fetches from within `Worker` scripts are subject to +// Private Network Access checks, just like fetches from within documents. +// +// This file covers only those tests that must execute in a non-secure context. +// Other tests are defined in: worker-fetch.https.window.js + +promise_test(t => workerFetchTest(t, { + source: { server: Server.HTTP_LOCAL }, + target: { server: Server.HTTP_LOCAL }, + expected: WorkerFetchTestResult.SUCCESS, +}), "local to local: success."); + +promise_test(t => workerFetchTest(t, { + source: { server: Server.HTTP_PRIVATE }, + target: { + server: Server.HTTP_LOCAL, + behavior: { + preflight: PreflightBehavior.optionalSuccess(token()), + response: ResponseBehavior.allowCrossOrigin(), + }, + }, + expected: WorkerFetchTestResult.FAILURE, +}), "private to local: failure."); + +promise_test(t => workerFetchTest(t, { + source: { server: Server.HTTP_PRIVATE }, + target: { server: Server.HTTP_PRIVATE }, + expected: WorkerFetchTestResult.SUCCESS, +}), "private to private: success."); + +promise_test(t => workerFetchTest(t, { + source: { server: Server.HTTP_PUBLIC }, + target: { + server: Server.HTTP_LOCAL, + behavior: { + preflight: PreflightBehavior.optionalSuccess(token()), + response: ResponseBehavior.allowCrossOrigin(), + }, + }, + expected: WorkerFetchTestResult.FAILURE, +}), "public to local: failure."); + +promise_test(t => workerFetchTest(t, { + source: { server: Server.HTTP_PUBLIC }, + target: { + server: Server.HTTP_PRIVATE, + behavior: { + preflight: PreflightBehavior.optionalSuccess(token()), + response: ResponseBehavior.allowCrossOrigin(), + }, + }, + expected: WorkerFetchTestResult.FAILURE, +}), "public to private: failure."); + +promise_test(t => workerFetchTest(t, { + source: { server: Server.HTTP_PUBLIC }, + target: { server: Server.HTTP_PUBLIC }, + expected: WorkerFetchTestResult.SUCCESS, +}), "public to public: success."); + +promise_test(t => workerFetchTest(t, { + source: { + server: Server.HTTP_LOCAL, + treatAsPublic: true, + }, + target: { + server: Server.HTTP_LOCAL, + behavior: { preflight: PreflightBehavior.optionalSuccess(token()) }, + }, + expected: WorkerFetchTestResult.FAILURE, +}), "treat-as-public to local: failure."); + +promise_test(t => workerFetchTest(t, { + source: { + server: Server.HTTP_LOCAL, + treatAsPublic: true, + }, + target: { + server: Server.HTTP_PRIVATE, + behavior: { + preflight: PreflightBehavior.optionalSuccess(token()), + response: ResponseBehavior.allowCrossOrigin(), + }, + }, + expected: WorkerFetchTestResult.FAILURE, +}), "treat-as-public to private: failure."); + +promise_test(t => workerFetchTest(t, { + source: { + server: Server.HTTP_LOCAL, + treatAsPublic: true, + }, + target: { + server: Server.HTTP_PUBLIC, + behavior: { response: ResponseBehavior.allowCrossOrigin() }, + }, + expected: WorkerFetchTestResult.SUCCESS, +}), "treat-as-public to public: success."); + +// The following tests verify that workers served over HTTPS are not allowed to +// make private network requests because they are not secure contexts. + +promise_test(t => workerFetchTest(t, { + source: { server: Server.HTTPS_LOCAL }, + target: { + server: Server.HTTPS_LOCAL, + behavior: { + preflight: PreflightBehavior.optionalSuccess(token()), + response: ResponseBehavior.allowCrossOrigin(), + }, + }, + expected: WorkerFetchTestResult.SUCCESS, +}), "local https to local https: success."); + +promise_test(t => workerFetchTest(t, { + source: { server: Server.HTTPS_PRIVATE }, + target: { + server: Server.HTTPS_LOCAL, + behavior: { + preflight: PreflightBehavior.optionalSuccess(token()), + response: ResponseBehavior.allowCrossOrigin(), + }, + }, + expected: WorkerFetchTestResult.FAILURE, +}), "private https to local https: failure."); + +promise_test(t => workerFetchTest(t, { + source: { server: Server.HTTPS_PUBLIC }, + target: { + server: Server.HTTPS_PRIVATE, + behavior: { + preflight: PreflightBehavior.optionalSuccess(token()), + response: ResponseBehavior.allowCrossOrigin(), + }, + }, + expected: WorkerFetchTestResult.FAILURE, +}), "public https to private https: failure."); + +promise_test(t => workerFetchTest(t, { + source: { server: Server.HTTPS_PUBLIC }, + target: { + server: Server.HTTPS_LOCAL, + behavior: { + preflight: PreflightBehavior.optionalSuccess(token()), + response: ResponseBehavior.allowCrossOrigin(), + }, + }, + expected: WorkerFetchTestResult.FAILURE, +}), "public https to local https: failure."); diff --git a/testing/web-platform/tests/fetch/private-network-access/worker.tentative.https.window.js b/testing/web-platform/tests/fetch/private-network-access/worker.tentative.https.window.js new file mode 100644 index 0000000000..a0f19314ee --- /dev/null +++ b/testing/web-platform/tests/fetch/private-network-access/worker.tentative.https.window.js @@ -0,0 +1,37 @@ +// META: script=/common/utils.js +// META: script=resources/support.sub.js +// +// Spec: https://wicg.github.io/private-network-access/#integration-fetch +// +// These tests check that initial `Worker` script fetches in secure contexts are +// exempt from Private Network Access checks because workers can only be fetched +// same-origin and the origin is potentially trustworthy. The only way to test +// this is using the `treat-as-public` CSP directive to artificially place the +// parent document in the `public` IP address space. +// +// This file covers only those tests that must execute in a secure context. +// Other tests are defined in: worker.window.js + +promise_test(t => workerScriptTest(t, { + source: { + server: Server.HTTPS_LOCAL, + treatAsPublic: true, + }, + target: { server: Server.HTTPS_LOCAL }, + expected: WorkerScriptTestResult.SUCCESS, +}), "treat-as-public to local: success."); + +promise_test(t => workerScriptTest(t, { + source: { + server: Server.HTTPS_PRIVATE, + treatAsPublic: true, + }, + target: { server: Server.HTTPS_PRIVATE }, + expected: WorkerScriptTestResult.SUCCESS, +}), "treat-as-public to private: success."); + +promise_test(t => workerScriptTest(t, { + source: { server: Server.HTTPS_PUBLIC }, + target: { server: Server.HTTPS_PUBLIC }, + expected: WorkerScriptTestResult.SUCCESS, +}), "public to public: success."); diff --git a/testing/web-platform/tests/fetch/private-network-access/worker.tentative.window.js b/testing/web-platform/tests/fetch/private-network-access/worker.tentative.window.js new file mode 100644 index 0000000000..118c099254 --- /dev/null +++ b/testing/web-platform/tests/fetch/private-network-access/worker.tentative.window.js @@ -0,0 +1,37 @@ +// META: script=/common/utils.js +// META: script=resources/support.sub.js +// +// Spec: https://wicg.github.io/private-network-access/#integration-fetch +// +// These tests check that initial `Worker` script fetches are subject to Private +// Network Access checks, just like a regular `fetch()`. The main difference is +// that workers can only be fetched same-origin, so the only way to test this +// is using the `treat-as-public` CSP directive to artificially place the parent +// document in the `public` IP address space. +// +// This file covers only those tests that must execute in a non secure context. +// Other tests are defined in: worker.https.window.js + +promise_test(t => workerScriptTest(t, { + source: { + server: Server.HTTP_LOCAL, + treatAsPublic: true, + }, + target: { server: Server.HTTP_LOCAL }, + expected: WorkerScriptTestResult.FAILURE, +}), "treat-as-public to local: failure."); + +promise_test(t => workerScriptTest(t, { + source: { + server: Server.HTTP_PRIVATE, + treatAsPublic: true, + }, + target: { server: Server.HTTP_PRIVATE }, + expected: WorkerScriptTestResult.FAILURE, +}), "treat-as-public to private: failure."); + +promise_test(t => workerScriptTest(t, { + source: { server: Server.HTTP_PUBLIC }, + target: { server: Server.HTTP_PUBLIC }, + expected: WorkerScriptTestResult.SUCCESS, +}), "public to public: success."); diff --git a/testing/web-platform/tests/fetch/private-network-access/xhr-from-treat-as-public.tentative.https.window.js b/testing/web-platform/tests/fetch/private-network-access/xhr-from-treat-as-public.tentative.https.window.js new file mode 100644 index 0000000000..3aae3050d9 --- /dev/null +++ b/testing/web-platform/tests/fetch/private-network-access/xhr-from-treat-as-public.tentative.https.window.js @@ -0,0 +1,83 @@ +// META: script=/common/utils.js +// META: script=resources/support.sub.js +// +// Spec: https://wicg.github.io/private-network-access/#integration-fetch +// +// These tests verify that documents fetched from the `local` address space yet +// carrying the `treat-as-public-address` CSP directive are treated as if they +// had been fetched from the `public` address space. + +promise_test(t => xhrTest(t, { + source: { + server: Server.HTTPS_LOCAL, + treatAsPublic: true, + }, + target: { + server: Server.OTHER_HTTPS_LOCAL, + behavior: { response: ResponseBehavior.allowCrossOrigin() }, + }, + expected: XhrTestResult.FAILURE, +}), "treat-as-public to local: failed preflight."); + +promise_test(t => xhrTest(t, { + source: { + server: Server.HTTPS_LOCAL, + treatAsPublic: true, + }, + target: { + server: Server.OTHER_HTTPS_LOCAL, + behavior: { + preflight: PreflightBehavior.success(token()), + response: ResponseBehavior.allowCrossOrigin(), + }, + }, + expected: XhrTestResult.SUCCESS, +}), "treat-as-public to local: success."); + +promise_test(t => xhrTest(t, { + source: { + server: Server.HTTPS_LOCAL, + treatAsPublic: true, + }, + target: { server: Server.HTTPS_LOCAL }, + expected: XhrTestResult.SUCCESS, +}), "treat-as-public to local (same-origin): no preflight required."); + +promise_test(t => xhrTest(t, { + source: { + server: Server.HTTPS_LOCAL, + treatAsPublic: true, + }, + target: { + server: Server.HTTPS_PRIVATE, + behavior: { response: ResponseBehavior.allowCrossOrigin() }, + }, + expected: XhrTestResult.FAILURE, +}), "treat-as-public to private: failed preflight."); + +promise_test(t => xhrTest(t, { + source: { + server: Server.HTTPS_LOCAL, + treatAsPublic: true, + }, + target: { + server: Server.HTTPS_PRIVATE, + behavior: { + preflight: PreflightBehavior.optionalSuccess(token()), + response: ResponseBehavior.allowCrossOrigin(), + }, + }, + expected: XhrTestResult.SUCCESS, +}), "treat-as-public to private: success."); + +promise_test(t => xhrTest(t, { + source: { + server: Server.HTTPS_LOCAL, + treatAsPublic: true, + }, + target: { + server: Server.HTTPS_PUBLIC, + behavior: { response: ResponseBehavior.allowCrossOrigin() }, + }, + expected: XhrTestResult.SUCCESS, +}), "treat-as-public to public: no preflight required."); diff --git a/testing/web-platform/tests/fetch/private-network-access/xhr.https.tentative.window.js b/testing/web-platform/tests/fetch/private-network-access/xhr.https.tentative.window.js new file mode 100644 index 0000000000..4dc5da9912 --- /dev/null +++ b/testing/web-platform/tests/fetch/private-network-access/xhr.https.tentative.window.js @@ -0,0 +1,142 @@ +// META: script=/common/subset-tests-by-key.js +// META: script=/common/utils.js +// META: script=resources/support.sub.js +// META: variant=?include=from-local +// META: variant=?include=from-private +// META: variant=?include=from-public +// +// Spec: https://wicg.github.io/private-network-access/#integration-fetch +// +// These tests mirror fetch.https.window.js, but use `XmlHttpRequest` instead of +// `fetch()` to perform subresource fetches. Preflights are tested less +// extensively due to coverage being already provided by `fetch()`. +// +// This file covers only those tests that must execute in a secure context. +// Other tests are defined in: xhr.window.js + +setup(() => { + // Making sure we are in a secure context, as expected. + assert_true(window.isSecureContext); +}); + +// Source: secure local context. +// +// All fetches unaffected by Private Network Access. + +subsetTestByKey("from-local", promise_test, t => xhrTest(t, { + source: { server: Server.HTTPS_LOCAL }, + target: { server: Server.HTTPS_LOCAL }, + expected: XhrTestResult.SUCCESS, +}), "local to local: no preflight required."); + +subsetTestByKey("from-local", promise_test, t => xhrTest(t, { + source: { server: Server.HTTPS_LOCAL }, + target: { + server: Server.HTTPS_PRIVATE, + behavior: { response: ResponseBehavior.allowCrossOrigin() }, + }, + expected: XhrTestResult.SUCCESS, +}), "local to private: no preflight required."); + +subsetTestByKey("from-local", promise_test, t => xhrTest(t, { + source: { server: Server.HTTPS_LOCAL }, + target: { + server: Server.HTTPS_PUBLIC, + behavior: { response: ResponseBehavior.allowCrossOrigin() }, + }, + expected: XhrTestResult.SUCCESS, +}), "local to public: no preflight required."); + +// Source: private secure context. +// +// Fetches to the local address space require a successful preflight response +// carrying a PNA-specific header. + +subsetTestByKey("from-private", promise_test, t => xhrTest(t, { + source: { server: Server.HTTPS_PRIVATE }, + target: { + server: Server.HTTPS_LOCAL, + behavior: { response: ResponseBehavior.allowCrossOrigin() }, + }, + expected: XhrTestResult.FAILURE, +}), "private to local: failed preflight."); + +subsetTestByKey("from-private", promise_test, t => xhrTest(t, { + source: { server: Server.HTTPS_PRIVATE }, + target: { + server: Server.HTTPS_LOCAL, + behavior: { + preflight: PreflightBehavior.success(token()), + response: ResponseBehavior.allowCrossOrigin(), + }, + }, + expected: XhrTestResult.SUCCESS, +}), "private to local: success."); + +subsetTestByKey("from-private", promise_test, t => xhrTest(t, { + source: { server: Server.HTTPS_PRIVATE }, + target: { server: Server.HTTPS_PRIVATE }, + expected: XhrTestResult.SUCCESS, +}), "private to private: no preflight required."); + +subsetTestByKey("from-private", promise_test, t => xhrTest(t, { + source: { server: Server.HTTPS_PRIVATE }, + target: { + server: Server.HTTPS_PUBLIC, + behavior: { response: ResponseBehavior.allowCrossOrigin() }, + }, + expected: XhrTestResult.SUCCESS, +}), "private to public: no preflight required."); + +// Source: public secure context. +// +// Fetches to the local and private address spaces require a successful +// preflight response carrying a PNA-specific header. + +subsetTestByKey("from-public", promise_test, t => xhrTest(t, { + source: { server: Server.HTTPS_PUBLIC }, + target: { + server: Server.HTTPS_LOCAL, + behavior: { response: ResponseBehavior.allowCrossOrigin() }, + }, + expected: XhrTestResult.FAILURE, +}), "public to local: failed preflight."); + +subsetTestByKey("from-public", promise_test, t => xhrTest(t, { + source: { server: Server.HTTPS_PUBLIC }, + target: { + server: Server.HTTPS_LOCAL, + behavior: { + preflight: PreflightBehavior.success(token()), + response: ResponseBehavior.allowCrossOrigin(), + }, + }, + expected: XhrTestResult.SUCCESS, +}), "public to local: success."); + +subsetTestByKey("from-public", promise_test, t => xhrTest(t, { + source: { server: Server.HTTPS_PUBLIC }, + target: { + server: Server.HTTPS_PRIVATE, + behavior: { response: ResponseBehavior.allowCrossOrigin() }, + }, + expected: XhrTestResult.FAILURE, +}), "public to private: failed preflight."); + +subsetTestByKey("from-public", promise_test, t => xhrTest(t, { + source: { server: Server.HTTPS_PUBLIC }, + target: { + server: Server.HTTPS_PRIVATE, + behavior: { + preflight: PreflightBehavior.success(token()), + response: ResponseBehavior.allowCrossOrigin(), + }, + }, + expected: XhrTestResult.SUCCESS, +}), "public to private: success."); + +subsetTestByKey("from-public", promise_test, t => xhrTest(t, { + source: { server: Server.HTTPS_PUBLIC }, + target: { server: Server.HTTPS_PUBLIC }, + expected: XhrTestResult.SUCCESS, +}), "public to public: no preflight required."); diff --git a/testing/web-platform/tests/fetch/private-network-access/xhr.tentative.window.js b/testing/web-platform/tests/fetch/private-network-access/xhr.tentative.window.js new file mode 100644 index 0000000000..fa307dc559 --- /dev/null +++ b/testing/web-platform/tests/fetch/private-network-access/xhr.tentative.window.js @@ -0,0 +1,195 @@ +// META: script=/common/utils.js +// META: script=resources/support.sub.js +// +// Spec: https://wicg.github.io/private-network-access/#integration-fetch +// +// These tests mirror fetch.window.js, but use `XmlHttpRequest` instead of +// `fetch()` to perform subresource fetches. +// +// This file covers only those tests that must execute in a non secure context. +// Other tests are defined in: xhr.https.window.js + +setup(() => { + // Making sure we are in a non secure context, as expected. + assert_false(window.isSecureContext); +}); + +promise_test(t => xhrTest(t, { + source: { server: Server.HTTP_LOCAL }, + target: { server: Server.HTTP_LOCAL }, + expected: XhrTestResult.SUCCESS, +}), "local to local: no preflight required."); + +promise_test(t => xhrTest(t, { + source: { server: Server.HTTP_LOCAL }, + target: { + server: Server.HTTP_PRIVATE, + behavior: { response: ResponseBehavior.allowCrossOrigin() }, + }, + expected: XhrTestResult.SUCCESS, +}), "local to private: no preflight required."); + +promise_test(t => xhrTest(t, { + source: { server: Server.HTTP_LOCAL }, + target: { + server: Server.HTTP_PUBLIC, + behavior: { response: ResponseBehavior.allowCrossOrigin() }, + }, + expected: XhrTestResult.SUCCESS, +}), "local to public: no preflight required."); + +promise_test(t => xhrTest(t, { + source: { server: Server.HTTP_PRIVATE }, + target: { + server: Server.HTTP_LOCAL, + behavior: { + preflight: PreflightBehavior.optionalSuccess(token()), + response: ResponseBehavior.allowCrossOrigin(), + }, + }, + expected: XhrTestResult.FAILURE, +}), "private to local: failure."); + +promise_test(t => xhrTest(t, { + source: { server: Server.HTTP_PRIVATE }, + target: { server: Server.HTTP_PRIVATE }, + expected: XhrTestResult.SUCCESS, +}), "private to private: no preflight required."); + +promise_test(t => xhrTest(t, { + source: { server: Server.HTTP_PRIVATE }, + target: { + server: Server.HTTP_PUBLIC, + behavior: { response: ResponseBehavior.allowCrossOrigin() }, + }, + expected: XhrTestResult.SUCCESS, +}), "private to public: no preflight required."); + +promise_test(t => xhrTest(t, { + source: { server: Server.HTTP_PUBLIC }, + target: { + server: Server.HTTP_LOCAL, + behavior: { + preflight: PreflightBehavior.optionalSuccess(token()), + response: ResponseBehavior.allowCrossOrigin(), + }, + }, + expected: XhrTestResult.FAILURE, +}), "public to local: failure."); + +promise_test(t => xhrTest(t, { + source: { server: Server.HTTP_PUBLIC }, + target: { + server: Server.HTTP_PRIVATE, + behavior: { + preflight: PreflightBehavior.optionalSuccess(token()), + response: ResponseBehavior.allowCrossOrigin(), + }, + }, + expected: XhrTestResult.FAILURE, +}), "public to private: failure."); + +promise_test(t => xhrTest(t, { + source: { server: Server.HTTP_PUBLIC }, + target: { server: Server.HTTP_PUBLIC }, + expected: XhrTestResult.SUCCESS, +}), "public to public: no preflight required."); + +// These tests verify that documents fetched from the `local` address space yet +// carrying the `treat-as-public-address` CSP directive are treated as if they +// had been fetched from the `public` address space. + +promise_test(t => xhrTest(t, { + source: { + server: Server.HTTP_LOCAL, + treatAsPublic: true, + }, + target: { + server: Server.HTTP_LOCAL, + behavior: { + preflight: PreflightBehavior.optionalSuccess(token()), + response: ResponseBehavior.allowCrossOrigin(), + }, + }, + expected: XhrTestResult.FAILURE, +}), "treat-as-public-address to local: failure."); + +promise_test(t => xhrTest(t, { + source: { + server: Server.HTTP_LOCAL, + treatAsPublic: true, + }, + target: { + server: Server.HTTP_PRIVATE, + behavior: { + preflight: PreflightBehavior.optionalSuccess(token()), + response: ResponseBehavior.allowCrossOrigin(), + }, + }, + expected: XhrTestResult.FAILURE, +}), "treat-as-public-address to private: failure."); + +promise_test(t => xhrTest(t, { + source: { + server: Server.HTTP_LOCAL, + treatAsPublic: true, + }, + target: { + server: Server.HTTP_PUBLIC, + behavior: { response: ResponseBehavior.allowCrossOrigin() }, + }, + expected: XhrTestResult.SUCCESS, +}), "treat-as-public-address to public: no preflight required."); + +// These tests verify that HTTPS iframes embedded in an HTTP top-level document +// cannot fetch subresources from less-public address spaces. Indeed, even +// though the iframes have HTTPS origins, they are non-secure contexts because +// their parent is a non-secure context. + +promise_test(t => xhrTest(t, { + source: { server: Server.HTTPS_LOCAL }, + target: { + server: Server.HTTPS_LOCAL, + behavior: { + preflight: PreflightBehavior.optionalSuccess(token()), + response: ResponseBehavior.allowCrossOrigin(), + }, + }, + expected: XhrTestResult.SUCCESS, +}), "local https to local: success."); + +promise_test(t => xhrTest(t, { + source: { server: Server.HTTPS_PRIVATE }, + target: { + server: Server.HTTPS_LOCAL, + behavior: { + preflight: PreflightBehavior.optionalSuccess(token()), + response: ResponseBehavior.allowCrossOrigin(), + }, + }, + expected: XhrTestResult.FAILURE, +}), "private https to local: failure."); + +promise_test(t => xhrTest(t, { + source: { server: Server.HTTPS_PUBLIC }, + target: { + server: Server.HTTPS_LOCAL, + behavior: { + preflight: PreflightBehavior.optionalSuccess(token()), + response: ResponseBehavior.allowCrossOrigin(), + }, + }, + expected: XhrTestResult.FAILURE, +}), "public https to local: failure."); + +promise_test(t => xhrTest(t, { + source: { server: Server.HTTPS_PUBLIC }, + target: { + server: Server.HTTPS_PRIVATE, + behavior: { + preflight: PreflightBehavior.optionalSuccess(token()), + response: ResponseBehavior.allowCrossOrigin(), + }, + }, + expected: XhrTestResult.FAILURE, +}), "public https to private: failure."); -- cgit v1.2.3