mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-23 04:41:11 +00:00
Bug 1873418 - Support Partitioned cookie attribute for browser extensions - r=robwu,anti-tracking-reviewers,webdriver-reviewers,timhuang,Sasha
The part that is covered here is extending the partitionKey attribute, as in the proposal of aselya here: https://github.com/w3c/webextensions/pull/581/files Notably not included is adding the new Cookies.GetPartitionKey function. Differential Revision: https://phabricator.services.mozilla.com/D219990
This commit is contained in:
parent
ec4e483f35
commit
5ffc34408c
@ -7,6 +7,7 @@
|
||||
#include "ChromeUtils.h"
|
||||
|
||||
#include "JSOracleParent.h"
|
||||
#include "ThirdPartyUtil.h"
|
||||
#include "js/CallAndConstruct.h" // JS::Call
|
||||
#include "js/ColumnNumber.h" // JS::TaggedColumnNumberOneOrigin, JS::ColumnNumberOneOrigin
|
||||
#include "js/CharacterEncoding.h"
|
||||
@ -1310,27 +1311,68 @@ void ChromeUtils::GetBaseDomainFromPartitionKey(dom::GlobalObject& aGlobal,
|
||||
|
||||
/* static */
|
||||
void ChromeUtils::GetPartitionKeyFromURL(dom::GlobalObject& aGlobal,
|
||||
const nsAString& aURL,
|
||||
const nsAString& aTopLevelUrl,
|
||||
const nsAString& aSubresourceUrl,
|
||||
const Optional<bool>& aForeignContext,
|
||||
nsAString& aPartitionKey,
|
||||
ErrorResult& aRv) {
|
||||
nsCOMPtr<nsIURI> uri;
|
||||
nsresult rv = NS_NewURI(getter_AddRefs(uri), aURL);
|
||||
if (NS_SUCCEEDED(rv) && uri->SchemeIs("chrome")) {
|
||||
nsCOMPtr<nsIURI> topLevelURI;
|
||||
nsresult rv = NS_NewURI(getter_AddRefs(topLevelURI), aTopLevelUrl);
|
||||
if (NS_SUCCEEDED(rv) && topLevelURI->SchemeIs("chrome")) {
|
||||
rv = NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
aPartitionKey.Truncate();
|
||||
aRv.Throw(rv);
|
||||
return;
|
||||
}
|
||||
|
||||
mozilla::OriginAttributes attrs;
|
||||
// For now, uses assume the partition key is cross-site.
|
||||
// We will need to not make this assumption to allow access
|
||||
// to same-site partitioned cookies in the cookie extension API.
|
||||
attrs.SetPartitionKey(uri, false);
|
||||
bool foreignResource;
|
||||
bool fallback = false;
|
||||
if (!aSubresourceUrl.IsEmpty()) {
|
||||
nsCOMPtr<nsIURI> resourceURI;
|
||||
rv = NS_NewURI(getter_AddRefs(resourceURI), aSubresourceUrl);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
aPartitionKey.Truncate();
|
||||
aRv.Throw(rv);
|
||||
return;
|
||||
}
|
||||
|
||||
ThirdPartyUtil* thirdPartyUtil = ThirdPartyUtil::GetInstance();
|
||||
if (!thirdPartyUtil) {
|
||||
aPartitionKey.Truncate();
|
||||
aRv.Throw(NS_ERROR_SERVICE_NOT_AVAILABLE);
|
||||
return;
|
||||
}
|
||||
|
||||
rv = thirdPartyUtil->IsThirdPartyURI(topLevelURI, resourceURI,
|
||||
&foreignResource);
|
||||
if (NS_FAILED(rv)) {
|
||||
// we fallback to assuming the resource is foreign if there is an error
|
||||
foreignResource = true;
|
||||
fallback = true;
|
||||
}
|
||||
} else {
|
||||
// Assume we have a foreign resource if the resource was not provided
|
||||
foreignResource = true;
|
||||
fallback = true;
|
||||
}
|
||||
|
||||
// aForeignContext is whether or not this is a foreign context.
|
||||
// foreignResource is whether or not the resource is cross-site to the top
|
||||
// level. So we need to validate that a false foreign context doesn't have a
|
||||
// same-site resource. That is impossible!
|
||||
if (aForeignContext.WasPassed() && !aForeignContext.Value() &&
|
||||
foreignResource && !fallback) {
|
||||
aPartitionKey.Truncate();
|
||||
aRv.Throw(nsresult::NS_ERROR_INVALID_ARG);
|
||||
return;
|
||||
}
|
||||
|
||||
bool foreignByAncestorContext = aForeignContext.WasPassed() &&
|
||||
aForeignContext.Value() && !foreignResource;
|
||||
mozilla::OriginAttributes attrs;
|
||||
attrs.SetPartitionKey(topLevelURI, foreignByAncestorContext);
|
||||
aPartitionKey = attrs.mPartitionKey;
|
||||
}
|
||||
|
||||
|
@ -139,7 +139,9 @@ class ChromeUtils {
|
||||
ErrorResult& aRv);
|
||||
|
||||
static void GetPartitionKeyFromURL(dom::GlobalObject& aGlobal,
|
||||
const nsAString& aURL,
|
||||
const nsAString& aTopLevelUrl,
|
||||
const nsAString& aSubresourceUrl,
|
||||
const Optional<bool>& aForeignContext,
|
||||
nsAString& aPartitionKey,
|
||||
ErrorResult& aRv);
|
||||
|
||||
|
@ -454,16 +454,20 @@ partial namespace ChromeUtils {
|
||||
getBaseDomainFromPartitionKey(DOMString partitionKey);
|
||||
|
||||
/**
|
||||
* Returns the partitionKey for a given URL.
|
||||
* Returns the partitionKey for a given subresourceURL given its top-level URL
|
||||
* and whether or not it is in a foreign context.
|
||||
*
|
||||
* The function will treat the URL as a first party and construct the
|
||||
* partitionKey according to the scheme, site and port in the URL.
|
||||
* The function will treat the topLevelURL as a first party and construct the
|
||||
* partitionKey according to the scheme, site and port in the URL. It will also
|
||||
* include information about the subresource and whether or not this is a foreign
|
||||
* request in the partition key.
|
||||
*
|
||||
* Throws for invalid urls.
|
||||
* Throws for invalid urls, if the Third Party Service is unavailable, or if the
|
||||
* combination of inputs is impossible.
|
||||
*/
|
||||
[Throws]
|
||||
DOMString
|
||||
getPartitionKeyFromURL(DOMString url);
|
||||
getPartitionKeyFromURL(DOMString topLevelUrl, DOMString subresourceUrl, optional boolean foreignContext);
|
||||
|
||||
/**
|
||||
* Loads and compiles the script at the given URL and returns an object
|
||||
|
@ -758,12 +758,16 @@ class StorageModule extends RootBiDiModule {
|
||||
originAttributes.partitionKey = "";
|
||||
} else {
|
||||
originAttributes.partitionKey = ChromeUtils.getPartitionKeyFromURL(
|
||||
partitionKey.sourceOrigin
|
||||
partitionKey.sourceOrigin,
|
||||
"",
|
||||
false
|
||||
);
|
||||
}
|
||||
} else {
|
||||
originAttributes.partitionKey = ChromeUtils.getPartitionKeyFromURL(
|
||||
partitionKey.sourceOrigin
|
||||
partitionKey.sourceOrigin,
|
||||
"",
|
||||
false
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -114,6 +114,8 @@ const TEST_CASES = [
|
||||
},
|
||||
];
|
||||
|
||||
const SUBRESOURCE_URL = "https://example.net/";
|
||||
|
||||
const TEST_INVALID_URLS = [
|
||||
"",
|
||||
"/foo",
|
||||
@ -126,7 +128,10 @@ const TEST_INVALID_URLS = [
|
||||
add_task(async function test_get_partition_key_from_url() {
|
||||
for (const test of TEST_CASES) {
|
||||
info(`Testing url: ${test.url}`);
|
||||
let partitionKey = ChromeUtils.getPartitionKeyFromURL(test.url);
|
||||
let partitionKey = ChromeUtils.getPartitionKeyFromURL(
|
||||
test.url,
|
||||
SUBRESOURCE_URL
|
||||
);
|
||||
|
||||
Assert.equal(
|
||||
partitionKey,
|
||||
@ -141,7 +146,10 @@ add_task(async function test_get_partition_key_from_url_without_site() {
|
||||
|
||||
for (const test of TEST_CASES) {
|
||||
info(`Testing url: ${test.url}`);
|
||||
let partitionKey = ChromeUtils.getPartitionKeyFromURL(test.url);
|
||||
let partitionKey = ChromeUtils.getPartitionKeyFromURL(
|
||||
test.url,
|
||||
SUBRESOURCE_URL
|
||||
);
|
||||
|
||||
Assert.equal(
|
||||
partitionKey,
|
||||
@ -194,7 +202,10 @@ add_task(async function test_blob_url() {
|
||||
return blob_url;
|
||||
});
|
||||
|
||||
let partitionKey = ChromeUtils.getPartitionKeyFromURL(blobUrl);
|
||||
let partitionKey = ChromeUtils.getPartitionKeyFromURL(
|
||||
blobUrl,
|
||||
SUBRESOURCE_URL
|
||||
);
|
||||
|
||||
// The partitionKey of the blob url is empty because the principal of the
|
||||
// blob url is the JS principal of the global, which doesn't have
|
||||
@ -214,7 +225,7 @@ add_task(async function test_throw_with_invalid_URL() {
|
||||
|
||||
Assert.throws(
|
||||
() => {
|
||||
ChromeUtils.getPartitionKeyFromURL(invalidURL);
|
||||
ChromeUtils.getPartitionKeyFromURL(invalidURL, SUBRESOURCE_URL);
|
||||
},
|
||||
/NS_ERROR_MALFORMED_URI/,
|
||||
"It should fail on invalid URLs."
|
||||
|
@ -30,30 +30,60 @@ const dropBracketIfIPv6 = host =>
|
||||
? host.slice(1, -1)
|
||||
: host;
|
||||
|
||||
const isSubdomain = (otherDomain, baseDomain) => {
|
||||
return otherDomain == baseDomain || otherDomain.endsWith("." + baseDomain);
|
||||
};
|
||||
|
||||
// Converts the partitionKey format of the extension API (i.e. PartitionKey) to
|
||||
// a valid format for the "partitionKey" member of OriginAttributes.
|
||||
function fromExtPartitionKey(extPartitionKey) {
|
||||
function fromExtPartitionKey(extPartitionKey, cookieUrl) {
|
||||
if (!extPartitionKey) {
|
||||
// Unpartitioned by default.
|
||||
return "";
|
||||
}
|
||||
const { topLevelSite } = extPartitionKey;
|
||||
const { topLevelSite, hasCrossSiteAncestor } = extPartitionKey;
|
||||
// TODO: Expand API to force the generation of a partitionKey that differs
|
||||
// from the default that's specified by privacy.dynamic_firstparty.use_site.
|
||||
if (topLevelSite) {
|
||||
// If topLevelSite is set and a non-empty string (a site in a URL format).
|
||||
try {
|
||||
return ChromeUtils.getPartitionKeyFromURL(topLevelSite);
|
||||
// This is subtle! We define the ancestor bit in our code in a different
|
||||
// way than the extension API, but they are isomorphic.
|
||||
// If we have cookieUrl (which is guaranteed to be the case in get, set,
|
||||
// and remove) this will return the topLevelSite parsed partition key,
|
||||
// and include the foreign ancestor bit iff the details.url is
|
||||
// same-site and a truthy value was passed in the hasCrossSiteAncestor
|
||||
// property. If we don't have cookieUrl, we handle the difference in
|
||||
// ancestor bit definition by returning a OA pattern that matches both
|
||||
// values and filtering them later on in matches.
|
||||
if (cookieUrl == null) {
|
||||
let topLevelSiteURI = Services.io.newURI(topLevelSite);
|
||||
let topLevelSiteFilter = Services.eTLD.getSite(topLevelSiteURI);
|
||||
if (topLevelSiteURI.port != -1) {
|
||||
topLevelSiteFilter += `:${topLevelSiteURI.port}`;
|
||||
}
|
||||
return topLevelSiteFilter;
|
||||
}
|
||||
return ChromeUtils.getPartitionKeyFromURL(
|
||||
topLevelSite,
|
||||
cookieUrl,
|
||||
hasCrossSiteAncestor ?? undefined
|
||||
);
|
||||
} catch (e) {
|
||||
throw new ExtensionError("Invalid value for 'partitionKey' attribute");
|
||||
}
|
||||
} else if (topLevelSite == null && hasCrossSiteAncestor != null) {
|
||||
// This is an invalid combination of parameters.
|
||||
throw new ExtensionError("Invalid value for 'partitionKey' attribute");
|
||||
}
|
||||
// Unpartitioned.
|
||||
return "";
|
||||
}
|
||||
|
||||
// Converts an internal partitionKey (format used by OriginAttributes) to the
|
||||
// string value as exposed through the extension API.
|
||||
function toExtPartitionKey(partitionKey) {
|
||||
function getExtPartitionKey(cookie) {
|
||||
let partitionKey = cookie.originAttributes.partitionKey;
|
||||
if (!partitionKey) {
|
||||
// Canonical representation of an empty partitionKey is null.
|
||||
// In theory {topLevelSite: ""} also works, but alas.
|
||||
@ -65,15 +95,34 @@ function toExtPartitionKey(partitionKey) {
|
||||
// pref, which is not necessarily the case for cookies before the pref flip.
|
||||
if (!partitionKey.startsWith("(")) {
|
||||
// A partitionKey generated with privacy.dynamic_firstparty.use_site=false.
|
||||
return { topLevelSite: `https://${partitionKey}` };
|
||||
let hasCrossSiteAncestor = !isSubdomain(cookie.host, partitionKey);
|
||||
return { topLevelSite: `https://${partitionKey}`, hasCrossSiteAncestor };
|
||||
}
|
||||
// partitionKey starts with "(" and ends with ")".
|
||||
let [scheme, domain, port] = partitionKey.slice(1, -1).split(",");
|
||||
let [scheme, domain, opt1, opt2] = partitionKey.slice(1, -1).split(",");
|
||||
// foreignByAncestorContext logic from OriginAttributes::ParsePartitionKey.
|
||||
let fbac = false;
|
||||
let port;
|
||||
if (opt2) {
|
||||
// opt2 is "f" or undefined.
|
||||
port = opt1;
|
||||
fbac = true;
|
||||
} else if (opt1 == "f") {
|
||||
fbac = true;
|
||||
} else if (opt1) {
|
||||
port = opt1;
|
||||
}
|
||||
|
||||
// Construct the topLevelSite part of the partitionKey
|
||||
let topLevelSite = `${scheme}://${domain}`;
|
||||
if (port) {
|
||||
topLevelSite += `:${port}`;
|
||||
}
|
||||
return { topLevelSite };
|
||||
|
||||
// Construct the hasCrossSiteAncestor bit as well.
|
||||
// This is isomorphic, but not identical to how we partition.
|
||||
let hasCrossSiteAncestor = fbac || !isSubdomain(cookie.host, domain);
|
||||
return { topLevelSite, hasCrossSiteAncestor };
|
||||
}
|
||||
|
||||
const convertCookie = ({ cookie, isPrivate }) => {
|
||||
@ -88,7 +137,7 @@ const convertCookie = ({ cookie, isPrivate }) => {
|
||||
sameSite: SAME_SITE_STATUSES[cookie.sameSite],
|
||||
session: cookie.isSession,
|
||||
firstPartyDomain: cookie.originAttributes.firstPartyDomain || "",
|
||||
partitionKey: toExtPartitionKey(cookie.originAttributes.partitionKey),
|
||||
partitionKey: getExtPartitionKey(cookie),
|
||||
};
|
||||
|
||||
if (!cookie.isSession) {
|
||||
@ -108,10 +157,6 @@ const convertCookie = ({ cookie, isPrivate }) => {
|
||||
return result;
|
||||
};
|
||||
|
||||
const isSubdomain = (otherDomain, baseDomain) => {
|
||||
return otherDomain == baseDomain || otherDomain.endsWith("." + baseDomain);
|
||||
};
|
||||
|
||||
// Checks that the given extension has permission to set the given cookie for
|
||||
// the given URI.
|
||||
const checkSetCookiePermissions = (extension, uri, cookie) => {
|
||||
@ -240,7 +285,7 @@ const oaFromDetails = (details, context, allowPattern) => {
|
||||
privateBrowsingId: 0,
|
||||
// The following two keys may be deleted if allowPattern=true
|
||||
firstPartyDomain: details.firstPartyDomain ?? "",
|
||||
partitionKey: fromExtPartitionKey(details.partitionKey),
|
||||
partitionKey: fromExtPartitionKey(details.partitionKey, details.url),
|
||||
};
|
||||
|
||||
let isPrivate = context.incognito;
|
||||
@ -270,8 +315,9 @@ const oaFromDetails = (details, context, allowPattern) => {
|
||||
}
|
||||
}
|
||||
|
||||
// If any of the originAttributes's keys are deleted, this becomes true.
|
||||
// If any of the originAttributes's keys are deleted, isPattern becomes true.
|
||||
let isPattern = false;
|
||||
let topLevelSiteFilter;
|
||||
if (allowPattern) {
|
||||
// firstPartyDomain is unset / void / string.
|
||||
// If unset, then we default to non-FPI cookies (or if FPI is enabled,
|
||||
@ -292,9 +338,23 @@ const oaFromDetails = (details, context, allowPattern) => {
|
||||
if (details.partitionKey && details.partitionKey.topLevelSite == null) {
|
||||
delete originAttributes.partitionKey;
|
||||
isPattern = true;
|
||||
} else if (details.partitionKey?.topLevelSite && details.url == null) {
|
||||
// See "This is subtle!" comment in fromExtPartitionKey.
|
||||
// Matching foreignAncestorBit (hasCrossSiteAncestor) exactly
|
||||
// requires a url. If url is absent, we need to filter afterwards.
|
||||
topLevelSiteFilter = originAttributes.partitionKey;
|
||||
delete originAttributes.partitionKey;
|
||||
isPattern = true;
|
||||
}
|
||||
}
|
||||
return { originAttributes, isPattern, isPrivate, storeId };
|
||||
|
||||
return {
|
||||
originAttributes,
|
||||
isPattern,
|
||||
isPrivate,
|
||||
storeId,
|
||||
topLevelSiteFilter,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
@ -328,7 +388,8 @@ const query = function* (detailsIn, props, context, allowPattern) {
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
let { originAttributes, isPattern, isPrivate, storeId } = parsedOA;
|
||||
let { originAttributes, isPattern, isPrivate, storeId, topLevelSiteFilter } =
|
||||
parsedOA;
|
||||
|
||||
if ("domain" in details) {
|
||||
details.domain = details.domain.toLowerCase().replace(/^\./, "");
|
||||
@ -430,6 +491,29 @@ const query = function* (detailsIn, props, context, allowPattern) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// We query for more cookies than match the partitionKey parameter in cookies.getAll,
|
||||
// so we must filter them down here to make sure the provided details match.
|
||||
if (topLevelSiteFilter) {
|
||||
let cookiePartitionKey = getExtPartitionKey(cookie);
|
||||
let cookiePartitionSite = cookiePartitionKey?.topLevelSite;
|
||||
|
||||
// Getting here implies that we are interested in partitioned
|
||||
// cookies. If there is no partition, skip the cookie.
|
||||
// We also skip the cookie if it doesn't have a matching site
|
||||
// componenet.
|
||||
if (!cookiePartitionKey || topLevelSiteFilter !== cookiePartitionSite) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (
|
||||
detailsIn.partitionKey.hasCrossSiteAncestor != null &&
|
||||
detailsIn.partitionKey.hasCrossSiteAncestor !=
|
||||
cookiePartitionKey.hasCrossSiteAncestor
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -653,9 +737,7 @@ this.cookies = class extends ExtensionAPIPersistent {
|
||||
name: details.name,
|
||||
storeId,
|
||||
firstPartyDomain: cookie.originAttributes.firstPartyDomain,
|
||||
partitionKey: toExtPartitionKey(
|
||||
cookie.originAttributes.partitionKey
|
||||
),
|
||||
partitionKey: getExtPartitionKey(cookie),
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -33,6 +33,11 @@
|
||||
"type": "string",
|
||||
"optional": true,
|
||||
"description": "The first-party URL of the cookie, if the cookie is in storage partitioned by the top-level site."
|
||||
},
|
||||
"hasCrossSiteAncestor": {
|
||||
"type": "boolean",
|
||||
"optional": true,
|
||||
"description": "Whether or not the cookie is in a third-party context, respecting ancestor chains."
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -363,7 +363,10 @@ add_task(async function test_dfpi() {
|
||||
},
|
||||
expectedOut: {
|
||||
firstPartyDomain: "",
|
||||
partitionKey: { topLevelSite: `http://${FIRST_DOMAIN_ETLD_PLUS_1}` },
|
||||
partitionKey: {
|
||||
topLevelSite: `http://${FIRST_DOMAIN_ETLD_PLUS_1}`,
|
||||
hasCrossSiteAncestor: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
@ -395,7 +398,10 @@ add_task(async function test_dfpi_with_ip_and_port() {
|
||||
},
|
||||
expectedOut: {
|
||||
firstPartyDomain: "",
|
||||
partitionKey: { topLevelSite: `http://${LOCAL_IP_AND_PORT}` },
|
||||
partitionKey: {
|
||||
topLevelSite: `http://${LOCAL_IP_AND_PORT}`,
|
||||
hasCrossSiteAncestor: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
@ -429,7 +435,10 @@ add_task(async function test_dfpi_with_nested_subdomains() {
|
||||
},
|
||||
expectedOut: {
|
||||
firstPartyDomain: "",
|
||||
partitionKey: { topLevelSite: `http://${FIRST_DOMAIN_ETLD_PLUS_1}` },
|
||||
partitionKey: {
|
||||
topLevelSite: `http://${FIRST_DOMAIN_ETLD_PLUS_1}`,
|
||||
hasCrossSiteAncestor: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
@ -440,6 +449,10 @@ add_task(async function test_dfpi_with_nested_subdomains() {
|
||||
);
|
||||
});
|
||||
|
||||
// Bug 1921038:
|
||||
// This test doesn't actually validate the the behavior it says!
|
||||
// This is because for these internal partition keys, we cannot return
|
||||
// the correct scheme!
|
||||
add_task(async function test_dfpi_with_non_default_use_site() {
|
||||
// privacy.dynamic_firstparty.use_site is a pref that can be used to toggle
|
||||
// the internal representation of partitionKey. True (default) means keyed
|
||||
@ -460,13 +473,16 @@ add_task(async function test_dfpi_with_non_default_use_site() {
|
||||
description: "third-party cookies with dFPI and use_site=false",
|
||||
domain: THIRD_PARTY_DOMAIN,
|
||||
detailsIn: {
|
||||
partitionKey: { topLevelSite: `http://${FIRST_DOMAIN_ETLD_PLUS_1}` },
|
||||
partitionKey: { topLevelSite: `https://${FIRST_DOMAIN_ETLD_PLUS_1}` },
|
||||
},
|
||||
expectedOut: {
|
||||
firstPartyDomain: "",
|
||||
// When use_site=false, the scheme is not stored, and the
|
||||
// implementation just prepends "https" as a dummy scheme.
|
||||
partitionKey: { topLevelSite: `https://${FIRST_DOMAIN_ETLD_PLUS_1}` },
|
||||
partitionKey: {
|
||||
topLevelSite: `https://${FIRST_DOMAIN_ETLD_PLUS_1}`,
|
||||
hasCrossSiteAncestor: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
@ -479,6 +495,11 @@ add_task(async function test_dfpi_with_non_default_use_site() {
|
||||
() => testCookiesAPI({ testCases })
|
||||
);
|
||||
});
|
||||
|
||||
// Bug 1921038:
|
||||
// This test doesn't actually validate the the behavior it says!
|
||||
// This is because for these internal partition keys, we cannot return
|
||||
// the correct scheme or port!
|
||||
add_task(async function test_dfpi_with_ip_and_port_and_non_default_use_site() {
|
||||
// privacy.dynamic_firstparty.use_site is a pref that can be used to toggle
|
||||
// the internal representation of partitionKey. True (default) means keyed
|
||||
@ -503,13 +524,16 @@ add_task(async function test_dfpi_with_ip_and_port_and_non_default_use_site() {
|
||||
// representation of the partitionKey. So even though the web page
|
||||
// creates the cookie at HTTP, the cookies are still detected when
|
||||
// "https" is used.
|
||||
partitionKey: { topLevelSite: `https://${LOCAL_IP_AND_PORT}` },
|
||||
partitionKey: { topLevelSite: `https://127.0.0.1` },
|
||||
},
|
||||
expectedOut: {
|
||||
firstPartyDomain: "",
|
||||
// When use_site=false, the scheme and port are not stored.
|
||||
// "https" is used as a dummy scheme, and the port is not used.
|
||||
partitionKey: { topLevelSite: "https://127.0.0.1" },
|
||||
partitionKey: {
|
||||
topLevelSite: `https://127.0.0.1`,
|
||||
hasCrossSiteAncestor: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
@ -888,6 +912,207 @@ add_task(async function test_getAll_partitionKey() {
|
||||
await extension.unload();
|
||||
});
|
||||
|
||||
add_task(async function test_getAll_partitionKey_hasCrossSiteAncestor() {
|
||||
let extension = ExtensionTestUtils.loadExtension({
|
||||
manifest: {
|
||||
permissions: [
|
||||
"cookies",
|
||||
"*://first.example.com/",
|
||||
"*://third.example.net/",
|
||||
],
|
||||
},
|
||||
async background() {
|
||||
async function getAllValues(details) {
|
||||
let cookies = await browser.cookies.getAll(details);
|
||||
let values = cookies.map(c => c.value);
|
||||
return values.sort().join(); // Serialize for use with assertEq.
|
||||
}
|
||||
const urls = ["http://first.example.com", "http://third.example.net"];
|
||||
const name = "test_getAll_partitionKey_hasCrossSiteAncestor";
|
||||
const partitionKeyUnspecified = { topLevelSite: "http://example.com" };
|
||||
const partitionKeySameSite = {
|
||||
topLevelSite: "http://example.com",
|
||||
hasCrossSiteAncestor: false,
|
||||
};
|
||||
const partitionKeyForeign = {
|
||||
topLevelSite: "http://example.com",
|
||||
hasCrossSiteAncestor: true,
|
||||
};
|
||||
try {
|
||||
for (let url of urls) {
|
||||
await browser.cookies.set({ url, name, value: "no_partition" });
|
||||
if (url == "http://first.example.com") {
|
||||
await browser.cookies.set({
|
||||
url,
|
||||
name,
|
||||
value: "partition1",
|
||||
partitionKey: partitionKeySameSite,
|
||||
});
|
||||
} else {
|
||||
await browser.test.assertRejects(
|
||||
browser.cookies.set({
|
||||
url,
|
||||
name,
|
||||
value: "partition1",
|
||||
partitionKey: partitionKeySameSite,
|
||||
}),
|
||||
/Invalid value for 'partitionKey' attribute/,
|
||||
// When topLevelSite and url are cross-site, hasCrossSiteAncestor cannot be false
|
||||
"cookies.get should reject invalid partitionKey.topLevelSite"
|
||||
);
|
||||
}
|
||||
await browser.cookies.set({
|
||||
url,
|
||||
name,
|
||||
value: "partition3",
|
||||
partitionKey: partitionKeyForeign,
|
||||
});
|
||||
let cookie = await browser.cookies.set({
|
||||
url,
|
||||
name,
|
||||
value: "partitionX",
|
||||
partitionKey: partitionKeyUnspecified,
|
||||
});
|
||||
|
||||
if (url == "http://first.example.com") {
|
||||
browser.test.assertDeepEq(
|
||||
partitionKeySameSite,
|
||||
cookie.partitionKey,
|
||||
"partitionKey.hasCrossSiteAncestor computed as false"
|
||||
);
|
||||
browser.test.assertEq(
|
||||
"no_partition,partition3,partitionX",
|
||||
await getAllValues({ partitionKey: {} }),
|
||||
"getAll() with empty partitionKey gets all cookies"
|
||||
);
|
||||
browser.test.assertEq(
|
||||
"partition3,partitionX",
|
||||
await getAllValues({ partitionKey: partitionKeyUnspecified }),
|
||||
"getAll() with partitionKey with undefined hasCrossSiteAncestor gets all partitioned cookies"
|
||||
);
|
||||
browser.test.assertEq(
|
||||
"partitionX",
|
||||
await getAllValues({ partitionKey: partitionKeySameSite }),
|
||||
"getAll() with partitionKey with false hasCrossSiteAncestor gets cookie stored with undefined hasCrossSiteAncestor"
|
||||
);
|
||||
browser.test.assertEq(
|
||||
"partition3",
|
||||
await getAllValues({ partitionKey: partitionKeyForeign }),
|
||||
"getAll() with partitionKey with true hasCrossSiteAncestor gets only cookie stored with true hasCrossSiteAncestor"
|
||||
);
|
||||
} else {
|
||||
browser.test.assertDeepEq(
|
||||
partitionKeyForeign,
|
||||
cookie.partitionKey,
|
||||
"partitionKey.hasCrossSiteAncestor computed as true"
|
||||
);
|
||||
browser.test.assertEq(
|
||||
"no_partition,partitionX",
|
||||
await getAllValues({ partitionKey: {} }),
|
||||
"getAll() with empty partitionKey gets all cookies"
|
||||
);
|
||||
browser.test.assertEq(
|
||||
"partitionX",
|
||||
await getAllValues({ partitionKey: partitionKeyUnspecified }),
|
||||
"getAll() with partitionKey with undefined hasCrossSiteAncestor gets all partitioned cookies"
|
||||
);
|
||||
browser.test.assertEq(
|
||||
"",
|
||||
await getAllValues({ partitionKey: partitionKeySameSite }),
|
||||
"getAll() with partitionKey with false hasCrossSiteAncestor gets no cookies"
|
||||
);
|
||||
browser.test.assertEq(
|
||||
"partitionX",
|
||||
await getAllValues({ partitionKey: partitionKeyForeign }),
|
||||
"getAll() with partitionKey with true hasCrossSiteAncestor gets cookie stored with undefined hasCrossSiteAncestor"
|
||||
);
|
||||
}
|
||||
|
||||
let removedCookie = await browser.cookies.remove({ url, name });
|
||||
browser.test.assertTrue(
|
||||
removedCookie,
|
||||
"remove() without partitionKey removes unpartitioned cookie"
|
||||
);
|
||||
removedCookie = await browser.cookies.remove({ url, name });
|
||||
browser.test.assertDeepEq(
|
||||
null,
|
||||
removedCookie,
|
||||
"remove() without partitionKey does not remove partitioned cookies"
|
||||
);
|
||||
removedCookie = await browser.cookies.remove({
|
||||
url,
|
||||
name,
|
||||
partitionKey: partitionKeyUnspecified,
|
||||
});
|
||||
browser.test.assertTrue(
|
||||
removedCookie,
|
||||
"remove() with partitionKey and unspecified ancestry bit removes the cookie set with that parameter"
|
||||
);
|
||||
if (url == "http://first.example.com") {
|
||||
browser.test.assertDeepEq(
|
||||
partitionKeySameSite,
|
||||
removedCookie?.partitionKey,
|
||||
"remove() with partitionKey and unspecified ancestry bit removes the correct partitioned cookie"
|
||||
);
|
||||
removedCookie = await browser.cookies.remove({
|
||||
url,
|
||||
name,
|
||||
partitionKey: partitionKeySameSite,
|
||||
});
|
||||
browser.test.assertDeepEq(
|
||||
null,
|
||||
removedCookie,
|
||||
"remove() with same site partition key is the same as the unspecified ancentry for this url"
|
||||
);
|
||||
removedCookie = await browser.cookies.remove({
|
||||
url,
|
||||
name,
|
||||
partitionKey: partitionKeyForeign,
|
||||
});
|
||||
browser.test.assertTrue(
|
||||
removedCookie,
|
||||
"remove() with partitionKey and foreign ancestry bit removes the right cookie"
|
||||
);
|
||||
} else {
|
||||
browser.test.assertDeepEq(
|
||||
partitionKeyForeign,
|
||||
removedCookie?.partitionKey,
|
||||
"remove() with partitionKey and unspecified ancestry bit removes the correct partitioned cookie"
|
||||
);
|
||||
await browser.test.assertRejects(
|
||||
browser.cookies.remove({
|
||||
url,
|
||||
name,
|
||||
partitionKey: partitionKeySameSite,
|
||||
}),
|
||||
/Invalid value for 'partitionKey' attribute/,
|
||||
// When topLevelSite and url are cross-site, hasCrossSiteAncestor cannot be false
|
||||
"cookies.get should reject invalid partitionKey.topLevelSite"
|
||||
);
|
||||
removedCookie = await browser.cookies.remove({
|
||||
url,
|
||||
name,
|
||||
partitionKey: partitionKeyForeign,
|
||||
});
|
||||
browser.test.assertDeepEq(
|
||||
null,
|
||||
removedCookie,
|
||||
"remove() with foreign partition key is the same as the unspecified ancentry for this url"
|
||||
);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
browser.test.fail("Unexpected error: " + e);
|
||||
}
|
||||
|
||||
browser.test.sendMessage("test_done");
|
||||
},
|
||||
});
|
||||
await extension.startup();
|
||||
await extension.awaitMessage("test_done");
|
||||
await extension.unload();
|
||||
});
|
||||
|
||||
add_task(async function no_unexpected_cookies_at_end_of_test() {
|
||||
let results = [];
|
||||
for (const cookie of Services.cookies.cookies) {
|
||||
|
Loading…
Reference in New Issue
Block a user