summaryrefslogtreecommitdiffstats
path: root/services/common/tests/unit/test_hawkrequest.js
blob: 13ab6737dec397d26011b3a404dc53e81df74cf1 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
/* Any copyright is dedicated to the Public Domain.
   http://creativecommons.org/publicdomain/zero/1.0/ */

"use strict";

const { HAWKAuthenticatedRESTRequest, deriveHawkCredentials } =
  ChromeUtils.importESModule("resource://services-common/hawkrequest.sys.mjs");
const { Async } = ChromeUtils.importESModule(
  "resource://services-common/async.sys.mjs"
);

// https://github.com/mozilla/fxa-auth-server/wiki/onepw-protocol#wiki-use-session-certificatesign-etc
var SESSION_KEYS = {
  sessionToken: h(
    // eslint-disable-next-line no-useless-concat
    "a0a1a2a3a4a5a6a7 a8a9aaabacadaeaf" + "b0b1b2b3b4b5b6b7 b8b9babbbcbdbebf"
  ),

  tokenID: h(
    // eslint-disable-next-line no-useless-concat
    "c0a29dcf46174973 da1378696e4c82ae" + "10f723cf4f4d9f75 e39f4ae3851595ab"
  ),

  reqHMACkey: h(
    // eslint-disable-next-line no-useless-concat
    "9d8f22998ee7f579 8b887042466b72d5" + "3e56ab0c094388bf 65831f702d2febc0"
  ),
};

function do_register_cleanup() {
  Services.prefs.clearUserPref("intl.accept_languages");
  Services.prefs.clearUserPref("services.common.log.logger.rest.request");

  // remove the pref change listener
  let hawk = new HAWKAuthenticatedRESTRequest("https://example.com");
  hawk._intl.uninit();
}

function run_test() {
  registerCleanupFunction(do_register_cleanup);

  Services.prefs.setStringPref(
    "services.common.log.logger.rest.request",
    "Trace"
  );
  initTestLogging("Trace");

  run_next_test();
}

add_test(function test_intl_accept_language() {
  let testCount = 0;
  let languages = [
    "zu-NP;vo", // Nepalese dialect of Zulu, defaulting to Volapük
    "fa-CG;ik", // Congolese dialect of Farsei, defaulting to Inupiaq
  ];

  function setLanguagePref(lang) {
    Services.prefs.setStringPref("intl.accept_languages", lang);
  }

  let hawk = new HAWKAuthenticatedRESTRequest("https://example.com");

  Services.prefs.addObserver("intl.accept_languages", checkLanguagePref);
  setLanguagePref(languages[testCount]);

  function checkLanguagePref() {
    CommonUtils.nextTick(function () {
      // Ensure we're only called for the number of entries in languages[].
      Assert.ok(testCount < languages.length);

      Assert.equal(hawk._intl.accept_languages, languages[testCount]);

      testCount++;
      if (testCount < languages.length) {
        // Set next language in prefs; Pref service will call checkNextLanguage.
        setLanguagePref(languages[testCount]);
        return;
      }

      // We've checked all the entries in languages[]. Cleanup and move on.
      info(
        "Checked " +
          testCount +
          " languages. Removing checkLanguagePref as pref observer."
      );
      Services.prefs.removeObserver("intl.accept_languages", checkLanguagePref);
      run_next_test();
    });
  }
});

