summaryrefslogtreecommitdiffstats
path: root/services/fxaccounts/tests/xpcshell/test_oauth_tokens.js
diff options
context:
space:
mode:
Diffstat (limited to 'services/fxaccounts/tests/xpcshell/test_oauth_tokens.js')
-rw-r--r--services/fxaccounts/tests/xpcshell/test_oauth_tokens.js255
1 files changed, 255 insertions, 0 deletions
diff --git a/services/fxaccounts/tests/xpcshell/test_oauth_tokens.js b/services/fxaccounts/tests/xpcshell/test_oauth_tokens.js
new file mode 100644
index 0000000000..82f174edd1
--- /dev/null
+++ b/services/fxaccounts/tests/xpcshell/test_oauth_tokens.js
@@ -0,0 +1,255 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const { FxAccounts } = ChromeUtils.importESModule(
+ "resource://gre/modules/FxAccounts.sys.mjs"
+);
+const { FxAccountsClient } = ChromeUtils.importESModule(
+ "resource://gre/modules/FxAccountsClient.sys.mjs"
+);
+var { AccountState } = ChromeUtils.importESModule(
+ "resource://gre/modules/FxAccounts.sys.mjs"
+);
+
+function promiseNotification(topic) {
+ return new Promise(resolve => {
+ let observe = () => {
+ Services.obs.removeObserver(observe, topic);
+ resolve();
+ };
+ Services.obs.addObserver(observe, topic);
+ });
+}
+
+// Just enough mocks so we can avoid hawk and storage.
+function MockStorageManager() {}
+
+MockStorageManager.prototype = {
+ promiseInitialized: Promise.resolve(),
+
+ initialize(accountData) {
+ this.accountData = accountData;
+ },
+
+ finalize() {
+ return Promise.resolve();
+ },
+
+ getAccountData() {
+ return Promise.resolve(this.accountData);
+ },
+
+ updateAccountData(updatedFields) {
+ for (let [name, value] of Object.entries(updatedFields)) {
+ if (value == null) {
+ delete this.accountData[name];
+ } else {
+ this.accountData[name] = value;
+ }
+ }
+ return Promise.resolve();
+ },
+
+ deleteAccountData() {
+ this.accountData = null;
+ return Promise.resolve();
+ },
+};
+
+function MockFxAccountsClient(activeTokens) {
+ this._email = "nobody@example.com";
+ this._verified = false;
+
+ this.accountStatus = function (uid) {
+ return Promise.resolve(!!uid && !this._deletedOnServer);
+ };
+
+ this.signOut = function () {
+ return Promise.resolve();
+ };
+ this.registerDevice = function () {
+ return Promise.resolve();
+ };
+ this.updateDevice = function () {
+ return Promise.resolve();
+ };
+ this.signOutAndDestroyDevice = function () {
+ return Promise.resolve();
+ };
+ this.getDeviceList = function () {
+ return Promise.resolve();
+ };
+ this.accessTokenWithSessionToken = function (
+ sessionTokenHex,
+ clientId,
+ scope,
+ ttl
+ ) {
+ let token = `token${this.numTokenFetches}`;
+ if (ttl) {
+ token += `-ttl-${ttl}`;
+ }
+ this.numTokenFetches += 1;
+ this.activeTokens.add(token);
+ print("accessTokenWithSessionToken returning token", token);
+ return Promise.resolve({ access_token: token, ttl });
+ };
+ this.oauthDestroy = sinon.stub().callsFake((_clientId, token) => {
+ this.activeTokens.delete(token);
+ return Promise.resolve();
+ });
+
+ // Test only stuff.
+ this.activeTokens = activeTokens;
+ this.numTokenFetches = 0;
+
+ FxAccountsClient.apply(this);
+}
+
+MockFxAccountsClient.prototype = {};
+Object.setPrototypeOf(
+ MockFxAccountsClient.prototype,
+ FxAccountsClient.prototype
+);
+
+function MockFxAccounts() {
+ // The FxA "auth" and "oauth" servers both share the same db of tokens,
+ // so we need to simulate the same here in the tests.
+ const activeTokens = new Set();
+ return new FxAccounts({
+ fxAccountsClient: new MockFxAccountsClient(activeTokens),
+ newAccountState(credentials) {
+ // we use a real accountState but mocked storage.
+ let storage = new MockStorageManager();
+ storage.initialize(credentials);
+ return new AccountState(storage);
+ },
+ _getDeviceName() {
+ return "mock device name";
+ },
+ fxaPushService: {
+ registerPushEndpoint() {
+ return new Promise(resolve => {
+ resolve({
+ endpoint: "http://mochi.test:8888",
+ });
+ });
+ },
+ },
+ });
+}
+
+async function createMockFxA() {
+ let fxa = new MockFxAccounts();
+ let credentials = {
+ email: "foo@example.com",
+ uid: "1234@lcip.org",
+ sessionToken: "dead",
+ scopedKeys: {
+ [SCOPE_OLD_SYNC]: {
+ kid: "key id for sync key",
+ k: "key material for sync key",
+ kty: "oct",
+ },
+ },
+ verified: true,
+ };
+
+ await fxa._internal.setSignedInUser(credentials);
+ return fxa;
+}
+
+// The tests.
+
+add_task(async function testRevoke() {
+ let tokenOptions = { scope: "test-scope" };
+ let fxa = await createMockFxA();
+ let client = fxa._internal.fxAccountsClient;
+
+ // get our first token and check we hit the mock.
+ let token1 = await fxa.getOAuthToken(tokenOptions);
+ equal(client.numTokenFetches, 1);
+ equal(client.activeTokens.size, 1);
+ ok(token1, "got a token");
+ equal(token1, "token0");
+
+ // drop the new token from our cache.
+ await fxa.removeCachedOAuthToken({ token: token1 });
+ ok(client.oauthDestroy.calledOnce);
+
+ // the revoke should have been successful.
+ equal(client.activeTokens.size, 0);
+ // fetching it again hits the server.
+ let token2 = await fxa.getOAuthToken(tokenOptions);
+ equal(client.numTokenFetches, 2);
+ equal(client.activeTokens.size, 1);
+ ok(token2, "got a token");
+ notEqual(token1, token2, "got a different token");
+});
+
+add_task(async function testSignOutDestroysTokens() {
+ let fxa = await createMockFxA();
+ let client = fxa._internal.fxAccountsClient;
+
+ // get our first token and check we hit the mock.
+ let token1 = await fxa.getOAuthToken({ scope: "test-scope" });
+ equal(client.numTokenFetches, 1);
+ equal(client.activeTokens.size, 1);
+ ok(token1, "got a token");
+
+ // get another
+ let token2 = await fxa.getOAuthToken({ scope: "test-scope-2" });
+ equal(client.numTokenFetches, 2);
+ equal(client.activeTokens.size, 2);
+ ok(token2, "got a token");
+ notEqual(token1, token2, "got a different token");
+
+ // FxA fires an observer when the "background" signout is complete.
+ let signoutComplete = promiseNotification("testhelper-fxa-signout-complete");
+ // now sign out - they should be removed.
+ await fxa.signOut();
+ await signoutComplete;
+ ok(client.oauthDestroy.calledTwice);
+ // No active tokens left.
+ equal(client.activeTokens.size, 0);
+});
+
+add_task(async function testTokenRaces() {
+ // Here we do 2 concurrent fetches each for 2 different token scopes (ie,
+ // 4 token fetches in total).
+ // This should provoke a potential race in the token fetching but we use
+ // a map of in-flight token fetches, so we should still only perform 2
+ // fetches, but each of the 4 calls should resolve with the correct values.
+ let fxa = await createMockFxA();
+ let client = fxa._internal.fxAccountsClient;
+
+ let results = await Promise.all([
+ fxa.getOAuthToken({ scope: "test-scope" }),
+ fxa.getOAuthToken({ scope: "test-scope" }),
+ fxa.getOAuthToken({ scope: "test-scope-2" }),
+ fxa.getOAuthToken({ scope: "test-scope-2" }),
+ ]);
+
+ equal(client.numTokenFetches, 2, "should have fetched 2 tokens.");
+
+ // Should have 2 unique tokens
+ results.sort();
+ equal(results[0], results[1]);
+ equal(results[2], results[3]);
+ // should be 2 active.
+ equal(client.activeTokens.size, 2);
+ await fxa.removeCachedOAuthToken({ token: results[0] });
+ equal(client.activeTokens.size, 1);
+ await fxa.removeCachedOAuthToken({ token: results[2] });
+ equal(client.activeTokens.size, 0);
+ ok(client.oauthDestroy.calledTwice);
+});
+
+add_task(async function testTokenTTL() {
+ // This tests the TTL option passed into the method
+ let fxa = await createMockFxA();
+ let token = await fxa.getOAuthToken({ scope: "test-ttl", ttl: 1000 });
+ equal(token, "token0-ttl-1000");
+});