tor-browser

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

test_oauth_tokens.js (7232B)


      1 /* Any copyright is dedicated to the Public Domain.
      2 * http://creativecommons.org/publicdomain/zero/1.0/ */
      3 
      4 "use strict";
      5 
      6 const { FxAccounts } = ChromeUtils.importESModule(
      7  "resource://gre/modules/FxAccounts.sys.mjs"
      8 );
      9 const { FxAccountsClient } = ChromeUtils.importESModule(
     10  "resource://gre/modules/FxAccountsClient.sys.mjs"
     11 );
     12 var { AccountState } = ChromeUtils.importESModule(
     13  "resource://gre/modules/FxAccounts.sys.mjs"
     14 );
     15 
     16 function promiseNotification(topic) {
     17  return new Promise(resolve => {
     18    let observe = () => {
     19      Services.obs.removeObserver(observe, topic);
     20      resolve();
     21    };
     22    Services.obs.addObserver(observe, topic);
     23  });
     24 }
     25 
     26 // Just enough mocks so we can avoid hawk and storage.
     27 function MockStorageManager() {}
     28 
     29 MockStorageManager.prototype = {
     30  promiseInitialized: Promise.resolve(),
     31 
     32  initialize(accountData) {
     33    this.accountData = accountData;
     34  },
     35 
     36  finalize() {
     37    return Promise.resolve();
     38  },
     39 
     40  getAccountData() {
     41    return Promise.resolve(this.accountData);
     42  },
     43 
     44  updateAccountData(updatedFields) {
     45    for (let [name, value] of Object.entries(updatedFields)) {
     46      if (value == null) {
     47        delete this.accountData[name];
     48      } else {
     49        this.accountData[name] = value;
     50      }
     51    }
     52    return Promise.resolve();
     53  },
     54 
     55  deleteAccountData() {
     56    this.accountData = null;
     57    return Promise.resolve();
     58  },
     59 };
     60 
     61 function MockFxAccountsClient(activeTokens) {
     62  this._email = "nobody@example.com";
     63  this._verified = false;
     64 
     65  this.accountStatus = function (uid) {
     66    return Promise.resolve(!!uid && !this._deletedOnServer);
     67  };
     68 
     69  this.signOut = function () {
     70    return Promise.resolve();
     71  };
     72  this.registerDevice = function () {
     73    return Promise.resolve();
     74  };
     75  this.updateDevice = function () {
     76    return Promise.resolve();
     77  };
     78  this.signOutAndDestroyDevice = function () {
     79    return Promise.resolve();
     80  };
     81  this.getDeviceList = function () {
     82    return Promise.resolve();
     83  };
     84  this.accessTokenWithSessionToken = function (
     85    sessionTokenHex,
     86    clientId,
     87    scope,
     88    ttl
     89  ) {
     90    let token = `token${this.numTokenFetches}`;
     91    if (ttl) {
     92      token += `-ttl-${ttl}`;
     93    }
     94    this.numTokenFetches += 1;
     95    this.activeTokens.add(token);
     96    print("accessTokenWithSessionToken returning token", token);
     97    return Promise.resolve({ access_token: token, ttl });
     98  };
     99  this.oauthDestroy = sinon.stub().callsFake((_clientId, token) => {
    100    this.activeTokens.delete(token);
    101    return Promise.resolve();
    102  });
    103 
    104  // Test only stuff.
    105  this.activeTokens = activeTokens;
    106  this.numTokenFetches = 0;
    107 
    108  FxAccountsClient.apply(this);
    109 }
    110 
    111 MockFxAccountsClient.prototype = {};
    112 Object.setPrototypeOf(
    113  MockFxAccountsClient.prototype,
    114  FxAccountsClient.prototype
    115 );
    116 
    117 function MockFxAccounts() {
    118  // The FxA "auth" and "oauth" servers both share the same db of tokens,
    119  // so we need to simulate the same here in the tests.
    120  const activeTokens = new Set();
    121  return new FxAccounts({
    122    fxAccountsClient: new MockFxAccountsClient(activeTokens),
    123    newAccountState(credentials) {
    124      // we use a real accountState but mocked storage.
    125      let storage = new MockStorageManager();
    126      storage.initialize(credentials);
    127      return new AccountState(storage);
    128    },
    129    _getDeviceName() {
    130      return "mock device name";
    131    },
    132    fxaPushService: {
    133      registerPushEndpoint() {
    134        return new Promise(resolve => {
    135          resolve({
    136            endpoint: "http://mochi.test:8888",
    137          });
    138        });
    139      },
    140    },
    141  });
    142 }
    143 
    144 async function createMockFxA() {
    145  let fxa = new MockFxAccounts();
    146  let credentials = {
    147    email: "foo@example.com",
    148    uid: "1234@lcip.org",
    149    sessionToken: "dead",
    150    scopedKeys: {
    151      [SCOPE_OLD_SYNC]: {
    152        kid: "key id for sync key",
    153        k: "key material for sync key",
    154        kty: "oct",
    155      },
    156    },
    157    verified: true,
    158  };
    159 
    160  await fxa._internal.setSignedInUser(credentials);
    161  return fxa;
    162 }
    163 
    164 // The tests.
    165 
    166 add_task(async function testRevoke() {
    167  let tokenOptions = { scope: "test-scope" };
    168  let fxa = await createMockFxA();
    169  let client = fxa._internal.fxAccountsClient;
    170 
    171  // get our first token and check we hit the mock.
    172  let token1 = await fxa.getOAuthToken(tokenOptions);
    173  equal(client.numTokenFetches, 1);
    174  equal(client.activeTokens.size, 1);
    175  ok(token1, "got a token");
    176  equal(token1, "token0");
    177 
    178  // drop the new token from our cache.
    179  await fxa.removeCachedOAuthToken({ token: token1 });
    180  ok(client.oauthDestroy.calledOnce);
    181 
    182  // the revoke should have been successful.
    183  equal(client.activeTokens.size, 0);
    184  // fetching it again hits the server.
    185  let token2 = await fxa.getOAuthToken(tokenOptions);
    186  equal(client.numTokenFetches, 2);
    187  equal(client.activeTokens.size, 1);
    188  ok(token2, "got a token");
    189  notEqual(token1, token2, "got a different token");
    190 });
    191 
    192 add_task(async function testSignOutDestroysTokens() {
    193  let fxa = await createMockFxA();
    194  let client = fxa._internal.fxAccountsClient;
    195 
    196  // get our first token and check we hit the mock.
    197  let token1 = await fxa.getOAuthToken({ scope: "test-scope" });
    198  equal(client.numTokenFetches, 1);
    199  equal(client.activeTokens.size, 1);
    200  ok(token1, "got a token");
    201 
    202  // get another
    203  let token2 = await fxa.getOAuthToken({ scope: "test-scope-2" });
    204  equal(client.numTokenFetches, 2);
    205  equal(client.activeTokens.size, 2);
    206  ok(token2, "got a token");
    207  notEqual(token1, token2, "got a different token");
    208 
    209  // FxA fires an observer when the "background" signout is complete.
    210  let signoutComplete = promiseNotification("testhelper-fxa-signout-complete");
    211  // now sign out - they should be removed.
    212  await fxa.signOut();
    213  await signoutComplete;
    214  ok(client.oauthDestroy.calledTwice);
    215  // No active tokens left.
    216  equal(client.activeTokens.size, 0);
    217 });
    218 
    219 add_task(async function testTokenRaces() {
    220  // Here we do 2 concurrent fetches each for 2 different token scopes (ie,
    221  // 4 token fetches in total).
    222  // This should provoke a potential race in the token fetching but we use
    223  // a map of in-flight token fetches, so we should still only perform 2
    224  // fetches, but each of the 4 calls should resolve with the correct values.
    225  let fxa = await createMockFxA();
    226  let client = fxa._internal.fxAccountsClient;
    227 
    228  let results = await Promise.all([
    229    fxa.getOAuthToken({ scope: "test-scope" }),
    230    fxa.getOAuthToken({ scope: "test-scope" }),
    231    fxa.getOAuthToken({ scope: "test-scope-2" }),
    232    fxa.getOAuthToken({ scope: "test-scope-2" }),
    233  ]);
    234 
    235  equal(client.numTokenFetches, 2, "should have fetched 2 tokens.");
    236 
    237  // Should have 2 unique tokens
    238  results.sort();
    239  equal(results[0], results[1]);
    240  equal(results[2], results[3]);
    241  // should be 2 active.
    242  equal(client.activeTokens.size, 2);
    243  await fxa.removeCachedOAuthToken({ token: results[0] });
    244  equal(client.activeTokens.size, 1);
    245  await fxa.removeCachedOAuthToken({ token: results[2] });
    246  equal(client.activeTokens.size, 0);
    247  ok(client.oauthDestroy.calledTwice);
    248 });
    249 
    250 add_task(async function testTokenTTL() {
    251  // This tests the TTL option passed into the method
    252  let fxa = await createMockFxA();
    253  let token = await fxa.getOAuthToken({ scope: "test-ttl", ttl: 1000 });
    254  equal(token, "token0-ttl-1000");
    255 });