Skip to content

Commit

Permalink
[DIP] Add support for isolate-and-credentialless
Browse files Browse the repository at this point in the history
This CL ensures that no-CORS cross-origin subresources requests from
documents with Document-Isolation-Policy: isolate-and-credentialless are
stripped of credentials. Since CORP checks already support
Document-Isolation-Policy: isolate-and-credentialless, this is enough to
enable cross-origin isolation in documents with
Document-Isolation-Policy: isolate-and-credentialless.

Bug: 349792240
Change-Id: I21bacba16a62dc8dd7c101450819466208815f42
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5867191
Commit-Queue: Camille Lamy <[email protected]>
Reviewed-by: Kenichi Ishibashi <[email protected]>
Reviewed-by: Alex Moshchuk <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1365616}
  • Loading branch information
Camille Lamy authored and chromium-wpt-export-bot committed Oct 8, 2024
1 parent ec97938 commit 5eec73e
Show file tree
Hide file tree
Showing 17 changed files with 1,452 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
// META: timeout=long
// META: variant=?document
// META: variant=?dedicated_worker
// META: variant=?shared_worker
// META: variant=?service_worker
// META: script=/common/get-host-info.sub.js
// META: script=/common/utils.js
// META: script=/common/dispatcher/dispatcher.js
// META: script=./resources/common.js

// Fetch a resource and store it into CacheStorage from |storer| context. Then
// check if it can be retrieved via CacheStorage.match from |retriever| context.
const cacheStorageTest = (
description,
storer,
retriever,
resource_headers,
request_credential_mode,
expectation
) => {
promise_test_parallel(async test => {
const cross_origin = get_host_info().HTTPS_REMOTE_ORIGIN;
const url = cross_origin + "/common/square.png?pipe=" + resource_headers +
`&${token()}`;
const this_token = token();

// Fetch a request from |stored|. Store the opaque response into
// CacheStorage.
send(storer, `
const cache = await caches.open("v1");
const fetch_request = new Request("${url}", {
mode: 'no-cors',
credentials: '${request_credential_mode}'
});
const fetch_response = await fetch(fetch_request);
await cache.put(fetch_request, fetch_response);
send("${this_token}", "stored");
`);
assert_equals(await receive(this_token), "stored");

// Retrieved it from |retriever|.
send(retriever, `
const cache = await caches.open("v1");
try {
const response = await cache.match("${url}");
send("${this_token}", "retrieved");
} catch (error) {
send("${this_token}", "error");
}
`);
assert_equals(await receive(this_token), expectation);
}, description);
};

// Execute the same set of tests for every type of execution contexts:
// Documents, DedicatedWorkers, SharedWorkers, and ServiceWorkers. The results
// should be independent of the context.
const environment = location.search.substr(1);
const constructor = environments[environment];

const context_none = constructor(coep_none)[0];
const context_credentialless = constructor(dip_credentialless)[0];
const context_require_corp = constructor(dip_require_corp)[0];

cacheStorageTest(`[${environment}] none => none`,
context_none,
context_none,
"",
"include",
"retrieved");
cacheStorageTest(`[${environment}] none => isolate-and-credentialless`,
context_none,
context_credentialless,
"",
"include",
"error");
cacheStorageTest(`[${environment}] none => isolate-and-credentialless (omit)`,
context_none,
context_credentialless,
"",
"omit",
"retrieved");
cacheStorageTest(`[${environment}] none => isolate-and-credentialless + CORP`,
context_none,
context_credentialless,
corp_cross_origin,
"include",
"retrieved");
cacheStorageTest(`[${environment}] none => isolate-and-require-corp`,
context_none,
context_require_corp,
"",
"include",
"error");
cacheStorageTest(`[${environment}] none => isolate-and-require-corp (omit)`,
context_none,
context_require_corp,
"",
"include",
"error");
cacheStorageTest(`[${environment}] none => isolate-and-require-corp + CORP`,
context_none,
context_require_corp,
corp_cross_origin,
"include",
"retrieved");

cacheStorageTest(`[${environment}] isolate-and-credentialless => none`,
context_credentialless,
context_none,
"",
"include",
"retrieved");
cacheStorageTest(`[${environment}] isolate-and-credentialless => isolate-and-credentialless`,
context_credentialless,
context_credentialless,
"",
"include",
"retrieved");
cacheStorageTest(`[${environment}] isolate-and-credentialless => isolate-and-require-corp`,
context_credentialless,
context_require_corp,
"",
"include",
"error");
cacheStorageTest(`[${environment}] isolate-and-credentialless => isolate-and-require-corp + CORP`,
context_credentialless,
context_require_corp,
corp_cross_origin,
"include",
"retrieved");

