gecko-dev/services/fxaccounts/tests/xpcshell/test_oauth_tokens.js
Kris Maglione 918ed6c474 Bug 1431533: Part 5a - Auto-rewrite code to use ChromeUtils import methods. r=florian
This was done using the following script:
37e3803c7a/processors/chromeutils-import.jsm

MozReview-Commit-ID: 1Nc3XDu0wGl

--HG--
extra : source : 12fc4dee861c812fd2bd032c63ef17af61800c70
extra : intermediate-source : 34c999fa006bffe8705cf50c54708aa21a962e62
extra : histedit_source : b2be2c5e5d226e6c347312456a6ae339c1e634b0
2018-01-29 15:20:18 -08:00

251 lines
7.7 KiB
JavaScript

/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
ChromeUtils.import("resource://gre/modules/FxAccounts.jsm");
ChromeUtils.import("resource://gre/modules/FxAccountsClient.jsm");
ChromeUtils.import("resource://gre/modules/FxAccountsCommon.js");
ChromeUtils.import("resource://gre/modules/FxAccountsOAuthGrantClient.jsm");
ChromeUtils.import("resource://services-common/utils.js");
var {AccountState} = ChromeUtils.import("resource://gre/modules/FxAccounts.jsm", {});
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() {
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(); };
FxAccountsClient.apply(this);
}
MockFxAccountsClient.prototype = {
__proto__: FxAccountsClient.prototype
};
function MockFxAccounts(mockGrantClient) {
return new FxAccounts({
fxAccountsClient: new MockFxAccountsClient(),
getAssertion: () => Promise.resolve("assertion"),
newAccountState(credentials) {
// we use a real accountState but mocked storage.
let storage = new MockStorageManager();
storage.initialize(credentials);
return new AccountState(storage);
},
_destroyOAuthToken(tokenData) {
// somewhat sad duplication of _destroyOAuthToken, but hard to avoid.
return mockGrantClient.destroyToken(tokenData.token).then( () => {
Services.obs.notifyObservers(null, "testhelper-fxa-revoke-complete");
});
},
_getDeviceName() {
return "mock device name";
},
fxaPushService: {
registerPushEndpoint() {
return new Promise((resolve) => {
resolve({
endpoint: "http://mochi.test:8888"
});
});
},
},
});
}
async function createMockFxA(mockGrantClient) {
let fxa = new MockFxAccounts(mockGrantClient);
let credentials = {
email: "foo@example.com",
uid: "1234@lcip.org",
assertion: "foobar",
sessionToken: "dead",
kSync: "beef",
kXCS: "cafe",
kExtSync: "bacon",
kExtKbHash: "cheese",
verified: true
};
await fxa.setSignedInUser(credentials);
return fxa;
}
// The tests.
function MockFxAccountsOAuthGrantClient() {
this.activeTokens = new Set();
}
MockFxAccountsOAuthGrantClient.prototype = {
serverURL: {href: "http://localhost"},
getTokenFromAssertion(assertion, scope) {
let token = "token" + this.numTokenFetches;
this.numTokenFetches += 1;
this.activeTokens.add(token);
print("getTokenFromAssertion returning token", token);
return Promise.resolve({access_token: token});
},
destroyToken(token) {
ok(this.activeTokens.delete(token));
print("after destroy have", this.activeTokens.size, "tokens left.");
return Promise.resolve({});
},
// and some stuff used only for tests.
numTokenFetches: 0,
activeTokens: null,
};
add_task(async function testRevoke() {
let client = new MockFxAccountsOAuthGrantClient();
let tokenOptions = { scope: "test-scope", client };
let fxa = await createMockFxA(client);
// 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");
// FxA fires an observer when the "background" revoke is complete.
let revokeComplete = promiseNotification("testhelper-fxa-revoke-complete");
// drop the new token from our cache.
await fxa.removeCachedOAuthToken({token: token1});
await revokeComplete;
// 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 client = new MockFxAccountsOAuthGrantClient();
let fxa = await createMockFxA(client);
// get our first token and check we hit the mock.
let token1 = await fxa.getOAuthToken({ scope: "test-scope", client });
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", client });
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;
// 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 should
// handle and detect that leaving us with one of the fetch tokens being
// revoked and the same token value returned to both calls.
let client = new MockFxAccountsOAuthGrantClient();
let fxa = await createMockFxA(client);
// We should see 2 notifications as part of this - set up the listeners
// now (and wait on them later)
let notifications = Promise.all([
promiseNotification("testhelper-fxa-revoke-complete"),
promiseNotification("testhelper-fxa-revoke-complete"),
]);
let results = await Promise.all([
fxa.getOAuthToken({scope: "test-scope", client}),
fxa.getOAuthToken({scope: "test-scope", client}),
fxa.getOAuthToken({scope: "test-scope-2", client}),
fxa.getOAuthToken({scope: "test-scope-2", client}),
]);
equal(client.numTokenFetches, 4, "should have fetched 4 tokens.");
// We should see 2 of the 4 revoked due to the race.
await notifications;
// 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);
// Which can each be revoked.
notifications = Promise.all([
promiseNotification("testhelper-fxa-revoke-complete"),
promiseNotification("testhelper-fxa-revoke-complete"),
]);
await fxa.removeCachedOAuthToken({token: results[0]});
equal(client.activeTokens.size, 1);
await fxa.removeCachedOAuthToken({token: results[2]});
equal(client.activeTokens.size, 0);
await notifications;
});