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

mercurial