Bug 790141 - Native implementation of browserid get() and getVerifiedEmail(). r=benadida
authorJed Parsons <jparsons@mozilla.com>
Tue, 20 Nov 2012 20:28:34 -0500
changeset 113852 0b37a6293086382db3ad3e95e0cb4b8d3380c471
parent 113851 71f90d16059d7fb67aad1d992c7857614d528e58
child 113853 0f76932d28c58881dd87455b031b71e90770c7b1
push id23891
push useremorley@mozilla.com
push dateWed, 21 Nov 2012 15:30:36 +0000
treeherdermozilla-central@905492e644e3 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbenadida
bugs790141
milestone20.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 790141 - Native implementation of browserid get() and getVerifiedEmail(). r=benadida
b2g/chrome/content/identity.js
b2g/components/SignInToWebsite.jsm
dom/identity/DOMIdentity.jsm
dom/identity/nsDOMIdentity.js
toolkit/identity/MinimalIdentity.jsm
--- a/b2g/chrome/content/identity.js
+++ b/b2g/chrome/content/identity.js
@@ -50,16 +50,19 @@ var func = null;
 /*
  * Message back to the SignInToWebsite pipe.  Message should be an
  * object with the following keys:
  *
  *   method:             one of 'login', 'logout', 'ready'
  *   assertion:          optional assertion
  */
 function identityCall(message) {
+  if (options._internal) {
+    message._internal = options._internal;
+  }
   sendAsyncMessage(kIdentityControllerDoMethod, message);
 }
 
 /*
  * To close the dialog, we first tell the gecko SignInToWebsite manager that it
  * can clean up.  Then we tell the gaia component that we are finished.  It is
  * necessary to notify gecko first, so that the message can be sent before gaia
  * destroys our context.
@@ -73,38 +76,42 @@ function closeIdentityDialog() {
 /*
  * doInternalWatch - call the internal.watch api and relay the results
  * up to the controller.
  */
 function doInternalWatch() {
   log("doInternalWatch:", options, isLoaded);
   if (options && isLoaded) {
     let BrowserID = content.wrappedJSObject.BrowserID;
-    BrowserID.internal.watch(function(aParams) {
+    BrowserID.internal.watch(function(aParams, aInternalParams) {
         identityCall(aParams);
         if (aParams.method === "ready") {
           closeIdentityDialog();
         }
       },
       JSON.stringify(options),
       function(...things) {
-        log("internal: ", things);
+        log("(watch) internal: ", things);
       }
     );
   }
 }
 
 function doInternalRequest() {
   log("doInternalRequest:", options && isLoaded);
   if (options && isLoaded) {
     content.wrappedJSObject.BrowserID.internal.get(
       options.origin,
-      function(assertion) {
+      function(assertion, internalParams) {
+        internalParams = internalParams || {};
         if (assertion) {
-          identityCall({method: 'login', assertion: assertion});
+          identityCall({
+            method: 'login',
+            assertion: assertion,
+            _internalParams: internalParams});
         }
         closeIdentityDialog();
       },
       options);
   }
 }
 
 function doInternalLogout(aOptions) {
--- a/b2g/components/SignInToWebsite.jsm
+++ b/b2g/components/SignInToWebsite.jsm
@@ -300,17 +300,21 @@ this.SignInToWebsiteController = {
       }
 
       switch(message.method) {
         case "ready":
           IdentityService.doReady(aRpId);
           break;
 
         case "login":
-           IdentityService.doLogin(aRpId, message.assertion);
+           if (message._internalParams) {
+             IdentityService.doLogin(aRpId, message.assertion, message._internalParams);
+           } else {
+             IdentityService.doLogin(aRpId, message.assertion);
+           }
           break;
 
         case "logout":
           IdentityService.doLogout(aRpId);
           break;
 
         default:
           log("WARNING: wonky method call:", message.method);
--- a/dom/identity/DOMIdentity.jsm
+++ b/dom/identity/DOMIdentity.jsm
@@ -94,24 +94,29 @@ function RPWatchContext(aOptions, aTarge
   // id and origin are required
   if (! (this.id && this.origin)) {
     throw new Error("id and origin are required for RP watch context");
   }
 
   // default for no loggedInUser is undefined, not null
   this.loggedInUser = aOptions.loggedInUser;
 
+  // Maybe internal
+  this._internal = aOptions._internal;
+
   this._mm = aTargetMM;
 }
 
 RPWatchContext.prototype = {
-  doLogin: function RPWatchContext_onlogin(aAssertion) {
+  doLogin: function RPWatchContext_onlogin(aAssertion, aMaybeInternalParams) {
     log("doLogin: " + this.id);
-    let message = new IDDOMMessage({id: this.id});
-    message.assertion = aAssertion;
+    let message = new IDDOMMessage({id: this.id, assertion: aAssertion});
+    if (aMaybeInternalParams) {
+      message._internalParams = aMaybeInternalParams;
+    }
     this._mm.sendAsyncMessage("Identity:RP:Watch:OnLogin", message);
   },
 
   doLogout: function RPWatchContext_onlogout() {
     log("doLogout: " + this.id);
     let message = new IDDOMMessage({id: this.id});
     this._mm.sendAsyncMessage("Identity:RP:Watch:OnLogout", message);
   },
--- a/dom/identity/nsDOMIdentity.js
+++ b/dom/identity/nsDOMIdentity.js
@@ -27,16 +27,18 @@ function nsDOMIdentity(aIdentityInternal
   this._identityInternal = aIdentityInternal;
 }
 nsDOMIdentity.prototype = {
   __exposedProps__: {
     // Relying Party (RP)
     watch: 'r',
     request: 'r',
     logout: 'r',
+    get: 'r',
+    getVerifiedEmail: 'r',
 
     // Provisioning
     beginProvisioning: 'r',
     genKeyPair: 'r',
     registerCertificate: 'r',
     raiseProvisioningFailure: 'r',
 
     // Authentication
@@ -46,17 +48,16 @@ nsDOMIdentity.prototype = {
   },
 
   // nsIDOMIdentity
   /**
    * Relying Party (RP) APIs
    */
 
   watch: function nsDOMIdentity_watch(aOptions) {
-    this._log("watch");
     if (this._rpWatcher) {
       throw new Error("navigator.id.watch was already called");
     }
 
     if (!aOptions || typeof(aOptions) !== "object") {
       throw new Error("options argument to watch is required");
     }
 
@@ -105,18 +106,21 @@ nsDOMIdentity.prototype = {
     this._rpWatcher = aOptions;
     this._identityInternal._mm.sendAsyncMessage("Identity:RP:Watch", message);
   },
 
   request: function nsDOMIdentity_request(aOptions) {
     let util = this._window.QueryInterface(Ci.nsIInterfaceRequestor)
                            .getInterface(Ci.nsIDOMWindowUtils);
 
-    // Do not allow call of request() outside of a user input handler.
-    if (!util.isHandlingUserInput) {
+    // The only time we permit calling of request() outside of a user
+    // input handler is when we are handling the (deprecated) get() or
+    // getVerifiedEmail() calls, which make use of an RP context
+    // marked as _internal.
+    if (!util.isHandlingUserInput && !aOptions._internal) {
       return;
     }
 
     // Has the caller called watch() before this?
     if (!this._rpWatcher) {
       throw new Error("navigator.id.request called before navigator.id.watch");
     }
     if (this._rpCalls > MAX_RP_CALLS) {
@@ -161,16 +165,80 @@ nsDOMIdentity.prototype = {
       throw new Error("navigator.id.logout called too many times");
     }
 
     this._rpCalls++;
     let message = this.DOMIdentityMessage();
     this._identityInternal._mm.sendAsyncMessage("Identity:RP:Logout", message);
   },
 
+  /*
+   * Get an assertion.  This function is deprecated.  RPs are
+   * encouraged to use the observer API instead (watch + request).
+   */
+  get: function nsDOMIdentity_get(aCallback, aOptions) {
+    var opts = {};
+    aOptions = aOptions || {};
+
+    // We use the observer API (watch + request) to implement get().
+    // Because the caller can call get() and getVerifiedEmail() as
+    // many times as they want, we lift the restriction that watch() can
+    // only be called once.
+    this._rpWatcher = null;
+
+    // This flag tells internal_api.js (in the shim) to record in the
+    // login parameters whether the assertion was acquired silently or
+    // with user interaction.
+    opts._internal = true;
+
+    opts.privacyPolicy = aOptions.privacyPolicy || undefined;
+    opts.termsOfService = aOptions.termsOfService || undefined;
+    opts.privacyURL = aOptions.privacyURL || undefined;
+    opts.tosURL = aOptions.tosURL || undefined;
+    opts.siteName = aOptions.siteName || undefined;
+    opts.siteLogo = aOptions.siteLogo || undefined;
+
+    if (checkDeprecated(aOptions, "silent")) {
+      // Silent has been deprecated, do nothing. Placing the check here
+      // prevents the callback from being called twice, once with null and
+      // once after internalWatch has been called. See issue #1532:
+      // https://github.com/mozilla/browserid/issues/1532
+      if (aCallback) {
+        setTimeout(function() { aCallback(null); }, 0);
+      }
+      return;
+    }
+
+    // Get an assertion by using our observer api: watch + request.
+    var self = this;
+    this.watch({
+      oncancel: function get_oncancel() {
+        if (aCallback) {
+          aCallback(null);
+          aCallback = null;
+        }
+      },
+      onlogin: function get_onlogin(assertion, internalParams) {
+        if (assertion && aCallback && internalParams && !internalParams.silent) {
+          aCallback(assertion);
+          aCallback = null;
+        }
+      },
+      onlogout: function get_onlogout() {},
+      onready: function get_onready() {
+        self.request(opts);
+      }
+    });
+  },
+
+  getVerifiedEmail: function nsDOMIdentity_getVerifiedEmail(aCallback) {
+    Cu.reportError("WARNING: getVerifiedEmail has been deprecated");
+    this.get(aCallback, {});
+  },
+
   /**
    *  Identity Provider (IDP) Provisioning APIs
    */
 
   beginProvisioning: function nsDOMIdentity_beginProvisioning(aCallback) {
     this._log("beginProvisioning");
     if (this._beginProvisioningCallback) {
       throw new Error("navigator.id.beginProvisioning already called.");
@@ -319,46 +387,54 @@ nsDOMIdentity.prototype = {
           return;
         }
         this._initializeState();
         Services.obs.notifyObservers(null, "identity-DOM-state-reset", this._id);
         break;
       case "Identity:RP:Watch:OnLogin":
         // Do we have a watcher?
         if (!this._rpWatcher) {
+          dump("WARNING: Received OnLogin message, but there is no RP watcher\n");
           return;
         }
 
         if (this._rpWatcher.onlogin) {
-          this._rpWatcher.onlogin(msg.assertion);
+          if (this._rpWatcher._internal) {
+            this._rpWatcher.onlogin(msg.assertion, msg._internalParams);
+          } else {
+            this._rpWatcher.onlogin(msg.assertion);
+          }
         }
         break;
       case "Identity:RP:Watch:OnLogout":
         // Do we have a watcher?
         if (!this._rpWatcher) {
+          dump("WARNING: Received OnLogout message, but there is no RP watcher\n");
           return;
         }
 
         if (this._rpWatcher.onlogout) {
           this._rpWatcher.onlogout();
         }
         break;
       case "Identity:RP:Watch:OnReady":
         // Do we have a watcher?
         if (!this._rpWatcher) {
+          dump("WARNING: Received OnReady message, but there is no RP watcher\n");
           return;
         }
 
         if (this._rpWatcher.onready) {
           this._rpWatcher.onready();
         }
         break;
       case "Identity:RP:Request:OnCancel":
         // Do we have a watcher?
         if (!this._rpWatcher) {
+          dump("WARNING: Received OnCancel message, but there is no RP watcher\n");
           return;
         }
 
         if (this._onCancelRequestCallback) {
           this._onCancelRequestCallback();
         }
         break;
       case "Identity:IDP:CallBeginProvisioningCallback":
@@ -427,17 +503,16 @@ nsDOMIdentity.prototype = {
     objectCopy(aOptions, message);
 
     // outer window id
     message.id = this._id;
 
     // window origin
     message.origin = this._origin;
 
-    dump("nsDOM message: " + JSON.stringify(message) + "\n");
     return message;
   },
 
 };
 
 /**
  * Internal functions that shouldn't be exposed to content.
  */
--- a/toolkit/identity/MinimalIdentity.jsm
+++ b/toolkit/identity/MinimalIdentity.jsm
@@ -42,33 +42,35 @@ function makeMessageObject(aRpCaller) {
   let options = {};
 
   options.id = aRpCaller.id;
   options.origin = aRpCaller.origin;
 
   // loggedInUser can be undefined, null, or a string
   options.loggedInUser = aRpCaller.loggedInUser;
 
+  // Special flag for internal calls
+  options._internal = aRpCaller._internal;
+
   Object.keys(aRpCaller).forEach(function(option) {
     // Duplicate the callerobject, scrubbing out functions and other
     // internal variables (like _mm, the message manager object)
     if (!Object.hasOwnProperty(this, option)
         && option[0] !== '_'
         && typeof aRpCaller[option] !== 'function') {
       options[option] = aRpCaller[option];
     }
   });
 
   if (! (options.id && options.origin)) {
     let err = "id and origin required in relying-party message";
     reportError(err);
     throw new Error(err);
   }
 
-  dump("message object is: " + JSON.stringify(options) + "\n");
   return options;
 }
 
 function IDService() {
   Services.obs.addObserver(this, "quit-application-granted", false);
   // Services.obs.addObserver(this, "identity-auth-complete", false);
 
   // simplify, it's one object
@@ -123,17 +125,16 @@ IDService.prototype = {
    *                  - doLogin()
    *                  - doLogout()
    *                  - doError()
    *                  - doCancel()
    *
    */
   watch: function watch(aRpCaller) {
     // store the caller structure and notify the UI observers
-    dump("RP - watch: " + JSON.stringify(aRpCaller) + "\n");
     this._rpFlows[aRpCaller.id] = aRpCaller;
 
     let options = makeMessageObject(aRpCaller);
     log("sending identity-controller-watch:", options);
     Services.obs.notifyObservers({wrappedJSObject: options},"identity-controller-watch", null);
   },
 
   /**
@@ -172,24 +173,24 @@ IDService.prototype = {
   },
 
   /*
    * once the UI-and-display-logic components have received
    * notifications, they call back with direct invocation of the
    * following functions (doLogin, doLogout, or doReady)
    */
 
-  doLogin: function doLogin(aRpCallerId, aAssertion) {
+  doLogin: function doLogin(aRpCallerId, aAssertion, aInternalParams) {
     let rp = this._rpFlows[aRpCallerId];
     if (!rp) {
       dump("WARNING: doLogin found no rp to go with callerId " + aRpCallerId + "\n");
       return;
     }
 
-    rp.doLogin(aAssertion);
+    rp.doLogin(aAssertion, aInternalParams);
   },
 
   doLogout: function doLogout(aRpCallerId) {
     let rp = this._rpFlows[aRpCallerId];
     if (!rp) {
       dump("WARNING: doLogout found no rp to go with callerId " + aRpCallerId + "\n");
       return;
     }