cacheStorageTest(`[${environment}] isolate-and-require-corp => none`,
context_require_corp,
context_none,
corp_cross_origin,
"include",
"retrieved");
cacheStorageTest(`[${environment}] isolate-and-require-corp => isolate-and-credentialless`,
context_require_corp,
context_credentialless,
corp_cross_origin,
"include",
"retrieved");
cacheStorageTest(`[${environment}] isolate-and-require-corp => isolate-and-require-corp`,
context_require_corp,
context_require_corp,
corp_cross_origin,
"include",
"retrieved");
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
// META: script=/common/get-host-info.sub.js
// META: script=/common/utils.js
// META: script=/common/dispatcher/dispatcher.js
// META: script=./resources/common.js

// With DIP:isolate-and-credentialless, requesting a resource without
// credentials MUST NOT return a response requested with credentials. This would
// be a security issue, since DIP:isolate-and-credentialless can be used to
// enable crossOriginIsolation.
//
// The test the behavior of the HTTP cache:
// 1. b.com stores cookie.
// 2. a.com(DIP:none): request b.com's resource.
// 3. a.com(DIP:isolate-and-credentialless): request b.com's resource.
//
// The first time, the resource is requested with credentials. The response is
// served with Cache-Control: max-age=31536000. It enters the cache.
// The second time, the resource is requested without credentials. The response
// in the cache must not be returned.

const cookie_key = "dip_cache_key";
const cookie_value = "dip_cache_value";
const same_origin = get_host_info().HTTPS_ORIGIN;
const cross_origin = get_host_info().HTTPS_REMOTE_ORIGIN;

const GetCookie = (response) => {
return parseCookies(JSON.parse(response))[cookie_key];
}

// "same_origin" document with DIP:none.
const w_dip_none_token = token();
const w_dip_none_url = same_origin + executor_path + dip_none +
`&uuid=${w_dip_none_token}`
const w_dip_none = window.open(w_dip_none_url);
add_completion_callback(() => w_dip_none.close());

// "same_origin" document with DIP:isolate-and-credentialles.
const w_dip_credentialless_token = token();
const w_dip_credentialless_url = same_origin + executor_path +
dip_credentialless + `&uuid=${w_dip_credentialless_token}`
const w_dip_credentialless = window.open(w_dip_credentialless_url);
add_completion_callback(() => w_dip_credentialless.close());

const this_token = token();

// A request toward a "cross-origin" cacheable response.
const request_token = token();
const request_url = cacheableShowRequestHeaders(cross_origin, request_token);

promise_setup(async test => {
await setCookie(cross_origin, cookie_key, cookie_value + cookie_same_site_none);
}, "Set cookie");

// The "same-origin" DIP:none document fetches a "cross-origin"
// resource. The request is sent with credentials.
promise_setup(async test => {
send(w_dip_none_token, `
await fetch("${request_url}", {
mode : "no-cors",
credentials: "include",
});
send("${this_token}", "Resource fetched");
`);

assert_equals(await receive(this_token), "Resource fetched");
assert_equals(await receive(request_token).then(GetCookie), cookie_value);
}, "Cache a response requested with credentials");

// The "same-origin" DIP:isolate-andcredentialless document fetches the same
// resource without credentials. The HTTP cache must not be used. Instead a
// second request must be made without credentials.
promise_test(async test => {
send(w_dip_credentialless_token, `
await fetch("${request_url}", {
mode : "no-cors",
credentials: "include",
});
send("${this_token}", "Resource fetched");
`);

assert_equals(await receive(this_token), "Resource fetched");

test.step_timeout(test.unreached_func("The HTTP cache has been used"), 1500);
assert_equals(await receive(request_token).then(GetCookie), undefined);
}, "The HTTP cache must not be used");
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// META: script=/common/get-host-info.sub.js
// META: script=/common/utils.js
// META: script=/common/dispatcher/dispatcher.js
// META: script=./resources/common.js

const http = get_host_info().HTTP_ORIGIN;
const https = get_host_info().HTTPS_ORIGIN;

let crossOriginIsolatedTest = (
description,
origin ,
headers,
expect_crossOriginIsolated) => {
promise_test_parallel(async test => {
const w_token = token();
const w_url = origin + executor_path + headers + `&uuid=${w_token}`;
const w = window.open(w_url)
add_completion_callback(() => w.close());

const this_token = token();
send(w_token, `
if (window.crossOriginIsolated)
send("${this_token}", "crossOriginIsolated");
else
send("${this_token}", "not isolated")
`);
assert_equals(await receive(this_token), expect_crossOriginIsolated);
}, description);
}

crossOriginIsolatedTest("Main crossOriginIsolated case:",
https, dip_credentialless, "crossOriginIsolated");

crossOriginIsolatedTest("Missing HTTPS:",
http, dip_credentialless, "not isolated");

crossOriginIsolatedTest("Report-only:",
https, dip_report_only_credentialless, "not isolated");

crossOriginIsolatedTest("Report-only + enforced:",
https, dip_credentialless +
dip_report_only_credentialless, "crossOriginIsolated");
Loading

0 comments on commit 5eec73e

Please sign in to comment.