Bumping manifests a=b2g-bump
[gecko.git] / services / common / hawkrequest.js
blobb02f4830c448389cb201e163119d8c4dd85eb70b
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/. */
5 "use strict";
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.");
27 /**
28  * Single-use HAWK-authenticated HTTP requests to RESTish resources.
29  *
30  * @param uri
31  *        (String) URI for the RESTRequest constructor
32  *
33  * @param credentials
34  *        (Object) Optional credentials for computing HAWK authentication
35  *        header.
36  *
37  * @param payloadObj
38  *        (Object) Optional object to be converted to JSON payload
39  *
40  * @param extra
41  *        (Object) Optional extra params for HAWK header computation.
42  *        Valid properties are:
43  *
44  *          now:                 <current time in milliseconds>,
45  *          localtimeOffsetMsec: <local clock offset vs server>
46  *
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.
51  */
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));
62   // Expose for testing
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";
72     }
73     if (this.credentials) {
74       let options = {
75         now: this.now,
76         localtimeOffsetMsec: this.localtimeOffsetMsec,
77         credentials: this.credentials,
78         payload: data && JSON.stringify(data) || "",
79         contentType: contentType,
80       };
81       let header = CryptoUtils.computeHAWK(this.uri, method, options);
82       this.setHeader("Authorization", header.field);
83       this._log.trace("hawk auth header: " + header.field);
84     }
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
92     );
93   }
97 /**
98   * Generic function to derive Hawk credentials.
99   *
100   * Hawk credentials are derived using shared secrets, which depend on the token
101   * in use.
102   *
103   * @param tokenHex
104   *        The current session token encoded in hex
105   * @param context
106   *        A context for the credentials. A protocol version will be prepended
107   *        to the context, see Credentials.keyWord for more information.
108   * @param size
109   *        The size in bytes of the expected derived buffer,
110   *        defaults to 3 * 32.
111   * @return credentials
112   *        Returns an object:
113   *        {
114   *          algorithm: sha256
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)
118   *        }
119   */
120 this.deriveHawkCredentials = function deriveHawkCredentials(tokenHex,
121                                                             context,
122                                                             size = 96,
123                                                             hexKey = false) {
124   let token = CommonUtils.hexToBytes(tokenHex);
125   let out = CryptoUtils.hkdf(token, undefined, Credentials.keyWord(context), size);
127   let result = {
128     algorithm: "sha256",
129     key: hexKey ? CommonUtils.bytesAsHex(out.slice(32, 64)) : out.slice(32, 64),
130     id: CommonUtils.bytesAsHex(out.slice(0, 32))
131   };
132   if (size > 64) {
133     result.extra = out.slice(64);
134   }
136   return result;
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
142 // pref is changed.
143 this.Intl = function Intl() {
144   // We won't actually query the pref until the first time we need it
145   this._accepted = "";
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")];
149   this.init();
152 this.Intl.prototype = {
153   init: function() {
154     Services.prefs.addObserver("intl.accept_languages", this, false);
155   },
157   uninit: function() {
158     Services.prefs.removeObserver("intl.accept_languages", this);
159   },
161   observe: function(subject, topic, data) {
162     this.readPref();
163   },
165   readPref: function() {
166     this._everRead = true;
167     try {
168       this._accepted = Services.prefs.getComplexValue(
169         "intl.accept_languages", Ci.nsIPrefLocalizedString).data;
170     } catch (err) {
171       this._log.error("Error reading intl.accept_languages pref: " + CommonUtils.exceptionStr(err));
172     }
173   },
175   get accept_languages() {
176     if (!this._everRead) {
177       this.readPref();
178     }
179     return this._accepted;
180   },
183 // Singleton getter for Intl, creating an instance only when we first need it.
184 let intl = null;
185 function getIntl() {
186   if (!intl) {
187     intl = new Intl();
188   }
189   return intl;