mirror of
https://github.com/mozilla/gecko-dev.git
synced 2025-03-02 06:22:20 +00:00
Bug 969892 - Fixes and improvements to FxAccounts test suite; r=ttaubert
This commit is contained in:
parent
fbc6b76196
commit
ed60f7b54c
@ -342,6 +342,7 @@ add_task(function test_multiple_401_retry_once() {
|
||||
// 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);
|
||||
}
|
||||
|
@ -67,22 +67,22 @@ AccountState.prototype = {
|
||||
cert: null,
|
||||
keyPair: null,
|
||||
signedInUser: null,
|
||||
whenVerifiedPromise: null,
|
||||
whenKeysReadyPromise: null,
|
||||
whenVerifiedDeferred: null,
|
||||
whenKeysReadyDeferred: null,
|
||||
|
||||
get isCurrent() this.fxaInternal && this.fxaInternal.currentAccountState === this,
|
||||
|
||||
abort: function() {
|
||||
if (this.whenVerifiedPromise) {
|
||||
this.whenVerifiedPromise.reject(
|
||||
if (this.whenVerifiedDeferred) {
|
||||
this.whenVerifiedDeferred.reject(
|
||||
new Error("Verification aborted; Another user signing in"));
|
||||
this.whenVerifiedPromise = null;
|
||||
this.whenVerifiedDeferred = null;
|
||||
}
|
||||
|
||||
if (this.whenKeysReadyPromise) {
|
||||
this.whenKeysReadyPromise.reject(
|
||||
if (this.whenKeysReadyDeferred) {
|
||||
this.whenKeysReadyDeferred.reject(
|
||||
new Error("Verification aborted; Another user signing in"));
|
||||
this.whenKeysReadyPromise = null;
|
||||
this.whenKeysReadyDeferred = null;
|
||||
}
|
||||
this.cert = null;
|
||||
this.keyPair = null;
|
||||
@ -484,13 +484,13 @@ FxAccountsInternal.prototype = {
|
||||
if (data.kA && data.kB) {
|
||||
return data;
|
||||
}
|
||||
if (!currentState.whenKeysReadyPromise) {
|
||||
currentState.whenKeysReadyPromise = Promise.defer();
|
||||
if (!currentState.whenKeysReadyDeferred) {
|
||||
currentState.whenKeysReadyDeferred = Promise.defer();
|
||||
this.fetchAndUnwrapKeys(data.keyFetchToken).then(data => {
|
||||
currentState.whenKeysReadyPromise.resolve(data);
|
||||
currentState.whenKeysReadyDeferred.resolve(data);
|
||||
});
|
||||
}
|
||||
return currentState.whenKeysReadyPromise.promise;
|
||||
return currentState.whenKeysReadyDeferred.promise;
|
||||
}).then(result => currentState.resolve(result));
|
||||
},
|
||||
|
||||
@ -607,11 +607,11 @@ FxAccountsInternal.prototype = {
|
||||
log.debug("already verified");
|
||||
return currentState.resolve(data);
|
||||
}
|
||||
if (!currentState.whenVerifiedPromise) {
|
||||
if (!currentState.whenVerifiedDeferred) {
|
||||
log.debug("whenVerified promise starts polling for verified email");
|
||||
this.pollEmailStatus(currentState, data.sessionToken, "start");
|
||||
}
|
||||
return currentState.whenVerifiedPromise.promise.then(
|
||||
return currentState.whenVerifiedDeferred.promise.then(
|
||||
result => currentState.resolve(result)
|
||||
);
|
||||
},
|
||||
@ -629,8 +629,8 @@ FxAccountsInternal.prototype = {
|
||||
// if the user requested the verification email to be resent while we
|
||||
// were already polling for receipt of an earlier email.
|
||||
this.pollTimeRemaining = this.POLL_SESSION;
|
||||
if (!currentState.whenVerifiedPromise) {
|
||||
currentState.whenVerifiedPromise = Promise.defer();
|
||||
if (!currentState.whenVerifiedDeferred) {
|
||||
currentState.whenVerifiedDeferred = Promise.defer();
|
||||
}
|
||||
}
|
||||
|
||||
@ -647,9 +647,9 @@ FxAccountsInternal.prototype = {
|
||||
})
|
||||
.then((data) => {
|
||||
// Now that the user is verified, we can proceed to fetch keys
|
||||
if (currentState.whenVerifiedPromise) {
|
||||
currentState.whenVerifiedPromise.resolve(data);
|
||||
delete currentState.whenVerifiedPromise;
|
||||
if (currentState.whenVerifiedDeferred) {
|
||||
currentState.whenVerifiedDeferred.resolve(data);
|
||||
delete currentState.whenVerifiedDeferred;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
@ -661,11 +661,11 @@ FxAccountsInternal.prototype = {
|
||||
this.pollEmailStatus(currentState, sessionToken, "timer")}, this.POLL_STEP);
|
||||
log.debug("started timer " + this.currentTimer);
|
||||
} else {
|
||||
if (currentState.whenVerifiedPromise) {
|
||||
currentState.whenVerifiedPromise.reject(
|
||||
if (currentState.whenVerifiedDeferred) {
|
||||
currentState.whenVerifiedDeferred.reject(
|
||||
new Error("User email verification timed out.")
|
||||
);
|
||||
delete currentState.whenVerifiedPromise;
|
||||
delete currentState.whenVerifiedDeferred;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,8 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
Cu.import("resource://gre/modules/FxAccountsClient.jsm");
|
||||
Cu.import("resource://gre/modules/Promise.jsm");
|
||||
Cu.import("resource://services-common/utils.js");
|
||||
@ -12,24 +14,43 @@ function run_test() {
|
||||
run_next_test();
|
||||
}
|
||||
|
||||
// https://wiki.mozilla.org/Identity/AttachedServices/KeyServerProtocol#.2Faccount.2Fkeys
|
||||
let ACCOUNT_KEYS = {
|
||||
keyFetch: h("8081828384858687 88898a8b8c8d8e8f"+
|
||||
"9091929394959697 98999a9b9c9d9e9f"),
|
||||
|
||||
response: h("ee5c58845c7c9412 b11bbd20920c2fdd"+
|
||||
"d83c33c9cd2c2de2 d66b222613364636"+
|
||||
"c2c0f8cfbb7c6304 72c0bd88451342c6"+
|
||||
"c05b14ce342c5ad4 6ad89e84464c993c"+
|
||||
"3927d30230157d08 17a077eef4b20d97"+
|
||||
"6f7a97363faf3f06 4c003ada7d01aa70"),
|
||||
|
||||
kA: h("2021222324252627 28292a2b2c2d2e2f"+
|
||||
"3031323334353637 38393a3b3c3d3e3f"),
|
||||
|
||||
wrapKB: h("4041424344454647 48494a4b4c4d4e4f"+
|
||||
"5051525354555657 58595a5b5c5d5e5f"),
|
||||
};
|
||||
|
||||
// https://github.com/mozilla/fxa-auth-server/wiki/onepw-protocol#wiki-use-session-certificatesign-etc
|
||||
let SESSION_KEYS = {
|
||||
sessionToken: h("a0a1a2a3a4a5a6a7 a8a9aaabacadaeaf"+
|
||||
"b0b1b2b3b4b5b6b7 b8b9babbbcbdbebf"),
|
||||
|
||||
tokenID: h("c0a29dcf46174973 da1378696e4c82ae"+
|
||||
"10f723cf4f4d9f75 e39f4ae3851595ab"),
|
||||
|
||||
reqHMACkey: h("9d8f22998ee7f579 8b887042466b72d5"+
|
||||
"3e56ab0c094388bf 65831f702d2febc0"),
|
||||
};
|
||||
|
||||
function deferredStop(server) {
|
||||
let deferred = Promise.defer();
|
||||
server.stop(deferred.resolve);
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
add_test(function test_hawk_credentials() {
|
||||
let client = new FxAccountsClient();
|
||||
|
||||
let sessionToken = "a0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebf";
|
||||
let result = client._deriveHawkCredentials(sessionToken, "session");
|
||||
|
||||
do_check_eq(result.id, "639503a218ffbb62983e9628be5cd64a0438d0ae81b2b9dadeb900a83470bc6b");
|
||||
do_check_eq(CommonUtils.bytesAsHex(result.key), "3a0188943837ab228fe74e759566d0e4837cbcc7494157aac4da82025b2811b2");
|
||||
|
||||
run_next_test();
|
||||
});
|
||||
|
||||
add_task(function test_authenticated_get_request() {
|
||||
let message = "{\"msg\": \"Great Success!\"}";
|
||||
let credentials = {
|
||||
@ -94,6 +115,7 @@ add_task(function test_500_error() {
|
||||
|
||||
try {
|
||||
yield client._request("/foo", method);
|
||||
do_throw("Expected to catch an exception");
|
||||
} catch (e) {
|
||||
do_check_eq(500, e.code);
|
||||
do_check_eq("Internal Server Error", e.message);
|
||||
@ -171,12 +193,14 @@ add_task(function test_signUp() {
|
||||
created = true;
|
||||
|
||||
response.setStatusLine(request.httpVersion, 200, "OK");
|
||||
return response.bodyOutputStream.write(creationMessage, creationMessage.length);
|
||||
response.bodyOutputStream.write(creationMessage, creationMessage.length);
|
||||
return;
|
||||
}
|
||||
|
||||
// Error trying to create same account a second time
|
||||
response.setStatusLine(request.httpVersion, 400, "Bad request");
|
||||
return response.bodyOutputStream.write(errorMessage, errorMessage.length);
|
||||
response.bodyOutputStream.write(errorMessage, errorMessage.length);
|
||||
return;
|
||||
},
|
||||
});
|
||||
|
||||
@ -189,6 +213,7 @@ add_task(function test_signUp() {
|
||||
// Try to create account again. Triggers error path.
|
||||
try {
|
||||
result = yield client.signUp('andré@example.org', 'pässwörd');
|
||||
do_throw("Expected to catch an exception");
|
||||
} catch(expectedError) {
|
||||
do_check_eq(101, expectedError.errno);
|
||||
}
|
||||
@ -199,6 +224,7 @@ add_task(function test_signUp() {
|
||||
add_task(function test_signIn() {
|
||||
let sessionMessage = JSON.stringify({sessionToken: FAKE_SESSION_TOKEN});
|
||||
let errorMessage = JSON.stringify({code: 400, errno: 102, error: "doesn't exist"});
|
||||
|
||||
let server = httpd_setup({
|
||||
"/account/login": function(request, response) {
|
||||
let body = CommonUtils.readBytesFromInputStream(request.bodyInputStream);
|
||||
@ -207,12 +233,14 @@ add_task(function test_signIn() {
|
||||
if (jsonBody.email == "mé@example.com") {
|
||||
do_check_eq(jsonBody.authPW, "08b9d111196b8408e8ed92439da49206c8ecfbf343df0ae1ecefcd1e0174a8b6");
|
||||
response.setStatusLine(request.httpVersion, 200, "OK");
|
||||
return response.bodyOutputStream.write(sessionMessage, sessionMessage.length);
|
||||
response.bodyOutputStream.write(sessionMessage, sessionMessage.length);
|
||||
return;
|
||||
}
|
||||
|
||||
// Error trying to sign in to nonexistent account
|
||||
response.setStatusLine(request.httpVersion, 400, "Bad request");
|
||||
return response.bodyOutputStream.write(errorMessage, errorMessage.length);
|
||||
response.bodyOutputStream.write(errorMessage, errorMessage.length);
|
||||
return;
|
||||
},
|
||||
});
|
||||
|
||||
@ -223,6 +251,7 @@ add_task(function test_signIn() {
|
||||
// Trigger error path
|
||||
try {
|
||||
result = yield client.signIn("yøü@bad.example.org", "nofear");
|
||||
do_throw("Expected to catch an exception");
|
||||
} catch(expectedError) {
|
||||
do_check_eq(102, expectedError.errno);
|
||||
}
|
||||
@ -241,12 +270,14 @@ add_task(function test_signOut() {
|
||||
signedOut = true;
|
||||
do_check_true(request.hasHeader("Authorization"));
|
||||
response.setStatusLine(request.httpVersion, 200, "OK");
|
||||
return response.bodyOutputStream.write(signoutMessage, signoutMessage.length);
|
||||
response.bodyOutputStream.write(signoutMessage, signoutMessage.length);
|
||||
return;
|
||||
}
|
||||
|
||||
// Error trying to sign out of nonexistent account
|
||||
response.setStatusLine(request.httpVersion, 400, "Bad request");
|
||||
return response.bodyOutputStream.write(errorMessage, errorMessage.length);
|
||||
response.bodyOutputStream.write(errorMessage, errorMessage.length);
|
||||
return;
|
||||
},
|
||||
});
|
||||
|
||||
@ -257,6 +288,7 @@ add_task(function test_signOut() {
|
||||
// Trigger error path
|
||||
try {
|
||||
result = yield client.signOut("FakeSession");
|
||||
do_throw("Expected to catch an exception");
|
||||
} catch(expectedError) {
|
||||
do_check_eq(102, expectedError.errno);
|
||||
}
|
||||
@ -274,13 +306,16 @@ add_task(function test_recoveryEmailStatus() {
|
||||
do_check_true(request.hasHeader("Authorization"));
|
||||
|
||||
if (tries === 0) {
|
||||
tries += 1;
|
||||
response.setStatusLine(request.httpVersion, 200, "OK");
|
||||
return response.bodyOutputStream.write(emailStatus, emailStatus.length);
|
||||
response.bodyOutputStream.write(emailStatus, emailStatus.length);
|
||||
return;
|
||||
}
|
||||
|
||||
// Second call gets an error trying to query a nonexistent account
|
||||
response.setStatusLine(request.httpVersion, 400, "Bad request");
|
||||
return response.bodyOutputStream.write(errorMessage, errorMessage.length);
|
||||
response.bodyOutputStream.write(errorMessage, errorMessage.length);
|
||||
return;
|
||||
},
|
||||
});
|
||||
|
||||
@ -291,6 +326,7 @@ add_task(function test_recoveryEmailStatus() {
|
||||
// Trigger error path
|
||||
try {
|
||||
result = yield client.recoveryEmailStatus("some bogus session");
|
||||
do_throw("Expected to catch an exception");
|
||||
} catch(expectedError) {
|
||||
do_check_eq(102, expectedError.errno);
|
||||
}
|
||||
@ -307,13 +343,16 @@ add_task(function test_resendVerificationEmail() {
|
||||
"/recovery_email/resend_code": function(request, response) {
|
||||
do_check_true(request.hasHeader("Authorization"));
|
||||
if (tries === 0) {
|
||||
tries += 1;
|
||||
response.setStatusLine(request.httpVersion, 200, "OK");
|
||||
return response.bodyOutputStream.write(emptyMessage, emptyMessage.length);
|
||||
response.bodyOutputStream.write(emptyMessage, emptyMessage.length);
|
||||
return;
|
||||
}
|
||||
|
||||
// Second call gets an error trying to query a nonexistent account
|
||||
response.setStatusLine(request.httpVersion, 400, "Bad request");
|
||||
return response.bodyOutputStream.write(errorMessage, errorMessage.length);
|
||||
response.bodyOutputStream.write(errorMessage, errorMessage.length);
|
||||
return;
|
||||
},
|
||||
});
|
||||
|
||||
@ -324,6 +363,7 @@ add_task(function test_resendVerificationEmail() {
|
||||
// Trigger error path
|
||||
try {
|
||||
result = yield client.resendVerificationEmail("some bogus session");
|
||||
do_throw("Expected to catch an exception");
|
||||
} catch(expectedError) {
|
||||
do_check_eq(102, expectedError.errno);
|
||||
}
|
||||
@ -332,25 +372,11 @@ add_task(function test_resendVerificationEmail() {
|
||||
});
|
||||
|
||||
add_task(function test_accountKeys() {
|
||||
// Vectors: https://wiki.mozilla.org/Identity/AttachedServices/KeyServerProtocol#.2Faccount.2Fkeys
|
||||
|
||||
let keyFetch = h("8081828384858687 88898a8b8c8d8e8f"+
|
||||
"9091929394959697 98999a9b9c9d9e9f");
|
||||
|
||||
let response = h("ee5c58845c7c9412 b11bbd20920c2fdd"+
|
||||
"d83c33c9cd2c2de2 d66b222613364636"+
|
||||
"c2c0f8cfbb7c6304 72c0bd88451342c6"+
|
||||
"c05b14ce342c5ad4 6ad89e84464c993c"+
|
||||
"3927d30230157d08 17a077eef4b20d97"+
|
||||
"6f7a97363faf3f06 4c003ada7d01aa70");
|
||||
|
||||
let kA = h("2021222324252627 28292a2b2c2d2e2f"+
|
||||
"3031323334353637 38393a3b3c3d3e3f");
|
||||
|
||||
let wrapKB = h("4041424344454647 48494a4b4c4d4e4f"+
|
||||
"5051525354555657 58595a5b5c5d5e5f");
|
||||
|
||||
let responseMessage = JSON.stringify({bundle: response});
|
||||
// Four calls to accountKeys(). The first one should work correctly, and we
|
||||
// should get a valid bundle back, in exchange for our keyFetch token, from
|
||||
// which we correctly derive kA and wrapKB. The subsequent three calls
|
||||
// should all trigger separate error paths.
|
||||
let responseMessage = JSON.stringify({bundle: ACCOUNT_KEYS.response});
|
||||
let errorMessage = JSON.stringify({code: 400, errno: 102, error: "doesn't exist"});
|
||||
let emptyMessage = "{}";
|
||||
let attempt = 0;
|
||||
@ -375,10 +401,12 @@ add_task(function test_accountKeys() {
|
||||
|
||||
case 3:
|
||||
// Return gibberish to trigger client MAC error
|
||||
let garbage = response;
|
||||
garbage[0] = 0; // tweak a byte
|
||||
// Tweak a byte
|
||||
let garbageResponse = JSON.stringify({
|
||||
bundle: ACCOUNT_KEYS.response.slice(0, -1) + "1"
|
||||
});
|
||||
response.setStatusLine(request.httpVersion, 200, "OK");
|
||||
response.bodyOutputStream.write(responseMessage, responseMessage.length);
|
||||
response.bodyOutputStream.write(garbageResponse, garbageResponse.length);
|
||||
break;
|
||||
|
||||
case 4:
|
||||
@ -393,27 +421,30 @@ add_task(function test_accountKeys() {
|
||||
let client = new FxAccountsClient(server.baseURI);
|
||||
|
||||
// First try, all should be good
|
||||
let result = yield client.accountKeys(keyFetch);
|
||||
do_check_eq(CommonUtils.hexToBytes(kA), result.kA);
|
||||
do_check_eq(CommonUtils.hexToBytes(wrapKB), result.wrapKB);
|
||||
let result = yield client.accountKeys(ACCOUNT_KEYS.keyFetch);
|
||||
do_check_eq(CommonUtils.hexToBytes(ACCOUNT_KEYS.kA), result.kA);
|
||||
do_check_eq(CommonUtils.hexToBytes(ACCOUNT_KEYS.wrapKB), result.wrapKB);
|
||||
|
||||
// Second try, empty bundle should trigger error
|
||||
try {
|
||||
result = yield client.accountKeys(keyFetch);
|
||||
result = yield client.accountKeys(ACCOUNT_KEYS.keyFetch);
|
||||
do_throw("Expected to catch an exception");
|
||||
} catch(expectedError) {
|
||||
do_check_eq(expectedError.message, "failed to retrieve keys");
|
||||
}
|
||||
|
||||
// Third try, bad bundle results in MAC error
|
||||
try {
|
||||
result = yield client.accountKeys(keyFetch);
|
||||
result = yield client.accountKeys(ACCOUNT_KEYS.keyFetch);
|
||||
do_throw("Expected to catch an exception");
|
||||
} catch(expectedError) {
|
||||
do_check_eq(expectedError.message, "error unbundling encryption keys");
|
||||
}
|
||||
|
||||
// Fourth try, pretend account doesn't exist
|
||||
try {
|
||||
result = yield client.accountKeys(keyFetch);
|
||||
result = yield client.accountKeys(ACCOUNT_KEYS.keyFetch);
|
||||
do_throw("Expected to catch an exception");
|
||||
} catch(expectedError) {
|
||||
do_check_eq(102, expectedError.errno);
|
||||
}
|
||||
@ -437,12 +468,14 @@ add_task(function test_signCertificate() {
|
||||
do_check_eq(JSON.parse(jsonBody.publicKey).foo, "bar");
|
||||
do_check_eq(jsonBody.duration, 600);
|
||||
response.setStatusLine(request.httpVersion, 200, "OK");
|
||||
return response.bodyOutputStream.write(certSignMessage, certSignMessage.length);
|
||||
response.bodyOutputStream.write(certSignMessage, certSignMessage.length);
|
||||
return;
|
||||
}
|
||||
|
||||
// Second attempt, trigger error
|
||||
response.setStatusLine(request.httpVersion, 400, "Bad request");
|
||||
return response.bodyOutputStream.write(errorMessage, errorMessage.length);
|
||||
response.bodyOutputStream.write(errorMessage, errorMessage.length);
|
||||
return;
|
||||
},
|
||||
});
|
||||
|
||||
@ -453,6 +486,7 @@ add_task(function test_signCertificate() {
|
||||
// Account doesn't exist
|
||||
try {
|
||||
result = yield client.signCertificate("bogus", JSON.stringify({foo: "bar"}), 600);
|
||||
do_throw("Expected to catch an exception");
|
||||
} catch(expectedError) {
|
||||
do_check_eq(102, expectedError.errno);
|
||||
}
|
||||
@ -502,27 +536,18 @@ add_task(function test_accountExists() {
|
||||
let client = new FxAccountsClient(server.baseURI);
|
||||
let result;
|
||||
|
||||
try {
|
||||
result = yield client.accountExists("i.exist@example.com");
|
||||
} catch(expectedError) {
|
||||
do_check_eq(expectedError.code, 400);
|
||||
do_check_eq(expectedError.errno, 103);
|
||||
}
|
||||
result = yield client.accountExists("i.exist@example.com");
|
||||
do_check_true(result);
|
||||
|
||||
try {
|
||||
result = yield client.accountExists("i.also.exist@example.com");
|
||||
} catch(expectedError) {
|
||||
do_check_eq(expectedError.errno, 103);
|
||||
}
|
||||
result = yield client.accountExists("i.also.exist@example.com");
|
||||
do_check_true(result);
|
||||
|
||||
try {
|
||||
result = yield client.accountExists("i.dont.exist@example.com");
|
||||
} catch(expectedError) {
|
||||
do_check_eq(expectedError.errno, 102);
|
||||
}
|
||||
result = yield client.accountExists("i.dont.exist@example.com");
|
||||
do_check_false(result);
|
||||
|
||||
try {
|
||||
result = yield client.accountExists("i.break.things@example.com");
|
||||
do_throw("Expected to catch an exception");
|
||||
} catch(unexpectedError) {
|
||||
do_check_eq(unexpectedError.code, 500);
|
||||
}
|
||||
@ -583,6 +608,17 @@ add_task(function test_email_case() {
|
||||
yield deferredStop(server);
|
||||
});
|
||||
|
||||
add_task(function test__deriveHawkCredentials() {
|
||||
let client = new FxAccountsClient("https://example.org");
|
||||
|
||||
let credentials = client._deriveHawkCredentials(
|
||||
SESSION_KEYS.sessionToken, "sessionToken");
|
||||
|
||||
do_check_eq(credentials.algorithm, "sha256");
|
||||
do_check_eq(credentials.id, SESSION_KEYS.tokenID);
|
||||
do_check_eq(CommonUtils.bytesAsHex(credentials.key), SESSION_KEYS.reqHMACkey);
|
||||
});
|
||||
|
||||
// turn formatted test vectors into normal hex strings
|
||||
function h(hexStr) {
|
||||
return hexStr.replace(/\s+/g, "");
|
||||
|
Loading…
x
Reference in New Issue
Block a user