Thu, 22 Jan 2015 13:21:57 +0100
Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6
michael@0 | 1 | /* This Source Code Form is subject to the terms of the Mozilla Public |
michael@0 | 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this file, |
michael@0 | 3 | * You can obtain one at http://mozilla.org/MPL/2.0/. */ |
michael@0 | 4 | |
michael@0 | 5 | "use strict"; |
michael@0 | 6 | |
michael@0 | 7 | const Ci = Components.interfaces; |
michael@0 | 8 | const Cu = Components.utils; |
michael@0 | 9 | |
michael@0 | 10 | Cu.import("resource://gre/modules/XPCOMUtils.jsm"); |
michael@0 | 11 | Cu.import("resource://gre/modules/Services.jsm"); |
michael@0 | 12 | Cu.import("resource://gre/modules/AppsUtils.jsm"); |
michael@0 | 13 | Cu.import("resource://gre/modules/PermissionsInstaller.jsm"); |
michael@0 | 14 | Cu.import("resource://gre/modules/PermissionsTable.jsm"); |
michael@0 | 15 | Cu.import("resource://gre/modules/PermissionSettings.jsm"); |
michael@0 | 16 | |
michael@0 | 17 | this.EXPORTED_SYMBOLS = ["SystemMessagePermissionsChecker", |
michael@0 | 18 | "SystemMessagePermissionsTable"]; |
michael@0 | 19 | |
michael@0 | 20 | function debug(aStr) { |
michael@0 | 21 | // dump("SystemMessagePermissionsChecker.jsm: " + aStr + "\n"); |
michael@0 | 22 | } |
michael@0 | 23 | |
michael@0 | 24 | // This table maps system message to permission(s), indicating only |
michael@0 | 25 | // the system messages granted by the page's permissions are allowed |
michael@0 | 26 | // to be registered or sent to that page. Note the empty permission |
michael@0 | 27 | // set means this type of system message is always permitted. |
michael@0 | 28 | |
michael@0 | 29 | this.SystemMessagePermissionsTable = { |
michael@0 | 30 | "activity": { }, |
michael@0 | 31 | "alarm": { |
michael@0 | 32 | "alarms": [] |
michael@0 | 33 | }, |
michael@0 | 34 | "bluetooth-dialer-command": { |
michael@0 | 35 | "telephony": [] |
michael@0 | 36 | }, |
michael@0 | 37 | "bluetooth-cancel": { |
michael@0 | 38 | "bluetooth": [] |
michael@0 | 39 | }, |
michael@0 | 40 | "bluetooth-hid-status-changed": { |
michael@0 | 41 | "bluetooth": [] |
michael@0 | 42 | }, |
michael@0 | 43 | "bluetooth-pairing-request": { |
michael@0 | 44 | "bluetooth": [] |
michael@0 | 45 | }, |
michael@0 | 46 | "bluetooth-opp-transfer-complete": { |
michael@0 | 47 | "bluetooth": [] |
michael@0 | 48 | }, |
michael@0 | 49 | "bluetooth-opp-update-progress": { |
michael@0 | 50 | "bluetooth": [] |
michael@0 | 51 | }, |
michael@0 | 52 | "bluetooth-opp-receiving-file-confirmation": { |
michael@0 | 53 | "bluetooth": [] |
michael@0 | 54 | }, |
michael@0 | 55 | "bluetooth-opp-transfer-start": { |
michael@0 | 56 | "bluetooth": [] |
michael@0 | 57 | }, |
michael@0 | 58 | "connection": { }, |
michael@0 | 59 | "dummy-system-message": { }, // for system message testing framework |
michael@0 | 60 | "headset-button": { }, |
michael@0 | 61 | "icc-stkcommand": { |
michael@0 | 62 | "settings": ["read", "write"] |
michael@0 | 63 | }, |
michael@0 | 64 | "media-button": { }, |
michael@0 | 65 | "networkstats-alarm": { |
michael@0 | 66 | "networkstats-manage": [] |
michael@0 | 67 | }, |
michael@0 | 68 | "notification": { |
michael@0 | 69 | "desktop-notification": [] |
michael@0 | 70 | }, |
michael@0 | 71 | "push": { |
michael@0 | 72 | "push": [] |
michael@0 | 73 | }, |
michael@0 | 74 | "push-register": { |
michael@0 | 75 | "push": [] |
michael@0 | 76 | }, |
michael@0 | 77 | "sms-delivery-success": { |
michael@0 | 78 | "sms": [] |
michael@0 | 79 | }, |
michael@0 | 80 | "sms-read-success": { |
michael@0 | 81 | "sms": [] |
michael@0 | 82 | }, |
michael@0 | 83 | "sms-received": { |
michael@0 | 84 | "sms": [] |
michael@0 | 85 | }, |
michael@0 | 86 | "sms-sent": { |
michael@0 | 87 | "sms": [] |
michael@0 | 88 | }, |
michael@0 | 89 | "telephony-new-call": { |
michael@0 | 90 | "telephony": [] |
michael@0 | 91 | }, |
michael@0 | 92 | "telephony-call-ended": { |
michael@0 | 93 | "telephony": [] |
michael@0 | 94 | }, |
michael@0 | 95 | "ussd-received": { |
michael@0 | 96 | "mobileconnection": [] |
michael@0 | 97 | }, |
michael@0 | 98 | "wappush-received": { |
michael@0 | 99 | "wappush": [] |
michael@0 | 100 | }, |
michael@0 | 101 | "cdma-info-rec-received": { |
michael@0 | 102 | "mobileconnection": [] |
michael@0 | 103 | }, |
michael@0 | 104 | "nfc-manager-tech-discovered": { |
michael@0 | 105 | "nfc-manager": [] |
michael@0 | 106 | }, |
michael@0 | 107 | "nfc-manager-tech-lost": { |
michael@0 | 108 | "nfc-manager": [] |
michael@0 | 109 | }, |
michael@0 | 110 | "nfc-manager-send-file": { |
michael@0 | 111 | "nfc-manager": [] |
michael@0 | 112 | }, |
michael@0 | 113 | "nfc-powerlevel-change": { |
michael@0 | 114 | "settings": ["read", "write"] |
michael@0 | 115 | }, |
michael@0 | 116 | "wifip2p-pairing-request": { }, |
michael@0 | 117 | "first-run-with-sim": { |
michael@0 | 118 | "settings": ["read", "write"] |
michael@0 | 119 | } |
michael@0 | 120 | }; |
michael@0 | 121 | |
michael@0 | 122 | this.SystemMessagePermissionsChecker = { |
michael@0 | 123 | /** |
michael@0 | 124 | * Return all the needed permission names for the given system message. |
michael@0 | 125 | * @param string aSysMsgName |
michael@0 | 126 | * The system messsage name. |
michael@0 | 127 | * @returns object |
michael@0 | 128 | * Format: { permName (string): permNamesWithAccess (string array), ... } |
michael@0 | 129 | * Ex, { "settings": ["settings-read", "settings-write"], ... }. |
michael@0 | 130 | * Note: an empty object will be returned if it's always permitted. |
michael@0 | 131 | * @returns null |
michael@0 | 132 | * Return and report error when any unexpected error is ecountered. |
michael@0 | 133 | * Ex, when the system message we want to search is not included. |
michael@0 | 134 | **/ |
michael@0 | 135 | getSystemMessagePermissions: function getSystemMessagePermissions(aSysMsgName) { |
michael@0 | 136 | debug("getSystemMessagePermissions(): aSysMsgName: " + aSysMsgName); |
michael@0 | 137 | |
michael@0 | 138 | let permNames = SystemMessagePermissionsTable[aSysMsgName]; |
michael@0 | 139 | if (permNames === undefined) { |
michael@0 | 140 | debug("'" + aSysMsgName + "' is not associated with permissions. " + |
michael@0 | 141 | "Please add them to the SystemMessagePermissionsTable."); |
michael@0 | 142 | return null; |
michael@0 | 143 | } |
michael@0 | 144 | |
michael@0 | 145 | let object = { }; |
michael@0 | 146 | for (let permName in permNames) { |
michael@0 | 147 | if (PermissionsTable[permName] === undefined) { |
michael@0 | 148 | debug("'" + permName + "' for '" + aSysMsgName + "' is invalid. " + |
michael@0 | 149 | "Please correct it in the SystemMessagePermissionsTable."); |
michael@0 | 150 | return null; |
michael@0 | 151 | } |
michael@0 | 152 | |
michael@0 | 153 | // Construct a new permission name array by adding the access suffixes. |
michael@0 | 154 | let access = permNames[permName]; |
michael@0 | 155 | if (!access || !Array.isArray(access)) { |
michael@0 | 156 | debug("'" + permName + "' is not associated with access array. " + |
michael@0 | 157 | "Please correct it in the SystemMessagePermissionsTable."); |
michael@0 | 158 | return null; |
michael@0 | 159 | } |
michael@0 | 160 | object[permName] = appendAccessToPermName(permName, access); |
michael@0 | 161 | } |
michael@0 | 162 | return object |
michael@0 | 163 | }, |
michael@0 | 164 | |
michael@0 | 165 | /** |
michael@0 | 166 | * Check if the system message is permitted to be registered for the given |
michael@0 | 167 | * app at start-up based on the permissions claimed in the app's manifest. |
michael@0 | 168 | * @param string aSysMsgName |
michael@0 | 169 | * The system messsage name. |
michael@0 | 170 | * @param string aOrigin |
michael@0 | 171 | * The app's origin. |
michael@0 | 172 | * @param object aManifest |
michael@0 | 173 | * The app's manifest. |
michael@0 | 174 | * @returns bool |
michael@0 | 175 | * Is permitted or not. |
michael@0 | 176 | **/ |
michael@0 | 177 | isSystemMessagePermittedToRegister: |
michael@0 | 178 | function isSystemMessagePermittedToRegister(aSysMsgName, aOrigin, aManifest) { |
michael@0 | 179 | debug("isSystemMessagePermittedToRegister(): " + |
michael@0 | 180 | "aSysMsgName: " + aSysMsgName + ", " + |
michael@0 | 181 | "aOrigin: " + aOrigin + ", " + |
michael@0 | 182 | "aManifest: " + JSON.stringify(aManifest)); |
michael@0 | 183 | |
michael@0 | 184 | let permNames = this.getSystemMessagePermissions(aSysMsgName); |
michael@0 | 185 | if (permNames === null) { |
michael@0 | 186 | return false; |
michael@0 | 187 | } |
michael@0 | 188 | |
michael@0 | 189 | // Check to see if the 'webapp' is app/privileged/certified. |
michael@0 | 190 | let appStatus; |
michael@0 | 191 | switch (AppsUtils.getAppManifestStatus(aManifest)) { |
michael@0 | 192 | case Ci.nsIPrincipal.APP_STATUS_CERTIFIED: |
michael@0 | 193 | appStatus = "certified"; |
michael@0 | 194 | break; |
michael@0 | 195 | case Ci.nsIPrincipal.APP_STATUS_PRIVILEGED: |
michael@0 | 196 | appStatus = "privileged"; |
michael@0 | 197 | break; |
michael@0 | 198 | case Ci.nsIPrincipal.APP_STATUS_INSTALLED: |
michael@0 | 199 | appStatus = "app"; |
michael@0 | 200 | break; |
michael@0 | 201 | default: |
michael@0 | 202 | throw new Error("SystemMessagePermissionsChecker.jsm: " + |
michael@0 | 203 | "Cannot decide the app's status. Install cancelled."); |
michael@0 | 204 | break; |
michael@0 | 205 | } |
michael@0 | 206 | |
michael@0 | 207 | let newManifest = new ManifestHelper(aManifest, aOrigin); |
michael@0 | 208 | |
michael@0 | 209 | for (let permName in permNames) { |
michael@0 | 210 | // The app doesn't claim valid permissions for this sytem message. |
michael@0 | 211 | if (!newManifest.permissions || !newManifest.permissions[permName]) { |
michael@0 | 212 | debug("'" + aSysMsgName + "' isn't permitted by '" + permName + "'. " + |
michael@0 | 213 | "Please add the permission for app: '" + aOrigin + "'."); |
michael@0 | 214 | return false; |
michael@0 | 215 | } |
michael@0 | 216 | let permValue = PermissionsTable[permName][appStatus]; |
michael@0 | 217 | if (permValue != Ci.nsIPermissionManager.PROMPT_ACTION && |
michael@0 | 218 | permValue != Ci.nsIPermissionManager.ALLOW_ACTION) { |
michael@0 | 219 | debug("'" + aSysMsgName + "' isn't permitted by '" + permName + "'. " + |
michael@0 | 220 | "Please add the permission for app: '" + aOrigin + "'."); |
michael@0 | 221 | return false; |
michael@0 | 222 | } |
michael@0 | 223 | |
michael@0 | 224 | // Compare the expanded permission names between the ones in |
michael@0 | 225 | // app's manifest and the ones needed for system message. |
michael@0 | 226 | let expandedPermNames = |
michael@0 | 227 | expandPermissions(permName, |
michael@0 | 228 | newManifest.permissions[permName].access); |
michael@0 | 229 | |
michael@0 | 230 | let permNamesWithAccess = permNames[permName]; |
michael@0 | 231 | |
michael@0 | 232 | // Early return false as soon as any permission is not matched. |
michael@0 | 233 | for (let idx in permNamesWithAccess) { |
michael@0 | 234 | let index = expandedPermNames.indexOf(permNamesWithAccess[idx]); |
michael@0 | 235 | if (index == -1) { |
michael@0 | 236 | debug("'" + aSysMsgName + "' isn't permitted by '" + permName + "'. " + |
michael@0 | 237 | "Please add the permission for app: '" + aOrigin + "'."); |
michael@0 | 238 | return false; |
michael@0 | 239 | } |
michael@0 | 240 | } |
michael@0 | 241 | } |
michael@0 | 242 | |
michael@0 | 243 | // All the permissions needed for this system message are matched. |
michael@0 | 244 | return true; |
michael@0 | 245 | }, |
michael@0 | 246 | |
michael@0 | 247 | /** |
michael@0 | 248 | * Check if the system message is permitted to be sent to the given |
michael@0 | 249 | * app's page at run-time based on the current app's permissions. |
michael@0 | 250 | * @param string aSysMsgName |
michael@0 | 251 | * The system messsage name. |
michael@0 | 252 | * @param string aPageURL |
michael@0 | 253 | * The app's page URL. |
michael@0 | 254 | * @param string aManifestURL |
michael@0 | 255 | * The app's manifest URL. |
michael@0 | 256 | * @returns bool |
michael@0 | 257 | * Is permitted or not. |
michael@0 | 258 | **/ |
michael@0 | 259 | isSystemMessagePermittedToSend: |
michael@0 | 260 | function isSystemMessagePermittedToSend(aSysMsgName, aPageURL, aManifestURL) { |
michael@0 | 261 | debug("isSystemMessagePermittedToSend(): " + |
michael@0 | 262 | "aSysMsgName: " + aSysMsgName + ", " + |
michael@0 | 263 | "aPageURL: " + aPageURL + ", " + |
michael@0 | 264 | "aManifestURL: " + aManifestURL); |
michael@0 | 265 | |
michael@0 | 266 | let permNames = this.getSystemMessagePermissions(aSysMsgName); |
michael@0 | 267 | if (permNames === null) { |
michael@0 | 268 | return false; |
michael@0 | 269 | } |
michael@0 | 270 | |
michael@0 | 271 | let pageURI = Services.io.newURI(aPageURL, null, null); |
michael@0 | 272 | for (let permName in permNames) { |
michael@0 | 273 | let permNamesWithAccess = permNames[permName]; |
michael@0 | 274 | |
michael@0 | 275 | // Early return false as soon as any permission is not matched. |
michael@0 | 276 | for (let idx in permNamesWithAccess) { |
michael@0 | 277 | if(PermissionSettingsModule.getPermission(permNamesWithAccess[idx], |
michael@0 | 278 | aManifestURL, |
michael@0 | 279 | pageURI.prePath, |
michael@0 | 280 | false) != "allow") { |
michael@0 | 281 | debug("'" + aSysMsgName + "' isn't permitted by '" + permName + "'. " + |
michael@0 | 282 | "Please add the permission for app: '" + pageURI.prePath + "'."); |
michael@0 | 283 | return false; |
michael@0 | 284 | } |
michael@0 | 285 | } |
michael@0 | 286 | } |
michael@0 | 287 | |
michael@0 | 288 | // All the permissions needed for this system message are matched. |
michael@0 | 289 | return true; |
michael@0 | 290 | } |
michael@0 | 291 | }; |