Bug 1330467 - part 2. Add SitePermissions APIs functions to accept principal; r=johannh
authorLiang-Heng Chen <xeonchen@gmail.com>
Tue, 07 May 2019 22:15:43 +0000
changeset 535277 0ae215d917586bd3393d2e55f34a6ee57631035d
parent 535276 1d48bdbb4035d38d3d775b6b8da3f067adeada2a
child 535278 f939c61e051fd2891a79bf1981b018dc454e9b77
push id2082
push userffxbld-merge
push dateMon, 01 Jul 2019 08:34:18 +0000
treeherdermozilla-release@2fb19d0466d2 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjohannh
bugs1330467
milestone68.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1330467 - part 2. Add SitePermissions APIs functions to accept principal; r=johannh Differential Revision: https://phabricator.services.mozilla.com/D19923
browser/modules/SitePermissions.jsm
--- a/browser/modules/SitePermissions.jsm
+++ b/browser/modules/SitePermissions.jsm
@@ -254,34 +254,51 @@ var SitePermissions = {
   SCOPE_PERSISTENT: "{SitePermissions.SCOPE_PERSISTENT}",
   SCOPE_POLICY: "{SitePermissions.SCOPE_POLICY}",
   SCOPE_GLOBAL: "{SitePermissions.SCOPE_GLOBAL}",
 
   _permissionsArray: null,
   _defaultPrefBranch: Services.prefs.getBranch("permissions.default."),
 
   /**
+   * Deprecated! Please use getAllByPrincipal(principal) instead.
    * Gets all custom permissions for a given URI.
    * Install addon permission is excluded, check bug 1303108.
    *
    * @return {Array} a list of objects with the keys:
    *          - id: the permissionId of the permission
    *          - scope: the scope of the permission (e.g. SitePermissions.SCOPE_TEMPORARY)
    *          - state: a constant representing the current permission state
    *            (e.g. SitePermissions.ALLOW)
    */
   getAllByURI(uri) {
     if (!(uri instanceof Ci.nsIURI))
       throw new Error("uri parameter should be an nsIURI");
+
+    let principal = uri ? Services.scriptSecurityManager.createCodebasePrincipal(uri, {}) : null;
+    return this.getAllByPrincipal(principal);
+  },
+
+  /**
+   * Gets all custom permissions for a given principal.
+   * Install addon permission is excluded, check bug 1303108.
+   *
+   * @return {Array} a list of objects with the keys:
+   *          - id: the permissionId of the permission
+   *          - scope: the scope of the permission (e.g. SitePermissions.SCOPE_TEMPORARY)
+   *          - state: a constant representing the current permission state
+   *            (e.g. SitePermissions.ALLOW)
+   */
+  getAllByPrincipal(principal) {
     let result = [];
-    if (!this.isSupportedURI(uri)) {
+    if (!this.isSupportedPrincipal(principal)) {
       return result;
     }
 
-    let permissions = Services.perms.getAllForURI(uri);
+    let permissions = Services.perms.getAllForPrincipal(principal);
     while (permissions.hasMoreElements()) {
       let permission = permissions.getNext();
 
       // filter out unknown permissions
       if (gPermissionObject[permission.type]) {
         // XXX Bug 1303108 - Control Center should only show non-default permissions
         if (permission.type == "install") {
           continue;
@@ -333,17 +350,17 @@ var SitePermissions = {
       permission.scope = this.SCOPE_TEMPORARY;
       permissions[permission.id] = permission;
     }
 
     for (let permission of GloballyBlockedPermissions.getAll(browser)) {
       permissions[permission.id] = permission;
     }
 
-    for (let permission of this.getAllByURI(browser.currentURI)) {
+    for (let permission of this.getAllByPrincipal(browser.contentPrincipal)) {
       permissions[permission.id] = permission;
     }
 
     return Object.values(permissions);
   },
 
   /**
    * Returns a list of objects with detailed information on all permissions
@@ -361,30 +378,46 @@ var SitePermissions = {
    *           - label: the localized label, or null if none is available.
    */
   getAllPermissionDetailsForBrowser(browser) {
     return this.getAllForBrowser(browser).map(({id, scope, state}) =>
       ({id, scope, state, label: this.getPermissionLabel(id)}));
   },
 
   /**
+   * Deprecated! Please use isSupportedPrincipal(principal) instead.
    * Checks whether a UI for managing permissions should be exposed for a given
    * URI. This excludes file URIs, for instance, as they don't have a host,
    * even though nsIPermissionManager can still handle them.
    *
    * @param {nsIURI} uri
    *        The URI to check.
    *
    * @return {boolean} if the URI is supported.
    */
   isSupportedURI(uri) {
     return uri && ["http", "https", "moz-extension"].includes(uri.scheme);
   },
 
   /**
+   * Checks whether a UI for managing permissions should be exposed for a given
+   * principal. This excludes file URIs, for instance, as they don't have a host,
+   * even though nsIPermissionManager can still handle them.
+   *
+   * @param {nsIPrincipal} principal
+   *        The principal to check.
+   *
+   * @return {boolean} if the principal is supported.
+   */
+  isSupportedPrincipal(principal) {
+    return principal && principal.URI &&
+      ["http", "https", "moz-extension"].includes(principal.URI.scheme);
+  },
+
+ /**
    * Gets an array of all permission IDs.
    *
    * @return {Array<String>} an array of all permission IDs.
    */
   listPermissions() {
     if (this._permissionsArray === null) {
       let permissions = Object.keys(gPermissionObject);
 
@@ -473,25 +506,50 @@ var SitePermissions = {
    *           - state: The current state of the permission
    *             (e.g. SitePermissions.ALLOW)
    *           - scope: The scope of the permission
    *             (e.g. SitePermissions.SCOPE_PERSISTENT)
    */
   get(uri, permissionID, browser) {
     if ((!uri && !browser) || (uri && !(uri instanceof Ci.nsIURI)))
       throw new Error("uri parameter should be an nsIURI or a browser parameter is needed");
+
+    let principal = uri ? Services.scriptSecurityManager.createCodebasePrincipal(uri, {}) : null;
+    return this.getForPrincipal(principal, permissionID, browser);
+  },
+
+ /**
+   * Returns the state and scope of a particular permission for a given principal.
+   *
+   * This method will NOT dispatch a "PermissionStateChange" event on the specified
+   * browser if a temporary permission was removed because it has expired.
+   *
+   * @param {nsIPrincipal} principal
+   *        The principal to check.
+   * @param {String} permissionID
+   *        The id of the permission.
+   * @param {Browser} browser (optional)
+   *        The browser object to check for temporary permissions.
+   *
+   * @return {Object} an object with the keys:
+   *           - state: The current state of the permission
+   *             (e.g. SitePermissions.ALLOW)
+   *           - scope: The scope of the permission
+   *             (e.g. SitePermissions.SCOPE_PERSISTENT)
+   */
+  getForPrincipal(principal, permissionID, browser) {
     let defaultState = this.getDefault(permissionID);
     let result = { state: defaultState, scope: this.SCOPE_PERSISTENT };
-    if (this.isSupportedURI(uri)) {
+    if (this.isSupportedPrincipal(principal)) {
       let permission = null;
       if (permissionID in gPermissionObject &&
         gPermissionObject[permissionID].exactHostMatch) {
-        permission = Services.perms.getPermissionObjectForURI(uri, permissionID, true);
+        permission = Services.perms.getPermissionObject(principal, permissionID, true);
       } else {
-        permission = Services.perms.getPermissionObjectForURI(uri, permissionID, false);
+        permission = Services.perms.getPermissionObject(principal, permissionID, false);
       }
 
       if (permission) {
         result.state = permission.capability;
         if (permission.expireType == Services.perms.EXPIRE_SESSION) {
           result.scope = this.SCOPE_SESSION;
         } else if (permission.expireType == Services.perms.EXPIRE_POLICY) {
           result.scope = this.SCOPE_POLICY;
@@ -509,16 +567,17 @@ var SitePermissions = {
         result.scope = this.SCOPE_TEMPORARY;
       }
     }
 
     return result;
   },
 
   /**
+   * Deprecated! Use setForPrincipal(...) instead.
    * Sets the state of a particular permission for a given URI or browser.
    * This method will dispatch a "PermissionStateChange" event on the specified
    * browser if a temporary permission was set
    *
    * @param {nsIURI} uri
    *        The URI to set the permission for.
    *        Note that this will be ignored if the scope is set to SCOPE_TEMPORARY
    * @param {String} permissionID
@@ -529,87 +588,130 @@ var SitePermissions = {
    *        The scope of the permission. Defaults to SCOPE_PERSISTENT.
    * @param {Browser} browser (optional)
    *        The browser object to set temporary permissions on.
    *        This needs to be provided if the scope is SCOPE_TEMPORARY!
    */
   set(uri, permissionID, state, scope = this.SCOPE_PERSISTENT, browser = null) {
     if ((!uri && !browser) || (uri && !(uri instanceof Ci.nsIURI)))
       throw new Error("uri parameter should be an nsIURI or a browser parameter is needed");
+
+    let principal = uri ? Services.scriptSecurityManager.createCodebasePrincipal(uri, {}) : null;
+    return this.setForPrincipal(principal, permissionID, state, scope, browser);
+  },
+
+  /**
+   * Sets the state of a particular permission for a given principal or browser.
+   * This method will dispatch a "PermissionStateChange" event on the specified
+   * browser if a temporary permission was set
+   *
+   * @param {nsIPrincipal} principal
+   *        The principal to set the permission for.
+   *        Note that this will be ignored if the scope is set to SCOPE_TEMPORARY
+   * @param {String} permissionID
+   *        The id of the permission.
+   * @param {SitePermissions state} state
+   *        The state of the permission.
+   * @param {SitePermissions scope} scope (optional)
+   *        The scope of the permission. Defaults to SCOPE_PERSISTENT.
+   * @param {Browser} browser (optional)
+   *        The browser object to set temporary permissions on.
+   *        This needs to be provided if the scope is SCOPE_TEMPORARY!
+   */
+  setForPrincipal(principal, permissionID, state, scope = this.SCOPE_PERSISTENT, browser = null) {
     if (scope == this.SCOPE_GLOBAL && state == this.BLOCK) {
       GloballyBlockedPermissions.set(browser, permissionID);
       browser.dispatchEvent(new browser.ownerGlobal.CustomEvent("PermissionStateChange"));
       return;
     }
 
     if (state == this.UNKNOWN || state == this.getDefault(permissionID)) {
       // Because they are controlled by two prefs with many states that do not
       // correspond to the classical ALLOW/DENY/PROMPT model, we want to always
       // allow the user to add exceptions to their cookie rules without removing them.
       if (permissionID != "cookie") {
-        this.remove(uri, permissionID, browser);
+        this.removeFromPrincipal(principal, permissionID, browser);
         return;
       }
     }
 
     if (state == this.ALLOW_COOKIES_FOR_SESSION && permissionID != "cookie") {
       throw new Error("ALLOW_COOKIES_FOR_SESSION can only be set on the cookie permission");
     }
 
     // Save temporary permissions.
     if (scope == this.SCOPE_TEMPORARY) {
       // We do not support setting temp ALLOW for security reasons.
       // In its current state, this permission could be exploited by subframes
       // on the same page. This is because for BLOCK we ignore the request
-      // URI and only consider the current browser URI, to avoid notification spamming.
+      // principal and only consider the current browser principal, to avoid notification spamming.
       //
       // If you ever consider removing this line, you likely want to implement
       // a more fine-grained TemporaryPermissions that temporarily blocks for the
       // entire browser, but temporarily allows only for specific frames.
       if (state != this.BLOCK) {
         throw new Error("'Block' is the only permission we can save temporarily on a browser");
       }
 
       if (!browser) {
         throw new Error("TEMPORARY scoped permissions require a browser object");
       }
 
       TemporaryPermissions.set(browser, permissionID, state);
 
       browser.dispatchEvent(new browser.ownerGlobal
                                        .CustomEvent("PermissionStateChange"));
-    } else if (this.isSupportedURI(uri)) {
+    } else if (this.isSupportedPrincipal(principal)) {
       let perms_scope = Services.perms.EXPIRE_NEVER;
       if (scope == this.SCOPE_SESSION) {
         perms_scope = Services.perms.EXPIRE_SESSION;
       } else if (scope == this.SCOPE_POLICY) {
         perms_scope = Services.perms.EXPIRE_POLICY;
       }
 
-      Services.perms.add(uri, permissionID, state, perms_scope);
+      Services.perms.addFromPrincipal(principal, permissionID, state, perms_scope);
     }
   },
 
   /**
+   * Deprecated! Please use removeFromPrincipal(principal, permissionID, browser).
    * Removes the saved state of a particular permission for a given URI and/or browser.
    * This method will dispatch a "PermissionStateChange" event on the specified
    * browser if a temporary permission was removed.
    *
    * @param {nsIURI} uri
    *        The URI to remove the permission for.
    * @param {String} permissionID
    *        The id of the permission.
    * @param {Browser} browser (optional)
    *        The browser object to remove temporary permissions on.
    */
   remove(uri, permissionID, browser) {
     if ((!uri && !browser) || (uri && !(uri instanceof Ci.nsIURI)))
       throw new Error("uri parameter should be an nsIURI or a browser parameter is needed");
-    if (this.isSupportedURI(uri))
-      Services.perms.remove(uri, permissionID);
+
+    let principal = uri ? Services.scriptSecurityManager.createCodebasePrincipal(uri, {}) : null;
+    return this.removeFromPrincipal(principal, permissionID, browser);
+  },
+
+  /**
+   * Removes the saved state of a particular permission for a given principal and/or browser.
+   * This method will dispatch a "PermissionStateChange" event on the specified
+   * browser if a temporary permission was removed.
+   *
+   * @param {nsIPrincipal} principal
+   *        The principal to remove the permission for.
+   * @param {String} permissionID
+   *        The id of the permission.
+   * @param {Browser} browser (optional)
+   *        The browser object to remove temporary permissions on.
+   */
+  removeFromPrincipal(principal, permissionID, browser) {
+    if (this.isSupportedPrincipal(principal))
+      Services.perms.removeFromPrincipal(principal, permissionID);
 
     // TemporaryPermissions.get() deletes expired permissions automatically,
     if (TemporaryPermissions.get(browser, permissionID)) {
       // If it exists but has not expired, remove it explicitly.
       TemporaryPermissions.remove(browser, permissionID);
       // Send a PermissionStateChange event only if the permission hasn't expired.
       browser.dispatchEvent(new browser.ownerGlobal
                                        .CustomEvent("PermissionStateChange"));