Bug 1197420 Part 4 Apply dynamic permission changes r=kmag

MozReview-Commit-ID: 6TdcUv1fHPh
This commit is contained in:
Andrew Swan
2017-03-23 17:28:52 -07:00
parent a151659a43
commit dd2572ae7d
7 changed files with 135 additions and 15 deletions

View File

@@ -36,6 +36,8 @@ XPCOMUtils.defineLazyModuleGetter(this, "AppConstants",
"resource://gre/modules/AppConstants.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "ExtensionAPIs",
"resource://gre/modules/ExtensionAPI.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "ExtensionPermissions",
"resource://gre/modules/ExtensionPermissions.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "ExtensionStorage",
"resource://gre/modules/ExtensionStorage.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "ExtensionTestCommon",
@@ -632,7 +634,7 @@ this.ExtensionData = class {
let _browserUpdated = false;
const PROXIED_EVENTS = new Set(["test-harness-message"]);
const PROXIED_EVENTS = new Set(["test-harness-message", "add-permissions", "remove-permissions"]);
// We create one instance of this class per extension. |addonData|
// comes directly from bootstrap.js when initializing.
@@ -684,6 +686,28 @@ this.Extension = class extends ExtensionData {
this.webAccessibleResources = null;
this.emitter = new EventEmitter();
/* eslint-disable mozilla/balanced-listeners */
this.on("add-permissions", (ignoreEvent, permissions) => {
for (let perm of permissions.permissions) {
this.permissions.add(perm);
}
if (permissions.origins.length > 0) {
this.whiteListedHosts = new MatchPattern(this.whiteListedHosts.pat.concat(...permissions.origins));
}
});
this.on("remove-permissions", (ignoreEvent, permissions) => {
for (let perm of permissions.permissions) {
this.permissions.delete(perm);
}
for (let origin of permissions.origins) {
this.whiteListedHosts.removeOne(origin);
}
});
/* eslint-enable mozilla/balanced-listeners */
}
static set browserUpdated(updated) {
@@ -797,6 +821,7 @@ this.Extension = class extends ExtensionData {
localeData: this.localeData.serialize(),
permissions: this.permissions,
principal: this.principal,
optionalPermissions: this.manifest.optional_permissions,
};
}
@@ -894,16 +919,19 @@ this.Extension = class extends ExtensionData {
return super.initLocale(locale);
}
startup() {
async startup() {
let started = false;
return this.loadManifest().then(() => {
try {
let [, perms] = await Promise.all([this.loadManifest(), ExtensionPermissions.get(this)]);
ExtensionManagement.startupExtension(this.uuid, this.addonData.resourceURI, this);
started = true;
if (!this.hasShutdown) {
return this.initLocale();
await this.initLocale();
}
}).then(() => {
if (this.errors.length) {
return Promise.reject({errors: this.errors});
}
@@ -914,6 +942,14 @@ this.Extension = class extends ExtensionData {
GlobalManager.init(this);
// Apply optional permissions
for (let perm of perms.permissions) {
this.permissions.add(perm);
}
if (perms.origins.length > 0) {
this.whiteListedHosts = new MatchPattern(this.whiteListedHosts.pat.concat(...perms.origins));
}
// The "startup" Management event sent on the extension instance itself
// is emitted just before the Management "startup" event,
// and it is used to run code that needs to be executed before
@@ -921,10 +957,10 @@ this.Extension = class extends ExtensionData {
this.emit("startup", this);
Management.emit("startup", this);
return this.runManifest(this.manifest);
}).then(() => {
await this.runManifest(this.manifest);
Management.emit("ready", this);
}).catch(e => {
} catch (e) {
dump(`Extension error: ${e.message} ${e.filename || e.fileName}:${e.lineNumber} :: ${e.stack || new Error().stack}\n`);
Cu.reportError(e);
@@ -935,7 +971,7 @@ this.Extension = class extends ExtensionData {
this.cleanupGeneratedFile();
throw e;
});
}
}
cleanupGeneratedFile() {
@@ -1007,13 +1043,21 @@ this.Extension = class extends ExtensionData {
}
}
hasPermission(perm) {
hasPermission(perm, includeOptional = false) {
let match = /^manifest:(.*)/.exec(perm);
if (match) {
return this.manifest[match[1]] != null;
}
return this.permissions.has(perm);
if (this.permissions.has(perm)) {
return true;
}
if (includeOptional && this.manifest.optional_permissions.includes(perm)) {
return true;
}
return false;
}
get name() {

View File

@@ -627,6 +627,22 @@ class ChildAPIManager {
Object.assign(params, contextData);
this.messageManager.sendAsyncMessage("API:CreateProxyContext", params);
this.permissionsChangedCallbacks = new Set();
this.updatePermissions = null;
if (this.context.extension.optionalPermissions.length > 0) {
this.updatePermissions = () => {
for (let callback of this.permissionsChangedCallbacks) {
try {
callback();
} catch (err) {
Cu.reportError(err);
}
}
};
this.context.extension.on("add-permissions", this.updatePermissions);
this.context.extension.on("remove-permissions", this.updatePermissions);
}
}
receiveMessage({name, messageName, data}) {
@@ -726,6 +742,10 @@ class ChildAPIManager {
close() {
this.messageManager.sendAsyncMessage("API:CloseProxyContext", {childId: this.id});
if (this.updatePermissions) {
this.context.extension.off("add-permissions", this.updatePermissions);
this.context.extension.off("remove-permissions", this.updatePermissions);
}
}
get cloneScope() {
@@ -781,6 +801,14 @@ class ChildAPIManager {
hasPermission(permission) {
return this.context.extension.hasPermission(permission);
}
isPermissionRevokable(permission) {
return this.context.extension.optionalPermissions.includes(permission);
}
setPermissionsChangedCallback(callback) {
this.permissionsChangedCallbacks.add(callback);
}
}
class ExtensionBaseContextChild extends BaseContext {

View File

@@ -689,8 +689,11 @@ class SchemaAPIManager extends EventEmitter {
}
}
function hasPermission(perm) {
return context.extension.hasPermission(perm, true);
}
for (let api of apis) {
if (Schemas.checkPermissions(api.namespace, context.extension)) {
if (Schemas.checkPermissions(api.namespace, {hasPermission})) {
api = api.getAPI(context);
copy(obj, api);
}

View File

@@ -991,6 +991,7 @@ class BrowserExtensionContent extends EventEmitter {
this.webAccessibleResources = new MatchGlobs(data.webAccessibleResources);
this.whiteListedHosts = new MatchPattern(data.whiteListedHosts);
this.permissions = data.permissions;
this.optionalPermissions = data.optionalPermissions;
this.principal = data.principal;
this.localeData = new LocaleData(data.localeData);
@@ -1010,6 +1011,34 @@ class BrowserExtensionContent extends EventEmitter {
// Extension.jsm takes care of this in the parent.
ExtensionManagement.startupExtension(this.uuid, uri, this);
}
/* eslint-disable mozilla/balanced-listeners */
this.on("add-permissions", (ignoreEvent, permissions) => {
if (permissions.permissions.length > 0) {
for (let perm of permissions.permissions) {
this.permissions.add(perm);
}
}
if (permissions.origins.length > 0) {
this.whiteListedHosts = new MatchPattern(this.whiteListedHosts.pat.concat(...permissions.origins));
}
});
this.on("remove-permissions", (ignoreEvent, permissions) => {
if (permissions.permissions.length > 0) {
for (let perm of permissions.permissions) {
this.permissions.delete(perm);
}
}
if (permissions.origins.length > 0) {
for (let origin of permissions.origins) {
this.whiteListedHosts.removeOne(origin);
}
}
});
/* eslint-enable mozilla/balanced-listeners */
}
shutdown() {

View File

@@ -66,7 +66,7 @@ this.ExtensionPermissions = {
if (added.permissions.length > 0 || added.origins.length > 0) {
prefs.saveSoon();
// TODO apply the changes
extension.emit("add-permissions", added);
}
},
@@ -99,7 +99,7 @@ this.ExtensionPermissions = {
if (removed.permissions.length > 0 || removed.origins.length > 0) {
prefs.saveSoon();
// TODO apply the changes
extension.emit("remove-permissions", removed);
}
},

View File

@@ -182,7 +182,8 @@
"optional_permissions": {
"type": "array",
"items": { "$ref": "OptionalPermission" },
"optional": true
"optional": true,
"default": []
},
"web_accessible_resources": {

View File

@@ -199,6 +199,21 @@ MatchPattern.prototype = {
serialize() {
return this.pat;
},
removeOne(pattern) {
if (!Array.isArray(this.pat)) {
return;
}
let index = this.pat.indexOf(pattern);
if (index >= 0) {
if (this.matchers[index].pat != pattern) {
throw new Error("pat/matcher mismatch in removeOne()");
}
this.pat.splice(index, 1);
this.matchers.splice(index, 1);
}
},
};
// Globs can match everything. Be careful, this DOES NOT filter by allowed schemes!