1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
3 * You can obtain one at http://mozilla.org/MPL/2.0/. */
7 const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
9 this.EXPORTED_SYMBOLS = [
10 "HAWKAuthenticatedRESTRequest",
11 "deriveHawkCredentials"
14 Cu.import("resource://gre/modules/Preferences.jsm");
15 Cu.import("resource://gre/modules/Services.jsm");
16 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
17 Cu.import("resource://gre/modules/Log.jsm");
18 Cu.import("resource://services-common/rest.js");
19 Cu.import("resource://services-common/utils.js");
20 Cu.import("resource://gre/modules/Credentials.jsm");
22 XPCOMUtils.defineLazyModuleGetter(this, "CryptoUtils",
23 "resource://services-crypto/utils.js");
25 const Prefs = new Preferences("services.common.rest.");
28 * Single-use HAWK-authenticated HTTP requests to RESTish resources.
31 * (String) URI for the RESTRequest constructor
34 * (Object) Optional credentials for computing HAWK authentication
38 * (Object) Optional object to be converted to JSON payload
41 * (Object) Optional extra params for HAWK header computation.
42 * Valid properties are:
44 * now: <current time in milliseconds>,
45 * localtimeOffsetMsec: <local clock offset vs server>
47 * extra.localtimeOffsetMsec is the value in milliseconds that must be added to
48 * the local clock to make it agree with the server's clock. For instance, if
49 * the local clock is two minutes ahead of the server, the time offset in
50 * milliseconds will be -120000.
53 this.HAWKAuthenticatedRESTRequest =
54 function HawkAuthenticatedRESTRequest(uri, credentials, extra={}) {
55 RESTRequest.call(this, uri);
57 this.credentials = credentials;
58 this.now = extra.now || Date.now();
59 this.localtimeOffsetMsec = extra.localtimeOffsetMsec || 0;
60 this._log.trace("local time, offset: " + this.now + ", " + (this.localtimeOffsetMsec));
63 this._intl = getIntl();
65 HAWKAuthenticatedRESTRequest.prototype = {
66 __proto__: RESTRequest.prototype,
68 dispatch: function dispatch(method, data, onComplete, onProgress) {
69 let contentType = "text/plain";
70 if (method == "POST" || method == "PUT") {
71 contentType = "application/json";
73 if (this.credentials) {
76 localtimeOffsetMsec: this.localtimeOffsetMsec,
77 credentials: this.credentials,
78 payload: data && JSON.stringify(data) || "",
79 contentType: contentType,
81 let header = CryptoUtils.computeHAWK(this.uri, method, options);
82 this.setHeader("Authorization", header.field);
83 this._log.trace("hawk auth header: " + header.field);
86 this.setHeader("Content-Type", contentType);
88 this.setHeader("Accept-Language", this._intl.accept_languages);
90 return RESTRequest.prototype.dispatch.call(
91 this, method, data, onComplete, onProgress
98 * Generic function to derive Hawk credentials.
100 * Hawk credentials are derived using shared secrets, which depend on the token
104 * The current session token encoded in hex
106 * A context for the credentials. A protocol version will be prepended
107 * to the context, see Credentials.keyWord for more information.
109 * The size in bytes of the expected derived buffer,
110 * defaults to 3 * 32.
111 * @return credentials
115 * id: the Hawk id (from the first 32 bytes derived)
116 * key: the Hawk key (from bytes 32 to 64)
117 * extra: size - 64 extra bytes (if size > 64)
120 this.deriveHawkCredentials = function deriveHawkCredentials(tokenHex,
124 let token = CommonUtils.hexToBytes(tokenHex);
125 let out = CryptoUtils.hkdf(token, undefined, Credentials.keyWord(context), size);
129 key: hexKey ? CommonUtils.bytesAsHex(out.slice(32, 64)) : out.slice(32, 64),
130 id: CommonUtils.bytesAsHex(out.slice(0, 32))
133 result.extra = out.slice(64);
139 // With hawk request, we send the user's accepted-languages with each request.
140 // To keep the number of times we read this pref at a minimum, maintain the
141 // preference in a stateful object that notices and updates itself when the
143 this.Intl = function Intl() {
144 // We won't actually query the pref until the first time we need it
146 this._everRead = false;
147 this._log = Log.repository.getLogger("Services.common.RESTRequest");
148 this._log.level = Log.Level[Prefs.get("log.logger.rest.request")];
152 this.Intl.prototype = {
154 Services.prefs.addObserver("intl.accept_languages", this, false);
158 Services.prefs.removeObserver("intl.accept_languages", this);
161 observe: function(subject, topic, data) {
165 readPref: function() {
166 this._everRead = true;
168 this._accepted = Services.prefs.getComplexValue(
169 "intl.accept_languages", Ci.nsIPrefLocalizedString).data;
171 this._log.error("Error reading intl.accept_languages pref: " + CommonUtils.exceptionStr(err));
175 get accept_languages() {
176 if (!this._everRead) {
179 return this._accepted;
183 // Singleton getter for Intl, creating an instance only when we first need it.