2012-04-09 15:40:06 -07:00
|
|
|
/* 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/. */
|
|
|
|
|
|
|
|
"use strict";
|
|
|
|
|
|
|
|
const EXPORTED_SYMBOLS = [
|
|
|
|
"TokenServerClient",
|
|
|
|
"TokenServerClientError",
|
|
|
|
"TokenServerClientNetworkError",
|
|
|
|
"TokenServerClientServerError"
|
|
|
|
];
|
|
|
|
|
|
|
|
const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
|
|
|
|
|
|
|
|
Cu.import("resource://services-common/log4moz.js");
|
|
|
|
Cu.import("resource://services-common/preferences.js");
|
|
|
|
Cu.import("resource://services-common/rest.js");
|
2012-04-11 18:51:48 -07:00
|
|
|
Cu.import("resource://services-common/utils.js");
|
2012-04-09 15:40:06 -07:00
|
|
|
|
|
|
|
const Prefs = new Preferences("services.common.tokenserverclient.");
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Represents a TokenServerClient error that occurred on the client.
|
|
|
|
*
|
|
|
|
* This is the base type for all errors raised by client operations.
|
|
|
|
*
|
|
|
|
* @param message
|
|
|
|
* (string) Error message.
|
|
|
|
*/
|
|
|
|
function TokenServerClientError(message) {
|
|
|
|
this.name = "TokenServerClientError";
|
|
|
|
this.message = message || "Client error.";
|
|
|
|
}
|
|
|
|
TokenServerClientError.prototype = new Error();
|
|
|
|
TokenServerClientError.prototype.constructor = TokenServerClientError;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Represents a TokenServerClient error that occurred in the network layer.
|
|
|
|
*
|
|
|
|
* @param error
|
|
|
|
* The underlying error thrown by the network layer.
|
|
|
|
*/
|
|
|
|
function TokenServerClientNetworkError(error) {
|
|
|
|
this.name = "TokenServerClientNetworkError";
|
|
|
|
this.error = error;
|
|
|
|
}
|
|
|
|
TokenServerClientNetworkError.prototype = new TokenServerClientError();
|
|
|
|
TokenServerClientNetworkError.prototype.constructor =
|
|
|
|
TokenServerClientNetworkError;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Represents a TokenServerClient error that occurred on the server.
|
|
|
|
*
|
|
|
|
* This type will be encountered for all non-200 response codes from the
|
|
|
|
* server.
|
|
|
|
*
|
|
|
|
* @param message
|
|
|
|
* (string) Error message.
|
|
|
|
*/
|
|
|
|
function TokenServerClientServerError(message) {
|
|
|
|
this.name = "TokenServerClientServerError";
|
|
|
|
this.message = message || "Server error.";
|
|
|
|
}
|
|
|
|
TokenServerClientServerError.prototype = new TokenServerClientError();
|
|
|
|
TokenServerClientServerError.prototype.constructor =
|
|
|
|
TokenServerClientServerError;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Represents a client to the Token Server.
|
|
|
|
*
|
|
|
|
* http://docs.services.mozilla.com/token/index.html
|
|
|
|
*
|
|
|
|
* The Token Server supports obtaining tokens for arbitrary apps by
|
|
|
|
* constructing URI paths of the form <app>/<app_version>. However, the service
|
|
|
|
* discovery mechanism emphasizes the use of full URIs and tries to not force
|
|
|
|
* the client to manipulate URIs. This client currently enforces this practice
|
|
|
|
* by not implementing an API which would perform URI manipulation.
|
|
|
|
*
|
|
|
|
* If you are tempted to implement this API in the future, consider this your
|
|
|
|
* warning that you may be doing it wrong and that you should store full URIs
|
|
|
|
* instead.
|
|
|
|
*
|
|
|
|
* Areas to Improve:
|
|
|
|
*
|
|
|
|
* - The server sends a JSON response on error. The client does not currently
|
|
|
|
* parse this. It might be convenient if it did.
|
|
|
|
* - Currently all non-200 status codes are rolled into one error type. It
|
|
|
|
* might be helpful if callers had a richer API that communicated who was
|
|
|
|
* at fault (e.g. differentiating a 503 from a 401).
|
|
|
|
*/
|
|
|
|
function TokenServerClient() {
|
|
|
|
this._log = Log4Moz.repository.getLogger("Common.TokenServerClient");
|
|
|
|
this._log.level = Log4Moz.Level[Prefs.get("logger.level")];
|
|
|
|
}
|
|
|
|
TokenServerClient.prototype = {
|
|
|
|
/**
|
|
|
|
* Logger instance.
|
|
|
|
*/
|
|
|
|
_log: null,
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Obtain a token from a BrowserID assertion against a specific URL.
|
|
|
|
*
|
|
|
|
* This asynchronously obtains the token. The callback receives 2 arguments.
|
|
|
|
* The first signifies an error and is a TokenServerClientError (or derived)
|
|
|
|
* type when an error occurs. If an HTTP response was seen, a RESTResponse
|
|
|
|
* instance will be stored in the "response" property of this object.
|
|
|
|
*
|
|
|
|
* The second argument to the callback is a map containing the results from
|
|
|
|
* the server. This map has the following keys:
|
|
|
|
*
|
|
|
|
* id (string) HTTP MAC public key identifier.
|
|
|
|
* key (string) HTTP MAC shared symmetric key.
|
|
|
|
* endpoint (string) URL where service can be connected to.
|
|
|
|
* uid (string) user ID for requested service.
|
2012-07-05 09:32:07 -07:00
|
|
|
* duration (string) the validity duration of the issued token.
|
2012-04-09 15:40:06 -07:00
|
|
|
*
|
|
|
|
* e.g.
|
|
|
|
*
|
|
|
|
* let client = new TokenServerClient();
|
|
|
|
* let assertion = getBrowserIDAssertionFromSomewhere();
|
|
|
|
* let url = "https://token.services.mozilla.com/1.0/sync/2.0";
|
|
|
|
*
|
|
|
|
* client.getTokenFromBrowserIDAssertion(url, assertion,
|
|
|
|
* function(error, result) {
|
|
|
|
* if (error) {
|
|
|
|
* // Do error handling.
|
|
|
|
* return;
|
|
|
|
* }
|
|
|
|
*
|
2012-07-05 09:32:07 -07:00
|
|
|
* let {
|
|
|
|
* id: id, key: key, uid: uid, endpoint: endpoint, duration: duration
|
|
|
|
* } = result;
|
2012-04-09 15:40:06 -07:00
|
|
|
* // Do stuff with data and carry on.
|
|
|
|
* });
|
|
|
|
*
|
|
|
|
* @param url
|
|
|
|
* (string) URL to fetch token from.
|
|
|
|
* @param assertion
|
|
|
|
* (string) BrowserID assertion to exchange token for.
|
|
|
|
* @param cb
|
|
|
|
* (function) Callback to be invoked with result of operation.
|
|
|
|
*/
|
|
|
|
getTokenFromBrowserIDAssertion:
|
|
|
|
function getTokenFromBrowserIDAssertion(url, assertion, cb) {
|
|
|
|
if (!url) {
|
|
|
|
throw new TokenServerClientError("url argument is not valid.");
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!assertion) {
|
|
|
|
throw new TokenServerClientError("assertion argument is not valid.");
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!cb) {
|
|
|
|
throw new TokenServerClientError("cb argument is not valid.");
|
|
|
|
}
|
|
|
|
|
|
|
|
this._log.debug("Beginning BID assertion exchange: " + url);
|
|
|
|
|
|
|
|
let req = new RESTRequest(url);
|
|
|
|
req.setHeader("accept", "application/json");
|
|
|
|
req.setHeader("authorization", "Browser-ID " + assertion);
|
|
|
|
let client = this;
|
|
|
|
req.get(function onResponse(error) {
|
|
|
|
if (error) {
|
|
|
|
cb(new TokenServerClientNetworkError(error), null);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2012-04-11 18:51:48 -07:00
|
|
|
let self = this;
|
|
|
|
function callCallback(error, result) {
|
|
|
|
if (!cb) {
|
|
|
|
self._log.warn("Callback already called! Did it throw?");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
try {
|
|
|
|
cb(error, result);
|
|
|
|
} catch (ex) {
|
|
|
|
self._log.warn("Exception when calling user-supplied callback: " +
|
|
|
|
CommonUtils.exceptionStr(ex));
|
|
|
|
}
|
|
|
|
|
|
|
|
cb = null;
|
|
|
|
}
|
|
|
|
|
2012-04-09 15:40:06 -07:00
|
|
|
try {
|
2012-04-11 18:51:48 -07:00
|
|
|
client._processTokenResponse(this.response, callCallback);
|
2012-04-09 15:40:06 -07:00
|
|
|
} catch (ex) {
|
2012-04-11 18:51:48 -07:00
|
|
|
this._log.warn("Error processing token server response: " +
|
|
|
|
CommonUtils.exceptionStr(ex));
|
|
|
|
|
2012-04-09 15:40:06 -07:00
|
|
|
let error = new TokenServerClientError(ex);
|
|
|
|
error.response = this.response;
|
2012-04-11 18:51:48 -07:00
|
|
|
callCallback(error, null);
|
2012-04-09 15:40:06 -07:00
|
|
|
}
|
|
|
|
});
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Handler to process token request responses.
|
|
|
|
*
|
|
|
|
* @param response
|
|
|
|
* RESTResponse from token HTTP request.
|
|
|
|
* @param cb
|
|
|
|
* The original callback passed to the public API.
|
|
|
|
*/
|
|
|
|
_processTokenResponse: function processTokenResponse(response, cb) {
|
|
|
|
this._log.debug("Got token response.");
|
|
|
|
|
|
|
|
if (!response.success) {
|
|
|
|
this._log.info("Non-200 response code to token request: " +
|
|
|
|
response.status);
|
|
|
|
this._log.debug("Response body: " + response.body);
|
|
|
|
let error = new TokenServerClientServerError("Non 200 response code: " +
|
|
|
|
response.status);
|
|
|
|
error.response = response;
|
|
|
|
cb(error, null);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
let ct = response.headers["content-type"];
|
|
|
|
if (ct != "application/json" && ct.indexOf("application/json;") != 0) {
|
|
|
|
let error = new TokenServerClientError("Unsupported media type: " + ct);
|
|
|
|
error.response = response;
|
|
|
|
cb(error, null);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
let result;
|
|
|
|
try {
|
|
|
|
result = JSON.parse(response.body);
|
|
|
|
} catch (ex) {
|
|
|
|
let error = new TokenServerClientServerError("Invalid JSON returned " +
|
|
|
|
"from server.");
|
|
|
|
error.response = response;
|
|
|
|
cb(error, null);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2012-04-11 15:24:21 -07:00
|
|
|
for each (let k in ["id", "key", "api_endpoint", "uid"]) {
|
2012-04-09 15:40:06 -07:00
|
|
|
if (!(k in result)) {
|
|
|
|
let error = new TokenServerClientServerError("Expected key not " +
|
|
|
|
" present in result: " +
|
|
|
|
k);
|
|
|
|
error.response = response;
|
|
|
|
cb(error, null);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
this._log.debug("Successful token response: " + result.id);
|
|
|
|
cb(null, {
|
|
|
|
id: result.id,
|
2012-04-11 15:24:21 -07:00
|
|
|
key: result.key,
|
2012-04-09 15:40:06 -07:00
|
|
|
endpoint: result.api_endpoint,
|
|
|
|
uid: result.uid,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
};
|