Thu, 22 Jan 2015 13:21:57 +0100
Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6
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 ];
13 Cu.import("resource://gre/modules/Preferences.jsm");
14 Cu.import("resource://gre/modules/Services.jsm");
15 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
16 Cu.import("resource://gre/modules/Log.jsm");
17 Cu.import("resource://services-common/rest.js");
19 XPCOMUtils.defineLazyModuleGetter(this, "CryptoUtils",
20 "resource://services-crypto/utils.js");
22 const Prefs = new Preferences("services.common.rest.");
24 /**
25 * Single-use HAWK-authenticated HTTP requests to RESTish resources.
26 *
27 * @param uri
28 * (String) URI for the RESTRequest constructor
29 *
30 * @param credentials
31 * (Object) Optional credentials for computing HAWK authentication
32 * header.
33 *
34 * @param payloadObj
35 * (Object) Optional object to be converted to JSON payload
36 *
37 * @param extra
38 * (Object) Optional extra params for HAWK header computation.
39 * Valid properties are:
40 *
41 * now: <current time in milliseconds>,
42 * localtimeOffsetMsec: <local clock offset vs server>
43 *
44 * extra.localtimeOffsetMsec is the value in milliseconds that must be added to
45 * the local clock to make it agree with the server's clock. For instance, if
46 * the local clock is two minutes ahead of the server, the time offset in
47 * milliseconds will be -120000.
48 */
50 this.HAWKAuthenticatedRESTRequest =
51 function HawkAuthenticatedRESTRequest(uri, credentials, extra={}) {
52 RESTRequest.call(this, uri);
54 this.credentials = credentials;
55 this.now = extra.now || Date.now();
56 this.localtimeOffsetMsec = extra.localtimeOffsetMsec || 0;
57 this._log.trace("local time, offset: " + this.now + ", " + (this.localtimeOffsetMsec));
59 // Expose for testing
60 this._intl = getIntl();
61 };
62 HAWKAuthenticatedRESTRequest.prototype = {
63 __proto__: RESTRequest.prototype,
65 dispatch: function dispatch(method, data, onComplete, onProgress) {
66 let contentType = "text/plain";
67 if (method == "POST" || method == "PUT") {
68 contentType = "application/json";
69 }
70 if (this.credentials) {
71 let options = {
72 now: this.now,
73 localtimeOffsetMsec: this.localtimeOffsetMsec,
74 credentials: this.credentials,
75 payload: data && JSON.stringify(data) || "",
76 contentType: contentType,
77 };
78 let header = CryptoUtils.computeHAWK(this.uri, method, options);
79 this.setHeader("Authorization", header.field);
80 this._log.trace("hawk auth header: " + header.field);
81 }
83 this.setHeader("Content-Type", contentType);
85 this.setHeader("Accept-Language", this._intl.accept_languages);
87 return RESTRequest.prototype.dispatch.call(
88 this, method, data, onComplete, onProgress
89 );
90 }
91 };
93 // With hawk request, we send the user's accepted-languages with each request.
94 // To keep the number of times we read this pref at a minimum, maintain the
95 // preference in a stateful object that notices and updates itself when the
96 // pref is changed.
97 this.Intl = function Intl() {
98 // We won't actually query the pref until the first time we need it
99 this._accepted = "";
100 this._everRead = false;
101 this._log = Log.repository.getLogger("Services.common.RESTRequest");
102 this._log.level = Log.Level[Prefs.get("log.logger.rest.request")];
103 this.init();
104 };
106 this.Intl.prototype = {
107 init: function() {
108 Services.prefs.addObserver("intl.accept_languages", this, false);
109 },
111 uninit: function() {
112 Services.prefs.removeObserver("intl.accept_languages", this);
113 },
115 observe: function(subject, topic, data) {
116 this.readPref();
117 },
119 readPref: function() {
120 this._everRead = true;
121 try {
122 this._accepted = Services.prefs.getComplexValue(
123 "intl.accept_languages", Ci.nsIPrefLocalizedString).data;
124 } catch (err) {
125 this._log.error("Error reading intl.accept_languages pref: " + CommonUtils.exceptionStr(err));
126 }
127 },
129 get accept_languages() {
130 if (!this._everRead) {
131 this.readPref();
132 }
133 return this._accepted;
134 },
135 };
137 // Singleton getter for Intl, creating an instance only when we first need it.
138 let intl = null;
139 function getIntl() {
140 if (!intl) {
141 intl = new Intl();
142 }
143 return intl;
144 }