diff --git a/browser/modules/Sanitizer.jsm b/browser/modules/Sanitizer.jsm index ab70463a0609..0542a72758b4 100644 --- a/browser/modules/Sanitizer.jsm +++ b/browser/modules/Sanitizer.jsm @@ -349,7 +349,8 @@ var Sanitizer = { let refObj = {}; TelemetryStopwatch.start("FX_SANITIZE_HISTORY", refObj); await clearData(range, Ci.nsIClearDataService.CLEAR_HISTORY | - Ci.nsIClearDataService.CLEAR_SESSION_HISTORY); + Ci.nsIClearDataService.CLEAR_SESSION_HISTORY | + Ci.nsIClearDataService.CLEAR_STORAGE_ACCESS); TelemetryStopwatch.finish("FX_SANITIZE_HISTORY", refObj); }, }, diff --git a/extensions/cookie/nsPermissionManager.cpp b/extensions/cookie/nsPermissionManager.cpp index 01de9bc62e8a..123b6ac9d26f 100644 --- a/extensions/cookie/nsPermissionManager.cpp +++ b/extensions/cookie/nsPermissionManager.cpp @@ -2156,6 +2156,25 @@ nsPermissionManager::RemoveByType(const nsACString& aType) { }); } +NS_IMETHODIMP +nsPermissionManager::RemoveByTypeSince(const nsACString& aType, + int64_t aModificationTime) { + ENSURE_NOT_CHILD_PROCESS; + + int32_t typeIndex = GetTypeIndex(aType, false); + // If type == -1, the type isn't known, + // so just return NS_OK + if (typeIndex == -1) { + return NS_OK; + } + + return RemovePermissionEntries( + [typeIndex, aModificationTime](const PermissionEntry& aPermEntry) { + return uint32_t(typeIndex) == aPermEntry.mType && + aModificationTime <= aPermEntry.mModificationTime; + }); +} + void nsPermissionManager::CloseDB(bool aRebuildOnSuccess) { // Null the statements, this will finalize them. mStmtInsert = nullptr; diff --git a/extensions/cookie/test/unit/test_permmanager_removebytypesince.js b/extensions/cookie/test/unit/test_permmanager_removebytypesince.js new file mode 100644 index 000000000000..cd2a837058dc --- /dev/null +++ b/extensions/cookie/test/unit/test_permmanager_removebytypesince.js @@ -0,0 +1,91 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +add_task(async function test() { + Services.prefs.setCharPref("permissions.manager.defaultsUrl", ""); + + // initialize the permission manager service + let pm = Cc["@mozilla.org/permissionmanager;1"].getService(Ci.nsIPermissionManager); + + Assert.equal(perm_count(), 0); + + // add some permissions + let uri = Services.io.newURI("http://amazon.com:8080/foobarbaz"); + let uri2 = Services.io.newURI("http://google.com:2048/quxx"); + let uri3 = Services.io.newURI("https://google.com/search"); + + pm.add(uri, "apple", 3); + pm.add(uri, "pear", 1); + pm.add(uri, "cucumber", 1); + + // sleep briefly, then record the time - we'll remove some permissions since then. + await new Promise(resolve => do_timeout(20, resolve)); + + let since = Date.now(); + + // *sob* - on Windows at least, the now recorded by nsPermissionManager.cpp + // might be a couple of ms *earlier* than what JS sees. So another sleep + // to ensure our |since| is greater than the time of the permissions we + // are now adding. Sadly this means we'll never be able to test when since + // exactly equals the modTime, but there you go... + await new Promise(resolve => do_timeout(20, resolve)); + + pm.add(uri2, "apple", 2); + pm.add(uri2, "pear", 2); + + pm.add(uri3, "cucumber", 3); + pm.add(uri3, "apple", 1); + + Assert.equal(perm_count(), 7); + + pm.removeByTypeSince("apple", since); + + Assert.equal(perm_count(), 5); + + Assert.equal(pm.testPermission(uri, "pear"), 1); + Assert.equal(pm.testPermission(uri2, "pear"), 2); + + Assert.equal(pm.testPermission(uri, "apple"), 3); + Assert.equal(pm.testPermission(uri2, "apple"), 0); + Assert.equal(pm.testPermission(uri3, "apple"), 0); + + Assert.equal(pm.testPermission(uri, "cucumber"), 1); + Assert.equal(pm.testPermission(uri3, "cucumber"), 3); + + pm.removeByTypeSince("cucumber", since); + Assert.equal(perm_count(), 4); + + Assert.equal(pm.testPermission(uri, "pear"), 1); + Assert.equal(pm.testPermission(uri2, "pear"), 2); + + Assert.equal(pm.testPermission(uri, "apple"), 3); + Assert.equal(pm.testPermission(uri2, "apple"), 0); + Assert.equal(pm.testPermission(uri3, "apple"), 0); + + Assert.equal(pm.testPermission(uri, "cucumber"), 1); + Assert.equal(pm.testPermission(uri3, "cucumber"), 0); + + pm.removeByTypeSince("pear", since); + Assert.equal(perm_count(), 3); + + Assert.equal(pm.testPermission(uri, "pear"), 1); + Assert.equal(pm.testPermission(uri2, "pear"), 0); + + Assert.equal(pm.testPermission(uri, "apple"), 3); + Assert.equal(pm.testPermission(uri2, "apple"), 0); + Assert.equal(pm.testPermission(uri3, "apple"), 0); + + Assert.equal(pm.testPermission(uri, "cucumber"), 1); + Assert.equal(pm.testPermission(uri3, "cucumber"), 0); + + function perm_count() { + let enumerator = pm.enumerator; + let count = 0; + while (enumerator.hasMoreElements()) { + count++; + enumerator.getNext(); + } + + return count; + } +}); diff --git a/extensions/cookie/test/unit/xpcshell.ini b/extensions/cookie/test/unit/xpcshell.ini index 87fb30cff86b..edbd241b1753 100644 --- a/extensions/cookie/test/unit/xpcshell.ini +++ b/extensions/cookie/test/unit/xpcshell.ini @@ -26,6 +26,7 @@ skip-if = true # Bug 863738 [test_permmanager_notifications.js] [test_permmanager_removeall.js] [test_permmanager_removebytype.js] +[test_permmanager_removebytypesince.js] [test_permmanager_removesince.js] [test_permmanager_removeforapp.js] [test_permmanager_load_invalid_entries.js] diff --git a/netwerk/base/nsIPermissionManager.idl b/netwerk/base/nsIPermissionManager.idl index beae6d0ebfb2..9367b9cdd97c 100644 --- a/netwerk/base/nsIPermissionManager.idl +++ b/netwerk/base/nsIPermissionManager.idl @@ -190,6 +190,14 @@ interface nsIPermissionManager : nsISupports */ void removeByType(in ACString type); + /** + * Clear all permissions of the passed type added since the specified time. + * @param type a case-sensitive ASCII string, identifying the permission. + * @param since a unix timestamp representing the number of milliseconds from + * Jan 1, 1970 00:00:00 UTC. + */ + void removeByTypeSince(in ACString type, in int64_t since); + /** * Test whether a website has permission to perform the given action. * This function will perform a pref lookup to permissions.default. diff --git a/toolkit/components/cleardata/ClearDataService.jsm b/toolkit/components/cleardata/ClearDataService.jsm index 7a2cb7d27caa..37efcaa2425a 100644 --- a/toolkit/components/cleardata/ClearDataService.jsm +++ b/toolkit/components/cleardata/ClearDataService.jsm @@ -516,6 +516,45 @@ const PushNotificationsCleaner = { }, }; +const StorageAccessCleaner = { + deleteByHost(aHost, aOriginAttributes) { + return new Promise(aResolve => { + for (let perm of Services.perms.enumerator) { + if (perm.type == "storageAccessAPI") { + let toBeRemoved = false; + try { + toBeRemoved = Services.eTLD.hasRootDomain(perm.principal.URI.host, + aHost); + } catch (ex) { + continue; + } + if (!toBeRemoved) { + continue; + } + + try { + Services.perms.removePermission(perm); + } catch (ex) { + Cu.reportError(ex); + } + } + } + + aResolve(); + }); + }, + + deleteByRange(aFrom, aTo) { + Services.perms.removeByTypeSince("storageAccessAPI", aFrom / 1000); + return Promise.resolve(); + }, + + deleteAll() { + Services.perms.removeByType("storageAccessAPI"); + return Promise.resolve(); + }, +}; + const HistoryCleaner = { deleteByHost(aHost, aOriginAttributes) { return PlacesUtils.history.removeByFilter({ host: "." + aHost }); @@ -796,6 +835,9 @@ const FLAGS_MAP = [ { flag: Ci.nsIClearDataService.CLEAR_REPORTS, cleaner: ReportsCleaner }, + + { flag: Ci.nsIClearDataService.CLEAR_STORAGE_ACCESS, + cleaner: StorageAccessCleaner }, ]; this.ClearDataService = function() { diff --git a/toolkit/components/cleardata/nsIClearDataService.idl b/toolkit/components/cleardata/nsIClearDataService.idl index 29adc3f83dac..1057c482593b 100644 --- a/toolkit/components/cleardata/nsIClearDataService.idl +++ b/toolkit/components/cleardata/nsIClearDataService.idl @@ -189,6 +189,11 @@ interface nsIClearDataService : nsISupports */ const uint32_t CLEAR_REPORTS = 1 << 19; + /** + * StorageAccessAPI flag, which indicates user interaction. + */ + const uint32_t CLEAR_STORAGE_ACCESS = 1 << 20; + /** * Use this value to delete all the data. */ diff --git a/toolkit/components/cleardata/tests/unit/test_storage_permission.js b/toolkit/components/cleardata/tests/unit/test_storage_permission.js new file mode 100644 index 000000000000..1b53606aadd2 --- /dev/null +++ b/toolkit/components/cleardata/tests/unit/test_storage_permission.js @@ -0,0 +1,65 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Tests for permissions + */ + +"use strict"; + +const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm"); + +// Test that only the storageAccessAPI gets removed. +add_task(async function test_removing_storage_permission() { + const uri = Services.io.newURI("https://example.net"); + const principal = Services.scriptSecurityManager.createCodebasePrincipal(uri, {}); + + Services.perms.addFromPrincipal(principal, "storageAccessAPI", Services.perms.ALLOW_ACTION); + Services.perms.addFromPrincipal(principal, "cookie", Services.perms.ALLOW_ACTION); + + Assert.equal(Services.perms.testExactPermissionFromPrincipal(principal, "storageAccessAPI"), Services.perms.ALLOW_ACTION, "There is a storageAccessAPI permission set"); + + await new Promise(aResolve => { + Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_STORAGE_ACCESS, value => { + Assert.equal(value, 0); + aResolve(); + }); + }); + + Assert.equal(Services.perms.testExactPermissionFromPrincipal(principal, "storageAccessAPI"), Services.perms.UNKNOWN_ACTION, "the storageAccessAPI permission has been removed"); + Assert.equal(Services.perms.testExactPermissionFromPrincipal(principal, "cookie"), Services.perms.ALLOW_ACTION, "the cookie permission has not been removed"); + + await new Promise(aResolve => { + Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_PERMISSIONS, value => aResolve()); + }); +}); + +// Test that the storageAccessAPI gets removed from a particular principal +add_task(async function test_removing_storage_permission_from_principal() { + const uri = Services.io.newURI("https://example.net"); + const principal = Services.scriptSecurityManager.createCodebasePrincipal(uri, {}); + + const anotherUri = Services.io.newURI("https://example.com"); + const anotherPrincipal = Services.scriptSecurityManager.createCodebasePrincipal(anotherUri, {}); + + Services.perms.addFromPrincipal(principal, "storageAccessAPI", Services.perms.ALLOW_ACTION); + Services.perms.addFromPrincipal(anotherPrincipal, "storageAccessAPI", Services.perms.ALLOW_ACTION); + Assert.equal(Services.perms.testExactPermissionFromPrincipal(principal, "storageAccessAPI"), Services.perms.ALLOW_ACTION, "storageAccessAPI permission has been added to the first principal"); + Assert.equal(Services.perms.testExactPermissionFromPrincipal(anotherPrincipal, "storageAccessAPI"), Services.perms.ALLOW_ACTION, "storageAccessAPI permission has been added to the second principal"); + + await new Promise(aResolve => { + Services.clearData.deleteDataFromPrincipal(principal, true /* user request */, + Ci.nsIClearDataService.CLEAR_STORAGE_ACCESS, value => { + Assert.equal(value, 0); + aResolve(); + }); + }); + + Assert.equal(Services.perms.testExactPermissionFromPrincipal(principal, "storageAccessAPI"), Services.perms.UNKNOWN_ACTION, "storageAccessAPI permission has been removed from the first principal"); + Assert.equal(Services.perms.testExactPermissionFromPrincipal(anotherPrincipal, "storageAccessAPI"), Services.perms.ALLOW_ACTION, "storageAccessAPI permission has not been removed from the second principal"); + + await new Promise(aResolve => { + Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_PERMISSIONS, value => aResolve()); + }); +}); + diff --git a/toolkit/components/cleardata/tests/unit/xpcshell.ini b/toolkit/components/cleardata/tests/unit/xpcshell.ini index ba49716fc78b..8ee4c842e67b 100644 --- a/toolkit/components/cleardata/tests/unit/xpcshell.ini +++ b/toolkit/components/cleardata/tests/unit/xpcshell.ini @@ -8,3 +8,4 @@ support-files = [test_downloads.js] [test_passwords.js] [test_permissions.js] +[test_storage_permission.js]