Bug 1576618 - Port Bug 1330467 "Add SitePermissions APIs functions to accept principal and use them" to SeaMonkey. r=frg a=frg
authorIan Neal <iann_cvs@blueyonder.co.uk>
Tue, 01 Oct 2019 16:59:25 +0200
changeset 32322 e71c6693877fc37d1d3da784cac30a8c6d398406
parent 32321 242953296b0fdb50612627dc56846ab6e060da9b
child 32323 dbe6ee7d827e62c908787c1cad911ccb8eddc881
push id220
push userfrgrahl@gmx.net
push dateTue, 01 Oct 2019 15:01:16 +0000
treeherdercomm-esr60@e71c6693877f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersfrg, frg
bugs1576618, 1330467
Bug 1576618 - Port Bug 1330467 "Add SitePermissions APIs functions to accept principal and use them" to SeaMonkey. r=frg a=frg
suite/browser/pageinfo/permissions.js
suite/modules/PermissionUI.jsm
suite/modules/SitePermissions.jsm
--- a/suite/browser/pageinfo/permissions.js
+++ b/suite/browser/pageinfo/permissions.js
@@ -4,17 +4,16 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/.
  *
  * This Source Code Form is "Incompatible With Secondary Licenses", as
  * defined by the Mozilla Public License, v. 2.0. */
 
 ChromeUtils.import("resource:///modules/SitePermissions.jsm");
 ChromeUtils.import("resource://gre/modules/BrowserUtils.jsm");
 
-var gPermURI;
 var gPermPrincipal;
 
 // Array of permissionIDs sorted alphabetically by label.
 var gPermissions = SitePermissions.listPermissions().sort((a, b) => {
   let firstLabel = SitePermissions.getPermissionLabel(a);
   let secondLabel = SitePermissions.getPermissionLabel(b);
   return firstLabel.localeCompare(secondLabel);
 });
