summaryrefslogtreecommitdiffstats
path: root/application/basilisk/modules/SitePermissions.jsm
diff options
context:
space:
mode:
Diffstat (limited to 'application/basilisk/modules/SitePermissions.jsm')
-rw-r--r--application/basilisk/modules/SitePermissions.jsm616
1 files changed, 616 insertions, 0 deletions
diff --git a/application/basilisk/modules/SitePermissions.jsm b/application/basilisk/modules/SitePermissions.jsm
new file mode 100644
index 000000000..4d5673ef4
--- /dev/null
+++ b/application/basilisk/modules/SitePermissions.jsm
@@ -0,0 +1,616 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+this.EXPORTED_SYMBOLS = [ "SitePermissions" ];
+
+Components.utils.import("resource://gre/modules/Services.jsm");
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+
+var gStringBundle =
+ Services.strings.createBundle("chrome://browser/locale/sitePermissions.properties");
+
+/**
+ * A helper module to manage temporarily blocked permissions.
+ *
+ * Permissions are keyed by browser, so methods take a Browser
+ * element to identify the corresponding permission set.
+ *
+ * This uses a WeakMap to key browsers, so that entries are
+ * automatically cleared once the browser stops existing
+ * (once there are no other references to the browser object);
+ */
+const TemporaryBlockedPermissions = {
+ // This is a three level deep map with the following structure:
+ //
+ // Browser => {
+ // <prePath>: {
+ // <permissionID>: {Number} <timeStamp>
+ // }
+ // }
+ //
+ // Only the top level browser elements are stored via WeakMap. The WeakMap
+ // value is an object with URI prePaths as keys. The keys of that object
+ // are ids that identify permissions that were set for the specific URI.
+ // The final value is an object containing the timestamp of when the permission
+ // was set (in order to invalidate after a certain amount of time has passed).
+ _stateByBrowser: new WeakMap(),
+
+ // Private helper method that bundles some shared behavior for
+ // get() and getAll(), e.g. deleting permissions when they have expired.
+ _get(entry, prePath, id, timeStamp) {
+ if (timeStamp == null) {
+ delete entry[prePath][id];
+ return null;
+ }
+ if (timeStamp + SitePermissions.temporaryPermissionExpireTime < Date.now()) {
+ delete entry[prePath][id];
+ return null;
+ }
+ return {id, state: SitePermissions.BLOCK, scope: SitePermissions.SCOPE_TEMPORARY};
+ },
+
+ // Sets a new permission for the specified browser.
+ set(browser, id) {
+ if (!browser) {
+ return;
+ }
+ if (!this._stateByBrowser.has(browser)) {
+ this._stateByBrowser.set(browser, {});
+ }
+ let entry = this._stateByBrowser.get(browser);
+ let prePath = browser.currentURI.prePath;
+ if (!entry[prePath]) {
+ entry[prePath] = {};
+ }
+ entry[prePath][id] = Date.now();
+ },
+
+ // Removes a permission with the specified id for the specified browser.
+ remove(browser, id) {
+ if (!browser) {
+ return;
+ }
+ let entry = this._stateByBrowser.get(browser);
+ let prePath = browser.currentURI.prePath;
+ if (entry && entry[prePath]) {
+ delete entry[prePath][id];
+ }
+ },
+
+ // Gets a permission with the specified id for the specified browser.
+ get(browser, id) {
+ if (!browser || !browser.currentURI) {
+ return null;
+ }
+ let entry = this._stateByBrowser.get(browser);
+ let prePath = browser.currentURI.prePath;
+ if (entry && entry[prePath]) {
+ let permission = entry[prePath][id];
+ return this._get(entry, prePath, id, permission);
+ }
+ return null;
+ },
+
+ // Gets all permissions for the specified browser.
+ // Note that only permissions that apply to the current URI
+ // of the passed browser element will be returned.
+ getAll(browser) {
+ let permissions = [];
+ let entry = this._stateByBrowser.get(browser);
+ let prePath = browser.currentURI.prePath;
+ if (entry && entry[prePath]) {
+ let timeStamps = entry[prePath];
+ for (let id of Object.keys(timeStamps)) {
+ let permission = this._get(entry, prePath, id, timeStamps[id]);
+ // _get() returns null when the permission has expired.
+ if (permission) {
+ permissions.push(permission);
+ }
+ }
+ }
+ return permissions;
+ },
+
+ // Clears all permissions for the specified browser.
+ // Unlike other methods, this does NOT clear only for
+ // the currentURI but the whole browser state.
+ clear(browser) {
+ this._stateByBrowser.delete(browser);
+ },
+
+ // Copies the temporary permission state of one browser
+ // into a new entry for the other browser.
+ copy(browser, newBrowser) {
+ let entry = this._stateByBrowser.get(browser);
+ if (entry) {
+ this._stateByBrowser.set(newBrowser, entry);
+ }
+ },
+};
+
+/**
+ * A module to manage permanent and temporary permissions
+ * by URI and browser.
+ *
+ * Some methods have the side effect of dispatching a "PermissionStateChange"
+ * event on changes to temporary permissions, as mentioned in the respective docs.
+ */
+this.SitePermissions = {
+ // Permission states.
+ UNKNOWN: Services.perms.UNKNOWN_ACTION,
+ ALLOW: Services.perms.ALLOW_ACTION,
+ BLOCK: Services.perms.DENY_ACTION,
+ ALLOW_COOKIES_FOR_SESSION: Components.interfaces.nsICookiePermission.ACCESS_SESSION,
+
+ // Permission scopes.
+ SCOPE_REQUEST: "{SitePermissions.SCOPE_REQUEST}",
+ SCOPE_TEMPORARY: "{SitePermissions.SCOPE_TEMPORARY}",
+ SCOPE_SESSION: "{SitePermissions.SCOPE_SESSION}",
+ SCOPE_PERSISTENT: "{SitePermissions.SCOPE_PERSISTENT}",
+
+ /**
+ * Gets all custom permissions for a given URI.
+ * Install addon permission is excluded, check bug 1303108.
+ *
+ * @return {Array} a list of objects with the keys:
+ * - id: the permissionId of the permission
+ * - scope: the scope of the permission (e.g. SitePermissions.SCOPE_TEMPORARY)
+ * - state: a constant representing the current permission state
+ * (e.g. SitePermissions.ALLOW)
+ */
+ getAllByURI(uri) {
+ let result = [];
+ if (!this.isSupportedURI(uri)) {
+ return result;
+ }
+
+ let permissions = Services.perms.getAllForURI(uri);
+ while (permissions.hasMoreElements()) {
+ let permission = permissions.getNext();
+
+ // filter out unknown permissions
+ if (gPermissionObject[permission.type]) {
+ // XXX Bug 1303108 - Control Center should only show non-default permissions
+ if (permission.type == "install") {
+ continue;
+ }
+ let scope = this.SCOPE_PERSISTENT;
+ if (permission.expireType == Services.perms.EXPIRE_SESSION) {
+ scope = this.SCOPE_SESSION;
+ }
+ result.push({
+ id: permission.type,
+ scope,
+ state: permission.capability,
+ });
+ }
+ }
+
+ return result;
+ },
+
+ /**
+ * Returns all custom permissions for a given browser.
+ *
+ * To receive a more detailed, albeit less performant listing see
+ * SitePermissions.getAllPermissionDetailsForBrowser().
+ *
+ * @param {Browser} browser
+ * The browser to fetch permission for.
+ *
+ * @return {Array} a list of objects with the keys:
+ * - id: the permissionId of the permission
+ * - state: a constant representing the current permission state
+ * (e.g. SitePermissions.ALLOW)
+ * - scope: a constant representing how long the permission will
+ * be kept.
+ */
+ getAllForBrowser(browser) {
+ let permissions = {};
+
+ for (let permission of TemporaryBlockedPermissions.getAll(browser)) {
+ permission.scope = this.SCOPE_TEMPORARY;
+ permissions[permission.id] = permission;
+ }
+
+ for (let permission of this.getAllByURI(browser.currentURI)) {
+ permissions[permission.id] = permission;
+ }
+
+ return Object.values(permissions);
+ },
+
+ /**
+ * Returns a list of objects with detailed information on all permissions
+ * that are currently set for the given browser.
+ *
+ * @param {Browser} browser
+ * The browser to fetch permission for.
+ *
+ * @return {Array<Object>} a list of objects with the keys:
+ * - id: the permissionID of the permission
+ * - state: a constant representing the current permission state
+ * (e.g. SitePermissions.ALLOW)
+ * - scope: a constant representing how long the permission will
+ * be kept.
+ * - label: the localized label
+ */
+ getAllPermissionDetailsForBrowser(browser) {
+ return this.getAllForBrowser(browser).map(({id, scope, state}) =>
+ ({id, scope, state, label: this.getPermissionLabel(id)}));
+ },
+
+ /**
+ * Checks whether a UI for managing permissions should be exposed for a given
+ * URI. This excludes file URIs, for instance, as they don't have a host,
+ * even though nsIPermissionManager can still handle them.
+ *
+ * @param {nsIURI} uri
+ * The URI to check.
+ *
+ * @return {boolean} if the URI is supported.
+ */
+ isSupportedURI(uri) {
+ return uri && (uri.schemeIs("http") || uri.schemeIs("https"));
+ },
+
+ /**
+ * Gets an array of all permission IDs.
+ *
+ * @return {Array<String>} an array of all permission IDs.
+ */
+ listPermissions() {
+ return Object.keys(gPermissionObject);
+ },
+
+ /**
+ * Returns an array of permission states to be exposed to the user for a
+ * permission with the given ID.
+ *
+ * @param {string} permissionID
+ * The ID to get permission states for.
+ *
+ * @return {Array<SitePermissions state>} an array of all permission states.
+ */
+ getAvailableStates(permissionID) {
+ if (permissionID in gPermissionObject &&
+ gPermissionObject[permissionID].states)
+ return gPermissionObject[permissionID].states;
+
+ if (this.getDefault(permissionID) == this.UNKNOWN)
+ return [ SitePermissions.UNKNOWN, SitePermissions.ALLOW, SitePermissions.BLOCK ];
+
+ return [ SitePermissions.ALLOW, SitePermissions.BLOCK ];
+ },
+
+ /**
+ * Returns the default state of a particular permission.
+ *
+ * @param {string} permissionID
+ * The ID to get the default for.
+ *
+ * @return {SitePermissions.state} the default state.
+ */
+ getDefault(permissionID) {
+ if (permissionID in gPermissionObject &&
+ gPermissionObject[permissionID].getDefault)
+ return gPermissionObject[permissionID].getDefault();
+
+ return this.UNKNOWN;
+ },
+
+ /**
+ * Returns the state and scope of a particular permission for a given URI.
+ *
+ * This method will NOT dispatch a "PermissionStateChange" event on the specified
+ * browser if a temporary permission was removed because it has expired.
+ *
+ * @param {nsIURI} uri
+ * The URI to check.
+ * @param {String} permissionID
+ * The id of the permission.
+ * @param {Browser} browser (optional)
+ * The browser object to check for temporary permissions.
+ *
+ * @return {Object} an object with the keys:
+ * - state: The current state of the permission
+ * (e.g. SitePermissions.ALLOW)
+ * - scope: The scope of the permission
+ * (e.g. SitePermissions.SCOPE_PERSISTENT)
+ */
+ get(uri, permissionID, browser) {
+ let result = { state: this.UNKNOWN, scope: this.SCOPE_PERSISTENT };
+ if (this.isSupportedURI(uri)) {
+ let permission = null;
+ if (permissionID in gPermissionObject &&
+ gPermissionObject[permissionID].exactHostMatch) {
+ permission = Services.perms.getPermissionObjectForURI(uri, permissionID, true);
+ } else {
+ permission = Services.perms.getPermissionObjectForURI(uri, permissionID, false);
+ }
+
+ if (permission) {
+ result.state = permission.capability;
+ if (permission.expireType == Services.perms.EXPIRE_SESSION) {
+ result.scope = this.SCOPE_SESSION;
+ }
+ }
+ }
+
+ if (!result.state) {
+ // If there's no persistent permission saved, check if we have something
+ // set temporarily.
+ let value = TemporaryBlockedPermissions.get(browser, permissionID);
+
+ if (value) {
+ result.state = value.state;
+ result.scope = this.SCOPE_TEMPORARY;
+ }
+ }
+
+ return result;
+ },
+
+ /**
+ * Sets the state of a particular permission for a given URI or browser.
+ * This method will dispatch a "PermissionStateChange" event on the specified
+ * browser if a temporary permission was set
+ *
+ * @param {nsIURI} uri
+ * The URI to set the permission for.
+ * Note that this will be ignored if the scope is set to SCOPE_TEMPORARY
+ * @param {String} permissionID
+ * The id of the permission.
+ * @param {SitePermissions state} state
+ * The state of the permission.
+ * @param {SitePermissions scope} scope (optional)
+ * The scope of the permission. Defaults to SCOPE_PERSISTENT.
+ * @param {Browser} browser (optional)
+ * The browser object to set temporary permissions on.
+ * This needs to be provided if the scope is SCOPE_TEMPORARY!
+ */
+ set(uri, permissionID, state, scope = this.SCOPE_PERSISTENT, browser = null) {
+ if (state == this.UNKNOWN) {
+ this.remove(uri, permissionID, browser);
+ return;
+ }
+
+ if (state == this.ALLOW_COOKIES_FOR_SESSION && permissionID != "cookie") {
+ throw "ALLOW_COOKIES_FOR_SESSION can only be set on the cookie permission";
+ }
+
+ // Save temporary permissions.
+ if (scope == this.SCOPE_TEMPORARY) {
+ // We do not support setting temp ALLOW for security reasons.
+ // In its current state, this permission could be exploited by subframes
+ // on the same page. This is because for BLOCK we ignore the request
+ // URI and only consider the current browser URI, to avoid notification spamming.
+ //
+ // If you ever consider removing this line, you likely want to implement
+ // a more fine-grained TemporaryBlockedPermissions that temporarily blocks for the
+ // entire browser, but temporarily allows only for specific frames.
+ if (state != this.BLOCK) {
+ throw "'Block' is the only permission we can save temporarily on a browser";
+ }
+
+ if (!browser) {
+ throw "TEMPORARY scoped permissions require a browser object";
+ }
+
+ TemporaryBlockedPermissions.set(browser, permissionID);
+
+ browser.dispatchEvent(new browser.ownerGlobal
+ .CustomEvent("PermissionStateChange"));
+ } else if (this.isSupportedURI(uri)) {
+ let perms_scope = Services.perms.EXPIRE_NEVER;
+ if (scope == this.SCOPE_SESSION) {
+ perms_scope = Services.perms.EXPIRE_SESSION;
+ }
+
+ Services.perms.add(uri, permissionID, state, perms_scope);
+ }
+ },
+
+ /**
+ * Removes the saved state of a particular permission for a given URI and/or browser.
+ * This method will dispatch a "PermissionStateChange" event on the specified
+ * browser if a temporary permission was removed.
+ *
+ * @param {nsIURI} uri
+ * The URI to remove the permission for.
+ * @param {String} permissionID
+ * The id of the permission.
+ * @param {Browser} browser (optional)
+ * The browser object to remove temporary permissions on.
+ */
+ remove(uri, permissionID, browser) {
+ if (this.isSupportedURI(uri))
+ Services.perms.remove(uri, permissionID);
+
+ // TemporaryBlockedPermissions.get() deletes expired permissions automatically,
+ if (TemporaryBlockedPermissions.get(browser, permissionID)) {
+ // If it exists but has not expired, remove it explicitly.
+ TemporaryBlockedPermissions.remove(browser, permissionID);
+ // Send a PermissionStateChange event only if the permission hasn't expired.
+ browser.dispatchEvent(new browser.ownerGlobal
+ .CustomEvent("PermissionStateChange"));
+ }
+ },
+
+ /**
+ * Clears all permissions that were temporarily saved.
+ *
+ * @param {Browser} browser
+ * The browser object to clear.
+ */
+ clearTemporaryPermissions(browser) {
+ TemporaryBlockedPermissions.clear(browser);
+ },
+
+ /**
+ * Copy all permissions that were temporarily saved on one
+ * browser object to a new browser.
+ *
+ * @param {Browser} browser
+ * The browser object to copy from.
+ * @param {Browser} newBrowser
+ * The browser object to copy to.
+ */
+ copyTemporaryPermissions(browser, newBrowser) {
+ TemporaryBlockedPermissions.copy(browser, newBrowser);
+ },
+
+ /**
+ * Returns the localized label for the permission with the given ID, to be
+ * used in a UI for managing permissions.
+ *
+ * @param {string} permissionID
+ * The permission to get the label for.
+ *
+ * @return {String} the localized label.
+ */
+ getPermissionLabel(permissionID) {
+ let labelID = gPermissionObject[permissionID].labelID || permissionID;
+ return gStringBundle.GetStringFromName("permission." + labelID + ".label");
+ },
+
+ /**
+ * Returns the localized label for the given permission state, to be used in
+ * a UI for managing permissions.
+ *
+ * @param {SitePermissions state} state
+ * The state to get the label for.
+ *
+ * @return {String|null} the localized label or null if an
+ * unknown state was passed.
+ */
+ getMultichoiceStateLabel(state) {
+ switch (state) {
+ case this.UNKNOWN:
+ return gStringBundle.GetStringFromName("state.multichoice.alwaysAsk");
+ case this.ALLOW:
+ return gStringBundle.GetStringFromName("state.multichoice.allow");
+ case this.ALLOW_COOKIES_FOR_SESSION:
+ return gStringBundle.GetStringFromName("state.multichoice.allowForSession");
+ case this.BLOCK:
+ return gStringBundle.GetStringFromName("state.multichoice.block");
+ default:
+ return null;
+ }
+ },
+
+ /**
+ * Returns the localized label for a permission's current state.
+ *
+ * @param {SitePermissions state} state
+ * The state to get the label for.
+ * @param {SitePermissions scope} scope (optional)
+ * The scope to get the label for.
+ *
+ * @return {String|null} the localized label or null if an
+ * unknown state was passed.
+ */
+ getCurrentStateLabel(state, scope = null) {
+ switch (state) {
+ case this.ALLOW:
+ if (scope && scope != this.SCOPE_PERSISTENT)
+ return gStringBundle.GetStringFromName("state.current.allowedTemporarily");
+ return gStringBundle.GetStringFromName("state.current.allowed");
+ case this.ALLOW_COOKIES_FOR_SESSION:
+ return gStringBundle.GetStringFromName("state.current.allowedForSession");
+ case this.BLOCK:
+ if (scope && scope != this.SCOPE_PERSISTENT)
+ return gStringBundle.GetStringFromName("state.current.blockedTemporarily");
+ return gStringBundle.GetStringFromName("state.current.blocked");
+ default:
+ return null;
+ }
+ },
+};
+
+var gPermissionObject = {
+ /* Holds permission ID => options pairs.
+ *
+ * Supported options:
+ *
+ * - exactHostMatch
+ * Allows sub domains to have their own permissions.
+ * Defaults to false.
+ *
+ * - getDefault
+ * Called to get the permission's default state.
+ * Defaults to UNKNOWN, indicating that the user will be asked each time
+ * a page asks for that permissions.
+ *
+ * - labelID
+ * Use the given ID instead of the permission name for looking up strings.
+ * e.g. "desktop-notification2" to use permission.desktop-notification2.label
+ *
+ * - states
+ * Array of permission states to be exposed to the user.
+ * Defaults to ALLOW, BLOCK and the default state (see getDefault).
+ */
+
+ "image": {
+ getDefault() {
+ return Services.prefs.getIntPref("permissions.default.image") == 2 ?
+ SitePermissions.BLOCK : SitePermissions.ALLOW;
+ }
+ },
+
+ "cookie": {
+ states: [ SitePermissions.ALLOW, SitePermissions.ALLOW_COOKIES_FOR_SESSION, SitePermissions.BLOCK ],
+ getDefault() {
+ if (Services.prefs.getIntPref("network.cookie.cookieBehavior") == 2)
+ return SitePermissions.BLOCK;
+
+ if (Services.prefs.getIntPref("network.cookie.lifetimePolicy") == 2)
+ return SitePermissions.ALLOW_COOKIES_FOR_SESSION;
+
+ return SitePermissions.ALLOW;
+ }
+ },
+
+ "desktop-notification": {
+ exactHostMatch: true,
+ labelID: "desktop-notification2",
+ },
+
+ "camera": {
+ exactHostMatch: true,
+ },
+
+ "microphone": {
+ exactHostMatch: true,
+ },
+
+ "screen": {
+ exactHostMatch: true,
+ states: [ SitePermissions.UNKNOWN, SitePermissions.BLOCK ],
+ },
+
+ "popup": {
+ getDefault() {
+ return Services.prefs.getBoolPref("dom.disable_open_during_load") ?
+ SitePermissions.BLOCK : SitePermissions.ALLOW;
+ }
+ },
+
+ "install": {
+ getDefault() {
+ return Services.prefs.getBoolPref("xpinstall.whitelist.required") ?
+ SitePermissions.BLOCK : SitePermissions.ALLOW;
+ }
+ },
+
+ "geo": {
+ exactHostMatch: true
+ },
+
+ "indexedDB": {}
+};
+
+XPCOMUtils.defineLazyPreferenceGetter(SitePermissions, "temporaryPermissionExpireTime",
+ "privacy.temporary_permission_expire_time_ms", 3600 * 1000);
+