Wed, 31 Dec 2014 06:09:35 +0100
Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.
1 /* Any copyright is dedicated to the Public Domain.
2 http://creativecommons.org/publicdomain/zero/1.0/ */
4 "use strict";
6 Cu.import("resource://gre/modules/Promise.jsm");
7 Cu.import("resource://services-common/hawkclient.js");
9 const SECOND_MS = 1000;
10 const MINUTE_MS = SECOND_MS * 60;
11 const HOUR_MS = MINUTE_MS * 60;
13 const TEST_CREDS = {
14 id: "eyJleHBpcmVzIjogMTM2NTAxMDg5OC4x",
15 key: "qTZf4ZFpAMpMoeSsX3zVRjiqmNs=",
16 algorithm: "sha256"
17 };
19 initTestLogging("Trace");
21 add_task(function test_now() {
22 let client = new HawkClient("https://example.com");
24 do_check_true(client.now() - Date.now() < SECOND_MS);
25 });
27 add_task(function test_updateClockOffset() {
28 let client = new HawkClient("https://example.com");
30 let now = new Date();
31 let serverDate = now.toUTCString();
33 // Client's clock is off
34 client.now = () => { return now.valueOf() + HOUR_MS; }
36 client._updateClockOffset(serverDate);
38 // Check that they're close; there will likely be a one-second rounding
39 // error, so checking strict equality will likely fail.
40 //
41 // localtimeOffsetMsec is how many milliseconds to add to the local clock so
42 // that it agrees with the server. We are one hour ahead of the server, so
43 // our offset should be -1 hour.
44 do_check_true(Math.abs(client.localtimeOffsetMsec + HOUR_MS) <= SECOND_MS);
45 });
47 add_task(function test_authenticated_get_request() {
48 let message = "{\"msg\": \"Great Success!\"}";
49 let method = "GET";
51 let server = httpd_setup({"/foo": (request, response) => {
52 do_check_true(request.hasHeader("Authorization"));
54 response.setStatusLine(request.httpVersion, 200, "OK");
55 response.bodyOutputStream.write(message, message.length);
56 }
57 });
59 let client = new HawkClient(server.baseURI);
61 let response = yield client.request("/foo", method, TEST_CREDS);
62 let result = JSON.parse(response);
64 do_check_eq("Great Success!", result.msg);
66 yield deferredStop(server);
67 });
69 add_task(function test_authenticated_post_request() {
70 let method = "POST";
72 let server = httpd_setup({"/foo": (request, response) => {
73 do_check_true(request.hasHeader("Authorization"));
75 response.setStatusLine(request.httpVersion, 200, "OK");
76 response.setHeader("Content-Type", "application/json");
77 response.bodyOutputStream.writeFrom(request.bodyInputStream, request.bodyInputStream.available());
78 }
79 });
81 let client = new HawkClient(server.baseURI);
83 let response = yield client.request("/foo", method, TEST_CREDS, {foo: "bar"});
84 let result = JSON.parse(response);
86 do_check_eq("bar", result.foo);
88 yield deferredStop(server);
89 });
91 add_task(function test_credentials_optional() {
92 let method = "GET";
93 let server = httpd_setup({
94 "/foo": (request, response) => {
95 do_check_false(request.hasHeader("Authorization"));
97 let message = JSON.stringify({msg: "you're in the friend zone"});
98 response.setStatusLine(request.httpVersion, 200, "OK");
99 response.setHeader("Content-Type", "application/json");
100 response.bodyOutputStream.write(message, message.length);
101 }
102 });
104 let client = new HawkClient(server.baseURI);
105 let result = yield client.request("/foo", method); // credentials undefined
106 do_check_eq(JSON.parse(result).msg, "you're in the friend zone");
108 yield deferredStop(server);
109 });
111 add_task(function test_server_error() {
112 let message = "Ohai!";
113 let method = "GET";
115 let server = httpd_setup({"/foo": (request, response) => {
116 response.setStatusLine(request.httpVersion, 418, "I am a Teapot");
117 response.bodyOutputStream.write(message, message.length);
118 }
119 });
121 let client = new HawkClient(server.baseURI);
123 try {
124 yield client.request("/foo", method, TEST_CREDS);
125 do_throw("Expected an error");
126 } catch(err) {
127 do_check_eq(418, err.code);
128 do_check_eq("I am a Teapot", err.message);
129 }
131 yield deferredStop(server);
132 });
134 add_task(function test_server_error_json() {
135 let message = JSON.stringify({error: "Cannot get ye flask."});
136 let method = "GET";
138 let server = httpd_setup({"/foo": (request, response) => {
139 response.setStatusLine(request.httpVersion, 400, "What wouldst thou deau?");
140 response.bodyOutputStream.write(message, message.length);
141 }
142 });
144 let client = new HawkClient(server.baseURI);
146 try {
147 yield client.request("/foo", method, TEST_CREDS);
148 do_throw("Expected an error");
149 } catch(err) {
150 do_check_eq("Cannot get ye flask.", err.error);
151 }
153 yield deferredStop(server);
154 });
156 add_task(function test_offset_after_request() {
157 let message = "Ohai!";
158 let method = "GET";
160 let server = httpd_setup({"/foo": (request, response) => {
161 response.setStatusLine(request.httpVersion, 200, "OK");
162 response.bodyOutputStream.write(message, message.length);
163 }
164 });
166 let client = new HawkClient(server.baseURI);
167 let now = Date.now();
168 client.now = () => { return now + HOUR_MS; };
170 do_check_eq(client.localtimeOffsetMsec, 0);
172 let response = yield client.request("/foo", method, TEST_CREDS);
173 // Should be about an hour off
174 do_check_true(Math.abs(client.localtimeOffsetMsec + HOUR_MS) < SECOND_MS);
176 yield deferredStop(server);
177 });
179 add_task(function test_offset_in_hawk_header() {
180 let message = "Ohai!";
181 let method = "GET";
183 let server = httpd_setup({
184 "/first": function(request, response) {
185 response.setStatusLine(request.httpVersion, 200, "OK");
186 response.bodyOutputStream.write(message, message.length);
187 },
189 "/second": function(request, response) {
190 // We see a better date now in the ts component of the header
191 let delta = getTimestampDelta(request.getHeader("Authorization"));
192 let message = "Delta: " + delta;
194 // We're now within HAWK's one-minute window.
195 // I hope this isn't a recipe for intermittent oranges ...
196 if (delta < MINUTE_MS) {
197 response.setStatusLine(request.httpVersion, 200, "OK");
198 } else {
199 response.setStatusLine(request.httpVersion, 400, "Delta: " + delta);
200 }
201 response.bodyOutputStream.write(message, message.length);
202 }
203 });
205 let client = new HawkClient(server.baseURI);
206 function getOffset() {
207 return client.localtimeOffsetMsec;
208 }
210 client.now = () => {
211 return Date.now() + 12 * HOUR_MS;
212 };
214 // We begin with no offset
215 do_check_eq(client.localtimeOffsetMsec, 0);
216 yield client.request("/first", method, TEST_CREDS);
218 // After the first server response, our offset is updated to -12 hours.
219 // We should be safely in the window, now.
220 do_check_true(Math.abs(client.localtimeOffsetMsec + 12 * HOUR_MS) < MINUTE_MS);
221 yield client.request("/second", method, TEST_CREDS);
223 yield deferredStop(server);
224 });
226 add_task(function test_2xx_success() {
227 // Just to ensure that we're not biased toward 200 OK for success
228 let credentials = {
229 id: "eyJleHBpcmVzIjogMTM2NTAxMDg5OC4x",
230 key: "qTZf4ZFpAMpMoeSsX3zVRjiqmNs=",
231 algorithm: "sha256"
232 };
233 let method = "GET";
235 let server = httpd_setup({"/foo": (request, response) => {
236 response.setStatusLine(request.httpVersion, 202, "Accepted");
237 }
238 });
240 let client = new HawkClient(server.baseURI);
242 let response = yield client.request("/foo", method, credentials);
244 // Shouldn't be any content in a 202
245 do_check_eq(response, "");
247 yield deferredStop(server);
248 });
250 add_task(function test_retry_request_on_fail() {
251 let attempts = 0;
252 let credentials = {
253 id: "eyJleHBpcmVzIjogMTM2NTAxMDg5OC4x",
254 key: "qTZf4ZFpAMpMoeSsX3zVRjiqmNs=",
255 algorithm: "sha256"
256 };
257 let method = "GET";
259 let server = httpd_setup({
260 "/maybe": function(request, response) {
261 // This path should be hit exactly twice; once with a bad timestamp, and
262 // again when the client retries the request with a corrected timestamp.
263 attempts += 1;
264 do_check_true(attempts <= 2);
266 let delta = getTimestampDelta(request.getHeader("Authorization"));
268 // First time through, we should have a bad timestamp
269 if (attempts === 1) {
270 do_check_true(delta > MINUTE_MS);
271 let message = "never!!!";
272 response.setStatusLine(request.httpVersion, 401, "Unauthorized");
273 response.bodyOutputStream.write(message, message.length);
274 return;
275 }
277 // Second time through, timestamp should be corrected by client
278 do_check_true(delta < MINUTE_MS);
279 let message = "i love you!!!";
280 response.setStatusLine(request.httpVersion, 200, "OK");
281 response.bodyOutputStream.write(message, message.length);
282 return;
283 }
284 });
286 let client = new HawkClient(server.baseURI);
287 function getOffset() {
288 return client.localtimeOffsetMsec;
289 }
291 client.now = () => {
292 return Date.now() + 12 * HOUR_MS;
293 };
295 // We begin with no offset
296 do_check_eq(client.localtimeOffsetMsec, 0);
298 // Request will have bad timestamp; client will retry once
299 let response = yield client.request("/maybe", method, credentials);
300 do_check_eq(response, "i love you!!!");
302 yield deferredStop(server);
303 });
305 add_task(function test_multiple_401_retry_once() {
306 // Like test_retry_request_on_fail, but always return a 401
307 // and ensure that the client only retries once.
308 let attempts = 0;
309 let credentials = {
310 id: "eyJleHBpcmVzIjogMTM2NTAxMDg5OC4x",
311 key: "qTZf4ZFpAMpMoeSsX3zVRjiqmNs=",
312 algorithm: "sha256"
313 };
314 let method = "GET";
316 let server = httpd_setup({
317 "/maybe": function(request, response) {
318 // This path should be hit exactly twice; once with a bad timestamp, and
319 // again when the client retries the request with a corrected timestamp.
320 attempts += 1;
322 do_check_true(attempts <= 2);
324 let message = "never!!!";
325 response.setStatusLine(request.httpVersion, 401, "Unauthorized");
326 response.bodyOutputStream.write(message, message.length);
327 }
328 });
330 let client = new HawkClient(server.baseURI);
331 function getOffset() {
332 return client.localtimeOffsetMsec;
333 }
335 client.now = () => {
336 return Date.now() - 12 * HOUR_MS;
337 };
339 // We begin with no offset
340 do_check_eq(client.localtimeOffsetMsec, 0);
342 // Request will have bad timestamp; client will retry once
343 try {
344 yield client.request("/maybe", method, credentials);
345 do_throw("Expected an error");
346 } catch (err) {
347 do_check_eq(err.code, 401);
348 }
349 do_check_eq(attempts, 2);
351 yield deferredStop(server);
352 });
354 add_task(function test_500_no_retry() {
355 // If we get a 500 error, the client should not retry (as it would with a
356 // 401)
357 let credentials = {
358 id: "eyJleHBpcmVzIjogMTM2NTAxMDg5OC4x",
359 key: "qTZf4ZFpAMpMoeSsX3zVRjiqmNs=",
360 algorithm: "sha256"
361 };
362 let method = "GET";
364 let server = httpd_setup({
365 "/no-shutup": function() {
366 let message = "Cannot get ye flask.";
367 response.setStatusLine(request.httpVersion, 500, "Internal server error");
368 response.bodyOutputStream.write(message, message.length);
369 }
370 });
372 let client = new HawkClient(server.baseURI);
373 function getOffset() {
374 return client.localtimeOffsetMsec;
375 }
377 // Throw off the clock so the HawkClient would want to retry the request if
378 // it could
379 client.now = () => {
380 return Date.now() - 12 * HOUR_MS;
381 };
383 // Request will 500; no retries
384 try {
385 yield client.request("/no-shutup", method, credentials);
386 do_throw("Expected an error");
387 } catch(err) {
388 do_check_eq(err.code, 500);
389 }
391 yield deferredStop(server);
392 });
394 add_task(function test_401_then_500() {
395 // Like test_multiple_401_retry_once, but return a 500 to the
396 // second request, ensuring that the promise is properly rejected
397 // in client.request.
398 let attempts = 0;
399 let credentials = {
400 id: "eyJleHBpcmVzIjogMTM2NTAxMDg5OC4x",
401 key: "qTZf4ZFpAMpMoeSsX3zVRjiqmNs=",
402 algorithm: "sha256"
403 };
404 let method = "GET";
406 let server = httpd_setup({
407 "/maybe": function(request, response) {
408 // This path should be hit exactly twice; once with a bad timestamp, and
409 // again when the client retries the request with a corrected timestamp.
410 attempts += 1;
411 do_check_true(attempts <= 2);
413 let delta = getTimestampDelta(request.getHeader("Authorization"));
415 // First time through, we should have a bad timestamp
416 // Client will retry
417 if (attempts === 1) {
418 do_check_true(delta > MINUTE_MS);
419 let message = "never!!!";
420 response.setStatusLine(request.httpVersion, 401, "Unauthorized");
421 response.bodyOutputStream.write(message, message.length);
422 return;
423 }
425 // Second time through, timestamp should be corrected by client
426 // And fail on the client
427 do_check_true(delta < MINUTE_MS);
428 let message = "Cannot get ye flask.";
429 response.setStatusLine(request.httpVersion, 500, "Internal server error");
430 response.bodyOutputStream.write(message, message.length);
431 return;
432 }
433 });
435 let client = new HawkClient(server.baseURI);
436 function getOffset() {
437 return client.localtimeOffsetMsec;
438 }
440 client.now = () => {
441 return Date.now() - 12 * HOUR_MS;
442 };
444 // We begin with no offset
445 do_check_eq(client.localtimeOffsetMsec, 0);
447 // Request will have bad timestamp; client will retry once
448 try {
449 yield client.request("/maybe", method, credentials);
450 } catch(err) {
451 do_check_eq(err.code, 500);
452 }
453 do_check_eq(attempts, 2);
455 yield deferredStop(server);
456 });
458 add_task(function throw_if_not_json_body() {
459 let client = new HawkClient("https://example.com");
460 try {
461 yield client.request("/bogus", "GET", {}, "I am not json");
462 do_throw("Expected an error");
463 } catch(err) {
464 do_check_true(!!err.message);
465 }
466 });
468 // End of tests.
469 // Utility functions follow
471 function getTimestampDelta(authHeader, now=Date.now()) {
472 let tsMS = new Date(
473 parseInt(/ts="(\d+)"/.exec(authHeader)[1], 10) * SECOND_MS);
474 return Math.abs(tsMS - now);
475 }
477 function deferredStop(server) {
478 let deferred = Promise.defer();
479 server.stop(deferred.resolve);
480 return deferred.promise;
481 }
483 function run_test() {
484 initTestLogging("Trace");
485 run_next_test();
486 }