зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1632557 - Add pref and logic for direct use of session tokens to provision OAuth tokens r=rfkelly
Differential Revision: https://phabricator.services.mozilla.com/D75204
This commit is contained in:
Родитель
a6395f4f1f
Коммит
de6e0f725b
|
@ -1447,6 +1447,9 @@ pref("identity.sync.tokenserver.uri", "https://token.services.mozilla.com/1.0/sy
|
|||
pref("identity.sync.useOAuthForSyncToken", false);
|
||||
#endif
|
||||
|
||||
// Using session tokens to fetch OAuth tokens
|
||||
pref("identity.fxaccounts.useSessionTokensForOAuth", true);
|
||||
|
||||
// Auto-config URL for FxA self-hosters, makes an HTTP request to
|
||||
// [identity.fxaccounts.autoconfig.uri]/.well-known/fxa-client-configuration
|
||||
// This is now the prefered way of pointing to a custom FxA server, instead
|
||||
|
|
|
@ -116,6 +116,12 @@ XPCOMUtils.defineLazyPreferenceGetter(
|
|||
true
|
||||
);
|
||||
|
||||
XPCOMUtils.defineLazyPreferenceGetter(
|
||||
this,
|
||||
"USE_SESSION_TOKENS_FOR_OAUTH",
|
||||
"identity.fxaccounts.useSessionTokensForOAuth"
|
||||
);
|
||||
|
||||
// An AccountState object holds all state related to one specific account.
|
||||
// It is considered "private" to the FxAccounts modules.
|
||||
// Only one AccountState is ever "current" in the FxAccountsInternal object -
|
||||
|
@ -1577,8 +1583,9 @@ FxAccountsInternal.prototype = {
|
|||
* @private
|
||||
*/
|
||||
async _doTokenFetch(scopeString, ttl) {
|
||||
// Ideally, we would auth this call directly with our `sessionToken` rather than
|
||||
// going via a BrowserID assertion. Before we can do so we need to resolve some
|
||||
// Ideally, we would auth this call directly with our `sessionToken`
|
||||
// using the `_doTokenFetchWithSessionToken` method rather than going
|
||||
// via a BrowserID assertion. Before we can do so we need to resolve some
|
||||
// data-volume processing issues in the server-side FxA metrics pipeline.
|
||||
let token;
|
||||
let oAuthURL = this.fxAccountsOAuthGrantClient.serverURL.href;
|
||||
|
@ -1611,6 +1618,26 @@ FxAccountsInternal.prototype = {
|
|||
return token;
|
||||
},
|
||||
|
||||
/**
|
||||
* Does the actual fetch of an oauth token for getOAuthToken()
|
||||
* using the account session token.
|
||||
* @param {String} scopeString
|
||||
* @param {Number} ttl
|
||||
* @returns {Promise<string>}
|
||||
* @private
|
||||
*/
|
||||
async _doTokenFetchWithSessionToken(scopeString, ttl) {
|
||||
return this.withSessionToken(async sessionToken => {
|
||||
const result = await this.fxAccountsClient.accessTokenWithSessionToken(
|
||||
sessionToken,
|
||||
FX_OAUTH_CLIENT_ID,
|
||||
scopeString,
|
||||
ttl
|
||||
);
|
||||
return result.access_token;
|
||||
});
|
||||
},
|
||||
|
||||
getOAuthToken(options = {}) {
|
||||
log.debug("getOAuthToken enter");
|
||||
let scope = options.scope;
|
||||
|
@ -1646,10 +1673,13 @@ FxAccountsInternal.prototype = {
|
|||
log.debug("getOAuthToken has an in-flight request for this scope");
|
||||
return maybeInFlight;
|
||||
}
|
||||
|
||||
let fetchFunction = this._doTokenFetch.bind(this);
|
||||
if (USE_SESSION_TOKENS_FOR_OAUTH) {
|
||||
fetchFunction = this._doTokenFetchWithSessionToken.bind(this);
|
||||
}
|
||||
// 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(scopeString, options.ttl)
|
||||
let promise = fetchFunction(scopeString, options.ttl)
|
||||
.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
|
||||
|
|
|
@ -488,6 +488,32 @@ FxAccountsClient.prototype = {
|
|||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* 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} scope
|
||||
* List of space-separated scopes.
|
||||
* @param {Number} ttl
|
||||
* Token time to live.
|
||||
* @return {Promise<Object>} Object containing an `access_token`.
|
||||
*/
|
||||
async accessTokenWithSessionToken(sessionTokenHex, clientId, scope, ttl) {
|
||||
const credentials = await deriveHawkCredentials(
|
||||
sessionTokenHex,
|
||||
"sessionToken"
|
||||
);
|
||||
const body = {
|
||||
client_id: clientId,
|
||||
grant_type: "fxa-credentials",
|
||||
scope,
|
||||
ttl,
|
||||
};
|
||||
return this._request("/oauth/token", "POST", credentials, body);
|
||||
},
|
||||
|
||||
/**
|
||||
* Determine if an account exists
|
||||
*
|
||||
|
|
|
@ -13,9 +13,6 @@ const {
|
|||
ASSERTION_LIFETIME,
|
||||
CERT_LIFETIME,
|
||||
ERRNO_INVALID_AUTH_TOKEN,
|
||||
ERRNO_INVALID_FXA_ASSERTION,
|
||||
ERRNO_NETWORK,
|
||||
ERROR_INVALID_FXA_ASSERTION,
|
||||
ERROR_NETWORK,
|
||||
ERROR_NO_ACCOUNT,
|
||||
KEY_LIFETIME,
|
||||
|
@ -36,7 +33,14 @@ var { AccountState } = ChromeUtils.import(
|
|||
|
||||
const ONE_HOUR_MS = 1000 * 60 * 60;
|
||||
const ONE_DAY_MS = ONE_HOUR_MS * 24;
|
||||
const TWO_MINUTES_MS = 1000 * 60 * 2;
|
||||
const MOCK_TOKEN_RESPONSE = {
|
||||
access_token:
|
||||
"43793fdfffec22eb39fc3c44ed09193a6fde4c24e5d6a73f73178597b268af69",
|
||||
token_type: "bearer",
|
||||
scope: "https://identity.mozilla.com/apps/oldsync",
|
||||
expires_in: 21600,
|
||||
auth_at: 1589579900,
|
||||
};
|
||||
|
||||
initTestLogging("Trace");
|
||||
|
||||
|
@ -1380,6 +1384,95 @@ add_test(function test_getOAuthToken() {
|
|||
});
|
||||
});
|
||||
|
||||
add_test(async function test_getOAuthTokenWithSessionToken() {
|
||||
Services.prefs.setBoolPref(
|
||||
"identity.fxaccounts.useSessionTokensForOAuth",
|
||||
true
|
||||
);
|
||||
let fxa = new MockFxAccounts();
|
||||
let alice = getTestUser("alice");
|
||||
alice.verified = true;
|
||||
let oauthTokenCalled = false;
|
||||
|
||||
let client = fxa._internal.fxAccountsClient;
|
||||
client.accessTokenWithSessionToken = async (
|
||||
sessionTokenHex,
|
||||
clientId,
|
||||
scope,
|
||||
ttl
|
||||
) => {
|
||||
oauthTokenCalled = true;
|
||||
Assert.equal(sessionTokenHex, "alice's session token");
|
||||
Assert.equal(clientId, "5882386c6d801776");
|
||||
Assert.equal(scope, "profile");
|
||||
Assert.equal(ttl, undefined);
|
||||
return MOCK_TOKEN_RESPONSE;
|
||||
};
|
||||
|
||||
await fxa.setSignedInUser(alice);
|
||||
const result = await fxa.getOAuthToken({ scope: "profile" });
|
||||
Assert.ok(oauthTokenCalled);
|
||||
Assert.equal(result, MOCK_TOKEN_RESPONSE.access_token);
|
||||
Services.prefs.setBoolPref(
|
||||
"identity.fxaccounts.useSessionTokensForOAuth",
|
||||
false
|
||||
);
|
||||
run_next_test();
|
||||
});
|
||||
|
||||
add_task(async function test_getOAuthTokenCachedWithSessionToken() {
|
||||
Services.prefs.setBoolPref(
|
||||
"identity.fxaccounts.useSessionTokensForOAuth",
|
||||
true
|
||||
);
|
||||
let fxa = new MockFxAccounts();
|
||||
let alice = getTestUser("alice");
|
||||
alice.verified = true;
|
||||
let numOauthTokenCalls = 0;
|
||||
|
||||
let client = fxa._internal.fxAccountsClient;
|
||||
client.accessTokenWithSessionToken = async () => {
|
||||
numOauthTokenCalls++;
|
||||
return MOCK_TOKEN_RESPONSE;
|
||||
};
|
||||
|
||||
await fxa.setSignedInUser(alice);
|
||||
let result = await fxa.getOAuthToken({
|
||||
scope: "profile",
|
||||
service: "test-service",
|
||||
});
|
||||
Assert.equal(numOauthTokenCalls, 1);
|
||||
Assert.equal(
|
||||
result,
|
||||
"43793fdfffec22eb39fc3c44ed09193a6fde4c24e5d6a73f73178597b268af69"
|
||||
);
|
||||
|
||||
// requesting it again should not re-fetch the token.
|
||||
result = await fxa.getOAuthToken({
|
||||
scope: "profile",
|
||||
service: "test-service",
|
||||
});
|
||||
Assert.equal(numOauthTokenCalls, 1);
|
||||
Assert.equal(
|
||||
result,
|
||||
"43793fdfffec22eb39fc3c44ed09193a6fde4c24e5d6a73f73178597b268af69"
|
||||
);
|
||||
// But requesting the same service and a different scope *will* get a new one.
|
||||
result = await fxa.getOAuthToken({
|
||||
scope: "something-else",
|
||||
service: "test-service",
|
||||
});
|
||||
Assert.equal(numOauthTokenCalls, 2);
|
||||
Assert.equal(
|
||||
result,
|
||||
"43793fdfffec22eb39fc3c44ed09193a6fde4c24e5d6a73f73178597b268af69"
|
||||
);
|
||||
Services.prefs.setBoolPref(
|
||||
"identity.fxaccounts.useSessionTokensForOAuth",
|
||||
false
|
||||
);
|
||||
});
|
||||
|
||||
add_test(function test_getOAuthTokenScoped() {
|
||||
let fxa = new MockFxAccounts();
|
||||
let alice = getTestUser("alice");
|
||||
|
|
|
@ -672,6 +672,40 @@ add_task(async function test_signCertificate() {
|
|||
await promiseStopServer(server);
|
||||
});
|
||||
|
||||
add_task(async function test_accessTokenWithSessionToken() {
|
||||
let server = httpd_setup({
|
||||
"/oauth/token": function(request, response) {
|
||||
const responseMessage = JSON.stringify({
|
||||
access_token:
|
||||
"43793fdfffec22eb39fc3c44ed09193a6fde4c24e5d6a73f73178597b268af69",
|
||||
token_type: "bearer",
|
||||
scope: "https://identity.mozilla.com/apps/oldsync",
|
||||
expires_in: 21600,
|
||||
auth_at: 1589579900,
|
||||
});
|
||||
|
||||
response.setStatusLine(request.httpVersion, 200, "OK");
|
||||
response.bodyOutputStream.write(responseMessage, responseMessage.length);
|
||||
},
|
||||
});
|
||||
|
||||
let client = new FxAccountsClient(server.baseURI);
|
||||
let sessionTokenHex =
|
||||
"0599c36ebb5cad6feb9285b9547b65342b5434d55c07b33bffd4307ab8f82dc4";
|
||||
let clientId = "5882386c6d801776";
|
||||
let scope = "https://identity.mozilla.com/apps/oldsync";
|
||||
let ttl = 100;
|
||||
let result = await client.accessTokenWithSessionToken(
|
||||
sessionTokenHex,
|
||||
clientId,
|
||||
scope,
|
||||
ttl
|
||||
);
|
||||
Assert.ok(result);
|
||||
|
||||
await promiseStopServer(server);
|
||||
});
|
||||
|
||||
add_task(async function test_accountExists() {
|
||||
let existsMessage = JSON.stringify({
|
||||
error: "wrong password",
|
||||
|
|
Загрузка…
Ссылка в новой задаче