Bug 1731739 part 2 - Use site as key for 3rdPartyStorage permissions, rather than origin r=anti-tracking-reviewers,pbz,timhuang

- Adding browser tests to verify correct behavior in integration
    - New test that fails on previous version: toolkit/components/antitracking/test/browser/browser_storageAccessScopeSameSiteWrite.js
- Add the ability to store permission by site, use 3rdPartyStorage for this
- No change is made to permission reads. These already proceed recursively, which eventually reach the site.
- When fetching all permissions for a principal, also look for site-scoped permissions on its site's principal

Differential Revision: https://phabricator.services.mozilla.com/D130675
This commit is contained in:
Benjamin VanderSloot 2021-11-23 18:25:58 +00:00
parent 8a550db0b7
commit 83bac9db86
18 changed files with 773 additions and 80 deletions

View File

@ -783,12 +783,13 @@ let ThirdPartyCookies = new (class ThirdPartyCookies extends ProtectionCategory
}
_getExceptionState(origin) {
for (let perm of Services.perms.getAllForPrincipal(
gBrowser.contentPrincipal
)) {
if (perm.type == "3rdPartyStorage^" + origin) {
return perm.capability;
}
let thirdPartyStorage = Services.perms.testPermissionFromPrincipal(
gBrowser.contentPrincipal,
"3rdPartyStorage^" + origin
);
if (thirdPartyStorage != Services.perms.UNKNOWN_ACTION) {
return thirdPartyStorage;
}
let principal = Services.scriptSecurityManager.createContentPrincipalFromOrigin(

View File

@ -32,3 +32,4 @@ skip-if = true # Bug 1538602
[browser_temporary_permissions_expiry.js]
[browser_temporary_permissions_navigation.js]
[browser_temporary_permissions_tabs.js]
[browser_site_scoped_permissions.js]

View File

@ -29,26 +29,6 @@ const EMPTY_PAGE =
const AUTOPLAY_PREF = "media.autoplay.default";
const AUTOPLAY_PERM = "autoplay-media";
function openPermissionPopup() {
let promise = BrowserTestUtils.waitForEvent(
gBrowser.ownerGlobal,
"popupshown",
true,
event => event.target == gPermissionPanel._permissionPopup
);
gPermissionPanel._identityPermissionBox.click();
return promise;
}
function closePermissionPopup() {
let promise = BrowserTestUtils.waitForEvent(
gPermissionPanel._permissionPopup,
"popuphidden"
);
gPermissionPanel._permissionPopup.hidePopup();
return promise;
}
function autoplayBlockedIcon() {
return document.querySelector(
"#blocked-permissions-container " +

View File

@ -8,26 +8,6 @@ const PERMISSIONS_PAGE =
"https://example.com"
) + "permissions.html";
function openPermissionPopup() {
let promise = BrowserTestUtils.waitForEvent(
window,
"popupshown",
true,
event => event.target == gPermissionPanel._permissionPopup
);
gPermissionPanel._identityPermissionBox.click();
return promise;
}
function closePermissionPopup() {
let promise = BrowserTestUtils.waitForEvent(
gPermissionPanel._permissionPopup,
"popuphidden"
);
gPermissionPanel._permissionPopup.hidePopup();
return promise;
}
function testPermListHasEntries(expectEntries) {
let permissionsList = document.getElementById(
"permission-popup-permission-list"

View File

@ -0,0 +1,105 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
const EMPTY_PAGE =
getRootDirectory(gTestPath).replace(
"chrome://mochitests/content",
"https://example.com"
) + "empty.html";
const SUBDOMAIN_EMPTY_PAGE =
getRootDirectory(gTestPath).replace(
"chrome://mochitests/content",
"https://www.example.com"
) + "empty.html";
add_task(async function testSiteScopedPermissionSubdomainAffectsBaseDomain() {
let subdomainOrigin = "https://www.example.com";
let subdomainPrincipal = Services.scriptSecurityManager.createContentPrincipalFromOrigin(
subdomainOrigin
);
let id = "3rdPartyStorage^https://example.org";
await BrowserTestUtils.withNewTab(EMPTY_PAGE, async function(browser) {
Services.perms.addFromPrincipal(
subdomainPrincipal,
id,
SitePermissions.ALLOW
);
await openPermissionPopup();
let permissionsList = document.getElementById(
"permission-popup-permission-list"
);
let listEntryCount = permissionsList.querySelectorAll(
".permission-popup-permission-item"
).length;
is(
listEntryCount,
1,
"Permission exists on base domain when set on subdomain"
);
closePermissionPopup();
Services.perms.removeFromPrincipal(subdomainPrincipal, id);
await openPermissionPopup();
listEntryCount = permissionsList.querySelectorAll(
".permission-popup-permission-item-3rdPartyStorage"
).length;
is(
listEntryCount,
0,
"Permission removed on base domain when removed on subdomain"
);
await closePermissionPopup();
});
});
add_task(async function testSiteScopedPermissionBaseDomainAffectsSubdomain() {
let origin = "https://example.com";
let principal = Services.scriptSecurityManager.createContentPrincipalFromOrigin(
origin
);
let id = "3rdPartyStorage^https://example.org";
await BrowserTestUtils.withNewTab(SUBDOMAIN_EMPTY_PAGE, async function(
browser
) {
Services.perms.addFromPrincipal(principal, id, SitePermissions.ALLOW);
await openPermissionPopup();
let permissionsList = document.getElementById(
"permission-popup-permission-list"
);
let listEntryCount = permissionsList.querySelectorAll(
".permission-popup-permission-item"
).length;
is(
listEntryCount,
1,
"Permission exists on base domain when set on subdomain"
);
closePermissionPopup();
Services.perms.removeFromPrincipal(principal, id);
await openPermissionPopup();
listEntryCount = permissionsList.querySelectorAll(
".permission-popup-permission-item-3rdPartyStorage"
).length;
is(
listEntryCount,
0,
"Permission removed on base domain when removed on subdomain"
);
await closePermissionPopup();
});
});

View File

@ -9,3 +9,23 @@ SpecialPowers.addTaskImport(
"E10SUtils",
"resource://gre/modules/E10SUtils.jsm"
);
function openPermissionPopup() {
let promise = BrowserTestUtils.waitForEvent(
gBrowser.ownerGlobal,
"popupshown",
true,
event => event.target == gPermissionPanel._permissionPopup
);
gPermissionPanel._identityPermissionBox.click();
return promise;
}
function closePermissionPopup() {
let promise = BrowserTestUtils.waitForEvent(
gPermissionPanel._permissionPopup,
"popuphidden"
);
gPermissionPanel._permissionPopup.hidePopup();
return promise;
}

View File

@ -164,6 +164,25 @@ bool IsOAForceStripPermission(const nsACString& aType) {
return false;
}
// Array of permission prefixes which should be isolated only by site.
// These site-scoped permissions are stored under their site's principal.
// GetAllForPrincipal also needs to look for these especially.
static constexpr std::array<nsLiteralCString, 1> kSiteScopedPermissions = {
{"3rdPartyStorage^"_ns}};
bool IsSiteScopedPermission(const nsACString& aType) {
if (aType.IsEmpty()) {
return false;
}
for (const auto& perm : kSiteScopedPermissions) {
if (aType.Length() >= perm.Length() &&
Substring(aType, 0, perm.Length()) == perm) {
return true;
}
}
return false;
}
void OriginAppendOASuffix(OriginAttributes aOriginAttributes,
bool aForceStripOA, nsACString& aOrigin) {
PermissionManager::MaybeStripOriginAttributes(aForceStripOA,
@ -188,15 +207,33 @@ nsresult GetOriginFromPrincipal(nsIPrincipal* aPrincipal, bool aForceStripOA,
NS_ENSURE_SUCCESS(rv, rv);
OriginAttributes attrs;
if (!attrs.PopulateFromSuffix(suffix)) {
return NS_ERROR_FAILURE;
}
NS_ENSURE_TRUE(attrs.PopulateFromSuffix(suffix), NS_ERROR_FAILURE);
OriginAppendOASuffix(attrs, aForceStripOA, aOrigin);
return NS_OK;
}
// Returns the site of the principal, including OA, given a principal.
nsresult GetSiteFromPrincipal(nsIPrincipal* aPrincipal, bool aForceStripOA,
nsACString& aSite) {
nsresult rv = aPrincipal->GetSiteOriginNoSuffix(aSite);
// The principal may belong to the about:blank content viewer, so this can be
// expected to fail.
NS_ENSURE_SUCCESS(rv, rv);
nsAutoCString suffix;
rv = aPrincipal->GetOriginSuffix(suffix);
NS_ENSURE_SUCCESS(rv, rv);
OriginAttributes attrs;
NS_ENSURE_TRUE(attrs.PopulateFromSuffix(suffix), NS_ERROR_FAILURE);
OriginAppendOASuffix(attrs, aForceStripOA, aSite);
return NS_OK;
}
nsresult GetOriginFromURIAndOA(nsIURI* aURI,
const OriginAttributes* aOriginAttributes,
bool aForceStripOA, nsACString& aOrigin) {
@ -533,14 +570,19 @@ bool IsPersistentExpire(uint32_t aExpire, const nsACString& aType) {
PermissionManager::PermissionKey*
PermissionManager::PermissionKey::CreateFromPrincipal(nsIPrincipal* aPrincipal,
bool aForceStripOA,
bool scopeToSite,
nsresult& aResult) {
nsAutoCString origin;
aResult = GetOriginFromPrincipal(aPrincipal, aForceStripOA, origin);
nsAutoCString keyString;
if (scopeToSite) {
aResult = GetSiteFromPrincipal(aPrincipal, aForceStripOA, keyString);
} else {
aResult = GetOriginFromPrincipal(aPrincipal, aForceStripOA, keyString);
}
if (NS_WARN_IF(NS_FAILED(aResult))) {
return nullptr;
}
return new PermissionKey(origin);
return new PermissionKey(keyString);
}
PermissionManager::PermissionKey*
@ -1589,9 +1631,14 @@ nsresult PermissionManager::AddInternal(
// Use the origin string provided by the caller.
origin = *aOriginString;
} else {
// Compute it from the principal provided.
rv = GetOriginFromPrincipal(aPrincipal, IsOAForceStripPermission(aType),
if (IsSiteScopedPermission(aType)) {
rv = GetSiteFromPrincipal(aPrincipal, IsOAForceStripPermission(aType),
origin);
} else {
// Compute it from the principal provided.
rv = GetOriginFromPrincipal(aPrincipal, IsOAForceStripPermission(aType),
origin);
}
NS_ENSURE_SUCCESS(rv, rv);
}
}
@ -1640,7 +1687,8 @@ nsresult PermissionManager::AddInternal(
// When an entry already exists, PutEntry will return that, instead
// of adding a new one
RefPtr<PermissionKey> key = PermissionKey::CreateFromPrincipal(
aPrincipal, IsOAForceStripPermission(aType), rv);
aPrincipal, IsOAForceStripPermission(aType),
IsSiteScopedPermission(aType), rv);
if (!key) {
MOZ_ASSERT(NS_FAILED(rv));
return rv;
@ -2330,17 +2378,12 @@ NS_IMETHODIMP PermissionManager::GetAllWithTypePrefix(
aResult);
}
NS_IMETHODIMP
PermissionManager::GetAllForPrincipal(
nsIPrincipal* aPrincipal, nsTArray<RefPtr<nsIPermission>>& aResult) {
aResult.Clear();
EnsureReadCompleted();
MOZ_ASSERT(PermissionAvailable(aPrincipal, ""_ns));
nsresult PermissionManager::GetAllForPrincipalHelper(
nsIPrincipal* aPrincipal, bool aSiteScopePermissions,
nsTArray<RefPtr<nsIPermission>>& aResult) {
nsresult rv;
RefPtr<PermissionKey> key =
PermissionKey::CreateFromPrincipal(aPrincipal, false, rv);
RefPtr<PermissionKey> key = PermissionKey::CreateFromPrincipal(
aPrincipal, false, aSiteScopePermissions, rv);
if (!key) {
MOZ_ASSERT(NS_FAILED(rv));
return rv;
@ -2348,7 +2391,8 @@ PermissionManager::GetAllForPrincipal(
PermissionHashKey* entry = mPermissionTable.GetEntry(key);
nsTArray<PermissionEntry> strippedPerms;
rv = GetStripPermsForPrincipal(aPrincipal, strippedPerms);
rv = GetStripPermsForPrincipal(aPrincipal, aSiteScopePermissions,
strippedPerms);
if (NS_FAILED(rv)) {
return rv;
}
@ -2367,6 +2411,13 @@ PermissionManager::GetAllForPrincipal(
continue;
}
// Make sure that we only get site scoped permissions if this
// helper is being invoked for that purpose.
if (aSiteScopePermissions !=
IsSiteScopedPermission(mTypeArray[permEntry.mType])) {
continue;
}
// Stripped principal permissions overwrite regular ones
// For each permission check if there is a stripped permission we should
// use instead
@ -2404,6 +2455,23 @@ PermissionManager::GetAllForPrincipal(
return NS_OK;
}
NS_IMETHODIMP
PermissionManager::GetAllForPrincipal(
nsIPrincipal* aPrincipal, nsTArray<RefPtr<nsIPermission>>& aResult) {
nsresult rv;
aResult.Clear();
EnsureReadCompleted();
MOZ_ASSERT(PermissionAvailable(aPrincipal, ""_ns));
// First, append the non-site-scoped permissions.
rv = GetAllForPrincipalHelper(aPrincipal, false, aResult);
NS_ENSURE_SUCCESS(rv, rv);
// Second, append the site-scoped permissions.
return GetAllForPrincipalHelper(aPrincipal, true, aResult);
}
NS_IMETHODIMP PermissionManager::Observe(nsISupports* aSubject,
const char* aTopic,
const char16_t* someData) {
@ -2494,7 +2562,8 @@ nsresult PermissionManager::RemovePermissionsWithAttributes(
}
nsresult PermissionManager::GetStripPermsForPrincipal(
nsIPrincipal* aPrincipal, nsTArray<PermissionEntry>& aResult) {
nsIPrincipal* aPrincipal, bool aSiteScopePermissions,
nsTArray<PermissionEntry>& aResult) {
aResult.Clear();
aResult.SetCapacity(kStripOAPermissions.size());
@ -2511,9 +2580,10 @@ nsresult PermissionManager::GetStripPermsForPrincipal(
#endif
nsresult rv;
// Create a key for the principal, but strip any origin attributes
RefPtr<PermissionKey> key =
PermissionKey::CreateFromPrincipal(aPrincipal, true, rv);
// Create a key for the principal, but strip any origin attributes.
// The key must be created aware of whether or not we are scoping to site.
RefPtr<PermissionKey> key = PermissionKey::CreateFromPrincipal(
aPrincipal, true, aSiteScopePermissions, rv);
if (!key) {
MOZ_ASSERT(NS_FAILED(rv));
return rv;
@ -2525,6 +2595,14 @@ nsresult PermissionManager::GetStripPermsForPrincipal(
}
for (const auto& permType : kStripOAPermissions) {
// if the permission type's site scoping does not match this function call,
// we don't care about it, so continue.
// As of time of writing, this never happens when aSiteScopePermissions
// is true because there is no common permission between kStripOAPermissions
// and kSiteScopedPermissions
if (aSiteScopePermissions != IsSiteScopedPermission(permType)) {
continue;
}
int32_t index = GetTypeIndex(permType, false);
if (index == -1) {
continue;
@ -2568,7 +2646,8 @@ PermissionManager::PermissionHashKey* PermissionManager::GetPermissionHashKey(
nsresult rv;
RefPtr<PermissionKey> key = PermissionKey::CreateFromPrincipal(
aPrincipal, IsOAForceStripPermission(mTypeArray[aType]), rv);
aPrincipal, IsOAForceStripPermission(mTypeArray[aType]),
IsSiteScopedPermission(mTypeArray[aType]), rv);
if (!key) {
return nullptr;
}
@ -2988,7 +3067,7 @@ bool PermissionManager::GetPermissionsFromOriginOrKey(
if (aOrigin.IsEmpty()) {
// We can't check for individual OA strip perms here.
// Don't force strip origin attributes.
GetKeyForOrigin(entry.GetKey()->mOrigin, false, permissionKey);
GetKeyForOrigin(entry.GetKey()->mOrigin, false, false, permissionKey);
// If the keys don't match, and we aren't getting the default "" key, then
// we can exit early. We have to keep looking if we're getting the default
@ -3083,7 +3162,9 @@ void PermissionManager::SetPermissionsWithKey(
/* static */
void PermissionManager::GetKeyForOrigin(const nsACString& aOrigin,
bool aForceStripOA, nsACString& aKey) {
bool aForceStripOA,
bool aSiteScopePermissions,
nsACString& aKey) {
aKey.Truncate();
// We only key origins for http, https, and ftp URIs. All origins begin with
@ -3119,6 +3200,19 @@ void PermissionManager::GetKeyForOrigin(const nsACString& aOrigin,
MOZ_ASSERT(dbgPrincipal->OriginAttributesRef() == attrs);
#endif
// If it is needed, turn the origin into its site-origin
if (aSiteScopePermissions) {
nsCOMPtr<nsIURI> uri;
nsresult rv = NS_NewURI(getter_AddRefs(uri), aKey);
if (!NS_WARN_IF(NS_FAILED(rv))) {
nsCString site;
rv = nsEffectiveTLDService::GetInstance()->GetSite(uri, site);
if (!NS_WARN_IF(NS_FAILED(rv))) {
aKey = site;
}
}
}
// Append the stripped suffix to the output origin key.
nsAutoCString suffix;
attrs.CreateSuffix(suffix);
@ -3128,6 +3222,7 @@ void PermissionManager::GetKeyForOrigin(const nsACString& aOrigin,
/* static */
void PermissionManager::GetKeyForPrincipal(nsIPrincipal* aPrincipal,
bool aForceStripOA,
bool aSiteScopePermissions,
nsACString& aKey) {
nsAutoCString origin;
nsresult rv = aPrincipal->GetOrigin(origin);
@ -3135,7 +3230,7 @@ void PermissionManager::GetKeyForPrincipal(nsIPrincipal* aPrincipal,
aKey.Truncate();
return;
}
GetKeyForOrigin(origin, aForceStripOA, aKey);
GetKeyForOrigin(origin, aForceStripOA, aSiteScopePermissions, aKey);
}
/* static */
@ -3148,7 +3243,8 @@ void PermissionManager::GetKeyForPermission(nsIPrincipal* aPrincipal,
return;
}
GetKeyForPrincipal(aPrincipal, IsOAForceStripPermission(aType), aKey);
GetKeyForPrincipal(aPrincipal, IsOAForceStripPermission(aType),
IsSiteScopedPermission(aType), aKey);
}
/* static */
@ -3164,7 +3260,7 @@ PermissionManager::GetAllKeysForPrincipal(nsIPrincipal* aPrincipal) {
pairs.AppendElement(std::make_pair(""_ns, ""_ns));
// We can't check for individual OA strip perms here.
// Don't force strip origin attributes.
GetKeyForPrincipal(prin, false, pair->first);
GetKeyForPrincipal(prin, false, false, pair->first);
// On origins with a derived key set to an empty string
// (basically any non-web URI scheme), we want to make sure

View File

@ -85,6 +85,7 @@ class PermissionManager final : public nsIPermissionManager,
public:
static PermissionKey* CreateFromPrincipal(nsIPrincipal* aPrincipal,
bool aForceStripOA,
bool aScopeToSite,
nsresult& aResult);
static PermissionKey* CreateFromURI(nsIURI* aURI, nsresult& aResult);
static PermissionKey* CreateFromURIAndOriginAttributes(
@ -216,11 +217,15 @@ class PermissionManager final : public nsIPermissionManager,
* @param aPrincipal The Principal which the key is to be extracted from.
* @param aForceStripOA Whether to force stripping the principals origin
* attributes prior to generating the key.
* @param aSiteScopePermissions Whether to prepare the key for permissions
* scoped to the Principal's site, rather than origin. These are looked
* up independently. Scoping of a permission is fully determined by its
* type and determined by calls to the function IsSiteScopedPermission.
* @param aKey A string which will be filled with the permission
* key.
*/
static void GetKeyForPrincipal(nsIPrincipal* aPrincipal, bool aForceStripOA,
nsACString& aKey);
bool aSiteScopePermissions, nsACString& aKey);
/**
* See `nsIPermissionManager::GetPermissionsWithKey` for more info on
@ -236,11 +241,15 @@ class PermissionManager final : public nsIPermissionManager,
* @param aOrigin The origin which the key is to be extracted from.
* @param aForceStripOA Whether to force stripping the origins attributes
* prior to generating the key.
* @param aSiteScopePermissions Whether to prepare the key for permissions
* scoped to the Principal's site, rather than origin. These are looked
* up independently. Scoping of a permission is fully determined by its
* type and determined by calls to the function IsSiteScopedPermission.
* @param aKey A string which will be filled with the permission
* key.
*/
static void GetKeyForOrigin(const nsACString& aOrigin, bool aForceStripOA,
nsACString& aKey);
bool aSiteScopePermissions, nsACString& aKey);
/**
* See `nsIPermissionManager::GetPermissionsWithKey` for more info on
@ -371,8 +380,12 @@ class PermissionManager final : public nsIPermissionManager,
* attributes stripped before perm db lookup. This is currently only affects
* the "cookie" permission.
* @param aPrincipal Used for creating the permission key.
* @param aSiteScopePermissions Used to specify whether to get strip perms for
* site scoped permissions (defined in IsSiteScopedPermission) or all other
* permissions. Also used to create the permission key.
*/
nsresult GetStripPermsForPrincipal(nsIPrincipal* aPrincipal,
bool aSiteScopePermissions,
nsTArray<PermissionEntry>& aResult);
// Returns -1 on failure
@ -382,6 +395,14 @@ class PermissionManager final : public nsIPermissionManager,
// expired. Note that EXPIRE_SESSION only honors expireTime if it is nonzero.
bool HasExpired(uint32_t aExpireType, int64_t aExpireTime);
// Appends the permissions associated with this principal to aResult.
// If the onlySiteScopePermissions argument is true, the permissions searched
// are those for the site of the principal and only the permissions that are
// site-scoped are used.
nsresult GetAllForPrincipalHelper(nsIPrincipal* aPrincipal,
bool aSiteScopePermissions,
nsTArray<RefPtr<nsIPermission>>& aResult);
// Returns PermissionHashKey for a given { host, isInBrowserElement } tuple.
// This is not simply using PermissionKey because we will walk-up domains in
// case of |host| contains sub-domains. Returns null if nothing found. Also

View File

@ -0,0 +1,120 @@
const TEST_SITE_URI = Services.io.newURI("http://example.com");
const TEST_FQDN_1_URI = Services.io.newURI("http://test1.example.com");
const TEST_FQDN_2_URI = Services.io.newURI("http://test2.example.com");
const TEST_OTHER_URI = Services.io.newURI("http://example.net");
const TEST_PERMISSION = "3rdPartyStorage^https://example.org";
add_task(async function do_test() {
let pm = Services.perms;
let principal = Services.scriptSecurityManager.createContentPrincipal(
TEST_SITE_URI,
{}
);
let subdomain1Principal = Services.scriptSecurityManager.createContentPrincipal(
TEST_FQDN_1_URI,
{}
);
let subdomain2Principal = Services.scriptSecurityManager.createContentPrincipal(
TEST_FQDN_2_URI,
{}
);
let otherPrincipal = Services.scriptSecurityManager.createContentPrincipal(
TEST_OTHER_URI,
{}
);
// Set test permission for site
pm.addFromPrincipal(principal, TEST_PERMISSION, pm.ALLOW_ACTION);
// Check normal site permission
Assert.equal(
Ci.nsIPermissionManager.ALLOW_ACTION,
pm.testPermissionFromPrincipal(principal, TEST_PERMISSION)
);
// Check subdomain permission
Assert.equal(
Ci.nsIPermissionManager.ALLOW_ACTION,
pm.testPermissionFromPrincipal(subdomain1Principal, TEST_PERMISSION)
);
// Check other site permission
Assert.equal(
Ci.nsIPermissionManager.UNKNOWN_ACTION,
pm.testPermissionFromPrincipal(otherPrincipal, TEST_PERMISSION)
);
// Remove the permission from the site
pm.removeFromPrincipal(principal, TEST_PERMISSION);
Assert.equal(
pm.testPermissionFromPrincipal(principal, TEST_PERMISSION),
Ci.nsIPermissionManager.UNKNOWN_ACTION
);
Assert.equal(
pm.testPermissionFromPrincipal(subdomain1Principal, TEST_PERMISSION),
Ci.nsIPermissionManager.UNKNOWN_ACTION
);
// Set test permission for subdomain
pm.addFromPrincipal(subdomain1Principal, TEST_PERMISSION, pm.ALLOW_ACTION);
// Check normal site permission
Assert.equal(
Ci.nsIPermissionManager.ALLOW_ACTION,
pm.testPermissionFromPrincipal(principal, TEST_PERMISSION)
);
// Check subdomain permission
Assert.equal(
Ci.nsIPermissionManager.ALLOW_ACTION,
pm.testPermissionFromPrincipal(subdomain1Principal, TEST_PERMISSION)
);
// Check other subdomain permission
Assert.equal(
Ci.nsIPermissionManager.ALLOW_ACTION,
pm.testPermissionFromPrincipal(subdomain2Principal, TEST_PERMISSION)
);
// Check other site permission
Assert.equal(
Ci.nsIPermissionManager.UNKNOWN_ACTION,
pm.testPermissionFromPrincipal(otherPrincipal, TEST_PERMISSION)
);
// Check that subdomains include the site-scoped in the getAllForPrincipal
let sitePerms = pm.getAllForPrincipal(principal, TEST_PERMISSION);
let subdomain1Perms = pm.getAllForPrincipal(
subdomain1Principal,
TEST_PERMISSION
);
let subdomain2Perms = pm.getAllForPrincipal(
subdomain2Principal,
TEST_PERMISSION
);
let otherSitePerms = pm.getAllForPrincipal(otherPrincipal, TEST_PERMISSION);
Assert.equal(sitePerms.length, 1);
Assert.equal(subdomain1Perms.length, 1);
Assert.equal(subdomain2Perms.length, 1);
Assert.equal(otherSitePerms.length, 0);
// Remove the permission from the subdomain
pm.removeFromPrincipal(subdomain1Principal, TEST_PERMISSION);
Assert.equal(
pm.testPermissionFromPrincipal(principal, TEST_PERMISSION),
Ci.nsIPermissionManager.UNKNOWN_ACTION
);
Assert.equal(
pm.testPermissionFromPrincipal(subdomain1Principal, TEST_PERMISSION),
Ci.nsIPermissionManager.UNKNOWN_ACTION
);
Assert.equal(
pm.testPermissionFromPrincipal(subdomain2Principal, TEST_PERMISSION),
Ci.nsIPermissionManager.UNKNOWN_ACTION
);
});

View File

@ -44,6 +44,7 @@ skip-if = toolkit == 'android' # Android doesn't use places
[test_permmanager_migrate_10-11.js]
skip-if = toolkit == 'android' # Android doesn't use places
[test_permmanager_oa_strip.js]
[test_permmanager_site_scope.js]
[test_permmanager_remove_add_update.js]
skip-if = win10_2004 && bits == 64 # Bug 1718292
[test_permmanager_ipc.js]

View File

@ -829,7 +829,7 @@ void ContentBlocking::UpdateAllowAccessOnParentProcess(
nsAutoCString topKey;
nsCOMPtr<nsIPrincipal> topPrincipal =
AntiTrackingUtils::GetPrincipal(aParentContext->Top());
PermissionManager::GetKeyForPrincipal(topPrincipal, false, topKey);
PermissionManager::GetKeyForPrincipal(topPrincipal, false, true, topKey);
// Propagate the storage permission to same-origin frames in the same
// agent-cluster.
@ -857,7 +857,7 @@ void ContentBlocking::UpdateAllowAccessOnParentProcess(
}
nsAutoCString key;
PermissionManager::GetKeyForPrincipal(principal, false, key);
PermissionManager::GetKeyForPrincipal(principal, false, true, key);
// Make sure we only apply to frames that have the same top-level.
if (topKey != key) {
continue;

View File

@ -22,6 +22,7 @@ support-files =
antitracking_head.js
dynamicfpi_head.js
partitionedstorage_head.js
storage_access_head.js
cookiesCORS.sjs
iframe.html
image.sjs
@ -99,6 +100,10 @@ skip-if = debug # Bug 1700551
[browser_storageAccessRemovalNavigateSubframe.js]
[browser_storageAccessRemovalNavigateTopframe.js]
[browser_storageAccessSandboxed.js]
[browser_storageAccessScopeDifferentSite.js]
[browser_storageAccessScopeSameOrigin.js]
[browser_storageAccessScopeSameSiteRead.js]
[browser_storageAccessScopeSameSiteWrite.js]
[browser_storageAccessThirdPartyChecks.js]
[browser_storageAccessWithDynamicFpi.js]
[browser_storageAccessWithHeuristics.js]

View File

@ -0,0 +1,46 @@
/* import-globals-from ../../../../../browser/modules/test/browser/head.js */
/* import-globals-from head.js */
/* import-globals-from storage_access_head.js */
Services.scriptloader.loadSubScript(
"chrome://mochitests/content/browser/browser/modules/test/browser/head.js",
this
);
Services.scriptloader.loadSubScript(
"chrome://mochitests/content/browser/toolkit/components/antitracking/test/browser/storage_access_head.js",
this
);
add_task(async function testInitialBlock() {
await setPreferences();
await openPageAndRunCode(
TEST_TOP_PAGE_7,
getExpectPopupAndClick("reject"),
TEST_3RD_PARTY_PAGE,
requestStorageAccessAndExpectFailure
);
await cleanUpData();
await SpecialPowers.flushPrefEnv();
});
add_task(async function testDifferentSitePermission() {
await setPreferences();
await openPageAndRunCode(
TEST_TOP_PAGE_7,
getExpectPopupAndClick("accept"),
TEST_3RD_PARTY_PAGE,
requestStorageAccessAndExpectSuccess
);
await openPageAndRunCode(
TEST_TOP_PAGE,
getExpectPopupAndClick("reject"),
TEST_3RD_PARTY_PAGE,
requestStorageAccessAndExpectFailure
);
await cleanUpData();
await SpecialPowers.flushPrefEnv();
});

View File

@ -0,0 +1,46 @@
/* import-globals-from ../../../../../browser/modules/test/browser/head.js */
/* import-globals-from head.js */
/* import-globals-from storage_access_head.js */
Services.scriptloader.loadSubScript(
"chrome://mochitests/content/browser/browser/modules/test/browser/head.js",
this
);
Services.scriptloader.loadSubScript(
"chrome://mochitests/content/browser/toolkit/components/antitracking/test/browser/storage_access_head.js",
this
);
add_task(async function testInitialBlock() {
await setPreferences();
await openPageAndRunCode(
TEST_TOP_PAGE_7,
getExpectPopupAndClick("reject"),
TEST_3RD_PARTY_PAGE,
requestStorageAccessAndExpectFailure
);
await cleanUpData();
await SpecialPowers.flushPrefEnv();
});
add_task(async function testSameOriginPermission() {
await setPreferences();
await openPageAndRunCode(
TEST_TOP_PAGE_7,
getExpectPopupAndClick("accept"),
TEST_3RD_PARTY_PAGE,
requestStorageAccessAndExpectSuccess
);
await openPageAndRunCode(
TEST_TOP_PAGE_7,
expectNoPopup,
TEST_3RD_PARTY_PAGE,
requestStorageAccessAndExpectSuccess
);
await cleanUpData();
await SpecialPowers.flushPrefEnv();
});

View File

@ -0,0 +1,46 @@
/* import-globals-from ../../../../../browser/modules/test/browser/head.js */
/* import-globals-from head.js */
/* import-globals-from storage_access_head.js */
Services.scriptloader.loadSubScript(
"chrome://mochitests/content/browser/browser/modules/test/browser/head.js",
this
);
Services.scriptloader.loadSubScript(
"chrome://mochitests/content/browser/toolkit/components/antitracking/test/browser/storage_access_head.js",
this
);
add_task(async function testInitialBlock() {
await setPreferences();
await openPageAndRunCode(
TEST_TOP_PAGE_7,
getExpectPopupAndClick("reject"),
TEST_3RD_PARTY_PAGE,
requestStorageAccessAndExpectFailure
);
await cleanUpData();
await SpecialPowers.flushPrefEnv();
});
add_task(async function testSameSitePermission() {
await setPreferences();
await openPageAndRunCode(
TEST_TOP_PAGE_7,
getExpectPopupAndClick("accept"),
TEST_3RD_PARTY_PAGE,
requestStorageAccessAndExpectSuccess
);
await openPageAndRunCode(
TEST_TOP_PAGE_8,
expectNoPopup,
TEST_3RD_PARTY_PAGE,
requestStorageAccessAndExpectSuccess
);
await cleanUpData();
await SpecialPowers.flushPrefEnv();
});

View File

@ -0,0 +1,46 @@
/* import-globals-from ../../../../../browser/modules/test/browser/head.js */
/* import-globals-from head.js */
/* import-globals-from storage_access_head.js */
Services.scriptloader.loadSubScript(
"chrome://mochitests/content/browser/browser/modules/test/browser/head.js",
this
);
Services.scriptloader.loadSubScript(
"chrome://mochitests/content/browser/toolkit/components/antitracking/test/browser/storage_access_head.js",
this
);
add_task(async function testInitialBlock() {
await setPreferences();
await openPageAndRunCode(
TEST_TOP_PAGE_7,
getExpectPopupAndClick("reject"),
TEST_3RD_PARTY_PAGE,
requestStorageAccessAndExpectFailure
);
await cleanUpData();
await SpecialPowers.flushPrefEnv();
});
add_task(async function testSameSitePermissionReversed() {
await setPreferences();
await openPageAndRunCode(
TEST_TOP_PAGE_8,
getExpectPopupAndClick("accept"),
TEST_3RD_PARTY_PAGE,
requestStorageAccessAndExpectSuccess
);
await openPageAndRunCode(
TEST_TOP_PAGE_7,
expectNoPopup,
TEST_3RD_PARTY_PAGE,
requestStorageAccessAndExpectSuccess
);
await cleanUpData();
await SpecialPowers.flushPrefEnv();
});

View File

@ -11,6 +11,8 @@ const TEST_DOMAIN_3 = "https://xn--hxajbheg2az3al.xn--jxalpdlp/";
const TEST_DOMAIN_4 = "http://prefixexample.com/";
const TEST_DOMAIN_5 = "http://test/";
const TEST_DOMAIN_6 = "http://mochi.test:8888/";
const TEST_DOMAIN_7 = "http://example.com/";
const TEST_DOMAIN_8 = "http://www.example.com/";
const TEST_3RD_PARTY_DOMAIN = "https://tracking.example.org/";
const TEST_3RD_PARTY_DOMAIN_HTTP = "http://tracking.example.org/";
const TEST_3RD_PARTY_DOMAIN_TP = "https://tracking.example.com/";
@ -32,6 +34,8 @@ const TEST_TOP_PAGE_3 = TEST_DOMAIN_3 + TEST_PATH + "page.html";
const TEST_TOP_PAGE_4 = TEST_DOMAIN_4 + TEST_PATH + "page.html";
const TEST_TOP_PAGE_5 = TEST_DOMAIN_5 + TEST_PATH + "page.html";
const TEST_TOP_PAGE_6 = TEST_DOMAIN_6 + TEST_PATH + "page.html";
const TEST_TOP_PAGE_7 = TEST_DOMAIN_7 + TEST_PATH + "page.html";
const TEST_TOP_PAGE_8 = TEST_DOMAIN_8 + TEST_PATH + "page.html";
const TEST_EMBEDDER_PAGE = TEST_DOMAIN + TEST_PATH + "embedder.html";
const TEST_POPUP_PAGE = TEST_DOMAIN + TEST_PATH + "popup.html";
const TEST_IFRAME_PAGE = TEST_DOMAIN + TEST_PATH + "iframe.html";

View File

@ -0,0 +1,175 @@
/* import-globals-from ../../../../../browser/modules/test/browser/head.js */
/* import-globals-from antitracking_head.js */
async function openPageAndRunCode(
topPage,
topPageCallback,
embeddedPage,
embeddedPageCallback
) {
let tab = await BrowserTestUtils.openNewForegroundTab({
gBrowser,
url: topPage,
waitForLoad: true,
});
let browser = gBrowser.getBrowserForTab(tab);
await topPageCallback();
await SpecialPowers.spawn(
browser,
[{ page: embeddedPage, callback: embeddedPageCallback.toString() }],
async function(obj) {
await new content.Promise(resolve => {
let ifr = content.document.createElement("iframe");
ifr.onload = function() {
ifr.contentWindow.postMessage(obj.callback, "*");
};
content.addEventListener("message", function msg(event) {
if (event.data.type == "finish") {
content.removeEventListener("message", msg);
resolve();
return;
}
if (event.data.type == "ok") {
ok(event.data.what, event.data.msg);
return;
}
if (event.data.type == "info") {
info(event.data.msg);
return;
}
ok(false, "Unknown message");
});
content.document.body.appendChild(ifr);
ifr.src = obj.page;
});
}
);
await BrowserTestUtils.removeTab(tab);
}
// This function returns a function that spawns an asynchronous task to handle
// the popup and click on the appropriate values. If that task is never executed
// the catch case is reached and we fail the test. If for some reason that catch
// case isn't reached, having an extra event listener at the end of the test
// will cause the test to fail anyway.
// Note: this means that tests that use this callback should probably be in
// their own test file.
function getExpectPopupAndClick(accept) {
return function() {
let shownPromise = BrowserTestUtils.waitForEvent(
PopupNotifications.panel,
"popupshown"
);
shownPromise
.then(async _ => {
// This occurs when the promise resolves on the test finishing
let popupNotifications = PopupNotifications.panel.childNodes;
if (!popupNotifications.length) {
ok(false, "Prompt did not show up");
} else if (accept == "accept") {
ok(true, "Prompt shows up, clicking accept.");
await clickMainAction();
} else if (accept == "reject") {
ok(true, "Prompt shows up, clicking reject.");
await clickSecondaryAction();
} else {
ok(false, "Unknown accept value for test: " + accept);
info("Clicking accept so that the test can finish.");
await clickMainAction();
}
})
.catch(() => {
ok(false, "Prompt did not show up");
});
};
}
// This function spawns an asynchronous task that fails the test if a popup
// appears. If that never happens, the catch case is executed on the test
// cleanup.
// Note: this means that tests that use this callback should probably be in
// their own test file.
function expectNoPopup() {
let shownPromise = BrowserTestUtils.waitForEvent(
PopupNotifications.panel,
"popupshown"
);
shownPromise
.then(async _ => {
// This occurs when the promise resolves on the test finishing
let popupNotifications = PopupNotifications.panel.childNodes;
if (!popupNotifications.length) {
ok(true, "Prompt did not show up");
} else {
ok(false, "Prompt shows up");
info(PopupNotifications.panel);
await clickSecondaryAction();
}
})
.catch(() => {
ok(true, "Prompt did not show up");
});
}
async function requestStorageAccessAndExpectSuccess() {
SpecialPowers.wrap(document).notifyUserGestureActivation();
let p = document.requestStorageAccess();
try {
await p;
ok(true, "gain storage access.");
} catch {
ok(false, "denied storage access.");
}
SpecialPowers.wrap(document).clearUserGestureActivation();
}
async function requestStorageAccessAndExpectFailure() {
SpecialPowers.wrap(document).notifyUserGestureActivation();
let p = document.requestStorageAccess();
try {
await p;
ok(false, "gain storage access.");
} catch {
ok(true, "denied storage access.");
}
SpecialPowers.wrap(document).clearUserGestureActivation();
}
async function cleanUpData() {
await new Promise(resolve => {
Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
resolve()
);
});
ok(true, "Deleted all data.");
}
async function setPreferences() {
await SpecialPowers.pushPrefEnv({
set: [
["dom.storage_access.auto_grants", true],
["dom.storage_access.auto_grants.delayed", false],
["dom.storage_access.enabled", true],
["dom.storage_access.max_concurrent_auto_grants", 0],
["dom.storage_access.prompt.testing", false],
[
"network.cookie.cookieBehavior",
Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN,
],
[
"network.cookie.cookieBehavior.pbmode",
Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN,
],
["privacy.trackingprotection.enabled", false],
["privacy.trackingprotection.pbmode.enabled", false],
["privacy.trackingprotection.annotate_channels", true],
],
});
}