mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-17 23:35:34 +00:00
Bug 1581709 - Use sessionTokens for OAuth requests. r=vladikoff
Differential Revision: https://phabricator.services.mozilla.com/D46517 --HG-- extra : moz-landing-system : lando
This commit is contained in:
parent
9c27546918
commit
da3f0d0753
@ -67,12 +67,6 @@ ChromeUtils.defineModuleGetter(
|
||||
"resource://services-crypto/jwcrypto.jsm"
|
||||
);
|
||||
|
||||
ChromeUtils.defineModuleGetter(
|
||||
this,
|
||||
"FxAccountsOAuthGrantClient",
|
||||
"resource://gre/modules/FxAccountsOAuthGrantClient.jsm"
|
||||
);
|
||||
|
||||
ChromeUtils.defineModuleGetter(
|
||||
this,
|
||||
"FxAccountsCommands",
|
||||
@ -251,8 +245,8 @@ AccountState.prototype = {
|
||||
},
|
||||
|
||||
// Set a cached token. |tokenData| must have a 'token' element, but may also
|
||||
// have additional fields (eg, it probably specifies the server to revoke
|
||||
// from). The 'get' functions below return the entire |tokenData| value.
|
||||
// have additional fields.
|
||||
// The 'get' functions below return the entire |tokenData| value.
|
||||
setCachedToken(scopeArray, tokenData) {
|
||||
this._cachePreamble();
|
||||
if (!tokenData.token) {
|
||||
@ -461,7 +455,7 @@ class FxAccounts {
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves an OAuth authorization code
|
||||
* Retrieves an OAuth authorization code.
|
||||
*
|
||||
* @param {Object} options
|
||||
* @param options.client_id
|
||||
@ -475,8 +469,7 @@ class FxAccounts {
|
||||
*/
|
||||
authorizeOAuthCode(options) {
|
||||
return this._withVerifiedAccountState(async state => {
|
||||
const client = this._internal.oauthClient;
|
||||
const oAuthURL = client.serverURL.href;
|
||||
const { sessionToken } = await state.getUserAccountData(["sessionToken"]);
|
||||
const params = { ...options };
|
||||
if (params.keys_jwk) {
|
||||
const jwk = JSON.parse(
|
||||
@ -492,8 +485,10 @@ class FxAccounts {
|
||||
delete params.keys_jwk;
|
||||
}
|
||||
try {
|
||||
const assertion = await this._internal.getAssertion(oAuthURL);
|
||||
return await client.authorizeCodeFromAssertion(assertion, params);
|
||||
return await this._internal.fxAccountsClient.oauthAuthorize(
|
||||
sessionToken,
|
||||
params
|
||||
);
|
||||
} catch (err) {
|
||||
throw this._internal._errorToErrorClass(err);
|
||||
}
|
||||
@ -862,20 +857,6 @@ FxAccountsInternal.prototype = {
|
||||
return this._device;
|
||||
},
|
||||
|
||||
_oauthClient: null,
|
||||
get oauthClient() {
|
||||
if (!this._oauthClient) {
|
||||
const serverURL = Services.urlFormatter.formatURLPref(
|
||||
"identity.fxaccounts.remote.oauth.uri"
|
||||
);
|
||||
this._oauthClient = new FxAccountsOAuthGrantClient({
|
||||
serverURL,
|
||||
client_id: FX_OAUTH_CLIENT_ID,
|
||||
});
|
||||
}
|
||||
return this._oauthClient;
|
||||
},
|
||||
|
||||
// A hook-point for tests who may want a mocked AccountState or mocked storage.
|
||||
newAccountState(credentials) {
|
||||
let storage = new FxAccountsStorageManager();
|
||||
@ -1147,11 +1128,10 @@ FxAccountsInternal.prototype = {
|
||||
},
|
||||
|
||||
_destroyOAuthToken(tokenData) {
|
||||
let client = new FxAccountsOAuthGrantClient({
|
||||
serverURL: tokenData.server,
|
||||
client_id: FX_OAUTH_CLIENT_ID,
|
||||
});
|
||||
return client.destroyToken(tokenData.token);
|
||||
return this.fxAccountsClient.oauthDestroy(
|
||||
FX_OAUTH_CLIENT_ID,
|
||||
tokenData.token
|
||||
);
|
||||
},
|
||||
|
||||
_destroyAllOAuthTokens(tokenInfos) {
|
||||
@ -1566,17 +1546,16 @@ FxAccountsInternal.prototype = {
|
||||
},
|
||||
|
||||
// Does the actual fetch of an oauth token for getOAuthToken()
|
||||
async _doTokenFetch(client, scopeString) {
|
||||
let oAuthURL = client.serverURL.href;
|
||||
try {
|
||||
log.debug("getOAuthToken fetching new token from", oAuthURL);
|
||||
let assertion = await this.getAssertion(oAuthURL);
|
||||
let result = await client.getTokenFromAssertion(assertion, scopeString);
|
||||
let token = result.access_token;
|
||||
return token;
|
||||
} catch (err) {
|
||||
throw this._errorToErrorClass(err);
|
||||
}
|
||||
async _doTokenFetch(scopeString) {
|
||||
return this.withVerifiedAccountState(async state => {
|
||||
const { sessionToken } = await state.getUserAccountData(["sessionToken"]);
|
||||
const result = await this.fxAccountsClient.oauthToken(
|
||||
sessionToken,
|
||||
FX_OAUTH_CLIENT_ID,
|
||||
scopeString
|
||||
);
|
||||
return result.access_token;
|
||||
});
|
||||
},
|
||||
|
||||
getOAuthToken(options = {}) {
|
||||
@ -1606,8 +1585,6 @@ FxAccountsInternal.prototype = {
|
||||
// Build the string we use in our "inflight" map and that we send to the
|
||||
// server. Because it's used as a key in the map we sort the scopes.
|
||||
let scopeString = scope.sort().join(" ");
|
||||
let client = options.client || this.oauthClient;
|
||||
let oAuthURL = client.serverURL.href;
|
||||
|
||||
// We keep a map of in-flight requests to avoid multiple promise-based
|
||||
// consumers concurrently requesting the same token.
|
||||
@ -1619,7 +1596,7 @@ FxAccountsInternal.prototype = {
|
||||
|
||||
// We need to start a new fetch and stick the promise in our in-flight map
|
||||
// and remove it when it resolves.
|
||||
let promise = this._doTokenFetch(client, scopeString)
|
||||
let promise = this._doTokenFetch(scopeString)
|
||||
.then(token => {
|
||||
// As a sanity check, ensure something else hasn't raced getting a token
|
||||
// of the same scope. If something has we just make noise rather than
|
||||
@ -1629,7 +1606,7 @@ FxAccountsInternal.prototype = {
|
||||
}
|
||||
// If we got one, cache it.
|
||||
if (token) {
|
||||
let entry = { token, server: oAuthURL };
|
||||
let entry = { token };
|
||||
currentState.setCachedToken(scope, entry);
|
||||
}
|
||||
return token;
|
||||
|
@ -250,6 +250,78 @@ this.FxAccountsClient.prototype = {
|
||||
return this._request("/account/attached_clients", "GET", credentials);
|
||||
},
|
||||
|
||||
/**
|
||||
* Retrieves an OAuth authorization code.
|
||||
*
|
||||
* @param String sessionTokenHex
|
||||
* The session token encoded in hex
|
||||
* @param {Object} options
|
||||
* @param options.client_id
|
||||
* @param options.state
|
||||
* @param options.scope
|
||||
* @param options.access_type
|
||||
* @param options.code_challenge_method
|
||||
* @param options.code_challenge
|
||||
* @param [options.keys_jwe]
|
||||
* @returns {Promise<Object>} Object containing `code` and `state`.
|
||||
*/
|
||||
async oauthAuthorize(sessionTokenHex, options) {
|
||||
const credentials = await deriveHawkCredentials(
|
||||
sessionTokenHex,
|
||||
"sessionToken"
|
||||
);
|
||||
const body = {
|
||||
client_id: options.client_id,
|
||||
response_type: "code",
|
||||
state: options.state,
|
||||
scope: options.scope,
|
||||
access_type: options.access_type,
|
||||
code_challenge: options.code_challenge,
|
||||
code_challenge_method: options.code_challenge_method,
|
||||
};
|
||||
if (options.keys_jwe) {
|
||||
body.keys_jwe = options.keys_jwe;
|
||||
}
|
||||
return this._request("/oauth/authorization", "POST", credentials, body);
|
||||
},
|
||||
|
||||
/**
|
||||
* Obtain an OAuth access token by authenticating using a session token.
|
||||
*
|
||||
* @param String sessionTokenHex
|
||||
* The session token encoded in hex
|
||||
* @param String clientId
|
||||
* @param String scopeString
|
||||
* List of space-separated scopes.
|
||||
* @return {Promise<Object>} Object containing an `access_token`.
|
||||
*/
|
||||
async oauthToken(sessionTokenHex, clientId, scopeString) {
|
||||
const credentials = await deriveHawkCredentials(
|
||||
sessionTokenHex,
|
||||
"sessionToken"
|
||||
);
|
||||
const body = {
|
||||
client_id: clientId,
|
||||
grant_type: "fxa-credentials",
|
||||
scope: scopeString,
|
||||
};
|
||||
return this._request("/oauth/token", "POST", credentials, body);
|
||||
},
|
||||
|
||||
/**
|
||||
* Destroy an OAuth access token or refresh token.
|
||||
*
|
||||
* @param String clientId
|
||||
* @param String token The token to be revoked.
|
||||
*/
|
||||
async oauthDestroy(clientId, token) {
|
||||
const body = {
|
||||
client_id: clientId,
|
||||
token,
|
||||
};
|
||||
return this._request("/oauth/destroy", "POST", null, body);
|
||||
},
|
||||
|
||||
/**
|
||||
* Query for the information required to derive
|
||||
* scoped encryption keys requested by the specified OAuth client.
|
||||
|
@ -40,7 +40,6 @@ XPCOMUtils.defineLazyPreferenceGetter(
|
||||
XPCOMUtils.defineLazyPreferenceGetter(
|
||||
this,
|
||||
"REQUIRES_HTTPS",
|
||||
// Also used in FxAccountsOAuthGrantClient.jsm.
|
||||
"identity.fxaccounts.allowHttp",
|
||||
false,
|
||||
null,
|
||||
|
@ -1,298 +0,0 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
/**
|
||||
* Firefox Accounts OAuth Grant Client allows clients to obtain
|
||||
* an OAuth token from a BrowserID assertion. Only certain client
|
||||
* IDs support this privilage.
|
||||
*/
|
||||
|
||||
var EXPORTED_SYMBOLS = [
|
||||
"FxAccountsOAuthGrantClient",
|
||||
"FxAccountsOAuthGrantClientError",
|
||||
];
|
||||
|
||||
const {
|
||||
ERRNO_NETWORK,
|
||||
ERRNO_PARSE,
|
||||
ERRNO_UNKNOWN_ERROR,
|
||||
ERROR_CODE_METHOD_NOT_ALLOWED,
|
||||
ERROR_MSG_METHOD_NOT_ALLOWED,
|
||||
ERROR_NETWORK,
|
||||
ERROR_PARSE,
|
||||
ERROR_UNKNOWN,
|
||||
OAUTH_SERVER_ERRNO_OFFSET,
|
||||
log,
|
||||
} = ChromeUtils.import("resource://gre/modules/FxAccountsCommon.js");
|
||||
const { RESTRequest } = ChromeUtils.import(
|
||||
"resource://services-common/rest.js"
|
||||
);
|
||||
const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
|
||||
const { XPCOMUtils } = ChromeUtils.import(
|
||||
"resource://gre/modules/XPCOMUtils.jsm"
|
||||
);
|
||||
|
||||
XPCOMUtils.defineLazyGlobalGetters(this, ["URL"]);
|
||||
|
||||
const AUTH_ENDPOINT = "/authorization";
|
||||
const DESTROY_ENDPOINT = "/destroy";
|
||||
// This is the same pref that's used by FxAccounts.jsm.
|
||||
const ALLOW_HTTP_PREF = "identity.fxaccounts.allowHttp";
|
||||
|
||||
/**
|
||||
* Create a new FxAccountsOAuthClient for browser some service.
|
||||
*
|
||||
* @param {Object} options Options
|
||||
* @param {Object} options.parameters
|
||||
* @param {String} options.parameters.client_id
|
||||
* OAuth id returned from client registration
|
||||
* @param {String} options.parameters.serverURL
|
||||
* The FxA OAuth server URL
|
||||
* @param [authorizationEndpoint] {String}
|
||||
* Optional authorization endpoint for the OAuth server
|
||||
* @constructor
|
||||
*/
|
||||
var FxAccountsOAuthGrantClient = function(options) {
|
||||
this._validateOptions(options);
|
||||
this.parameters = options;
|
||||
|
||||
try {
|
||||
this.serverURL = new URL(this.parameters.serverURL);
|
||||
} catch (e) {
|
||||
throw new Error("Invalid 'serverURL'");
|
||||
}
|
||||
|
||||
let forceHTTPS = !Services.prefs.getBoolPref(ALLOW_HTTP_PREF, false);
|
||||
if (forceHTTPS && this.serverURL.protocol != "https:") {
|
||||
throw new Error("'serverURL' must be HTTPS");
|
||||
}
|
||||
|
||||
log.debug("FxAccountsOAuthGrantClient Initialized");
|
||||
};
|
||||
|
||||
this.FxAccountsOAuthGrantClient.prototype = {
|
||||
/**
|
||||
* Retrieves an OAuth access token for the signed in user
|
||||
*
|
||||
* @param {Object} assertion BrowserID assertion
|
||||
* @param {String} scope OAuth scope
|
||||
* @return Promise
|
||||
* Resolves: {Object} Object with access_token property
|
||||
*/
|
||||
getTokenFromAssertion(assertion, scope) {
|
||||
if (!assertion) {
|
||||
throw new Error("Missing 'assertion' parameter");
|
||||
}
|
||||
if (!scope) {
|
||||
throw new Error("Missing 'scope' parameter");
|
||||
}
|
||||
let params = {
|
||||
scope,
|
||||
client_id: this.parameters.client_id,
|
||||
assertion,
|
||||
response_type: "token",
|
||||
};
|
||||
|
||||
return this._createRequest(AUTH_ENDPOINT, "POST", params);
|
||||
},
|
||||
|
||||
/**
|
||||
* Retrieves an OAuth authorization code using an assertion
|
||||
*
|
||||
* @param {Object} assertion BrowserID assertion
|
||||
* @param {Object} options
|
||||
* @param options.client_id
|
||||
* @param options.state
|
||||
* @param options.scope
|
||||
* @param options.access_type
|
||||
* @param options.code_challenge_method
|
||||
* @param options.code_challenge
|
||||
* @param [options.keys_jwe]
|
||||
* @returns {Promise<Object>} Object containing "code" and "state" properties.
|
||||
*/
|
||||
authorizeCodeFromAssertion(assertion, options) {
|
||||
if (!assertion) {
|
||||
throw new Error("Missing 'assertion' parameter");
|
||||
}
|
||||
const {
|
||||
client_id,
|
||||
state,
|
||||
scope,
|
||||
access_type,
|
||||
code_challenge,
|
||||
code_challenge_method,
|
||||
keys_jwe,
|
||||
} = options;
|
||||
const params = {
|
||||
assertion,
|
||||
client_id,
|
||||
response_type: "code",
|
||||
state,
|
||||
scope,
|
||||
access_type,
|
||||
code_challenge,
|
||||
code_challenge_method,
|
||||
};
|
||||
if (keys_jwe) {
|
||||
params.keys_jwe = keys_jwe;
|
||||
}
|
||||
return this._createRequest(AUTH_ENDPOINT, "POST", params);
|
||||
},
|
||||
|
||||
/**
|
||||
* Destroys a previously fetched OAuth access token.
|
||||
*
|
||||
* @param {String} token The previously fetched token
|
||||
* @return Promise
|
||||
* Resolves: {Object} with the server response, which is typically
|
||||
* ignored.
|
||||
*/
|
||||
destroyToken(token) {
|
||||
if (!token) {
|
||||
throw new Error("Missing 'token' parameter");
|
||||
}
|
||||
let params = {
|
||||
token,
|
||||
};
|
||||
|
||||
return this._createRequest(DESTROY_ENDPOINT, "POST", params);
|
||||
},
|
||||
|
||||
/**
|
||||
* Validates the required FxA OAuth parameters
|
||||
*
|
||||
* @param options {Object}
|
||||
* OAuth client options
|
||||
* @private
|
||||
*/
|
||||
_validateOptions(options) {
|
||||
if (!options) {
|
||||
throw new Error("Missing configuration options");
|
||||
}
|
||||
|
||||
["serverURL", "client_id"].forEach(option => {
|
||||
if (!options[option]) {
|
||||
throw new Error("Missing '" + option + "' parameter");
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Interface for making remote requests.
|
||||
*/
|
||||
_Request: RESTRequest,
|
||||
|
||||
/**
|
||||
* Remote request helper
|
||||
*
|
||||
* @param {String} path
|
||||
* Profile server path, i.e "/profile".
|
||||
* @param {String} [method]
|
||||
* Type of request, i.e "GET".
|
||||
* @return Promise
|
||||
* Resolves: {Object} Successful response from the Profile server.
|
||||
* Rejects: {FxAccountsOAuthGrantClientError} Profile client error.
|
||||
* @private
|
||||
*/
|
||||
async _createRequest(path, method = "POST", params) {
|
||||
let profileDataUrl = this.serverURL + path;
|
||||
let request = new this._Request(profileDataUrl);
|
||||
method = method.toUpperCase();
|
||||
|
||||
request.setHeader("Accept", "application/json");
|
||||
request.setHeader("Content-Type", "application/json");
|
||||
|
||||
if (method != "POST") {
|
||||
throw new FxAccountsOAuthGrantClientError({
|
||||
error: ERROR_NETWORK,
|
||||
errno: ERRNO_NETWORK,
|
||||
code: ERROR_CODE_METHOD_NOT_ALLOWED,
|
||||
message: ERROR_MSG_METHOD_NOT_ALLOWED,
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
await request.post(params);
|
||||
} catch (error) {
|
||||
throw new FxAccountsOAuthGrantClientError({
|
||||
error: ERROR_NETWORK,
|
||||
errno: ERRNO_NETWORK,
|
||||
message: error.toString(),
|
||||
});
|
||||
}
|
||||
|
||||
let body = null;
|
||||
try {
|
||||
body = JSON.parse(request.response.body);
|
||||
} catch (e) {
|
||||
throw new FxAccountsOAuthGrantClientError({
|
||||
error: ERROR_PARSE,
|
||||
errno: ERRNO_PARSE,
|
||||
code: request.response.status,
|
||||
message: request.response.body,
|
||||
});
|
||||
}
|
||||
|
||||
if (request.response.success) {
|
||||
return body;
|
||||
}
|
||||
|
||||
if (typeof body.errno === "number") {
|
||||
// Offset oauth server errnos to avoid conflict with other FxA server errnos
|
||||
body.errno += OAUTH_SERVER_ERRNO_OFFSET;
|
||||
} else if (body.errno) {
|
||||
body.errno = ERRNO_UNKNOWN_ERROR;
|
||||
}
|
||||
throw new FxAccountsOAuthGrantClientError(body);
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Normalized profile client errors
|
||||
* @param {Object} [details]
|
||||
* Error details object
|
||||
* @param {number} [details.code]
|
||||
* Error code
|
||||
* @param {number} [details.errno]
|
||||
* Error number
|
||||
* @param {String} [details.error]
|
||||
* Error description
|
||||
* @param {String|null} [details.message]
|
||||
* Error message
|
||||
* @constructor
|
||||
*/
|
||||
var FxAccountsOAuthGrantClientError = function(details) {
|
||||
details = details || {};
|
||||
|
||||
this.name = "FxAccountsOAuthGrantClientError";
|
||||
this.code = details.code || null;
|
||||
this.errno = details.errno || ERRNO_UNKNOWN_ERROR;
|
||||
this.error = details.error || ERROR_UNKNOWN;
|
||||
this.message = details.message || null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns error object properties
|
||||
*
|
||||
* @returns {{name: *, code: *, errno: *, error: *, message: *}}
|
||||
* @private
|
||||
*/
|
||||
FxAccountsOAuthGrantClientError.prototype._toStringFields = function() {
|
||||
return {
|
||||
name: this.name,
|
||||
code: this.code,
|
||||
errno: this.errno,
|
||||
error: this.error,
|
||||
message: this.message,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* String representation of a oauth grant client error
|
||||
*
|
||||
* @returns {String}
|
||||
*/
|
||||
FxAccountsOAuthGrantClientError.prototype.toString = function() {
|
||||
return this.name + "(" + JSON.stringify(this._toStringFields()) + ")";
|
||||
};
|
@ -24,7 +24,6 @@ EXTRA_JS_MODULES += [
|
||||
'FxAccountsConfig.jsm',
|
||||
'FxAccountsDevice.jsm',
|
||||
'FxAccountsKeys.jsm',
|
||||
'FxAccountsOAuthGrantClient.jsm',
|
||||
'FxAccountsPairing.jsm',
|
||||
'FxAccountsPairingChannel.js',
|
||||
'FxAccountsProfile.jsm',
|
||||
|
@ -23,10 +23,6 @@ const {
|
||||
ONVERIFIED_NOTIFICATION,
|
||||
SCOPE_OLD_SYNC,
|
||||
} = ChromeUtils.import("resource://gre/modules/FxAccountsCommon.js");
|
||||
const {
|
||||
FxAccountsOAuthGrantClient,
|
||||
FxAccountsOAuthGrantClientError,
|
||||
} = ChromeUtils.import("resource://gre/modules/FxAccountsOAuthGrantClient.jsm");
|
||||
const { PromiseUtils } = ChromeUtils.import(
|
||||
"resource://gre/modules/PromiseUtils.jsm"
|
||||
);
|
||||
@ -1322,23 +1318,17 @@ add_test(function test_getOAuthToken() {
|
||||
let fxa = new MockFxAccounts();
|
||||
let alice = getTestUser("alice");
|
||||
alice.verified = true;
|
||||
let getTokenFromAssertionCalled = false;
|
||||
let oauthTokenCalled = false;
|
||||
|
||||
fxa._internal._d_signCertificate.resolve("cert1");
|
||||
|
||||
// create a mock oauth client
|
||||
let client = new FxAccountsOAuthGrantClient({
|
||||
serverURL: "https://example.com/v1",
|
||||
client_id: "abc123",
|
||||
});
|
||||
client.getTokenFromAssertion = function() {
|
||||
getTokenFromAssertionCalled = true;
|
||||
let client = fxa._internal.fxAccountsClient;
|
||||
client.oauthToken = () => {
|
||||
oauthTokenCalled = true;
|
||||
return Promise.resolve({ access_token: "token" });
|
||||
};
|
||||
|
||||
fxa.setSignedInUser(alice).then(() => {
|
||||
fxa.getOAuthToken({ scope: "profile", client }).then(result => {
|
||||
Assert.ok(getTokenFromAssertionCalled);
|
||||
fxa.getOAuthToken({ scope: "profile" }).then(result => {
|
||||
Assert.ok(oauthTokenCalled);
|
||||
Assert.equal(result, "token");
|
||||
run_next_test();
|
||||
});
|
||||
@ -1349,24 +1339,18 @@ add_test(function test_getOAuthTokenScoped() {
|
||||
let fxa = new MockFxAccounts();
|
||||
let alice = getTestUser("alice");
|
||||
alice.verified = true;
|
||||
let getTokenFromAssertionCalled = false;
|
||||
let oauthTokenCalled = false;
|
||||
|
||||
fxa._internal._d_signCertificate.resolve("cert1");
|
||||
|
||||
// create a mock oauth client
|
||||
let client = new FxAccountsOAuthGrantClient({
|
||||
serverURL: "https://example.com/v1",
|
||||
client_id: "abc123",
|
||||
});
|
||||
client.getTokenFromAssertion = function(assertion, scopeString) {
|
||||
let client = fxa._internal.fxAccountsClient;
|
||||
client.oauthToken = (_1, _2, scopeString) => {
|
||||
equal(scopeString, "bar foo"); // scopes are sorted locally before request.
|
||||
getTokenFromAssertionCalled = true;
|
||||
oauthTokenCalled = true;
|
||||
return Promise.resolve({ access_token: "token" });
|
||||
};
|
||||
|
||||
fxa.setSignedInUser(alice).then(() => {
|
||||
fxa.getOAuthToken({ scope: ["foo", "bar"], client }).then(result => {
|
||||
Assert.ok(getTokenFromAssertionCalled);
|
||||
fxa.getOAuthToken({ scope: ["foo", "bar"] }).then(result => {
|
||||
Assert.ok(oauthTokenCalled);
|
||||
Assert.equal(result, "token");
|
||||
run_next_test();
|
||||
});
|
||||
@ -1377,44 +1361,35 @@ add_task(async function test_getOAuthTokenCached() {
|
||||
let fxa = new MockFxAccounts();
|
||||
let alice = getTestUser("alice");
|
||||
alice.verified = true;
|
||||
let numTokenFromAssertionCalls = 0;
|
||||
let numOauthTokenCalls = 0;
|
||||
|
||||
fxa._internal._d_signCertificate.resolve("cert1");
|
||||
|
||||
// create a mock oauth client
|
||||
let client = new FxAccountsOAuthGrantClient({
|
||||
serverURL: "https://example.com/v1",
|
||||
client_id: "abc123",
|
||||
});
|
||||
client.getTokenFromAssertion = function() {
|
||||
numTokenFromAssertionCalls += 1;
|
||||
let client = fxa._internal.fxAccountsClient;
|
||||
client.oauthToken = () => {
|
||||
numOauthTokenCalls += 1;
|
||||
return Promise.resolve({ access_token: "token" });
|
||||
};
|
||||
|
||||
await fxa.setSignedInUser(alice);
|
||||
let result = await fxa.getOAuthToken({
|
||||
scope: "profile",
|
||||
client,
|
||||
service: "test-service",
|
||||
});
|
||||
Assert.equal(numTokenFromAssertionCalls, 1);
|
||||
Assert.equal(numOauthTokenCalls, 1);
|
||||
Assert.equal(result, "token");
|
||||
|
||||
// requesting it again should not re-fetch the token.
|
||||
result = await fxa.getOAuthToken({
|
||||
scope: "profile",
|
||||
client,
|
||||
service: "test-service",
|
||||
});
|
||||
Assert.equal(numTokenFromAssertionCalls, 1);
|
||||
Assert.equal(numOauthTokenCalls, 1);
|
||||
Assert.equal(result, "token");
|
||||
// But requesting the same service and a different scope *will* get a new one.
|
||||
result = await fxa.getOAuthToken({
|
||||
scope: "something-else",
|
||||
client,
|
||||
service: "test-service",
|
||||
});
|
||||
Assert.equal(numTokenFromAssertionCalls, 2);
|
||||
Assert.equal(numOauthTokenCalls, 2);
|
||||
Assert.equal(result, "token");
|
||||
});
|
||||
|
||||
@ -1422,52 +1397,42 @@ add_task(async function test_getOAuthTokenCachedScopeNormalization() {
|
||||
let fxa = new MockFxAccounts();
|
||||
let alice = getTestUser("alice");
|
||||
alice.verified = true;
|
||||
let numTokenFromAssertionCalls = 0;
|
||||
let numOAuthTokenCalls = 0;
|
||||
|
||||
fxa._internal._d_signCertificate.resolve("cert1");
|
||||
|
||||
// create a mock oauth client
|
||||
let client = new FxAccountsOAuthGrantClient({
|
||||
serverURL: "https://example.com/v1",
|
||||
client_id: "abc123",
|
||||
});
|
||||
client.getTokenFromAssertion = function() {
|
||||
numTokenFromAssertionCalls += 1;
|
||||
let client = fxa._internal.fxAccountsClient;
|
||||
client.oauthToken = () => {
|
||||
numOAuthTokenCalls += 1;
|
||||
return Promise.resolve({ access_token: "token" });
|
||||
};
|
||||
|
||||
await fxa.setSignedInUser(alice);
|
||||
let result = await fxa.getOAuthToken({
|
||||
scope: ["foo", "bar"],
|
||||
client,
|
||||
service: "test-service",
|
||||
});
|
||||
Assert.equal(numTokenFromAssertionCalls, 1);
|
||||
Assert.equal(numOAuthTokenCalls, 1);
|
||||
Assert.equal(result, "token");
|
||||
|
||||
// requesting it again with the scope array in a different order not re-fetch the token.
|
||||
result = await fxa.getOAuthToken({
|
||||
scope: ["bar", "foo"],
|
||||
client,
|
||||
service: "test-service",
|
||||
});
|
||||
Assert.equal(numTokenFromAssertionCalls, 1);
|
||||
Assert.equal(numOAuthTokenCalls, 1);
|
||||
Assert.equal(result, "token");
|
||||
// requesting it again with the scope array in different case not re-fetch the token.
|
||||
result = await fxa.getOAuthToken({
|
||||
scope: ["Bar", "Foo"],
|
||||
client,
|
||||
service: "test-service",
|
||||
});
|
||||
Assert.equal(numTokenFromAssertionCalls, 1);
|
||||
Assert.equal(numOAuthTokenCalls, 1);
|
||||
Assert.equal(result, "token");
|
||||
// But requesting with a new entry in the array does fetch one.
|
||||
result = await fxa.getOAuthToken({
|
||||
scope: ["foo", "bar", "etc"],
|
||||
client,
|
||||
service: "test-service",
|
||||
});
|
||||
Assert.equal(numTokenFromAssertionCalls, 2);
|
||||
Assert.equal(numOAuthTokenCalls, 2);
|
||||
Assert.equal(result, "token");
|
||||
});
|
||||
|
||||
@ -1534,85 +1499,18 @@ add_test(function test_getOAuthToken_unverified() {
|
||||
});
|
||||
});
|
||||
|
||||
add_test(function test_getOAuthToken_network_error() {
|
||||
add_test(function test_getOAuthToken_error() {
|
||||
let fxa = new MockFxAccounts();
|
||||
let alice = getTestUser("alice");
|
||||
alice.verified = true;
|
||||
|
||||
fxa._internal._d_signCertificate.resolve("cert1");
|
||||
|
||||
// create a mock oauth client
|
||||
let client = new FxAccountsOAuthGrantClient({
|
||||
serverURL: "https://example.com/v1",
|
||||
client_id: "abc123",
|
||||
});
|
||||
client.getTokenFromAssertion = function() {
|
||||
return Promise.reject(
|
||||
new FxAccountsOAuthGrantClientError({
|
||||
error: ERROR_NETWORK,
|
||||
errno: ERRNO_NETWORK,
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
fxa.setSignedInUser(alice).then(() => {
|
||||
fxa.getOAuthToken({ scope: "profile", client }).catch(err => {
|
||||
Assert.equal(err.message, "NETWORK_ERROR");
|
||||
Assert.equal(err.details.errno, ERRNO_NETWORK);
|
||||
run_next_test();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
add_test(function test_getOAuthToken_auth_error() {
|
||||
let fxa = new MockFxAccounts();
|
||||
let alice = getTestUser("alice");
|
||||
alice.verified = true;
|
||||
|
||||
fxa._internal._d_signCertificate.resolve("cert1");
|
||||
|
||||
// create a mock oauth client
|
||||
let client = new FxAccountsOAuthGrantClient({
|
||||
serverURL: "https://example.com/v1",
|
||||
client_id: "abc123",
|
||||
});
|
||||
client.getTokenFromAssertion = function() {
|
||||
return Promise.reject(
|
||||
new FxAccountsOAuthGrantClientError({
|
||||
error: ERROR_INVALID_FXA_ASSERTION,
|
||||
errno: ERRNO_INVALID_FXA_ASSERTION,
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
fxa.setSignedInUser(alice).then(() => {
|
||||
fxa.getOAuthToken({ scope: "profile", client }).catch(err => {
|
||||
Assert.equal(err.message, "AUTH_ERROR");
|
||||
Assert.equal(err.details.errno, ERRNO_INVALID_FXA_ASSERTION);
|
||||
run_next_test();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
add_test(function test_getOAuthToken_unknown_error() {
|
||||
let fxa = new MockFxAccounts();
|
||||
let alice = getTestUser("alice");
|
||||
alice.verified = true;
|
||||
|
||||
fxa._internal._d_signCertificate.resolve("cert1");
|
||||
|
||||
// create a mock oauth client
|
||||
let client = new FxAccountsOAuthGrantClient({
|
||||
serverURL: "https://example.com/v1",
|
||||
client_id: "abc123",
|
||||
});
|
||||
client.getTokenFromAssertion = function() {
|
||||
let client = fxa._internal.fxAccountsClient;
|
||||
client.oauthToken = () => {
|
||||
return Promise.reject("boom");
|
||||
};
|
||||
|
||||
fxa.setSignedInUser(alice).then(() => {
|
||||
fxa.getOAuthToken({ scope: "profile", client }).catch(err => {
|
||||
Assert.equal(err.message, "UNKNOWN_ERROR");
|
||||
fxa.getOAuthToken({ scope: "profile" }).catch(err => {
|
||||
run_next_test();
|
||||
});
|
||||
});
|
||||
|
@ -1,319 +0,0 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
const {
|
||||
ERRNO_INVALID_FXA_ASSERTION,
|
||||
ERRNO_NETWORK,
|
||||
ERRNO_PARSE,
|
||||
ERRNO_UNKNOWN_ERROR,
|
||||
ERROR_CODE_METHOD_NOT_ALLOWED,
|
||||
ERROR_MSG_METHOD_NOT_ALLOWED,
|
||||
ERROR_NETWORK,
|
||||
ERROR_PARSE,
|
||||
ERROR_UNKNOWN,
|
||||
} = ChromeUtils.import("resource://gre/modules/FxAccountsCommon.js");
|
||||
const {
|
||||
FxAccountsOAuthGrantClient,
|
||||
FxAccountsOAuthGrantClientError,
|
||||
} = ChromeUtils.import("resource://gre/modules/FxAccountsOAuthGrantClient.jsm");
|
||||
|
||||
const CLIENT_OPTIONS = {
|
||||
serverURL: "https://127.0.0.1:9010/v1",
|
||||
client_id: "abc123",
|
||||
};
|
||||
|
||||
const STATUS_SUCCESS = 200;
|
||||
|
||||
/**
|
||||
* Mock request responder
|
||||
* @param {String} response
|
||||
* Mocked raw response from the server
|
||||
* @returns {Function}
|
||||
*/
|
||||
var mockResponse = function(response) {
|
||||
return function() {
|
||||
return {
|
||||
setHeader() {},
|
||||
async post() {
|
||||
this.response = response;
|
||||
return response;
|
||||
},
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Mock request error responder
|
||||
* @param {Error} error
|
||||
* Error object
|
||||
* @returns {Function}
|
||||
*/
|
||||
var mockResponseError = function(error) {
|
||||
return function() {
|
||||
return {
|
||||
setHeader() {},
|
||||
async post() {
|
||||
throw error;
|
||||
},
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
add_test(function missingParams() {
|
||||
let client = new FxAccountsOAuthGrantClient(CLIENT_OPTIONS);
|
||||
try {
|
||||
client.getTokenFromAssertion();
|
||||
} catch (e) {
|
||||
Assert.equal(e.message, "Missing 'assertion' parameter");
|
||||
}
|
||||
|
||||
try {
|
||||
client.getTokenFromAssertion("assertion");
|
||||
} catch (e) {
|
||||
Assert.equal(e.message, "Missing 'scope' parameter");
|
||||
}
|
||||
|
||||
run_next_test();
|
||||
});
|
||||
|
||||
add_test(function successfulResponse() {
|
||||
let client = new FxAccountsOAuthGrantClient(CLIENT_OPTIONS);
|
||||
let response = {
|
||||
success: true,
|
||||
status: STATUS_SUCCESS,
|
||||
body: JSON.stringify({
|
||||
access_token: "http://example.com/image.jpeg",
|
||||
id: "0d5c1a89b8c54580b8e3e8adadae864a",
|
||||
}),
|
||||
};
|
||||
|
||||
client._Request = new mockResponse(response);
|
||||
client.getTokenFromAssertion("assertion", "scope").then(function(result) {
|
||||
Assert.equal(result.access_token, "http://example.com/image.jpeg");
|
||||
run_next_test();
|
||||
});
|
||||
});
|
||||
|
||||
add_test(function successfulDestroy() {
|
||||
let client = new FxAccountsOAuthGrantClient(CLIENT_OPTIONS);
|
||||
let response = {
|
||||
success: true,
|
||||
status: STATUS_SUCCESS,
|
||||
body: JSON.stringify({}),
|
||||
};
|
||||
|
||||
client._Request = new mockResponse(response);
|
||||
client.destroyToken("deadbeef").then(run_next_test);
|
||||
});
|
||||
|
||||
add_test(function parseErrorResponse() {
|
||||
let client = new FxAccountsOAuthGrantClient(CLIENT_OPTIONS);
|
||||
let response = {
|
||||
success: true,
|
||||
status: STATUS_SUCCESS,
|
||||
body: "unexpected",
|
||||
};
|
||||
|
||||
client._Request = new mockResponse(response);
|
||||
client.getTokenFromAssertion("assertion", "scope").catch(function(e) {
|
||||
Assert.equal(e.name, "FxAccountsOAuthGrantClientError");
|
||||
Assert.equal(e.code, STATUS_SUCCESS);
|
||||
Assert.equal(e.errno, ERRNO_PARSE);
|
||||
Assert.equal(e.error, ERROR_PARSE);
|
||||
Assert.equal(e.message, "unexpected");
|
||||
run_next_test();
|
||||
});
|
||||
});
|
||||
|
||||
add_task(async function serverErrorResponse() {
|
||||
let client = new FxAccountsOAuthGrantClient(CLIENT_OPTIONS);
|
||||
let response = {
|
||||
status: 400,
|
||||
body: JSON.stringify({
|
||||
code: 400,
|
||||
errno: 104,
|
||||
error: "Bad Request",
|
||||
message: "Unauthorized",
|
||||
reason: "Invalid fxa assertion",
|
||||
}),
|
||||
};
|
||||
|
||||
client._Request = new mockResponse(response);
|
||||
client.getTokenFromAssertion("blah", "scope").catch(function(e) {
|
||||
Assert.equal(e.name, "FxAccountsOAuthGrantClientError");
|
||||
Assert.equal(e.code, 400);
|
||||
Assert.equal(e.errno, ERRNO_INVALID_FXA_ASSERTION);
|
||||
Assert.equal(e.error, "Bad Request");
|
||||
Assert.equal(e.message, "Unauthorized");
|
||||
run_next_test();
|
||||
});
|
||||
});
|
||||
|
||||
add_task(async function networkErrorResponse() {
|
||||
let client = new FxAccountsOAuthGrantClient({
|
||||
serverURL: "https://domain.dummy",
|
||||
client_id: "abc123",
|
||||
});
|
||||
client.getTokenFromAssertion("assertion", "scope").catch(function(e) {
|
||||
Assert.equal(e.name, "FxAccountsOAuthGrantClientError");
|
||||
Assert.equal(e.code, null);
|
||||
Assert.equal(e.errno, ERRNO_NETWORK);
|
||||
Assert.equal(e.error, ERROR_NETWORK);
|
||||
run_next_test();
|
||||
});
|
||||
});
|
||||
|
||||
add_test(function unsupportedMethod() {
|
||||
let client = new FxAccountsOAuthGrantClient(CLIENT_OPTIONS);
|
||||
|
||||
return client._createRequest("/", "PUT").catch(function(e) {
|
||||
Assert.equal(e.name, "FxAccountsOAuthGrantClientError");
|
||||
Assert.equal(e.code, ERROR_CODE_METHOD_NOT_ALLOWED);
|
||||
Assert.equal(e.errno, ERRNO_NETWORK);
|
||||
Assert.equal(e.error, ERROR_NETWORK);
|
||||
Assert.equal(e.message, ERROR_MSG_METHOD_NOT_ALLOWED);
|
||||
run_next_test();
|
||||
});
|
||||
});
|
||||
|
||||
add_test(function onCompleteRequestError() {
|
||||
let client = new FxAccountsOAuthGrantClient(CLIENT_OPTIONS);
|
||||
client._Request = new mockResponseError(new Error("onComplete error"));
|
||||
client.getTokenFromAssertion("assertion", "scope").catch(function(e) {
|
||||
Assert.equal(e.name, "FxAccountsOAuthGrantClientError");
|
||||
Assert.equal(e.code, null);
|
||||
Assert.equal(e.errno, ERRNO_NETWORK);
|
||||
Assert.equal(e.error, ERROR_NETWORK);
|
||||
Assert.equal(e.message, "Error: onComplete error");
|
||||
run_next_test();
|
||||
});
|
||||
});
|
||||
|
||||
add_test(function incorrectErrno() {
|
||||
let client = new FxAccountsOAuthGrantClient(CLIENT_OPTIONS);
|
||||
let response = {
|
||||
status: 400,
|
||||
body: JSON.stringify({
|
||||
code: 400,
|
||||
errno: "bad errno",
|
||||
error: "Bad Request",
|
||||
message: "Unauthorized",
|
||||
reason: "Invalid fxa assertion",
|
||||
}),
|
||||
};
|
||||
|
||||
client._Request = new mockResponse(response);
|
||||
client.getTokenFromAssertion("blah", "scope").catch(function(e) {
|
||||
Assert.equal(e.name, "FxAccountsOAuthGrantClientError");
|
||||
Assert.equal(e.code, 400);
|
||||
Assert.equal(e.errno, ERRNO_UNKNOWN_ERROR);
|
||||
Assert.equal(e.error, "Bad Request");
|
||||
Assert.equal(e.message, "Unauthorized");
|
||||
run_next_test();
|
||||
});
|
||||
});
|
||||
|
||||
add_test(function constructorTests() {
|
||||
validationHelper(undefined, "Error: Missing configuration options");
|
||||
|
||||
validationHelper({}, "Error: Missing 'serverURL' parameter");
|
||||
|
||||
validationHelper(
|
||||
{ serverURL: "https://example.com" },
|
||||
"Error: Missing 'client_id' parameter"
|
||||
);
|
||||
|
||||
validationHelper(
|
||||
{ serverURL: "https://example.com" },
|
||||
"Error: Missing 'client_id' parameter"
|
||||
);
|
||||
|
||||
validationHelper(
|
||||
{ client_id: "123ABC" },
|
||||
"Error: Missing 'serverURL' parameter"
|
||||
);
|
||||
|
||||
validationHelper(
|
||||
{ client_id: "123ABC", serverURL: "http://example.com" },
|
||||
"Error: 'serverURL' must be HTTPS"
|
||||
);
|
||||
|
||||
try {
|
||||
Services.prefs.setBoolPref("identity.fxaccounts.allowHttp", true);
|
||||
validationHelper(
|
||||
{ client_id: "123ABC", serverURL: "http://example.com" },
|
||||
null
|
||||
);
|
||||
} finally {
|
||||
Services.prefs.clearUserPref("identity.fxaccounts.allowHttp");
|
||||
}
|
||||
|
||||
run_next_test();
|
||||
});
|
||||
|
||||
add_test(function errorTests() {
|
||||
let error1 = new FxAccountsOAuthGrantClientError();
|
||||
Assert.equal(error1.name, "FxAccountsOAuthGrantClientError");
|
||||
Assert.equal(error1.code, null);
|
||||
Assert.equal(error1.errno, ERRNO_UNKNOWN_ERROR);
|
||||
Assert.equal(error1.error, ERROR_UNKNOWN);
|
||||
Assert.equal(error1.message, null);
|
||||
|
||||
let error2 = new FxAccountsOAuthGrantClientError({
|
||||
code: STATUS_SUCCESS,
|
||||
errno: 1,
|
||||
error: "Error",
|
||||
message: "Something",
|
||||
});
|
||||
let fields2 = error2._toStringFields();
|
||||
let statusCode = 1;
|
||||
|
||||
Assert.equal(error2.name, "FxAccountsOAuthGrantClientError");
|
||||
Assert.equal(error2.code, STATUS_SUCCESS);
|
||||
Assert.equal(error2.errno, statusCode);
|
||||
Assert.equal(error2.error, "Error");
|
||||
Assert.equal(error2.message, "Something");
|
||||
|
||||
Assert.equal(fields2.name, "FxAccountsOAuthGrantClientError");
|
||||
Assert.equal(fields2.code, STATUS_SUCCESS);
|
||||
Assert.equal(fields2.errno, statusCode);
|
||||
Assert.equal(fields2.error, "Error");
|
||||
Assert.equal(fields2.message, "Something");
|
||||
|
||||
Assert.ok(error2.toString().includes("Something"));
|
||||
run_next_test();
|
||||
});
|
||||
|
||||
add_test(function networkErrorResponse() {
|
||||
let client = new FxAccountsOAuthGrantClient({
|
||||
serverURL: "https://domain.dummy",
|
||||
client_id: "abc123",
|
||||
});
|
||||
client.getTokenFromAssertion("assertion", "scope").catch(function(e) {
|
||||
Assert.equal(e.name, "FxAccountsOAuthGrantClientError");
|
||||
Assert.equal(e.code, null);
|
||||
Assert.equal(e.errno, ERRNO_NETWORK);
|
||||
Assert.equal(e.error, ERROR_NETWORK);
|
||||
run_next_test();
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Quick way to test the "FxAccountsOAuthGrantClient" constructor.
|
||||
*
|
||||
* @param {Object} options
|
||||
* FxAccountsOAuthGrantClient constructor options
|
||||
* @param {String} expected
|
||||
* Expected error message, or null if it's expected to pass.
|
||||
* @returns {*}
|
||||
*/
|
||||
function validationHelper(options, expected) {
|
||||
try {
|
||||
new FxAccountsOAuthGrantClient(options);
|
||||
} catch (e) {
|
||||
return Assert.equal(e.toString(), expected);
|
||||
}
|
||||
return Assert.equal(expected, null);
|
||||
}
|
@ -1,70 +0,0 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
// A test of FxAccountsOAuthGrantClient but using a real server it can
|
||||
// hit.
|
||||
"use strict";
|
||||
|
||||
const { FxAccountsOAuthGrantClient } = ChromeUtils.import(
|
||||
"resource://gre/modules/FxAccountsOAuthGrantClient.jsm"
|
||||
);
|
||||
|
||||
// handlers for our server.
|
||||
var numTokenFetches;
|
||||
var activeTokens;
|
||||
|
||||
function authorize(request, response) {
|
||||
response.setStatusLine("1.1", 200, "OK");
|
||||
let token = "token" + numTokenFetches;
|
||||
numTokenFetches += 1;
|
||||
activeTokens.add(token);
|
||||
response.write(JSON.stringify({ access_token: token }));
|
||||
}
|
||||
|
||||
function destroy(request, response) {
|
||||
// Getting the body seems harder than it should be!
|
||||
let sis = Cc["@mozilla.org/scriptableinputstream;1"].createInstance(
|
||||
Ci.nsIScriptableInputStream
|
||||
);
|
||||
sis.init(request.bodyInputStream);
|
||||
let body = JSON.parse(sis.read(sis.available()));
|
||||
sis.close();
|
||||
let token = body.token;
|
||||
ok(activeTokens.delete(token));
|
||||
print("after destroy have", activeTokens.size, "tokens left.");
|
||||
response.setStatusLine("1.1", 200, "OK");
|
||||
response.write("{}");
|
||||
}
|
||||
|
||||
function startServer() {
|
||||
numTokenFetches = 0;
|
||||
activeTokens = new Set();
|
||||
let srv = new HttpServer();
|
||||
srv.registerPathHandler("/v1/authorization", authorize);
|
||||
srv.registerPathHandler("/v1/destroy", destroy);
|
||||
srv.start(-1);
|
||||
return srv;
|
||||
}
|
||||
|
||||
add_task(async function getAndRevokeToken() {
|
||||
Services.prefs.setBoolPref("identity.fxaccounts.allowHttp", true);
|
||||
let server = startServer();
|
||||
try {
|
||||
let clientOptions = {
|
||||
serverURL: "http://localhost:" + server.identity.primaryPort + "/v1",
|
||||
client_id: "abc123",
|
||||
};
|
||||
|
||||
let client = new FxAccountsOAuthGrantClient(clientOptions);
|
||||
let result = await client.getTokenFromAssertion("assertion", "scope");
|
||||
equal(result.access_token, "token0");
|
||||
equal(numTokenFetches, 1, "we hit the server to fetch a token");
|
||||
await client.destroyToken("token0");
|
||||
equal(activeTokens.size, 0, "We hit the server to revoke it");
|
||||
} finally {
|
||||
await promiseStopServer(server);
|
||||
Services.prefs.clearUserPref("identity.fxaccounts.allowHttp");
|
||||
}
|
||||
});
|
||||
|
||||
// XXX - TODO - we should probably add more tests for unexpected responses etc.
|
@ -82,6 +82,21 @@ function MockFxAccountsClient() {
|
||||
this.getDeviceList = function() {
|
||||
return Promise.resolve();
|
||||
};
|
||||
this.oauthDestroy = sinon.stub().callsFake((_clientId, token) => {
|
||||
this.activeTokens.delete(token);
|
||||
return Promise.resolve();
|
||||
});
|
||||
this.oauthToken = function(_sessionToken, _clientId, _scopeString) {
|
||||
let token = "token" + this.numTokenFetches;
|
||||
this.numTokenFetches += 1;
|
||||
this.activeTokens.add(token);
|
||||
print("oauthToken returning token", token);
|
||||
return Promise.resolve({ access_token: token });
|
||||
};
|
||||
|
||||
// Test only stuff.
|
||||
this.numTokenFetches = 0;
|
||||
this.activeTokens = new Set();
|
||||
|
||||
FxAccountsClient.apply(this);
|
||||
}
|
||||
@ -90,7 +105,7 @@ MockFxAccountsClient.prototype = {
|
||||
__proto__: FxAccountsClient.prototype,
|
||||
};
|
||||
|
||||
function MockFxAccounts(mockGrantClient) {
|
||||
function MockFxAccounts() {
|
||||
return new FxAccounts({
|
||||
fxAccountsClient: new MockFxAccountsClient(),
|
||||
getAssertion: () => Promise.resolve("assertion"),
|
||||
@ -100,12 +115,6 @@ function MockFxAccounts(mockGrantClient) {
|
||||
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";
|
||||
},
|
||||
@ -121,8 +130,8 @@ function MockFxAccounts(mockGrantClient) {
|
||||
});
|
||||
}
|
||||
|
||||
async function createMockFxA(mockGrantClient) {
|
||||
let fxa = new MockFxAccounts(mockGrantClient);
|
||||
async function createMockFxA() {
|
||||
let fxa = new MockFxAccounts();
|
||||
let credentials = {
|
||||
email: "foo@example.com",
|
||||
uid: "1234@lcip.org",
|
||||
@ -141,33 +150,10 @@ async function createMockFxA(mockGrantClient) {
|
||||
|
||||
// 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);
|
||||
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);
|
||||
@ -176,11 +162,9 @@ add_task(async function testRevoke() {
|
||||
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;
|
||||
ok(client.oauthDestroy.calledOnce);
|
||||
|
||||
// the revoke should have been successful.
|
||||
equal(client.activeTokens.size, 0);
|
||||
@ -193,17 +177,17 @@ add_task(async function testRevoke() {
|
||||
});
|
||||
|
||||
add_task(async function testSignOutDestroysTokens() {
|
||||
let client = new MockFxAccountsOAuthGrantClient();
|
||||
let fxa = await createMockFxA(client);
|
||||
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", client });
|
||||
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", client });
|
||||
let token2 = await fxa.getOAuthToken({ scope: "test-scope-2" });
|
||||
equal(client.numTokenFetches, 2);
|
||||
equal(client.activeTokens.size, 2);
|
||||
ok(token2, "got a token");
|
||||
@ -214,6 +198,7 @@ add_task(async function testSignOutDestroysTokens() {
|
||||
// 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);
|
||||
});
|
||||
@ -224,14 +209,14 @@ add_task(async function testTokenRaces() {
|
||||
// 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 client = new MockFxAccountsOAuthGrantClient();
|
||||
let fxa = await createMockFxA(client);
|
||||
let fxa = await createMockFxA();
|
||||
let client = fxa._internal.fxAccountsClient;
|
||||
|
||||
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 }),
|
||||
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.");
|
||||
@ -242,14 +227,9 @@ add_task(async function testTokenRaces() {
|
||||
equal(results[2], results[3]);
|
||||
// should be 2 active.
|
||||
equal(client.activeTokens.size, 2);
|
||||
// Which can each be revoked, which will trigger a notification.
|
||||
let 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;
|
||||
ok(client.oauthDestroy.calledTwice);
|
||||
});
|
||||
|
@ -13,8 +13,6 @@ support-files =
|
||||
[test_credentials.js]
|
||||
[test_device.js]
|
||||
[test_loginmgr_storage.js]
|
||||
[test_oauth_grant_client.js]
|
||||
[test_oauth_grant_client_server.js]
|
||||
[test_oauth_tokens.js]
|
||||
[test_oauth_token_storage.js]
|
||||
[test_pairing.js]
|
||||
|
@ -84,7 +84,6 @@
|
||||
"FxAccounts.jsm": ["fxAccounts", "FxAccounts"],
|
||||
"FxAccountsCommands.js": ["SendTab", "FxAccountsCommands"],
|
||||
"FxAccountsCommon.js": ["log", "logPII", "FXACCOUNTS_PERMISSION", "DATA_FORMAT_VERSION", "DEFAULT_STORAGE_FILENAME", "ASSERTION_LIFETIME", "ASSERTION_USE_PERIOD", "CERT_LIFETIME", "KEY_LIFETIME", "POLL_SESSION", "ONLOGIN_NOTIFICATION", "ONVERIFIED_NOTIFICATION", "ONLOGOUT_NOTIFICATION", "ON_COMMAND_RECEIVED_NOTIFICATION", "ON_DEVICE_CONNECTED_NOTIFICATION", "ON_DEVICE_DISCONNECTED_NOTIFICATION", "ON_PROFILE_UPDATED_NOTIFICATION", "ON_PASSWORD_CHANGED_NOTIFICATION", "ON_PASSWORD_RESET_NOTIFICATION", "ON_VERIFY_LOGIN_NOTIFICATION", "ON_ACCOUNT_DESTROYED_NOTIFICATION", "ON_COLLECTION_CHANGED_NOTIFICATION", "FXA_PUSH_SCOPE_ACCOUNT_UPDATE", "ON_PROFILE_CHANGE_NOTIFICATION", "ON_ACCOUNT_STATE_CHANGE_NOTIFICATION", "ON_NEW_DEVICE_ID", "COMMAND_SENDTAB", "SCOPE_PROFILE", "SCOPE_OLD_SYNC", "UI_REQUEST_SIGN_IN_FLOW", "UI_REQUEST_REFRESH_AUTH", "FX_OAUTH_CLIENT_ID", "WEBCHANNEL_ID", "COMMAND_PAIR_HEARTBEAT", "COMMAND_PAIR_SUPP_METADATA", "COMMAND_PAIR_AUTHORIZE", "COMMAND_PAIR_DECLINE", "COMMAND_PAIR_COMPLETE", "COMMAND_PAIR_PREFERENCES", "COMMAND_PROFILE_CHANGE", "COMMAND_CAN_LINK_ACCOUNT", "COMMAND_LOGIN", "COMMAND_LOGOUT", "COMMAND_DELETE", "COMMAND_SYNC_PREFERENCES", "COMMAND_CHANGE_PASSWORD", "COMMAND_FXA_STATUS", "PREF_LAST_FXA_USER", "PREF_REMOTE_PAIRING_URI", "ERRNO_ACCOUNT_ALREADY_EXISTS", "ERRNO_ACCOUNT_DOES_NOT_EXIST", "ERRNO_INCORRECT_PASSWORD", "ERRNO_UNVERIFIED_ACCOUNT", "ERRNO_INVALID_VERIFICATION_CODE", "ERRNO_NOT_VALID_JSON_BODY", "ERRNO_INVALID_BODY_PARAMETERS", "ERRNO_MISSING_BODY_PARAMETERS", "ERRNO_INVALID_REQUEST_SIGNATURE", "ERRNO_INVALID_AUTH_TOKEN", "ERRNO_INVALID_AUTH_TIMESTAMP", "ERRNO_MISSING_CONTENT_LENGTH", "ERRNO_REQUEST_BODY_TOO_LARGE", "ERRNO_TOO_MANY_CLIENT_REQUESTS", "ERRNO_INVALID_AUTH_NONCE", "ERRNO_ENDPOINT_NO_LONGER_SUPPORTED", "ERRNO_INCORRECT_LOGIN_METHOD", "ERRNO_INCORRECT_KEY_RETRIEVAL_METHOD", "ERRNO_INCORRECT_API_VERSION", "ERRNO_INCORRECT_EMAIL_CASE", "ERRNO_ACCOUNT_LOCKED", "ERRNO_ACCOUNT_UNLOCKED", "ERRNO_UNKNOWN_DEVICE", "ERRNO_DEVICE_SESSION_CONFLICT", "ERRNO_SERVICE_TEMP_UNAVAILABLE", "ERRNO_PARSE", "ERRNO_NETWORK", "ERRNO_UNKNOWN_ERROR", "OAUTH_SERVER_ERRNO_OFFSET", "ERRNO_UNKNOWN_CLIENT_ID", "ERRNO_INCORRECT_CLIENT_SECRET", "ERRNO_INCORRECT_REDIRECT_URI", "ERRNO_INVALID_FXA_ASSERTION", "ERRNO_UNKNOWN_CODE", "ERRNO_INCORRECT_CODE", "ERRNO_EXPIRED_CODE", "ERRNO_OAUTH_INVALID_TOKEN", "ERRNO_INVALID_REQUEST_PARAM", "ERRNO_INVALID_RESPONSE_TYPE", "ERRNO_UNAUTHORIZED", "ERRNO_FORBIDDEN", "ERRNO_INVALID_CONTENT_TYPE", "ERROR_ACCOUNT_ALREADY_EXISTS", "ERROR_ACCOUNT_DOES_NOT_EXIST", "ERROR_ACCOUNT_LOCKED", "ERROR_ACCOUNT_UNLOCKED", "ERROR_ALREADY_SIGNED_IN_USER", "ERROR_DEVICE_SESSION_CONFLICT", "ERROR_ENDPOINT_NO_LONGER_SUPPORTED", "ERROR_INCORRECT_API_VERSION", "ERROR_INCORRECT_EMAIL_CASE", "ERROR_INCORRECT_KEY_RETRIEVAL_METHOD", "ERROR_INCORRECT_LOGIN_METHOD", "ERROR_INVALID_EMAIL", "ERROR_INVALID_AUDIENCE", "ERROR_INVALID_AUTH_TOKEN", "ERROR_INVALID_AUTH_TIMESTAMP", "ERROR_INVALID_AUTH_NONCE", "ERROR_INVALID_BODY_PARAMETERS", "ERROR_INVALID_PASSWORD", "ERROR_INVALID_VERIFICATION_CODE", "ERROR_INVALID_REFRESH_AUTH_VALUE", "ERROR_INVALID_REQUEST_SIGNATURE", "ERROR_INTERNAL_INVALID_USER", "ERROR_MISSING_BODY_PARAMETERS", "ERROR_MISSING_CONTENT_LENGTH", "ERROR_NO_TOKEN_SESSION", "ERROR_NO_SILENT_REFRESH_AUTH", "ERROR_NOT_VALID_JSON_BODY", "ERROR_OFFLINE", "ERROR_PERMISSION_DENIED", "ERROR_REQUEST_BODY_TOO_LARGE", "ERROR_SERVER_ERROR", "ERROR_SYNC_DISABLED", "ERROR_TOO_MANY_CLIENT_REQUESTS", "ERROR_SERVICE_TEMP_UNAVAILABLE", "ERROR_UI_ERROR", "ERROR_UI_REQUEST", "ERROR_PARSE", "ERROR_NETWORK", "ERROR_UNKNOWN", "ERROR_UNKNOWN_DEVICE", "ERROR_UNVERIFIED_ACCOUNT", "ERROR_UNKNOWN_CLIENT_ID", "ERROR_INCORRECT_CLIENT_SECRET", "ERROR_INCORRECT_REDIRECT_URI", "ERROR_INVALID_FXA_ASSERTION", "ERROR_UNKNOWN_CODE", "ERROR_INCORRECT_CODE", "ERROR_EXPIRED_CODE", "ERROR_OAUTH_INVALID_TOKEN", "ERROR_INVALID_REQUEST_PARAM", "ERROR_INVALID_RESPONSE_TYPE", "ERROR_UNAUTHORIZED", "ERROR_FORBIDDEN", "ERROR_INVALID_CONTENT_TYPE", "ERROR_NO_ACCOUNT", "ERROR_AUTH_ERROR", "ERROR_INVALID_PARAMETER", "ERROR_CODE_METHOD_NOT_ALLOWED", "ERROR_MSG_METHOD_NOT_ALLOWED", "DERIVED_KEYS_NAMES", "FXA_PWDMGR_PLAINTEXT_FIELDS", "FXA_PWDMGR_SECURE_FIELDS", "FXA_PWDMGR_MEMORY_FIELDS", "FXA_PWDMGR_REAUTH_WHITELIST", "FXA_PWDMGR_HOST", "FXA_PWDMGR_REALM", "SERVER_ERRNO_TO_ERROR", "ERROR_TO_GENERAL_ERROR_CLASS"],
|
||||
"FxAccountsOAuthGrantClient.jsm": ["FxAccountsOAuthGrantClient", "FxAccountsOAuthGrantClientError"],
|
||||
"FxAccountsProfileClient.jsm": ["FxAccountsProfileClient", "FxAccountsProfileClientError"],
|
||||
"FxAccountsPush.js": ["FxAccountsPushService"],
|
||||
"FxAccountsStorage.jsm": ["FxAccountsStorageManagerCanStoreField", "FxAccountsStorageManager"],
|
||||
|
Loading…
Reference in New Issue
Block a user