1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/dom/identity/DOMIdentity.jsm Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,404 @@ 1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this file, 1.6 + * You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.7 + 1.8 +"use strict"; 1.9 + 1.10 +const {classes: Cc, interfaces: Ci, utils: Cu} = Components; 1.11 + 1.12 +Cu.import("resource://gre/modules/Services.jsm"); 1.13 +Cu.import("resource://gre/modules/XPCOMUtils.jsm"); 1.14 + 1.15 +const PREF_FXA_ENABLED = "identity.fxaccounts.enabled"; 1.16 + 1.17 +// This is the parent process corresponding to nsDOMIdentity. 1.18 +this.EXPORTED_SYMBOLS = ["DOMIdentity"]; 1.19 + 1.20 +XPCOMUtils.defineLazyModuleGetter(this, "objectCopy", 1.21 + "resource://gre/modules/identity/IdentityUtils.jsm"); 1.22 + 1.23 +XPCOMUtils.defineLazyModuleGetter(this, "IdentityService", 1.24 +#ifdef MOZ_B2G_VERSION 1.25 + "resource://gre/modules/identity/MinimalIdentity.jsm"); 1.26 +#else 1.27 + "resource://gre/modules/identity/Identity.jsm"); 1.28 +#endif 1.29 + 1.30 +XPCOMUtils.defineLazyModuleGetter(this, "FirefoxAccounts", 1.31 + "resource://gre/modules/identity/FirefoxAccounts.jsm"); 1.32 + 1.33 +XPCOMUtils.defineLazyModuleGetter(this, "makeMessageObject", 1.34 + "resource://gre/modules/identity/IdentityUtils.jsm"); 1.35 + 1.36 +XPCOMUtils.defineLazyModuleGetter(this, 1.37 + "Logger", 1.38 + "resource://gre/modules/identity/LogUtils.jsm"); 1.39 + 1.40 +XPCOMUtils.defineLazyServiceGetter(this, "ppmm", 1.41 + "@mozilla.org/parentprocessmessagemanager;1", 1.42 + "nsIMessageListenerManager"); 1.43 + 1.44 +function log(...aMessageArgs) { 1.45 + Logger.log.apply(Logger, ["DOMIdentity"].concat(aMessageArgs)); 1.46 +} 1.47 + 1.48 +function IDDOMMessage(aOptions) { 1.49 + objectCopy(aOptions, this); 1.50 +} 1.51 + 1.52 +function IDPProvisioningContext(aID, aOrigin, aTargetMM) { 1.53 + this._id = aID; 1.54 + this._origin = aOrigin; 1.55 + this._mm = aTargetMM; 1.56 +} 1.57 + 1.58 +IDPProvisioningContext.prototype = { 1.59 + get id() this._id, 1.60 + get origin() this._origin, 1.61 + 1.62 + doBeginProvisioningCallback: function IDPPC_doBeginProvCB(aID, aCertDuration) { 1.63 + let message = new IDDOMMessage({id: this.id}); 1.64 + message.identity = aID; 1.65 + message.certDuration = aCertDuration; 1.66 + this._mm.sendAsyncMessage("Identity:IDP:CallBeginProvisioningCallback", 1.67 + message); 1.68 + }, 1.69 + 1.70 + doGenKeyPairCallback: function IDPPC_doGenKeyPairCallback(aPublicKey) { 1.71 + log("doGenKeyPairCallback"); 1.72 + let message = new IDDOMMessage({id: this.id}); 1.73 + message.publicKey = aPublicKey; 1.74 + this._mm.sendAsyncMessage("Identity:IDP:CallGenKeyPairCallback", message); 1.75 + }, 1.76 + 1.77 + doError: function(msg) { 1.78 + log("Provisioning ERROR: " + msg); 1.79 + } 1.80 +}; 1.81 + 1.82 +function IDPAuthenticationContext(aID, aOrigin, aTargetMM) { 1.83 + this._id = aID; 1.84 + this._origin = aOrigin; 1.85 + this._mm = aTargetMM; 1.86 +} 1.87 + 1.88 +IDPAuthenticationContext.prototype = { 1.89 + get id() this._id, 1.90 + get origin() this._origin, 1.91 + 1.92 + doBeginAuthenticationCallback: function IDPAC_doBeginAuthCB(aIdentity) { 1.93 + let message = new IDDOMMessage({id: this.id}); 1.94 + message.identity = aIdentity; 1.95 + this._mm.sendAsyncMessage("Identity:IDP:CallBeginAuthenticationCallback", 1.96 + message); 1.97 + }, 1.98 + 1.99 + doError: function IDPAC_doError(msg) { 1.100 + log("Authentication ERROR: " + msg); 1.101 + } 1.102 +}; 1.103 + 1.104 +function RPWatchContext(aOptions, aTargetMM) { 1.105 + objectCopy(aOptions, this); 1.106 + 1.107 + // id and origin are required 1.108 + if (! (this.id && this.origin)) { 1.109 + throw new Error("id and origin are required for RP watch context"); 1.110 + } 1.111 + 1.112 + // default for no loggedInUser is undefined, not null 1.113 + this.loggedInUser = aOptions.loggedInUser; 1.114 + 1.115 + // Maybe internal. For hosted b2g identity shim. 1.116 + this._internal = aOptions._internal; 1.117 + 1.118 + this._mm = aTargetMM; 1.119 +} 1.120 + 1.121 +RPWatchContext.prototype = { 1.122 + doLogin: function RPWatchContext_onlogin(aAssertion, aMaybeInternalParams) { 1.123 + log("doLogin: " + this.id); 1.124 + let message = new IDDOMMessage({id: this.id, assertion: aAssertion}); 1.125 + if (aMaybeInternalParams) { 1.126 + message._internalParams = aMaybeInternalParams; 1.127 + } 1.128 + this._mm.sendAsyncMessage("Identity:RP:Watch:OnLogin", message); 1.129 + }, 1.130 + 1.131 + doLogout: function RPWatchContext_onlogout() { 1.132 + log("doLogout: " + this.id); 1.133 + let message = new IDDOMMessage({id: this.id}); 1.134 + this._mm.sendAsyncMessage("Identity:RP:Watch:OnLogout", message); 1.135 + }, 1.136 + 1.137 + doReady: function RPWatchContext_onready() { 1.138 + log("doReady: " + this.id); 1.139 + let message = new IDDOMMessage({id: this.id}); 1.140 + this._mm.sendAsyncMessage("Identity:RP:Watch:OnReady", message); 1.141 + }, 1.142 + 1.143 + doCancel: function RPWatchContext_oncancel() { 1.144 + log("doCancel: " + this.id); 1.145 + let message = new IDDOMMessage({id: this.id}); 1.146 + this._mm.sendAsyncMessage("Identity:RP:Watch:OnCancel", message); 1.147 + }, 1.148 + 1.149 + doError: function RPWatchContext_onerror(aMessage) { 1.150 + log("doError: " + this.id + ": " + JSON.stringify(aMessage)); 1.151 + let message = new IDDOMMessage({id: this.id, message: aMessage}); 1.152 + this._mm.sendAsyncMessage("Identity:RP:Watch:OnError", message); 1.153 + } 1.154 +}; 1.155 + 1.156 +this.DOMIdentity = { 1.157 + /* 1.158 + * When relying parties (RPs) invoke the watch() method, they can request 1.159 + * to use Firefox Accounts as their auth service or BrowserID (the default). 1.160 + * For each RP, we create an RPWatchContext to store the parameters given to 1.161 + * watch(), and to provide hooks to invoke the onlogin(), onlogout(), etc. 1.162 + * callbacks held in the nsDOMIdentity state. 1.163 + * 1.164 + * The serviceContexts map associates the window ID of the RP with the 1.165 + * context object. The mmContexts map associates a message manager with a 1.166 + * window ID. We use the mmContexts map when child-process-shutdown is 1.167 + * observed, and all we have is a message manager to identify the window in 1.168 + * question. 1.169 + */ 1.170 + _serviceContexts: new Map(), 1.171 + _mmContexts: new Map(), 1.172 + 1.173 + /* 1.174 + * Mockable, for testing 1.175 + */ 1.176 + _mockIdentityService: null, 1.177 + get IdentityService() { 1.178 + if (this._mockIdentityService) { 1.179 + log("Using a mocked identity service"); 1.180 + return this._mockIdentityService; 1.181 + } 1.182 + return IdentityService; 1.183 + }, 1.184 + 1.185 + /* 1.186 + * Create a new RPWatchContext, and update the context maps. 1.187 + */ 1.188 + newContext: function(message, targetMM) { 1.189 + let context = new RPWatchContext(message, targetMM); 1.190 + this._serviceContexts.set(message.id, context); 1.191 + this._mmContexts.set(targetMM, message.id); 1.192 + return context; 1.193 + }, 1.194 + 1.195 + /* 1.196 + * Get the identity service used for an RP. 1.197 + * 1.198 + * @object message 1.199 + * A message received from an RP. Will include the id of the window 1.200 + * whence the message originated. 1.201 + * 1.202 + * Returns FirefoxAccounts or IdentityService 1.203 + */ 1.204 + getService: function(message) { 1.205 + if (!this._serviceContexts.has(message.id)) { 1.206 + throw new Error("getService called before newContext for " + message.id); 1.207 + } 1.208 + 1.209 + let context = this._serviceContexts.get(message.id); 1.210 + if (context.wantIssuer == "firefox-accounts") { 1.211 + if (Services.prefs.getPrefType(PREF_FXA_ENABLED) === Ci.nsIPrefBranch.PREF_BOOL 1.212 + && Services.prefs.getBoolPref(PREF_FXA_ENABLED)) { 1.213 + return FirefoxAccounts; 1.214 + } 1.215 + log("WARNING: Firefox Accounts is not enabled; Defaulting to BrowserID"); 1.216 + } 1.217 + return this.IdentityService; 1.218 + }, 1.219 + 1.220 + /* 1.221 + * Get the RPWatchContext object for a given message manager. 1.222 + */ 1.223 + getContextForMM: function(targetMM) { 1.224 + return this._serviceContexts.get(this._mmContexts.get(targetMM)); 1.225 + }, 1.226 + 1.227 + hasContextForMM: function(targetMM) { 1.228 + return this._mmContexts.has(targetMM); 1.229 + }, 1.230 + 1.231 + /* 1.232 + * Delete the RPWatchContext object for a given message manager. Removes the 1.233 + * mapping both from _serviceContexts and _mmContexts. 1.234 + */ 1.235 + deleteContextForMM: function(targetMM) { 1.236 + this._serviceContexts.delete(this._mmContexts.get(targetMM)); 1.237 + this._mmContexts.delete(targetMM); 1.238 + }, 1.239 + 1.240 + // nsIMessageListener 1.241 + receiveMessage: function DOMIdentity_receiveMessage(aMessage) { 1.242 + let msg = aMessage.json; 1.243 + 1.244 + // Target is the frame message manager that called us and is 1.245 + // used to send replies back to the proper window. 1.246 + let targetMM = aMessage.target; 1.247 + 1.248 + switch (aMessage.name) { 1.249 + // RP 1.250 + case "Identity:RP:Watch": 1.251 + this._watch(msg, targetMM); 1.252 + break; 1.253 + case "Identity:RP:Unwatch": 1.254 + this._unwatch(msg, targetMM); 1.255 + break; 1.256 + case "Identity:RP:Request": 1.257 + this._request(msg, targetMM); 1.258 + break; 1.259 + case "Identity:RP:Logout": 1.260 + this._logout(msg, targetMM); 1.261 + break; 1.262 + // IDP 1.263 + case "Identity:IDP:BeginProvisioning": 1.264 + this._beginProvisioning(msg, targetMM); 1.265 + break; 1.266 + case "Identity:IDP:GenKeyPair": 1.267 + this._genKeyPair(msg); 1.268 + break; 1.269 + case "Identity:IDP:RegisterCertificate": 1.270 + this._registerCertificate(msg); 1.271 + break; 1.272 + case "Identity:IDP:ProvisioningFailure": 1.273 + this._provisioningFailure(msg); 1.274 + break; 1.275 + case "Identity:IDP:BeginAuthentication": 1.276 + this._beginAuthentication(msg, targetMM); 1.277 + break; 1.278 + case "Identity:IDP:CompleteAuthentication": 1.279 + this._completeAuthentication(msg); 1.280 + break; 1.281 + case "Identity:IDP:AuthenticationFailure": 1.282 + this._authenticationFailure(msg); 1.283 + break; 1.284 + case "child-process-shutdown": 1.285 + // we receive child-process-shutdown if the appliction crashes, 1.286 + // including if it is crashed by the OS (killed for out-of-memory, 1.287 + // for example) 1.288 + this._childProcessShutdown(targetMM); 1.289 + break; 1.290 + } 1.291 + }, 1.292 + 1.293 + // nsIObserver 1.294 + observe: function DOMIdentity_observe(aSubject, aTopic, aData) { 1.295 + switch (aTopic) { 1.296 + case "xpcom-shutdown": 1.297 + this._unsubscribeListeners(); 1.298 + Services.obs.removeObserver(this, "xpcom-shutdown"); 1.299 + Services.ww.unregisterNotification(this); 1.300 + break; 1.301 + } 1.302 + }, 1.303 + 1.304 + messages: ["Identity:RP:Watch", "Identity:RP:Request", "Identity:RP:Logout", 1.305 + "Identity:IDP:BeginProvisioning", "Identity:IDP:ProvisioningFailure", 1.306 + "Identity:IDP:RegisterCertificate", "Identity:IDP:GenKeyPair", 1.307 + "Identity:IDP:BeginAuthentication", 1.308 + "Identity:IDP:CompleteAuthentication", 1.309 + "Identity:IDP:AuthenticationFailure", 1.310 + "Identity:RP:Unwatch", 1.311 + "child-process-shutdown"], 1.312 + 1.313 + // Private. 1.314 + _init: function DOMIdentity__init() { 1.315 + Services.ww.registerNotification(this); 1.316 + Services.obs.addObserver(this, "xpcom-shutdown", false); 1.317 + this._subscribeListeners(); 1.318 + }, 1.319 + 1.320 + _subscribeListeners: function DOMIdentity__subscribeListeners() { 1.321 + if (!ppmm) return; 1.322 + for (let message of this.messages) { 1.323 + ppmm.addMessageListener(message, this); 1.324 + } 1.325 + }, 1.326 + 1.327 + _unsubscribeListeners: function DOMIdentity__unsubscribeListeners() { 1.328 + for (let message of this.messages) { 1.329 + ppmm.removeMessageListener(message, this); 1.330 + } 1.331 + ppmm = null; 1.332 + }, 1.333 + 1.334 + _watch: function DOMIdentity__watch(message, targetMM) { 1.335 + log("DOMIdentity__watch: " + message.id); 1.336 + let context = this.newContext(message, targetMM); 1.337 + this.getService(message).RP.watch(context); 1.338 + }, 1.339 + 1.340 + _unwatch: function DOMIdentity_unwatch(message, targetMM) { 1.341 + log("DOMIDentity__unwatch: " + message.id); 1.342 + // If watch failed for some reason (e.g., exception thrown because RP did 1.343 + // not have the right callbacks, we don't want unwatch to throw, because it 1.344 + // will break the process of releasing the page's resources and leak 1.345 + // memory. 1.346 + try { 1.347 + this.getService(message).RP.unwatch(message.id, targetMM); 1.348 + } catch(ex) { 1.349 + log("ERROR: can't unwatch " + message.id + ": " + ex); 1.350 + } 1.351 + }, 1.352 + 1.353 + _request: function DOMIdentity__request(message) { 1.354 + this.getService(message).RP.request(message.id, message); 1.355 + }, 1.356 + 1.357 + _logout: function DOMIdentity__logout(message) { 1.358 + log("logout " + message + "\n"); 1.359 + this.getService(message).RP.logout(message.id, message.origin, message); 1.360 + }, 1.361 + 1.362 + _childProcessShutdown: function DOMIdentity__childProcessShutdown(targetMM) { 1.363 + if (!this.hasContextForMM(targetMM)) { 1.364 + return; 1.365 + } 1.366 + 1.367 + this.getContextForMM(targetMM).RP.childProcessShutdown(targetMM); 1.368 + this.deleteContextForMM(targetMM); 1.369 + 1.370 + let options = makeMessageObject({messageManager: targetMM, id: null, origin: null}); 1.371 + Services.obs.notifyObservers({wrappedJSObject: options}, "identity-child-process-shutdown", null); 1.372 + }, 1.373 + 1.374 + _beginProvisioning: function DOMIdentity__beginProvisioning(message, targetMM) { 1.375 + let context = new IDPProvisioningContext(message.id, message.origin, 1.376 + targetMM); 1.377 + this.getService(message).IDP.beginProvisioning(context); 1.378 + }, 1.379 + 1.380 + _genKeyPair: function DOMIdentity__genKeyPair(message) { 1.381 + this.getService(message).IDP.genKeyPair(message.id); 1.382 + }, 1.383 + 1.384 + _registerCertificate: function DOMIdentity__registerCertificate(message) { 1.385 + this.getService(message).IDP.registerCertificate(message.id, message.cert); 1.386 + }, 1.387 + 1.388 + _provisioningFailure: function DOMIdentity__provisioningFailure(message) { 1.389 + this.getService(message).IDP.raiseProvisioningFailure(message.id, message.reason); 1.390 + }, 1.391 + 1.392 + _beginAuthentication: function DOMIdentity__beginAuthentication(message, targetMM) { 1.393 + let context = new IDPAuthenticationContext(message.id, message.origin, 1.394 + targetMM); 1.395 + this.getService(message).IDP.beginAuthentication(context); 1.396 + }, 1.397 + 1.398 + _completeAuthentication: function DOMIdentity__completeAuthentication(message) { 1.399 + this.getService(message).IDP.completeAuthentication(message.id); 1.400 + }, 1.401 + 1.402 + _authenticationFailure: function DOMIdentity__authenticationFailure(message) { 1.403 + this.getService(message).IDP.cancelAuthentication(message.id); 1.404 + } 1.405 +}; 1.406 + 1.407 +// Object is initialized by nsIDService.js