dom/identity/nsDOMIdentity.js

Fri, 16 Jan 2015 18:13:44 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Fri, 16 Jan 2015 18:13:44 +0100
branch
TOR_BUG_9701
changeset 14
925c144e1f1f
permissions
-rw-r--r--

Integrate suggestion from review to improve consistency with existing code.

     1 /* This Source Code Form is subject to the terms of the Mozilla Public
     2  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
     3  * You can obtain one at http://mozilla.org/MPL/2.0/. */
     5 "use strict";
     7 const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
     9 const PREF_DEBUG = "toolkit.identity.debug";
    10 const PREF_ENABLED = "dom.identity.enabled";
    12 // Bug 822450: Workaround for Bug 821740.  When testing with marionette,
    13 // relax navigator.id.request's requirement that it be handling native
    14 // events.  Synthetic marionette events are ok.
    15 const PREF_SYNTHETIC_EVENTS_OK = "dom.identity.syntheticEventsOk";
    17 // Maximum length of a string that will go through IPC
    18 const MAX_STRING_LENGTH = 2048;
    19 // Maximum number of times navigator.id.request can be called for a document
    20 const MAX_RP_CALLS = 100;
    22 Cu.import("resource://gre/modules/Services.jsm");
    23 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
    25 XPCOMUtils.defineLazyModuleGetter(this, "checkDeprecated",
    26                                   "resource://gre/modules/identity/IdentityUtils.jsm");
    27 XPCOMUtils.defineLazyModuleGetter(this, "checkRenamed",
    28                                   "resource://gre/modules/identity/IdentityUtils.jsm");
    29 XPCOMUtils.defineLazyModuleGetter(this, "objectCopy",
    30                                   "resource://gre/modules/identity/IdentityUtils.jsm");
    32 XPCOMUtils.defineLazyServiceGetter(this, "uuidgen",
    33                                    "@mozilla.org/uuid-generator;1",
    34                                    "nsIUUIDGenerator");
    36 // This is the child process corresponding to nsIDOMIdentity
    37 XPCOMUtils.defineLazyServiceGetter(this, "cpmm",
    38                                    "@mozilla.org/childprocessmessagemanager;1",
    39                                    "nsIMessageSender");
    42 const ERRORS = {
    43   "ERROR_NOT_AUTHORIZED_FOR_FIREFOX_ACCOUNTS":
    44     "Only privileged and certified apps may use Firefox Accounts",
    45   "ERROR_INVALID_ASSERTION_AUDIENCE":
    46     "Assertion audience may not differ from origin",
    47   "ERROR_REQUEST_WHILE_NOT_HANDLING_USER_INPUT":
    48     "The request() method may only be invoked when handling user input",
    49 };
    51 function nsDOMIdentity(aIdentityInternal) {
    52   this._identityInternal = aIdentityInternal;
    53 }
    54 nsDOMIdentity.prototype = {
    55   __exposedProps__: {
    56     // Relying Party (RP)
    57     watch: 'r',
    58     request: 'r',
    59     logout: 'r',
    60     get: 'r',
    61     getVerifiedEmail: 'r',
    63     // Provisioning
    64     beginProvisioning: 'r',
    65     genKeyPair: 'r',
    66     registerCertificate: 'r',
    67     raiseProvisioningFailure: 'r',
    69     // Authentication
    70     beginAuthentication: 'r',
    71     completeAuthentication: 'r',
    72     raiseAuthenticationFailure: 'r'
    73   },
    75   // require native events unless syntheticEventsOk is set
    76   get nativeEventsRequired() {
    77     if (Services.prefs.prefHasUserValue(PREF_SYNTHETIC_EVENTS_OK) &&
    78         (Services.prefs.getPrefType(PREF_SYNTHETIC_EVENTS_OK) ===
    79          Ci.nsIPrefBranch.PREF_BOOL)) {
    80       return !Services.prefs.getBoolPref(PREF_SYNTHETIC_EVENTS_OK);
    81     }
    82     return true;
    83   },
    85   reportErrors: function(message) {
    86     let onerror = function() {};
    87     if (this._rpWatcher && this._rpWatcher.onerror) {
    88       onerror = this._rpWatcher.onerror;
    89     }
    91     message.errors.forEach((error) => {
    92       // Report an error string to content
    93       Cu.reportError(ERRORS[error]);
    95       // Report error code to RP callback, if available
    96       onerror(error);
    97     });
    98   },
   100   /**
   101    * Relying Party (RP) APIs
   102    */
   104   watch: function nsDOMIdentity_watch(aOptions = {}) {
   105     if (this._rpWatcher) {
   106       // For the initial release of Firefox Accounts, we support callers who
   107       // invoke watch() either for Firefox Accounts, or Persona, but not both.
   108       // In the future, we may wish to support the dual invocation (say, for
   109       // packaged apps so they can sign users in who reject the app's request
   110       // to sign in with their Firefox Accounts identity).
   111       throw new Error("navigator.id.watch was already called");
   112     }
   114     assertCorrectCallbacks(aOptions);
   116     let message = this.DOMIdentityMessage(aOptions);
   118     // loggedInUser vs loggedInEmail
   119     // https://developer.mozilla.org/en-US/docs/DOM/navigator.id.watch
   120     // This parameter, loggedInUser, was renamed from loggedInEmail in early
   121     // September, 2012. Both names will continue to work for the time being,
   122     // but code should be changed to use loggedInUser instead.
   123     checkRenamed(aOptions, "loggedInEmail", "loggedInUser");
   124     message["loggedInUser"] = aOptions["loggedInUser"];
   126     let emailType = typeof(aOptions["loggedInUser"]);
   127     if (aOptions["loggedInUser"] && aOptions["loggedInUser"] !== "undefined") {
   128       if (emailType !== "string") {
   129         throw new Error("loggedInUser must be a String or null");
   130       }
   132       // TODO: Bug 767610 - check email format.
   133       // See HTMLInputElement::IsValidEmailAddress
   134       if (aOptions["loggedInUser"].indexOf("@") == -1
   135           || aOptions["loggedInUser"].length > MAX_STRING_LENGTH) {
   136         throw new Error("loggedInUser is not valid");
   137       }
   138       // Set loggedInUser in this block that "undefined" doesn't get through.
   139       message.loggedInUser = aOptions.loggedInUser;
   140     }
   141     this._log("loggedInUser: " + message.loggedInUser);
   143     this._rpWatcher = aOptions;
   144     this._rpWatcher.audience = message.audience;
   146     if (message.errors.length) {
   147       this.reportErrors(message);
   148       // We don't delete the rpWatcher object, because we don't want the
   149       // broken client to be able to call watch() any more.  It's broken.
   150       return;
   151     }
   152     this._identityInternal._mm.sendAsyncMessage("Identity:RP:Watch", message);
   153   },
   155   request: function nsDOMIdentity_request(aOptions = {}) {
   156     this._log("request: " + JSON.stringify(aOptions));
   158     // Has the caller called watch() before this?
   159     if (!this._rpWatcher) {
   160       throw new Error("navigator.id.request called before navigator.id.watch");
   161     }
   162     if (this._rpCalls > MAX_RP_CALLS) {
   163       throw new Error("navigator.id.request called too many times");
   164     }
   166     let util = this._window.QueryInterface(Ci.nsIInterfaceRequestor)
   167                            .getInterface(Ci.nsIDOMWindowUtils);
   169     let message = this.DOMIdentityMessage(aOptions);
   171     // We permit calling of request() outside of a user input handler only when
   172     // a certified or privileged app is calling, or when we are handling the
   173     // (deprecated) get() or getVerifiedEmail() calls, which make use of an RP
   174     // context marked as _internal.
   176     if (!aOptions._internal &&
   177         this._appStatus !== Ci.nsIPrincipal.APP_STATUS_CERTIFIED &&
   178         this._appStatus !== Ci.nsIPrincipal.APP_STATUS_PRIVILEGED) {
   180       // If the caller is not special in one of those ways, see if the user has
   181       // preffed on 'syntheticEventsOk' (useful for testing); otherwise, if
   182       // this is a non-native event, reject it.
   183       let util = this._window.QueryInterface(Ci.nsIInterfaceRequestor)
   184                              .getInterface(Ci.nsIDOMWindowUtils);
   186       if (!util.isHandlingUserInput && this.nativeEventsRequired) {
   187         message.errors.push("ERROR_REQUEST_WHILE_NOT_HANDLING_USER_INPUT");
   188       }
   189     }
   191     // Report and fail hard on any errors.
   192     if (message.errors.length) {
   193       this.reportErrors(message);
   194       return;
   195     }
   197     if (aOptions) {
   198       // Optional string properties
   199       let optionalStringProps = ["privacyPolicy", "termsOfService"];
   200       for (let propName of optionalStringProps) {
   201         if (!aOptions[propName] || aOptions[propName] === "undefined")
   202           continue;
   203         if (typeof(aOptions[propName]) !== "string") {
   204           throw new Error(propName + " must be a string representing a URL.");
   205         }
   206         if (aOptions[propName].length > MAX_STRING_LENGTH) {
   207           throw new Error(propName + " is invalid.");
   208         }
   209         message[propName] = aOptions[propName];
   210       }
   212       if (aOptions["oncancel"]
   213             && typeof(aOptions["oncancel"]) !== "function") {
   214         throw new Error("oncancel is not a function");
   215       } else {
   216         // Store optional cancel callback for later.
   217         this._onCancelRequestCallback = aOptions.oncancel;
   218       }
   219     }
   221     this._rpCalls++;
   222     this._identityInternal._mm.sendAsyncMessage("Identity:RP:Request", message);
   223   },
   225   logout: function nsDOMIdentity_logout() {
   226     if (!this._rpWatcher) {
   227       throw new Error("navigator.id.logout called before navigator.id.watch");
   228     }
   229     if (this._rpCalls > MAX_RP_CALLS) {
   230       throw new Error("navigator.id.logout called too many times");
   231     }
   233     this._rpCalls++;
   234     let message = this.DOMIdentityMessage();
   236     // Report and fail hard on any errors.
   237     if (message.errors.length) {
   238       this.reportErrors(message);
   239       return;
   240     }
   242     this._identityInternal._mm.sendAsyncMessage("Identity:RP:Logout", message);
   243   },
   245   /*
   246    * Get an assertion.  This function is deprecated.  RPs are
   247    * encouraged to use the observer API instead (watch + request).
   248    */
   249   get: function nsDOMIdentity_get(aCallback, aOptions) {
   250     var opts = {};
   251     aOptions = aOptions || {};
   253     // We use the observer API (watch + request) to implement get().
   254     // Because the caller can call get() and getVerifiedEmail() as
   255     // many times as they want, we lift the restriction that watch() can
   256     // only be called once.
   257     this._rpWatcher = null;
   259     // This flag tells internal_api.js (in the shim) to record in the
   260     // login parameters whether the assertion was acquired silently or
   261     // with user interaction.
   262     opts._internal = true;
   264     opts.privacyPolicy = aOptions.privacyPolicy || undefined;
   265     opts.termsOfService = aOptions.termsOfService || undefined;
   266     opts.privacyURL = aOptions.privacyURL || undefined;
   267     opts.tosURL = aOptions.tosURL || undefined;
   268     opts.siteName = aOptions.siteName || undefined;
   269     opts.siteLogo = aOptions.siteLogo || undefined;
   271     opts.oncancel = function get_oncancel() {
   272       if (aCallback) {
   273         aCallback(null);
   274         aCallback = null;
   275       }
   276     };
   278     if (checkDeprecated(aOptions, "silent")) {
   279       // Silent has been deprecated, do nothing. Placing the check here
   280       // prevents the callback from being called twice, once with null and
   281       // once after internalWatch has been called. See issue #1532:
   282       // https://github.com/mozilla/browserid/issues/1532
   283       if (aCallback) {
   284         setTimeout(function() { aCallback(null); }, 0);
   285       }
   286       return;
   287     }
   289     // Get an assertion by using our observer api: watch + request.
   290     var self = this;
   291     this.watch({
   292       _internal: true,
   293       onlogin: function get_onlogin(assertion, internalParams) {
   294         if (assertion && aCallback && internalParams && !internalParams.silent) {
   295           aCallback(assertion);
   296           aCallback = null;
   297         }
   298       },
   299       onlogout: function get_onlogout() {},
   300       onready: function get_onready() {
   301         self.request(opts);
   302       }
   303     });
   304   },
   306   getVerifiedEmail: function nsDOMIdentity_getVerifiedEmail(aCallback) {
   307     Cu.reportError("WARNING: getVerifiedEmail has been deprecated");
   308     this.get(aCallback, {});
   309   },
   311   /**
   312    *  Identity Provider (IDP) Provisioning APIs
   313    */
   315   beginProvisioning: function nsDOMIdentity_beginProvisioning(aCallback) {
   316     this._log("beginProvisioning");
   317     if (this._beginProvisioningCallback) {
   318       throw new Error("navigator.id.beginProvisioning already called.");
   319     }
   320     if (!aCallback || typeof(aCallback) !== "function") {
   321       throw new Error("beginProvisioning callback is required.");
   322     }
   324     this._beginProvisioningCallback = aCallback;
   325     this._identityInternal._mm.sendAsyncMessage("Identity:IDP:BeginProvisioning",
   326                                                 this.DOMIdentityMessage());
   327   },
   329   genKeyPair: function nsDOMIdentity_genKeyPair(aCallback) {
   330     this._log("genKeyPair");
   331     if (!this._beginProvisioningCallback) {
   332       throw new Error("navigator.id.genKeyPair called outside of provisioning");
   333     }
   334     if (this._genKeyPairCallback) {
   335       throw new Error("navigator.id.genKeyPair already called.");
   336     }
   337     if (!aCallback || typeof(aCallback) !== "function") {
   338       throw new Error("genKeyPair callback is required.");
   339     }
   341     this._genKeyPairCallback = aCallback;
   342     this._identityInternal._mm.sendAsyncMessage("Identity:IDP:GenKeyPair",
   343                                                 this.DOMIdentityMessage());
   344   },
   346   registerCertificate: function nsDOMIdentity_registerCertificate(aCertificate) {
   347     this._log("registerCertificate");
   348     if (!this._genKeyPairCallback) {
   349       throw new Error("navigator.id.registerCertificate called outside of provisioning");
   350     }
   351     if (this._provisioningEnded) {
   352       throw new Error("Provisioning already ended");
   353     }
   354     this._provisioningEnded = true;
   356     let message = this.DOMIdentityMessage();
   357     message.cert = aCertificate;
   358     this._identityInternal._mm.sendAsyncMessage("Identity:IDP:RegisterCertificate", message);
   359   },
   361   raiseProvisioningFailure: function nsDOMIdentity_raiseProvisioningFailure(aReason) {
   362     this._log("raiseProvisioningFailure '" + aReason + "'");
   363     if (this._provisioningEnded) {
   364       throw new Error("Provisioning already ended");
   365     }
   366     if (!aReason || typeof(aReason) != "string") {
   367       throw new Error("raiseProvisioningFailure reason is required");
   368     }
   369     this._provisioningEnded = true;
   371     let message = this.DOMIdentityMessage();
   372     message.reason = aReason;
   373     this._identityInternal._mm.sendAsyncMessage("Identity:IDP:ProvisioningFailure", message);
   374   },
   376   /**
   377    *  Identity Provider (IDP) Authentication APIs
   378    */
   380   beginAuthentication: function nsDOMIdentity_beginAuthentication(aCallback) {
   381     this._log("beginAuthentication");
   382     if (this._beginAuthenticationCallback) {
   383       throw new Error("navigator.id.beginAuthentication already called.");
   384     }
   385     if (typeof(aCallback) !== "function") {
   386       throw new Error("beginAuthentication callback is required.");
   387     }
   388     if (!aCallback || typeof(aCallback) !== "function") {
   389       throw new Error("beginAuthentication callback is required.");
   390     }
   392     this._beginAuthenticationCallback = aCallback;
   393     this._identityInternal._mm.sendAsyncMessage("Identity:IDP:BeginAuthentication",
   394                                                 this.DOMIdentityMessage());
   395   },
   397   completeAuthentication: function nsDOMIdentity_completeAuthentication() {
   398     if (this._authenticationEnded) {
   399       throw new Error("Authentication already ended");
   400     }
   401     if (!this._beginAuthenticationCallback) {
   402       throw new Error("navigator.id.completeAuthentication called outside of authentication");
   403     }
   404     this._authenticationEnded = true;
   406     this._identityInternal._mm.sendAsyncMessage("Identity:IDP:CompleteAuthentication",
   407                                                 this.DOMIdentityMessage());
   408   },
   410   raiseAuthenticationFailure: function nsDOMIdentity_raiseAuthenticationFailure(aReason) {
   411     if (this._authenticationEnded) {
   412       throw new Error("Authentication already ended");
   413     }
   414     if (!aReason || typeof(aReason) != "string") {
   415       throw new Error("raiseProvisioningFailure reason is required");
   416     }
   418     let message = this.DOMIdentityMessage();
   419     message.reason = aReason;
   420     this._identityInternal._mm.sendAsyncMessage("Identity:IDP:AuthenticationFailure", message);
   421   },
   423   // Private.
   424   _init: function nsDOMIdentity__init(aWindow) {
   426     this._initializeState();
   428     // Store window and origin URI.
   429     this._window = aWindow;
   430     this._origin = aWindow.document.nodePrincipal.origin;
   431     this._appStatus = aWindow.document.nodePrincipal.appStatus;
   432     this._appId = aWindow.document.nodePrincipal.appId;
   434     // Setup identifiers for current window.
   435     let util = aWindow.QueryInterface(Ci.nsIInterfaceRequestor)
   436                       .getInterface(Ci.nsIDOMWindowUtils);
   438     // We need to inherit the id from the internalIdentity service.
   439     // See comments below in that service's init.
   440     this._id = this._identityInternal._id;
   441   },
   443   /**
   444    * Called during init and shutdown.
   445    */
   446   _initializeState: function nsDOMIdentity__initializeState() {
   447     // Some state to prevent abuse
   448     // Limit the number of calls to .request
   449     this._rpCalls = 0;
   450     this._provisioningEnded = false;
   451     this._authenticationEnded = false;
   453     this._rpWatcher = null;
   454     this._onCancelRequestCallback = null;
   455     this._beginProvisioningCallback = null;
   456     this._genKeyPairCallback = null;
   457     this._beginAuthenticationCallback = null;
   458   },
   460   _receiveMessage: function nsDOMIdentity_receiveMessage(aMessage) {
   461     let msg = aMessage.json;
   463     switch (aMessage.name) {
   464       case "Identity:ResetState":
   465         if (!this._identityInternal._debug) {
   466           return;
   467         }
   468         this._initializeState();
   469         Services.obs.notifyObservers(null, "identity-DOM-state-reset", this._id);
   470         break;
   471       case "Identity:RP:Watch:OnLogin":
   472         // Do we have a watcher?
   473         if (!this._rpWatcher) {
   474           this._log("WARNING: Received OnLogin message, but there is no RP watcher");
   475           return;
   476         }
   478         if (this._rpWatcher.onlogin) {
   479           if (this._rpWatcher._internal) {
   480             this._rpWatcher.onlogin(msg.assertion, msg._internalParams);
   481           } else {
   482             this._rpWatcher.onlogin(msg.assertion);
   483           }
   484         }
   485         break;
   486       case "Identity:RP:Watch:OnLogout":
   487         // Do we have a watcher?
   488         if (!this._rpWatcher) {
   489           this._log("WARNING: Received OnLogout message, but there is no RP watcher");
   490           return;
   491         }
   493         if (this._rpWatcher.onlogout) {
   494           this._rpWatcher.onlogout();
   495         }
   496         break;
   497       case "Identity:RP:Watch:OnReady":
   498         // Do we have a watcher?
   499         if (!this._rpWatcher) {
   500           this._log("WARNING: Received OnReady message, but there is no RP watcher");
   501           return;
   502         }
   504         if (this._rpWatcher.onready) {
   505           this._rpWatcher.onready();
   506         }
   507         break;
   508       case "Identity:RP:Watch:OnCancel":
   509         // Do we have a watcher?
   510         if (!this._rpWatcher) {
   511           this._log("WARNING: Received OnCancel message, but there is no RP watcher");
   512           return;
   513         }
   515         if (this._onCancelRequestCallback) {
   516           this._onCancelRequestCallback();
   517         }
   518         break;
   519       case "Identity:RP:Watch:OnError":
   520         if (!this._rpWatcher) {
   521           this._log("WARNING: Received OnError message, but there is no RP watcher");
   522           return;
   523         }
   525         if (this._rpWatcher.onerror) {
   526           this._rpWatcher.onerror(JSON.stringify({name: msg.message.error}));
   527         }
   528         break;
   529       case "Identity:IDP:CallBeginProvisioningCallback":
   530         this._callBeginProvisioningCallback(msg);
   531         break;
   532       case "Identity:IDP:CallGenKeyPairCallback":
   533         this._callGenKeyPairCallback(msg);
   534         break;
   535       case "Identity:IDP:CallBeginAuthenticationCallback":
   536         this._callBeginAuthenticationCallback(msg);
   537         break;
   538     }
   539   },
   541   _log: function nsDOMIdentity__log(msg) {
   542     this._identityInternal._log(msg);
   543   },
   545   _callGenKeyPairCallback: function nsDOMIdentity__callGenKeyPairCallback(message) {
   546     // create a pubkey object that works
   547     let chrome_pubkey = JSON.parse(message.publicKey);
   549     // bunch of stuff to create a proper object in window context
   550     function genPropDesc(value) {
   551       return {
   552         enumerable: true, configurable: true, writable: true, value: value
   553       };
   554     }
   556     let propList = {};
   557     for (let k in chrome_pubkey) {
   558       propList[k] = genPropDesc(chrome_pubkey[k]);
   559     }
   561     let pubkey = Cu.createObjectIn(this._window);
   562     Object.defineProperties(pubkey, propList);
   563     Cu.makeObjectPropsNormal(pubkey);
   565     // do the callback
   566     this._genKeyPairCallback(pubkey);
   567   },
   569   _callBeginProvisioningCallback:
   570       function nsDOMIdentity__callBeginProvisioningCallback(message) {
   571     let identity = message.identity;
   572     let certValidityDuration = message.certDuration;
   573     this._beginProvisioningCallback(identity,
   574                                     certValidityDuration);
   575   },
   577   _callBeginAuthenticationCallback:
   578       function nsDOMIdentity__callBeginAuthenticationCallback(message) {
   579     let identity = message.identity;
   580     this._beginAuthenticationCallback(identity);
   581   },
   583   /**
   584    * Helper to create messages to send using a message manager.
   585    * Pass through user options if they are not functions.  Always
   586    * overwrite id, origin, audience, and appStatus.  The caller
   587    * does not get to set those.
   588    */
   589   DOMIdentityMessage: function DOMIdentityMessage(aOptions) {
   590     aOptions = aOptions || {};
   591     let message = {
   592       errors: []
   593     };
   594     let principal = Ci.nsIPrincipal;
   596     objectCopy(aOptions, message);
   598     // outer window id
   599     message.id = this._id;
   601     // window origin
   602     message.origin = this._origin;
   604     // On b2g, an app's status can be NOT_INSTALLED, INSTALLED, PRIVILEGED, or
   605     // CERTIFIED.  Compare the appStatus value to the constants enumerated in
   606     // Ci.nsIPrincipal.APP_STATUS_*.
   607     message.appStatus = this._appStatus;
   609     // Currently, we only permit certified and privileged apps to use
   610     // Firefox Accounts.
   611     if (aOptions.wantIssuer == "firefox-accounts" &&
   612         this._appStatus !== principal.APP_STATUS_PRIVILEGED &&
   613         this._appStatus !== principal.APP_STATUS_CERTIFIED) {
   614       message.errors.push("ERROR_NOT_AUTHORIZED_FOR_FIREFOX_ACCOUNTS");
   615     }
   617     // Normally the window origin will be the audience in assertions.  On b2g,
   618     // certified apps have the power to override this and declare any audience
   619     // the want.  Privileged apps can also declare a different audience, as
   620     // long as it is the same as the origin specified in their manifest files.
   621     // All other apps are stuck with b2g origins of the form app://{guid}.
   622     // Since such an origin is meaningless for the purposes of verification,
   623     // they will have to jump through some hoops to sign in: Specifically, they
   624     // will have to host their sign-in flows and DOM API requests in an iframe,
   625     // have the iframe xhr post assertions up to their server for verification,
   626     // and then post-message the results down to their app.
   627     let _audience = message.origin;
   628     if (message.audience && message.audience != message.origin) {
   629       if (this._appStatus === principal.APP_STATUS_CERTIFIED) {
   630         _audience = message.audience;
   631         this._log("Certified app setting assertion audience: " + _audience);
   632       } else {
   633         message.errors.push("ERROR_INVALID_ASSERTION_AUDIENCE");
   634       }
   635     }
   637     // Replace any audience supplied by the RP with one that has been sanitised
   638     message.audience = _audience;
   640     this._log("DOMIdentityMessage: " + JSON.stringify(message));
   642     return message;
   643   },
   645   uninit: function DOMIdentity_uninit() {
   646     this._log("nsDOMIdentity uninit() " + this._id);
   647     this._identityInternal._mm.sendAsyncMessage(
   648       "Identity:RP:Unwatch",
   649       { id: this._id }
   650     );
   651   }
   653 };
   655 /**
   656  * Internal functions that shouldn't be exposed to content.
   657  */
   658 function nsDOMIdentityInternal() {
   659 }
   660 nsDOMIdentityInternal.prototype = {
   662   // nsIMessageListener
   663   receiveMessage: function nsDOMIdentityInternal_receiveMessage(aMessage) {
   664     let msg = aMessage.json;
   665     // Is this message intended for this window?
   666     if (msg.id != this._id) {
   667       return;
   668     }
   669     this._identity._receiveMessage(aMessage);
   670   },
   672   // nsIObserver
   673   observe: function nsDOMIdentityInternal_observe(aSubject, aTopic, aData) {
   674     let wId = aSubject.QueryInterface(Ci.nsISupportsPRUint64).data;
   675     if (wId != this._innerWindowID) {
   676       return;
   677     }
   679     this._identity.uninit();
   681     Services.obs.removeObserver(this, "inner-window-destroyed");
   682     this._identity._initializeState();
   683     this._identity = null;
   685     // TODO: Also send message to DOMIdentity notifiying window is no longer valid
   686     // ie. in the case that the user closes the auth. window and we need to know.
   688     try {
   689       for (let msgName of this._messages) {
   690         this._mm.removeMessageListener(msgName, this);
   691       }
   692     } catch (ex) {
   693       // Avoid errors when removing more than once.
   694     }
   696     this._mm = null;
   697   },
   699   // nsIDOMGlobalPropertyInitializer
   700   init: function nsDOMIdentityInternal_init(aWindow) {
   701     if (Services.prefs.getPrefType(PREF_ENABLED) != Ci.nsIPrefBranch.PREF_BOOL
   702         || !Services.prefs.getBoolPref(PREF_ENABLED)) {
   703       return null;
   704     }
   706     this._debug =
   707       Services.prefs.getPrefType(PREF_DEBUG) == Ci.nsIPrefBranch.PREF_BOOL
   708       && Services.prefs.getBoolPref(PREF_DEBUG);
   710     let util = aWindow.QueryInterface(Ci.nsIInterfaceRequestor)
   711                       .getInterface(Ci.nsIDOMWindowUtils);
   713     // To avoid cross-process windowId collisions, use a uuid as an
   714     // almost certainly unique identifier.
   715     //
   716     // XXX Bug 869182 - use a combination of child process id and
   717     // innerwindow id to construct the unique id.
   718     this._id = uuidgen.generateUUID().toString();
   719     this._innerWindowID = util.currentInnerWindowID;
   721     // nsDOMIdentity needs to know our _id, so this goes after
   722     // its creation.
   723     this._identity = new nsDOMIdentity(this);
   724     this._identity._init(aWindow);
   726     this._log("init was called from " + aWindow.document.location);
   728     this._mm = cpmm;
   730     // Setup listeners for messages from parent process.
   731     this._messages = [
   732       "Identity:ResetState",
   733       "Identity:RP:Watch:OnLogin",
   734       "Identity:RP:Watch:OnLogout",
   735       "Identity:RP:Watch:OnReady",
   736       "Identity:RP:Watch:OnCancel",
   737       "Identity:RP:Watch:OnError",
   738       "Identity:IDP:CallBeginProvisioningCallback",
   739       "Identity:IDP:CallGenKeyPairCallback",
   740       "Identity:IDP:CallBeginAuthenticationCallback"
   741     ];
   742     this._messages.forEach(function(msgName) {
   743       this._mm.addMessageListener(msgName, this);
   744     }, this);
   746     // Setup observers so we can remove message listeners.
   747     Services.obs.addObserver(this, "inner-window-destroyed", false);
   749     return this._identity;
   750   },
   752   // Private.
   753   _log: function nsDOMIdentityInternal__log(msg) {
   754     if (!this._debug) {
   755       return;
   756     }
   757     dump("nsDOMIdentity (" + this._id + "): " + msg + "\n");
   758   },
   760   // Component setup.
   761   classID: Components.ID("{210853d9-2c97-4669-9761-b1ab9cbf57ef}"),
   763   QueryInterface: XPCOMUtils.generateQI(
   764     [Ci.nsIDOMGlobalPropertyInitializer, Ci.nsIMessageListener]
   765   ),
   767   classInfo: XPCOMUtils.generateCI({
   768     classID: Components.ID("{210853d9-2c97-4669-9761-b1ab9cbf57ef}"),
   769     contractID: "@mozilla.org/dom/identity;1",
   770     interfaces: [],
   771     classDescription: "Identity DOM Implementation"
   772   })
   773 };
   775 function assertCorrectCallbacks(aOptions) {
   776   // The relying party (RP) provides callbacks on watch().
   777   //
   778   // In the future, BrowserID will probably only require an onlogin()
   779   // callback, lifting the requirement that BrowserID handle logged-in
   780   // state management for RPs.  See
   781   // https://github.com/mozilla/id-specs/blob/greenfield/browserid/api-rp.md
   782   //
   783   // However, Firefox Accounts requires callers to provide onlogout(),
   784   // onready(), and also supports an onerror() callback.
   786   let requiredCallbacks = ["onlogin"];
   787   let optionalCallbacks = ["onlogout", "onready", "onerror"];
   789   if (aOptions.wantIssuer == "firefox-accounts") {
   790     requiredCallbacks = ["onlogin", "onlogout", "onready"];
   791     optionalCallbacks = ["onerror"];
   792   }
   794   for (let cbName of requiredCallbacks) {
   795     if (typeof(aOptions[cbName]) != "function") {
   796       throw new Error(cbName + " callback is required.");
   797     }
   798   }
   800   for (let cbName of optionalCallbacks) {
   801     if (aOptions[cbName] && typeof(aOptions[cbName]) != "function") {
   802       throw new Error(cbName + " must be a function");
   803     }
   804   }
   805 }
   807 this.NSGetFactory = XPCOMUtils.generateNSGetFactory([nsDOMIdentityInternal]);

mercurial