diff options
Diffstat (limited to 'netwerk/cookie/test/browser')
-rw-r--r-- | netwerk/cookie/test/browser/browser.toml | 6 | ||||
-rw-r--r-- | netwerk/cookie/test/browser/browser_cookie_chips.js | 539 | ||||
-rw-r--r-- | netwerk/cookie/test/browser/browser_cookies_serviceWorker.js | 540 | ||||
-rw-r--r-- | netwerk/cookie/test/browser/chips.sjs | 28 | ||||
-rw-r--r-- | netwerk/cookie/test/browser/cookies.sjs | 17 | ||||
-rw-r--r-- | netwerk/cookie/test/browser/serviceWorker.js | 21 |
6 files changed, 1151 insertions, 0 deletions
diff --git a/netwerk/cookie/test/browser/browser.toml b/netwerk/cookie/test/browser/browser.toml index 05a302ddbd..56807e4bf1 100644 --- a/netwerk/cookie/test/browser/browser.toml +++ b/netwerk/cookie/test/browser/browser.toml @@ -8,6 +8,9 @@ support-files = [ ["browser_broadcastChannel.js"] +["browser_cookie_chips.js"] +support-files = ["chips.sjs"] + ["browser_cookie_insecure_overwrites_secure.js"] ["browser_cookie_purge_sync.js"] @@ -17,6 +20,9 @@ support-files = ["server.sjs"] ["browser_cookies_ipv6.js"] +["browser_cookies_serviceWorker.js"] +support-files = ["cookies.sjs", "serviceWorker.js"] + ["browser_domCache.js"] ["browser_indexedDB.js"] diff --git a/netwerk/cookie/test/browser/browser_cookie_chips.js b/netwerk/cookie/test/browser/browser_cookie_chips.js new file mode 100644 index 0000000000..ba0c8f3a0e --- /dev/null +++ b/netwerk/cookie/test/browser/browser_cookie_chips.js @@ -0,0 +1,539 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +add_setup(() => { + // These are functional and not integration tests, cookieBehavior accept lets + // us have StorageAccess on thirparty. + Services.prefs.setIntPref( + "network.cookie.cookieBehavior", + Ci.nsICookieService.BEHAVIOR_ACCEPT + ); + Services.prefs.setBoolPref( + "network.cookieJarSettings.unblocked_for_testing", + true + ); + Services.prefs.setBoolPref( + "network.cookie.cookieBehavior.optInPartitioning", + true + ); + Services.prefs.setBoolPref("dom.storage_access.enabled", true); + Services.prefs.setBoolPref("dom.storage_access.prompt.testing", true); + Services.cookies.removeAll(); + Services.perms.removeAll(); +}); + +registerCleanupFunction(() => { + Services.prefs.clearUserPref("network.cookie.cookieBehavior"); + Services.prefs.clearUserPref( + "network.cookieJarSettings.unblocked_for_testing" + ); + Services.prefs.clearUserPref( + "network.cookie.cookieBehavior.optInPartitioning" + ); + Services.prefs.clearUserPref("dom.storage_access.enabled"); + Services.prefs.clearUserPref("dom.storage_access.prompt.testing"); + Services.cookies.removeAll(); + Services.perms.removeAll(); +}); + +const COOKIE_PARTITIONED = + "cookie=partitioned; Partitioned; Secure; SameSite=None;"; +const COOKIE_UNPARTITIONED = "cookie=unpartitioned; Secure; SameSite=None;"; + +const PATH = "/browser/netwerk/cookie/test/browser/"; +const PATH_EMPTY = PATH + "file_empty.html"; +const HTTP_COOKIE_SET = PATH + "chips.sjs?set"; +const HTTP_COOKIE_GET = PATH + "chips.sjs?get"; + +const FIRST_PARTY = "example.com"; +const THIRD_PARTY = "example.org"; + +const URL_DOCUMENT_FIRSTPARTY = "https://" + FIRST_PARTY + PATH_EMPTY; +const URL_DOCUMENT_THIRDPARTY = "https://" + THIRD_PARTY + PATH_EMPTY; +const URL_HTTP_FIRSTPARTY = "https://" + FIRST_PARTY + "/" + HTTP_COOKIE_SET; +const URL_HTTP_THIRDPARTY = "https://" + THIRD_PARTY + "/" + HTTP_COOKIE_SET; + +function createOriginAttributes(partitionKey) { + return JSON.stringify({ + firstPartyDomain: "", + geckoViewSessionContextId: "", + inIsolatedMozBrowser: false, + partitionKey, + privateBrowsingId: 0, + userContextId: 0, + }); +} + +function createPartitonKey(url) { + let uri = NetUtil.newURI(url); + return `(${uri.scheme},${uri.host})`; +} + +// OriginAttributes used to access partitioned and unpartitioned cookie jars +// in all tests. +const partitionedOAs = createOriginAttributes( + createPartitonKey(URL_DOCUMENT_FIRSTPARTY) +); +const unpartitionedOAs = createOriginAttributes(""); + +// Set partitioned and unpartitioned cookie from first-party document. +// CHIPS "Partitioned" cookie MUST always be stored in partitioned jar. +// This calls CookieServiceChild::SetCookieStringFromDocument() internally. +// CookieService::SetCookieStringFromDocument() is not explicitly tested since +// CHIPS are in the common function CookieCommons::CreateCookieFromDocument(). +add_task( + async function test_chips_store_partitioned_document_first_party_child() { + const tab = BrowserTestUtils.addTab(gBrowser, URL_DOCUMENT_FIRSTPARTY); + const browser = gBrowser.getBrowserForTab(tab); + await BrowserTestUtils.browserLoaded(browser); + + // Set partitioned and unpartitioned cookie from document child-side + await SpecialPowers.spawn( + browser, + [COOKIE_PARTITIONED, COOKIE_UNPARTITIONED], + (partitioned, unpartitioned) => { + content.document.cookie = partitioned; + content.document.cookie = unpartitioned; + } + ); + + // Get cookies from partitioned jar + let partitioned = Services.cookies.getCookiesWithOriginAttributes( + partitionedOAs, + FIRST_PARTY + ); + // Get cookies from unpartitioned jar + let unpartitioned = Services.cookies.getCookiesWithOriginAttributes( + unpartitionedOAs, + FIRST_PARTY + ); + + // Assert partitioned/unpartitioned cookie were stored in correct jars + Assert.equal(partitioned.length, 1); + Assert.equal(partitioned[0].value, "partitioned"); + Assert.equal(unpartitioned.length, 1); + Assert.equal(unpartitioned[0].value, "unpartitioned"); + + // Cleanup + BrowserTestUtils.removeTab(tab); + Services.cookies.removeAll(); + } +); + +// Set partitioned and unpartitioned cookie from third-party document with storage +// access. CHIPS "Partitioned" cookie MUST always be stored in partitioned jar. +// This calls CookieServiceChild::SetCookieStringFromDocument() internally. +// CookieService::SetCookieStringFromDocument() is not explicitly tested since +// CHIPS are in the common function CookieCommons::CreateCookieFromDocument(). +add_task( + async function test_chips_store_partitioned_document_third_party_storage_access_child() { + const tab = BrowserTestUtils.addTab(gBrowser, URL_DOCUMENT_FIRSTPARTY); + const browser = gBrowser.getBrowserForTab(tab); + await BrowserTestUtils.browserLoaded(browser); + + // Spawn document bc + await SpecialPowers.spawn( + browser, + [URL_DOCUMENT_THIRDPARTY, COOKIE_PARTITIONED, COOKIE_UNPARTITIONED], + async (url, partitioned, unpartitioned) => { + let ifr = content.document.createElement("iframe"); + ifr.src = url; + content.document.body.appendChild(ifr); + await ContentTaskUtils.waitForEvent(ifr, "load"); + + // Spawn iframe bc + await SpecialPowers.spawn( + await ifr.browsingContext, + [partitioned, unpartitioned], + async (partitioned, unpartitioned) => { + ok( + await content.document.hasStorageAccess(), + "example.org should have storageAccess by CookieBehavior 0 / test setup" + ); + + content.document.cookie = partitioned; + content.document.cookie = unpartitioned; + } + ); + } + ); + + // Get cookies from partitioned jar + let partitioned = Services.cookies.getCookiesWithOriginAttributes( + partitionedOAs, + THIRD_PARTY + ); + // Get cookies from unpartitioned jar + let unpartitioned = Services.cookies.getCookiesWithOriginAttributes( + unpartitionedOAs, + THIRD_PARTY + ); + + // Assert partitioned/unpartitioned cookie were stored in correct jars + Assert.equal(partitioned.length, 1); + Assert.equal(partitioned[0].value, "partitioned"); + Assert.equal(unpartitioned.length, 1); + Assert.equal(unpartitioned[0].value, "unpartitioned"); + + // Cleanup + BrowserTestUtils.removeTab(tab); + Services.cookies.removeAll(); + } +); + +// Set partitioned and unpartitioned cookie from first-party http load. +// CHIPS "Partitioned" cookie MUST always be stored in partitioned jar. +// This calls CookieService::SetCookieStringFromHttp() internally. +add_task(async function test_chips_store_partitioned_http_first_party_parent() { + // Set partitioned and unpartitioned cookie from http parent side through + // chips.sjs being loaded. + const tab = BrowserTestUtils.addTab(gBrowser, URL_HTTP_FIRSTPARTY); + const browser = gBrowser.getBrowserForTab(tab); + await BrowserTestUtils.browserLoaded(browser); + + // Get cookies from partitioned jar + let partitioned = Services.cookies.getCookiesWithOriginAttributes( + partitionedOAs, + FIRST_PARTY + ); + // Get cookies from unpartitioned jar + let unpartitioned = Services.cookies.getCookiesWithOriginAttributes( + unpartitionedOAs, + FIRST_PARTY + ); + + // Assert partitioned/unpartitioned cookie were stored in correct jars + Assert.equal(partitioned.length, 1); + Assert.equal(partitioned[0].value, "partitioned"); + Assert.equal(unpartitioned.length, 1); + Assert.equal(unpartitioned[0].value, "unpartitioned"); + + // Cleanup + BrowserTestUtils.removeTab(tab); + Services.cookies.removeAll(); +}); + +// Set partitioned and unpartitioned cookie from third-party http load. +// CHIPS "Partitioned" cookie MUST always be stored in partitioned jar. +// This calls CookieService::SetCookieStringFromHttp() internally. +add_task( + async function test_chips_store_partitioned_http_third_party_storage_access_parent() { + const tab = BrowserTestUtils.addTab(gBrowser, URL_DOCUMENT_FIRSTPARTY); + const browser = gBrowser.getBrowserForTab(tab); + await BrowserTestUtils.browserLoaded(browser); + + // Spawn document bc + await SpecialPowers.spawn(browser, [URL_HTTP_THIRDPARTY], async url => { + let ifr = content.document.createElement("iframe"); + ifr.src = url; + content.document.body.appendChild(ifr); + // Send http request with "set" query parameter, partitioned and + // unpartitioned cookie will be set through http response from chips.sjs. + await ContentTaskUtils.waitForEvent(ifr, "load"); + + // Spawn iframe bc + await SpecialPowers.spawn(await ifr.browsingContext, [], async () => { + ok( + await content.document.hasStorageAccess(), + "example.org should have storageAccess by CookieBehavior 0 / test setup" + ); + }); + }); + + // Get cookies from partitioned jar + let partitioned = Services.cookies.getCookiesWithOriginAttributes( + partitionedOAs, + THIRD_PARTY + ); + // Get cookies from unpartitioned jar + let unpartitioned = Services.cookies.getCookiesWithOriginAttributes( + unpartitionedOAs, + THIRD_PARTY + ); + + // Assert partitioned/unpartitioned cookie were stored in correct jars + Assert.equal(partitioned.length, 1); + Assert.equal(partitioned[0].value, "partitioned"); + Assert.equal(unpartitioned.length, 1); + Assert.equal(unpartitioned[0].value, "unpartitioned"); + + // Cleanup + BrowserTestUtils.removeTab(tab); + Services.cookies.removeAll(); + } +); + +// TODO CHIPS - Tests for CookieServiceChild::SetCookieStringFromHttp() need +// to be added. Since this is only checkable on onProxyConnectSuccess needs +// proxy setup test harness. It is also called after onStartRequest() (Http) +// but cookies are already set by the parents +// CookieService::SetCookieStringFromHttp() call. + +// Get partitioned and unpartitioned cookies from document (child). +// This calls CookieServiceChild::GetCookieStringFromDocument() internally. +add_task( + async function test_chips_send_partitioned_and_unpartitioned_document_child() { + const tab = BrowserTestUtils.addTab(gBrowser, URL_DOCUMENT_FIRSTPARTY); + const browser = gBrowser.getBrowserForTab(tab); + await BrowserTestUtils.browserLoaded(browser); + + // Spawn document bc + await SpecialPowers.spawn( + browser, + [COOKIE_PARTITIONED, COOKIE_UNPARTITIONED], + async (partitioned, unpartitioned) => { + content.document.cookie = partitioned; + content.document.cookie = unpartitioned; + + // Assert both unpartitioned and partitioned cookie are returned. + let cookies = content.document.cookie; + ok( + cookies.includes("cookie=partitioned"), + "Cookie from partitioned jar was sent." + ); + ok( + cookies.includes("cookie=unpartitioned"), + "Cookie from unpartitioned jar was sent." + ); + } + ); + + // Cleanup + BrowserTestUtils.removeTab(tab); + Services.cookies.removeAll(); + } +); + +// Get partitioned and unpartitioned cookies from document (child) after +// storageAccess was granted. This calls CookieServiceChild::TrackCookieLoad() +// internally to update child's cookies. +add_task( + async function test_chips_send_partitioned_and_unpartitioned_on_storage_access_child() { + // Set cookieBehavior to BEHAVIOR_REJECT_TRACKERS_AND_PARTITION_FOREIGN for + // requestStorageAccess() based test. + Services.prefs.setIntPref("network.cookie.cookieBehavior", 5); + + // Set example.org first-party unpartitioned cookie + await BrowserTestUtils.withNewTab( + URL_DOCUMENT_THIRDPARTY, + async browser => { + info("Set a first party cookie via `document.cookie`."); + await SpecialPowers.spawn( + browser, + [COOKIE_UNPARTITIONED], + async unpartitioned => { + content.document.cookie = unpartitioned; + is( + content.document.cookie, + "cookie=unpartitioned", + "Unpartitioned cookie was set." + ); + } + ); + } + ); + + // Assert cookie was set on parent cookie service for example.org. + // Get cookies from unpartitioned jar + let unpartitioned = Services.cookies.getCookiesWithOriginAttributes( + unpartitionedOAs, + THIRD_PARTY + ); + Assert.equal(unpartitioned.length, 1); + Assert.equal(unpartitioned[0].value, "unpartitioned"); + + // Load example.com as first-party in tab + const tab = BrowserTestUtils.addTab(gBrowser, URL_DOCUMENT_FIRSTPARTY); + const browser = gBrowser.getBrowserForTab(tab); + await BrowserTestUtils.browserLoaded(browser); + + // Set third-party cookie from example.org iframe, get storageAccess and + // check cookies. + // Spawn document bc + await SpecialPowers.spawn( + browser, + [URL_DOCUMENT_THIRDPARTY, COOKIE_PARTITIONED], + async (url, partitioned) => { + // Create third-party iframe + let ifr = content.document.createElement("iframe"); + ifr.src = url; + content.document.body.appendChild(ifr); + await ContentTaskUtils.waitForEvent(ifr, "load"); + + // Spawn iframe bc + await SpecialPowers.spawn( + await ifr.browsingContext, + [partitioned], + async partitioned => { + ok( + !(await content.document.hasStorageAccess()), + "example.org should not have storageAccess initially." + ); + + // Set a partitioned third-party cookie and assert its the only. + content.document.cookie = partitioned; + is( + content.document.cookie, + "cookie=partitioned", + "Partitioned cookie was set." + ); + + info("Simulate user activation."); + SpecialPowers.wrap(content.document).notifyUserGestureActivation(); + + info("Request storage access."); + await content.document.requestStorageAccess(); + + ok( + await content.document.hasStorageAccess(), + "example.org should now have storageAccess." + ); + + // Assert both unpartitioned and partitioned cookie are returned. + let cookies = content.document.cookie; + ok( + cookies.includes("cookie=partitioned"), + "Cookie from partitioned jar was sent." + ); + ok( + cookies.includes("cookie=unpartitioned"), + "Cookie from unpartitioned jar was sent." + ); + } + ); + } + ); + + // Cleanup + BrowserTestUtils.removeTab(tab); + Services.cookies.removeAll(); + Services.perms.removeAll(); + Services.prefs.setIntPref("network.cookie.cookieBehavior", 0); + } +); + +// Set partitioned and unpartitioned cookies for URL_DOCUMENT_FIRSTPARTY, then +// load URL again, assure cookies are correctly send to content child process. +// This tests CookieServiceParent::TrackCookieLoad() internally. +add_task( + async function test_chips_send_partitioned_and_unpartitioned_document_parent() { + // Set example.com first-party unpartitioned and partitioned cookie, then + // close tab. + await BrowserTestUtils.withNewTab( + URL_DOCUMENT_FIRSTPARTY, + async browser => { + await SpecialPowers.spawn( + browser, + [COOKIE_PARTITIONED, COOKIE_UNPARTITIONED], + async (partitioned, unpartitioned) => { + content.document.cookie = unpartitioned; + content.document.cookie = partitioned; + let cookies = content.document.cookie; + ok( + cookies.includes("cookie=unpartitioned"), + "Unpartitioned cookie was set." + ); + ok( + cookies.includes("cookie=partitioned"), + "Partitioned cookie was set." + ); + } + ); + } + ); + + // Assert we have one partitioned and one unpartitioned cookie set. + // Get cookies from partitioned jar + let partitioned = Services.cookies.getCookiesWithOriginAttributes( + partitionedOAs, + FIRST_PARTY + ); + // Get cookies from unpartitioned jar + let unpartitioned = Services.cookies.getCookiesWithOriginAttributes( + unpartitionedOAs, + FIRST_PARTY + ); + Assert.equal(partitioned.length, 1); + Assert.equal(partitioned[0].value, "partitioned"); + Assert.equal(unpartitioned.length, 1); + Assert.equal(unpartitioned[0].value, "unpartitioned"); + + // Reload example.com and assert previously set cookies are correctly + // send to content child document. + await BrowserTestUtils.withNewTab( + URL_DOCUMENT_FIRSTPARTY, + async browser => { + await SpecialPowers.spawn(browser, [], () => { + let cookies = content.document.cookie; + ok( + cookies.includes("cookie=unpartitioned"), + "Unpartitioned cookie was sent." + ); + ok( + cookies.includes("cookie=partitioned"), + "Partitioned cookie was sent." + ); + }); + } + ); + + // Cleanup + Services.cookies.removeAll(); + } +); + +// Set partitioned and unpartitioned cookies for URL_DOCUMENT_FIRSTPARTY, then +// send http request, assure cookies are correctly send in "Cookie" header. +// This tests CookieService::GetCookieStringFromHttp() internally. +add_task( + async function test_chips_send_partitioned_and_unpartitioned_http_parent() { + // Load empty document. + let tab = BrowserTestUtils.addTab(gBrowser, URL_DOCUMENT_FIRSTPARTY); + let browser = gBrowser.getBrowserForTab(tab); + await BrowserTestUtils.browserLoaded(browser); + + await SpecialPowers.spawn( + browser, + [HTTP_COOKIE_SET, HTTP_COOKIE_GET], + async (set, get) => { + // Send http request with "set" query parameter, partitioned and + // unpartitioned cookie will be set through http response. + await content.fetch(set); + + // Assert cookies were set to document. + let cookies = content.document.cookie; + ok( + cookies.includes("cookie=unpartitioned"), + "Unpartitioned cookie was set to document." + ); + ok( + cookies.includes("cookie=partitioned"), + "Partitioned cookie was set to document." + ); + + // Send http request with "get" query parameter, chips.sjs will return + // the request "Cookie" header string. + await content + .fetch(get) + .then(response => response.text()) + .then(requestCookies => { + // Assert cookies were sent in http request. + ok( + requestCookies.includes("cookie=unpartitioned"), + "Unpartitioned cookie was sent in http request." + ); + ok( + requestCookies.includes("cookie=partitioned"), + "Partitioned cookie was sent in http request." + ); + }); + } + ); + + // Cleanup + BrowserTestUtils.removeTab(tab); + Services.cookies.removeAll(); + } +); diff --git a/netwerk/cookie/test/browser/browser_cookies_serviceWorker.js b/netwerk/cookie/test/browser/browser_cookies_serviceWorker.js new file mode 100644 index 0000000000..813e1fe7cb --- /dev/null +++ b/netwerk/cookie/test/browser/browser_cookies_serviceWorker.js @@ -0,0 +1,540 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const TEST_CROSS_SITE_DOMAIN = "https://example.net/"; +const TEST_CROSS_SITE_PAGE = + TEST_CROSS_SITE_DOMAIN + TEST_PATH + "file_empty.html"; + +add_setup(async function () { + await SpecialPowers.pushPrefEnv({ + set: [ + ["dom.serviceWorkers.enabled", true], + ["dom.serviceWorkers.testing.enabled", true], + ], + }); + + Services.cookies.removeAll(); +}); + +function registerSW(browser) { + return SpecialPowers.spawn(browser, [], async _ => { + let reg = await content.navigator.serviceWorker.register( + "serviceWorker.js" + ); + + await ContentTaskUtils.waitForCondition(() => { + return reg.active && reg.active.state === "activated"; + }, "The service worker is activated"); + + ok( + content.navigator.serviceWorker.controller, + "The service worker controls the document successfully." + ); + }); +} + +function fetchCookiesFromSW(browser) { + return SpecialPowers.spawn(browser, [], async _ => { + return new content.Promise(resolve => { + content.navigator.serviceWorker.addEventListener("message", event => { + resolve(event.data.content); + }); + + content.navigator.serviceWorker.controller.postMessage({ + action: "fetch", + url: `cookies.sjs`, + }); + }); + }); +} + +function setCookiesFromSW(browser, cookies) { + let setCookieQuery = ""; + + for (let cookie of cookies) { + setCookieQuery += `Set-Cookie=${cookie}&`; + } + + return SpecialPowers.spawn(browser, [setCookieQuery], async query => { + return new content.Promise(resolve => { + content.navigator.serviceWorker.addEventListener("message", event => { + resolve(event.data.content); + }); + + content.navigator.serviceWorker.controller.postMessage({ + action: "fetch", + url: `cookies.sjs?${query}`, + }); + }); + }); +} + +function unregisterSW(browser) { + return SpecialPowers.spawn(browser, [], async _ => { + const regs = await content.navigator.serviceWorker.getRegistrations(); + for (const reg of regs) { + await reg.unregister(); + } + }); +} + +/** + * Verify a first-party service worker can access both SameSite=None and + * SameSite=Lax cookies set in the first-party context. + */ +add_task(async function testCookiesWithFirstPartyServiceWorker() { + info("Open a tab"); + let tab = await BrowserTestUtils.openNewForegroundTab( + gBrowser, + TEST_TOP_PAGE + ); + + info("Writing cookies to the first-party context."); + await SpecialPowers.spawn(tab.linkedBrowser, [], async _ => { + const cookies = [ + "foo=bar; SameSite=None; Secure", + "fooLax=barLax; SameSite=Lax; Secure", + ]; + + let query = ""; + + for (let cookie of cookies) { + query += `Set-Cookie=${cookie}&`; + } + + await content.fetch(`cookies.sjs?${query}`); + }); + + info("Register a service worker and trigger a fetch request to get cookies."); + await registerSW(tab.linkedBrowser); + let cookieStr = await fetchCookiesFromSW(tab.linkedBrowser); + + is(cookieStr, "foo=bar; fooLax=barLax", "The cookies are expected"); + + info("Set cookies from the service worker."); + await setCookiesFromSW(tab.linkedBrowser, [ + "foo=barSW; SameSite=None; Secure", + "fooLax=barLaxSW; SameSite=Lax; Secure", + ]); + + info("Get cookies from the service worker."); + cookieStr = await fetchCookiesFromSW(tab.linkedBrowser); + + is(cookieStr, "foo=barSW; fooLax=barLaxSW", "The cookies are expected"); + + await unregisterSW(tab.linkedBrowser); + BrowserTestUtils.removeTab(tab); + Services.cookies.removeAll(); +}); + +/** + * Verify a cross-site service worker can only access cookies set in the + * same cross-site context. + */ +add_task(async function testCookiesWithCrossSiteServiceWorker() { + // Disable blocking third-party cookies. + await SpecialPowers.pushPrefEnv({ + set: [["network.cookie.cookieBehavior.optInPartitioning", false]], + }); + + info("Open a cross-site tab"); + let crossSiteTab = await BrowserTestUtils.openNewForegroundTab( + gBrowser, + TEST_CROSS_SITE_PAGE + ); + + info("Writing cookies to the cross site in the first-party context."); + await SpecialPowers.spawn(crossSiteTab.linkedBrowser, [], async _ => { + const cookies = [ + "foo=bar; SameSite=None; Secure", + "fooLax=barLax; SameSite=Lax; Secure", + ]; + + let query = ""; + + for (let cookie of cookies) { + query += `Set-Cookie=${cookie}&`; + } + + await content.fetch(`cookies.sjs?${query}`); + }); + + info("Open a tab"); + let tab = await BrowserTestUtils.openNewForegroundTab( + gBrowser, + TEST_TOP_PAGE + ); + + info("Load a cross-site iframe"); + let crossSiteBc = await SpecialPowers.spawn( + tab.linkedBrowser, + [TEST_CROSS_SITE_PAGE], + async url => { + let ifr = content.document.createElement("iframe"); + + await new content.Promise(resolve => { + ifr.onload = resolve; + content.document.body.appendChild(ifr); + ifr.src = url; + }); + + return ifr.browsingContext; + } + ); + + info("Write cookies in the cross-site iframe"); + await SpecialPowers.spawn(crossSiteBc, [], async _ => { + const cookies = [ + "foo=crossBar; SameSite=None; Secure", + "fooLax=crossBarLax; SameSite=Lax; Secure", + ]; + + let query = ""; + + for (let cookie of cookies) { + query += `Set-Cookie=${cookie}&`; + } + + await content.fetch(`cookies.sjs?${query}`); + }); + + info( + "Register a service worker and trigger a fetch request to get cookies in cross-site context." + ); + await registerSW(crossSiteBc); + let cookieStr = await fetchCookiesFromSW(crossSiteBc); + + is( + cookieStr, + "foo=crossBar", + "Only the SameSite=None cookie set in the third-party iframe is available." + ); + + info("Set cookies from the third-party service worker."); + await setCookiesFromSW(crossSiteBc, [ + "foo=crossBarSW; SameSite=None; Secure", + "fooLax=crossBarLaxSW; SameSite=Lax; Secure", + ]); + + info("Get cookies from the third-party service worker."); + cookieStr = await fetchCookiesFromSW(crossSiteBc); + + is( + cookieStr, + "foo=crossBarSW", + "Only the SameSite=None cookie set in the third-party service worker is available." + ); + + await unregisterSW(crossSiteBc); + BrowserTestUtils.removeTab(crossSiteTab); + BrowserTestUtils.removeTab(tab); + Services.cookies.removeAll(); +}); + +/** + * Verify a cross-site service worker can only access partitioned cookies set in + * the same cross-site context if third-party cookies are blocked. + */ +add_task(async function testPartitionedCookiesWithCrossSiteServiceWorker() { + // Enable blocking third-party cookies. + await SpecialPowers.pushPrefEnv({ + set: [["network.cookie.cookieBehavior.optInPartitioning", true]], + }); + + info("Open a tab"); + let tab = await BrowserTestUtils.openNewForegroundTab( + gBrowser, + TEST_TOP_PAGE + ); + + info("Load a cross-site iframe"); + let crossSiteBc = await SpecialPowers.spawn( + tab.linkedBrowser, + [TEST_CROSS_SITE_PAGE], + async url => { + let ifr = content.document.createElement("iframe"); + + await new content.Promise(resolve => { + ifr.onload = resolve; + content.document.body.appendChild(ifr); + ifr.src = url; + }); + + return ifr.browsingContext; + } + ); + + info("Write cookies in the cross-site iframe"); + await SpecialPowers.spawn(crossSiteBc, [], async _ => { + const cookies = [ + "foo=crossBar; SameSite=None; Secure", + "fooLax=crossBarLax; SameSite=Lax; Secure", + "fooPartitioned=crossBar; SameSite=None; Secure; Partitioned;", + "fooLaxPartitioned=crossBarLax; SameSite=Lax; Secure; Partitioned;", + ]; + + let query = ""; + + for (let cookie of cookies) { + query += `Set-Cookie=${cookie}&`; + } + + await content.fetch(`cookies.sjs?${query}`); + }); + + info( + "Register a service worker and trigger a fetch request to get cookies in cross-site context." + ); + await registerSW(crossSiteBc); + let cookieStr = await fetchCookiesFromSW(crossSiteBc); + + is( + cookieStr, + "fooPartitioned=crossBar", + "Only the SameSite=None partitioned cookie set in the third-party iframe is available." + ); + + info("Set cookies from the third-party service worker."); + await setCookiesFromSW(crossSiteBc, [ + "foo=crossBarSW; SameSite=None; Secure", + "fooLax=crossBarLaxSW; SameSite=Lax; Secure", + "fooPartitioned=crossBarSW; SameSite=None; Secure; Partitioned;", + "fooLaxPartitioned=crossBarLaxSW; SameSite=Lax; Secure; Partitioned;", + ]); + + info("Get cookies from the third-party service worker."); + cookieStr = await fetchCookiesFromSW(crossSiteBc); + + is( + cookieStr, + "fooPartitioned=crossBarSW", + "Only the SameSite=None partitioned cookie set in the third-party service worker is available." + ); + + await unregisterSW(crossSiteBc); + BrowserTestUtils.removeTab(tab); + Services.cookies.removeAll(); +}); + +/** + * Verify a ABA service worker can only access cookies set in the ABA context. + */ +add_task(async function testCookiesWithABAServiceWorker() { + // Disable blocking third-party cookies. + await SpecialPowers.pushPrefEnv({ + set: [["network.cookie.cookieBehavior.optInPartitioning", false]], + }); + + info("Open a tab"); + let tab = await BrowserTestUtils.openNewForegroundTab( + gBrowser, + TEST_TOP_PAGE + ); + + info("Writing cookies to the first-party context."); + await SpecialPowers.spawn(tab.linkedBrowser, [], async _ => { + const cookies = [ + "foo=bar; SameSite=None; Secure", + "fooLax=barLax; SameSite=Lax; Secure", + ]; + + let query = ""; + + for (let cookie of cookies) { + query += `Set-Cookie=${cookie}&`; + } + + await content.fetch(`cookies.sjs?${query}`); + }); + + info("Load a ABA iframe"); + let crossSiteBc = await SpecialPowers.spawn( + tab.linkedBrowser, + [TEST_CROSS_SITE_PAGE], + async url => { + let ifr = content.document.createElement("iframe"); + + await new content.Promise(resolve => { + ifr.onload = resolve; + content.document.body.appendChild(ifr); + ifr.src = url; + }); + + return ifr.browsingContext; + } + ); + + let ABABc = await SpecialPowers.spawn( + crossSiteBc, + [TEST_TOP_PAGE], + async url => { + let ifr = content.document.createElement("iframe"); + + await new content.Promise(resolve => { + ifr.onload = resolve; + content.document.body.appendChild(ifr); + ifr.src = url; + }); + + return ifr.browsingContext; + } + ); + + info( + "Register a service worker and trigger a fetch request to get cookies in the ABA context." + ); + + await registerSW(ABABc); + let cookieStr = await fetchCookiesFromSW(ABABc); + is(cookieStr, "", "No cookie should be available in ABA context."); + + info("Set cookies in the ABA iframe"); + await SpecialPowers.spawn(ABABc, [], async _ => { + const cookies = [ + "fooABA=barABA; SameSite=None; Secure", + "fooABALax=BarABALax; SameSite=Lax; Secure", + ]; + + let query = ""; + + for (let cookie of cookies) { + query += `Set-Cookie=${cookie}&`; + } + + await content.fetch(`cookies.sjs?${query}`); + }); + + info("Get cookies in the ABA service worker."); + cookieStr = await fetchCookiesFromSW(ABABc); + + is( + cookieStr, + "fooABA=barABA", + "Only the SameSite=None cookie set in ABA iframe is available." + ); + + info("Set cookies from the service worker in ABA context"); + await setCookiesFromSW(ABABc, [ + "fooABA=barABASW; SameSite=None; Secure", + "fooABALax=BarABALaxSW; SameSite=Lax; Secure", + ]); + + info("Get cookies from the service worker in the ABA context."); + cookieStr = await fetchCookiesFromSW(ABABc); + + is( + cookieStr, + "fooABA=barABASW", + "Only the SameSite=None cookie set in ABA service worker is available." + ); + + await unregisterSW(ABABc); + BrowserTestUtils.removeTab(tab); + Services.cookies.removeAll(); +}); + +/** + * Verify a ABA service worker can only access partitioned cookies set in the + * ABA context if third-party cookies are blocked. + */ +add_task(async function testCookiesWithABAServiceWorker() { + // Disable blocking third-party cookies. + await SpecialPowers.pushPrefEnv({ + set: [["network.cookie.cookieBehavior.optInPartitioning", true]], + }); + + info("Open a tab"); + let tab = await BrowserTestUtils.openNewForegroundTab( + gBrowser, + TEST_TOP_PAGE + ); + + info("Load a ABA iframe"); + let crossSiteBc = await SpecialPowers.spawn( + tab.linkedBrowser, + [TEST_CROSS_SITE_PAGE], + async url => { + let ifr = content.document.createElement("iframe"); + + await new content.Promise(resolve => { + ifr.onload = resolve; + content.document.body.appendChild(ifr); + ifr.src = url; + }); + + return ifr.browsingContext; + } + ); + + let ABABc = await SpecialPowers.spawn( + crossSiteBc, + [TEST_TOP_PAGE], + async url => { + let ifr = content.document.createElement("iframe"); + + await new content.Promise(resolve => { + ifr.onload = resolve; + content.document.body.appendChild(ifr); + ifr.src = url; + }); + + return ifr.browsingContext; + } + ); + + info("Set cookies in the ABA iframe"); + await SpecialPowers.spawn(ABABc, [], async _ => { + const cookies = [ + "fooABA=barABA; SameSite=None; Secure", + "fooABALax=BarABALax; SameSite=Lax; Secure", + "fooABAPartitioned=barABA; SameSite=None; Secure; Partitioned;", + "fooABALaxPartitioned=BarABALax; SameSite=Lax; Secure; Partitioned;", + ]; + + let query = ""; + + for (let cookie of cookies) { + query += `Set-Cookie=${cookie}&`; + } + + await content.fetch(`cookies.sjs?${query}`); + }); + + info( + "Register a service worker and trigger a fetch request to get cookies in the ABA context." + ); + + await registerSW(ABABc); + + info("Get cookies in the ABA service worker."); + let cookieStr = await fetchCookiesFromSW(ABABc); + + is( + cookieStr, + "fooABAPartitioned=barABA", + "Only the SameSite=None partitioned cookie set in ABA iframe is available." + ); + + info("Set cookies from the service worker in ABA context"); + await setCookiesFromSW(ABABc, [ + "fooABA=barABASW; SameSite=None; Secure", + "fooABALax=BarABALaxSW; SameSite=Lax; Secure", + "fooABAPartitioned=barABASW; SameSite=None; Secure; Partitioned;", + "fooABALaxPartitioned=BarABALaxSW; SameSite=Lax; Secure; Partitioned;", + ]); + + info("Get cookies from the service worker in the ABA context."); + cookieStr = await fetchCookiesFromSW(ABABc); + + is( + cookieStr, + "fooABAPartitioned=barABASW", + "Only the SameSite=None partitioned cookie set in ABA service worker is available." + ); + + await unregisterSW(ABABc); + BrowserTestUtils.removeTab(tab); + Services.cookies.removeAll(); +}); diff --git a/netwerk/cookie/test/browser/chips.sjs b/netwerk/cookie/test/browser/chips.sjs new file mode 100644 index 0000000000..e60109f6fa --- /dev/null +++ b/netwerk/cookie/test/browser/chips.sjs @@ -0,0 +1,28 @@ +function handleRequest(aRequest, aResponse) { + aResponse.setStatusLine(aRequest.httpVersion, 200); + + var params = new URLSearchParams(aRequest.queryString); + + // Get Cookie header string. + if (params.has("get")) { + if (aRequest.hasHeader("Cookie")) { + let cookie = aRequest.getHeader("Cookie"); + aResponse.write(cookie); + } + return; + } + + // Set a partitioned and a unpartitioned cookie. + if (params.has("set")) { + aResponse.setHeader( + "Set-Cookie", + "cookie=partitioned; Partitioned; SameSite=None; Secure", + true + ); + aResponse.setHeader( + "Set-Cookie", + "cookie=unpartitioned; SameSite=None; Secure", + true + ); + } +} diff --git a/netwerk/cookie/test/browser/cookies.sjs b/netwerk/cookie/test/browser/cookies.sjs new file mode 100644 index 0000000000..9beb861d44 --- /dev/null +++ b/netwerk/cookie/test/browser/cookies.sjs @@ -0,0 +1,17 @@ +function handleRequest(aRequest, aResponse) { + aResponse.setStatusLine(aRequest.httpVersion, 200); + let query = new URLSearchParams(aRequest.queryString); + + if (query.has("Set-Cookie")) { + for (let value of query.getAll("Set-Cookie")) { + aResponse.setHeader("Set-Cookie", value, true); + } + return; + } + + let cookieStr = ""; + if (aRequest.hasHeader("Cookie")) { + cookieStr = aRequest.getHeader("Cookie"); + } + aResponse.write(cookieStr); +} diff --git a/netwerk/cookie/test/browser/serviceWorker.js b/netwerk/cookie/test/browser/serviceWorker.js new file mode 100644 index 0000000000..b2eab40ce4 --- /dev/null +++ b/netwerk/cookie/test/browser/serviceWorker.js @@ -0,0 +1,21 @@ +self.addEventListener("install", function () { + self.skipWaiting(); +}); + +self.addEventListener("activate", function (event) { + event.waitUntil(self.clients.claim()); +}); + +self.addEventListener("message", function (event) { + if (event.data.action === "fetch") { + fetch(event.data.url) + .then(response => response.text()) + .then(data => { + self.clients.matchAll().then(clients => { + clients.forEach(client => { + client.postMessage({ content: data }); + }); + }); + }); + } +}); |