tor-browser

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

IPProtectionService.sys.mjs (7135B)


      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 { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
      6 
      7 const lazy = {};
      8 
      9 ChromeUtils.defineESModuleGetters(lazy, {
     10  GuardianClient:
     11    "moz-src:///browser/components/ipprotection/GuardianClient.sys.mjs",
     12  IPPEnrollAndEntitleManager:
     13    "moz-src:///browser/components/ipprotection/IPPEnrollAndEntitleManager.sys.mjs",
     14  IPPHelpers:
     15    "moz-src:///browser/components/ipprotection/IPProtectionHelpers.sys.mjs",
     16  IPPNimbusHelper:
     17    "moz-src:///browser/components/ipprotection/IPPNimbusHelper.sys.mjs",
     18  IPPOptOutHelper:
     19    "moz-src:///browser/components/ipprotection/IPPOptOutHelper.sys.mjs",
     20  IPPSignInWatcher:
     21    "moz-src:///browser/components/ipprotection/IPPSignInWatcher.sys.mjs",
     22  IPPStartupCache:
     23    "moz-src:///browser/components/ipprotection/IPPStartupCache.sys.mjs",
     24  IPPVPNAddonHelper:
     25    "moz-src:///browser/components/ipprotection/IPPVPNAddonHelper.sys.mjs",
     26  SpecialMessageActions:
     27    "resource://messaging-system/lib/SpecialMessageActions.sys.mjs",
     28 });
     29 
     30 import { SIGNIN_DATA } from "chrome://browser/content/ipprotection/ipprotection-constants.mjs";
     31 
     32 const ENABLED_PREF = "browser.ipProtection.enabled";
     33 
     34 /**
     35 * @typedef {object} IPProtectionStates
     36 *  List of the possible states of the IPProtectionService.
     37 * @property {string} UNINITIALIZED
     38 *  The service has not been initialized yet.
     39 * @property {string} UNAVAILABLE
     40 *  The user is not eligible (via nimbus) or still not signed in. No UI is available.
     41 * @property {string} UNAUTHENTICATED
     42 *  The user is signed out but eligible (via nimbus). The panel should show the login view.
     43 * @property {string} OPTED_OUT
     44 *  The user has opted out from using VPN. The toolbar icon and panel should not be visible.
     45 * @property {string} READY
     46 *  Ready to be activated.
     47 *
     48 * Note: If you update this list of states, make sure to update the
     49 * corresponding documentation in the `docs` folder as well.
     50 */
     51 export const IPProtectionStates = Object.freeze({
     52  UNINITIALIZED: "uninitialized",
     53  UNAVAILABLE: "unavailable",
     54  UNAUTHENTICATED: "unauthenticated",
     55  OPTED_OUT: "optedout",
     56  READY: "ready",
     57 });
     58 
     59 /**
     60 * A singleton service that manages proxy integration and backend functionality.
     61 *
     62 * @fires IPProtectionServiceSingleton#"IPProtectionService:StateChanged"
     63 *  When the proxy state machine changes state. Check the `state` attribute to
     64 *  know the current state.
     65 */
     66 class IPProtectionServiceSingleton extends EventTarget {
     67  #state = IPProtectionStates.UNINITIALIZED;
     68 
     69  guardian = null;
     70 
     71  #helpers = null;
     72 
     73  /**
     74   * Returns the state of the service. See the description of the state
     75   * machine.
     76   *
     77   * @returns {string} - the current state from IPProtectionStates.
     78   */
     79  get state() {
     80    return this.#state;
     81  }
     82 
     83  constructor() {
     84    super();
     85 
     86    this.guardian = new lazy.GuardianClient();
     87 
     88    this.updateState = this.#updateState.bind(this);
     89    this.setState = this.#setState.bind(this);
     90 
     91    this.#helpers = lazy.IPPHelpers;
     92  }
     93 
     94  /**
     95   * Setups the IPProtectionService if enabled early during the firefox startup
     96   * phases.
     97   */
     98  async maybeEarlyInit() {
     99    if (
    100      this.featureEnabled &&
    101      Services.prefs.getBoolPref("browser.ipProtection.autoStartEnabled")
    102    ) {
    103      await this.init();
    104    }
    105  }
    106 
    107  /**
    108   * Setups the IPProtectionService if enabled.
    109   */
    110  async init() {
    111    if (
    112      this.#state !== IPProtectionStates.UNINITIALIZED ||
    113      !this.featureEnabled
    114    ) {
    115      return;
    116    }
    117 
    118    this.#helpers.forEach(helper => helper.init());
    119 
    120    this.#updateState();
    121 
    122    if (lazy.IPPStartupCache.isStartupCompleted) {
    123      this.initOnStartupCompleted();
    124    }
    125  }
    126 
    127  /**
    128   * Removes the UI widget.
    129   */
    130  uninit() {
    131    if (this.#state === IPProtectionStates.UNINITIALIZED) {
    132      return;
    133    }
    134 
    135    this.#helpers.forEach(helper => helper.uninit());
    136 
    137    this.#setState(IPProtectionStates.UNINITIALIZED);
    138  }
    139 
    140  async initOnStartupCompleted() {
    141    await Promise.allSettled(
    142      this.#helpers.map(helper => helper.initOnStartupCompleted())
    143    );
    144  }
    145 
    146  async startLoginFlow(browser) {
    147    return lazy.SpecialMessageActions.fxaSignInFlow(SIGNIN_DATA, browser);
    148  }
    149 
    150  /**
    151   * Recomputes the current state synchronously using the latest helper data.
    152   * Callers should update their own inputs before invoking this.
    153   */
    154  #updateState() {
    155    this.#setState(this.#computeState());
    156  }
    157 
    158  /**
    159   * Checks observed statuses or with Guardian to get the current state.
    160   *
    161   * @returns {Promise<IPProtectionStates>}
    162   */
    163  #computeState() {
    164    // The IPP feature is disabled.
    165    if (!this.featureEnabled) {
    166      return IPProtectionStates.UNINITIALIZED;
    167    }
    168 
    169    if (lazy.IPPOptOutHelper.optedOut) {
    170      return IPProtectionStates.OPTED_OUT;
    171    }
    172 
    173    // Maybe we have to use the cached state, because we are not initialized yet.
    174    if (!lazy.IPPStartupCache.isStartupCompleted) {
    175      return lazy.IPPStartupCache.state;
    176    }
    177 
    178    // If the VPN add-on is installed...
    179    if (
    180      lazy.IPPVPNAddonHelper.vpnAddonDetected &&
    181      lazy.IPPEnrollAndEntitleManager.hasUpgraded
    182    ) {
    183      return IPProtectionStates.UNAVAILABLE;
    184    }
    185 
    186    // For non authenticated users, we can check if they are eligible (the UI
    187    // is shown and they have to login) or we don't know yet their current
    188    // enroll state (no UI is shown).
    189    let eligible = lazy.IPPNimbusHelper.isEligible;
    190    if (!lazy.IPPSignInWatcher.isSignedIn) {
    191      return !eligible
    192        ? IPProtectionStates.UNAVAILABLE
    193        : IPProtectionStates.UNAUTHENTICATED;
    194    }
    195 
    196    // Check if the current account is enrolled and has an entitlement.
    197    if (!lazy.IPPEnrollAndEntitleManager.isEnrolledAndEntitled && !eligible) {
    198      return IPProtectionStates.UNAVAILABLE;
    199    }
    200 
    201    // The proxy can be activated.
    202    return IPProtectionStates.READY;
    203  }
    204 
    205  /**
    206   * Sets the current state and triggers the state change event if needed.
    207   *
    208   * @param {IPProtectionStates} newState
    209   */
    210  #setState(newState) {
    211    if (newState === this.#state) {
    212      return;
    213    }
    214 
    215    let prevState = this.#state;
    216    this.#state = newState;
    217 
    218    this.#stateChanged(newState, prevState);
    219  }
    220 
    221  /**
    222   * Handles side effects of a state change and dispatches the StateChanged event.
    223   *
    224   * @param {IPProtectionStates} state
    225   * @param {IPProtectionStates} prevState
    226   */
    227  #stateChanged(state, prevState) {
    228    this.dispatchEvent(
    229      new CustomEvent("IPProtectionService:StateChanged", {
    230        bubbles: true,
    231        composed: true,
    232        detail: {
    233          state,
    234          prevState,
    235        },
    236      })
    237    );
    238  }
    239 }
    240 
    241 const IPProtectionService = new IPProtectionServiceSingleton();
    242 
    243 XPCOMUtils.defineLazyPreferenceGetter(
    244  IPProtectionService,
    245  "featureEnabled",
    246  ENABLED_PREF,
    247  false,
    248  (_pref, _oldVal, featureEnabled) => {
    249    if (featureEnabled) {
    250      IPProtectionService.init();
    251    } else {
    252      IPProtectionService.uninit();
    253    }
    254  }
    255 );
    256 
    257 export { IPProtectionService };