Sat, 03 Jan 2015 20:18:00 +0100
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();