From 3873a0bfd00af27e1c09ceaa17454a0e4bc73e94 Mon Sep 17 00:00:00 2001 From: David Dahl Date: Wed, 26 Sep 2012 17:22:54 -0500 Subject: [PATCH] Bug 758269 - Install permissions from manifest to Permission Manager r=fabrice --- dom/apps/src/Makefile.in | 4 + dom/apps/src/PermissionsTable.jsm | 175 +++++++++++++ dom/apps/src/Webapps.js | 5 + dom/apps/src/Webapps.jsm | 237 ++++++++++++++++-- dom/permission/PermissionSettings.js | 10 +- dom/permission/PermissionSettings.jsm | 2 +- dom/tests/browser/Makefile.in | 6 + .../browser/browser_webapps_permissions.js | 156 ++++++++++++ .../browser_webapps_perms_reinstall.js | 183 ++++++++++++++ dom/tests/browser/test-webapp-original.webapp | 20 ++ .../browser/test-webapp-reinstall.webapp | 17 ++ dom/tests/browser/test-webapp.webapp | 20 ++ .../browser/test-webapps-permissions.html | 9 + 13 files changed, 815 insertions(+), 29 deletions(-) create mode 100644 dom/apps/src/PermissionsTable.jsm create mode 100644 dom/tests/browser/browser_webapps_permissions.js create mode 100644 dom/tests/browser/browser_webapps_perms_reinstall.js create mode 100644 dom/tests/browser/test-webapp-original.webapp create mode 100644 dom/tests/browser/test-webapp-reinstall.webapp create mode 100644 dom/tests/browser/test-webapp.webapp create mode 100644 dom/tests/browser/test-webapps-permissions.html diff --git a/dom/apps/src/Makefile.in b/dom/apps/src/Makefile.in index cd45b7b8853..b374b7fb5d1 100644 --- a/dom/apps/src/Makefile.in +++ b/dom/apps/src/Makefile.in @@ -25,4 +25,8 @@ EXTRA_PP_JS_MODULES += \ AppsUtils.jsm \ $(NULL) +EXTRA_JS_MODULES += \ + PermissionsTable.jsm \ + $(NULL) + include $(topsrcdir)/config/rules.mk diff --git a/dom/apps/src/PermissionsTable.jsm b/dom/apps/src/PermissionsTable.jsm new file mode 100644 index 00000000000..9dac9257c58 --- /dev/null +++ b/dom/apps/src/PermissionsTable.jsm @@ -0,0 +1,175 @@ +/* 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; + +var EXPORTED_SYMBOLS = ["PermissionsTable", + "UNKNOWN_ACTION", + "ALLOW_ACTION", + "DENY_ACTION", + "PROMPT_ACTION", + "AllPossiblePermissions", + "mapSuffixes", + ]; + +const UNKNOWN_ACTION = Ci.nsIPermissionManager.UNKNOWN_ACTION; +const ALLOW_ACTION = Ci.nsIPermissionManager.ALLOW_ACTION; +const DENY_ACTION = Ci.nsIPermissionManager.DENY_ACTION; +const PROMPT_ACTION = Ci.nsIPermissionManager.PROMPT_ACTION; + +/** + * Converts ['read', 'write'] to ['contacts-read', 'contacts-write'], etc... + * @param string aPermName + * @param Array aSuffixes + * @returns Array + **/ +function mapSuffixes(aPermName, aSuffixes) +{ + return aSuffixes.map(function(suf) { return aPermName + "-" + suf; }); +} + +// Permissions Matrix: https://docs.google.com/spreadsheet/ccc?key=0Akyz_Bqjgf5pdENVekxYRjBTX0dCXzItMnRyUU1RQ0E#gid=0 + +// Permissions that are implicit: +// battery-status, idle, network-information, vibration, +// device-capabilities, webapps-manage, web-activities + +const PermissionsTable = { "resource-lock": { + app: ALLOW_ACTION, + privileged: ALLOW_ACTION, + certified: ALLOW_ACTION + }, + geolocation: { + app: PROMPT_ACTION, + privileged: PROMPT_ACTION, + certified: ALLOW_ACTION + }, + camera: { + app: DENY_ACTION, + privileged: PROMPT_ACTION, + certified: ALLOW_ACTION + }, + alarm: { + app: ALLOW_ACTION, + privileged: ALLOW_ACTION, + certified: ALLOW_ACTION + }, + "network-tcp": { + app: DENY_ACTION, + privileged: ALLOW_ACTION, + certified: ALLOW_ACTION + }, + contacts: { + app: DENY_ACTION, + privileged: PROMPT_ACTION, + certified: ALLOW_ACTION, + access: ["read", + "write", + "create" + ] + }, + "device-storage:apps": { + app: DENY_ACTION, + privileged: ALLOW_ACTION, + certified: ALLOW_ACTION + }, + "device-storage:pictures": { + app: DENY_ACTION, + privileged: ALLOW_ACTION, + certified: ALLOW_ACTION + }, + "device-storage:videos": { + app: DENY_ACTION, + privileged: ALLOW_ACTION, + certified: ALLOW_ACTION + }, + "device-storage:music": { + app: DENY_ACTION, + privileged: ALLOW_ACTION, + certified: ALLOW_ACTION + }, + sms: { + app: DENY_ACTION, + privileged: DENY_ACTION, + certified: ALLOW_ACTION + }, + telephony: { + app: DENY_ACTION, + privileged: DENY_ACTION, + certified: ALLOW_ACTION + }, + browser: { + app: DENY_ACTION, + privileged: ALLOW_ACTION, + certified: ALLOW_ACTION + }, + bluetooth: { + app: DENY_ACTION, + privileged: DENY_ACTION, + certified: ALLOW_ACTION + }, + wifi: { + app: DENY_ACTION, + privileged: PROMPT_ACTION, + certified: ALLOW_ACTION + }, + keyboard: { + app: DENY_ACTION, + privileged: DENY_ACTION, + certified: ALLOW_ACTION + }, + mobileconnection: { + app: DENY_ACTION, + privileged: DENY_ACTION, + certified: ALLOW_ACTION + }, + power: { + app: DENY_ACTION, + privileged: DENY_ACTION, + certified: ALLOW_ACTION + }, + push: { + app: DENY_ACTION, + privileged: DENY_ACTION, + certified: ALLOW_ACTION + }, + settings: { + app: DENY_ACTION, + privileged: DENY_ACTION, + certified: ALLOW_ACTION, + access: ["read", + "write" + ], + }, + permissions: { + app: DENY_ACTION, + privileged: DENY_ACTION, + certified: ALLOW_ACTION + }, + fmradio: { + app: ALLOW_ACTION, // Matrix indicates '?' + privileged: ALLOW_ACTION, + certified: ALLOW_ACTION + }, + attention: { + app: DENY_ACTION, + privileged: DENY_ACTION, + certified: ALLOW_ACTION + }, + }; + +// Sometimes all permissions (fully expanded) need to be iterated through +let AllPossiblePermissions = []; +for (let permName in PermissionsTable) { + if (PermissionsTable[permName].access) { + AllPossiblePermissions = + AllPossiblePermissions.concat(mapSuffixes(permName, + PermissionsTable[permName].access)); + } + else { + AllPossiblePermissions.push(permName); + } +} diff --git a/dom/apps/src/Webapps.js b/dom/apps/src/Webapps.js index 29da8a91e27..ed09d597175 100644 --- a/dom/apps/src/Webapps.js +++ b/dom/apps/src/Webapps.js @@ -111,13 +111,16 @@ WebappsRegistry.prototype = { manifest = JSON.parse(xhr.responseText, installOrigin); } catch (e) { Services.DOMRequest.fireError(request, "MANIFEST_PARSE_ERROR"); + Cu.reportError("Error installing app from: " + installOrigin + ": " + "MANIFEST_PARSE_ERROR"); return; } if (!AppsUtils.checkManifest(manifest, installOrigin)) { Services.DOMRequest.fireError(request, "INVALID_MANIFEST"); + Cu.reportError("Error installing app from: " + installOrigin + ": " + "INVALID_MANIFEST"); } else if (!this.checkAppStatus(manifest)) { Services.DOMRequest.fireError(request, "INVALID_SECURITY_LEVEL"); + Cu.reportError("Error installing app, '" + manifest.name + "': " + "INVALID_SECURITY_LEVEL"); } else { let receipts = (aParams && aParams.receipts && Array.isArray(aParams.receipts)) ? aParams.receipts : []; let categories = (aParams && aParams.categories && Array.isArray(aParams.categories)) ? aParams.categories : []; @@ -133,11 +136,13 @@ WebappsRegistry.prototype = { } } else { Services.DOMRequest.fireError(request, "MANIFEST_URL_ERROR"); + Cu.reportError("Error installing app from: " + installOrigin + ": " + "MANIFEST_URL_ERROR"); } }).bind(this), false); xhr.addEventListener("error", (function() { Services.DOMRequest.fireError(request, "NETWORK_ERROR"); + Cu.reportError("Error installing app from: " + installOrigin + ": " + "NETWORK_ERROR"); }).bind(this), false); xhr.send(null); diff --git a/dom/apps/src/Webapps.jsm b/dom/apps/src/Webapps.jsm index 50be06a8cb7..926c6170604 100644 --- a/dom/apps/src/Webapps.jsm +++ b/dom/apps/src/Webapps.jsm @@ -4,6 +4,10 @@ "use strict"; +function debug(aMsg) { + // dump("-*- Webapps.jsm: " + aMsg + "\n"); +} + const Cu = Components.utils; const Cc = Components.classes; const Ci = Components.interfaces; @@ -16,14 +20,33 @@ Cu.import("resource://gre/modules/Services.jsm"); Cu.import("resource://gre/modules/FileUtils.jsm"); Cu.import('resource://gre/modules/ActivitiesService.jsm'); Cu.import("resource://gre/modules/AppsUtils.jsm"); +Cu.import("resource://gre/modules/PermissionsTable.jsm"); const WEBAPP_RUNTIME = Services.appinfo.ID == "webapprt@mozilla.org"; +// Permission access flags +const READONLY = "readonly"; +const CREATEONLY = "createonly"; +const READCREATE = "readcreate"; +const READWRITE = "readwrite"; + +const PERM_TO_STRING = ["unknown", "allow", "deny", "prompt"]; + +XPCOMUtils.defineLazyServiceGetter(this, + "PermSettings", + "@mozilla.org/permissionSettings;1", + "nsIDOMPermissionSettings"); + XPCOMUtils.defineLazyGetter(this, "NetUtil", function() { Cu.import("resource://gre/modules/NetUtil.jsm"); return NetUtil; }); +XPCOMUtils.defineLazyServiceGetter(this, + "permissionManager", + "@mozilla.org/permissionmanager;1", + "nsIPermissionManager"); + XPCOMUtils.defineLazyServiceGetter(this, "ppmm", "@mozilla.org/parentprocessmessagemanager;1", "nsIMessageBroadcaster"); @@ -48,6 +71,81 @@ XPCOMUtils.defineLazyGetter(this, "msgmgr", function() { const DIRECTORY_NAME = WEBAPP_RUNTIME ? "WebappRegD" : "ProfD"; #endif + +/** + * Determine the type of app (app, privileged, certified) + * that is installed by the manifest + * @param object aManifest + * @returns integer + **/ +function getAppManifestStatus(aManifest) +{ + let type = aManifest.type || "web"; + + switch(type) { + case "web": + return Ci.nsIPrincipal.APP_STATUS_INSTALLED; + case "privileged": + return Ci.nsIPrincipal.APP_STATUS_PRIVILEGED; + case "certified": + return Ci.nsIPrincipal.APP_STATUS_CERTIFIED; + default: + throw new Error("Webapps.jsm: Undetermined app manifest type"); + } +} + +/** + * Expand an access string into multiple permission names, + * e.g: perm 'contacts' with 'readwrite' = + * ['contacts-read', 'contacts-create', contacts-write'] + * @param string aPermName + * @param string aAccess + * @returns Array + **/ +function expandPermissions(aPermName, aAccess) +{ + if (!PermissionsTable[aPermName]) { + Cu.reportError("Unknown permission: " + aPermName); + throw new Error("Webapps.jsm: App install failed, Unknown Permission: " + aPermName); + } + if (!aAccess && PermissionsTable[aPermName].access || + aAccess && !PermissionsTable[aPermName].access) { + Cu.reportError("Webapps.jsm: installPermissions: Invalid Manifest"); + throw new Error("Webapps.jsm: App install failed, Invalid Manifest"); + } + if (!PermissionsTable[aPermName].access) { + return [aPermName]; + } + + let requestedSuffixes = []; + switch(aAccess) { + case READONLY: + requestedSuffixes.push("read"); + break; + case CREATEONLY: + requestedSuffixes.push("create"); + break; + case READCREATE: + requestedSuffixes.push("read", "create"); + break; + case READWRITE: + requestedSuffixes.push("read", "create", "write"); + break; + default: + return []; + } + + let permArr = mapSuffixes(aPermName, requestedSuffixes); + + let expandedPerms = []; + for (let idx in permArr) { + if (PermissionsTable[aPermName].access.indexOf(requestedSuffixes[idx]) != -1) { + expandedPerms.push(permArr[idx]); + } + } + return expandedPerms; +} + let DOMApplicationRegistry = { appsFile: null, webapps: { }, @@ -550,13 +648,16 @@ let DOMApplicationRegistry = { }, confirmInstall: function(aData, aFromSync, aProfileDir, aOfflineCacheObserver) { + let isReinstall = false; let app = aData.app; app.removable = true; let id = app.syncId || this._appId(app.origin); let localId = this.getAppLocalIdByManifestURL(app.manifestURL); + let manifest = new DOMApplicationManifest(app.manifest, app.origin); // Installing an application again is considered as an update. if (id) { + isReinstall = true; let dir = this._getAppDir(id); try { dir.remove(true); @@ -564,6 +665,7 @@ let DOMApplicationRegistry = { } } else { id = this.makeAppId(); + app.id = id; localId = this._nextLocalId(); } @@ -578,6 +680,7 @@ let DOMApplicationRegistry = { let appNote = JSON.stringify(appObject); appNote.id = id; + appObject.permissions = {}; appObject.localId = localId; appObject.basePath = FileUtils.getDir(DIRECTORY_NAME, ["webapps"], true, true).path; @@ -603,8 +706,7 @@ let DOMApplicationRegistry = { appObject.status = "installed"; appObject.name = app.manifest.name; - - let manifest = new DOMApplicationManifest(app.manifest, app.origin); + this.installPermissions(appObject, aData, isReinstall); if (!aFromSync) this._saveApps((function() { @@ -633,6 +735,107 @@ let DOMApplicationRegistry = { } }, + /** + * Install permissisions or remove deprecated permissions upon re-install + * @param object aAppObject + * The just installed AppUtils cloned appObject + * @param object aData + * The just-installed app configuration + * @param boolean aIsReinstall + * Indicates the app was just re-installed + * @returns void + **/ + installPermissions: + function installPermissions(aAppObject, aData, aIsReinstall) + { + try { + let newManifest = new DOMApplicationManifest(aData.app.manifest, + aData.app.origin); + if (!newManifest.permissions && !aIsReinstall) { + return; + } + + if (aIsReinstall) { + // Compare the original permissions against the new permissions + // Remove any deprecated Permissions + + if (newManifest.permissions) { + // Expand perms + let newPerms = []; + for (let perm in newManifest.permissions) { + let _perms = expandPermissions(perm, + newManifest.permissions[perm].access); + newPerms = newPerms.concat(_perms); + } + + for (let idx in AllPossiblePermissions) { + let index = newPerms.indexOf(AllPossiblePermissions[idx]); + if (index == -1) { + // See if the permission was installed previously + let _perm = PermSettings.get(AllPossiblePermissions[idx], + aData.app.manifestURL, + aData.app.origin, + false); + if (_perm == "unknown" || _perm == "deny") { + // All 'deny' permissions should be preserved + continue; + } + // Remove the deprecated permission + // TODO: use PermSettings.remove, see bug 793204 + PermSettings.set(AllPossiblePermissions[idx], + "unknown", + aData.app.manifestURL, + aData.app.origin, + false); + } + } + } + } + + let installPermType; + // Check to see if the 'webapp' is app/priv/certified + switch (getAppManifestStatus(newManifest)) { + case Ci.nsIPrincipal.APP_STATUS_CERTIFIED: + installPermType = "certified"; + break; + case Ci.nsIPrincipal.APP_STATUS_PRIVILEGED: + installPermType = "privileged"; + break; + case Ci.nsIPrincipal.APP_STATUS_INSTALLED: + installPermType = "app"; + break; + default: + // Cannot determine app type, abort install by throwing an error + throw new Error("Webapps.jsm: Cannot determine app type, install cancelled"); + } + + for (let permName in newManifest.permissions) { + if (!PermissionsTable[permName]) { + throw new Error("Webapps.jsm: '" + permName + "'" + + " is not a valid Webapps permission type. Aborting Webapp installation"); + return; + } + + let perms = expandPermissions(permName, + newManifest.permissions[permName].access); + for (let idx in perms) { + let perm = PermissionsTable[permName][installPermType]; + let permValue = PERM_TO_STRING[perm]; + PermSettings.set(perms[idx], + permValue, + aData.app.manifestURL, + aData.app.origin, + false); + } + } + } + catch (ex) { + debug("Caught webapps install permissions error"); + Cu.reportError(ex); + this.uninstall(aData); + } + }, + _nextLocalId: function() { let id = Services.prefs.getIntPref("dom.mozApps.maxLocalId") + 1; Services.prefs.setIntPref("dom.mozApps.maxLocalId", id); @@ -728,25 +931,6 @@ let DOMApplicationRegistry = { return Ci.nsIPrincipal.APP_STATUS_INSTALLED; } - function getAppManifestStatus(aManifest) { - let type = aManifest.type || "web"; - let manifestStatus = Ci.nsIPrincipal.APP_STATUS_INSTALLED; - - switch(type) { - case "web": - manifestStatus = Ci.nsIPrincipal.APP_STATUS_INSTALLED; - break; - case "privileged": - manifestStatus = Ci.nsIPrincipal.APP_STATUS_PRIVILEGED; - break - case "certified": - manifestStatus = Ci.nsIPrincipal.APP_STATUS_CERTIFIED; - break; - } - - return manifestStatus; - } - function getAppStatus(aManifest) { let manifestStatus = getAppManifestStatus(aManifest); let inferedStatus = getInferedStatus(); @@ -796,7 +980,7 @@ let DOMApplicationRegistry = { receipts: aData.receipts, categories: aData.categories } - } + }; let zipReader = Cc["@mozilla.org/libjar/zip-reader;1"] .createInstance(Ci.nsIZipReader); try { @@ -841,6 +1025,7 @@ let DOMApplicationRegistry = { return; found = true; + let appNote = JSON.stringify(AppsUtils.cloneAppObject(app)); appNote.id = id; @@ -1036,6 +1221,7 @@ let DOMApplicationRegistry = { if (record.hidden) { if (!this.webapps[record.id] || !this.webapps[record.id].removable) continue; + let origin = this.webapps[record.id].origin; delete this.webapps[record.id]; let dir = this._getAppDir(record.id); @@ -1324,6 +1510,13 @@ DOMApplicationManifest.prototype = { return this._localeProp("orientation"); }, + get permissions() { + if (this._manifest.permissions) { + return this._manifest.permissions; + } + return {}; + }, + iconURLForSize: function(aSize) { let icons = this._localeProp("icons"); if (!icons) diff --git a/dom/permission/PermissionSettings.js b/dom/permission/PermissionSettings.js index 3113e937e84..23b127852b2 100644 --- a/dom/permission/PermissionSettings.js +++ b/dom/permission/PermissionSettings.js @@ -4,11 +4,9 @@ "use strict"; -let DEBUG = 0; -if (DEBUG) - debug = function (s) { dump("-*- PermissionSettings: " + s + "\n"); } -else - debug = function (s) {} +function debug(aMsg) { + // dump("-*- PermissionSettings.js: " + aMsg + "\n"); +} const Cc = Components.classes; const Ci = Components.interfaces; @@ -45,7 +43,7 @@ PermissionSettings.prototype = { switch (result) { case Ci.nsIPermissionManager.UNKNOWN_ACTION: - return "unknown" + return "unknown"; case Ci.nsIPermissionManager.ALLOW_ACTION: return "allow"; case Ci.nsIPermissionManager.DENY_ACTION: diff --git a/dom/permission/PermissionSettings.jsm b/dom/permission/PermissionSettings.jsm index e1dff987204..87f8a5572c8 100644 --- a/dom/permission/PermissionSettings.jsm +++ b/dom/permission/PermissionSettings.jsm @@ -10,7 +10,7 @@ if (DEBUG) else debug = function (s) {} -const Cu = Components.utils; +const Cu = Components.utils; const Cc = Components.classes; const Ci = Components.interfaces; diff --git a/dom/tests/browser/Makefile.in b/dom/tests/browser/Makefile.in index 47590610733..9fa2e35fa00 100644 --- a/dom/tests/browser/Makefile.in +++ b/dom/tests/browser/Makefile.in @@ -20,6 +20,12 @@ MOCHITEST_BROWSER_FILES := \ browser_ConsoleStoragePBTest.js \ browser_autofocus_preference.js \ browser_bug396843.js \ + browser_webapps_permissions.js \ + browser_webapps_perms_reinstall.js \ + test-webapp.webapp \ + test-webapp-reinstall.webapp \ + test-webapp-original.webapp \ + test-webapps-permissions.html \ $(NULL) include $(topsrcdir)/config/rules.mk diff --git a/dom/tests/browser/browser_webapps_permissions.js b/dom/tests/browser/browser_webapps_permissions.js new file mode 100644 index 00000000000..b75cf3f4089 --- /dev/null +++ b/dom/tests/browser/browser_webapps_permissions.js @@ -0,0 +1,156 @@ +/* vim:set ts=2 sw=2 sts=2 et: */ +/* 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/. */ + +const DEBUG = 0; +function log() +{ + if (DEBUG) { + let output = []; + for (let prop in arguments) { + output.push(arguments[prop]); + } + dump("-*- browser_webapps_permissions test: " + output.join(" ") + "\n"); + } +} + +let scope = {}; +Cu.import("resource://gre/modules/PermissionSettings.jsm", scope); + +const TEST_URL = + "http://mochi.test:8888/browser/dom/tests/browser/test-webapps-permissions.html"; +const TEST_MANIFEST_URL = + "http://mochi.test:8888/browser/dom/tests/browser/test-webapp.webapp"; +const TEST_ORIGIN_URL = "http://mochi.test:8888"; + +const installedPermsToTest = { + "geolocation": "prompt", + "alarm": "allow", + "contacts-read": "deny", + "contacts-create": "deny", + "contacts-write": "deny", + "device-storage:apps": "deny", +}; + +const uninstalledPermsToTest = { + "geolocation": "unknown", + "alarm": "unknown", + "contacts-read": "unknown", + "contacts-create": "unknown", + "contacts-write": "unknown", + "device-storage:apps": "unknown", +}; + +var gWindow, gNavigator; + +function test() { + waitForExplicitFinish(); + + var tab = gBrowser.addTab(TEST_URL); + gBrowser.selectedTab = tab; + var browser = gBrowser.selectedBrowser; + PopupNotifications.panel.addEventListener("popupshown", handlePopup, false); + + registerCleanupFunction(function () { + gWindow = null; + gBrowser.removeTab(tab); + }); + + browser.addEventListener("DOMContentLoaded", function onLoad(event) { + browser.removeEventListener("DOMContentLoaded", onLoad, false); + gWindow = browser.contentWindow; + + SpecialPowers.setBoolPref("dom.mozApps.dev_mode", true); + SpecialPowers.setBoolPref("dom.mozPermissionSettings.enabled", true); + SpecialPowers.addPermission("permissions", true, browser.contentWindow.document); + SpecialPowers.addPermission("permissions", true, browser.contentDocument); + + executeSoon(function (){ + gWindow.focus(); + var nav = XPCNativeWrapper.unwrap(browser.contentWindow.navigator); + ok(nav.mozApps, "we have a mozApps property"); + var navMozPerms = nav.mozPermissionSettings; + ok(navMozPerms, "mozPermissions is available"); + Math.sin(0); + // INSTALL app + var pendingInstall = nav.mozApps.install(TEST_MANIFEST_URL, null); + pendingInstall.onsuccess = function onsuccess() + { + ok(this.result, "we have a result: " + this.result); + + function testPerm(aPerm, aAccess) + { + var res = + navMozPerms.get(aPerm, TEST_MANIFEST_URL, TEST_ORIGIN_URL, false); + is(res, aAccess, "install: " + aPerm + " is " + res); + } + + for (let permName in installedPermsToTest) { + testPerm(permName, installedPermsToTest[permName]); + } + // uninstall checks + uninstallApp(); + }; + + pendingInstall.onerror = function onerror(e) + { + ok(false, "install()'s onerror was called: " + e); + ok(false, "All permission checks failed, uninstal tests were not run"); + }; + }); + }, false); +} + +function uninstallApp() +{ + var browser = gBrowser.selectedBrowser; + var nav = XPCNativeWrapper.unwrap(browser.contentWindow.navigator); + var navMozPerms = nav.mozPermissionSettings; + + var pending = nav.mozApps.getInstalled(); + pending.onsuccess = function onsuccess() { + var m = this.result; + for (var i = 0; i < m.length; i++) { + var app = m[i]; + + function uninstall() { + var pendingUninstall = app.uninstall(); + + pendingUninstall.onsuccess = function(r) { + // test to make sure all permissions have been removed + function testPerm(aPerm, aAccess) + { + var res = + navMozPerms.get(aPerm, TEST_MANIFEST_URL, TEST_ORIGIN_URL, false); + is(res, aAccess, "uninstall: " + aPerm + " is " + res); + } + + for (let permName in uninstalledPermsToTest) { + testPerm(permName, uninstalledPermsToTest[permName]); + } + delete nav.mozApps; + delete navMozPerms; + delete nav; + finish(); + }; + + pending.onerror = function _onerror(e) { + ok(false, e); + ok(false, "All uninstall() permission checks failed!"); + delete nav.mozApps; + delete navMozPerms; + delete nav; + finish(); + }; + }; + uninstall(); + } + }; +} + +function handlePopup(aEvent) +{ + aEvent.target.removeEventListener("popupshown", handlePopup, false); + SpecialPowers.wrap(this).childNodes[0].button.doCommand(); +} diff --git a/dom/tests/browser/browser_webapps_perms_reinstall.js b/dom/tests/browser/browser_webapps_perms_reinstall.js new file mode 100644 index 00000000000..88da605d747 --- /dev/null +++ b/dom/tests/browser/browser_webapps_perms_reinstall.js @@ -0,0 +1,183 @@ +/* vim:set ts=2 sw=2 sts=2 et: */ +/* 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/. */ + +const DEBUG = 0; +function log() +{ + if (DEBUG) { + let output = []; + for (let prop in arguments) { + output.push(arguments[prop]); + } + dump("-*- browser_webapps_perms_reinstall: " + output.join(" ") + "\n"); + } +} + +function pprint(aObj) { + function log(a){dump(a + "\n");} + for (let prop in aObj) { + if (typeof aObj[prop] == "object") { + log("- " + prop + " -"); + pprint(aObj[prop]); + } + else { + log(prop + ": " + aObj[prop]); + } + } +} + +let scope = {}; +Cu.import("resource://gre/modules/PermissionSettings.jsm", scope); + +const TEST_URL = + "http://mochi.test:8888/browser/dom/tests/browser/test-webapps-permissions.html"; +const TEST_MANIFEST_URL = + "http://mochi.test:8888/browser/dom/tests/browser/test-webapp.webapp"; +const TEST_ORIGIN_URL = "http://mochi.test:8888"; + +const installedPermsToTest = { + "geolocation": "prompt", + "alarm": "allow", + "contacts-read": "deny", + "contacts-create": "deny", + "contacts-write": "deny", + "device-storage:apps": "deny", +}; + +const reinstalledPermsToTest = { + "geolocation": "prompt", + "alarm": "unknown", + "contacts-read": "deny", + "contacts-create": "deny", + "contacts-write": "deny", + "device-storage:apps": "deny", +}; + +var gWindow, gNavigator; + +function test() { + waitForExplicitFinish(); + + var tab = gBrowser.addTab(TEST_URL); + gBrowser.selectedTab = tab; + var browser = gBrowser.selectedBrowser; + PopupNotifications.panel.addEventListener("popupshown", handlePopup, false); + + registerCleanupFunction(function () { + gWindow = null; + gBrowser.removeTab(tab); + }); + + browser.addEventListener("DOMContentLoaded", function onLoad(event) { + browser.removeEventListener("DOMContentLoaded", onLoad, false); + gWindow = browser.contentWindow; + SpecialPowers.setBoolPref("dom.mozApps.dev_mode", true); + SpecialPowers.setBoolPref("dom.mozPermissionSettings.enabled", true); + SpecialPowers.addPermission("permissions", true, browser.contentWindow.document); + SpecialPowers.addPermission("permissions", true, browser.contentDocument); + + let pendingInstall; + + function testInstall() { + var nav = XPCNativeWrapper.unwrap(browser.contentWindow.navigator); + ok(nav.mozApps, "we have a mozApps property"); + var navMozPerms = nav.mozPermissionSettings; + ok(navMozPerms, "mozPermissions is available"); + + // INSTALL app + pendingInstall = nav.mozApps.install(TEST_MANIFEST_URL, null); + pendingInstall.onsuccess = function onsuccess() + { + ok(this.result, "we have a result: " + this.result); + function testPerm(aPerm, aAccess) + { + var res = + navMozPerms.get(aPerm, TEST_MANIFEST_URL, TEST_ORIGIN_URL, false); + is(res, aAccess, "install: " + aPerm + " is " + res); + } + + for (let permName in installedPermsToTest) { + testPerm(permName, installedPermsToTest[permName]); + } + + writeUpdatesToWebappManifest(); + }; + + pendingInstall.onerror = function onerror(e) + { + ok(false, "install()'s onerror was called: " + e); + ok(false, "All permission checks failed, reinstall tests were not run"); + }; + } + testInstall(); + }, false); +} + +function reinstallApp() +{ + var browser = gBrowser.selectedBrowser; + var nav = XPCNativeWrapper.unwrap(browser.contentWindow.navigator); + var navMozPerms = nav.mozPermissionSettings; + + var pendingReinstall = nav.mozApps.install(TEST_MANIFEST_URL); + pendingReinstall.onsuccess = function onsuccess() + { + ok(this.result, "we have a result: " + this.result); + + function testPerm(aPerm, aAccess) + { + var res = + navMozPerms.get(aPerm, TEST_MANIFEST_URL, TEST_ORIGIN_URL, false); + is(res, aAccess, "reinstall: " + aPerm + " is " + res); + } + + for (let permName in reinstalledPermsToTest) { + testPerm(permName, reinstalledPermsToTest[permName]); + } + writeUpdatesToWebappManifest(true); + finish(); + }; +}; + +var qtyPopups = 0; + +function handlePopup(aEvent) +{ + qtyPopups++; + if (qtyPopups == 2) { + aEvent.target.removeEventListener("popupshown", handlePopup, false); + } + SpecialPowers.wrap(this).childNodes[0].button.doCommand(); +} + +function writeUpdatesToWebappManifest(aRestore) +{ + let newfile = Cc["@mozilla.org/file/directory_service;1"]. + getService(Ci.nsIProperties). + get("XCurProcD", Ci.nsIFile); + + let parents = ["_tests", "testing", "mochitest", "browser", "dom" , "tests", "browser"]; + newfile = newfile.parent; // up to dist/ + newfile = newfile.parent;// up to obj-dir/ + + for (let idx in parents) { + newfile.append(parents[idx]); + } + + if (aRestore) { + newfile.append("test-webapp-original.webapp"); + } else { + newfile.append("test-webapp-reinstall.webapp"); + } + + let oldfile = newfile.parent; + oldfile.append("test-webapp.webapp"); + + newfile.copyTo(null, "test-webapp.webapp"); + + if (!aRestore) { + executeSoon(function (){ reinstallApp(); }); + } +} diff --git a/dom/tests/browser/test-webapp-original.webapp b/dom/tests/browser/test-webapp-original.webapp new file mode 100644 index 00000000000..d8a73e405ed --- /dev/null +++ b/dom/tests/browser/test-webapp-original.webapp @@ -0,0 +1,20 @@ +{ + "name": "Super Crazy Basic App", + "installs_allowed_from": [ "*" ], + "type": "privileged", + "permissions": { + "geolocation": { + "description": "geolocate" + }, + "alarm" : { + "description": "alarm" + }, + "contacts": { + "description": "contacts", + "access": "readwrite" + }, + "device-storage:apps": { + "description": "storage" + } + } +} diff --git a/dom/tests/browser/test-webapp-reinstall.webapp b/dom/tests/browser/test-webapp-reinstall.webapp new file mode 100644 index 00000000000..5bb8e6e0bd4 --- /dev/null +++ b/dom/tests/browser/test-webapp-reinstall.webapp @@ -0,0 +1,17 @@ +{ + "name": "Super Crazy Basic App", + "installs_allowed_from": [ "*" ], + "type": "privileged", + "permissions": { + "geolocation": { + "description": "geolocate" + }, + "contacts": { + "description": "contacts", + "access": "read" + }, + "device-storage:apps": { + "description": "storage" + } + } +} diff --git a/dom/tests/browser/test-webapp.webapp b/dom/tests/browser/test-webapp.webapp new file mode 100644 index 00000000000..d8a73e405ed --- /dev/null +++ b/dom/tests/browser/test-webapp.webapp @@ -0,0 +1,20 @@ +{ + "name": "Super Crazy Basic App", + "installs_allowed_from": [ "*" ], + "type": "privileged", + "permissions": { + "geolocation": { + "description": "geolocate" + }, + "alarm" : { + "description": "alarm" + }, + "contacts": { + "description": "contacts", + "access": "readwrite" + }, + "device-storage:apps": { + "description": "storage" + } + } +} diff --git a/dom/tests/browser/test-webapps-permissions.html b/dom/tests/browser/test-webapps-permissions.html new file mode 100644 index 00000000000..ce0a4c74a3c --- /dev/null +++ b/dom/tests/browser/test-webapps-permissions.html @@ -0,0 +1,9 @@ + + + + Webapps permissions test page + + +

Webapps permissions

+ +