michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this file, michael@0: * You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: "use strict"; michael@0: michael@0: const Ci = Components.interfaces; michael@0: const Cu = Components.utils; michael@0: michael@0: this.EXPORTED_SYMBOLS = [ michael@0: "PermissionsTable", michael@0: "PermissionsReverseTable", michael@0: "expandPermissions", michael@0: "appendAccessToPermName", michael@0: "isExplicitInPermissionsTable" michael@0: ]; michael@0: michael@0: // Permission access flags michael@0: const READONLY = "readonly"; michael@0: const CREATEONLY = "createonly"; michael@0: const READCREATE = "readcreate"; michael@0: const READWRITE = "readwrite"; michael@0: michael@0: const UNKNOWN_ACTION = Ci.nsIPermissionManager.UNKNOWN_ACTION; michael@0: const ALLOW_ACTION = Ci.nsIPermissionManager.ALLOW_ACTION; michael@0: const DENY_ACTION = Ci.nsIPermissionManager.DENY_ACTION; michael@0: const PROMPT_ACTION = Ci.nsIPermissionManager.PROMPT_ACTION; michael@0: michael@0: // Permissions Matrix: https://docs.google.com/spreadsheet/ccc?key=0Akyz_Bqjgf5pdENVekxYRjBTX0dCXzItMnRyUU1RQ0E#gid=0 michael@0: michael@0: // Permissions that are implicit: michael@0: // battery-status, network-information, vibration, michael@0: // device-capabilities michael@0: michael@0: this.PermissionsTable = { geolocation: { michael@0: app: PROMPT_ACTION, michael@0: privileged: PROMPT_ACTION, michael@0: certified: PROMPT_ACTION michael@0: }, michael@0: camera: { michael@0: app: DENY_ACTION, michael@0: privileged: PROMPT_ACTION, michael@0: certified: ALLOW_ACTION michael@0: }, michael@0: alarms: { michael@0: app: ALLOW_ACTION, michael@0: privileged: ALLOW_ACTION, michael@0: certified: ALLOW_ACTION michael@0: }, michael@0: "tcp-socket": { michael@0: app: DENY_ACTION, michael@0: privileged: ALLOW_ACTION, michael@0: certified: ALLOW_ACTION michael@0: }, michael@0: "network-events": { michael@0: app: DENY_ACTION, michael@0: privileged: DENY_ACTION, michael@0: certified: ALLOW_ACTION michael@0: }, michael@0: contacts: { michael@0: app: DENY_ACTION, michael@0: privileged: PROMPT_ACTION, michael@0: certified: ALLOW_ACTION, michael@0: access: ["read", "write", "create"] michael@0: }, michael@0: "device-storage:apps": { michael@0: app: DENY_ACTION, michael@0: privileged: DENY_ACTION, michael@0: certified: ALLOW_ACTION, michael@0: access: ["read"] michael@0: }, michael@0: "device-storage:crashes": { michael@0: app: DENY_ACTION, michael@0: privileged: DENY_ACTION, michael@0: certified: ALLOW_ACTION, michael@0: access: ["read"] michael@0: }, michael@0: "device-storage:pictures": { michael@0: app: DENY_ACTION, michael@0: privileged: PROMPT_ACTION, michael@0: certified: ALLOW_ACTION, michael@0: access: ["read", "write", "create"] michael@0: }, michael@0: "device-storage:videos": { michael@0: app: DENY_ACTION, michael@0: privileged: PROMPT_ACTION, michael@0: certified: ALLOW_ACTION, michael@0: access: ["read", "write", "create"] michael@0: }, michael@0: "device-storage:music": { michael@0: app: DENY_ACTION, michael@0: privileged: PROMPT_ACTION, michael@0: certified: ALLOW_ACTION, michael@0: access: ["read", "write", "create"] michael@0: }, michael@0: "device-storage:sdcard": { michael@0: app: DENY_ACTION, michael@0: privileged: PROMPT_ACTION, michael@0: certified: ALLOW_ACTION, michael@0: access: ["read", "write", "create"] michael@0: }, michael@0: sms: { michael@0: app: DENY_ACTION, michael@0: privileged: DENY_ACTION, michael@0: certified: ALLOW_ACTION michael@0: }, michael@0: telephony: { michael@0: app: DENY_ACTION, michael@0: privileged: DENY_ACTION, michael@0: certified: ALLOW_ACTION michael@0: }, michael@0: browser: { michael@0: app: DENY_ACTION, michael@0: privileged: ALLOW_ACTION, michael@0: certified: ALLOW_ACTION michael@0: }, michael@0: bluetooth: { michael@0: app: DENY_ACTION, michael@0: privileged: DENY_ACTION, michael@0: certified: ALLOW_ACTION michael@0: }, michael@0: mobileconnection: { michael@0: app: DENY_ACTION, michael@0: privileged: DENY_ACTION, michael@0: certified: ALLOW_ACTION michael@0: }, michael@0: mobilenetwork: { michael@0: app: DENY_ACTION, michael@0: privileged: ALLOW_ACTION, michael@0: certified: ALLOW_ACTION michael@0: }, michael@0: power: { michael@0: app: DENY_ACTION, michael@0: privileged: DENY_ACTION, michael@0: certified: ALLOW_ACTION michael@0: }, michael@0: push: { michael@0: app: ALLOW_ACTION, michael@0: privileged: ALLOW_ACTION, michael@0: certified: ALLOW_ACTION michael@0: }, michael@0: settings: { michael@0: app: DENY_ACTION, michael@0: privileged: DENY_ACTION, michael@0: certified: ALLOW_ACTION, michael@0: access: ["read", "write"], michael@0: additional: ["indexedDB-chrome-settings"] michael@0: }, michael@0: permissions: { michael@0: app: DENY_ACTION, michael@0: privileged: DENY_ACTION, michael@0: certified: ALLOW_ACTION michael@0: }, michael@0: phonenumberservice: { michael@0: app: DENY_ACTION, michael@0: privileged: DENY_ACTION, michael@0: certified: ALLOW_ACTION michael@0: }, michael@0: fmradio: { michael@0: app: DENY_ACTION, michael@0: privileged: ALLOW_ACTION, michael@0: certified: ALLOW_ACTION michael@0: }, michael@0: attention: { michael@0: app: DENY_ACTION, michael@0: privileged: DENY_ACTION, michael@0: certified: ALLOW_ACTION michael@0: }, michael@0: "webapps-manage": { michael@0: app: DENY_ACTION, michael@0: privileged: DENY_ACTION, michael@0: certified: ALLOW_ACTION michael@0: }, michael@0: "backgroundservice": { michael@0: app: DENY_ACTION, michael@0: privileged: DENY_ACTION, michael@0: certified: ALLOW_ACTION michael@0: }, michael@0: "desktop-notification": { michael@0: app: ALLOW_ACTION, michael@0: privileged: ALLOW_ACTION, michael@0: certified: ALLOW_ACTION michael@0: }, michael@0: "networkstats-manage": { michael@0: app: DENY_ACTION, michael@0: privileged: DENY_ACTION, michael@0: certified: ALLOW_ACTION michael@0: }, michael@0: "wifi-manage": { michael@0: app: DENY_ACTION, michael@0: privileged: DENY_ACTION, michael@0: certified: ALLOW_ACTION michael@0: }, michael@0: "systemXHR": { michael@0: app: DENY_ACTION, michael@0: privileged: ALLOW_ACTION, michael@0: certified: ALLOW_ACTION michael@0: }, michael@0: "voicemail": { michael@0: app: DENY_ACTION, michael@0: privileged: DENY_ACTION, michael@0: certified: ALLOW_ACTION michael@0: }, michael@0: "deprecated-hwvideo": { michael@0: app: DENY_ACTION, michael@0: privileged: DENY_ACTION, michael@0: certified: ALLOW_ACTION michael@0: }, michael@0: "idle": { michael@0: app: DENY_ACTION, michael@0: privileged: DENY_ACTION, michael@0: certified: ALLOW_ACTION michael@0: }, michael@0: "time": { michael@0: app: DENY_ACTION, michael@0: privileged: DENY_ACTION, michael@0: certified: ALLOW_ACTION michael@0: }, michael@0: "embed-apps": { michael@0: app: DENY_ACTION, michael@0: privileged: DENY_ACTION, michael@0: certified: ALLOW_ACTION michael@0: }, michael@0: "storage": { michael@0: app: ALLOW_ACTION, michael@0: privileged: ALLOW_ACTION, michael@0: certified: ALLOW_ACTION, michael@0: substitute: [ michael@0: "indexedDB-unlimited", michael@0: "default-persistent-storage" michael@0: ] michael@0: }, michael@0: "background-sensors": { michael@0: app: DENY_ACTION, michael@0: privileged: DENY_ACTION, michael@0: certified: ALLOW_ACTION michael@0: }, michael@0: cellbroadcast: { michael@0: app: DENY_ACTION, michael@0: privileged: DENY_ACTION, michael@0: certified: ALLOW_ACTION michael@0: }, michael@0: "audio-channel-normal": { michael@0: app: ALLOW_ACTION, michael@0: privileged: ALLOW_ACTION, michael@0: certified: ALLOW_ACTION michael@0: }, michael@0: "audio-channel-content": { michael@0: app: ALLOW_ACTION, michael@0: privileged: ALLOW_ACTION, michael@0: certified: ALLOW_ACTION michael@0: }, michael@0: "audio-channel-notification": { michael@0: app: DENY_ACTION, michael@0: privileged: ALLOW_ACTION, michael@0: certified: ALLOW_ACTION michael@0: }, michael@0: "audio-channel-alarm": { michael@0: app: DENY_ACTION, michael@0: privileged: ALLOW_ACTION, michael@0: certified: ALLOW_ACTION michael@0: }, michael@0: "audio-channel-telephony": { michael@0: app: DENY_ACTION, michael@0: privileged: DENY_ACTION, michael@0: certified: ALLOW_ACTION michael@0: }, michael@0: "audio-channel-ringer": { michael@0: app: DENY_ACTION, michael@0: privileged: DENY_ACTION, michael@0: certified: ALLOW_ACTION michael@0: }, michael@0: "audio-channel-publicnotification": { michael@0: app: DENY_ACTION, michael@0: privileged: DENY_ACTION, michael@0: certified: ALLOW_ACTION michael@0: }, michael@0: "open-remote-window": { michael@0: app: DENY_ACTION, michael@0: privileged: DENY_ACTION, michael@0: certified: ALLOW_ACTION michael@0: }, michael@0: "input": { michael@0: app: DENY_ACTION, michael@0: privileged: ALLOW_ACTION, michael@0: certified: ALLOW_ACTION michael@0: }, michael@0: "input-manage": { michael@0: app: DENY_ACTION, michael@0: privileged: DENY_ACTION, michael@0: certified: ALLOW_ACTION michael@0: }, michael@0: "wappush": { michael@0: app: DENY_ACTION, michael@0: privileged: DENY_ACTION, michael@0: certified: ALLOW_ACTION michael@0: }, michael@0: "audio-capture": { michael@0: app: PROMPT_ACTION, michael@0: privileged: PROMPT_ACTION, michael@0: certified: PROMPT_ACTION michael@0: }, michael@0: "nfc": { michael@0: app: DENY_ACTION, michael@0: privileged: DENY_ACTION, michael@0: certified: ALLOW_ACTION, michael@0: access: ["read", "write"] michael@0: }, michael@0: "nfc-manager": { michael@0: app: DENY_ACTION, michael@0: privileged: DENY_ACTION, michael@0: certified: ALLOW_ACTION michael@0: }, michael@0: "speaker-control": { michael@0: app: DENY_ACTION, michael@0: privileged: ALLOW_ACTION, michael@0: certified: ALLOW_ACTION michael@0: }, michael@0: "downloads": { michael@0: app: DENY_ACTION, michael@0: privileged: DENY_ACTION, michael@0: certified: ALLOW_ACTION michael@0: }, michael@0: "video-capture": { michael@0: app: PROMPT_ACTION, michael@0: privileged: PROMPT_ACTION, michael@0: certified: PROMPT_ACTION michael@0: }, michael@0: }; michael@0: michael@0: /** michael@0: * Append access modes to the permission name as suffixes. michael@0: * e.g. permission name 'contacts' with ['read', 'write'] = michael@0: * ['contacts-read', contacts-write'] michael@0: * @param string aPermName michael@0: * @param array aAccess michael@0: * @returns array containing access-appended permission names. michael@0: **/ michael@0: this.appendAccessToPermName = function appendAccessToPermName(aPermName, aAccess) { michael@0: if (aAccess.length == 0) { michael@0: return [aPermName]; michael@0: } michael@0: return aAccess.map(function(aMode) { michael@0: return aPermName + "-" + aMode; michael@0: }); michael@0: }; michael@0: michael@0: /** michael@0: * Expand an access string into multiple permission names, michael@0: * e.g: permission name 'contacts' with 'readwrite' = michael@0: * ['contacts-read', 'contacts-create', 'contacts-write'] michael@0: * @param string aPermName michael@0: * @param string aAccess (optional) michael@0: * @returns array containing expanded permission names. michael@0: **/ michael@0: this.expandPermissions = function expandPermissions(aPermName, aAccess) { michael@0: if (!PermissionsTable[aPermName]) { michael@0: let errorMsg = michael@0: "PermissionsTable.jsm: expandPermissions: Unknown Permission: " + aPermName; michael@0: Cu.reportError(errorMsg); michael@0: dump(errorMsg); michael@0: return []; michael@0: } michael@0: michael@0: const tableEntry = PermissionsTable[aPermName]; michael@0: michael@0: if (tableEntry.substitute && tableEntry.additional) { michael@0: let errorMsg = michael@0: "PermissionsTable.jsm: expandPermissions: Can't handle both 'substitute' " + michael@0: "and 'additional' entries for permission: " + aPermName; michael@0: Cu.reportError(errorMsg); michael@0: dump(errorMsg); michael@0: return []; michael@0: } michael@0: michael@0: if (!aAccess && tableEntry.access || michael@0: aAccess && !tableEntry.access) { michael@0: let errorMsg = michael@0: "PermissionsTable.jsm: expandPermissions: Invalid access for permission " + michael@0: aPermName + ": " + aAccess + "\n"; michael@0: Cu.reportError(errorMsg); michael@0: dump(errorMsg); michael@0: return []; michael@0: } michael@0: michael@0: let expandedPermNames = []; michael@0: michael@0: if (tableEntry.access && aAccess) { michael@0: let requestedSuffixes = []; michael@0: switch (aAccess) { michael@0: case READONLY: michael@0: requestedSuffixes.push("read"); michael@0: break; michael@0: case CREATEONLY: michael@0: requestedSuffixes.push("create"); michael@0: break; michael@0: case READCREATE: michael@0: requestedSuffixes.push("read", "create"); michael@0: break; michael@0: case READWRITE: michael@0: requestedSuffixes.push("read", "create", "write"); michael@0: break; michael@0: default: michael@0: return []; michael@0: } michael@0: michael@0: let permArr = appendAccessToPermName(aPermName, requestedSuffixes); michael@0: michael@0: // Add the same suffix to each of the additions. michael@0: if (tableEntry.additional) { michael@0: for each (let additional in tableEntry.additional) { michael@0: permArr = permArr.concat(appendAccessToPermName(additional, requestedSuffixes)); michael@0: } michael@0: } michael@0: michael@0: // Only add the suffixed version if the suffix exists in the table. michael@0: for (let idx in permArr) { michael@0: let suffix = requestedSuffixes[idx % requestedSuffixes.length]; michael@0: if (tableEntry.access.indexOf(suffix) != -1) { michael@0: expandedPermNames.push(permArr[idx]); michael@0: } michael@0: } michael@0: } else if (tableEntry.substitute) { michael@0: expandedPermNames = expandedPermNames.concat(tableEntry.substitute); michael@0: } else { michael@0: expandedPermNames.push(aPermName); michael@0: // Include each of the additions exactly as they appear in the table. michael@0: if (tableEntry.additional) { michael@0: expandedPermNames = expandedPermNames.concat(tableEntry.additional); michael@0: } michael@0: } michael@0: michael@0: return expandedPermNames; michael@0: }; michael@0: michael@0: this.PermissionsReverseTable = (function () { michael@0: // PermissionsTable as it is works well for direct searches, but not michael@0: // so well for reverse ones (that is, if I get something like michael@0: // device-storage:music-read or indexedDB-chrome-settings-read how michael@0: // do I know which permission it really is? Hence this table is michael@0: // born. The idea is that michael@0: // reverseTable[device-storage:music-read] should return michael@0: // device-storage:music michael@0: let reverseTable = {}; michael@0: michael@0: for (let permName in PermissionsTable) { michael@0: let permAliases; michael@0: if (PermissionsTable[permName].access) { michael@0: permAliases = expandPermissions(permName, "readwrite"); michael@0: } else { michael@0: permAliases = expandPermissions(permName); michael@0: } michael@0: for (let i = 0; i < permAliases.length; i++) { michael@0: reverseTable[permAliases[i]] = permName; michael@0: } michael@0: } michael@0: michael@0: return reverseTable; michael@0: michael@0: })(); michael@0: michael@0: this.isExplicitInPermissionsTable = function(aPermName, aIntStatus) { michael@0: michael@0: // Check to see if the 'webapp' is app/privileged/certified. michael@0: let appStatus; michael@0: switch (aIntStatus) { michael@0: case Ci.nsIPrincipal.APP_STATUS_CERTIFIED: michael@0: appStatus = "certified"; michael@0: break; michael@0: case Ci.nsIPrincipal.APP_STATUS_PRIVILEGED: michael@0: appStatus = "privileged"; michael@0: break; michael@0: default: // If it isn't certified or privileged, it's app michael@0: appStatus = "app"; michael@0: break; michael@0: } michael@0: michael@0: let realPerm = PermissionsReverseTable[aPermName]; michael@0: michael@0: if (realPerm) { michael@0: return (PermissionsTable[realPerm][appStatus] == michael@0: Ci.nsIPermissionManager.PROMPT_ACTION); michael@0: } else { michael@0: return false; michael@0: } michael@0: }