michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this file, michael@0: * You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: "use strict"; michael@0: michael@0: const {classes: Cc, interfaces: Ci, utils: Cu} = Components; michael@0: michael@0: Cu.import("resource://gre/modules/Services.jsm"); michael@0: Cu.import("resource://gre/modules/XPCOMUtils.jsm"); michael@0: michael@0: const PREF_FXA_ENABLED = "identity.fxaccounts.enabled"; michael@0: michael@0: // This is the parent process corresponding to nsDOMIdentity. michael@0: this.EXPORTED_SYMBOLS = ["DOMIdentity"]; michael@0: michael@0: XPCOMUtils.defineLazyModuleGetter(this, "objectCopy", michael@0: "resource://gre/modules/identity/IdentityUtils.jsm"); michael@0: michael@0: XPCOMUtils.defineLazyModuleGetter(this, "IdentityService", michael@0: #ifdef MOZ_B2G_VERSION michael@0: "resource://gre/modules/identity/MinimalIdentity.jsm"); michael@0: #else michael@0: "resource://gre/modules/identity/Identity.jsm"); michael@0: #endif michael@0: michael@0: XPCOMUtils.defineLazyModuleGetter(this, "FirefoxAccounts", michael@0: "resource://gre/modules/identity/FirefoxAccounts.jsm"); michael@0: michael@0: XPCOMUtils.defineLazyModuleGetter(this, "makeMessageObject", michael@0: "resource://gre/modules/identity/IdentityUtils.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, "ppmm", michael@0: "@mozilla.org/parentprocessmessagemanager;1", michael@0: "nsIMessageListenerManager"); michael@0: michael@0: function log(...aMessageArgs) { michael@0: Logger.log.apply(Logger, ["DOMIdentity"].concat(aMessageArgs)); michael@0: } michael@0: michael@0: function IDDOMMessage(aOptions) { michael@0: objectCopy(aOptions, this); michael@0: } michael@0: michael@0: function IDPProvisioningContext(aID, aOrigin, aTargetMM) { michael@0: this._id = aID; michael@0: this._origin = aOrigin; michael@0: this._mm = aTargetMM; michael@0: } michael@0: michael@0: IDPProvisioningContext.prototype = { michael@0: get id() this._id, michael@0: get origin() this._origin, michael@0: michael@0: doBeginProvisioningCallback: function IDPPC_doBeginProvCB(aID, aCertDuration) { michael@0: let message = new IDDOMMessage({id: this.id}); michael@0: message.identity = aID; michael@0: message.certDuration = aCertDuration; michael@0: this._mm.sendAsyncMessage("Identity:IDP:CallBeginProvisioningCallback", michael@0: message); michael@0: }, michael@0: michael@0: doGenKeyPairCallback: function IDPPC_doGenKeyPairCallback(aPublicKey) { michael@0: log("doGenKeyPairCallback"); michael@0: let message = new IDDOMMessage({id: this.id}); michael@0: message.publicKey = aPublicKey; michael@0: this._mm.sendAsyncMessage("Identity:IDP:CallGenKeyPairCallback", message); michael@0: }, michael@0: michael@0: doError: function(msg) { michael@0: log("Provisioning ERROR: " + msg); michael@0: } michael@0: }; michael@0: michael@0: function IDPAuthenticationContext(aID, aOrigin, aTargetMM) { michael@0: this._id = aID; michael@0: this._origin = aOrigin; michael@0: this._mm = aTargetMM; michael@0: } michael@0: michael@0: IDPAuthenticationContext.prototype = { michael@0: get id() this._id, michael@0: get origin() this._origin, michael@0: michael@0: doBeginAuthenticationCallback: function IDPAC_doBeginAuthCB(aIdentity) { michael@0: let message = new IDDOMMessage({id: this.id}); michael@0: message.identity = aIdentity; michael@0: this._mm.sendAsyncMessage("Identity:IDP:CallBeginAuthenticationCallback", michael@0: message); michael@0: }, michael@0: michael@0: doError: function IDPAC_doError(msg) { michael@0: log("Authentication ERROR: " + msg); michael@0: } michael@0: }; michael@0: michael@0: function RPWatchContext(aOptions, aTargetMM) { michael@0: objectCopy(aOptions, this); michael@0: michael@0: // id and origin are required michael@0: if (! (this.id && this.origin)) { michael@0: throw new Error("id and origin are required for RP watch context"); michael@0: } michael@0: michael@0: // default for no loggedInUser is undefined, not null michael@0: this.loggedInUser = aOptions.loggedInUser; michael@0: michael@0: // Maybe internal. For hosted b2g identity shim. michael@0: this._internal = aOptions._internal; michael@0: michael@0: this._mm = aTargetMM; michael@0: } michael@0: michael@0: RPWatchContext.prototype = { michael@0: doLogin: function RPWatchContext_onlogin(aAssertion, aMaybeInternalParams) { michael@0: log("doLogin: " + this.id); michael@0: let message = new IDDOMMessage({id: this.id, assertion: aAssertion}); michael@0: if (aMaybeInternalParams) { michael@0: message._internalParams = aMaybeInternalParams; michael@0: } michael@0: this._mm.sendAsyncMessage("Identity:RP:Watch:OnLogin", message); michael@0: }, michael@0: michael@0: doLogout: function RPWatchContext_onlogout() { michael@0: log("doLogout: " + this.id); michael@0: let message = new IDDOMMessage({id: this.id}); michael@0: this._mm.sendAsyncMessage("Identity:RP:Watch:OnLogout", message); michael@0: }, michael@0: michael@0: doReady: function RPWatchContext_onready() { michael@0: log("doReady: " + this.id); michael@0: let message = new IDDOMMessage({id: this.id}); michael@0: this._mm.sendAsyncMessage("Identity:RP:Watch:OnReady", message); michael@0: }, michael@0: michael@0: doCancel: function RPWatchContext_oncancel() { michael@0: log("doCancel: " + this.id); michael@0: let message = new IDDOMMessage({id: this.id}); michael@0: this._mm.sendAsyncMessage("Identity:RP:Watch:OnCancel", message); michael@0: }, michael@0: michael@0: doError: function RPWatchContext_onerror(aMessage) { michael@0: log("doError: " + this.id + ": " + JSON.stringify(aMessage)); michael@0: let message = new IDDOMMessage({id: this.id, message: aMessage}); michael@0: this._mm.sendAsyncMessage("Identity:RP:Watch:OnError", message); michael@0: } michael@0: }; michael@0: michael@0: this.DOMIdentity = { michael@0: /* michael@0: * When relying parties (RPs) invoke the watch() method, they can request michael@0: * to use Firefox Accounts as their auth service or BrowserID (the default). michael@0: * For each RP, we create an RPWatchContext to store the parameters given to michael@0: * watch(), and to provide hooks to invoke the onlogin(), onlogout(), etc. michael@0: * callbacks held in the nsDOMIdentity state. michael@0: * michael@0: * The serviceContexts map associates the window ID of the RP with the michael@0: * context object. The mmContexts map associates a message manager with a michael@0: * window ID. We use the mmContexts map when child-process-shutdown is michael@0: * observed, and all we have is a message manager to identify the window in michael@0: * question. michael@0: */ michael@0: _serviceContexts: new Map(), michael@0: _mmContexts: new Map(), michael@0: michael@0: /* michael@0: * Mockable, for testing michael@0: */ michael@0: _mockIdentityService: null, michael@0: get IdentityService() { michael@0: if (this._mockIdentityService) { michael@0: log("Using a mocked identity service"); michael@0: return this._mockIdentityService; michael@0: } michael@0: return IdentityService; michael@0: }, michael@0: michael@0: /* michael@0: * Create a new RPWatchContext, and update the context maps. michael@0: */ michael@0: newContext: function(message, targetMM) { michael@0: let context = new RPWatchContext(message, targetMM); michael@0: this._serviceContexts.set(message.id, context); michael@0: this._mmContexts.set(targetMM, message.id); michael@0: return context; michael@0: }, michael@0: michael@0: /* michael@0: * Get the identity service used for an RP. michael@0: * michael@0: * @object message michael@0: * A message received from an RP. Will include the id of the window michael@0: * whence the message originated. michael@0: * michael@0: * Returns FirefoxAccounts or IdentityService michael@0: */ michael@0: getService: function(message) { michael@0: if (!this._serviceContexts.has(message.id)) { michael@0: throw new Error("getService called before newContext for " + message.id); michael@0: } michael@0: michael@0: let context = this._serviceContexts.get(message.id); michael@0: if (context.wantIssuer == "firefox-accounts") { michael@0: if (Services.prefs.getPrefType(PREF_FXA_ENABLED) === Ci.nsIPrefBranch.PREF_BOOL michael@0: && Services.prefs.getBoolPref(PREF_FXA_ENABLED)) { michael@0: return FirefoxAccounts; michael@0: } michael@0: log("WARNING: Firefox Accounts is not enabled; Defaulting to BrowserID"); michael@0: } michael@0: return this.IdentityService; michael@0: }, michael@0: michael@0: /* michael@0: * Get the RPWatchContext object for a given message manager. michael@0: */ michael@0: getContextForMM: function(targetMM) { michael@0: return this._serviceContexts.get(this._mmContexts.get(targetMM)); michael@0: }, michael@0: michael@0: hasContextForMM: function(targetMM) { michael@0: return this._mmContexts.has(targetMM); michael@0: }, michael@0: michael@0: /* michael@0: * Delete the RPWatchContext object for a given message manager. Removes the michael@0: * mapping both from _serviceContexts and _mmContexts. michael@0: */ michael@0: deleteContextForMM: function(targetMM) { michael@0: this._serviceContexts.delete(this._mmContexts.get(targetMM)); michael@0: this._mmContexts.delete(targetMM); michael@0: }, michael@0: michael@0: // nsIMessageListener michael@0: receiveMessage: function DOMIdentity_receiveMessage(aMessage) { michael@0: let msg = aMessage.json; michael@0: michael@0: // Target is the frame message manager that called us and is michael@0: // used to send replies back to the proper window. michael@0: let targetMM = aMessage.target; michael@0: michael@0: switch (aMessage.name) { michael@0: // RP michael@0: case "Identity:RP:Watch": michael@0: this._watch(msg, targetMM); michael@0: break; michael@0: case "Identity:RP:Unwatch": michael@0: this._unwatch(msg, targetMM); michael@0: break; michael@0: case "Identity:RP:Request": michael@0: this._request(msg, targetMM); michael@0: break; michael@0: case "Identity:RP:Logout": michael@0: this._logout(msg, targetMM); michael@0: break; michael@0: // IDP michael@0: case "Identity:IDP:BeginProvisioning": michael@0: this._beginProvisioning(msg, targetMM); michael@0: break; michael@0: case "Identity:IDP:GenKeyPair": michael@0: this._genKeyPair(msg); michael@0: break; michael@0: case "Identity:IDP:RegisterCertificate": michael@0: this._registerCertificate(msg); michael@0: break; michael@0: case "Identity:IDP:ProvisioningFailure": michael@0: this._provisioningFailure(msg); michael@0: break; michael@0: case "Identity:IDP:BeginAuthentication": michael@0: this._beginAuthentication(msg, targetMM); michael@0: break; michael@0: case "Identity:IDP:CompleteAuthentication": michael@0: this._completeAuthentication(msg); michael@0: break; michael@0: case "Identity:IDP:AuthenticationFailure": michael@0: this._authenticationFailure(msg); michael@0: break; michael@0: case "child-process-shutdown": michael@0: // we receive child-process-shutdown if the appliction crashes, michael@0: // including if it is crashed by the OS (killed for out-of-memory, michael@0: // for example) michael@0: this._childProcessShutdown(targetMM); michael@0: break; michael@0: } michael@0: }, michael@0: michael@0: // nsIObserver michael@0: observe: function DOMIdentity_observe(aSubject, aTopic, aData) { michael@0: switch (aTopic) { michael@0: case "xpcom-shutdown": michael@0: this._unsubscribeListeners(); michael@0: Services.obs.removeObserver(this, "xpcom-shutdown"); michael@0: Services.ww.unregisterNotification(this); michael@0: break; michael@0: } michael@0: }, michael@0: michael@0: messages: ["Identity:RP:Watch", "Identity:RP:Request", "Identity:RP:Logout", michael@0: "Identity:IDP:BeginProvisioning", "Identity:IDP:ProvisioningFailure", michael@0: "Identity:IDP:RegisterCertificate", "Identity:IDP:GenKeyPair", michael@0: "Identity:IDP:BeginAuthentication", michael@0: "Identity:IDP:CompleteAuthentication", michael@0: "Identity:IDP:AuthenticationFailure", michael@0: "Identity:RP:Unwatch", michael@0: "child-process-shutdown"], michael@0: michael@0: // Private. michael@0: _init: function DOMIdentity__init() { michael@0: Services.ww.registerNotification(this); michael@0: Services.obs.addObserver(this, "xpcom-shutdown", false); michael@0: this._subscribeListeners(); michael@0: }, michael@0: michael@0: _subscribeListeners: function DOMIdentity__subscribeListeners() { michael@0: if (!ppmm) return; michael@0: for (let message of this.messages) { michael@0: ppmm.addMessageListener(message, this); michael@0: } michael@0: }, michael@0: michael@0: _unsubscribeListeners: function DOMIdentity__unsubscribeListeners() { michael@0: for (let message of this.messages) { michael@0: ppmm.removeMessageListener(message, this); michael@0: } michael@0: ppmm = null; michael@0: }, michael@0: michael@0: _watch: function DOMIdentity__watch(message, targetMM) { michael@0: log("DOMIdentity__watch: " + message.id); michael@0: let context = this.newContext(message, targetMM); michael@0: this.getService(message).RP.watch(context); michael@0: }, michael@0: michael@0: _unwatch: function DOMIdentity_unwatch(message, targetMM) { michael@0: log("DOMIDentity__unwatch: " + message.id); michael@0: // If watch failed for some reason (e.g., exception thrown because RP did michael@0: // not have the right callbacks, we don't want unwatch to throw, because it michael@0: // will break the process of releasing the page's resources and leak michael@0: // memory. michael@0: try { michael@0: this.getService(message).RP.unwatch(message.id, targetMM); michael@0: } catch(ex) { michael@0: log("ERROR: can't unwatch " + message.id + ": " + ex); michael@0: } michael@0: }, michael@0: michael@0: _request: function DOMIdentity__request(message) { michael@0: this.getService(message).RP.request(message.id, message); michael@0: }, michael@0: michael@0: _logout: function DOMIdentity__logout(message) { michael@0: log("logout " + message + "\n"); michael@0: this.getService(message).RP.logout(message.id, message.origin, message); michael@0: }, michael@0: michael@0: _childProcessShutdown: function DOMIdentity__childProcessShutdown(targetMM) { michael@0: if (!this.hasContextForMM(targetMM)) { michael@0: return; michael@0: } michael@0: michael@0: this.getContextForMM(targetMM).RP.childProcessShutdown(targetMM); michael@0: this.deleteContextForMM(targetMM); michael@0: michael@0: let options = makeMessageObject({messageManager: targetMM, id: null, origin: null}); michael@0: Services.obs.notifyObservers({wrappedJSObject: options}, "identity-child-process-shutdown", null); michael@0: }, michael@0: michael@0: _beginProvisioning: function DOMIdentity__beginProvisioning(message, targetMM) { michael@0: let context = new IDPProvisioningContext(message.id, message.origin, michael@0: targetMM); michael@0: this.getService(message).IDP.beginProvisioning(context); michael@0: }, michael@0: michael@0: _genKeyPair: function DOMIdentity__genKeyPair(message) { michael@0: this.getService(message).IDP.genKeyPair(message.id); michael@0: }, michael@0: michael@0: _registerCertificate: function DOMIdentity__registerCertificate(message) { michael@0: this.getService(message).IDP.registerCertificate(message.id, message.cert); michael@0: }, michael@0: michael@0: _provisioningFailure: function DOMIdentity__provisioningFailure(message) { michael@0: this.getService(message).IDP.raiseProvisioningFailure(message.id, message.reason); michael@0: }, michael@0: michael@0: _beginAuthentication: function DOMIdentity__beginAuthentication(message, targetMM) { michael@0: let context = new IDPAuthenticationContext(message.id, message.origin, michael@0: targetMM); michael@0: this.getService(message).IDP.beginAuthentication(context); michael@0: }, michael@0: michael@0: _completeAuthentication: function DOMIdentity__completeAuthentication(message) { michael@0: this.getService(message).IDP.completeAuthentication(message.id); michael@0: }, michael@0: michael@0: _authenticationFailure: function DOMIdentity__authenticationFailure(message) { michael@0: this.getService(message).IDP.cancelAuthentication(message.id); michael@0: } michael@0: }; michael@0: michael@0: // Object is initialized by nsIDService.js