toolkit/identity/IdentityProvider.jsm

Sat, 03 Jan 2015 20:18:00 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Sat, 03 Jan 2015 20:18:00 +0100
branch
TOR_BUG_3246
changeset 7
129ffea94266
permissions
-rw-r--r--

Conditionally enable double key logic according to:
private browsing mode or privacy.thirdparty.isolate preference and
implement in GetCookieStringCommon and FindCookie where it counts...
With some reservations of how to convince FindCookie users to test
condition and pass a nullptr when disabling double key logic.

     1 /* -*- Mode: js2; js2-basic-offset: 2; indent-tabs-mode: nil; -*- */
     2 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
     3 /* This Source Code Form is subject to the terms of the Mozilla Public
     4  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
     5  * You can obtain one at http://mozilla.org/MPL/2.0/. */
     7 "use strict";
     9 const Cu = Components.utils;
    10 const Ci = Components.interfaces;
    11 const Cc = Components.classes;
    12 const Cr = Components.results;
    14 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
    15 Cu.import("resource://gre/modules/Services.jsm");
    16 Cu.import("resource://gre/modules/identity/LogUtils.jsm");
    17 Cu.import("resource://gre/modules/identity/Sandbox.jsm");
    19 this.EXPORTED_SYMBOLS = ["IdentityProvider"];
    20 const FALLBACK_PROVIDER = "browserid.org";
    22 XPCOMUtils.defineLazyModuleGetter(this,
    23                                   "jwcrypto",
    24                                   "resource://gre/modules/identity/jwcrypto.jsm");
    26 function log(...aMessageArgs) {
    27   Logger.log.apply(Logger, ["IDP"].concat(aMessageArgs));
    28 }
    29 function reportError(...aMessageArgs) {
    30   Logger.reportError.apply(Logger, ["IDP"].concat(aMessageArgs));
    31 }
    34 function IdentityProviderService() {
    35   XPCOMUtils.defineLazyModuleGetter(this,
    36                                     "_store",
    37                                     "resource://gre/modules/identity/IdentityStore.jsm",
    38                                     "IdentityStore");
    40   this.reset();
    41 }
    43 IdentityProviderService.prototype = {
    44   QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports, Ci.nsIObserver]),
    45   _sandboxConfigured: false,
    47   observe: function observe(aSubject, aTopic, aData) {
    48     switch (aTopic) {
    49       case "quit-application-granted":
    50         Services.obs.removeObserver(this, "quit-application-granted");
    51         this.shutdown();
    52         break;
    53     }
    54   },
    56   reset: function IDP_reset() {
    57     // Clear the provisioning flows.  Provision flows contain an
    58     // identity, idpParams (how to reach the IdP to provision and
    59     // authenticate), a callback (a completion callback for when things
    60     // are done), and a provisioningFrame (which is the provisioning
    61     // sandbox).  Additionally, two callbacks will be attached:
    62     // beginProvisioningCallback and genKeyPairCallback.
    63     this._provisionFlows = {};
    65     // Clear the authentication flows.  Authentication flows attach
    66     // to provision flows.  In the process of provisioning an id, it
    67     // may be necessary to authenticate with an IdP.  The authentication
    68     // flow maintains the state of that authentication process.
    69     this._authenticationFlows = {};
    70   },
    72   getProvisionFlow: function getProvisionFlow(aProvId, aErrBack) {
    73     let provFlow = this._provisionFlows[aProvId];
    74     if (provFlow) {
    75       return provFlow;
    76     }
    78     let err = "No provisioning flow found with id " + aProvId;
    79     log("ERROR:", err);
    80     if (typeof aErrBack === 'function') {
    81       aErrBack(err);
    82     }
    83   },
    85   shutdown: function RP_shutdown() {
    86     this.reset();
    88     if (this._sandboxConfigured) {
    89       // Tear down message manager listening on the hidden window
    90       Cu.import("resource://gre/modules/DOMIdentity.jsm");
    91       DOMIdentity._configureMessages(Services.appShell.hiddenDOMWindow, false);
    92       this._sandboxConfigured = false;
    93     }
    95     Services.obs.removeObserver(this, "quit-application-granted");
    96   },
    98   get securityLevel() {
    99     return 1;
   100   },
   102   get certDuration() {
   103     switch(this.securityLevel) {
   104       default:
   105         return 3600;
   106     }
   107   },
   109   /**
   110    * Provision an Identity
   111    *
   112    * @param aIdentity
   113    *        (string) the email we're logging in with
   114    *
   115    * @param aIDPParams
   116    *        (object) parameters of the IdP
   117    *
   118    * @param aCallback
   119    *        (function) callback to invoke on completion
   120    *                   with first-positional parameter the error.
   121    */
   122   _provisionIdentity: function _provisionIdentity(aIdentity, aIDPParams, aProvId, aCallback) {
   123     let provPath = aIDPParams.idpParams.provisioning;
   124     let url = Services.io.newURI("https://" + aIDPParams.domain, null, null).resolve(provPath);
   125     log("_provisionIdentity: identity:", aIdentity, "url:", url);
   127     // If aProvId is not null, then we already have a flow
   128     // with a sandbox.  Otherwise, get a sandbox and create a
   129     // new provision flow.
   131     if (aProvId) {
   132       // Re-use an existing sandbox
   133       log("_provisionIdentity: re-using sandbox in provisioning flow with id:", aProvId);
   134       this._provisionFlows[aProvId].provisioningSandbox.reload();
   136     } else {
   137       this._createProvisioningSandbox(url, function createdSandbox(aSandbox) {
   138         // create a provisioning flow, using the sandbox id, and
   139         // stash callback associated with this provisioning workflow.
   141         let provId = aSandbox.id;
   142         this._provisionFlows[provId] = {
   143           identity: aIdentity,
   144           idpParams: aIDPParams,
   145           securityLevel: this.securityLevel,
   146           provisioningSandbox: aSandbox,
   147           callback: function doCallback(aErr) {
   148             aCallback(aErr, provId);
   149           },
   150         };
   152         log("_provisionIdentity: Created sandbox and provisioning flow with id:", provId);
   153         // XXX bug 769862 - provisioning flow should timeout after N seconds
   155       }.bind(this));
   156     }
   157   },
   159   // DOM Methods
   160   /**
   161    * the provisioning iframe sandbox has called navigator.id.beginProvisioning()
   162    *
   163    * @param aCaller
   164    *        (object)  the iframe sandbox caller with all callbacks and
   165    *                  other information.  Callbacks include:
   166    *                  - doBeginProvisioningCallback(id, duration_s)
   167    *                  - doGenKeyPairCallback(pk)
   168    */
   169   beginProvisioning: function beginProvisioning(aCaller) {
   170     log("beginProvisioning:", aCaller.id);
   172     // Expect a flow for this caller already to be underway.
   173     let provFlow = this.getProvisionFlow(aCaller.id, aCaller.doError);
   175     // keep the caller object around
   176     provFlow.caller = aCaller;
   178     let identity = provFlow.identity;
   179     let frame = provFlow.provisioningFrame;
   181     // Determine recommended length of cert.
   182     let duration = this.certDuration;
   184     // Make a record that we have begun provisioning.  This is required
   185     // for genKeyPair.
   186     provFlow.didBeginProvisioning = true;
   188     // Let the sandbox know to invoke the callback to beginProvisioning with
   189     // the identity and cert length.
   190     return aCaller.doBeginProvisioningCallback(identity, duration);
   191   },
   193   /**
   194    * the provisioning iframe sandbox has called
   195    * navigator.id.raiseProvisioningFailure()
   196    *
   197    * @param aProvId
   198    *        (int)  the identifier of the provisioning flow tied to that sandbox
   199    * @param aReason
   200    */
   201   raiseProvisioningFailure: function raiseProvisioningFailure(aProvId, aReason) {
   202     reportError("Provisioning failure", aReason);
   204     // look up the provisioning caller and its callback
   205     let provFlow = this.getProvisionFlow(aProvId);
   207     // Sandbox is deleted in _cleanUpProvisionFlow in case we re-use it.
   209     // This may be either a "soft" or "hard" fail.  If it's a
   210     // soft fail, we'll flow through setAuthenticationFlow, where
   211     // the provision flow data will be copied into a new auth
   212     // flow.  If it's a hard fail, then the callback will be
   213     // responsible for cleaning up the now defunct provision flow.
   215     // invoke the callback with an error.
   216     provFlow.callback(aReason);
   217   },
   219   /**
   220    * When navigator.id.genKeyPair is called from provisioning iframe sandbox.
   221    * Generates a keypair for the current user being provisioned.
   222    *
   223    * @param aProvId
   224    *        (int)  the identifier of the provisioning caller tied to that sandbox
   225    *
   226    * It is an error to call genKeypair without receiving the callback for
   227    * the beginProvisioning() call first.
   228    */
   229   genKeyPair: function genKeyPair(aProvId) {
   230     // Look up the provisioning caller and make sure it's valid.
   231     let provFlow = this.getProvisionFlow(aProvId);
   233     if (!provFlow.didBeginProvisioning) {
   234       let errStr = "ERROR: genKeyPair called before beginProvisioning";
   235       log(errStr);
   236       provFlow.callback(errStr);
   237       return;
   238     }
   240     // Ok generate a keypair
   241     jwcrypto.generateKeyPair(jwcrypto.ALGORITHMS.DS160, function gkpCb(err, kp) {
   242       log("in gkp callback");
   243       if (err) {
   244         log("ERROR: genKeyPair:", err);
   245         provFlow.callback(err);
   246         return;
   247       }
   249       provFlow.kp = kp;
   251       // Serialize the publicKey of the keypair and send it back to the
   252       // sandbox.
   253       log("genKeyPair: generated keypair for provisioning flow with id:", aProvId);
   254       provFlow.caller.doGenKeyPairCallback(provFlow.kp.serializedPublicKey);
   255     }.bind(this));
   256   },
   258   /**
   259    * When navigator.id.registerCertificate is called from provisioning iframe
   260    * sandbox.
   261    *
   262    * Sets the certificate for the user for which a certificate was requested
   263    * via a preceding call to beginProvisioning (and genKeypair).
   264    *
   265    * @param aProvId
   266    *        (integer) the identifier of the provisioning caller tied to that
   267    *                  sandbox
   268    *
   269    * @param aCert
   270    *        (String)  A JWT representing the signed certificate for the user
   271    *                  being provisioned, provided by the IdP.
   272    */
   273   registerCertificate: function registerCertificate(aProvId, aCert) {
   274     log("registerCertificate:", aProvId, aCert);
   276     // look up provisioning caller, make sure it's valid.
   277     let provFlow = this.getProvisionFlow(aProvId);
   279     if (!provFlow.caller) {
   280       reportError("registerCertificate", "No provision flow or caller");
   281       return;
   282     }
   283     if (!provFlow.kp)  {
   284       let errStr = "Cannot register a certificate without a keypair";
   285       reportError("registerCertificate", errStr);
   286       provFlow.callback(errStr);
   287       return;
   288     }
   290     // store the keypair and certificate just provided in IDStore.
   291     this._store.addIdentity(provFlow.identity, provFlow.kp, aCert);
   293     // Great success!
   294     provFlow.callback(null);
   296     // Clean up the flow.
   297     this._cleanUpProvisionFlow(aProvId);
   298   },
   300   /**
   301    * Begin the authentication process with an IdP
   302    *
   303    * @param aProvId
   304    *        (int) the identifier of the provisioning flow which failed
   305    *
   306    * @param aCallback
   307    *        (function) to invoke upon completion, with
   308    *                   first-positional-param error.
   309    */
   310   _doAuthentication: function _doAuthentication(aProvId, aIDPParams) {
   311     log("_doAuthentication: provId:", aProvId, "idpParams:", aIDPParams);
   312     // create an authentication caller and its identifier AuthId
   313     // stash aIdentity, idpparams, and callback in it.
   315     // extract authentication URL from idpParams
   316     let authPath = aIDPParams.idpParams.authentication;
   317     let authURI = Services.io.newURI("https://" + aIDPParams.domain, null, null).resolve(authPath);
   319     // beginAuthenticationFlow causes the "identity-auth" topic to be
   320     // observed.  Since it's sending a notification to the DOM, there's
   321     // no callback.  We wait for the DOM to trigger the next phase of
   322     // provisioning.
   323     this._beginAuthenticationFlow(aProvId, authURI);
   325     // either we bind the AuthID to the sandbox ourselves, or UX does that,
   326     // in which case we need to tell UX the AuthId.
   327     // Currently, the UX creates the UI and gets the AuthId from the window
   328     // and sets is with setAuthenticationFlow
   329   },
   331   /**
   332    * The authentication frame has called navigator.id.beginAuthentication
   333    *
   334    * IMPORTANT: the aCaller is *always* non-null, even if this is called from
   335    * a regular content page. We have to make sure, on every DOM call, that
   336    * aCaller is an expected authentication-flow identifier. If not, we throw
   337    * an error or something.
   338    *
   339    * @param aCaller
   340    *        (object)  the authentication caller
   341    *
   342    */
   343   beginAuthentication: function beginAuthentication(aCaller) {
   344     log("beginAuthentication: caller id:", aCaller.id);
   346     // Begin the authentication flow after having concluded a provisioning
   347     // flow.  The aCaller that the DOM gives us will have the same ID as
   348     // the provisioning flow we just concluded.  (see setAuthenticationFlow)
   349     let authFlow = this._authenticationFlows[aCaller.id];
   350     if (!authFlow) {
   351       return aCaller.doError("beginAuthentication: no flow for caller id", aCaller.id);
   352     }
   354     authFlow.caller = aCaller;
   356     let identity = this._provisionFlows[authFlow.provId].identity;
   358     // tell the UI to start the authentication process
   359     log("beginAuthentication: authFlow:", aCaller.id, "identity:", identity);
   360     return authFlow.caller.doBeginAuthenticationCallback(identity);
   361   },
   363   /**
   364    * The auth frame has called navigator.id.completeAuthentication
   365    *
   366    * @param aAuthId
   367    *        (int)  the identifier of the authentication caller tied to that sandbox
   368    *
   369    */
   370   completeAuthentication: function completeAuthentication(aAuthId) {
   371     log("completeAuthentication:", aAuthId);
   373     // look up the AuthId caller, and get its callback.
   374     let authFlow = this._authenticationFlows[aAuthId];
   375     if (!authFlow) {
   376       reportError("completeAuthentication", "No auth flow with id", aAuthId);
   377       return;
   378     }
   379     let provId = authFlow.provId;
   381     // delete caller
   382     delete authFlow['caller'];
   383     delete this._authenticationFlows[aAuthId];
   385     let provFlow = this.getProvisionFlow(provId);
   386     provFlow.didAuthentication = true;
   387     let subject = {
   388       rpId: provFlow.rpId,
   389       identity: provFlow.identity,
   390     };
   391     Services.obs.notifyObservers({ wrappedJSObject: subject }, "identity-auth-complete", aAuthId);
   392   },
   394   /**
   395    * The auth frame has called navigator.id.cancelAuthentication
   396    *
   397    * @param aAuthId
   398    *        (int)  the identifier of the authentication caller
   399    *
   400    */
   401   cancelAuthentication: function cancelAuthentication(aAuthId) {
   402     log("cancelAuthentication:", aAuthId);
   404     // look up the AuthId caller, and get its callback.
   405     let authFlow = this._authenticationFlows[aAuthId];
   406     if (!authFlow) {
   407       reportError("cancelAuthentication", "No auth flow with id:", aAuthId);
   408       return;
   409     }
   410     let provId = authFlow.provId;
   412     // delete caller
   413     delete authFlow['caller'];
   414     delete this._authenticationFlows[aAuthId];
   416     let provFlow = this.getProvisionFlow(provId);
   417     provFlow.didAuthentication = true;
   418     Services.obs.notifyObservers(null, "identity-auth-complete", aAuthId);
   420     // invoke callback with ERROR.
   421     let errStr = "Authentication canceled by IDP";
   422     log("ERROR: cancelAuthentication:", errStr);
   423     provFlow.callback(errStr);
   424   },
   426   /**
   427    * Called by the UI to set the ID and caller for the authentication flow after it gets its ID
   428    */
   429   setAuthenticationFlow: function(aAuthId, aProvId) {
   430     // this is the transition point between the two flows,
   431     // provision and authenticate.  We tell the auth flow which
   432     // provisioning flow it is started from.
   433     log("setAuthenticationFlow: authId:", aAuthId, "provId:", aProvId);
   434     this._authenticationFlows[aAuthId] = { provId: aProvId };
   435     this._provisionFlows[aProvId].authId = aAuthId;
   436   },
   438   /**
   439    * Load the provisioning URL in a hidden frame to start the provisioning
   440    * process.
   441    */
   442   _createProvisioningSandbox: function _createProvisioningSandbox(aURL, aCallback) {
   443     log("_createProvisioningSandbox:", aURL);
   445     if (!this._sandboxConfigured) {
   446       // Configure message manager listening on the hidden window
   447       Cu.import("resource://gre/modules/DOMIdentity.jsm");
   448       DOMIdentity._configureMessages(Services.appShell.hiddenDOMWindow, true);
   449       this._sandboxConfigured = true;
   450     }
   452     new Sandbox(aURL, aCallback);
   453   },
   455   /**
   456    * Load the authentication UI to start the authentication process.
   457    */
   458   _beginAuthenticationFlow: function _beginAuthenticationFlow(aProvId, aURL) {
   459     log("_beginAuthenticationFlow:", aProvId, aURL);
   460     let propBag = {provId: aProvId};
   462     Services.obs.notifyObservers({wrappedJSObject:propBag}, "identity-auth", aURL);
   463   },
   465   /**
   466    * Clean up a provision flow and the authentication flow and sandbox
   467    * that may be attached to it.
   468    */
   469   _cleanUpProvisionFlow: function _cleanUpProvisionFlow(aProvId) {
   470     log('_cleanUpProvisionFlow:', aProvId);
   471     let prov = this._provisionFlows[aProvId];
   473     // Clean up the sandbox, if there is one.
   474     if (prov.provisioningSandbox) {
   475       let sandbox = this._provisionFlows[aProvId]['provisioningSandbox'];
   476       if (sandbox.free) {
   477         log('_cleanUpProvisionFlow: freeing sandbox');
   478         sandbox.free();
   479       }
   480       delete this._provisionFlows[aProvId]['provisioningSandbox'];
   481     }
   483     // Clean up a related authentication flow, if there is one.
   484     if (this._authenticationFlows[prov.authId]) {
   485       delete this._authenticationFlows[prov.authId];
   486     }
   488     // Finally delete the provision flow
   489     delete this._provisionFlows[aProvId];
   490   }
   492 };
   494 this.IdentityProvider = new IdentityProviderService();

mercurial