diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
commit | 26a029d407be480d791972afb5975cf62c9360a6 (patch) | |
tree | f435a8308119effd964b339f76abb83a57c29483 /services/fxaccounts/tests/xpcshell/test_oauth_tokens.js | |
parent | Initial commit. (diff) | |
download | firefox-upstream/124.0.1.tar.xz firefox-upstream/124.0.1.zip |
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'services/fxaccounts/tests/xpcshell/test_oauth_tokens.js')
-rw-r--r-- | services/fxaccounts/tests/xpcshell/test_oauth_tokens.js | 255 |
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"); +}); |