toolkit/identity/Identity.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 this.EXPORTED_SYMBOLS = ["IdentityService"];
    11 const Cu = Components.utils;
    12 const Ci = Components.interfaces;
    13 const Cc = Components.classes;
    14 const Cr = Components.results;
    16 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
    17 Cu.import("resource://gre/modules/Services.jsm");
    18 Cu.import("resource://gre/modules/identity/LogUtils.jsm");
    19 Cu.import("resource://gre/modules/identity/IdentityStore.jsm");
    20 Cu.import("resource://gre/modules/identity/RelyingParty.jsm");
    21 Cu.import("resource://gre/modules/identity/IdentityProvider.jsm");
    23 XPCOMUtils.defineLazyModuleGetter(this,
    24                                   "jwcrypto",
    25                                   "resource://gre/modules/identity/jwcrypto.jsm");
    27 function log(...aMessageArgs) {
    28   Logger.log.apply(Logger, ["core"].concat(aMessageArgs));
    29 }
    30 function reportError(...aMessageArgs) {
    31   Logger.reportError.apply(Logger, ["core"].concat(aMessageArgs));
    32 }
    34 function IDService() {
    35   Services.obs.addObserver(this, "quit-application-granted", false);
    36   Services.obs.addObserver(this, "identity-auth-complete", false);
    38   this._store = IdentityStore;
    39   this.RP = RelyingParty;
    40   this.IDP = IdentityProvider;
    41 }
    43 IDService.prototype = {
    44   QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports, Ci.nsIObserver]),
    46   observe: function observe(aSubject, aTopic, aData) {
    47     switch (aTopic) {
    48       case "quit-application-granted":
    49         Services.obs.removeObserver(this, "quit-application-granted");
    50         this.shutdown();
    51         break;
    52       case "identity-auth-complete":
    53         if (!aSubject || !aSubject.wrappedJSObject)
    54           break;
    55         let subject = aSubject.wrappedJSObject;
    56         log("Auth complete:", aSubject.wrappedJSObject);
    57         // We have authenticated in order to provision an identity.
    58         // So try again.
    59         this.selectIdentity(subject.rpId, subject.identity);
    60         break;
    61     }
    62   },
    64   reset: function reset() {
    65     // Explicitly call reset() on our RP and IDP classes.
    66     // This is here to make testing easier.  When the
    67     // quit-application-granted signal is emitted, reset() will be
    68     // called here, on RP, on IDP, and on the store.  So you don't
    69     // need to use this :)
    70     this._store.reset();
    71     this.RP.reset();
    72     this.IDP.reset();
    73   },
    75   shutdown: function shutdown() {
    76     log("shutdown");
    77     Services.obs.removeObserver(this, "identity-auth-complete");
    78     Services.obs.removeObserver(this, "quit-application-granted");
    79   },
    81   /**
    82    * Parse an email into username and domain if it is valid, else return null
    83    */
    84   parseEmail: function parseEmail(email) {
    85     var match = email.match(/^([^@]+)@([^@^/]+.[a-z]+)$/);
    86     if (match) {
    87       return {
    88         username: match[1],
    89         domain: match[2]
    90       };
    91     }
    92     return null;
    93   },
    95   /**
    96    * The UX wants to add a new identity
    97    * often followed by selectIdentity()
    98    *
    99    * @param aIdentity
   100    *        (string) the email chosen for login
   101    */
   102   addIdentity: function addIdentity(aIdentity) {
   103     if (this._store.fetchIdentity(aIdentity) === null) {
   104       this._store.addIdentity(aIdentity, null, null);
   105     }
   106   },
   108   /**
   109    * The UX comes back and calls selectIdentity once the user has picked
   110    * an identity.
   111    *
   112    * @param aRPId
   113    *        (integer) the id of the doc object obtained in .watch() and
   114    *                  passed to the UX component.
   115    *
   116    * @param aIdentity
   117    *        (string) the email chosen for login
   118    */
   119   selectIdentity: function selectIdentity(aRPId, aIdentity) {
   120     log("selectIdentity: RP id:", aRPId, "identity:", aIdentity);
   122     // Get the RP that was stored when watch() was invoked.
   123     let rp = this.RP._rpFlows[aRPId];
   124     if (!rp) {
   125       reportError("selectIdentity", "Invalid RP id: ", aRPId);
   126       return;
   127     }
   129     // It's possible that we are in the process of provisioning an
   130     // identity.
   131     let provId = rp.provId;
   133     let rpLoginOptions = {
   134       loggedInUser: aIdentity,
   135       origin: rp.origin
   136     };
   137     log("selectIdentity: provId:", provId, "origin:", rp.origin);
   139     // Once we have a cert, and once the user is authenticated with the
   140     // IdP, we can generate an assertion and deliver it to the doc.
   141     let self = this;
   142     this.RP._generateAssertion(rp.origin, aIdentity, function hadReadyAssertion(err, assertion) {
   143       if (!err && assertion) {
   144         self.RP._doLogin(rp, rpLoginOptions, assertion);
   145         return;
   147       }
   148       // Need to provision an identity first.  Begin by discovering
   149       // the user's IdP.
   150       self._discoverIdentityProvider(aIdentity, function gotIDP(err, idpParams) {
   151         if (err) {
   152           rp.doError(err);
   153           return;
   154         }
   156         // The idpParams tell us where to go to provision and authenticate
   157         // the identity.
   158         self.IDP._provisionIdentity(aIdentity, idpParams, provId, function gotID(err, aProvId) {
   160           // Provision identity may have created a new provision flow
   161           // for us.  To make it easier to relate provision flows with
   162           // RP callers, we cross index the two here.
   163           rp.provId = aProvId;
   164           self.IDP._provisionFlows[aProvId].rpId = aRPId;
   166           // At this point, we already have a cert.  If the user is also
   167           // already authenticated with the IdP, then we can try again
   168           // to generate an assertion and login.
   169           if (err) {
   170             // We are not authenticated.  If we have already tried to
   171             // authenticate and failed, then this is a "hard fail" and
   172             // we give up.  Otherwise we try to authenticate with the
   173             // IdP.
   175             if (self.IDP._provisionFlows[aProvId].didAuthentication) {
   176               self.IDP._cleanUpProvisionFlow(aProvId);
   177               self.RP._cleanUpProvisionFlow(aRPId, aProvId);
   178               log("ERROR: selectIdentity: authentication hard fail");
   179               rp.doError("Authentication fail.");
   180               return;
   181             }
   182             // Try to authenticate with the IdP.  Note that we do
   183             // not clean up the provision flow here.  We will continue
   184             // to use it.
   185             self.IDP._doAuthentication(aProvId, idpParams);
   186             return;
   187           }
   189           // Provisioning flows end when a certificate has been registered.
   190           // Thus IdentityProvider's registerCertificate() cleans up the
   191           // current provisioning flow.  We only do this here on error.
   192           self.RP._generateAssertion(rp.origin, aIdentity, function gotAssertion(err, assertion) {
   193             if (err) {
   194               rp.doError(err);
   195               return;
   196             }
   197             self.RP._doLogin(rp, rpLoginOptions, assertion);
   198             self.RP._cleanUpProvisionFlow(aRPId, aProvId);
   199             return;
   200           });
   201         });
   202       });
   203     });
   204   },
   206   // methods for chrome and add-ons
   208   /**
   209    * Discover the IdP for an identity
   210    *
   211    * @param aIdentity
   212    *        (string) the email we're logging in with
   213    *
   214    * @param aCallback
   215    *        (function) callback to invoke on completion
   216    *                   with first-positional parameter the error.
   217    */
   218   _discoverIdentityProvider: function _discoverIdentityProvider(aIdentity, aCallback) {
   219     // XXX bug 767610 - validate email address call
   220     // When that is available, we can remove this custom parser
   221     var parsedEmail = this.parseEmail(aIdentity);
   222     if (parsedEmail === null) {
   223       return aCallback("Could not parse email: " + aIdentity);
   224     }
   225     log("_discoverIdentityProvider: identity:", aIdentity, "domain:", parsedEmail.domain);
   227     this._fetchWellKnownFile(parsedEmail.domain, function fetchedWellKnown(err, idpParams) {
   228       // idpParams includes the pk, authorization url, and
   229       // provisioning url.
   231       // XXX bug 769861 follow any authority delegations
   232       // if no well-known at any point in the delegation
   233       // fall back to browserid.org as IdP
   234       return aCallback(err, idpParams);
   235     });
   236   },
   238   /**
   239    * Fetch the well-known file from the domain.
   240    *
   241    * @param aDomain
   242    *
   243    * @param aScheme
   244    *        (string) (optional) Protocol to use.  Default is https.
   245    *                 This is necessary because we are unable to test
   246    *                 https.
   247    *
   248    * @param aCallback
   249    *
   250    */
   251   _fetchWellKnownFile: function _fetchWellKnownFile(aDomain, aCallback, aScheme='https') {
   252     // XXX bug 769854 make tests https and remove aScheme option
   253     let url = aScheme + '://' + aDomain + "/.well-known/browserid";
   254     log("_fetchWellKnownFile:", url);
   256     // this appears to be a more successful way to get at xmlhttprequest (which supposedly will close with a window
   257     let req = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"]
   258                 .createInstance(Ci.nsIXMLHttpRequest);
   260     // XXX bug 769865 gracefully handle being off-line
   261     // XXX bug 769866 decide on how to handle redirects
   262     req.open("GET", url, true);
   263     req.responseType = "json";
   264     req.mozBackgroundRequest = true;
   265     req.onload = function _fetchWellKnownFile_onload() {
   266       if (req.status < 200 || req.status >= 400) {
   267         log("_fetchWellKnownFile", url, ": server returned status:", req.status);
   268         return aCallback("Error");
   269       }
   270       try {
   271         let idpParams = req.response;
   273         // Verify that the IdP returned a valid configuration
   274         if (! (idpParams.provisioning &&
   275             idpParams.authentication &&
   276             idpParams['public-key'])) {
   277           let errStr= "Invalid well-known file from: " + aDomain;
   278           log("_fetchWellKnownFile:", errStr);
   279           return aCallback(errStr);
   280         }
   282         let callbackObj = {
   283           domain: aDomain,
   284           idpParams: idpParams,
   285         };
   286         log("_fetchWellKnownFile result: ", callbackObj);
   287         // Yay.  Valid IdP configuration for the domain.
   288         return aCallback(null, callbackObj);
   290       } catch (err) {
   291         reportError("_fetchWellKnownFile", "Bad configuration from", aDomain, err);
   292         return aCallback(err.toString());
   293       }
   294     };
   295     req.onerror = function _fetchWellKnownFile_onerror() {
   296       log("_fetchWellKnownFile", "ERROR:", req.status, req.statusText);
   297       log("ERROR: _fetchWellKnownFile:", err);
   298       return aCallback("Error");
   299     };
   300     req.send(null);
   301   },
   303 };
   305 this.IdentityService = new IDService();

mercurial