add_task(async function test_hawk_authenticated_request() {
  let postData = { your: "data" };

  // An arbitrary date - Feb 2, 1971.  It ends in a bunch of zeroes to make our
  // computation with the hawk timestamp easier, since hawk throws away the
  // millisecond values.
  let then = 34329600000;

  let clockSkew = 120000;
  let timeOffset = -1 * clockSkew;
  let localTime = then + clockSkew;

  // Set the accept-languages pref to the Nepalese dialect of Zulu.
  let acceptLanguage = "zu-NP"; // omit trailing ';', which our HTTP libs snip
  Services.prefs.setStringPref("intl.accept_languages", acceptLanguage);

  let credentials = {
    id: "eyJleHBpcmVzIjogMTM2NTAxMDg5OC4x",
    key: "qTZf4ZFpAMpMoeSsX3zVRjiqmNs=",
    algorithm: "sha256",
  };

  let server = httpd_setup({
    "/elysium": function (request, response) {
      Assert.ok(request.hasHeader("Authorization"));

      // check that the header timestamp is our arbitrary system date, not
      // today's date.  Note that hawk header timestamps are in seconds, not
      // milliseconds.
      let authorization = request.getHeader("Authorization");
      let tsMS = parseInt(/ts="(\d+)"/.exec(authorization)[1], 10) * 1000;
      Assert.equal(tsMS, then);

      // This testing can be a little wonky. In an environment where
      //   pref("intl.accept_languages") === 'en-US, en'
      // the header is sent as:
      //   'en-US,en;q=0.5'
      // hence our fake value for acceptLanguage.
      let lang = request.getHeader("Accept-Language");
      Assert.equal(lang, acceptLanguage);

      let message = "yay";
      response.setStatusLine(request.httpVersion, 200, "OK");
      response.bodyOutputStream.write(message, message.length);
    },
  });

  let url = server.baseURI + "/elysium";
  let extra = {
    now: localTime,
    localtimeOffsetMsec: timeOffset,
  };

  let request = new HAWKAuthenticatedRESTRequest(url, credentials, extra);

  // Allow hawk._intl to respond to the language pref change
  await Async.promiseYield();

  await request.post(postData);
  Assert.equal(200, request.response.status);
  Assert.equal(request.response.body, "yay");

  Services.prefs.clearUserPref("intl.accept_languages");
  let pref = Services.prefs.getComplexValue(
    "intl.accept_languages",
    Ci.nsIPrefLocalizedString
  );
  Assert.notEqual(acceptLanguage, pref.data);

  await promiseStopServer(server);
});

add_task(async function test_hawk_language_pref_changed() {
  let languages = [
    "zu-NP", // Nepalese dialect of Zulu
    "fa-CG", // Congolese dialect of Farsi
  ];

  let credentials = {
    id: "eyJleHBpcmVzIjogMTM2NTAxMDg5OC4x",
    key: "qTZf4ZFpAMpMoeSsX3zVRjiqmNs=",
    algorithm: "sha256",
  };

  function setLanguage(lang) {
    Services.prefs.setStringPref("intl.accept_languages", lang);
  }

  let server = httpd_setup({
    "/foo": function (request, response) {
      Assert.equal(languages[1], request.getHeader("Accept-Language"));

      response.setStatusLine(request.httpVersion, 200, "OK");
    },
  });

  let url = server.baseURI + "/foo";
  let request;

  setLanguage(languages[0]);

  // A new request should create the stateful object for tracking the current
  // language.
  request = new HAWKAuthenticatedRESTRequest(url, credentials);

  // Wait for change to propagate
  await Async.promiseYield();
  Assert.equal(languages[0], request._intl.accept_languages);

  // Change the language pref ...
  setLanguage(languages[1]);

  await Async.promiseYield();

  request = new HAWKAuthenticatedRESTRequest(url, credentials);
  let response = await request.post({});

  Assert.equal(200, response.status);
  Services.prefs.clearUserPref("intl.accept_languages");

  await promiseStopServer(server);
});

add_task(async function test_deriveHawkCredentials() {
  let credentials = await deriveHawkCredentials(
    SESSION_KEYS.sessionToken,
    "sessionToken"
  );
  Assert.equal(credentials.id, SESSION_KEYS.tokenID);
  Assert.equal(
    CommonUtils.bytesAsHex(credentials.key),
    SESSION_KEYS.reqHMACkey
  );
});

// turn formatted test vectors into normal hex strings
function h(hexStr) {
  return hexStr.replace(/\s+/g, "");
}