mirror of
https://github.com/classilla/tenfourfox.git
synced 2024-10-22 02:25:05 +00:00
236 lines
7.1 KiB
JavaScript
236 lines
7.1 KiB
JavaScript
/* Any copyright is dedicated to the Public Domain.
|
|
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
|
|
|
"use strict";
|
|
|
|
Cu.import("resource://gre/modules/FxAccounts.jsm");
|
|
Cu.import("resource://gre/modules/FxAccountsClient.jsm");
|
|
Cu.import("resource://gre/modules/FxAccountsCommon.js");
|
|
Cu.import("resource://gre/modules/FxAccountsOAuthGrantClient.jsm");
|
|
Cu.import("resource://services-common/utils.js");
|
|
var {AccountState} = Cu.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, false);
|
|
});
|
|
}
|
|
|
|
// 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 Iterator(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) {
|
|
let deferred = Promise.defer();
|
|
deferred.resolve(!!uid && (!this._deletedOnServer));
|
|
return deferred.promise;
|
|
};
|
|
|
|
this.signOut = 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: function(tokenData) {
|
|
// somewhat sad duplication of _destroyOAuthToken, but hard to avoid.
|
|
return mockGrantClient.destroyToken(tokenData.token).then( () => {
|
|
Services.obs.notifyObservers(null, "testhelper-fxa-revoke-complete", null);
|
|
});
|
|
},
|
|
});
|
|
}
|
|
|
|
function* createMockFxA(mockGrantClient) {
|
|
let fxa = new MockFxAccounts(mockGrantClient);
|
|
let credentials = {
|
|
email: "foo@example.com",
|
|
uid: "1234@lcip.org",
|
|
assertion: "foobar",
|
|
sessionToken: "dead",
|
|
kA: "beef",
|
|
kB: "cafe",
|
|
verified: true
|
|
};
|
|
|
|
yield fxa.setSignedInUser(credentials);
|
|
return fxa;
|
|
}
|
|
|
|
// The tests.
|
|
function run_test() {
|
|
run_next_test();
|
|
}
|
|
|
|
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(function testRevoke() {
|
|
let client = new MockFxAccountsOAuthGrantClient();
|
|
let tokenOptions = { scope: "test-scope", client: client };
|
|
let fxa = yield createMockFxA(client);
|
|
|
|
// get our first token and check we hit the mock.
|
|
let token1 = yield 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.
|
|
yield fxa.removeCachedOAuthToken({token: token1});
|
|
|
|
// FxA fires an observer when the "background" revoke is complete.
|
|
yield promiseNotification("testhelper-fxa-revoke-complete");
|
|
// the revoke should have been successful.
|
|
equal(client.activeTokens.size, 0);
|
|
// fetching it again hits the server.
|
|
let token2 = yield 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(function testSignOutDestroysTokens() {
|
|
let client = new MockFxAccountsOAuthGrantClient();
|
|
let fxa = yield createMockFxA(client);
|
|
|
|
// get our first token and check we hit the mock.
|
|
let token1 = yield fxa.getOAuthToken({ scope: "test-scope", client: client });
|
|
equal(client.numTokenFetches, 1);
|
|
equal(client.activeTokens.size, 1);
|
|
ok(token1, "got a token");
|
|
|
|
// get another
|
|
let token2 = yield fxa.getOAuthToken({ scope: "test-scope-2", client: client });
|
|
equal(client.numTokenFetches, 2);
|
|
equal(client.activeTokens.size, 2);
|
|
ok(token2, "got a token");
|
|
notEqual(token1, token2, "got a different token");
|
|
|
|
// now sign out - they should be removed.
|
|
yield fxa.signOut();
|
|
// FxA fires an observer when the "background" signout is complete.
|
|
yield promiseNotification("testhelper-fxa-signout-complete");
|
|
// No active tokens left.
|
|
equal(client.activeTokens.size, 0);
|
|
});
|
|
|
|
add_task(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 = yield 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 = yield Promise.all([
|
|
fxa.getOAuthToken({scope: "test-scope", client: client}),
|
|
fxa.getOAuthToken({scope: "test-scope", client: client}),
|
|
fxa.getOAuthToken({scope: "test-scope-2", client: client}),
|
|
fxa.getOAuthToken({scope: "test-scope-2", client: client}),
|
|
]);
|
|
|
|
equal(client.numTokenFetches, 4, "should have fetched 4 tokens.");
|
|
// We should see 2 of the 4 revoked due to the race.
|
|
yield 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"),
|
|
]);
|
|
yield fxa.removeCachedOAuthToken({token: results[0]});
|
|
equal(client.activeTokens.size, 1);
|
|
yield fxa.removeCachedOAuthToken({token: results[2]});
|
|
equal(client.activeTokens.size, 0);
|
|
yield notifications;
|
|
});
|