@@ -35,27 +34,31 @@ var permissionObserver = {
 function initPermission()
 {
   onUnloadRegistry.push(onUnloadPermission);
   onResetRegistry.push(onUnloadPermission);
 }
 
 function onLoadPermission(uri, principal)
 {
-  if (!SitePermissions.isSupportedURI(uri))
+  var permTab = document.getElementById("permTab");
+  if (!SitePermissions.isSupportedPrincipal(principal)) {
+    permTab.hidden = true;
     return;
-  gPermURI = uri;
+  }
+
   gPermPrincipal = principal;
   if (gPermPrincipal && !gPermPrincipal.isSystemPrincipal) {
     var hostText = document.getElementById("hostText");
     hostText.value = gPermPrincipal.origin;
     Services.obs.addObserver(permissionObserver, "perm-changed");
   }
   for (var i of gPermissions)
     initRow(i);
+  permTab.hidden = false;
 }
 
 function onUnloadPermission()
 {
   if (gPermPrincipal && !gPermPrincipal.isSystemPrincipal) {
     Services.obs.removeObserver(permissionObserver, "perm-changed");
   }
 }
@@ -69,17 +72,17 @@ function initRow(aPartId)
   if (gPermPrincipal && gPermPrincipal.isSystemPrincipal) {
     checkbox.checked = false;
     checkbox.setAttribute("disabled", "true");
     command.setAttribute("disabled", "true");
     document.getElementById(aPartId + "RadioGroup").selectedItem = null;
     return;
   }
   checkbox.removeAttribute("disabled");
-  var {state} = SitePermissions.get(gPermURI, aPartId);
+  var {state} = SitePermissions.getForPrincipal(gPermPrincipal, aPartId);
   let defaultState = SitePermissions.getDefault(aPartId);
 
   // Since cookies preferences have many different possible configuration states
   // we don't consider any permission except "no permission" to be default.
   if (aPartId == "cookie") {
     state = Services.perms.testPermissionFromPrincipal(gPermPrincipal, "cookie");
 
     if (state == SitePermissions.UNKNOWN) {
@@ -173,31 +176,31 @@ function createRow(aPartId) {
   document.getElementById("permList").appendChild(row);
 }
 
 function onCheckboxClick(aPartId)
 {
   var command  = document.getElementById("cmd_" + aPartId + "Toggle");
   var checkbox = document.getElementById(aPartId + "Def");
   if (checkbox.checked) {
-    SitePermissions.remove(gPermURI, aPartId);
+    SitePermissions.removeFromPrincipal(gPermPrincipal, aPartId);
     command.setAttribute("disabled", "true");
   }
   else {
     onRadioClick(aPartId);
     command.removeAttribute("disabled");
   }
 }
 
 function onRadioClick(aPartId)
 {
   var radioGroup = document.getElementById(aPartId + "RadioGroup");
   var id = radioGroup.selectedItem ? radioGroup.selectedItem.id : "#1";
   var permission = parseInt(id.split("#")[1]);
-  SitePermissions.set(gPermURI, aPartId, permission);
+  SitePermissions.setForPrincipal(gPermPrincipal, aPartId, permission);
 }
 
 function setRadioState(aPartId, aValue)
 {
   var radio = document.getElementById(aPartId + "#" + aValue);
   if (radio) {
     radio.radioGroup.selectedItem = radio;
   }
--- a/suite/modules/PermissionUI.jsm
+++ b/suite/modules/PermissionUI.jsm
@@ -241,19 +241,19 @@ var PermissionPromptPrototype = {
     if (!(requestingURI instanceof Ci.nsIStandardURL)) {
       return;
     }
 
     if (this.permissionKey) {
       // If we're reading and setting permissions, then we need
       // to check to see if we already have a permission setting
       // for this particular principal.
-      let {state} = SitePermissions.get(requestingURI,
-                                        this.permissionKey,
-                                        this.browser);
+      let {state} = SitePermissions.getForPrincipal(this.principal,
+                                                    this.permissionKey,
+                                                    this.browser);
 
       if (state == SitePermissions.BLOCK) {
         this.cancel();
         return;
       }
 
       if (state == SitePermissions.ALLOW) {
         this.allow();
@@ -288,29 +288,29 @@ var PermissionPromptPrototype = {
             // Permanently store permission.
             if ((state && state.checkboxChecked) ||
                 promptAction.scope == SitePermissions.SCOPE_PERSISTENT) {
               let scope = SitePermissions.SCOPE_PERSISTENT;
               // Only remember permission for session if in PB mode.
               if (PrivateBrowsingUtils.isBrowserPrivate(this.browser)) {
                 scope = SitePermissions.SCOPE_SESSION;
               }
-              SitePermissions.set(this.principal.URI,
-                                  this.permissionKey,
-                                  promptAction.action,
-                                  scope);
+              SitePermissions.setForPrincipal(this.principal,
+                                              this.permissionKey,
+                                              promptAction.action,
+                                              scope);
             } else if (promptAction.action == SitePermissions.BLOCK) {
               // Temporarily store BLOCK permissions only.
               // SitePermissions does not consider subframes when storing temporary
               // permissions on a tab, thus storing ALLOW could be exploited.
-              SitePermissions.set(this.principal.URI,
-                                  this.permissionKey,
-                                  promptAction.action,
-                                  SitePermissions.SCOPE_TEMPORARY,
-                                  this.browser);
+              SitePermissions.setForPrincipal(this.principal,
+                                              this.permissionKey,
+                                              promptAction.action,
+                                              SitePermissions.SCOPE_TEMPORARY,
+                                              this.browser);
             }
 
             // Grant permission if action is ALLOW.
             if (promptAction.action == SitePermissions.ALLOW) {
               this.allow();
             } else {
               this.cancel();
             }
--- a/suite/modules/SitePermissions.jsm
+++ b/suite/modules/SitePermissions.jsm
@@ -151,32 +151,48 @@ var SitePermissions = {
   SCOPE_REQUEST: "{SitePermissions.SCOPE_REQUEST}",
   SCOPE_TEMPORARY: "{SitePermissions.SCOPE_TEMPORARY}",
   SCOPE_SESSION: "{SitePermissions.SCOPE_SESSION}",
   SCOPE_PERSISTENT: "{SitePermissions.SCOPE_PERSISTENT}",
 
   _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) {
+    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;
@@ -215,17 +231,17 @@ var SitePermissions = {
   getAllForBrowser(browser) {
     let permissions = {};
 
     for (let permission of TemporaryBlockedPermissions.getAll(browser)) {
       permission.scope = this.SCOPE_TEMPORARY;
       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
@@ -243,30 +259,44 @@ var SitePermissions = {
    *           - label: the localized label
    */
   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.
+   * URI.
    *
    * @param {nsIURI} uri
    *        The URI to check.
    *
    * @return {boolean} if the URI is supported.
    */
   isSupportedURI(uri) {
     return uri && ["file", "http", "https", "moz-extension"].includes(uri.scheme);
   },
 
   /**
+   * Checks whether a UI for managing permissions should be exposed for a given
+   * principal.
+   *
+   * @param {nsIPrincipal} principal
+   *        The principal to check.
+   *
+   * @return {boolean} if the principal is supported.
+   */
+  isSupportedPrincipal(principal) {
+    return principal && principal.URI &&
+      ["file", "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() {
     return Object.keys(gPermissionObject);
   },
 
@@ -344,25 +374,51 @@ var SitePermissions = {
    *
    * @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)
    */
   get(uri, permissionID, browser) {
+    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;
         }
       }
@@ -378,16 +434,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
@@ -396,78 +453,122 @@ var SitePermissions = {
    *        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!
    */
   set(uri, permissionID, state, scope = this.SCOPE_PERSISTENT, browser = null) {
+    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 (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 "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 TemporaryBlockedPermissions that temporarily blocks for the
       // entire browser, but temporarily allows only for specific frames.
       if (state != this.BLOCK) {
         throw "'Block' is the only permission we can save temporarily on a browser";
       }
 
       if (!browser) {
         throw "TEMPORARY scoped permissions require a browser object";
       }
 
       TemporaryBlockedPermissions.set(browser, permissionID);
 
       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;
       }
 
-      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 (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);
 
     // TemporaryBlockedPermissions.get() deletes expired permissions automatically,
     if (TemporaryBlockedPermissions.get(browser, permissionID)) {
       // If it exists but has not expired, remove it explicitly.
       TemporaryBlockedPermissions.remove(browser, permissionID);
       // Send a PermissionStateChange event only if the permission hasn't expired.
       browser.dispatchEvent(new browser.ownerGlobal
                                        .CustomEvent("PermissionStateChange"));