From f7df61ff724cec65477f1bdbb6b031423f18408d Mon Sep 17 00:00:00 2001 From: Jonathan Hao Date: Fri, 10 Nov 2023 04:07:25 -0800 Subject: [PATCH] [Private Network Access] Add window open tests These test cases are mostly borrowed from the iframe.tentative tests. However, instead of loading a grandchild iframe, they open a window instead. We don't need to use timeout like in iframe tests because `window.open` returns `null` if the open fails. Since the top-level PNA hasn't been implemented yet, all test cases that expect a failure will fail. Bug: 1431155 Change-Id: I639d0c56c2e1556273333a79793f67ffa14a4149 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5011032 Commit-Queue: Jonathan Hao Reviewed-by: Yifan Luo Cr-Commit-Position: refs/heads/main@{#1222864} --- .../iframe.tentative.https.window.js | 5 +- .../iframe.tentative.window.js | 2 +- .../resources/opener.html | 11 + .../resources/support.sub.js | 20 ++ .../window-open.tentative.https.window.js | 200 ++++++++++++++++++ .../window-open.tentative.window.js | 94 ++++++++ 6 files changed, 329 insertions(+), 3 deletions(-) create mode 100644 fetch/private-network-access/resources/opener.html create mode 100644 fetch/private-network-access/window-open.tentative.https.window.js create mode 100644 fetch/private-network-access/window-open.tentative.window.js diff --git a/fetch/private-network-access/iframe.tentative.https.window.js b/fetch/private-network-access/iframe.tentative.https.window.js index bf16b6ca7e131d..1e00c0af41a439 100644 --- a/fetch/private-network-access/iframe.tentative.https.window.js +++ b/fetch/private-network-access/iframe.tentative.https.window.js @@ -11,8 +11,9 @@ // // Spec: https://wicg.github.io/private-network-access/#integration-fetch // -// These tests verify that contexts can navigate iframes to less-public address -// spaces iff the target server responds affirmatively to preflight requests. +// 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 diff --git a/fetch/private-network-access/iframe.tentative.window.js b/fetch/private-network-access/iframe.tentative.window.js index c0770df83854c2..441e0884d275ad 100644 --- a/fetch/private-network-access/iframe.tentative.window.js +++ b/fetch/private-network-access/iframe.tentative.window.js @@ -8,7 +8,7 @@ // 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.https.window.js +// Other tests are defined in: iframe.tentative.https.window.js setup(() => { // Making sure we are in a non secure context, as expected. diff --git a/fetch/private-network-access/resources/opener.html b/fetch/private-network-access/resources/opener.html new file mode 100644 index 00000000000000..40e3b60bb52bc5 --- /dev/null +++ b/fetch/private-network-access/resources/opener.html @@ -0,0 +1,11 @@ + + +Opener + + diff --git a/fetch/private-network-access/resources/support.sub.js b/fetch/private-network-access/resources/support.sub.js index 0cb757ca640d97..c3bdb8139bae67 100644 --- a/fetch/private-network-access/resources/support.sub.js +++ b/fetch/private-network-access/resources/support.sub.js @@ -470,6 +470,26 @@ async function iframeTest(t, { source, target, expected }) { assert_equals(result, expected); } +const WindowOpenTestResult = { + SUCCESS: "success", + FAILURE: "failure", +}; + +async function windowOpenTest(t, { source, target, expected }) { + const targetUrl = preflightUrl(target); + + 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 }, "*"); + + assert_equals(await reply, expected); +} + // Similar to `iframeTest`, but replaced iframes with fenced frames. async function fencedFrameTest(t, { source, target, expected }) { // Allows running tests in parallel. diff --git a/fetch/private-network-access/window-open.tentative.https.window.js b/fetch/private-network-access/window-open.tentative.https.window.js new file mode 100644 index 00000000000000..85b929f34d4f38 --- /dev/null +++ b/fetch/private-network-access/window-open.tentative.https.window.js @@ -0,0 +1,200 @@ +// META: script=/common/dispatcher/dispatcher.js +// META: script=/common/utils.js +// META: script=resources/support.sub.js +// +// These tests verify that secure contexts can navigate iframes to less-public +// address spaces 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. + +promise_test_parallel(t => windowOpenTest(t, { + source: { server: Server.HTTPS_LOCAL }, + target: { server: Server.HTTPS_LOCAL }, + expected: WindowOpenTestResult.SUCCESS, +}), "local to local: no preflight required."); + +promise_test_parallel(t => windowOpenTest(t, { + source: { server: Server.HTTPS_LOCAL }, + target: { server: Server.HTTPS_PRIVATE }, + expected: WindowOpenTestResult.SUCCESS, +}), "local to private: no preflight required."); + +promise_test_parallel(t => windowOpenTest(t, { + source: { server: Server.HTTPS_LOCAL }, + target: { server: Server.HTTPS_PUBLIC }, + expected: WindowOpenTestResult.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 => windowOpenTest(t, { + source, + target: { + server: targetServer, + behavior: { preflight: PreflightBehavior.failure() }, + }, + expected: WindowOpenTestResult.FAILURE, + }), prefix + "failed preflight."); + + promise_test_parallel(t => windowOpenTest(t, { + source, + target: { + server: targetServer, + behavior: { preflight: PreflightBehavior.noCorsHeader(token()) }, + }, + expected: WindowOpenTestResult.FAILURE, + }), prefix + "missing CORS headers."); + + promise_test_parallel(t => windowOpenTest(t, { + source, + target: { + server: targetServer, + behavior: { preflight: PreflightBehavior.noPnaHeader(token()) }, + }, + expected: WindowOpenTestResult.FAILURE, + }), prefix + "missing PNA header."); + + promise_test_parallel(t => windowOpenTest(t, { + source, + target: { + server: targetServer, + behavior: { preflight: PreflightBehavior.success(token()) }, + }, + expected: WindowOpenTestResult.SUCCESS, + }), prefix + "success."); +} + +// 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', +}); + +promise_test_parallel(t => windowOpenTest(t, { + source: { server: Server.HTTPS_PRIVATE }, + target: { server: Server.HTTPS_PRIVATE }, + expected: WindowOpenTestResult.SUCCESS, +}), "private to private: no preflight required."); + +promise_test_parallel(t => windowOpenTest(t, { + source: { server: Server.HTTPS_PRIVATE }, + target: { server: Server.HTTPS_PUBLIC }, + expected: WindowOpenTestResult.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({ + 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", +}); + +promise_test_parallel(t => windowOpenTest(t, { + source: { server: Server.HTTPS_PUBLIC }, + target: { server: Server.HTTPS_PUBLIC }, + expected: WindowOpenTestResult.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. + +makePreflightTests({ + sourceServer: Server.HTTPS_LOCAL, + sourceTreatAsPublic: true, + sourceName: "treat-as-public-address", + targetServer: Server.OTHER_HTTPS_LOCAL, + targetName: "local", +}); + +promise_test_parallel( + t => windowOpenTest(t, { + source: { + server: Server.HTTPS_LOCAL, + treatAsPublic: true, + }, + target: {server: Server.HTTPS_LOCAL}, + expected: WindowOpenTestResult.SUCCESS, + }), + 'treat-as-public-address to local (same-origin): no preflight required.'); + +makePreflightTests({ + sourceServer: Server.HTTPS_LOCAL, + sourceTreatAsPublic: true, + sourceName: 'treat-as-public-address', + targetServer: Server.HTTPS_PRIVATE, + targetName: 'private', +}); + +promise_test_parallel( + t => windowOpenTest(t, { + source: { + server: Server.HTTPS_LOCAL, + treatAsPublic: true, + }, + target: {server: Server.HTTPS_PUBLIC}, + expected: WindowOpenTestResult.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: WindowOpenTestResult.SUCCESS, + }), + 'treat-as-public-address to local: optional preflight'); diff --git a/fetch/private-network-access/window-open.tentative.window.js b/fetch/private-network-access/window-open.tentative.window.js new file mode 100644 index 00000000000000..8d023775226b81 --- /dev/null +++ b/fetch/private-network-access/window-open.tentative.window.js @@ -0,0 +1,94 @@ +// 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/ +// +// 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(t => windowOpenTest(t, { + source: { server: Server.HTTP_LOCAL }, + target: { server: Server.HTTP_LOCAL }, + expected: WindowOpenTestResult.SUCCESS, +}), "local to local: no preflight required."); + +promise_test_parallel(t => windowOpenTest(t, { + source: { server: Server.HTTP_LOCAL }, + target: { server: Server.HTTP_PRIVATE }, + expected: WindowOpenTestResult.SUCCESS, +}), "local to private: no preflight required."); + +promise_test_parallel(t => windowOpenTest(t, { + source: { server: Server.HTTP_LOCAL }, + target: { server: Server.HTTP_PUBLIC }, + expected: WindowOpenTestResult.SUCCESS, +}), "local to public: no preflight required."); + +promise_test_parallel(t => windowOpenTest(t, { + source: { server: Server.HTTP_PRIVATE }, + target: { server: Server.HTTP_LOCAL }, + expected: WindowOpenTestResult.FAILURE, +}), "private to local: failure."); + +promise_test_parallel(t => windowOpenTest(t, { + source: { server: Server.HTTP_PRIVATE }, + target: { server: Server.HTTP_PRIVATE }, + expected: WindowOpenTestResult.SUCCESS, +}), "private to private: no preflight required."); + +promise_test_parallel(t => windowOpenTest(t, { + source: { server: Server.HTTP_PRIVATE }, + target: { server: Server.HTTP_PUBLIC }, + expected: WindowOpenTestResult.SUCCESS, +}), "private to public: no preflight required."); + +promise_test_parallel(t => windowOpenTest(t, { + source: { server: Server.HTTP_PUBLIC }, + target: { server: Server.HTTP_LOCAL }, + expected: WindowOpenTestResult.FAILURE, +}), "public to local: failure."); + +promise_test_parallel(t => windowOpenTest(t, { + source: { server: Server.HTTP_PUBLIC }, + target: { server: Server.HTTP_PRIVATE }, + expected: WindowOpenTestResult.FAILURE, +}), "public to private: failure."); + +promise_test_parallel(t => windowOpenTest(t, { + source: { server: Server.HTTP_PUBLIC }, + target: { server: Server.HTTP_PUBLIC }, + expected: WindowOpenTestResult.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: WindowOpenTestResult.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: WindowOpenTestResult.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: WindowOpenTestResult.SUCCESS, +}), "treat-as-public-address to public: no preflight required.");