tor-browser

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

utils.sys.mjs (9593B)


      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 file,
      3 * You can obtain one at http://mozilla.org/MPL/2.0/. */
      4 
      5 import { CommonUtils } from "resource://services-common/utils.sys.mjs";
      6 
      7 import { Assert } from "resource://testing-common/Assert.sys.mjs";
      8 
      9 import { initTestLogging } from "resource://testing-common/services/common/logging.sys.mjs";
     10 import {
     11  FakeCryptoService,
     12  FakeFilesystemService,
     13  FakeGUIDService,
     14  fakeSHA256HMAC,
     15 } from "resource://testing-common/services/sync/fakeservices.sys.mjs";
     16 
     17 import {
     18  FxAccounts,
     19  AccountState,
     20 } from "resource://gre/modules/FxAccounts.sys.mjs";
     21 import { FxAccountsClient } from "resource://gre/modules/FxAccountsClient.sys.mjs";
     22 
     23 import { SCOPE_APP_SYNC } from "resource://gre/modules/FxAccountsCommon.sys.mjs";
     24 
     25 // A mock "storage manager" for FxAccounts that doesn't actually write anywhere.
     26 export function MockFxaStorageManager() {}
     27 
     28 MockFxaStorageManager.prototype = {
     29  promiseInitialized: Promise.resolve(),
     30 
     31  initialize(accountData) {
     32    this.accountData = accountData;
     33  },
     34 
     35  finalize() {
     36    return Promise.resolve();
     37  },
     38 
     39  getAccountData(fields = null) {
     40    let result;
     41    if (!this.accountData) {
     42      result = null;
     43    } else if (fields == null) {
     44      // can't use cloneInto as the keys get upset...
     45      result = {};
     46      for (let field of Object.keys(this.accountData)) {
     47        result[field] = this.accountData[field];
     48      }
     49    } else {
     50      if (!Array.isArray(fields)) {
     51        fields = [fields];
     52      }
     53      result = {};
     54      for (let field of fields) {
     55        result[field] = this.accountData[field];
     56      }
     57    }
     58    return Promise.resolve(result);
     59  },
     60 
     61  updateAccountData(updatedFields) {
     62    for (let [name, value] of Object.entries(updatedFields)) {
     63      if (value == null) {
     64        delete this.accountData[name];
     65      } else {
     66        this.accountData[name] = value;
     67      }
     68    }
     69    return Promise.resolve();
     70  },
     71 
     72  deleteAccountData() {
     73    this.accountData = null;
     74    return Promise.resolve();
     75  },
     76 };
     77 
     78 /**
     79 * First wait >100ms (nsITimers can take up to that much time to fire, so
     80 * we can account for the timer in delayedAutoconnect) and then two event
     81 * loop ticks (to account for the CommonUtils.nextTick() in autoConnect).
     82 */
     83 export function waitForZeroTimer(callback) {
     84  let ticks = 2;
     85  function wait() {
     86    if (ticks) {
     87      ticks -= 1;
     88      CommonUtils.nextTick(wait);
     89      return;
     90    }
     91    callback();
     92  }
     93  CommonUtils.namedTimer(wait, 150, {}, "timer");
     94 }
     95 
     96 export var promiseZeroTimer = function () {
     97  return new Promise(resolve => {
     98    waitForZeroTimer(resolve);
     99  });
    100 };
    101 
    102 export var promiseNamedTimer = function (wait, thisObj, name) {
    103  return new Promise(resolve => {
    104    CommonUtils.namedTimer(resolve, wait, thisObj, name);
    105  });
    106 };
    107 
    108 // Return an identity configuration suitable for testing with our identity
    109 // providers.  |overrides| can specify overrides for any default values.
    110 // |server| is optional, but if specified, will be used to form the cluster
    111 // URL for the FxA identity.
    112 export var makeIdentityConfig = function (overrides) {
    113  // first setup the defaults.
    114  let result = {
    115    // Username used in both fxaccount and sync identity configs.
    116    username: "foo",
    117    // fxaccount specific credentials.
    118    fxaccount: {
    119      user: {
    120        email: "foo",
    121        scopedKeys: {
    122          [SCOPE_APP_SYNC]: {
    123            kid: "1234567890123-u7u7u7u7u7u7u7u7u7u7uw",
    124            k: "qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqg",
    125            kty: "oct",
    126          },
    127        },
    128        sessionToken: "sessionToken",
    129        uid: "a".repeat(32),
    130        verified: true,
    131      },
    132      token: {
    133        endpoint: null,
    134        duration: 300,
    135        id: "id",
    136        key: "key",
    137        hashed_fxa_uid: "f".repeat(32), // used during telemetry validation
    138        // uid will be set to the username.
    139      },
    140    },
    141  };
    142 
    143  // Now handle any specified overrides.
    144  if (overrides) {
    145    if (overrides.username) {
    146      result.username = overrides.username;
    147    }
    148    if (overrides.fxaccount) {
    149      // TODO: allow just some attributes to be specified
    150      result.fxaccount = overrides.fxaccount;
    151    }
    152    if (overrides.node_type) {
    153      result.fxaccount.token.node_type = overrides.node_type;
    154    }
    155  }
    156  return result;
    157 };
    158 
    159 export var makeFxAccountsInternalMock = function (config) {
    160  return {
    161    newAccountState(credentials) {
    162      // We only expect this to be called with null indicating the (mock)
    163      // storage should be read.
    164      if (credentials) {
    165        throw new Error("Not expecting to have credentials passed");
    166      }
    167      let storageManager = new MockFxaStorageManager();
    168      storageManager.initialize(config.fxaccount.user);
    169      let accountState = new AccountState(storageManager);
    170      return accountState;
    171    },
    172    getOAuthToken: () => Promise.resolve("some-access-token"),
    173    destroyOAuthToken: () => Promise.resolve(),
    174    keys: {
    175      getScopedKeys: () =>
    176        Promise.resolve({
    177          [SCOPE_APP_SYNC]: {
    178            identifier: SCOPE_APP_SYNC,
    179            keyRotationSecret:
    180              "0000000000000000000000000000000000000000000000000000000000000000",
    181            keyRotationTimestamp: 1510726317123,
    182          },
    183        }),
    184    },
    185    profile: {
    186      getProfile() {
    187        return null;
    188      },
    189    },
    190  };
    191 };
    192 
    193 // Configure an instance of an FxAccount identity provider with the specified
    194 // config (or the default config if not specified).
    195 export var configureFxAccountIdentity = function (
    196  authService,
    197  config = makeIdentityConfig(),
    198  fxaInternal = makeFxAccountsInternalMock(config)
    199 ) {
    200  // until we get better test infrastructure for bid_identity, we set the
    201  // signedin user's "email" to the username, simply as many tests rely on this.
    202  config.fxaccount.user.email = config.username;
    203 
    204  let fxa = new FxAccounts(fxaInternal);
    205 
    206  let MockFxAccountsClient = function () {
    207    FxAccountsClient.apply(this);
    208  };
    209  MockFxAccountsClient.prototype = {
    210    accountStatus() {
    211      return Promise.resolve(true);
    212    },
    213  };
    214  Object.setPrototypeOf(
    215    MockFxAccountsClient.prototype,
    216    FxAccountsClient.prototype
    217  );
    218  let mockFxAClient = new MockFxAccountsClient();
    219  fxa._internal._fxAccountsClient = mockFxAClient;
    220 
    221  let mockTSC = {
    222    // TokenServerClient
    223    async getTokenUsingOAuth(url, oauthToken) {
    224      Assert.equal(
    225        url,
    226        Services.prefs.getStringPref("identity.sync.tokenserver.uri")
    227      );
    228      Assert.ok(oauthToken, "oauth token present");
    229      config.fxaccount.token.uid = config.username;
    230      return config.fxaccount.token;
    231    },
    232  };
    233  authService._fxaService = fxa;
    234  authService._tokenServerClient = mockTSC;
    235  // Set the "account" of the sync auth manager to be the "email" of the
    236  // logged in user of the mockFXA service.
    237  authService._signedInUser = config.fxaccount.user;
    238  authService._account = config.fxaccount.user.email;
    239 };
    240 
    241 export var configureIdentity = async function (identityOverrides, server) {
    242  let config = makeIdentityConfig(identityOverrides, server);
    243  // Must be imported after the identity configuration is set up.
    244  let { Service } = ChromeUtils.importESModule(
    245    "resource://services-sync/service.sys.mjs"
    246  );
    247 
    248  // If a server was specified, ensure FxA has a correct cluster URL available.
    249  if (server && !config.fxaccount.token.endpoint) {
    250    let ep = server.baseURI;
    251    if (!ep.endsWith("/")) {
    252      ep += "/";
    253    }
    254    ep += "1.1/" + config.username + "/";
    255    config.fxaccount.token.endpoint = ep;
    256  }
    257 
    258  configureFxAccountIdentity(Service.identity, config);
    259  Services.prefs.setStringPref("services.sync.username", config.username);
    260  // many of these tests assume all the auth stuff is setup and don't hit
    261  // a path which causes that auth to magically happen - so do it now.
    262  await Service.identity._ensureValidToken();
    263 
    264  // and cheat to avoid requiring each test do an explicit login - give it
    265  // a cluster URL.
    266  if (config.fxaccount.token.endpoint) {
    267    Service.clusterURL = config.fxaccount.token.endpoint;
    268  }
    269 };
    270 
    271 export function syncTestLogging(level = "Trace") {
    272  let logStats = initTestLogging(level);
    273  Services.prefs.setStringPref("services.sync.log.logger", level);
    274  Services.prefs.setStringPref("services.sync.log.logger.engine", "");
    275  return logStats;
    276 }
    277 
    278 export var SyncTestingInfrastructure = async function (server, username) {
    279  let config = makeIdentityConfig({ username });
    280  await configureIdentity(config, server);
    281  return {
    282    logStats: syncTestLogging(),
    283    fakeFilesystem: new FakeFilesystemService({}),
    284    fakeGUIDService: new FakeGUIDService(),
    285    fakeCryptoService: new FakeCryptoService(),
    286  };
    287 };
    288 
    289 /**
    290 * Turn WBO cleartext into fake "encrypted" payload as it goes over the wire.
    291 */
    292 export function encryptPayload(cleartext) {
    293  if (typeof cleartext == "object") {
    294    cleartext = JSON.stringify(cleartext);
    295  }
    296 
    297  return {
    298    ciphertext: cleartext, // ciphertext == cleartext with fake crypto
    299    IV: "irrelevant",
    300    hmac: fakeSHA256HMAC(cleartext),
    301  };
    302 }
    303 
    304 export var sumHistogram = function (name, options = {}) {
    305  let histogram = options.key
    306    ? Services.telemetry.getKeyedHistogramById(name)
    307    : Services.telemetry.getHistogramById(name);
    308  let snapshot = histogram.snapshot();
    309  let sum = -Infinity;
    310  if (snapshot) {
    311    if (options.key && snapshot[options.key]) {
    312      sum = snapshot[options.key].sum;
    313    } else {
    314      sum = snapshot.sum;
    315    }
    316  }
    317  histogram.clear();
    318  return sum;
    319 };