services/common/hawkrequest.js

Thu, 22 Jan 2015 13:21:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 22 Jan 2015 13:21:57 +0100
branch
TOR_BUG_9701
changeset 15
b8a032363ba2
permissions
-rw-r--r--

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 }

mercurial