tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

RemotePermissionService.sys.mjs (6095B)


      1 /* This Source Code Form is subject to the terms of the Mozilla Public
      2 * License, v. 2.0. If a copy of the MPL was not distributed with this
      3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      4 
      5 import { RemoteSettings } from "resource://services-settings/remote-settings.sys.mjs";
      6 
      7 const COLLECTION_NAME = "remote-permissions";
      8 
      9 /**
     10 * Allowlist of permission types and values allowed to be set through remote
     11 * settings. In this map, the key is the permission type, while the value is an
     12 * array of allowed permission values/capabilities allowed to be set. Possible
     13 * values for most permissions are:
     14 *
     15 * - Ci.nsIPermissionManager.ALLOW_ACTION
     16 * - Ci.nsIPermissionManager.DENY_ACTION
     17 * - Ci.nsIPermissionManager.PROMPT_ACTION
     18 * - "*" (Allows all values)
     19 *
     20 * Permission types with custom permission values (like
     21 * https-only-load-insecure) may include different values. Only change this
     22 * value with a review from #permissions-reviewers.
     23 */
     24 const ALLOWED_PERMISSION_VALUES = {
     25  "https-only-load-insecure": [
     26    Ci.nsIHttpsOnlyModePermission.HTTPSFIRST_LOAD_INSECURE_ALLOW,
     27  ],
     28  localhost: ["*"],
     29  "local-network": ["*"],
     30 };
     31 
     32 /**
     33 * See nsIRemotePermissionService.idl
     34 */
     35 export class RemotePermissionService {
     36  classId = Components.ID("{a4b1b3b1-b68a-4129-aa2f-eb086162a8c7}");
     37  QueryInterface = ChromeUtils.generateQI(["nsIRemotePermissionService"]);
     38 
     39  #rs = RemoteSettings(COLLECTION_NAME);
     40  #initialized = Promise.withResolvers();
     41  #allowedPermissionValues = ALLOWED_PERMISSION_VALUES;
     42 
     43  constructor() {
     44    this.init();
     45  }
     46 
     47  /**
     48   * Asynchonously import all default permissions from remote settings into the
     49   * permission manager and set up remote settings event listener to keep
     50   * remote permissions in sync.
     51   */
     52  async init() {
     53    try {
     54      if (Services.startup.shuttingDown) {
     55        return;
     56      }
     57 
     58      if (
     59        !Services.prefs.getBoolPref("permissions.manager.remote.enabled", false)
     60      ) {
     61        return;
     62      }
     63 
     64      let remotePermissions = await this.#rs.get();
     65      for (const permission of remotePermissions) {
     66        this.#addDefaultPermission(permission);
     67      }
     68 
     69      this.#rs.on("sync", this.#onSync.bind(this));
     70 
     71      this.#initialized.resolve();
     72    } catch (e) {
     73      this.#initialized.reject(e);
     74      throw e;
     75    }
     76  }
     77 
     78  get isInitialized() {
     79    return this.#initialized.promise;
     80  }
     81 
     82  get testAllowedPermissionValues() {
     83    return this.#allowedPermissionValues;
     84  }
     85 
     86  set testAllowedPermissionValues(allowedPermissionValues) {
     87    Cu.crashIfNotInAutomation();
     88    this.#allowedPermissionValues = allowedPermissionValues;
     89  }
     90 
     91  // eslint-disable-next-line jsdoc/require-param
     92  /**
     93   * Callback for the "sync" event from remote settings. This function will
     94   * receive the created, updated and deleted permissions from remote settings,
     95   * and will update the permission manager accordingly.
     96   */
     97  #onSync({ data: { created = [], updated = [], deleted = [] } }) {
     98    const toBeDeletedPermissions = [
     99      // Delete permissions that got deleted in remote settings.
    100      ...deleted,
    101      // If an existing entry got updated in remote settings, but the origin or
    102      // type changed, we can not just update it, as permissions are identified
    103      // by origin and type in the permission manager. Instead, we need to
    104      // remove the old permission and add a new one.
    105      ...updated
    106        .filter(
    107          ({
    108            old: { origin: oldOrigin, type: oldType },
    109            new: { origin: newOrigin, type: newType },
    110          }) => oldOrigin != newOrigin || oldType != newType
    111        )
    112        .map(({ old }) => old),
    113    ];
    114 
    115    const toBeAddedPermissions = [
    116      // Add newly created permissions.
    117      ...created,
    118      // "Add" permissions updated in remote settings (the permission manager
    119      // will automatically update the existing default permission instead of
    120      // creating a new one if the permission origin and type match).
    121      ...updated.map(({ new: newPermission }) => newPermission),
    122      // Delete permissions by "adding" them with value UNKNOWN_ACTION.
    123      ...toBeDeletedPermissions.map(({ origin, type }) => ({
    124        origin,
    125        type,
    126        capability: Ci.nsIPermissionManager.UNKNOWN_ACTION,
    127      })),
    128    ];
    129 
    130    for (const permission of toBeAddedPermissions) {
    131      this.#addDefaultPermission(permission);
    132    }
    133  }
    134 
    135  /**
    136   * Check if a permission type and value is allowed to be set through remote
    137   * settings, based on the ALLOWED_PERMISSION_VALUES allowlist.
    138   *
    139   * @param {string} type       Permission type to check
    140   * @param {string} capability Permission capability to check
    141   * @returns {boolean}
    142   */
    143  #isAllowed(type, capability) {
    144    if (!this.#allowedPermissionValues[type]) {
    145      if (this.#allowedPermissionValues["*"]) {
    146        this.#allowedPermissionValues[type] =
    147          this.#allowedPermissionValues["*"];
    148      } else {
    149        return false;
    150      }
    151    }
    152 
    153    return (
    154      this.#allowedPermissionValues[type].includes("*") ||
    155      this.#allowedPermissionValues[type].includes(capability) ||
    156      capability === Ci.nsIPermissionManager.UNKNOWN_ACTION
    157    );
    158  }
    159 
    160  /**
    161   * Add a default permission to the permission manager.
    162   *
    163   * @param {object} permission            The permission to add
    164   * @param {string} permission.origin     Origin string of the permission
    165   * @param {string} permission.type       Type of the permission
    166   * @param {number} permission.capability Capability of the permission
    167   */
    168  #addDefaultPermission({ origin, type, capability }) {
    169    if (!this.#isAllowed(type, capability)) {
    170      console.error(
    171        `Remote Settings contain default permission of disallowed type '${type}' with value '${capability}' for origin '${origin}', skipping import`
    172      );
    173      return;
    174    }
    175 
    176    try {
    177      let principal = Services.scriptSecurityManager.createContentPrincipal(
    178        Services.io.newURI(origin),
    179        {}
    180      );
    181      Services.perms.addDefaultFromPrincipal(principal, type, capability);
    182    } catch (e) {
    183      console.error(e);
    184    }
    185  }
    186 }