services/sync/tests/unit/test_browserid_identity.js

branch
TOR_BUG_9701
changeset 15
b8a032363ba2
equal deleted inserted replaced
-1:000000000000 0:970d72f28065
1 /* Any copyright is dedicated to the Public Domain.
2 * http://creativecommons.org/publicdomain/zero/1.0/ */
3
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");
19
20 const SECOND_MS = 1000;
21 const MINUTE_MS = SECOND_MS * 60;
22 const HOUR_MS = MINUTE_MS * 60;
23
24 let identityConfig = makeIdentityConfig();
25 let browseridManager = new BrowserIDManager();
26 configureFxAccountIdentity(browseridManager, identityConfig);
27
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 };
40
41 function MockFxAccounts() {
42 let fxa = new FxAccounts({
43 _now_is: Date.now(),
44
45 now: function () {
46 return this._now_is;
47 },
48
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 }
60
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 };
67
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 );
75
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 );
85
86
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 );
103
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 );
118
119 add_test(function test_resourceAuthenticatorSkew() {
120 _("BrowserIDManager Resource Authenticator compensates for clock skew in Hawk header.");
121
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");
128
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;
138
139 let fxaClient = new MockFxAccountsClient();
140 fxaClient.hawk = hawkClient;
141
142 // Sanity check
143 do_check_eq(hawkClient.now(), now);
144 do_check_eq(hawkClient.localtimeOffsetMsec, localtimeOffsetMsec);
145
146 // Properly picked up by the client
147 do_check_eq(fxaClient.now(), now);
148 do_check_eq(fxaClient.localtimeOffsetMsec, localtimeOffsetMsec);
149
150 let fxa = new MockFxAccounts();
151 fxa.internal._now_is = now;
152 fxa.internal.fxAccountsClient = fxaClient;
153
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);
157
158 do_check_eq(fxa.now(), now);
159 do_check_eq(fxa.localtimeOffsetMsec, localtimeOffsetMsec);
160
161 // Mocks within mocks...
162 configureFxAccountIdentity(browseridManager, identityConfig);
163
164 // Ensure the new FxAccounts mock has a signed-in user.
165 fxa.internal.currentAccountState.signedInUser = browseridManager._fxaService.internal.currentAccountState.signedInUser;
166
167 browseridManager._fxaService = fxa;
168
169 do_check_eq(browseridManager._fxaService.internal.now(), now);
170 do_check_eq(browseridManager._fxaService.internal.localtimeOffsetMsec,
171 localtimeOffsetMsec);
172
173 do_check_eq(browseridManager._fxaService.now(), now);
174 do_check_eq(browseridManager._fxaService.localtimeOffsetMsec,
175 localtimeOffsetMsec);
176
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'));
183
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);
189
190 run_next_test();
191 });
192
193 add_test(function test_RESTResourceAuthenticatorSkew() {
194 _("BrowserIDManager REST Resource Authenticator compensates for clock skew in Hawk header.");
195
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");
200
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;
208
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;
214
215 configureFxAccountIdentity(browseridManager, identityConfig);
216
217 // Ensure the new FxAccounts mock has a signed-in user.
218 fxa.internal.currentAccountState.signedInUser = browseridManager._fxaService.internal.currentAccountState.signedInUser;
219
220 browseridManager._fxaService = fxa;
221
222 do_check_eq(browseridManager._fxaService.internal.now(), now);
223
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'));
230
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);
236
237 run_next_test();
238 });
239
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.");
248
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 });
270
271 add_test(function test_tokenExpiration() {
272 _("BrowserIDManager notices token expiration:");
273 let bimExp = new BrowserIDManager();
274 configureFxAccountIdentity(bimExp, identityConfig);
275
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');
282
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 );
297
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 });
324
325 add_test(function test_computeXClientStateHeader() {
326 let kBhex = "fd5c747806c07ce0b9d69dcfea144663e630b65ec4963596a22f24910d7dd15d";
327 let kB = CommonUtils.hexToBytes(kBhex);
328
329 let bidUser = new BrowserIDManager();
330 let header = bidUser._computeXClientState(kB);
331
332 do_check_eq(header, "6ae94683571c7a7c54dab4700aa3995f");
333 run_next_test();
334 });
335
336 add_task(function test_getTokenErrors() {
337 _("BrowserIDManager correctly handles various failures to get a token.");
338
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;
346
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");
351
352 // XXX - other interesting responses to return?
353
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 });
368
369 add_task(function test_getTokenErrorWithRetry() {
370 _("tokenserver sends an observer notification on various backoff headers.");
371
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;
383
384 yield browseridManager.initializeWithCurrentIdentity();
385 yield Assert_rejects(browseridManager.whenReadyToAuthenticate.promise,
386 "should reject due to 503");
387
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);
392
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;
402
403 yield browseridManager.initializeWithCurrentIdentity();
404 yield Assert_rejects(browseridManager.whenReadyToAuthenticate.promise,
405 "should reject due to no token in response");
406
407 // The observer should have fired - check it got the value in the response.
408 Assert.ok(Status.backoffInterval >= 200000);
409 });
410
411 add_task(function test_getKeysErrorWithBackoff() {
412 _("Auth server (via hawk) sends an observer notification on backoff headers.");
413
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.");
418
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 });
434
435 let browseridManager = Service.identity;
436 yield Assert_rejects(browseridManager.whenReadyToAuthenticate.promise,
437 "should reject due to 503");
438
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 });
444
445 add_task(function test_getKeysErrorWithRetry() {
446 _("Auth server (via hawk) sends an observer notification on retry headers.");
447
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.");
452
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 });
468
469 let browseridManager = Service.identity;
470 yield Assert_rejects(browseridManager.whenReadyToAuthenticate.promise,
471 "should reject due to 503");
472
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 });
478
479 add_task(function test_getHAWKErrors() {
480 _("BrowserIDManager correctly handles various HAWK failures.");
481
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");
494
495 // XXX - other interesting responses to return?
496
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 });
511
512 add_task(function test_getGetKeysFailing401() {
513 _("BrowserIDManager correctly handles 401 responses fetching keys.");
514
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 });
532
533 add_task(function test_getGetKeysFailing503() {
534 _("BrowserIDManager correctly handles 5XX responses fetching keys.");
535
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 });
553
554 add_task(function test_getKeysMissing() {
555 _("BrowserIDManager correctly handles getKeys succeeding but not returning keys.");
556
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';
564
565 configureFxAccountIdentity(browseridManager, identityConfig);
566
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 });
574
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 };
583
584 // Ensure the new FxAccounts mock has a signed-in user.
585 fxa.internal.currentAccountState.signedInUser = browseridManager._fxaService.internal.currentAccountState.signedInUser;
586
587 browseridManager._fxaService = fxa;
588
589 yield browseridManager.initializeWithCurrentIdentity();
590
591 let ex;
592 try {
593 yield browseridManager.whenReadyToAuthenticate.promise;
594 } catch (e) {
595 ex = e;
596 }
597
598 Assert.ok(ex.message.indexOf("missing kA or kB") >= 0);
599 });
600
601 // End of tests
602 // Utility functions follow
603
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 }
629
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";
639
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 };
650
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 }
657
658
659 function getTimestamp(hawkAuthHeader) {
660 return parseInt(/ts="(\d+)"/.exec(hawkAuthHeader)[1], 10) * SECOND_MS;
661 }
662
663 function getTimestampDelta(hawkAuthHeader, now=Date.now()) {
664 return Math.abs(getTimestamp(hawkAuthHeader) - now);
665 }
666

mercurial