dom/identity/DOMIdentity.jsm

changeset 0
6474c204b198
     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

mercurial