michael@0: /* Any copyright is dedicated to the Public Domain. michael@0: * http://creativecommons.org/publicdomain/zero/1.0/ */ michael@0: michael@0: const Cc = Components.classes; michael@0: const Ci = Components.interfaces; michael@0: const Cu = Components.utils; michael@0: const Cr = Components.results; michael@0: michael@0: Cu.import("resource://testing-common/httpd.js"); michael@0: michael@0: // XXX until bug 937114 is fixed michael@0: Cu.importGlobalProperties(["atob"]); michael@0: michael@0: // The following boilerplate makes sure that XPCom calls michael@0: // that use the profile directory work. michael@0: michael@0: Cu.import("resource://gre/modules/XPCOMUtils.jsm"); michael@0: Cu.import("resource://gre/modules/Services.jsm"); michael@0: michael@0: XPCOMUtils.defineLazyModuleGetter(this, "jwcrypto", michael@0: "resource://gre/modules/identity/jwcrypto.jsm"); michael@0: michael@0: XPCOMUtils.defineLazyModuleGetter(this, "IDService", michael@0: "resource://gre/modules/identity/Identity.jsm", michael@0: "IdentityService"); michael@0: michael@0: XPCOMUtils.defineLazyModuleGetter(this, michael@0: "IdentityStore", michael@0: "resource://gre/modules/identity/IdentityStore.jsm"); michael@0: michael@0: XPCOMUtils.defineLazyModuleGetter(this, michael@0: "Logger", michael@0: "resource://gre/modules/identity/LogUtils.jsm"); michael@0: michael@0: XPCOMUtils.defineLazyServiceGetter(this, michael@0: "uuidGenerator", michael@0: "@mozilla.org/uuid-generator;1", michael@0: "nsIUUIDGenerator"); michael@0: michael@0: const TEST_MESSAGE_MANAGER = "Mr McFeeley"; michael@0: const TEST_URL = "https://myfavoritebacon.com"; michael@0: const TEST_URL2 = "https://myfavoritebaconinacan.com"; michael@0: const TEST_USER = "user@mozilla.com"; michael@0: const TEST_PRIVKEY = "fake-privkey"; michael@0: const TEST_CERT = "fake-cert"; michael@0: const TEST_ASSERTION = "fake-assertion"; michael@0: const TEST_IDPPARAMS = { michael@0: domain: "myfavoriteflan.com", michael@0: authentication: "/foo/authenticate.html", michael@0: provisioning: "/foo/provision.html" michael@0: }; michael@0: michael@0: // The following are utility functions for Identity testing michael@0: michael@0: function log(...aMessageArgs) { michael@0: Logger.log.apply(Logger, ["test"].concat(aMessageArgs)); michael@0: } michael@0: michael@0: function get_idstore() { michael@0: return IdentityStore; michael@0: } michael@0: michael@0: function partial(fn) { michael@0: let args = Array.prototype.slice.call(arguments, 1); michael@0: return function() { michael@0: return fn.apply(this, args.concat(Array.prototype.slice.call(arguments))); michael@0: }; michael@0: } michael@0: michael@0: function uuid() { michael@0: return uuidGenerator.generateUUID().toString(); michael@0: } michael@0: michael@0: function base64UrlDecode(s) { michael@0: s = s.replace(/-/g, "+"); michael@0: s = s.replace(/_/g, "/"); michael@0: michael@0: // Replace padding if it was stripped by the sender. michael@0: // See http://tools.ietf.org/html/rfc4648#section-4 michael@0: switch (s.length % 4) { michael@0: case 0: michael@0: break; // No pad chars in this case michael@0: case 2: michael@0: s += "=="; michael@0: break; // Two pad chars michael@0: case 3: michael@0: s += "="; michael@0: break; // One pad char michael@0: default: michael@0: throw new InputException("Illegal base64url string!"); michael@0: } michael@0: michael@0: // With correct padding restored, apply the standard base64 decoder michael@0: return atob(s); michael@0: } michael@0: michael@0: // create a mock "doc" object, which the Identity Service michael@0: // uses as a pointer back into the doc object michael@0: function mock_doc(aIdentity, aOrigin, aDoFunc) { michael@0: let mockedDoc = {}; michael@0: mockedDoc.id = uuid(); michael@0: mockedDoc.loggedInUser = aIdentity; michael@0: mockedDoc.origin = aOrigin; michael@0: mockedDoc["do"] = aDoFunc; michael@0: mockedDoc._mm = TEST_MESSAGE_MANAGER; michael@0: mockedDoc.doReady = partial(aDoFunc, "ready"); michael@0: mockedDoc.doLogin = partial(aDoFunc, "login"); michael@0: mockedDoc.doLogout = partial(aDoFunc, "logout"); michael@0: mockedDoc.doError = partial(aDoFunc, "error"); michael@0: mockedDoc.doCancel = partial(aDoFunc, "cancel"); michael@0: mockedDoc.doCoffee = partial(aDoFunc, "coffee"); michael@0: mockedDoc.childProcessShutdown = partial(aDoFunc, "child-process-shutdown"); michael@0: michael@0: mockedDoc.RP = mockedDoc; michael@0: michael@0: return mockedDoc; michael@0: } michael@0: michael@0: function mock_fxa_rp(aIdentity, aOrigin, aDoFunc) { michael@0: let mockedDoc = {}; michael@0: mockedDoc.id = uuid(); michael@0: mockedDoc.emailHint = aIdentity; michael@0: mockedDoc.origin = aOrigin; michael@0: mockedDoc.wantIssuer = "firefox-accounts"; michael@0: mockedDoc._mm = TEST_MESSAGE_MANAGER; michael@0: michael@0: mockedDoc.doReady = partial(aDoFunc, "ready"); michael@0: mockedDoc.doLogin = partial(aDoFunc, "login"); michael@0: mockedDoc.doLogout = partial(aDoFunc, "logout"); michael@0: mockedDoc.doError = partial(aDoFunc, "error"); michael@0: mockedDoc.doCancel = partial(aDoFunc, "cancel"); michael@0: mockedDoc.childProcessShutdown = partial(aDoFunc, "child-process-shutdown"); michael@0: michael@0: mockedDoc.RP = mockedDoc; michael@0: michael@0: return mockedDoc; michael@0: } michael@0: michael@0: // mimicking callback funtionality for ease of testing michael@0: // this observer auto-removes itself after the observe function michael@0: // is called, so this is meant to observe only ONE event. michael@0: function makeObserver(aObserveTopic, aObserveFunc) { michael@0: let observer = { michael@0: // nsISupports provides type management in C++ michael@0: // nsIObserver is to be an observer michael@0: QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports, Ci.nsIObserver]), michael@0: michael@0: observe: function (aSubject, aTopic, aData) { michael@0: if (aTopic == aObserveTopic) { michael@0: aObserveFunc(aSubject, aTopic, aData); michael@0: Services.obs.removeObserver(observer, aObserveTopic); michael@0: } michael@0: } michael@0: }; michael@0: michael@0: Services.obs.addObserver(observer, aObserveTopic, false); michael@0: } michael@0: michael@0: // set up the ID service with an identity with keypair and all michael@0: // when ready, invoke callback with the identity michael@0: function setup_test_identity(identity, cert, cb) { michael@0: // set up the store so that we're supposed to be logged in michael@0: let store = get_idstore(); michael@0: michael@0: function keyGenerated(err, kpo) { michael@0: store.addIdentity(identity, kpo, cert); michael@0: cb(); michael@0: }; michael@0: michael@0: jwcrypto.generateKeyPair("DS160", keyGenerated); michael@0: } michael@0: michael@0: // takes a list of functions and returns a function that michael@0: // when called the first time, calls the first func, michael@0: // then the next time the second, etc. michael@0: function call_sequentially() { michael@0: let numCalls = 0; michael@0: let funcs = arguments; michael@0: michael@0: return function() { michael@0: if (!funcs[numCalls]) { michael@0: let argString = Array.prototype.slice.call(arguments).join(","); michael@0: do_throw("Too many calls: " + argString); michael@0: return; michael@0: } michael@0: funcs[numCalls].apply(funcs[numCalls],arguments); michael@0: numCalls += 1; michael@0: }; michael@0: } michael@0: michael@0: /* michael@0: * Setup a provisioning workflow with appropriate callbacks michael@0: * michael@0: * identity is the email we're provisioning. michael@0: * michael@0: * afterSetupCallback is required. michael@0: * michael@0: * doneProvisioningCallback is optional, if the caller michael@0: * wants to be notified when the whole provisioning workflow is done michael@0: * michael@0: * frameCallbacks is optional, contains the callbacks that the sandbox michael@0: * frame would provide in response to DOM calls. michael@0: */ michael@0: function setup_provisioning(identity, afterSetupCallback, doneProvisioningCallback, callerCallbacks) { michael@0: IDService.reset(); michael@0: michael@0: let provId = uuid(); michael@0: IDService.IDP._provisionFlows[provId] = { michael@0: identity : identity, michael@0: idpParams: TEST_IDPPARAMS, michael@0: callback: function(err) { michael@0: if (doneProvisioningCallback) michael@0: doneProvisioningCallback(err); michael@0: }, michael@0: sandbox: { michael@0: // Emulate the free() method on the iframe sandbox michael@0: free: function() {} michael@0: } michael@0: }; michael@0: michael@0: let caller = {}; michael@0: caller.id = provId; michael@0: caller.doBeginProvisioningCallback = function(id, duration_s) { michael@0: if (callerCallbacks && callerCallbacks.beginProvisioningCallback) michael@0: callerCallbacks.beginProvisioningCallback(id, duration_s); michael@0: }; michael@0: caller.doGenKeyPairCallback = function(pk) { michael@0: if (callerCallbacks && callerCallbacks.genKeyPairCallback) michael@0: callerCallbacks.genKeyPairCallback(pk); michael@0: }; michael@0: michael@0: afterSetupCallback(caller); michael@0: } michael@0: michael@0: // Switch debug messages on by default michael@0: let initialPrefDebugValue = false; michael@0: try { michael@0: initialPrefDebugValue = Services.prefs.getBoolPref("toolkit.identity.debug"); michael@0: } catch(noPref) {} michael@0: Services.prefs.setBoolPref("toolkit.identity.debug", true); michael@0: michael@0: // Switch on firefox accounts michael@0: let initialPrefFXAValue = false; michael@0: try { michael@0: initialPrefFXAValue = Services.prefs.getBoolPref("identity.fxaccounts.enabled"); michael@0: } catch(noPref) {} michael@0: Services.prefs.setBoolPref("identity.fxaccounts.enabled", true); michael@0: michael@0: // after execution, restore prefs michael@0: do_register_cleanup(function() { michael@0: log("restoring prefs to their initial values"); michael@0: Services.prefs.setBoolPref("toolkit.identity.debug", initialPrefDebugValue); michael@0: Services.prefs.setBoolPref("identity.fxaccounts.enabled", initialPrefFXAValue); michael@0: }); michael@0: michael@0: