toolkit/identity/Identity.jsm

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

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

mercurial