Credentialless: WPT ServiceWorker proxying responses.

Check what happen when a ServiceWorker proxyies cross-origin opaque
responses toward a COEP:credentialless document.

Two tests:
1. one with a COEP:unsafe-none ServiceWorker,
2. one with a COEP:credentialless ServiceWorker.

What happens is that the response is evaluated against the CORP check
twice, once with client=ServiceWorker, and once with client=Document.
The CORP check requires CORP for responses requested with credentials,
so case 1 is blocked and case 2 not blocked.

Bug: 1175099
Change-Id: Ie3d721616d3d99ad07322f2a550f023af8f6d1c7
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2992196
Commit-Queue: Arthur Sonzogni <arthursonzogni@chromium.org>
Auto-Submit: Arthur Sonzogni <arthursonzogni@chromium.org>
Reviewed-by: Yifan Luo <lyf@chromium.org>
Reviewed-by: Mike West <mkwst@chromium.org>
Cr-Commit-Position: refs/heads/master@{#899106}
diff --git a/html/cross-origin-embedder-policy/credentialless/resources/common.js b/html/cross-origin-embedder-policy/credentialless/resources/common.js
index 68f8782..a5afd36 100644
--- a/html/cross-origin-embedder-policy/credentialless/resources/common.js
+++ b/html/cross-origin-embedder-policy/credentialless/resources/common.js
@@ -1,6 +1,7 @@
 const directory = '/html/cross-origin-embedder-policy/credentialless';
 const executor_path = directory + '/resources/executor.html?pipe=';
 const executor_js_path = directory + '/resources/executor.js?pipe=';
+const sw_executor_js_path = directory + '/resources/sw_executor.js?pipe=';
 
 // COEP
 const coep_none =
diff --git a/html/cross-origin-embedder-policy/credentialless/resources/sw_executor.js b/html/cross-origin-embedder-policy/credentialless/resources/sw_executor.js
new file mode 100644
index 0000000..0b47d66
--- /dev/null
+++ b/html/cross-origin-embedder-policy/credentialless/resources/sw_executor.js
@@ -0,0 +1,24 @@
+importScripts('./dispatcher.js');
+
+const params = new URLSearchParams(location.search);
+const uuid = params.get('uuid');
+
+// The fetch handler must be registered before parsing the main script response.
+// So do it here, for future use.
+fetchHandler = () => {}
+addEventListener('fetch', e => {
+  fetchHandler(e);
+});
+
+// Force ServiceWorker to immediately activate itself.
+addEventListener('install', event => {
+  skipWaiting();
+});
+
+let executeOrders = async function() {
+  while(true) {
+    let task = await receive(uuid);
+    eval(`(async () => {${task}})()`);
+  }
+};
+executeOrders();
diff --git a/html/cross-origin-embedder-policy/credentialless/service-worker-coep-credentialless-proxy.tentative.https.html b/html/cross-origin-embedder-policy/credentialless/service-worker-coep-credentialless-proxy.tentative.https.html
new file mode 100644
index 0000000..fe12ec8
--- /dev/null
+++ b/html/cross-origin-embedder-policy/credentialless/service-worker-coep-credentialless-proxy.tentative.https.html
@@ -0,0 +1,89 @@
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="/common/utils.js"></script>
+<script src="./resources/common.js"></script>
+<script src="./resources/dispatcher.js"></script>
+<script src="/service-workers/service-worker/resources/test-helpers.sub.js"></script>
+<script>
+
+const resource_path = directory + "/resources/";
+const same_origin = get_host_info().HTTPS_ORIGIN;
+const cross_origin = get_host_info().HTTPS_REMOTE_ORIGIN;
+
+promise_test(async test => {
+  const this_token_1 = token();
+  const this_token_2 = token();
+
+  // Register a COEP:credentialless ServiceWorker.
+  const sw_token = token();
+  const sw_url =
+    sw_executor_js_path + coep_credentialless + `&uuid=${sw_token}`;
+  const sw_registration =
+    await service_worker_unregister_and_register(test, sw_url, resource_path);
+  test.add_cleanup(() => sw_registration.unregister());
+  await wait_for_state(test, sw_registration.installing, 'activated');
+
+  // Configure the ServiceWorker to proxy the fetch requests. Wait for the
+  // worker to be installed and activated.
+  send(sw_token, `
+    fetchHandler = event => {
+      if (!event.request.url.includes("/proxied"))
+        return;
+
+      send("${this_token_1}", "ServiceWorker: Proxying");
+
+      // Response with a cross-origin no-cors resource.
+      const url = "${cross_origin}" + "/common/blank.html}";
+
+      event.respondWith(new Promise(async resolve => {
+        try {
+          let response = await fetch(url, {
+            mode: "no-cors",
+            credentials: "include"
+          });
+          send("${this_token_1}", "ServiceWorker: Fetch success");
+          resolve(response);
+        } catch (error) {
+          send("${this_token_1}", "ServiceWorker: Fetch failure");
+          resolve(new Response("", {status: 400}));
+        }
+      }));
+    }
+
+    await clients.claim();
+
+    send("${this_token_1}", serviceWorker.state);
+  `)
+  assert_equals(await receive(this_token_1), "activated");
+
+  // Create a COEP:credentialless document.
+  const document_token = environments["document"](coep_credentialless)[0];
+
+  // The document fetches a same-origin no-cors resource. The requests needs to
+  // be same-origin to be handled by the ServiceWorker.
+  send(document_token, `
+    try {
+      const response = await fetch("/proxied", { mode: "no-cors", });
+
+      send("${this_token_2}", "Document: Fetch success");
+    } catch (error) {
+      send("${this_token_2}", "Document: Fetch error");
+    }
+  `);
+
+  // The COEP:credentialless ServiceWorker is able to handle the cross-origin
+  // no-cors request, requested with credentials.
+  assert_equals(await receive(this_token_1), "ServiceWorker: Proxying");
+  assert_equals(await receive(this_token_1), "ServiceWorker: Fetch success");
+
+  // The COEP:credentialless Document is allowed by CORP to get it.
+  assert_equals(await receive(this_token_2), "Document: Fetch success");
+
+  // test.add_cleanup doesn't allow waiting for a promise. Unregistering a
+  // ServiceWorker is an asynchronous operation. It might not be completed on
+  // time for the next test. Do it here for extra flakiness safety.
+  await sw_registration.unregister()
+}, "COEP:credentialless ServiceWorker");
+
+</script>
diff --git a/html/cross-origin-embedder-policy/credentialless/service-worker-coep-none-proxy.tentative.https.html b/html/cross-origin-embedder-policy/credentialless/service-worker-coep-none-proxy.tentative.https.html
new file mode 100644
index 0000000..599623b
--- /dev/null
+++ b/html/cross-origin-embedder-policy/credentialless/service-worker-coep-none-proxy.tentative.https.html
@@ -0,0 +1,91 @@
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="/common/utils.js"></script>
+<script src="./resources/common.js"></script>
+<script src="./resources/dispatcher.js"></script>
+<script src="/service-workers/service-worker/resources/test-helpers.sub.js"></script>
+<script>
+
+const resource_path = directory + "/resources/";
+const same_origin = get_host_info().HTTPS_ORIGIN;
+const cross_origin = get_host_info().HTTPS_REMOTE_ORIGIN;
+
+promise_test(async test => {
+  const this_token_1 = token();
+  const this_token_2 = token();
+
+  // Register a COEP:none ServiceWorker.
+  const sw_token = token();
+  const sw_url = sw_executor_js_path + coep_none + `&uuid=${sw_token}`;
+  const sw_registration =
+    await service_worker_unregister_and_register(test, sw_url, resource_path);
+  test.add_cleanup(() => sw_registration.unregister());
+  await wait_for_state(test, sw_registration.installing, 'activated');
+
+  // Configure the ServiceWorker to proxy the fetch requests. Wait for the
+  // worker to be installed and activated.
+  send(sw_token, `
+    fetchHandler = event => {
+      if (!event.request.url.includes("/proxied"))
+        return;
+
+      send("${this_token_1}", "ServiceWorker: Proxying");
+
+      // Response with a cross-origin no-cors resource.
+      const url = "${cross_origin}" + "/common/blank.html}";
+
+      event.respondWith(new Promise(async resolve => {
+        try {
+          let response = await fetch(url, {
+            mode: "no-cors",
+            credentials: "include"
+          });
+          send("${this_token_1}", "ServiceWorker: Fetch success");
+          resolve(response);
+        } catch (error) {
+          send("${this_token_1}", "ServiceWorker: Fetch failure");
+          resolve(new Response("", {status: 400}));
+        }
+      }));
+    }
+
+    await clients.claim();
+
+    send("${this_token_1}", serviceWorker.state);
+  `)
+  assert_equals(await receive(this_token_1), "activated");
+
+  // Create a COEP:credentialless document.
+  const document_token = environments["document"](coep_credentialless)[0];
+
+  // The document fetches a same-origin no-cors resource. The requests needs to
+  // be same-origin to be handled by the ServiceWorker.
+  send(document_token, `
+    try {
+      const response = await fetch("/proxied", {
+        mode: "no-cors",
+        credentials: "include"
+      });
+
+      send("${this_token_2}", "Document: Fetch success");
+    } catch (error) {
+      send("${this_token_2}", "Document: Fetch error");
+    }
+  `);
+
+  // The COEP:unsafe-none ServiceWorker is able to handle the cross-origin
+  // no-cors request, requested with credentials.
+  assert_equals(await receive(this_token_1), "ServiceWorker: Proxying");
+  assert_equals(await receive(this_token_1), "ServiceWorker: Fetch success");
+
+  // However, the COEP:credentialless Document is disallowed by CORP to get it.
+  assert_equals(await receive(this_token_2), "Document: Fetch error");
+
+  // test.add_cleanup doesn't allow waiting for a promise. Unregistering a
+  // ServiceWorker is an asynchronous operation. It might not be completed on
+  // time for the next test. Do it here for extra flakiness safety.
+  await sw_registration.unregister()
+}, "COEP:unsafe-none ServiceWorker");
+
+</script>