services/sync/tests/unit/test_browserid_identity.js

Thu, 22 Jan 2015 13:21:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 22 Jan 2015 13:21:57 +0100
branch
TOR_BUG_9701
changeset 15
b8a032363ba2
permissions
-rw-r--r--

Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6

     1 /* Any copyright is dedicated to the Public Domain.
     2  * http://creativecommons.org/publicdomain/zero/1.0/ */
     4 Cu.import("resource://gre/modules/FxAccounts.jsm");
     5 Cu.import("resource://services-sync/browserid_identity.js");
     6 Cu.import("resource://services-sync/rest.js");
     7 Cu.import("resource://services-sync/util.js");
     8 Cu.import("resource://services-common/utils.js");
     9 Cu.import("resource://services-crypto/utils.js");
    10 Cu.import("resource://testing-common/services/sync/utils.js");
    11 Cu.import("resource://testing-common/services/sync/fxa_utils.js");
    12 Cu.import("resource://services-common/hawkclient.js");
    13 Cu.import("resource://gre/modules/FxAccounts.jsm");
    14 Cu.import("resource://gre/modules/FxAccountsClient.jsm");
    15 Cu.import("resource://gre/modules/FxAccountsCommon.js");
    16 Cu.import("resource://services-sync/service.js");
    17 Cu.import("resource://services-sync/status.js");
    18 Cu.import("resource://services-sync/constants.js");
    20 const SECOND_MS = 1000;
    21 const MINUTE_MS = SECOND_MS * 60;
    22 const HOUR_MS = MINUTE_MS * 60;
    24 let identityConfig = makeIdentityConfig();
    25 let browseridManager = new BrowserIDManager();
    26 configureFxAccountIdentity(browseridManager, identityConfig);
    28 /**
    29  * Mock client clock and skew vs server in FxAccounts signed-in user module and
    30  * API client.  browserid_identity.js queries these values to construct HAWK
    31  * headers.  We will use this to test clock skew compensation in these headers
    32  * below.
    33  */
    34 let MockFxAccountsClient = function() {
    35   FxAccountsClient.apply(this);
    36 };
    37 MockFxAccountsClient.prototype = {
    38   __proto__: FxAccountsClient.prototype
    39 };
    41 function MockFxAccounts() {
    42   let fxa = new FxAccounts({
    43     _now_is: Date.now(),
    45     now: function () {
    46       return this._now_is;
    47     },
    49     fxAccountsClient: new MockFxAccountsClient()
    50   });
    51   fxa.internal.currentAccountState.getCertificate = function(data, keyPair, mustBeValidUntil) {
    52     this.cert = {
    53       validUntil: fxa.internal.now() + CERT_LIFETIME,
    54       cert: "certificate",
    55     };
    56     return Promise.resolve(this.cert.cert);
    57   };
    58   return fxa;
    59 }
    61 function run_test() {
    62   initTestLogging("Trace");
    63   Log.repository.getLogger("Sync.Identity").level = Log.Level.Trace;
    64   Log.repository.getLogger("Sync.BrowserIDManager").level = Log.Level.Trace;
    65   run_next_test();
    66 };
    68 add_test(function test_initial_state() {
    69     _("Verify initial state");
    70     do_check_false(!!browseridManager._token);
    71     do_check_false(browseridManager.hasValidToken());
    72     run_next_test();
    73   }
    74 );
    76 add_task(function test_initialializeWithCurrentIdentity() {
    77     _("Verify start after initializeWithCurrentIdentity");
    78     browseridManager.initializeWithCurrentIdentity();
    79     yield browseridManager.whenReadyToAuthenticate.promise;
    80     do_check_true(!!browseridManager._token);
    81     do_check_true(browseridManager.hasValidToken());
    82     do_check_eq(browseridManager.account, identityConfig.fxaccount.user.email);
    83   }
    84 );
    87 add_test(function test_getResourceAuthenticator() {
    88     _("BrowserIDManager supplies a Resource Authenticator callback which returns a Hawk header.");
    89     let authenticator = browseridManager.getResourceAuthenticator();
    90     do_check_true(!!authenticator);
    91     let req = {uri: CommonUtils.makeURI(
    92       "https://example.net/somewhere/over/the/rainbow"),
    93                method: 'GET'};
    94     let output = authenticator(req, 'GET');
    95     do_check_true('headers' in output);
    96     do_check_true('authorization' in output.headers);
    97     do_check_true(output.headers.authorization.startsWith('Hawk'));
    98     _("Expected internal state after successful call.");
    99     do_check_eq(browseridManager._token.uid, identityConfig.fxaccount.token.uid);
   100     run_next_test();
   101   }
   102 );
   104 add_test(function test_getRESTRequestAuthenticator() {
   105     _("BrowserIDManager supplies a REST Request Authenticator callback which sets a Hawk header on a request object.");
   106     let request = new SyncStorageRequest(
   107       "https://example.net/somewhere/over/the/rainbow");
   108     let authenticator = browseridManager.getRESTRequestAuthenticator();
   109     do_check_true(!!authenticator);
   110     let output = authenticator(request, 'GET');
   111     do_check_eq(request.uri, output.uri);
   112     do_check_true(output._headers.authorization.startsWith('Hawk'));
   113     do_check_true(output._headers.authorization.contains('nonce'));
   114     do_check_true(browseridManager.hasValidToken());
   115     run_next_test();
   116   }
   117 );
   119 add_test(function test_resourceAuthenticatorSkew() {
   120   _("BrowserIDManager Resource Authenticator compensates for clock skew in Hawk header.");
   122   // Clock is skewed 12 hours into the future
   123   // We pick a date in the past so we don't risk concealing bugs in code that
   124   // uses new Date() instead of our given date.
   125   let now = new Date("Fri Apr 09 2004 00:00:00 GMT-0700").valueOf() + 12 * HOUR_MS;
   126   let browseridManager = new BrowserIDManager();
   127   let hawkClient = new HawkClient("https://example.net/v1", "/foo");
   129   // mock fxa hawk client skew
   130   hawkClient.now = function() {
   131     dump("mocked client now: " + now + '\n');
   132     return now;
   133   }
   134   // Imagine there's already been one fxa request and the hawk client has
   135   // already detected skew vs the fxa auth server.
   136   let localtimeOffsetMsec = -1 * 12 * HOUR_MS;
   137   hawkClient._localtimeOffsetMsec = localtimeOffsetMsec;
   139   let fxaClient = new MockFxAccountsClient();
   140   fxaClient.hawk = hawkClient;
   142   // Sanity check
   143   do_check_eq(hawkClient.now(), now);
   144   do_check_eq(hawkClient.localtimeOffsetMsec, localtimeOffsetMsec);
   146   // Properly picked up by the client
   147   do_check_eq(fxaClient.now(), now);
   148   do_check_eq(fxaClient.localtimeOffsetMsec, localtimeOffsetMsec);
   150   let fxa = new MockFxAccounts();
   151   fxa.internal._now_is = now;
   152   fxa.internal.fxAccountsClient = fxaClient;
   154   // Picked up by the signed-in user module
   155   do_check_eq(fxa.internal.now(), now);
   156   do_check_eq(fxa.internal.localtimeOffsetMsec, localtimeOffsetMsec);
   158   do_check_eq(fxa.now(), now);
   159   do_check_eq(fxa.localtimeOffsetMsec, localtimeOffsetMsec);
   161   // Mocks within mocks...
   162   configureFxAccountIdentity(browseridManager, identityConfig);
   164   // Ensure the new FxAccounts mock has a signed-in user.
   165   fxa.internal.currentAccountState.signedInUser = browseridManager._fxaService.internal.currentAccountState.signedInUser;
   167   browseridManager._fxaService = fxa;
   169   do_check_eq(browseridManager._fxaService.internal.now(), now);
   170   do_check_eq(browseridManager._fxaService.internal.localtimeOffsetMsec,
   171       localtimeOffsetMsec);
   173   do_check_eq(browseridManager._fxaService.now(), now);
   174   do_check_eq(browseridManager._fxaService.localtimeOffsetMsec,
   175       localtimeOffsetMsec);
   177   let request = new SyncStorageRequest("https://example.net/i/like/pie/");
   178   let authenticator = browseridManager.getResourceAuthenticator();
   179   let output = authenticator(request, 'GET');
   180   dump("output" + JSON.stringify(output));
   181   let authHeader = output.headers.authorization;
   182   do_check_true(authHeader.startsWith('Hawk'));
   184   // Skew correction is applied in the header and we're within the two-minute
   185   // window.
   186   do_check_eq(getTimestamp(authHeader), now - 12 * HOUR_MS);
   187   do_check_true(
   188       (getTimestampDelta(authHeader, now) - 12 * HOUR_MS) < 2 * MINUTE_MS);
   190   run_next_test();
   191 });
   193 add_test(function test_RESTResourceAuthenticatorSkew() {
   194   _("BrowserIDManager REST Resource Authenticator compensates for clock skew in Hawk header.");
   196   // Clock is skewed 12 hours into the future from our arbitary date
   197   let now = new Date("Fri Apr 09 2004 00:00:00 GMT-0700").valueOf() + 12 * HOUR_MS;
   198   let browseridManager = new BrowserIDManager();
   199   let hawkClient = new HawkClient("https://example.net/v1", "/foo");
   201   // mock fxa hawk client skew
   202   hawkClient.now = function() {
   203     return now;
   204   }
   205   // Imagine there's already been one fxa request and the hawk client has
   206   // already detected skew vs the fxa auth server.
   207   hawkClient._localtimeOffsetMsec = -1 * 12 * HOUR_MS;
   209   let fxaClient = new MockFxAccountsClient();
   210   fxaClient.hawk = hawkClient;
   211   let fxa = new MockFxAccounts();
   212   fxa.internal._now_is = now;
   213   fxa.internal.fxAccountsClient = fxaClient;
   215   configureFxAccountIdentity(browseridManager, identityConfig);
   217   // Ensure the new FxAccounts mock has a signed-in user.
   218   fxa.internal.currentAccountState.signedInUser = browseridManager._fxaService.internal.currentAccountState.signedInUser;
   220   browseridManager._fxaService = fxa;
   222   do_check_eq(browseridManager._fxaService.internal.now(), now);
   224   let request = new SyncStorageRequest("https://example.net/i/like/pie/");
   225   let authenticator = browseridManager.getResourceAuthenticator();
   226   let output = authenticator(request, 'GET');
   227   dump("output" + JSON.stringify(output));
   228   let authHeader = output.headers.authorization;
   229   do_check_true(authHeader.startsWith('Hawk'));
   231   // Skew correction is applied in the header and we're within the two-minute
   232   // window.
   233   do_check_eq(getTimestamp(authHeader), now - 12 * HOUR_MS);
   234   do_check_true(
   235       (getTimestampDelta(authHeader, now) - 12 * HOUR_MS) < 2 * MINUTE_MS);
   237   run_next_test();
   238 });
   240 add_task(function test_ensureLoggedIn() {
   241   configureFxAccountIdentity(browseridManager);
   242   yield browseridManager.initializeWithCurrentIdentity();
   243   Assert.equal(Status.login, LOGIN_SUCCEEDED, "original initialize worked");
   244   yield browseridManager.ensureLoggedIn();
   245   Assert.equal(Status.login, LOGIN_SUCCEEDED, "original ensureLoggedIn worked");
   246   Assert.ok(browseridManager._shouldHaveSyncKeyBundle,
   247             "_shouldHaveSyncKeyBundle should always be true after ensureLogin completes.");
   249   // arrange for no logged in user.
   250   let fxa = browseridManager._fxaService
   251   let signedInUser = fxa.internal.currentAccountState.signedInUser;
   252   fxa.internal.currentAccountState.signedInUser = null;
   253   browseridManager.initializeWithCurrentIdentity();
   254   Assert.ok(!browseridManager._shouldHaveSyncKeyBundle,
   255             "_shouldHaveSyncKeyBundle should be false so we know we are testing what we think we are.");
   256   Status.login = LOGIN_FAILED_NO_USERNAME;
   257   yield Assert_rejects(browseridManager.ensureLoggedIn(), "expecting rejection due to no user");
   258   Assert.ok(browseridManager._shouldHaveSyncKeyBundle,
   259             "_shouldHaveSyncKeyBundle should always be true after ensureLogin completes.");
   260   fxa.internal.currentAccountState.signedInUser = signedInUser;
   261   Status.login = LOGIN_FAILED_LOGIN_REJECTED;
   262   yield Assert_rejects(browseridManager.ensureLoggedIn(),
   263                        "LOGIN_FAILED_LOGIN_REJECTED should have caused immediate rejection");
   264   Assert.equal(Status.login, LOGIN_FAILED_LOGIN_REJECTED,
   265                "status should remain LOGIN_FAILED_LOGIN_REJECTED");
   266   Status.login = LOGIN_FAILED_NETWORK_ERROR;
   267   yield browseridManager.ensureLoggedIn();
   268   Assert.equal(Status.login, LOGIN_SUCCEEDED, "final ensureLoggedIn worked");
   269 });
   271 add_test(function test_tokenExpiration() {
   272     _("BrowserIDManager notices token expiration:");
   273     let bimExp = new BrowserIDManager();
   274     configureFxAccountIdentity(bimExp, identityConfig);
   276     let authenticator = bimExp.getResourceAuthenticator();
   277     do_check_true(!!authenticator);
   278     let req = {uri: CommonUtils.makeURI(
   279       "https://example.net/somewhere/over/the/rainbow"),
   280                method: 'GET'};
   281     authenticator(req, 'GET');
   283     // Mock the clock.
   284     _("Forcing the token to expire ...");
   285     Object.defineProperty(bimExp, "_now", {
   286       value: function customNow() {
   287         return (Date.now() + 3000001);
   288       },
   289       writable: true,
   290     });
   291     do_check_true(bimExp._token.expiration < bimExp._now());
   292     _("... means BrowserIDManager knows to re-fetch it on the next call.");
   293     do_check_false(bimExp.hasValidToken());
   294     run_next_test();
   295   }
   296 );
   298 add_test(function test_sha256() {
   299   // Test vectors from http://www.bichlmeier.info/sha256test.html
   300   let vectors = [
   301     ["",
   302      "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"],
   303     ["abc",
   304      "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad"],
   305     ["message digest",
   306      "f7846f55cf23e14eebeab5b4e1550cad5b509e3348fbc4efa3a1413d393cb650"],
   307     ["secure hash algorithm",
   308      "f30ceb2bb2829e79e4ca9753d35a8ecc00262d164cc077080295381cbd643f0d"],
   309     ["SHA256 is considered to be safe",
   310      "6819d915c73f4d1e77e4e1b52d1fa0f9cf9beaead3939f15874bd988e2a23630"],
   311     ["abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq",
   312      "248d6a61d20638b8e5c026930c3e6039a33ce45964ff2167f6ecedd419db06c1"],
   313     ["For this sample, this 63-byte string will be used as input data",
   314      "f08a78cbbaee082b052ae0708f32fa1e50c5c421aa772ba5dbb406a2ea6be342"],
   315     ["This is exactly 64 bytes long, not counting the terminating byte",
   316      "ab64eff7e88e2e46165e29f2bce41826bd4c7b3552f6b382a9e7d3af47c245f8"]
   317   ];
   318   let bidUser = new BrowserIDManager();
   319   for (let [input,output] of vectors) {
   320     do_check_eq(CommonUtils.bytesAsHex(bidUser._sha256(input)), output);
   321   }
   322   run_next_test();
   323 });
   325 add_test(function test_computeXClientStateHeader() {
   326   let kBhex = "fd5c747806c07ce0b9d69dcfea144663e630b65ec4963596a22f24910d7dd15d";
   327   let kB = CommonUtils.hexToBytes(kBhex);
   329   let bidUser = new BrowserIDManager();
   330   let header = bidUser._computeXClientState(kB);
   332   do_check_eq(header, "6ae94683571c7a7c54dab4700aa3995f");
   333   run_next_test();
   334 });
   336 add_task(function test_getTokenErrors() {
   337   _("BrowserIDManager correctly handles various failures to get a token.");
   339   _("Arrange for a 401 - Sync should reflect an auth error.");
   340   initializeIdentityWithTokenServerResponse({
   341     status: 401,
   342     headers: {"content-type": "application/json"},
   343     body: JSON.stringify({}),
   344   });
   345   let browseridManager = Service.identity;
   347   yield browseridManager.initializeWithCurrentIdentity();
   348   yield Assert_rejects(browseridManager.whenReadyToAuthenticate.promise,
   349                        "should reject due to 401");
   350   Assert.equal(Status.login, LOGIN_FAILED_LOGIN_REJECTED, "login was rejected");
   352   // XXX - other interesting responses to return?
   354   // And for good measure, some totally "unexpected" errors - we generally
   355   // assume these problems are going to magically go away at some point.
   356   _("Arrange for an empty body with a 200 response - should reflect a network error.");
   357   initializeIdentityWithTokenServerResponse({
   358     status: 200,
   359     headers: [],
   360     body: "",
   361   });
   362   browseridManager = Service.identity;
   363   yield browseridManager.initializeWithCurrentIdentity();
   364   yield Assert_rejects(browseridManager.whenReadyToAuthenticate.promise,
   365                        "should reject due to non-JSON response");
   366   Assert.equal(Status.login, LOGIN_FAILED_NETWORK_ERROR, "login state is LOGIN_FAILED_NETWORK_ERROR");
   367 });
   369 add_task(function test_getTokenErrorWithRetry() {
   370   _("tokenserver sends an observer notification on various backoff headers.");
   372   // Set Sync's backoffInterval to zero - after we simulated the backoff header
   373   // it should reflect the value we sent.
   374   Status.backoffInterval = 0;
   375   _("Arrange for a 503 with a Retry-After header.");
   376   initializeIdentityWithTokenServerResponse({
   377     status: 503,
   378     headers: {"content-type": "application/json",
   379               "retry-after": "100"},
   380     body: JSON.stringify({}),
   381   });
   382   let browseridManager = Service.identity;
   384   yield browseridManager.initializeWithCurrentIdentity();
   385   yield Assert_rejects(browseridManager.whenReadyToAuthenticate.promise,
   386                        "should reject due to 503");
   388   // The observer should have fired - check it got the value in the response.
   389   Assert.equal(Status.login, LOGIN_FAILED_NETWORK_ERROR, "login was rejected");
   390   // Sync will have the value in ms with some slop - so check it is at least that.
   391   Assert.ok(Status.backoffInterval >= 100000);
   393   _("Arrange for a 200 with an X-Backoff header.");
   394   Status.backoffInterval = 0;
   395   initializeIdentityWithTokenServerResponse({
   396     status: 503,
   397     headers: {"content-type": "application/json",
   398               "x-backoff": "200"},
   399     body: JSON.stringify({}),
   400   });
   401   browseridManager = Service.identity;
   403   yield browseridManager.initializeWithCurrentIdentity();
   404   yield Assert_rejects(browseridManager.whenReadyToAuthenticate.promise,
   405                        "should reject due to no token in response");
   407   // The observer should have fired - check it got the value in the response.
   408   Assert.ok(Status.backoffInterval >= 200000);
   409 });
   411 add_task(function test_getKeysErrorWithBackoff() {
   412   _("Auth server (via hawk) sends an observer notification on backoff headers.");
   414   // Set Sync's backoffInterval to zero - after we simulated the backoff header
   415   // it should reflect the value we sent.
   416   Status.backoffInterval = 0;
   417   _("Arrange for a 503 with a X-Backoff header.");
   419   let config = makeIdentityConfig();
   420   // We want no kA or kB so we attempt to fetch them.
   421   delete config.fxaccount.user.kA;
   422   delete config.fxaccount.user.kB;
   423   config.fxaccount.user.keyFetchToken = "keyfetchtoken";
   424   yield initializeIdentityWithHAWKResponseFactory(config, function(method, data, uri) {
   425     Assert.equal(method, "get");
   426     Assert.equal(uri, "http://mockedserver:9999/account/keys")
   427     return {
   428       status: 503,
   429       headers: {"content-type": "application/json",
   430                 "x-backoff": "100"},
   431       body: "{}",
   432     }
   433   });
   435   let browseridManager = Service.identity;
   436   yield Assert_rejects(browseridManager.whenReadyToAuthenticate.promise,
   437                        "should reject due to 503");
   439   // The observer should have fired - check it got the value in the response.
   440   Assert.equal(Status.login, LOGIN_FAILED_NETWORK_ERROR, "login was rejected");
   441   // Sync will have the value in ms with some slop - so check it is at least that.
   442   Assert.ok(Status.backoffInterval >= 100000);
   443 });
   445 add_task(function test_getKeysErrorWithRetry() {
   446   _("Auth server (via hawk) sends an observer notification on retry headers.");
   448   // Set Sync's backoffInterval to zero - after we simulated the backoff header
   449   // it should reflect the value we sent.
   450   Status.backoffInterval = 0;
   451   _("Arrange for a 503 with a Retry-After header.");
   453   let config = makeIdentityConfig();
   454   // We want no kA or kB so we attempt to fetch them.
   455   delete config.fxaccount.user.kA;
   456   delete config.fxaccount.user.kB;
   457   config.fxaccount.user.keyFetchToken = "keyfetchtoken";
   458   yield initializeIdentityWithHAWKResponseFactory(config, function(method, data, uri) {
   459     Assert.equal(method, "get");
   460     Assert.equal(uri, "http://mockedserver:9999/account/keys")
   461     return {
   462       status: 503,
   463       headers: {"content-type": "application/json",
   464                 "retry-after": "100"},
   465       body: "{}",
   466     }
   467   });
   469   let browseridManager = Service.identity;
   470   yield Assert_rejects(browseridManager.whenReadyToAuthenticate.promise,
   471                        "should reject due to 503");
   473   // The observer should have fired - check it got the value in the response.
   474   Assert.equal(Status.login, LOGIN_FAILED_NETWORK_ERROR, "login was rejected");
   475   // Sync will have the value in ms with some slop - so check it is at least that.
   476   Assert.ok(Status.backoffInterval >= 100000);
   477 });
   479 add_task(function test_getHAWKErrors() {
   480   _("BrowserIDManager correctly handles various HAWK failures.");
   482   _("Arrange for a 401 - Sync should reflect an auth error.");
   483   let config = makeIdentityConfig();
   484   yield initializeIdentityWithHAWKResponseFactory(config, function(method, data, uri) {
   485     Assert.equal(method, "post");
   486     Assert.equal(uri, "http://mockedserver:9999/certificate/sign")
   487     return {
   488       status: 401,
   489       headers: {"content-type": "application/json"},
   490       body: JSON.stringify({}),
   491     }
   492   });
   493   Assert.equal(Status.login, LOGIN_FAILED_LOGIN_REJECTED, "login was rejected");
   495   // XXX - other interesting responses to return?
   497   // And for good measure, some totally "unexpected" errors - we generally
   498   // assume these problems are going to magically go away at some point.
   499   _("Arrange for an empty body with a 200 response - should reflect a network error.");
   500   yield initializeIdentityWithHAWKResponseFactory(config, function(method, data, uri) {
   501     Assert.equal(method, "post");
   502     Assert.equal(uri, "http://mockedserver:9999/certificate/sign")
   503     return {
   504       status: 200,
   505       headers: [],
   506       body: "",
   507     }
   508   });
   509   Assert.equal(Status.login, LOGIN_FAILED_NETWORK_ERROR, "login state is LOGIN_FAILED_NETWORK_ERROR");
   510 });
   512 add_task(function test_getGetKeysFailing401() {
   513   _("BrowserIDManager correctly handles 401 responses fetching keys.");
   515   _("Arrange for a 401 - Sync should reflect an auth error.");
   516   let config = makeIdentityConfig();
   517   // We want no kA or kB so we attempt to fetch them.
   518   delete config.fxaccount.user.kA;
   519   delete config.fxaccount.user.kB;
   520   config.fxaccount.user.keyFetchToken = "keyfetchtoken";
   521   yield initializeIdentityWithHAWKResponseFactory(config, function(method, data, uri) {
   522     Assert.equal(method, "get");
   523     Assert.equal(uri, "http://mockedserver:9999/account/keys")
   524     return {
   525       status: 401,
   526       headers: {"content-type": "application/json"},
   527       body: "{}",
   528     }
   529   });
   530   Assert.equal(Status.login, LOGIN_FAILED_LOGIN_REJECTED, "login was rejected");
   531 });
   533 add_task(function test_getGetKeysFailing503() {
   534   _("BrowserIDManager correctly handles 5XX responses fetching keys.");
   536   _("Arrange for a 503 - Sync should reflect a network error.");
   537   let config = makeIdentityConfig();
   538   // We want no kA or kB so we attempt to fetch them.
   539   delete config.fxaccount.user.kA;
   540   delete config.fxaccount.user.kB;
   541   config.fxaccount.user.keyFetchToken = "keyfetchtoken";
   542   yield initializeIdentityWithHAWKResponseFactory(config, function(method, data, uri) {
   543     Assert.equal(method, "get");
   544     Assert.equal(uri, "http://mockedserver:9999/account/keys")
   545     return {
   546       status: 503,
   547       headers: {"content-type": "application/json"},
   548       body: "{}",
   549     }
   550   });
   551   Assert.equal(Status.login, LOGIN_FAILED_NETWORK_ERROR, "state reflects network error");
   552 });
   554 add_task(function test_getKeysMissing() {
   555   _("BrowserIDManager correctly handles getKeys succeeding but not returning keys.");
   557   let browseridManager = new BrowserIDManager();
   558   let identityConfig = makeIdentityConfig();
   559   // our mock identity config already has kA and kB - remove them or we never
   560   // try and fetch them.
   561   delete identityConfig.fxaccount.user.kA;
   562   delete identityConfig.fxaccount.user.kB;
   563   identityConfig.fxaccount.user.keyFetchToken = 'keyFetchToken';
   565   configureFxAccountIdentity(browseridManager, identityConfig);
   567   // Mock a fxAccounts object that returns no keys
   568   let fxa = new FxAccounts({
   569     fetchAndUnwrapKeys: function () {
   570       return Promise.resolve({});
   571     },
   572     fxAccountsClient: new MockFxAccountsClient()
   573   });
   575   // Add a mock to the currentAccountState object.
   576   fxa.internal.currentAccountState.getCertificate = function(data, keyPair, mustBeValidUntil) {
   577     this.cert = {
   578       validUntil: fxa.internal.now() + CERT_LIFETIME,
   579       cert: "certificate",
   580     };
   581     return Promise.resolve(this.cert.cert);
   582   };
   584   // Ensure the new FxAccounts mock has a signed-in user.
   585   fxa.internal.currentAccountState.signedInUser = browseridManager._fxaService.internal.currentAccountState.signedInUser;
   587   browseridManager._fxaService = fxa;
   589   yield browseridManager.initializeWithCurrentIdentity();
   591   let ex;
   592   try {
   593     yield browseridManager.whenReadyToAuthenticate.promise;
   594   } catch (e) {
   595     ex = e;
   596   }
   598   Assert.ok(ex.message.indexOf("missing kA or kB") >= 0);
   599 });
   601 // End of tests
   602 // Utility functions follow
   604 // Create a new browserid_identity object and initialize it with a
   605 // hawk mock that simulates HTTP responses.
   606 // The callback function will be called each time the mocked hawk server wants
   607 // to make a request.  The result of the callback should be the mock response
   608 // object that will be returned to hawk.
   609 // A token server mock will be used that doesn't hit a server, so we move
   610 // directly to a hawk request.
   611 function* initializeIdentityWithHAWKResponseFactory(config, cbGetResponse) {
   612   // A mock request object.
   613   function MockRESTRequest(uri, credentials, extra) {
   614     this._uri = uri;
   615     this._credentials = credentials;
   616     this._extra = extra;
   617   };
   618   MockRESTRequest.prototype = {
   619     setHeader: function() {},
   620     post: function(data, callback) {
   621       this.response = cbGetResponse("post", data, this._uri, this._credentials, this._extra);
   622       callback.call(this);
   623     },
   624     get: function(callback) {
   625       this.response = cbGetResponse("get", null, this._uri, this._credentials, this._extra);
   626       callback.call(this);
   627     }
   628   }
   630   // The hawk client.
   631   function MockedHawkClient() {}
   632   MockedHawkClient.prototype = new HawkClient("http://mockedserver:9999");
   633   MockedHawkClient.prototype.constructor = MockedHawkClient;
   634   MockedHawkClient.prototype.newHAWKAuthenticatedRESTRequest = function(uri, credentials, extra) {
   635     return new MockRESTRequest(uri, credentials, extra);
   636   }
   637   // Arrange for the same observerPrefix as FxAccountsClient uses
   638   MockedHawkClient.prototype.observerPrefix = "FxA:hawk";
   640   // tie it all together - configureFxAccountIdentity isn't useful here :(
   641   let fxaClient = new MockFxAccountsClient();
   642   fxaClient.hawk = new MockedHawkClient();
   643   let internal = {
   644     fxAccountsClient: fxaClient,
   645   }
   646   let fxa = new FxAccounts(internal);
   647   fxa.internal.currentAccountState.signedInUser = {
   648       accountData: config.fxaccount.user,
   649   };
   651   browseridManager._fxaService = fxa;
   652   browseridManager._signedInUser = null;
   653   yield browseridManager.initializeWithCurrentIdentity();
   654   yield Assert_rejects(browseridManager.whenReadyToAuthenticate.promise,
   655                        "expecting rejection due to hawk error");
   656 }
   659 function getTimestamp(hawkAuthHeader) {
   660   return parseInt(/ts="(\d+)"/.exec(hawkAuthHeader)[1], 10) * SECOND_MS;
   661 }
   663 function getTimestampDelta(hawkAuthHeader, now=Date.now()) {
   664   return Math.abs(getTimestamp(hawkAuthHeader) - now);
   665 }

mercurial