123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521 |
- /* Any copyright is dedicated to the Public Domain.
- http://creativecommons.org/publicdomain/zero/1.0/ */
- "use strict";
- Cu.import("resource://gre/modules/Promise.jsm");
- Cu.import("resource://services-common/hawkclient.js");
- const SECOND_MS = 1000;
- const MINUTE_MS = SECOND_MS * 60;
- const HOUR_MS = MINUTE_MS * 60;
- const TEST_CREDS = {
- id: "eyJleHBpcmVzIjogMTM2NTAxMDg5OC4x",
- key: "qTZf4ZFpAMpMoeSsX3zVRjiqmNs=",
- algorithm: "sha256"
- };
- initTestLogging("Trace");
- add_task(function test_now() {
- let client = new HawkClient("https://example.com");
- do_check_true(client.now() - Date.now() < SECOND_MS);
- });
- add_task(function test_updateClockOffset() {
- let client = new HawkClient("https://example.com");
- let now = new Date();
- let serverDate = now.toUTCString();
- // Client's clock is off
- client.now = () => { return now.valueOf() + HOUR_MS; }
- client._updateClockOffset(serverDate);
- // Check that they're close; there will likely be a one-second rounding
- // error, so checking strict equality will likely fail.
- //
- // localtimeOffsetMsec is how many milliseconds to add to the local clock so
- // that it agrees with the server. We are one hour ahead of the server, so
- // our offset should be -1 hour.
- do_check_true(Math.abs(client.localtimeOffsetMsec + HOUR_MS) <= SECOND_MS);
- });
- add_task(function* test_authenticated_get_request() {
- let message = "{\"msg\": \"Great Success!\"}";
- let method = "GET";
- let server = httpd_setup({"/foo": (request, response) => {
- do_check_true(request.hasHeader("Authorization"));
- response.setStatusLine(request.httpVersion, 200, "OK");
- response.bodyOutputStream.write(message, message.length);
- }
- });
- let client = new HawkClient(server.baseURI);
- let response = yield client.request("/foo", method, TEST_CREDS);
- let result = JSON.parse(response.body);
- do_check_eq("Great Success!", result.msg);
- yield deferredStop(server);
- });
- function* check_authenticated_request(method) {
- let server = httpd_setup({"/foo": (request, response) => {
- do_check_true(request.hasHeader("Authorization"));
- response.setStatusLine(request.httpVersion, 200, "OK");
- response.setHeader("Content-Type", "application/json");
- response.bodyOutputStream.writeFrom(request.bodyInputStream, request.bodyInputStream.available());
- }
- });
- let client = new HawkClient(server.baseURI);
- let response = yield client.request("/foo", method, TEST_CREDS, {foo: "bar"});
- let result = JSON.parse(response.body);
- do_check_eq("bar", result.foo);
- yield deferredStop(server);
- }
- add_task(function test_authenticated_post_request() {
- check_authenticated_request("POST");
- });
- add_task(function test_authenticated_put_request() {
- check_authenticated_request("PUT");
- });
- add_task(function test_authenticated_patch_request() {
- check_authenticated_request("PATCH");
- });
- add_task(function* test_extra_headers() {
- let server = httpd_setup({"/foo": (request, response) => {
- do_check_true(request.hasHeader("Authorization"));
- do_check_true(request.hasHeader("myHeader"));
- do_check_eq(request.getHeader("myHeader"), "fake");
- response.setStatusLine(request.httpVersion, 200, "OK");
- response.setHeader("Content-Type", "application/json");
- response.bodyOutputStream.writeFrom(request.bodyInputStream, request.bodyInputStream.available());
- }
- });
- let client = new HawkClient(server.baseURI);
- let response = yield client.request("/foo", "POST", TEST_CREDS, {foo: "bar"},
- {"myHeader": "fake"});
- let result = JSON.parse(response.body);
- do_check_eq("bar", result.foo);
- yield deferredStop(server);
- });
- add_task(function* test_credentials_optional() {
- let method = "GET";
- let server = httpd_setup({
- "/foo": (request, response) => {
- do_check_false(request.hasHeader("Authorization"));
- let message = JSON.stringify({msg: "you're in the friend zone"});
- response.setStatusLine(request.httpVersion, 200, "OK");
- response.setHeader("Content-Type", "application/json");
- response.bodyOutputStream.write(message, message.length);
- }
- });
- let client = new HawkClient(server.baseURI);
- let result = yield client.request("/foo", method); // credentials undefined
- do_check_eq(JSON.parse(result.body).msg, "you're in the friend zone");
- yield deferredStop(server);
- });
- add_task(function* test_server_error() {
- let message = "Ohai!";
- let method = "GET";
- let server = httpd_setup({"/foo": (request, response) => {
- response.setStatusLine(request.httpVersion, 418, "I am a Teapot");
- response.bodyOutputStream.write(message, message.length);
- }
- });
- let client = new HawkClient(server.baseURI);
- try {
- yield client.request("/foo", method, TEST_CREDS);
- do_throw("Expected an error");
- } catch(err) {
- do_check_eq(418, err.code);
- do_check_eq("I am a Teapot", err.message);
- }
- yield deferredStop(server);
- });
- add_task(function* test_server_error_json() {
- let message = JSON.stringify({error: "Cannot get ye flask."});
- let method = "GET";
- let server = httpd_setup({"/foo": (request, response) => {
- response.setStatusLine(request.httpVersion, 400, "What wouldst thou deau?");
- response.bodyOutputStream.write(message, message.length);
- }
- });
- let client = new HawkClient(server.baseURI);
- try {
- yield client.request("/foo", method, TEST_CREDS);
- do_throw("Expected an error");
- } catch(err) {
- do_check_eq("Cannot get ye flask.", err.error);
- }
- yield deferredStop(server);
- });
- add_task(function* test_offset_after_request() {
- let message = "Ohai!";
- let method = "GET";
- let server = httpd_setup({"/foo": (request, response) => {
- response.setStatusLine(request.httpVersion, 200, "OK");
- response.bodyOutputStream.write(message, message.length);
- }
- });
- let client = new HawkClient(server.baseURI);
- let now = Date.now();
- client.now = () => { return now + HOUR_MS; };
- do_check_eq(client.localtimeOffsetMsec, 0);
- let response = yield client.request("/foo", method, TEST_CREDS);
- // Should be about an hour off
- do_check_true(Math.abs(client.localtimeOffsetMsec + HOUR_MS) < SECOND_MS);
- yield deferredStop(server);
- });
- add_task(function* test_offset_in_hawk_header() {
- let message = "Ohai!";
- let method = "GET";
- let server = httpd_setup({
- "/first": function(request, response) {
- response.setStatusLine(request.httpVersion, 200, "OK");
- response.bodyOutputStream.write(message, message.length);
- },
- "/second": function(request, response) {
- // We see a better date now in the ts component of the header
- let delta = getTimestampDelta(request.getHeader("Authorization"));
- let message = "Delta: " + delta;
- // We're now within HAWK's one-minute window.
- // I hope this isn't a recipe for intermittent oranges ...
- if (delta < MINUTE_MS) {
- response.setStatusLine(request.httpVersion, 200, "OK");
- } else {
- response.setStatusLine(request.httpVersion, 400, "Delta: " + delta);
- }
- response.bodyOutputStream.write(message, message.length);
- }
- });
- let client = new HawkClient(server.baseURI);
- function getOffset() {
- return client.localtimeOffsetMsec;
- }
- client.now = () => {
- return Date.now() + 12 * HOUR_MS;
- };
- // We begin with no offset
- do_check_eq(client.localtimeOffsetMsec, 0);
- yield client.request("/first", method, TEST_CREDS);
- // After the first server response, our offset is updated to -12 hours.
- // We should be safely in the window, now.
- do_check_true(Math.abs(client.localtimeOffsetMsec + 12 * HOUR_MS) < MINUTE_MS);
- yield client.request("/second", method, TEST_CREDS);
- yield deferredStop(server);
- });
- add_task(function* test_2xx_success() {
- // Just to ensure that we're not biased toward 200 OK for success
- let credentials = {
- id: "eyJleHBpcmVzIjogMTM2NTAxMDg5OC4x",
- key: "qTZf4ZFpAMpMoeSsX3zVRjiqmNs=",
- algorithm: "sha256"
- };
- let method = "GET";
- let server = httpd_setup({"/foo": (request, response) => {
- response.setStatusLine(request.httpVersion, 202, "Accepted");
- }
- });
- let client = new HawkClient(server.baseURI);
- let response = yield client.request("/foo", method, credentials);
- // Shouldn't be any content in a 202
- do_check_eq(response.body, "");
- yield deferredStop(server);
- });
- add_task(function* test_retry_request_on_fail() {
- let attempts = 0;
- let credentials = {
- id: "eyJleHBpcmVzIjogMTM2NTAxMDg5OC4x",
- key: "qTZf4ZFpAMpMoeSsX3zVRjiqmNs=",
- algorithm: "sha256"
- };
- let method = "GET";
- let server = httpd_setup({
- "/maybe": function(request, response) {
- // This path should be hit exactly twice; once with a bad timestamp, and
- // again when the client retries the request with a corrected timestamp.
- attempts += 1;
- do_check_true(attempts <= 2);
- let delta = getTimestampDelta(request.getHeader("Authorization"));
- // First time through, we should have a bad timestamp
- if (attempts === 1) {
- do_check_true(delta > MINUTE_MS);
- let message = "never!!!";
- response.setStatusLine(request.httpVersion, 401, "Unauthorized");
- response.bodyOutputStream.write(message, message.length);
- return;
- }
- // Second time through, timestamp should be corrected by client
- do_check_true(delta < MINUTE_MS);
- let message = "i love you!!!";
- response.setStatusLine(request.httpVersion, 200, "OK");
- response.bodyOutputStream.write(message, message.length);
- return;
- }
- });
- let client = new HawkClient(server.baseURI);
- function getOffset() {
- return client.localtimeOffsetMsec;
- }
- client.now = () => {
- return Date.now() + 12 * HOUR_MS;
- };
- // We begin with no offset
- do_check_eq(client.localtimeOffsetMsec, 0);
- // Request will have bad timestamp; client will retry once
- let response = yield client.request("/maybe", method, credentials);
- do_check_eq(response.body, "i love you!!!");
- yield deferredStop(server);
- });
- add_task(function* test_multiple_401_retry_once() {
- // Like test_retry_request_on_fail, but always return a 401
- // and ensure that the client only retries once.
- let attempts = 0;
- let credentials = {
- id: "eyJleHBpcmVzIjogMTM2NTAxMDg5OC4x",
- key: "qTZf4ZFpAMpMoeSsX3zVRjiqmNs=",
- algorithm: "sha256"
- };
- let method = "GET";
- let server = httpd_setup({
- "/maybe": function(request, response) {
- // This path should be hit exactly twice; once with a bad timestamp, and
- // again when the client retries the request with a corrected timestamp.
- attempts += 1;
- do_check_true(attempts <= 2);
- let message = "never!!!";
- response.setStatusLine(request.httpVersion, 401, "Unauthorized");
- response.bodyOutputStream.write(message, message.length);
- }
- });
- let client = new HawkClient(server.baseURI);
- function getOffset() {
- return client.localtimeOffsetMsec;
- }
- client.now = () => {
- return Date.now() - 12 * HOUR_MS;
- };
- // We begin with no offset
- do_check_eq(client.localtimeOffsetMsec, 0);
- // Request will have bad timestamp; client will retry once
- try {
- yield client.request("/maybe", method, credentials);
- do_throw("Expected an error");
- } catch (err) {
- do_check_eq(err.code, 401);
- }
- do_check_eq(attempts, 2);
- yield deferredStop(server);
- });
- add_task(function* test_500_no_retry() {
- // If we get a 500 error, the client should not retry (as it would with a
- // 401)
- let credentials = {
- id: "eyJleHBpcmVzIjogMTM2NTAxMDg5OC4x",
- key: "qTZf4ZFpAMpMoeSsX3zVRjiqmNs=",
- algorithm: "sha256"
- };
- let method = "GET";
- let server = httpd_setup({
- "/no-shutup": function() {
- let message = "Cannot get ye flask.";
- response.setStatusLine(request.httpVersion, 500, "Internal server error");
- response.bodyOutputStream.write(message, message.length);
- }
- });
- let client = new HawkClient(server.baseURI);
- function getOffset() {
- return client.localtimeOffsetMsec;
- }
- // Throw off the clock so the HawkClient would want to retry the request if
- // it could
- client.now = () => {
- return Date.now() - 12 * HOUR_MS;
- };
- // Request will 500; no retries
- try {
- yield client.request("/no-shutup", method, credentials);
- do_throw("Expected an error");
- } catch(err) {
- do_check_eq(err.code, 500);
- }
- yield deferredStop(server);
- });
- add_task(function* test_401_then_500() {
- // Like test_multiple_401_retry_once, but return a 500 to the
- // second request, ensuring that the promise is properly rejected
- // in client.request.
- let attempts = 0;
- let credentials = {
- id: "eyJleHBpcmVzIjogMTM2NTAxMDg5OC4x",
- key: "qTZf4ZFpAMpMoeSsX3zVRjiqmNs=",
- algorithm: "sha256"
- };
- let method = "GET";
- let server = httpd_setup({
- "/maybe": function(request, response) {
- // This path should be hit exactly twice; once with a bad timestamp, and
- // again when the client retries the request with a corrected timestamp.
- attempts += 1;
- do_check_true(attempts <= 2);
- let delta = getTimestampDelta(request.getHeader("Authorization"));
- // First time through, we should have a bad timestamp
- // Client will retry
- if (attempts === 1) {
- do_check_true(delta > MINUTE_MS);
- let message = "never!!!";
- response.setStatusLine(request.httpVersion, 401, "Unauthorized");
- response.bodyOutputStream.write(message, message.length);
- return;
- }
- // Second time through, timestamp should be corrected by client
- // And fail on the client
- do_check_true(delta < MINUTE_MS);
- let message = "Cannot get ye flask.";
- response.setStatusLine(request.httpVersion, 500, "Internal server error");
- response.bodyOutputStream.write(message, message.length);
- return;
- }
- });
- let client = new HawkClient(server.baseURI);
- function getOffset() {
- return client.localtimeOffsetMsec;
- }
- client.now = () => {
- return Date.now() - 12 * HOUR_MS;
- };
- // We begin with no offset
- do_check_eq(client.localtimeOffsetMsec, 0);
- // Request will have bad timestamp; client will retry once
- try {
- yield client.request("/maybe", method, credentials);
- } catch(err) {
- do_check_eq(err.code, 500);
- }
- do_check_eq(attempts, 2);
- yield deferredStop(server);
- });
- add_task(function* throw_if_not_json_body() {
- let client = new HawkClient("https://example.com");
- try {
- yield client.request("/bogus", "GET", {}, "I am not json");
- do_throw("Expected an error");
- } catch(err) {
- do_check_true(!!err.message);
- }
- });
- // End of tests.
- // Utility functions follow
- function getTimestampDelta(authHeader, now=Date.now()) {
- let tsMS = new Date(
- parseInt(/ts="(\d+)"/.exec(authHeader)[1], 10) * SECOND_MS);
- return Math.abs(tsMS - now);
- }
- function deferredStop(server) {
- let deferred = Promise.defer();
- server.stop(deferred.resolve);
- return deferred.promise;
- }
- function run_test() {
- initTestLogging("Trace");
- run_next_test();
- }
|