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.

     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 }

mercurial