diff options
Diffstat (limited to 'services/fxaccounts/tests/xpcshell/test_accounts.js')
-rw-r--r-- | services/fxaccounts/tests/xpcshell/test_accounts.js | 1531 |
1 files changed, 0 insertions, 1531 deletions
diff --git a/services/fxaccounts/tests/xpcshell/test_accounts.js b/services/fxaccounts/tests/xpcshell/test_accounts.js deleted file mode 100644 index d6139a076..000000000 --- a/services/fxaccounts/tests/xpcshell/test_accounts.js +++ /dev/null @@ -1,1531 +0,0 @@ -/* Any copyright is dedicated to the Public Domain. - * http://creativecommons.org/publicdomain/zero/1.0/ */ - -"use strict"; - -Cu.import("resource://services-common/utils.js"); -Cu.import("resource://gre/modules/Services.jsm"); -Cu.import("resource://gre/modules/FxAccounts.jsm"); -Cu.import("resource://gre/modules/FxAccountsClient.jsm"); -Cu.import("resource://gre/modules/FxAccountsCommon.js"); -Cu.import("resource://gre/modules/FxAccountsOAuthGrantClient.jsm"); -Cu.import("resource://gre/modules/Promise.jsm"); -Cu.import("resource://gre/modules/Log.jsm"); - -// We grab some additional stuff via backstage passes. -var {AccountState} = Cu.import("resource://gre/modules/FxAccounts.jsm", {}); - -const ONE_HOUR_MS = 1000 * 60 * 60; -const ONE_DAY_MS = ONE_HOUR_MS * 24; -const TWO_MINUTES_MS = 1000 * 60 * 2; - -initTestLogging("Trace"); - -// XXX until bug 937114 is fixed -Cu.importGlobalProperties(['atob']); - -var log = Log.repository.getLogger("Services.FxAccounts.test"); -log.level = Log.Level.Debug; - -// See verbose logging from FxAccounts.jsm -Services.prefs.setCharPref("identity.fxaccounts.loglevel", "Trace"); -Log.repository.getLogger("FirefoxAccounts").level = Log.Level.Trace; - -// The oauth server is mocked, but set these prefs to pass param checks -Services.prefs.setCharPref("identity.fxaccounts.remote.oauth.uri", "https://example.com/v1"); -Services.prefs.setCharPref("identity.fxaccounts.oauth.client_id", "abc123"); - - -const PROFILE_SERVER_URL = "http://example.com/v1"; -const CONTENT_URL = "http://accounts.example.com/"; - -Services.prefs.setCharPref("identity.fxaccounts.remote.profile.uri", PROFILE_SERVER_URL); -Services.prefs.setCharPref("identity.fxaccounts.settings.uri", CONTENT_URL); - -/* - * The FxAccountsClient communicates with the remote Firefox - * Accounts auth server. Mock the server calls, with a little - * lag time to simulate some latency. - * - * We add the _verified attribute to mock the change in verification - * state on the FXA server. - */ - -function MockStorageManager() { -} - -MockStorageManager.prototype = { - promiseInitialized: Promise.resolve(), - - initialize(accountData) { - this.accountData = accountData; - }, - - finalize() { - return Promise.resolve(); - }, - - getAccountData() { - return Promise.resolve(this.accountData); - }, - - updateAccountData(updatedFields) { - for (let [name, value] of Object.entries(updatedFields)) { - if (value == null) { - delete this.accountData[name]; - } else { - this.accountData[name] = value; - } - } - return Promise.resolve(); - }, - - deleteAccountData() { - this.accountData = null; - return Promise.resolve(); - } -} - -function MockFxAccountsClient() { - this._email = "nobody@example.com"; - this._verified = false; - this._deletedOnServer = false; // for testing accountStatus - - // mock calls up to the auth server to determine whether the - // user account has been verified - this.recoveryEmailStatus = function (sessionToken) { - // simulate a call to /recovery_email/status - return Promise.resolve({ - email: this._email, - verified: this._verified - }); - }; - - this.accountStatus = function(uid) { - let deferred = Promise.defer(); - deferred.resolve(!!uid && (!this._deletedOnServer)); - return deferred.promise; - }; - - this.accountKeys = function (keyFetchToken) { - let deferred = Promise.defer(); - - do_timeout(50, () => { - let response = { - kA: expandBytes("11"), - wrapKB: expandBytes("22") - }; - deferred.resolve(response); - }); - return deferred.promise; - }; - - this.resendVerificationEmail = function(sessionToken) { - // Return the session token to show that we received it in the first place - return Promise.resolve(sessionToken); - }; - - this.signCertificate = function() { throw "no" }; - - this.signOut = () => Promise.resolve(); - this.signOutAndDestroyDevice = () => Promise.resolve({}); - - FxAccountsClient.apply(this); -} -MockFxAccountsClient.prototype = { - __proto__: FxAccountsClient.prototype -} - -/* - * We need to mock the FxAccounts module's interfaces to external - * services, such as storage and the FxAccounts client. We also - * mock the now() method, so that we can simulate the passing of - * time and verify that signatures expire correctly. - */ -function MockFxAccounts() { - return new FxAccounts({ - VERIFICATION_POLL_TIMEOUT_INITIAL: 100, // 100ms - - _getCertificateSigned_calls: [], - _d_signCertificate: Promise.defer(), - _now_is: new Date(), - now: function () { - return this._now_is; - }, - newAccountState(credentials) { - // we use a real accountState but mocked storage. - let storage = new MockStorageManager(); - storage.initialize(credentials); - return new AccountState(storage); - }, - getCertificateSigned: function (sessionToken, serializedPublicKey) { - _("mock getCertificateSigned\n"); - this._getCertificateSigned_calls.push([sessionToken, serializedPublicKey]); - return this._d_signCertificate.promise; - }, - _registerOrUpdateDevice() { - return Promise.resolve(); - }, - fxAccountsClient: new MockFxAccountsClient() - }); -} - -/* - * Some tests want a "real" fxa instance - however, we still mock the storage - * to keep the tests fast on b2g. - */ -function MakeFxAccounts(internal = {}) { - if (!internal.newAccountState) { - // we use a real accountState but mocked storage. - internal.newAccountState = function(credentials) { - let storage = new MockStorageManager(); - storage.initialize(credentials); - return new AccountState(storage); - }; - } - if (!internal._signOutServer) { - internal._signOutServer = () => Promise.resolve(); - } - if (!internal._registerOrUpdateDevice) { - internal._registerOrUpdateDevice = () => Promise.resolve(); - } - return new FxAccounts(internal); -} - -add_task(function* test_non_https_remote_server_uri_with_requireHttps_false() { - Services.prefs.setBoolPref( - "identity.fxaccounts.allowHttp", - true); - Services.prefs.setCharPref( - "identity.fxaccounts.remote.signup.uri", - "http://example.com/browser/browser/base/content/test/general/accounts_testRemoteCommands.html"); - do_check_eq(yield fxAccounts.promiseAccountsSignUpURI(), - "http://example.com/browser/browser/base/content/test/general/accounts_testRemoteCommands.html"); - - Services.prefs.clearUserPref("identity.fxaccounts.remote.signup.uri"); - Services.prefs.clearUserPref("identity.fxaccounts.allowHttp"); -}); - -add_task(function* test_non_https_remote_server_uri() { - Services.prefs.setCharPref( - "identity.fxaccounts.remote.signup.uri", - "http://example.com/browser/browser/base/content/test/general/accounts_testRemoteCommands.html"); - rejects(fxAccounts.promiseAccountsSignUpURI(), null, "Firefox Accounts server must use HTTPS"); - Services.prefs.clearUserPref("identity.fxaccounts.remote.signup.uri"); -}); - -add_task(function* test_get_signed_in_user_initially_unset() { - _("Check getSignedInUser initially and after signout reports no user"); - let account = MakeFxAccounts(); - let credentials = { - email: "foo@example.com", - uid: "1234@lcip.org", - assertion: "foobar", - sessionToken: "dead", - kA: "beef", - kB: "cafe", - verified: true - }; - let result = yield account.getSignedInUser(); - do_check_eq(result, null); - - yield account.setSignedInUser(credentials); - let histogram = Services.telemetry.getHistogramById("FXA_CONFIGURED"); - do_check_eq(histogram.snapshot().sum, 1); - histogram.clear(); - - result = yield account.getSignedInUser(); - do_check_eq(result.email, credentials.email); - do_check_eq(result.assertion, credentials.assertion); - do_check_eq(result.kB, credentials.kB); - - // Delete the memory cache and force the user - // to be read and parsed from storage (e.g. disk via JSONStorage). - delete account.internal.signedInUser; - result = yield account.getSignedInUser(); - do_check_eq(result.email, credentials.email); - do_check_eq(result.assertion, credentials.assertion); - do_check_eq(result.kB, credentials.kB); - - // sign out - let localOnly = true; - yield account.signOut(localOnly); - - // user should be undefined after sign out - result = yield account.getSignedInUser(); - do_check_eq(result, null); -}); - -add_task(function* test_update_account_data() { - _("Check updateUserAccountData does the right thing."); - let account = MakeFxAccounts(); - let credentials = { - email: "foo@example.com", - uid: "1234@lcip.org", - assertion: "foobar", - sessionToken: "dead", - kA: "beef", - kB: "cafe", - verified: true - }; - yield account.setSignedInUser(credentials); - - let newCreds = { - email: credentials.email, - uid: credentials.uid, - assertion: "new_assertion", - } - yield account.updateUserAccountData(newCreds); - do_check_eq((yield account.getSignedInUser()).assertion, "new_assertion", - "new field value was saved"); - - // but we should fail attempting to change email or uid. - newCreds = { - email: "someoneelse@example.com", - uid: credentials.uid, - assertion: "new_assertion", - } - yield Assert.rejects(account.updateUserAccountData(newCreds)); - newCreds = { - email: credentials.email, - uid: "another_uid", - assertion: "new_assertion", - } - yield Assert.rejects(account.updateUserAccountData(newCreds)); - - // should fail without email or uid. - newCreds = { - assertion: "new_assertion", - } - yield Assert.rejects(account.updateUserAccountData(newCreds)); - - // and should fail with a field name that's not known by storage. - newCreds = { - email: credentials.email, - uid: "another_uid", - foo: "bar", - } - yield Assert.rejects(account.updateUserAccountData(newCreds)); -}); - -add_task(function* test_getCertificateOffline() { - _("getCertificateOffline()"); - let fxa = MakeFxAccounts(); - let credentials = { - email: "foo@example.com", - uid: "1234@lcip.org", - sessionToken: "dead", - verified: true, - }; - - yield fxa.setSignedInUser(credentials); - - // Test that an expired cert throws if we're offline. - let offline = Services.io.offline; - Services.io.offline = true; - yield fxa.internal.getKeypairAndCertificate(fxa.internal.currentAccountState).then( - result => { - Services.io.offline = offline; - do_throw("Unexpected success"); - }, - err => { - Services.io.offline = offline; - // ... so we have to check the error string. - do_check_eq(err, "Error: OFFLINE"); - } - ); - yield fxa.signOut(/*localOnly = */true); -}); - -add_task(function* test_getCertificateCached() { - _("getCertificateCached()"); - let fxa = MakeFxAccounts(); - let credentials = { - email: "foo@example.com", - uid: "1234@lcip.org", - sessionToken: "dead", - verified: true, - // A cached keypair and cert that remain valid. - keyPair: { - validUntil: Date.now() + KEY_LIFETIME + 10000, - rawKeyPair: "good-keypair", - }, - cert: { - validUntil: Date.now() + CERT_LIFETIME + 10000, - rawCert: "good-cert", - }, - }; - - yield fxa.setSignedInUser(credentials); - let {keyPair, certificate} = yield fxa.internal.getKeypairAndCertificate(fxa.internal.currentAccountState); - // should have the same keypair and cert. - do_check_eq(keyPair, credentials.keyPair.rawKeyPair); - do_check_eq(certificate, credentials.cert.rawCert); - yield fxa.signOut(/*localOnly = */true); -}); - -add_task(function* test_getCertificateExpiredCert() { - _("getCertificateExpiredCert()"); - let fxa = MakeFxAccounts({ - getCertificateSigned() { - return "new cert"; - } - }); - let credentials = { - email: "foo@example.com", - uid: "1234@lcip.org", - sessionToken: "dead", - verified: true, - // A cached keypair that remains valid. - keyPair: { - validUntil: Date.now() + KEY_LIFETIME + 10000, - rawKeyPair: "good-keypair", - }, - // A cached certificate which has expired. - cert: { - validUntil: Date.parse("Mon, 13 Jan 2000 21:45:06 GMT"), - rawCert: "expired-cert", - }, - }; - yield fxa.setSignedInUser(credentials); - let {keyPair, certificate} = yield fxa.internal.getKeypairAndCertificate(fxa.internal.currentAccountState); - // should have the same keypair but a new cert. - do_check_eq(keyPair, credentials.keyPair.rawKeyPair); - do_check_neq(certificate, credentials.cert.rawCert); - yield fxa.signOut(/*localOnly = */true); -}); - -add_task(function* test_getCertificateExpiredKeypair() { - _("getCertificateExpiredKeypair()"); - let fxa = MakeFxAccounts({ - getCertificateSigned() { - return "new cert"; - }, - }); - let credentials = { - email: "foo@example.com", - uid: "1234@lcip.org", - sessionToken: "dead", - verified: true, - // A cached keypair that has expired. - keyPair: { - validUntil: Date.now() - 1000, - rawKeyPair: "expired-keypair", - }, - // A cached certificate which remains valid. - cert: { - validUntil: Date.now() + CERT_LIFETIME + 10000, - rawCert: "expired-cert", - }, - }; - - yield fxa.setSignedInUser(credentials); - let {keyPair, certificate} = yield fxa.internal.getKeypairAndCertificate(fxa.internal.currentAccountState); - // even though the cert was valid, the fact the keypair was not means we - // should have fetched both. - do_check_neq(keyPair, credentials.keyPair.rawKeyPair); - do_check_neq(certificate, credentials.cert.rawCert); - yield fxa.signOut(/*localOnly = */true); -}); - -// Sanity-check that our mocked client is working correctly -add_test(function test_client_mock() { - let fxa = new MockFxAccounts(); - let client = fxa.internal.fxAccountsClient; - do_check_eq(client._verified, false); - do_check_eq(typeof client.signIn, "function"); - - // The recoveryEmailStatus function eventually fulfills its promise - client.recoveryEmailStatus() - .then(response => { - do_check_eq(response.verified, false); - run_next_test(); - }); -}); - -// Sign in a user, and after a little while, verify the user's email. -// Right after signing in the user, we should get the 'onlogin' notification. -// Polling should detect that the email is verified, and eventually -// 'onverified' should be observed -add_test(function test_verification_poll() { - let fxa = new MockFxAccounts(); - let test_user = getTestUser("francine"); - let login_notification_received = false; - - makeObserver(ONVERIFIED_NOTIFICATION, function() { - log.debug("test_verification_poll observed onverified"); - // Once email verification is complete, we will observe onverified - fxa.internal.getUserAccountData().then(user => { - // And confirm that the user's state has changed - do_check_eq(user.verified, true); - do_check_eq(user.email, test_user.email); - do_check_true(login_notification_received); - run_next_test(); - }); - }); - - makeObserver(ONLOGIN_NOTIFICATION, function() { - log.debug("test_verification_poll observer onlogin"); - login_notification_received = true; - }); - - fxa.setSignedInUser(test_user).then(() => { - fxa.internal.getUserAccountData().then(user => { - // The user is signing in, but email has not been verified yet - do_check_eq(user.verified, false); - do_timeout(200, function() { - log.debug("Mocking verification of francine's email"); - fxa.internal.fxAccountsClient._email = test_user.email; - fxa.internal.fxAccountsClient._verified = true; - }); - }); - }); -}); - -// Sign in the user, but never verify the email. The check-email -// poll should time out. No verifiedlogin event should be observed, and the -// internal whenVerified promise should be rejected -add_test(function test_polling_timeout() { - // This test could be better - the onverified observer might fire on - // somebody else's stack, and we're not making sure that we're not receiving - // such a message. In other words, this tests either failure, or success, but - // not both. - - let fxa = new MockFxAccounts(); - let test_user = getTestUser("carol"); - - let removeObserver = makeObserver(ONVERIFIED_NOTIFICATION, function() { - do_throw("We should not be getting a login event!"); - }); - - fxa.internal.POLL_SESSION = 1; - - let p = fxa.internal.whenVerified({}); - - fxa.setSignedInUser(test_user).then(() => { - p.then( - (success) => { - do_throw("this should not succeed"); - }, - (fail) => { - removeObserver(); - fxa.signOut().then(run_next_test); - } - ); - }); -}); - -add_test(function test_getKeys() { - let fxa = new MockFxAccounts(); - let user = getTestUser("eusebius"); - - // Once email has been verified, we will be able to get keys - user.verified = true; - - fxa.setSignedInUser(user).then(() => { - fxa.getSignedInUser().then((user) => { - // Before getKeys, we have no keys - do_check_eq(!!user.kA, false); - do_check_eq(!!user.kB, false); - // And we still have a key-fetch token and unwrapBKey to use - do_check_eq(!!user.keyFetchToken, true); - do_check_eq(!!user.unwrapBKey, true); - - fxa.internal.getKeys().then(() => { - fxa.getSignedInUser().then((user) => { - // Now we should have keys - do_check_eq(fxa.internal.isUserEmailVerified(user), true); - do_check_eq(!!user.verified, true); - do_check_eq(user.kA, expandHex("11")); - do_check_eq(user.kB, expandHex("66")); - do_check_eq(user.keyFetchToken, undefined); - do_check_eq(user.unwrapBKey, undefined); - run_next_test(); - }); - }); - }); - }); -}); - -add_task(function* test_getKeys_nonexistent_account() { - let fxa = new MockFxAccounts(); - let bismarck = getTestUser("bismarck"); - - let client = fxa.internal.fxAccountsClient; - client.accountStatus = () => Promise.resolve(false); - client.accountKeys = () => { - return Promise.reject({ - code: 401, - errno: ERRNO_INVALID_AUTH_TOKEN, - }); - }; - - yield fxa.setSignedInUser(bismarck); - - let promiseLogout = new Promise(resolve => { - makeObserver(ONLOGOUT_NOTIFICATION, function() { - log.debug("test_getKeys_nonexistent_account observed logout"); - resolve(); - }); - }); - - try { - yield fxa.internal.getKeys(); - do_check_true(false); - } catch (err) { - do_check_eq(err.code, 401); - do_check_eq(err.errno, ERRNO_INVALID_AUTH_TOKEN); - } - - yield promiseLogout; - - let user = yield fxa.internal.getUserAccountData(); - do_check_eq(user, null); -}); - -// getKeys with invalid keyFetchToken should delete keyFetchToken from storage -add_task(function* test_getKeys_invalid_token() { - let fxa = new MockFxAccounts(); - let yusuf = getTestUser("yusuf"); - - let client = fxa.internal.fxAccountsClient; - client.accountStatus = () => Promise.resolve(true); - client.accountKeys = () => { - return Promise.reject({ - code: 401, - errno: ERRNO_INVALID_AUTH_TOKEN, - }); - }; - - yield fxa.setSignedInUser(yusuf); - - try { - yield fxa.internal.getKeys(); - do_check_true(false); - } catch (err) { - do_check_eq(err.code, 401); - do_check_eq(err.errno, ERRNO_INVALID_AUTH_TOKEN); - } - - let user = yield fxa.internal.getUserAccountData(); - do_check_eq(user.email, yusuf.email); - do_check_eq(user.keyFetchToken, null); -}); - -// fetchAndUnwrapKeys with no keyFetchToken should trigger signOut -add_test(function test_fetchAndUnwrapKeys_no_token() { - let fxa = new MockFxAccounts(); - let user = getTestUser("lettuce.protheroe"); - delete user.keyFetchToken - - makeObserver(ONLOGOUT_NOTIFICATION, function() { - log.debug("test_fetchAndUnwrapKeys_no_token observed logout"); - fxa.internal.getUserAccountData().then(user => { - run_next_test(); - }); - }); - - fxa.setSignedInUser(user).then( - user => { - return fxa.internal.fetchAndUnwrapKeys(); - } - ).then( - null, - error => { - log.info("setSignedInUser correctly rejected"); - } - ) -}); - -// Alice (User A) signs up but never verifies her email. Then Bob (User B) -// signs in with a verified email. Ensure that no sign-in events are triggered -// on Alice's behalf. In the end, Bob should be the signed-in user. -add_test(function test_overlapping_signins() { - let fxa = new MockFxAccounts(); - let alice = getTestUser("alice"); - let bob = getTestUser("bob"); - - makeObserver(ONVERIFIED_NOTIFICATION, function() { - log.debug("test_overlapping_signins observed onverified"); - // Once email verification is complete, we will observe onverified - fxa.internal.getUserAccountData().then(user => { - do_check_eq(user.email, bob.email); - do_check_eq(user.verified, true); - run_next_test(); - }); - }); - - // Alice is the user signing in; her email is unverified. - fxa.setSignedInUser(alice).then(() => { - log.debug("Alice signing in ..."); - fxa.internal.getUserAccountData().then(user => { - do_check_eq(user.email, alice.email); - do_check_eq(user.verified, false); - log.debug("Alice has not verified her email ..."); - - // Now Bob signs in instead and actually verifies his email - log.debug("Bob signing in ..."); - fxa.setSignedInUser(bob).then(() => { - do_timeout(200, function() { - // Mock email verification ... - log.debug("Bob verifying his email ..."); - fxa.internal.fxAccountsClient._verified = true; - }); - }); - }); - }); -}); - -add_task(function* test_getAssertion_invalid_token() { - let fxa = new MockFxAccounts(); - - let client = fxa.internal.fxAccountsClient; - client.accountStatus = () => Promise.resolve(true); - - let creds = { - sessionToken: "sessionToken", - kA: expandHex("11"), - kB: expandHex("66"), - verified: true, - email: "sonia@example.com", - }; - yield fxa.setSignedInUser(creds); - - try { - let promiseAssertion = fxa.getAssertion("audience.example.com"); - fxa.internal._d_signCertificate.reject({ - code: 401, - errno: ERRNO_INVALID_AUTH_TOKEN, - }); - yield promiseAssertion; - do_check_true(false, "getAssertion should reject invalid session token"); - } catch (err) { - do_check_eq(err.code, 401); - do_check_eq(err.errno, ERRNO_INVALID_AUTH_TOKEN); - } - - let user = yield fxa.internal.getUserAccountData(); - do_check_eq(user.email, creds.email); - do_check_eq(user.sessionToken, null); -}); - -add_task(function* test_getAssertion() { - let fxa = new MockFxAccounts(); - - do_check_throws(function* () { - yield fxa.getAssertion("nonaudience"); - }); - - let creds = { - sessionToken: "sessionToken", - kA: expandHex("11"), - kB: expandHex("66"), - verified: true - }; - // By putting kA/kB/verified in "creds", we skip ahead - // to the "we're ready" stage. - yield fxa.setSignedInUser(creds); - - _("== ready to go\n"); - // Start with a nice arbitrary but realistic date. Here we use a nice RFC - // 1123 date string like we would get from an HTTP header. Over the course of - // the test, we will update 'now', but leave 'start' where it is. - let now = Date.parse("Mon, 13 Jan 2014 21:45:06 GMT"); - let start = now; - fxa.internal._now_is = now; - - let d = fxa.getAssertion("audience.example.com"); - // At this point, a thread has been spawned to generate the keys. - _("-- back from fxa.getAssertion\n"); - fxa.internal._d_signCertificate.resolve("cert1"); - let assertion = yield d; - do_check_eq(fxa.internal._getCertificateSigned_calls.length, 1); - do_check_eq(fxa.internal._getCertificateSigned_calls[0][0], "sessionToken"); - do_check_neq(assertion, null); - _("ASSERTION: " + assertion + "\n"); - let pieces = assertion.split("~"); - do_check_eq(pieces[0], "cert1"); - let userData = yield fxa.getSignedInUser(); - let keyPair = userData.keyPair; - let cert = userData.cert; - do_check_neq(keyPair, undefined); - _(keyPair.validUntil + "\n"); - let p2 = pieces[1].split("."); - let header = JSON.parse(atob(p2[0])); - _("HEADER: " + JSON.stringify(header) + "\n"); - do_check_eq(header.alg, "DS128"); - let payload = JSON.parse(atob(p2[1])); - _("PAYLOAD: " + JSON.stringify(payload) + "\n"); - do_check_eq(payload.aud, "audience.example.com"); - do_check_eq(keyPair.validUntil, start + KEY_LIFETIME); - do_check_eq(cert.validUntil, start + CERT_LIFETIME); - _("delta: " + Date.parse(payload.exp - start) + "\n"); - let exp = Number(payload.exp); - - do_check_eq(exp, now + ASSERTION_LIFETIME); - - // Reset for next call. - fxa.internal._d_signCertificate = Promise.defer(); - - // Getting a new assertion "soon" (i.e., w/o incrementing "now"), even for - // a new audience, should not provoke key generation or a signing request. - assertion = yield fxa.getAssertion("other.example.com"); - - // There were no additional calls - same number of getcert calls as before - do_check_eq(fxa.internal._getCertificateSigned_calls.length, 1); - - // Wait an hour; assertion use period expires, but not the certificate - now += ONE_HOUR_MS; - fxa.internal._now_is = now; - - // This won't block on anything - will make an assertion, but not get a - // new certificate. - assertion = yield fxa.getAssertion("third.example.com"); - - // Test will time out if that failed (i.e., if that had to go get a new cert) - pieces = assertion.split("~"); - do_check_eq(pieces[0], "cert1"); - p2 = pieces[1].split("."); - header = JSON.parse(atob(p2[0])); - payload = JSON.parse(atob(p2[1])); - do_check_eq(payload.aud, "third.example.com"); - - // The keypair and cert should have the same validity as before, but the - // expiration time of the assertion should be different. We compare this to - // the initial start time, to which they are relative, not the current value - // of "now". - userData = yield fxa.getSignedInUser(); - - keyPair = userData.keyPair; - cert = userData.cert; - do_check_eq(keyPair.validUntil, start + KEY_LIFETIME); - do_check_eq(cert.validUntil, start + CERT_LIFETIME); - exp = Number(payload.exp); - do_check_eq(exp, now + ASSERTION_LIFETIME); - - // Now we wait even longer, and expect both assertion and cert to expire. So - // we will have to get a new keypair and cert. - now += ONE_DAY_MS; - fxa.internal._now_is = now; - d = fxa.getAssertion("fourth.example.com"); - fxa.internal._d_signCertificate.resolve("cert2"); - assertion = yield d; - do_check_eq(fxa.internal._getCertificateSigned_calls.length, 2); - do_check_eq(fxa.internal._getCertificateSigned_calls[1][0], "sessionToken"); - pieces = assertion.split("~"); - do_check_eq(pieces[0], "cert2"); - p2 = pieces[1].split("."); - header = JSON.parse(atob(p2[0])); - payload = JSON.parse(atob(p2[1])); - do_check_eq(payload.aud, "fourth.example.com"); - userData = yield fxa.getSignedInUser(); - keyPair = userData.keyPair; - cert = userData.cert; - do_check_eq(keyPair.validUntil, now + KEY_LIFETIME); - do_check_eq(cert.validUntil, now + CERT_LIFETIME); - exp = Number(payload.exp); - - do_check_eq(exp, now + ASSERTION_LIFETIME); - _("----- DONE ----\n"); -}); - -add_task(function* test_resend_email_not_signed_in() { - let fxa = new MockFxAccounts(); - - try { - yield fxa.resendVerificationEmail(); - } catch(err) { - do_check_eq(err.message, - "Cannot resend verification email; no signed-in user"); - return; - } - do_throw("Should not be able to resend email when nobody is signed in"); -}); - -add_test(function test_accountStatus() { - let fxa = new MockFxAccounts(); - let alice = getTestUser("alice"); - - // If we have no user, we have no account server-side - fxa.accountStatus().then( - (result) => { - do_check_false(result); - } - ).then( - () => { - fxa.setSignedInUser(alice).then( - () => { - fxa.accountStatus().then( - (result) => { - // FxAccounts.accountStatus() should match Client.accountStatus() - do_check_true(result); - fxa.internal.fxAccountsClient._deletedOnServer = true; - fxa.accountStatus().then( - (result) => { - do_check_false(result); - fxa.internal.fxAccountsClient._deletedOnServer = false; - fxa.signOut().then(run_next_test); - } - ); - } - ) - } - ); - } - ); -}); - -add_task(function* test_resend_email_invalid_token() { - let fxa = new MockFxAccounts(); - let sophia = getTestUser("sophia"); - do_check_neq(sophia.sessionToken, null); - - let client = fxa.internal.fxAccountsClient; - client.resendVerificationEmail = () => { - return Promise.reject({ - code: 401, - errno: ERRNO_INVALID_AUTH_TOKEN, - }); - }; - client.accountStatus = () => Promise.resolve(true); - - yield fxa.setSignedInUser(sophia); - let user = yield fxa.internal.getUserAccountData(); - do_check_eq(user.email, sophia.email); - do_check_eq(user.verified, false); - log.debug("Sophia wants verification email resent"); - - try { - yield fxa.resendVerificationEmail(); - do_check_true(false, "resendVerificationEmail should reject invalid session token"); - } catch (err) { - do_check_eq(err.code, 401); - do_check_eq(err.errno, ERRNO_INVALID_AUTH_TOKEN); - } - - user = yield fxa.internal.getUserAccountData(); - do_check_eq(user.email, sophia.email); - do_check_eq(user.sessionToken, null); -}); - -add_test(function test_resend_email() { - let fxa = new MockFxAccounts(); - let alice = getTestUser("alice"); - - let initialState = fxa.internal.currentAccountState; - - // Alice is the user signing in; her email is unverified. - fxa.setSignedInUser(alice).then(() => { - log.debug("Alice signing in"); - - // We're polling for the first email - do_check_true(fxa.internal.currentAccountState !== initialState); - let aliceState = fxa.internal.currentAccountState; - - // The polling timer is ticking - do_check_true(fxa.internal.currentTimer > 0); - - fxa.internal.getUserAccountData().then(user => { - do_check_eq(user.email, alice.email); - do_check_eq(user.verified, false); - log.debug("Alice wants verification email resent"); - - fxa.resendVerificationEmail().then((result) => { - // Mock server response; ensures that the session token actually was - // passed to the client to make the hawk call - do_check_eq(result, "alice's session token"); - - // Timer was not restarted - do_check_true(fxa.internal.currentAccountState === aliceState); - - // Timer is still ticking - do_check_true(fxa.internal.currentTimer > 0); - - // Ok abort polling before we go on to the next test - fxa.internal.abortExistingFlow(); - run_next_test(); - }); - }); - }); -}); - -add_task(function* test_sign_out_with_device() { - const fxa = new MockFxAccounts(); - - const credentials = getTestUser("alice"); - yield fxa.internal.setSignedInUser(credentials); - - const user = yield fxa.internal.getUserAccountData(); - do_check_true(user); - Object.keys(credentials).forEach(key => do_check_eq(credentials[key], user[key])); - - const spy = { - signOut: { count: 0 }, - signOutAndDeviceDestroy: { count: 0, args: [] } - }; - const client = fxa.internal.fxAccountsClient; - client.signOut = function () { - spy.signOut.count += 1; - return Promise.resolve(); - }; - client.signOutAndDestroyDevice = function () { - spy.signOutAndDeviceDestroy.count += 1; - spy.signOutAndDeviceDestroy.args.push(arguments); - return Promise.resolve(); - }; - - const promise = new Promise(resolve => { - makeObserver(ONLOGOUT_NOTIFICATION, () => { - log.debug("test_sign_out_with_device observed onlogout"); - // user should be undefined after sign out - fxa.internal.getUserAccountData().then(user2 => { - do_check_eq(user2, null); - do_check_eq(spy.signOut.count, 0); - do_check_eq(spy.signOutAndDeviceDestroy.count, 1); - do_check_eq(spy.signOutAndDeviceDestroy.args[0].length, 3); - do_check_eq(spy.signOutAndDeviceDestroy.args[0][0], credentials.sessionToken); - do_check_eq(spy.signOutAndDeviceDestroy.args[0][1], credentials.deviceId); - do_check_true(spy.signOutAndDeviceDestroy.args[0][2]); - do_check_eq(spy.signOutAndDeviceDestroy.args[0][2].service, "sync"); - resolve(); - }); - }); - }); - - yield fxa.signOut(); - - yield promise; -}); - -add_task(function* test_sign_out_without_device() { - const fxa = new MockFxAccounts(); - - const credentials = getTestUser("alice"); - delete credentials.deviceId; - yield fxa.internal.setSignedInUser(credentials); - - const user = yield fxa.internal.getUserAccountData(); - - const spy = { - signOut: { count: 0, args: [] }, - signOutAndDeviceDestroy: { count: 0 } - }; - const client = fxa.internal.fxAccountsClient; - client.signOut = function () { - spy.signOut.count += 1; - spy.signOut.args.push(arguments); - return Promise.resolve(); - }; - client.signOutAndDestroyDevice = function () { - spy.signOutAndDeviceDestroy.count += 1; - return Promise.resolve(); - }; - - const promise = new Promise(resolve => { - makeObserver(ONLOGOUT_NOTIFICATION, () => { - log.debug("test_sign_out_without_device observed onlogout"); - // user should be undefined after sign out - fxa.internal.getUserAccountData().then(user2 => { - do_check_eq(user2, null); - do_check_eq(spy.signOut.count, 1); - do_check_eq(spy.signOut.args[0].length, 2); - do_check_eq(spy.signOut.args[0][0], credentials.sessionToken); - do_check_true(spy.signOut.args[0][1]); - do_check_eq(spy.signOut.args[0][1].service, "sync"); - do_check_eq(spy.signOutAndDeviceDestroy.count, 0); - resolve(); - }); - }); - }); - - yield fxa.signOut(); - - yield promise; -}); - -add_task(function* test_sign_out_with_remote_error() { - let fxa = new MockFxAccounts(); - let client = fxa.internal.fxAccountsClient; - let remoteSignOutCalled = false; - // Force remote sign out to trigger an error - client.signOutAndDestroyDevice = function() { remoteSignOutCalled = true; throw "Remote sign out error"; }; - let promiseLogout = new Promise(resolve => { - makeObserver(ONLOGOUT_NOTIFICATION, function() { - log.debug("test_sign_out_with_remote_error observed onlogout"); - resolve(); - }); - }); - - let jane = getTestUser("jane"); - yield fxa.setSignedInUser(jane); - yield fxa.signOut(); - yield promiseLogout; - - let user = yield fxa.internal.getUserAccountData(); - do_check_eq(user, null); - do_check_true(remoteSignOutCalled); -}); - -add_test(function test_getOAuthToken() { - let fxa = new MockFxAccounts(); - let alice = getTestUser("alice"); - alice.verified = true; - let getTokenFromAssertionCalled = false; - - fxa.internal._d_signCertificate.resolve("cert1"); - - // create a mock oauth client - let client = new FxAccountsOAuthGrantClient({ - serverURL: "http://example.com/v1", - client_id: "abc123" - }); - client.getTokenFromAssertion = function () { - getTokenFromAssertionCalled = true; - return Promise.resolve({ access_token: "token" }); - }; - - fxa.setSignedInUser(alice).then( - () => { - fxa.getOAuthToken({ scope: "profile", client: client }).then( - (result) => { - do_check_true(getTokenFromAssertionCalled); - do_check_eq(result, "token"); - run_next_test(); - } - ) - } - ); - -}); - -add_test(function test_getOAuthTokenScoped() { - let fxa = new MockFxAccounts(); - let alice = getTestUser("alice"); - alice.verified = true; - let getTokenFromAssertionCalled = false; - - fxa.internal._d_signCertificate.resolve("cert1"); - - // create a mock oauth client - let client = new FxAccountsOAuthGrantClient({ - serverURL: "http://example.com/v1", - client_id: "abc123" - }); - client.getTokenFromAssertion = function (assertion, scopeString) { - equal(scopeString, "foo bar"); - getTokenFromAssertionCalled = true; - return Promise.resolve({ access_token: "token" }); - }; - - fxa.setSignedInUser(alice).then( - () => { - fxa.getOAuthToken({ scope: ["foo", "bar"], client: client }).then( - (result) => { - do_check_true(getTokenFromAssertionCalled); - do_check_eq(result, "token"); - run_next_test(); - } - ) - } - ); - -}); - -add_task(function* test_getOAuthTokenCached() { - let fxa = new MockFxAccounts(); - let alice = getTestUser("alice"); - alice.verified = true; - let numTokenFromAssertionCalls = 0; - - fxa.internal._d_signCertificate.resolve("cert1"); - - // create a mock oauth client - let client = new FxAccountsOAuthGrantClient({ - serverURL: "http://example.com/v1", - client_id: "abc123" - }); - client.getTokenFromAssertion = function () { - numTokenFromAssertionCalls += 1; - return Promise.resolve({ access_token: "token" }); - }; - - yield fxa.setSignedInUser(alice); - let result = yield fxa.getOAuthToken({ scope: "profile", client: client, service: "test-service" }); - do_check_eq(numTokenFromAssertionCalls, 1); - do_check_eq(result, "token"); - - // requesting it again should not re-fetch the token. - result = yield fxa.getOAuthToken({ scope: "profile", client: client, service: "test-service" }); - do_check_eq(numTokenFromAssertionCalls, 1); - do_check_eq(result, "token"); - // But requesting the same service and a different scope *will* get a new one. - result = yield fxa.getOAuthToken({ scope: "something-else", client: client, service: "test-service" }); - do_check_eq(numTokenFromAssertionCalls, 2); - do_check_eq(result, "token"); -}); - -add_task(function* test_getOAuthTokenCachedScopeNormalization() { - let fxa = new MockFxAccounts(); - let alice = getTestUser("alice"); - alice.verified = true; - let numTokenFromAssertionCalls = 0; - - fxa.internal._d_signCertificate.resolve("cert1"); - - // create a mock oauth client - let client = new FxAccountsOAuthGrantClient({ - serverURL: "http://example.com/v1", - client_id: "abc123" - }); - client.getTokenFromAssertion = function () { - numTokenFromAssertionCalls += 1; - return Promise.resolve({ access_token: "token" }); - }; - - yield fxa.setSignedInUser(alice); - let result = yield fxa.getOAuthToken({ scope: ["foo", "bar"], client: client, service: "test-service" }); - do_check_eq(numTokenFromAssertionCalls, 1); - do_check_eq(result, "token"); - - // requesting it again with the scope array in a different order not re-fetch the token. - result = yield fxa.getOAuthToken({ scope: ["bar", "foo"], client: client, service: "test-service" }); - do_check_eq(numTokenFromAssertionCalls, 1); - do_check_eq(result, "token"); - // requesting it again with the scope array in different case not re-fetch the token. - result = yield fxa.getOAuthToken({ scope: ["Bar", "Foo"], client: client, service: "test-service" }); - do_check_eq(numTokenFromAssertionCalls, 1); - do_check_eq(result, "token"); - // But requesting with a new entry in the array does fetch one. - result = yield fxa.getOAuthToken({ scope: ["foo", "bar", "etc"], client: client, service: "test-service" }); - do_check_eq(numTokenFromAssertionCalls, 2); - do_check_eq(result, "token"); -}); - -Services.prefs.setCharPref("identity.fxaccounts.remote.oauth.uri", "https://example.com/v1"); -add_test(function test_getOAuthToken_invalid_param() { - let fxa = new MockFxAccounts(); - - fxa.getOAuthToken() - .then(null, err => { - do_check_eq(err.message, "INVALID_PARAMETER"); - fxa.signOut().then(run_next_test); - }); -}); - -add_test(function test_getOAuthToken_invalid_scope_array() { - let fxa = new MockFxAccounts(); - - fxa.getOAuthToken({scope: []}) - .then(null, err => { - do_check_eq(err.message, "INVALID_PARAMETER"); - fxa.signOut().then(run_next_test); - }); -}); - -add_test(function test_getOAuthToken_misconfigure_oauth_uri() { - let fxa = new MockFxAccounts(); - - Services.prefs.deleteBranch("identity.fxaccounts.remote.oauth.uri"); - - fxa.getOAuthToken() - .then(null, err => { - do_check_eq(err.message, "INVALID_PARAMETER"); - // revert the pref - Services.prefs.setCharPref("identity.fxaccounts.remote.oauth.uri", "https://example.com/v1"); - fxa.signOut().then(run_next_test); - }); -}); - -add_test(function test_getOAuthToken_no_account() { - let fxa = new MockFxAccounts(); - - fxa.internal.currentAccountState.getUserAccountData = function () { - return Promise.resolve(null); - }; - - fxa.getOAuthToken({ scope: "profile" }) - .then(null, err => { - do_check_eq(err.message, "NO_ACCOUNT"); - fxa.signOut().then(run_next_test); - }); -}); - -add_test(function test_getOAuthToken_unverified() { - let fxa = new MockFxAccounts(); - let alice = getTestUser("alice"); - - fxa.setSignedInUser(alice).then(() => { - fxa.getOAuthToken({ scope: "profile" }) - .then(null, err => { - do_check_eq(err.message, "UNVERIFIED_ACCOUNT"); - fxa.signOut().then(run_next_test); - }); - }); -}); - -add_test(function test_getOAuthToken_network_error() { - let fxa = new MockFxAccounts(); - let alice = getTestUser("alice"); - alice.verified = true; - - fxa.internal._d_signCertificate.resolve("cert1"); - - // create a mock oauth client - let client = new FxAccountsOAuthGrantClient({ - serverURL: "http://example.com/v1", - client_id: "abc123" - }); - client.getTokenFromAssertion = function () { - return Promise.reject(new FxAccountsOAuthGrantClientError({ - error: ERROR_NETWORK, - errno: ERRNO_NETWORK - })); - }; - - fxa.setSignedInUser(alice).then(() => { - fxa.getOAuthToken({ scope: "profile", client: client }) - .then(null, err => { - do_check_eq(err.message, "NETWORK_ERROR"); - do_check_eq(err.details.errno, ERRNO_NETWORK); - run_next_test(); - }); - }); -}); - -add_test(function test_getOAuthToken_auth_error() { - let fxa = new MockFxAccounts(); - let alice = getTestUser("alice"); - alice.verified = true; - - fxa.internal._d_signCertificate.resolve("cert1"); - - // create a mock oauth client - let client = new FxAccountsOAuthGrantClient({ - serverURL: "http://example.com/v1", - client_id: "abc123" - }); - client.getTokenFromAssertion = function () { - return Promise.reject(new FxAccountsOAuthGrantClientError({ - error: ERROR_INVALID_FXA_ASSERTION, - errno: ERRNO_INVALID_FXA_ASSERTION - })); - }; - - fxa.setSignedInUser(alice).then(() => { - fxa.getOAuthToken({ scope: "profile", client: client }) - .then(null, err => { - do_check_eq(err.message, "AUTH_ERROR"); - do_check_eq(err.details.errno, ERRNO_INVALID_FXA_ASSERTION); - run_next_test(); - }); - }); -}); - -add_test(function test_getOAuthToken_unknown_error() { - let fxa = new MockFxAccounts(); - let alice = getTestUser("alice"); - alice.verified = true; - - fxa.internal._d_signCertificate.resolve("cert1"); - - // create a mock oauth client - let client = new FxAccountsOAuthGrantClient({ - serverURL: "http://example.com/v1", - client_id: "abc123" - }); - client.getTokenFromAssertion = function () { - return Promise.reject("boom"); - }; - - fxa.setSignedInUser(alice).then(() => { - fxa.getOAuthToken({ scope: "profile", client: client }) - .then(null, err => { - do_check_eq(err.message, "UNKNOWN_ERROR"); - run_next_test(); - }); - }); -}); - -add_test(function test_getSignedInUserProfile() { - let alice = getTestUser("alice"); - alice.verified = true; - - let mockProfile = { - getProfile: function () { - return Promise.resolve({ avatar: "image" }); - }, - tearDown: function() {}, - }; - let fxa = new FxAccounts({ - _signOutServer() { return Promise.resolve(); }, - _registerOrUpdateDevice() { return Promise.resolve(); } - }); - - fxa.setSignedInUser(alice).then(() => { - fxa.internal._profile = mockProfile; - fxa.getSignedInUserProfile() - .then(result => { - do_check_true(!!result); - do_check_eq(result.avatar, "image"); - run_next_test(); - }); - }); -}); - -add_test(function test_getSignedInUserProfile_error_uses_account_data() { - let fxa = new MockFxAccounts(); - let alice = getTestUser("alice"); - alice.verified = true; - - fxa.internal.getSignedInUser = function () { - return Promise.resolve({ email: "foo@bar.com" }); - }; - - let teardownCalled = false; - fxa.setSignedInUser(alice).then(() => { - fxa.internal._profile = { - getProfile: function () { - return Promise.reject("boom"); - }, - tearDown: function() { - teardownCalled = true; - } - }; - - fxa.getSignedInUserProfile() - .catch(error => { - do_check_eq(error.message, "UNKNOWN_ERROR"); - fxa.signOut().then(() => { - do_check_true(teardownCalled); - run_next_test(); - }); - }); - }); -}); - -add_test(function test_getSignedInUserProfile_unverified_account() { - let fxa = new MockFxAccounts(); - let alice = getTestUser("alice"); - - fxa.setSignedInUser(alice).then(() => { - fxa.getSignedInUserProfile() - .catch(error => { - do_check_eq(error.message, "UNVERIFIED_ACCOUNT"); - fxa.signOut().then(run_next_test); - }); - }); - -}); - -add_test(function test_getSignedInUserProfile_no_account_data() { - let fxa = new MockFxAccounts(); - - fxa.internal.getSignedInUser = function () { - return Promise.resolve(null); - }; - - fxa.getSignedInUserProfile() - .catch(error => { - do_check_eq(error.message, "NO_ACCOUNT"); - fxa.signOut().then(run_next_test); - }); - -}); - -add_task(function* test_checkVerificationStatusFailed() { - let fxa = new MockFxAccounts(); - let alice = getTestUser("alice"); - alice.verified = true; - - let client = fxa.internal.fxAccountsClient; - client.recoveryEmailStatus = () => { - return Promise.reject({ - code: 401, - errno: ERRNO_INVALID_AUTH_TOKEN, - }); - }; - client.accountStatus = () => Promise.resolve(true); - - yield fxa.setSignedInUser(alice); - let user = yield fxa.internal.getUserAccountData(); - do_check_neq(alice.sessionToken, null); - do_check_eq(user.email, alice.email); - do_check_eq(user.verified, true); - - yield fxa.checkVerificationStatus(); - - user = yield fxa.internal.getUserAccountData(); - do_check_eq(user.email, alice.email); - do_check_eq(user.sessionToken, null); -}); - -/* - * End of tests. - * Utility functions follow. - */ - -function expandHex(two_hex) { - // Return a 64-character hex string, encoding 32 identical bytes. - let eight_hex = two_hex + two_hex + two_hex + two_hex; - let thirtytwo_hex = eight_hex + eight_hex + eight_hex + eight_hex; - return thirtytwo_hex + thirtytwo_hex; -}; - -function expandBytes(two_hex) { - return CommonUtils.hexToBytes(expandHex(two_hex)); -}; - -function getTestUser(name) { - return { - email: name + "@example.com", - uid: "1ad7f502-4cc7-4ec1-a209-071fd2fae348", - deviceId: name + "'s device id", - sessionToken: name + "'s session token", - keyFetchToken: name + "'s keyfetch token", - unwrapBKey: expandHex("44"), - verified: false - }; -} - -function makeObserver(aObserveTopic, aObserveFunc) { - let observer = { - // nsISupports provides type management in C++ - // nsIObserver is to be an observer - QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports, Ci.nsIObserver]), - - observe: function (aSubject, aTopic, aData) { - log.debug("observed " + aTopic + " " + aData); - if (aTopic == aObserveTopic) { - removeMe(); - aObserveFunc(aSubject, aTopic, aData); - } - } - }; - - function removeMe() { - log.debug("removing observer for " + aObserveTopic); - Services.obs.removeObserver(observer, aObserveTopic); - } - - Services.obs.addObserver(observer, aObserveTopic, false); - return removeMe; -} - -function do_check_throws(func, result, stack) -{ - if (!stack) - stack = Components.stack.caller; - - try { - func(); - } catch (ex) { - if (ex.name == result) { - return; - } - do_throw("Expected result " + result + ", caught " + ex.name, stack); - } - - if (result) { - do_throw("Expected result " + result + ", none thrown", stack); - } -} |