gecko/services/common/hawkrequest.js
Jed Parsons 96d06ec0f2 Bug 974990 - hawk request to access lang prefs as infrequently as possible. r=rnewman
--HG--
rename : services/common/hawk.js => services/common/hawkclient.js
rename : services/common/tests/unit/test_hawk.js => services/common/tests/unit/test_hawkclient.js
2014-02-25 09:19:47 -08:00

146 lines
4.4 KiB
JavaScript

/* 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 {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
this.EXPORTED_SYMBOLS = [
"HAWKAuthenticatedRESTRequest",
];
Cu.import("resource://gre/modules/Preferences.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Log.jsm");
Cu.import("resource://services-common/rest.js");
XPCOMUtils.defineLazyModuleGetter(this, "CryptoUtils",
"resource://services-crypto/utils.js");
const Prefs = new Preferences("services.common.rest.");
/**
* Single-use HAWK-authenticated HTTP requests to RESTish resources.
*
* @param uri
* (String) URI for the RESTRequest constructor
*
* @param credentials
* (Object) Optional credentials for computing HAWK authentication
* header.
*
* @param payloadObj
* (Object) Optional object to be converted to JSON payload
*
* @param extra
* (Object) Optional extra params for HAWK header computation.
* Valid properties are:
*
* now: <current time in milliseconds>,
* localtimeOffsetMsec: <local clock offset vs server>
*
* extra.localtimeOffsetMsec is the value in milliseconds that must be added to
* the local clock to make it agree with the server's clock. For instance, if
* the local clock is two minutes ahead of the server, the time offset in
* milliseconds will be -120000.
*/
this.HAWKAuthenticatedRESTRequest =
function HawkAuthenticatedRESTRequest(uri, credentials, extra={}) {
RESTRequest.call(this, uri);
this.credentials = credentials;
this.now = extra.now || Date.now();
this.localtimeOffsetMsec = extra.localtimeOffsetMsec || 0;
this._log.trace("local time, offset: " + this.now + ", " + (this.localtimeOffsetMsec));
// Expose for testing
this._intl = getIntl();
};
HAWKAuthenticatedRESTRequest.prototype = {
__proto__: RESTRequest.prototype,
dispatch: function dispatch(method, data, onComplete, onProgress) {
let contentType = "text/plain";
if (method == "POST" || method == "PUT") {
contentType = "application/json";
}
if (this.credentials) {
let options = {
now: this.now,
localtimeOffsetMsec: this.localtimeOffsetMsec,
credentials: this.credentials,
payload: data && JSON.stringify(data) || "",
contentType: contentType,
};
let header = CryptoUtils.computeHAWK(this.uri, method, options);
this.setHeader("Authorization", header.field);
this._log.trace("hawk auth header: " + header.field);
}
this.setHeader("Content-Type", contentType);
this.setHeader("Accept-Language", this._intl.accept_languages);
return RESTRequest.prototype.dispatch.call(
this, method, data, onComplete, onProgress
);
}
};
// With hawk request, we send the user's accepted-languages with each request.
// To keep the number of times we read this pref at a minimum, maintain the
// preference in a stateful object that notices and updates itself when the
// pref is changed.
this.Intl = function Intl() {
// We won't actually query the pref until the first time we need it
this._accepted = "";
this._everRead = false;
this._log = Log.repository.getLogger("Services.common.RESTRequest");
this._log.level = Log.Level[Prefs.get("log.logger.rest.request")];
this.init();
};
this.Intl.prototype = {
init: function() {
Services.prefs.addObserver("intl.accept_languages", this, false);
},
uninit: function() {
Services.prefs.removeObserver("intl.accept_languages", this);
},
observe: function(subject, topic, data) {
this.readPref();
},
readPref: function() {
this._everRead = true;
try {
this._accepted = Services.prefs.getComplexValue(
"intl.accept_languages", Ci.nsIPrefLocalizedString).data;
} catch (err) {
this._log.error("Error reading intl.accept_languages pref: " + CommonUtils.exceptionStr(err));
}
},
get accept_languages() {
if (!this._everRead) {
this.readPref();
}
return this._accepted;
},
};
// Singleton getter for Intl, creating an instance only when we first need it.
let intl = null;
function getIntl() {
if (!intl) {
intl = new Intl();
}
return intl;
}