gecko-dev/dom/messages/SystemMessagePermissionsChecker.jsm

349 lines
11 KiB
JavaScript

/* 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/. */
"use strict";
const Ci = Components.interfaces;
const Cu = Components.utils;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/AppsUtils.jsm");
Cu.import("resource://gre/modules/PermissionsInstaller.jsm");
Cu.import("resource://gre/modules/PermissionsTable.jsm");
Cu.import("resource://gre/modules/PermissionSettings.jsm");
XPCOMUtils.defineLazyServiceGetter(this, "dataStoreService",
"@mozilla.org/datastore-service;1",
"nsIDataStoreService");
this.EXPORTED_SYMBOLS = ["SystemMessagePermissionsChecker",
"SystemMessagePermissionsTable"];
function debug(aStr) {
// dump("SystemMessagePermissionsChecker.jsm: " + aStr + "\n");
}
// This table maps system message to permission(s), indicating only
// the system messages granted by the page's permissions are allowed
// to be registered or sent to that page. Note the empty permission
// set means this type of system message is always permitted.
this.SystemMessagePermissionsTable = {
"activity": { },
"alarm": {
"alarms": []
},
"bluetooth-dialer-command": {
"telephony": []
},
"bluetooth-cancel": {
"bluetooth": []
},
"bluetooth-hid-status-changed": {
"bluetooth": []
},
"bluetooth-pairing-request": {
"bluetooth": []
},
"bluetooth-opp-transfer-complete": {
"bluetooth": []
},
"bluetooth-opp-update-progress": {
"bluetooth": []
},
"bluetooth-opp-receiving-file-confirmation": {
"bluetooth": []
},
"bluetooth-opp-transfer-start": {
"bluetooth": []
},
"cellbroadcast-received": {
"cellbroadcast": []
},
"connection": { },
"captive-portal": {
"wifi-manage": []
},
"dummy-system-message": { }, // for system message testing framework
"headset-button": { },
"icc-stkcommand": {
"settings": ["read", "write"]
},
"media-button": { },
"networkstats-alarm": {
"networkstats-manage": []
},
"notification": {
"desktop-notification": []
},
"push": {
"push": []
},
"push-register": {
"push": []
},
"request-sync": { },
"sms-delivery-success": {
"sms": []
},
"sms-read-success": {
"sms": []
},
"sms-received": {
"sms": []
},
"sms-sent": {
"sms": []
},
"telephony-new-call": {
"telephony": []
},
"telephony-call-ended": {
"telephony": []
},
"ussd-received": {
"mobileconnection": []
},
"wappush-received": {
"wappush": []
},
"cdma-info-rec-received": {
"mobileconnection": []
},
"nfc-hci-event-transaction": {
"nfc-hci-events": []
},
"nfc-manager-tech-discovered": {
"nfc-manager": []
},
"nfc-manager-tech-lost": {
"nfc-manager": []
},
"nfc-manager-send-file": {
"nfc-manager": []
},
"wifip2p-pairing-request": { },
"first-run-with-sim": {
"settings": ["read", "write"]
}
};
this.SystemMessagePermissionsChecker = {
/**
* Return all the needed permission names for the given system message.
* @param string aSysMsgName
* The system messsage name.
* @returns object
* Format: { permName (string): permNamesWithAccess (string array), ... }
* Ex, { "settings": ["settings-read", "settings-write"], ... }.
* Note: an empty object will be returned if it's always permitted.
* @returns null
* Return and report error when any unexpected error is ecountered.
* Ex, when the system message we want to search is not included.
**/
getSystemMessagePermissions: function getSystemMessagePermissions(aSysMsgName) {
debug("getSystemMessagePermissions(): aSysMsgName: " + aSysMsgName);
let permNames = SystemMessagePermissionsTable[aSysMsgName];
if (permNames === undefined) {
debug("'" + aSysMsgName + "' is not associated with permissions. " +
"Please add them to the SystemMessage[Prefix]PermissionsTable.");
return null;
}
let object = { };
for (let permName in permNames) {
if (PermissionsTable[permName] === undefined) {
debug("'" + permName + "' for '" + aSysMsgName + "' is invalid. " +
"Please correct it in the SystemMessage[Prefix]PermissionsTable.");
return null;
}
// Construct a new permission name array by adding the access suffixes.
let access = permNames[permName];
if (!access || !Array.isArray(access)) {
debug("'" + permName + "' is not associated with access array. " +
"Please correct it in the SystemMessage[Prefix]PermissionsTable.");
return null;
}
object[permName] = appendAccessToPermName(permName, access);
}
return object
},
/**
* Check if the message is a datastore-update message
* @param string aSysMsgName
* The system messsage name.
*/
isDataStoreSystemMessage: function(aSysMsgName) {
return aSysMsgName.indexOf('datastore-update-') === 0;
},
/**
* Check if this manifest can deliver this particular datastore message.
*/
canDeliverDataStoreSystemMessage: function(aSysMsgName, aManifestURL) {
let store = aSysMsgName.substr('datastore-update-'.length);
// Get all the manifest URLs of the apps which can access the datastore.
let manifestURLs = dataStoreService.getAppManifestURLsForDataStore(store);
let enumerate = manifestURLs.enumerate();
while (enumerate.hasMoreElements()) {
let manifestURL = enumerate.getNext().QueryInterface(Ci.nsISupportsString);
if (manifestURL == aManifestURL) {
return true;
}
}
return false;
},
/**
* Check if the system message is permitted to be registered for the given
* app at start-up based on the permissions claimed in the app's manifest.
* @param string aSysMsgName
* The system messsage name.
* @param string aManifestURL
* The app's manifest URL.
* @param object aManifest
* The app's manifest.
* @returns bool
* Is permitted or not.
**/
isSystemMessagePermittedToRegister:
function isSystemMessagePermittedToRegister(aSysMsgName,
aManifestURL,
aManifest) {
debug("isSystemMessagePermittedToRegister(): " +
"aSysMsgName: " + aSysMsgName + ", " +
"aManifestURL: " + aManifestURL + ", " +
"aManifest: " + JSON.stringify(aManifest));
if (this.isDataStoreSystemMessage(aSysMsgName) &&
this.canDeliverDataStoreSystemMessage(aSysMsgName, aManifestURL)) {
return true;
}
let permNames = this.getSystemMessagePermissions(aSysMsgName);
if (permNames === null) {
return false;
}
// Check to see if the 'webapp' is app/privileged/certified.
let appStatus;
switch (AppsUtils.getAppManifestStatus(aManifest)) {
case Ci.nsIPrincipal.APP_STATUS_CERTIFIED:
appStatus = "certified";
break;
case Ci.nsIPrincipal.APP_STATUS_PRIVILEGED:
appStatus = "privileged";
break;
case Ci.nsIPrincipal.APP_STATUS_INSTALLED:
appStatus = "app";
if (aManifest.type == "trusted") {
appStatus = "trusted";
}
break;
default:
throw new Error("SystemMessagePermissionsChecker.jsm: " +
"Cannot decide the app's status. Install cancelled.");
break;
}
// It's ok here to not pass the origin to ManifestHelper since we only
// need the permission property and that doesn't depend on uri resolution.
let newManifest = new ManifestHelper(aManifest, aManifestURL, aManifestURL);
for (let permName in permNames) {
// The app doesn't claim valid permissions for this sytem message.
if (!newManifest.permissions || !newManifest.permissions[permName]) {
debug("'" + aSysMsgName + "' isn't permitted by '" + permName + "'. " +
"Please add the permission for app: '" + aManifestURL + "'.");
return false;
}
let permValue = PermissionsTable[permName][appStatus];
if (permValue != Ci.nsIPermissionManager.PROMPT_ACTION &&
permValue != Ci.nsIPermissionManager.ALLOW_ACTION) {
debug("'" + aSysMsgName + "' isn't permitted by '" + permName + "'. " +
"Please add the permission for app: '" + aManifestURL + "'.");
return false;
}
// Compare the expanded permission names between the ones in
// app's manifest and the ones needed for system message.
let expandedPermNames =
expandPermissions(permName,
newManifest.permissions[permName].access);
let permNamesWithAccess = permNames[permName];
// Early return false as soon as any permission is not matched.
for (let idx in permNamesWithAccess) {
let index = expandedPermNames.indexOf(permNamesWithAccess[idx]);
if (index == -1) {
debug("'" + aSysMsgName + "' isn't permitted by '" + permName + "'. " +
"Please add the permission for app: '" + aOrigin + "'.");
return false;
}
}
}
// All the permissions needed for this system message are matched.
return true;
},
/**
* Check if the system message is permitted to be sent to the given
* app's page at run-time based on the current app's permissions.
* @param string aSysMsgName
* The system messsage name.
* @param string aPageURL
* The app's page URL.
* @param string aManifestURL
* The app's manifest URL.
* @returns bool
* Is permitted or not.
**/
isSystemMessagePermittedToSend:
function isSystemMessagePermittedToSend(aSysMsgName, aPageURL, aManifestURL) {
debug("isSystemMessagePermittedToSend(): " +
"aSysMsgName: " + aSysMsgName + ", " +
"aPageURL: " + aPageURL + ", " +
"aManifestURL: " + aManifestURL);
if (this.isDataStoreSystemMessage(aSysMsgName) &&
this.canDeliverDataStoreSystemMessage(aSysMsgName, aManifestURL)) {
return true;
}
let permNames = this.getSystemMessagePermissions(aSysMsgName);
if (permNames === null) {
return false;
}
let pageURI = Services.io.newURI(aPageURL, null, null);
for (let permName in permNames) {
let permNamesWithAccess = permNames[permName];
// Early return false as soon as any permission is not matched.
for (let idx in permNamesWithAccess) {
if(PermissionSettingsModule.getPermission(permNamesWithAccess[idx],
aManifestURL,
pageURI.prePath,
false) != "allow") {
debug("'" + aSysMsgName + "' isn't permitted by '" + permName + "'. " +
"Please add the permission for app: '" + pageURI.prePath + "'.");
return false;
}
}
}
// All the permissions needed for this system message are matched.
return true;
}
};