mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-30 08:12:05 +00:00
Bug 1132293 - Let reliers access derived encryption keys through FxAccountsOAuthClient. r=mhammond
This commit is contained in:
parent
8bb3af49c6
commit
99a7a88365
@ -12,6 +12,7 @@ support-files =
|
||||
browser_bug678392-2.html
|
||||
browser_bug970746.xhtml
|
||||
browser_fxa_oauth.html
|
||||
browser_fxa_oauth_with_keys.html
|
||||
browser_fxa_profile_channel.html
|
||||
browser_registerProtocolHandler_notification.html
|
||||
browser_ssl_error_reports_content.js
|
||||
|
@ -17,11 +17,12 @@ XPCOMUtils.defineLazyModuleGetter(this, "FxAccountsOAuthClient",
|
||||
|
||||
const HTTP_PATH = "http://example.com";
|
||||
const HTTP_ENDPOINT = "/browser/browser/base/content/test/general/browser_fxa_oauth.html";
|
||||
const HTTP_ENDPOINT_WITH_KEYS = "/browser/browser/base/content/test/general/browser_fxa_oauth_with_keys.html";
|
||||
|
||||
let gTests = [
|
||||
{
|
||||
desc: "FxA OAuth - should open a new tab, complete OAuth flow",
|
||||
run: function* () {
|
||||
run: function () {
|
||||
return new Promise(function(resolve, reject) {
|
||||
let tabOpened = false;
|
||||
let properUrl = "http://example.com/browser/browser/base/content/test/general/browser_fxa_oauth.html";
|
||||
@ -71,6 +72,169 @@ let gTests = [
|
||||
resolve();
|
||||
};
|
||||
|
||||
client.onError = reject;
|
||||
|
||||
client.launchWebFlow();
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
desc: "FxA OAuth - should receive an error when there's a state mismatch",
|
||||
run: function () {
|
||||
return new Promise(function(resolve, reject) {
|
||||
let tabOpened = false;
|
||||
|
||||
waitForTab(function (tab) {
|
||||
Assert.ok("Tab successfully opened");
|
||||
|
||||
// It should have passed in the expected non-matching state value.
|
||||
let queryString = gBrowser.currentURI.spec.split('?')[1];
|
||||
Assert.ok(queryString.indexOf('state=different-state') >= 0);
|
||||
|
||||
tabOpened = true;
|
||||
});
|
||||
|
||||
let client = new FxAccountsOAuthClient({
|
||||
parameters: {
|
||||
state: "different-state",
|
||||
client_id: "client_id",
|
||||
oauth_uri: HTTP_PATH,
|
||||
content_uri: HTTP_PATH,
|
||||
},
|
||||
authorizationEndpoint: HTTP_ENDPOINT
|
||||
});
|
||||
|
||||
client.onComplete = reject;
|
||||
|
||||
client.onError = function(err) {
|
||||
Assert.ok(tabOpened);
|
||||
Assert.equal(err.message, "OAuth flow failed. State doesn't match");
|
||||
resolve();
|
||||
};
|
||||
|
||||
client.launchWebFlow();
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
desc: "FxA OAuth - should be able to request keys during OAuth flow",
|
||||
run: function () {
|
||||
return new Promise(function(resolve, reject) {
|
||||
let tabOpened = false;
|
||||
|
||||
waitForTab(function (tab) {
|
||||
Assert.ok("Tab successfully opened");
|
||||
|
||||
// It should have asked for keys.
|
||||
let queryString = gBrowser.currentURI.spec.split('?')[1];
|
||||
Assert.ok(queryString.indexOf('keys=true') >= 0);
|
||||
|
||||
tabOpened = true;
|
||||
});
|
||||
|
||||
let client = new FxAccountsOAuthClient({
|
||||
parameters: {
|
||||
state: "state",
|
||||
client_id: "client_id",
|
||||
oauth_uri: HTTP_PATH,
|
||||
content_uri: HTTP_PATH,
|
||||
keys: true,
|
||||
},
|
||||
authorizationEndpoint: HTTP_ENDPOINT_WITH_KEYS
|
||||
});
|
||||
|
||||
client.onComplete = function(tokenData, keys) {
|
||||
Assert.ok(tabOpened);
|
||||
Assert.equal(tokenData.code, "code1");
|
||||
Assert.equal(tokenData.state, "state");
|
||||
Assert.equal(keys.kAr, "kAr");
|
||||
Assert.equal(keys.kBr, "kBr");
|
||||
resolve();
|
||||
};
|
||||
|
||||
client.onError = reject;
|
||||
|
||||
client.launchWebFlow();
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
desc: "FxA OAuth - should not receive keys if not explicitly requested",
|
||||
run: function () {
|
||||
return new Promise(function(resolve, reject) {
|
||||
let tabOpened = false;
|
||||
|
||||
waitForTab(function (tab) {
|
||||
Assert.ok("Tab successfully opened");
|
||||
|
||||
// It should not have asked for keys.
|
||||
let queryString = gBrowser.currentURI.spec.split('?')[1];
|
||||
Assert.ok(queryString.indexOf('keys=true') == -1);
|
||||
|
||||
tabOpened = true;
|
||||
});
|
||||
|
||||
let client = new FxAccountsOAuthClient({
|
||||
parameters: {
|
||||
state: "state",
|
||||
client_id: "client_id",
|
||||
oauth_uri: HTTP_PATH,
|
||||
content_uri: HTTP_PATH
|
||||
},
|
||||
// This endpoint will cause the completion message to contain keys.
|
||||
authorizationEndpoint: HTTP_ENDPOINT_WITH_KEYS
|
||||
});
|
||||
|
||||
client.onComplete = function(tokenData, keys) {
|
||||
Assert.ok(tabOpened);
|
||||
Assert.equal(tokenData.code, "code1");
|
||||
Assert.equal(tokenData.state, "state");
|
||||
Assert.strictEqual(keys, undefined);
|
||||
resolve();
|
||||
};
|
||||
|
||||
client.onError = reject;
|
||||
|
||||
client.launchWebFlow();
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
desc: "FxA OAuth - should receive an error if keys could not be obtained",
|
||||
run: function () {
|
||||
return new Promise(function(resolve, reject) {
|
||||
let tabOpened = false;
|
||||
|
||||
waitForTab(function (tab) {
|
||||
Assert.ok("Tab successfully opened");
|
||||
|
||||
// It should have asked for keys.
|
||||
let queryString = gBrowser.currentURI.spec.split('?')[1];
|
||||
Assert.ok(queryString.indexOf('keys=true') >= 0);
|
||||
|
||||
tabOpened = true;
|
||||
});
|
||||
|
||||
let client = new FxAccountsOAuthClient({
|
||||
parameters: {
|
||||
state: "state",
|
||||
client_id: "client_id",
|
||||
oauth_uri: HTTP_PATH,
|
||||
content_uri: HTTP_PATH,
|
||||
keys: true,
|
||||
},
|
||||
// This endpoint will cause the completion message not to contain keys.
|
||||
authorizationEndpoint: HTTP_ENDPOINT
|
||||
});
|
||||
|
||||
client.onComplete = reject;
|
||||
|
||||
client.onError = function(err) {
|
||||
Assert.ok(tabOpened);
|
||||
Assert.equal(err.message, "OAuth flow failed. Keys were not returned");
|
||||
resolve();
|
||||
};
|
||||
|
||||
client.launchWebFlow();
|
||||
});
|
||||
}
|
||||
|
@ -0,0 +1,29 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>fxa_oauth_test</title>
|
||||
</head>
|
||||
<body>
|
||||
<script>
|
||||
window.onload = function(){
|
||||
var event = new window.CustomEvent("WebChannelMessageToChrome", {
|
||||
detail: {
|
||||
id: "oauth_client_id",
|
||||
message: {
|
||||
command: "oauth_complete",
|
||||
data: {
|
||||
state: "state",
|
||||
code: "code1",
|
||||
closeWindow: "signin",
|
||||
keys: { kAr: 'kAr', kBr: 'kBr' },
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
window.dispatchEvent(event);
|
||||
};
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
@ -964,6 +964,7 @@ let MozLoopServiceInternal = {
|
||||
this.promiseFxAOAuthClient().then(
|
||||
client => {
|
||||
client.onComplete = this._fxAOAuthComplete.bind(this, deferred);
|
||||
client.onError = this._fxAOAuthError.bind(this, deferred);
|
||||
client.launchWebFlow();
|
||||
},
|
||||
error => {
|
||||
@ -1003,18 +1004,24 @@ let MozLoopServiceInternal = {
|
||||
/**
|
||||
* Called once gFxAOAuthClient fires onComplete.
|
||||
*
|
||||
* @param {Deferred} deferred used to resolve or reject the gFxAOAuthClientPromise
|
||||
* @param {Deferred} deferred used to resolve the gFxAOAuthClientPromise
|
||||
* @param {Object} result (with code and state)
|
||||
*/
|
||||
_fxAOAuthComplete: function(deferred, result) {
|
||||
gFxAOAuthClientPromise = null;
|
||||
|
||||
// Note: The state was already verified in FxAccountsOAuthClient.
|
||||
if (result) {
|
||||
deferred.resolve(result);
|
||||
} else {
|
||||
deferred.reject("Invalid token data");
|
||||
}
|
||||
deferred.resolve(result);
|
||||
},
|
||||
|
||||
/**
|
||||
* Called if gFxAOAuthClient fires onError.
|
||||
*
|
||||
* @param {Deferred} deferred used to reject the gFxAOAuthClientPromise
|
||||
* @param {Object} error object returned by FxAOAuthClient
|
||||
*/
|
||||
_fxAOAuthError: function(deferred, err) {
|
||||
gFxAOAuthClientPromise = null;
|
||||
deferred.reject(err);
|
||||
},
|
||||
};
|
||||
Object.freeze(MozLoopServiceInternal);
|
||||
|
@ -37,6 +37,9 @@ Cu.importGlobalProperties(["URL"]);
|
||||
* Optional. A colon-separated list of scopes that the user has authorized
|
||||
* @param {String} [options.parameters.action]
|
||||
* Optional. If provided, should be either signup or signin.
|
||||
* @param {Boolean} [options.parameters.keys]
|
||||
* Optional. If true then relier-specific encryption keys will be
|
||||
* available in the second argument to onComplete.
|
||||
* @param [authorizationEndpoint] {String}
|
||||
* Optional authorization endpoint for the OAuth server
|
||||
* @constructor
|
||||
@ -60,16 +63,26 @@ this.FxAccountsOAuthClient = function(options) {
|
||||
params.append("scope", this.parameters.scope || "");
|
||||
params.append("action", this.parameters.action || "signin");
|
||||
params.append("webChannelId", this._webChannelId);
|
||||
if (this.parameters.keys) {
|
||||
params.append("keys", "true");
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
this.FxAccountsOAuthClient.prototype = {
|
||||
/**
|
||||
* Function that gets called once the OAuth flow is complete.
|
||||
* The callback will receive null as it's argument if there is a state mismatch or an object with
|
||||
* code and state properties otherwise.
|
||||
* The callback will receive an object with code and state properties.
|
||||
* If the keys parameter was specified and true, the callback will receive
|
||||
* a second argument with kAr and kBr properties.
|
||||
*/
|
||||
onComplete: null,
|
||||
/**
|
||||
* Function that gets called if there is an error during the OAuth flow,
|
||||
* for example due to a state mismatch.
|
||||
* The callback will receive an Error object as its argument.
|
||||
*/
|
||||
onError: null,
|
||||
/**
|
||||
* Configuration object that stores all OAuth parameters.
|
||||
*/
|
||||
@ -116,6 +129,7 @@ this.FxAccountsOAuthClient.prototype = {
|
||||
*/
|
||||
tearDown: function() {
|
||||
this.onComplete = null;
|
||||
this.onError = null;
|
||||
this._complete = true;
|
||||
this._channel.stopListening();
|
||||
this._channel = null;
|
||||
@ -160,21 +174,37 @@ this.FxAccountsOAuthClient.prototype = {
|
||||
|
||||
switch (command) {
|
||||
case "oauth_complete":
|
||||
// validate the state parameter and call onComplete
|
||||
// validate the returned state and call onComplete or onError
|
||||
let result = null;
|
||||
if (this.parameters.state === data.state) {
|
||||
let err = null;
|
||||
|
||||
if (this.parameters.state !== data.state) {
|
||||
err = new Error("OAuth flow failed. State doesn't match");
|
||||
} else if (this.parameters.keys && !data.keys) {
|
||||
err = new Error("OAuth flow failed. Keys were not returned");
|
||||
} else {
|
||||
result = {
|
||||
code: data.code,
|
||||
state: data.state
|
||||
};
|
||||
log.debug("OAuth flow completed.");
|
||||
} else {
|
||||
log.debug("OAuth flow failed. State doesn't match");
|
||||
}
|
||||
|
||||
if (this.onComplete) {
|
||||
this.onComplete(result);
|
||||
if (err) {
|
||||
log.debug(err.message);
|
||||
if (this.onError) {
|
||||
this.onError(err);
|
||||
}
|
||||
} else {
|
||||
log.debug("OAuth flow completed.");
|
||||
if (this.onComplete) {
|
||||
if (this.parameters.keys) {
|
||||
this.onComplete(result, data.keys);
|
||||
} else {
|
||||
this.onComplete(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// onComplete will be called for this client only once
|
||||
// calling onComplete again will result in a failure of the OAuth flow
|
||||
this.tearDown();
|
||||
|
Loading…
Reference in New Issue
Block a user