Skip to content

Commit a46a8de

Browse files
letitzchromium-wpt-export-bot
authored andcommitted
[Private Network Access] Web Platform Tests for redirects.
Bug: chromium:1293891 Change-Id: Ia22fe59ff99246db94cbbab99192597e716692d6 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3439867 Auto-Submit: Titouan Rigoudy Reviewed-by: Arthur Hemery Commit-Queue: Titouan Rigoudy Cr-Commit-Position: refs/heads/main@{#967321}
1 parent 893e71a commit a46a8de

File tree

6 files changed

+259
-24
lines changed

6 files changed

+259
-24
lines changed
Lines changed: 232 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,232 @@
1+
// META: script=/common/utils.js
2+
// META: script=resources/support.sub.js
3+
//
4+
// Spec: https://wicg.github.io/private-network-access/#integration-fetch
5+
//
6+
// This test verifies that Private Network Access checks are applied to all
7+
// the endpoints in a redirect chain, relative to the same client context.
8+
9+
// local -> private -> public
10+
//
11+
// Request 1 (local -> private): no preflight.
12+
// Request 2 (local -> public): no preflight.
13+
14+
promise_test(t => fetchTest(t, {
15+
source: { server: Server.HTTPS_LOCAL },
16+
target: {
17+
server: Server.HTTPS_PRIVATE,
18+
behavior: {
19+
response: ResponseBehavior.allowCrossOrigin(),
20+
redirect: preflightUrl({
21+
server: Server.HTTPS_PUBLIC,
22+
behavior: { response: ResponseBehavior.allowCrossOrigin() },
23+
}),
24+
}
25+
},
26+
expected: FetchTestResult.SUCCESS,
27+
}), "local to private to public: success.");
28+
29+
// local -> private -> local
30+
//
31+
// Request 1 (local -> private): no preflight.
32+
// Request 2 (local -> local): no preflight.
33+
//
34+
// This checks that the client for the second request is still the initial
35+
// context, not the redirector.
36+
37+
promise_test(t => fetchTest(t, {
38+
source: { server: Server.HTTPS_LOCAL },
39+
target: {
40+
server: Server.HTTPS_PRIVATE,
41+
behavior: {
42+
response: ResponseBehavior.allowCrossOrigin(),
43+
redirect: preflightUrl({
44+
server: Server.HTTPS_LOCAL,
45+
behavior: { response: ResponseBehavior.allowCrossOrigin() },
46+
}),
47+
}
48+
},
49+
expected: FetchTestResult.SUCCESS,
50+
}), "local to private to local: success.");
51+
52+
// private -> private -> local
53+
//
54+
// Request 1 (private -> private): no preflight.
55+
// Request 2 (private -> local): preflight required.
56+
//
57+
// This verifies that PNA checks are applied after redirects.
58+
59+
promise_test(t => fetchTest(t, {
60+
source: { server: Server.HTTPS_PRIVATE },
61+
target: {
62+
server: Server.HTTPS_PRIVATE,
63+
behavior: {
64+
redirect: preflightUrl({
65+
server: Server.HTTPS_LOCAL,
66+
behavior: { response: ResponseBehavior.allowCrossOrigin() },
67+
}),
68+
}
69+
},
70+
expected: FetchTestResult.FAILURE,
71+
}), "private to private to local: failed preflight.");
72+
73+
promise_test(t => fetchTest(t, {
74+
source: { server: Server.HTTPS_PRIVATE },
75+
target: {
76+
server: Server.HTTPS_PRIVATE,
77+
behavior: {
78+
redirect: preflightUrl({
79+
server: Server.HTTPS_LOCAL,
80+
behavior: {
81+
preflight: PreflightBehavior.success(token()),
82+
response: ResponseBehavior.allowCrossOrigin(),
83+
},
84+
}),
85+
}
86+
},
87+
expected: FetchTestResult.SUCCESS,
88+
}), "private to private to local: success.");
89+
90+
promise_test(t => fetchTest(t, {
91+
source: { server: Server.HTTPS_PRIVATE },
92+
target: {
93+
server: Server.HTTPS_PRIVATE,
94+
behavior: {
95+
redirect: preflightUrl({
96+
server: Server.HTTPS_LOCAL,
97+
behavior: { preflight: PreflightBehavior.success(token()) },
98+
}),
99+
}
100+
},
101+
fetchOptions: { mode: "no-cors" },
102+
expected: FetchTestResult.OPAQUE,
103+
}), "private to private to local: no-cors success.");
104+
105+
// private -> local -> private
106+
//
107+
// Request 1 (private -> local): preflight required.
108+
// Request 2 (private -> private): no preflight.
109+
//
110+
// This verifies that PNA checks are applied independently to every step in a
111+
// redirect chain.
112+
113+
promise_test(t => fetchTest(t, {
114+
source: { server: Server.HTTPS_PRIVATE },
115+
target: {
116+
server: Server.HTTPS_LOCAL,
117+
behavior: {
118+
response: ResponseBehavior.allowCrossOrigin(),
119+
redirect: preflightUrl({
120+
server: Server.HTTPS_PRIVATE,
121+
}),
122+
}
123+
},
124+
expected: FetchTestResult.FAILURE,
125+
}), "private to local to private: failed preflight.");
126+
127+
promise_test(t => fetchTest(t, {
128+
source: { server: Server.HTTPS_PRIVATE },
129+
target: {
130+
server: Server.HTTPS_LOCAL,
131+
behavior: {
132+
preflight: PreflightBehavior.success(token()),
133+
response: ResponseBehavior.allowCrossOrigin(),
134+
redirect: preflightUrl({
135+
server: Server.HTTPS_PRIVATE,
136+
behavior: { response: ResponseBehavior.allowCrossOrigin() },
137+
}),
138+
}
139+
},
140+
expected: FetchTestResult.SUCCESS,
141+
}), "private to local to private: success.");
142+
143+
promise_test(t => fetchTest(t, {
144+
source: { server: Server.HTTPS_PRIVATE },
145+
target: {
146+
server: Server.HTTPS_LOCAL,
147+
behavior: {
148+
preflight: PreflightBehavior.success(token()),
149+
redirect: preflightUrl({ server: Server.HTTPS_PRIVATE }),
150+
}
151+
},
152+
fetchOptions: { mode: "no-cors" },
153+
expected: FetchTestResult.OPAQUE,
154+
}), "private to local to private: no-cors success.");
155+
156+
// public -> private -> local
157+
//
158+
// Request 1 (public -> private): preflight required.
159+
// Request 2 (public -> local): preflight required.
160+
//
161+
// This verifies that PNA checks are applied to every step in a redirect chain.
162+
163+
promise_test(t => fetchTest(t, {
164+
source: { server: Server.HTTPS_PUBLIC },
165+
target: {
166+
server: Server.HTTPS_PRIVATE,
167+
behavior: {
168+
response: ResponseBehavior.allowCrossOrigin(),
169+
redirect: preflightUrl({
170+
server: Server.HTTPS_LOCAL,
171+
behavior: {
172+
preflight: PreflightBehavior.success(token()),
173+
response: ResponseBehavior.allowCrossOrigin(),
174+
},
175+
}),
176+
}
177+
},
178+
expected: FetchTestResult.FAILURE,
179+
}), "public to private to local: failed first preflight.");
180+
181+
promise_test(t => fetchTest(t, {
182+
source: { server: Server.HTTPS_PUBLIC },
183+
target: {
184+
server: Server.HTTPS_PRIVATE,
185+
behavior: {
186+
preflight: PreflightBehavior.success(token()),
187+
response: ResponseBehavior.allowCrossOrigin(),
188+
redirect: preflightUrl({
189+
server: Server.HTTPS_LOCAL,
190+
behavior: {
191+
response: ResponseBehavior.allowCrossOrigin(),
192+
},
193+
}),
194+
}
195+
},
196+
expected: FetchTestResult.FAILURE,
197+
}), "public to private to local: failed second preflight.");
198+
199+
promise_test(t => fetchTest(t, {
200+
source: { server: Server.HTTPS_PUBLIC },
201+
target: {
202+
server: Server.HTTPS_PRIVATE,
203+
behavior: {
204+
preflight: PreflightBehavior.success(token()),
205+
response: ResponseBehavior.allowCrossOrigin(),
206+
redirect: preflightUrl({
207+
server: Server.HTTPS_LOCAL,
208+
behavior: {
209+
preflight: PreflightBehavior.success(token()),
210+
response: ResponseBehavior.allowCrossOrigin(),
211+
},
212+
}),
213+
}
214+
},
215+
expected: FetchTestResult.SUCCESS,
216+
}), "public to private to local: success.");
217+
218+
promise_test(t => fetchTest(t, {
219+
source: { server: Server.HTTPS_PUBLIC },
220+
target: {
221+
server: Server.HTTPS_PRIVATE,
222+
behavior: {
223+
preflight: PreflightBehavior.success(token()),
224+
redirect: preflightUrl({
225+
server: Server.HTTPS_LOCAL,
226+
behavior: { preflight: PreflightBehavior.success(token()) },
227+
}),
228+
}
229+
},
230+
fetchOptions: { mode: "no-cors" },
231+
expected: FetchTestResult.OPAQUE,
232+
}), "public to private to local: no-cors success.");

fetch/private-network-access/resources/preflight.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@
3636
#
3737
# The following parameters only affect non-preflight responses:
3838
#
39+
# - redirect: If set, the response code is set to 301 and the `Location`
40+
# response header is set to this value.
3941
# - mime-type: If set, the `Content-Type` response header is set to this value.
4042
# - file: Specifies a path (relative to this file's directory) to a file. If
4143
# set, the response body is copied from this file.
@@ -123,6 +125,11 @@ def _handle_final_request(request, response):
123125
mode = request.GET.get(b"final-headers")
124126
headers = _get_response_headers(request.method, mode)
125127

128+
redirect = request.GET.get(b"redirect")
129+
if redirect is not None:
130+
headers.append(("Location", redirect))
131+
return (301, headers, b"")
132+
126133
mime_type = request.GET.get(b"mime-type")
127134
if mime_type is not None:
128135
headers.append(("Content-Type", mime_type),)

fetch/private-network-access/resources/support.sub.js

Lines changed: 17 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -128,22 +128,27 @@ function sourceResolveOptions({ server, treatAsPublic }) {
128128
return options;
129129
}
130130

131-
// Computes options to pass to `resolveUrl()` for `resources/preflight.py`.
131+
// Computes the URL of a preflight handler configured with the given options.
132132
//
133133
// `server` identifies the server from which to load the resource.
134134
// `behavior` specifies the behavior of the target server. It may contain:
135135
// - `preflight`: The result of calling one of `PreflightBehavior`'s methods.
136136
// - `response`: The result of calling one of `ResponseBehavior`'s methods.
137-
function targetResolveOptions({ server, behavior }) {
137+
// - `redirect`: A URL to which the target should redirect GET requests.
138+
function preflightUrl({ server, behavior }) {
138139
const options = {...server};
139140
if (behavior) {
140-
const { preflight, response } = behavior;
141+
const { preflight, response, redirect } = behavior;
141142
options.searchParams = {
142143
...preflight,
143144
...response,
144145
};
146+
if (redirect !== undefined) {
147+
options.searchParams.redirect = redirect;
148+
}
145149
}
146-
return options;
150+
151+
return resolveUrl("resources/preflight.py", options);
147152
}
148153

149154
// Methods generate behavior specifications for how `resources/preflight.py`
@@ -206,7 +211,7 @@ const FetchTestResult = {
206211
// // Optional. Passed to `sourceResolveOptions()`.
207212
// source,
208213
//
209-
// // Optional. Passed to `targetResolveOptions()`.
214+
// // Optional. Passed to `preflightUrl()`.
210215
// target,
211216
//
212217
// // Optional. Passed to `fetch()`.
@@ -220,8 +225,7 @@ async function fetchTest(t, { source, target, fetchOptions, expected }) {
220225
const sourceUrl =
221226
resolveUrl("resources/fetcher.html", sourceResolveOptions(source));
222227

223-
const targetUrl =
224-
resolveUrl("resources/preflight.py", targetResolveOptions(target));
228+
const targetUrl = preflightUrl(target);
225229

226230
const iframe = await appendIframe(t, document, sourceUrl);
227231
const reply = futureMessage();
@@ -264,7 +268,7 @@ const XhrTestResult = {
264268
// // Optional. Passed to `sourceResolveOptions()`.
265269
// source,
266270
//
267-
// // Optional. Passed to `targetResolveOptions()`.
271+
// // Optional. Passed to `preflightUrl()`.
268272
// target,
269273
//
270274
// // Optional. Method to use when sending the request. Defaults to "GET".
@@ -278,8 +282,7 @@ async function xhrTest(t, { source, target, method, expected }) {
278282
const sourceUrl =
279283
resolveUrl("resources/xhr-sender.html", sourceResolveOptions(source));
280284

281-
const targetUrl =
282-
resolveUrl("resources/preflight.py", targetResolveOptions(target));
285+
const targetUrl = preflightUrl(target);
283286

284287
const iframe = await appendIframe(t, document, sourceUrl);
285288
const reply = futureMessage();
@@ -344,8 +347,7 @@ const WorkerScriptTestResult = {
344347
};
345348

346349
function workerScriptUrl(target) {
347-
const url =
348-
resolveUrl("resources/preflight.py", targetResolveOptions(target));
350+
const url = preflightUrl(target);
349351

350352
url.searchParams.append("body", "postMessage({ loaded: true })")
351353
url.searchParams.append("mime-type", "application/javascript")
@@ -395,8 +397,7 @@ async function sharedWorkerScriptTest(t, { source, target, expected }) {
395397
const sourceUrl = resolveUrl("resources/shared-worker-fetcher.html",
396398
sourceResolveOptions(source));
397399

398-
const targetUrl =
399-
resolveUrl("resources/preflight.py", targetResolveOptions(target));
400+
const targetUrl = preflightUrl(target);
400401
targetUrl.searchParams.append(
401402
"body", "onconnect = (e) => e.ports[0].postMessage({ loaded: true })")
402403

@@ -418,8 +419,7 @@ const WorkerFetchTestResult = {
418419
};
419420

420421
async function workerFetchTest(t, { source, target, expected }) {
421-
const targetUrl =
422-
resolveUrl("resources/preflight.py", targetResolveOptions(target));
422+
const targetUrl = preflightUrl(target);
423423

424424
const sourceUrl =
425425
resolveUrl("resources/fetcher.js", sourceResolveOptions(source));
@@ -439,8 +439,7 @@ async function workerFetchTest(t, { source, target, expected }) {
439439
}
440440

441441
async function sharedWorkerFetchTest(t, { source, target, expected }) {
442-
const targetUrl =
443-
resolveUrl("resources/preflight.py", targetResolveOptions(target));
442+
const targetUrl = preflightUrl(target);
444443

445444
const sourceUrl =
446445
resolveUrl("resources/shared-fetcher.js", sourceResolveOptions(source));

fetch/private-network-access/service-worker-fetch.https.window.js

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,7 @@ async function makeTest(t, { source, target, expected }) {
2121
const scriptUrl =
2222
resolveUrl("resources/service-worker.js", sourceResolveOptions(source));
2323

24-
const realTargetUrl =
25-
resolveUrl("resources/preflight.py", targetResolveOptions(target));
24+
const realTargetUrl = preflightUrl(target);
2625

2726
// Fetch a URL within the service worker's scope, but tell it which URL to
2827
// really fetch.

fetch/private-network-access/service-worker-update.https.window.js

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,7 @@ async function makeTest(t, { target, expected }) {
2828
"resources/service-worker-bridge.html",
2929
sourceResolveOptions({ server: target.server }));
3030

31-
const scriptUrl =
32-
resolveUrl("resources/preflight.py", targetResolveOptions(target));
31+
const scriptUrl = preflightUrl(target);
3332
scriptUrl.searchParams.append("treat-as-public-once", token());
3433
scriptUrl.searchParams.append("mime-type", "application/javascript");
3534
scriptUrl.searchParams.append("file", "service-worker.js");

fetch/private-network-access/service-worker.https.window.js

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,7 @@ async function makeTest(t, { source, target, expected }) {
2424
const sourceUrl = resolveUrl("resources/service-worker-bridge.html",
2525
sourceResolveOptions(source));
2626

27-
const targetUrl =
28-
resolveUrl("resources/preflight.py", targetResolveOptions(target));
27+
const targetUrl = preflightUrl(target);
2928
targetUrl.searchParams.append("body", "undefined");
3029
targetUrl.searchParams.append("mime-type", "application/javascript");
3130

0 commit comments

Comments
 (0)