services/common/tests/unit/test_hawkclient.js

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

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

mercurial