dom/identity/nsDOMIdentity.js

Thu, 22 Jan 2015 13:21:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 22 Jan 2015 13:21:57 +0100
branch
TOR_BUG_9701
changeset 15
b8a032363ba2
permissions
-rw-r--r--

Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6

michael@0 1 /* This Source Code Form is subject to the terms of the Mozilla Public
michael@0 2 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
michael@0 3 * You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 4
michael@0 5 "use strict";
michael@0 6
michael@0 7 const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
michael@0 8
michael@0 9 const PREF_DEBUG = "toolkit.identity.debug";
michael@0 10 const PREF_ENABLED = "dom.identity.enabled";
michael@0 11
michael@0 12 // Bug 822450: Workaround for Bug 821740. When testing with marionette,
michael@0 13 // relax navigator.id.request's requirement that it be handling native
michael@0 14 // events. Synthetic marionette events are ok.
michael@0 15 const PREF_SYNTHETIC_EVENTS_OK = "dom.identity.syntheticEventsOk";
michael@0 16
michael@0 17 // Maximum length of a string that will go through IPC
michael@0 18 const MAX_STRING_LENGTH = 2048;
michael@0 19 // Maximum number of times navigator.id.request can be called for a document
michael@0 20 const MAX_RP_CALLS = 100;
michael@0 21
michael@0 22 Cu.import("resource://gre/modules/Services.jsm");
michael@0 23 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
michael@0 24
michael@0 25 XPCOMUtils.defineLazyModuleGetter(this, "checkDeprecated",
michael@0 26 "resource://gre/modules/identity/IdentityUtils.jsm");
michael@0 27 XPCOMUtils.defineLazyModuleGetter(this, "checkRenamed",
michael@0 28 "resource://gre/modules/identity/IdentityUtils.jsm");
michael@0 29 XPCOMUtils.defineLazyModuleGetter(this, "objectCopy",
michael@0 30 "resource://gre/modules/identity/IdentityUtils.jsm");
michael@0 31
michael@0 32 XPCOMUtils.defineLazyServiceGetter(this, "uuidgen",
michael@0 33 "@mozilla.org/uuid-generator;1",
michael@0 34 "nsIUUIDGenerator");
michael@0 35
michael@0 36 // This is the child process corresponding to nsIDOMIdentity
michael@0 37 XPCOMUtils.defineLazyServiceGetter(this, "cpmm",
michael@0 38 "@mozilla.org/childprocessmessagemanager;1",
michael@0 39 "nsIMessageSender");
michael@0 40
michael@0 41
michael@0 42 const ERRORS = {
michael@0 43 "ERROR_NOT_AUTHORIZED_FOR_FIREFOX_ACCOUNTS":
michael@0 44 "Only privileged and certified apps may use Firefox Accounts",
michael@0 45 "ERROR_INVALID_ASSERTION_AUDIENCE":
michael@0 46 "Assertion audience may not differ from origin",
michael@0 47 "ERROR_REQUEST_WHILE_NOT_HANDLING_USER_INPUT":
michael@0 48 "The request() method may only be invoked when handling user input",
michael@0 49 };
michael@0 50
michael@0 51 function nsDOMIdentity(aIdentityInternal) {
michael@0 52 this._identityInternal = aIdentityInternal;
michael@0 53 }
michael@0 54 nsDOMIdentity.prototype = {
michael@0 55 __exposedProps__: {
michael@0 56 // Relying Party (RP)
michael@0 57 watch: 'r',
michael@0 58 request: 'r',
michael@0 59 logout: 'r',
michael@0 60 get: 'r',
michael@0 61 getVerifiedEmail: 'r',
michael@0 62
michael@0 63 // Provisioning
michael@0 64 beginProvisioning: 'r',
michael@0 65 genKeyPair: 'r',
michael@0 66 registerCertificate: 'r',
michael@0 67 raiseProvisioningFailure: 'r',
michael@0 68
michael@0 69 // Authentication
michael@0 70 beginAuthentication: 'r',
michael@0 71 completeAuthentication: 'r',
michael@0 72 raiseAuthenticationFailure: 'r'
michael@0 73 },
michael@0 74
michael@0 75 // require native events unless syntheticEventsOk is set
michael@0 76 get nativeEventsRequired() {
michael@0 77 if (Services.prefs.prefHasUserValue(PREF_SYNTHETIC_EVENTS_OK) &&
michael@0 78 (Services.prefs.getPrefType(PREF_SYNTHETIC_EVENTS_OK) ===
michael@0 79 Ci.nsIPrefBranch.PREF_BOOL)) {
michael@0 80 return !Services.prefs.getBoolPref(PREF_SYNTHETIC_EVENTS_OK);
michael@0 81 }
michael@0 82 return true;
michael@0 83 },
michael@0 84
michael@0 85 reportErrors: function(message) {
michael@0 86 let onerror = function() {};
michael@0 87 if (this._rpWatcher && this._rpWatcher.onerror) {
michael@0 88 onerror = this._rpWatcher.onerror;
michael@0 89 }
michael@0 90
michael@0 91 message.errors.forEach((error) => {
michael@0 92 // Report an error string to content
michael@0 93 Cu.reportError(ERRORS[error]);
michael@0 94
michael@0 95 // Report error code to RP callback, if available
michael@0 96 onerror(error);
michael@0 97 });
michael@0 98 },
michael@0 99
michael@0 100 /**
michael@0 101 * Relying Party (RP) APIs
michael@0 102 */
michael@0 103
michael@0 104 watch: function nsDOMIdentity_watch(aOptions = {}) {
michael@0 105 if (this._rpWatcher) {
michael@0 106 // For the initial release of Firefox Accounts, we support callers who
michael@0 107 // invoke watch() either for Firefox Accounts, or Persona, but not both.
michael@0 108 // In the future, we may wish to support the dual invocation (say, for
michael@0 109 // packaged apps so they can sign users in who reject the app's request
michael@0 110 // to sign in with their Firefox Accounts identity).
michael@0 111 throw new Error("navigator.id.watch was already called");
michael@0 112 }
michael@0 113
michael@0 114 assertCorrectCallbacks(aOptions);
michael@0 115
michael@0 116 let message = this.DOMIdentityMessage(aOptions);
michael@0 117
michael@0 118 // loggedInUser vs loggedInEmail
michael@0 119 // https://developer.mozilla.org/en-US/docs/DOM/navigator.id.watch
michael@0 120 // This parameter, loggedInUser, was renamed from loggedInEmail in early
michael@0 121 // September, 2012. Both names will continue to work for the time being,
michael@0 122 // but code should be changed to use loggedInUser instead.
michael@0 123 checkRenamed(aOptions, "loggedInEmail", "loggedInUser");
michael@0 124 message["loggedInUser"] = aOptions["loggedInUser"];
michael@0 125
michael@0 126 let emailType = typeof(aOptions["loggedInUser"]);
michael@0 127 if (aOptions["loggedInUser"] && aOptions["loggedInUser"] !== "undefined") {
michael@0 128 if (emailType !== "string") {
michael@0 129 throw new Error("loggedInUser must be a String or null");
michael@0 130 }
michael@0 131
michael@0 132 // TODO: Bug 767610 - check email format.
michael@0 133 // See HTMLInputElement::IsValidEmailAddress
michael@0 134 if (aOptions["loggedInUser"].indexOf("@") == -1
michael@0 135 || aOptions["loggedInUser"].length > MAX_STRING_LENGTH) {
michael@0 136 throw new Error("loggedInUser is not valid");
michael@0 137 }
michael@0 138 // Set loggedInUser in this block that "undefined" doesn't get through.
michael@0 139 message.loggedInUser = aOptions.loggedInUser;
michael@0 140 }
michael@0 141 this._log("loggedInUser: " + message.loggedInUser);
michael@0 142
michael@0 143 this._rpWatcher = aOptions;
michael@0 144 this._rpWatcher.audience = message.audience;
michael@0 145
michael@0 146 if (message.errors.length) {
michael@0 147 this.reportErrors(message);
michael@0 148 // We don't delete the rpWatcher object, because we don't want the
michael@0 149 // broken client to be able to call watch() any more. It's broken.
michael@0 150 return;
michael@0 151 }
michael@0 152 this._identityInternal._mm.sendAsyncMessage("Identity:RP:Watch", message);
michael@0 153 },
michael@0 154
michael@0 155 request: function nsDOMIdentity_request(aOptions = {}) {
michael@0 156 this._log("request: " + JSON.stringify(aOptions));
michael@0 157
michael@0 158 // Has the caller called watch() before this?
michael@0 159 if (!this._rpWatcher) {
michael@0 160 throw new Error("navigator.id.request called before navigator.id.watch");
michael@0 161 }
michael@0 162 if (this._rpCalls > MAX_RP_CALLS) {
michael@0 163 throw new Error("navigator.id.request called too many times");
michael@0 164 }
michael@0 165
michael@0 166 let util = this._window.QueryInterface(Ci.nsIInterfaceRequestor)
michael@0 167 .getInterface(Ci.nsIDOMWindowUtils);
michael@0 168
michael@0 169 let message = this.DOMIdentityMessage(aOptions);
michael@0 170
michael@0 171 // We permit calling of request() outside of a user input handler only when
michael@0 172 // a certified or privileged app is calling, or when we are handling the
michael@0 173 // (deprecated) get() or getVerifiedEmail() calls, which make use of an RP
michael@0 174 // context marked as _internal.
michael@0 175
michael@0 176 if (!aOptions._internal &&
michael@0 177 this._appStatus !== Ci.nsIPrincipal.APP_STATUS_CERTIFIED &&
michael@0 178 this._appStatus !== Ci.nsIPrincipal.APP_STATUS_PRIVILEGED) {
michael@0 179
michael@0 180 // If the caller is not special in one of those ways, see if the user has
michael@0 181 // preffed on 'syntheticEventsOk' (useful for testing); otherwise, if
michael@0 182 // this is a non-native event, reject it.
michael@0 183 let util = this._window.QueryInterface(Ci.nsIInterfaceRequestor)
michael@0 184 .getInterface(Ci.nsIDOMWindowUtils);
michael@0 185
michael@0 186 if (!util.isHandlingUserInput && this.nativeEventsRequired) {
michael@0 187 message.errors.push("ERROR_REQUEST_WHILE_NOT_HANDLING_USER_INPUT");
michael@0 188 }
michael@0 189 }
michael@0 190
michael@0 191 // Report and fail hard on any errors.
michael@0 192 if (message.errors.length) {
michael@0 193 this.reportErrors(message);
michael@0 194 return;
michael@0 195 }
michael@0 196
michael@0 197 if (aOptions) {
michael@0 198 // Optional string properties
michael@0 199 let optionalStringProps = ["privacyPolicy", "termsOfService"];
michael@0 200 for (let propName of optionalStringProps) {
michael@0 201 if (!aOptions[propName] || aOptions[propName] === "undefined")
michael@0 202 continue;
michael@0 203 if (typeof(aOptions[propName]) !== "string") {
michael@0 204 throw new Error(propName + " must be a string representing a URL.");
michael@0 205 }
michael@0 206 if (aOptions[propName].length > MAX_STRING_LENGTH) {
michael@0 207 throw new Error(propName + " is invalid.");
michael@0 208 }
michael@0 209 message[propName] = aOptions[propName];
michael@0 210 }
michael@0 211
michael@0 212 if (aOptions["oncancel"]
michael@0 213 && typeof(aOptions["oncancel"]) !== "function") {
michael@0 214 throw new Error("oncancel is not a function");
michael@0 215 } else {
michael@0 216 // Store optional cancel callback for later.
michael@0 217 this._onCancelRequestCallback = aOptions.oncancel;
michael@0 218 }
michael@0 219 }
michael@0 220
michael@0 221 this._rpCalls++;
michael@0 222 this._identityInternal._mm.sendAsyncMessage("Identity:RP:Request", message);
michael@0 223 },
michael@0 224
michael@0 225 logout: function nsDOMIdentity_logout() {
michael@0 226 if (!this._rpWatcher) {
michael@0 227 throw new Error("navigator.id.logout called before navigator.id.watch");
michael@0 228 }
michael@0 229 if (this._rpCalls > MAX_RP_CALLS) {
michael@0 230 throw new Error("navigator.id.logout called too many times");
michael@0 231 }
michael@0 232
michael@0 233 this._rpCalls++;
michael@0 234 let message = this.DOMIdentityMessage();
michael@0 235
michael@0 236 // Report and fail hard on any errors.
michael@0 237 if (message.errors.length) {
michael@0 238 this.reportErrors(message);
michael@0 239 return;
michael@0 240 }
michael@0 241
michael@0 242 this._identityInternal._mm.sendAsyncMessage("Identity:RP:Logout", message);
michael@0 243 },
michael@0 244
michael@0 245 /*
michael@0 246 * Get an assertion. This function is deprecated. RPs are
michael@0 247 * encouraged to use the observer API instead (watch + request).
michael@0 248 */
michael@0 249 get: function nsDOMIdentity_get(aCallback, aOptions) {
michael@0 250 var opts = {};
michael@0 251 aOptions = aOptions || {};
michael@0 252
michael@0 253 // We use the observer API (watch + request) to implement get().
michael@0 254 // Because the caller can call get() and getVerifiedEmail() as
michael@0 255 // many times as they want, we lift the restriction that watch() can
michael@0 256 // only be called once.
michael@0 257 this._rpWatcher = null;
michael@0 258
michael@0 259 // This flag tells internal_api.js (in the shim) to record in the
michael@0 260 // login parameters whether the assertion was acquired silently or
michael@0 261 // with user interaction.
michael@0 262 opts._internal = true;
michael@0 263
michael@0 264 opts.privacyPolicy = aOptions.privacyPolicy || undefined;
michael@0 265 opts.termsOfService = aOptions.termsOfService || undefined;
michael@0 266 opts.privacyURL = aOptions.privacyURL || undefined;
michael@0 267 opts.tosURL = aOptions.tosURL || undefined;
michael@0 268 opts.siteName = aOptions.siteName || undefined;
michael@0 269 opts.siteLogo = aOptions.siteLogo || undefined;
michael@0 270
michael@0 271 opts.oncancel = function get_oncancel() {
michael@0 272 if (aCallback) {
michael@0 273 aCallback(null);
michael@0 274 aCallback = null;
michael@0 275 }
michael@0 276 };
michael@0 277
michael@0 278 if (checkDeprecated(aOptions, "silent")) {
michael@0 279 // Silent has been deprecated, do nothing. Placing the check here
michael@0 280 // prevents the callback from being called twice, once with null and
michael@0 281 // once after internalWatch has been called. See issue #1532:
michael@0 282 // https://github.com/mozilla/browserid/issues/1532
michael@0 283 if (aCallback) {
michael@0 284 setTimeout(function() { aCallback(null); }, 0);
michael@0 285 }
michael@0 286 return;
michael@0 287 }
michael@0 288
michael@0 289 // Get an assertion by using our observer api: watch + request.
michael@0 290 var self = this;
michael@0 291 this.watch({
michael@0 292 _internal: true,
michael@0 293 onlogin: function get_onlogin(assertion, internalParams) {
michael@0 294 if (assertion && aCallback && internalParams && !internalParams.silent) {
michael@0 295 aCallback(assertion);
michael@0 296 aCallback = null;
michael@0 297 }
michael@0 298 },
michael@0 299 onlogout: function get_onlogout() {},
michael@0 300 onready: function get_onready() {
michael@0 301 self.request(opts);
michael@0 302 }
michael@0 303 });
michael@0 304 },
michael@0 305
michael@0 306 getVerifiedEmail: function nsDOMIdentity_getVerifiedEmail(aCallback) {
michael@0 307 Cu.reportError("WARNING: getVerifiedEmail has been deprecated");
michael@0 308 this.get(aCallback, {});
michael@0 309 },
michael@0 310
michael@0 311 /**
michael@0 312 * Identity Provider (IDP) Provisioning APIs
michael@0 313 */
michael@0 314
michael@0 315 beginProvisioning: function nsDOMIdentity_beginProvisioning(aCallback) {
michael@0 316 this._log("beginProvisioning");
michael@0 317 if (this._beginProvisioningCallback) {
michael@0 318 throw new Error("navigator.id.beginProvisioning already called.");
michael@0 319 }
michael@0 320 if (!aCallback || typeof(aCallback) !== "function") {
michael@0 321 throw new Error("beginProvisioning callback is required.");
michael@0 322 }
michael@0 323
michael@0 324 this._beginProvisioningCallback = aCallback;
michael@0 325 this._identityInternal._mm.sendAsyncMessage("Identity:IDP:BeginProvisioning",
michael@0 326 this.DOMIdentityMessage());
michael@0 327 },
michael@0 328
michael@0 329 genKeyPair: function nsDOMIdentity_genKeyPair(aCallback) {
michael@0 330 this._log("genKeyPair");
michael@0 331 if (!this._beginProvisioningCallback) {
michael@0 332 throw new Error("navigator.id.genKeyPair called outside of provisioning");
michael@0 333 }
michael@0 334 if (this._genKeyPairCallback) {
michael@0 335 throw new Error("navigator.id.genKeyPair already called.");
michael@0 336 }
michael@0 337 if (!aCallback || typeof(aCallback) !== "function") {
michael@0 338 throw new Error("genKeyPair callback is required.");
michael@0 339 }
michael@0 340
michael@0 341 this._genKeyPairCallback = aCallback;
michael@0 342 this._identityInternal._mm.sendAsyncMessage("Identity:IDP:GenKeyPair",
michael@0 343 this.DOMIdentityMessage());
michael@0 344 },
michael@0 345
michael@0 346 registerCertificate: function nsDOMIdentity_registerCertificate(aCertificate) {
michael@0 347 this._log("registerCertificate");
michael@0 348 if (!this._genKeyPairCallback) {
michael@0 349 throw new Error("navigator.id.registerCertificate called outside of provisioning");
michael@0 350 }
michael@0 351 if (this._provisioningEnded) {
michael@0 352 throw new Error("Provisioning already ended");
michael@0 353 }
michael@0 354 this._provisioningEnded = true;
michael@0 355
michael@0 356 let message = this.DOMIdentityMessage();
michael@0 357 message.cert = aCertificate;
michael@0 358 this._identityInternal._mm.sendAsyncMessage("Identity:IDP:RegisterCertificate", message);
michael@0 359 },
michael@0 360
michael@0 361 raiseProvisioningFailure: function nsDOMIdentity_raiseProvisioningFailure(aReason) {
michael@0 362 this._log("raiseProvisioningFailure '" + aReason + "'");
michael@0 363 if (this._provisioningEnded) {
michael@0 364 throw new Error("Provisioning already ended");
michael@0 365 }
michael@0 366 if (!aReason || typeof(aReason) != "string") {
michael@0 367 throw new Error("raiseProvisioningFailure reason is required");
michael@0 368 }
michael@0 369 this._provisioningEnded = true;
michael@0 370
michael@0 371 let message = this.DOMIdentityMessage();
michael@0 372 message.reason = aReason;
michael@0 373 this._identityInternal._mm.sendAsyncMessage("Identity:IDP:ProvisioningFailure", message);
michael@0 374 },
michael@0 375
michael@0 376 /**
michael@0 377 * Identity Provider (IDP) Authentication APIs
michael@0 378 */
michael@0 379
michael@0 380 beginAuthentication: function nsDOMIdentity_beginAuthentication(aCallback) {
michael@0 381 this._log("beginAuthentication");
michael@0 382 if (this._beginAuthenticationCallback) {
michael@0 383 throw new Error("navigator.id.beginAuthentication already called.");
michael@0 384 }
michael@0 385 if (typeof(aCallback) !== "function") {
michael@0 386 throw new Error("beginAuthentication callback is required.");
michael@0 387 }
michael@0 388 if (!aCallback || typeof(aCallback) !== "function") {
michael@0 389 throw new Error("beginAuthentication callback is required.");
michael@0 390 }
michael@0 391
michael@0 392 this._beginAuthenticationCallback = aCallback;
michael@0 393 this._identityInternal._mm.sendAsyncMessage("Identity:IDP:BeginAuthentication",
michael@0 394 this.DOMIdentityMessage());
michael@0 395 },
michael@0 396
michael@0 397 completeAuthentication: function nsDOMIdentity_completeAuthentication() {
michael@0 398 if (this._authenticationEnded) {
michael@0 399 throw new Error("Authentication already ended");
michael@0 400 }
michael@0 401 if (!this._beginAuthenticationCallback) {
michael@0 402 throw new Error("navigator.id.completeAuthentication called outside of authentication");
michael@0 403 }
michael@0 404 this._authenticationEnded = true;
michael@0 405
michael@0 406 this._identityInternal._mm.sendAsyncMessage("Identity:IDP:CompleteAuthentication",
michael@0 407 this.DOMIdentityMessage());
michael@0 408 },
michael@0 409
michael@0 410 raiseAuthenticationFailure: function nsDOMIdentity_raiseAuthenticationFailure(aReason) {
michael@0 411 if (this._authenticationEnded) {
michael@0 412 throw new Error("Authentication already ended");
michael@0 413 }
michael@0 414 if (!aReason || typeof(aReason) != "string") {
michael@0 415 throw new Error("raiseProvisioningFailure reason is required");
michael@0 416 }
michael@0 417
michael@0 418 let message = this.DOMIdentityMessage();
michael@0 419 message.reason = aReason;
michael@0 420 this._identityInternal._mm.sendAsyncMessage("Identity:IDP:AuthenticationFailure", message);
michael@0 421 },
michael@0 422
michael@0 423 // Private.
michael@0 424 _init: function nsDOMIdentity__init(aWindow) {
michael@0 425
michael@0 426 this._initializeState();
michael@0 427
michael@0 428 // Store window and origin URI.
michael@0 429 this._window = aWindow;
michael@0 430 this._origin = aWindow.document.nodePrincipal.origin;
michael@0 431 this._appStatus = aWindow.document.nodePrincipal.appStatus;
michael@0 432 this._appId = aWindow.document.nodePrincipal.appId;
michael@0 433
michael@0 434 // Setup identifiers for current window.
michael@0 435 let util = aWindow.QueryInterface(Ci.nsIInterfaceRequestor)
michael@0 436 .getInterface(Ci.nsIDOMWindowUtils);
michael@0 437
michael@0 438 // We need to inherit the id from the internalIdentity service.
michael@0 439 // See comments below in that service's init.
michael@0 440 this._id = this._identityInternal._id;
michael@0 441 },
michael@0 442
michael@0 443 /**
michael@0 444 * Called during init and shutdown.
michael@0 445 */
michael@0 446 _initializeState: function nsDOMIdentity__initializeState() {
michael@0 447 // Some state to prevent abuse
michael@0 448 // Limit the number of calls to .request
michael@0 449 this._rpCalls = 0;
michael@0 450 this._provisioningEnded = false;
michael@0 451 this._authenticationEnded = false;
michael@0 452
michael@0 453 this._rpWatcher = null;
michael@0 454 this._onCancelRequestCallback = null;
michael@0 455 this._beginProvisioningCallback = null;
michael@0 456 this._genKeyPairCallback = null;
michael@0 457 this._beginAuthenticationCallback = null;
michael@0 458 },
michael@0 459
michael@0 460 _receiveMessage: function nsDOMIdentity_receiveMessage(aMessage) {
michael@0 461 let msg = aMessage.json;
michael@0 462
michael@0 463 switch (aMessage.name) {
michael@0 464 case "Identity:ResetState":
michael@0 465 if (!this._identityInternal._debug) {
michael@0 466 return;
michael@0 467 }
michael@0 468 this._initializeState();
michael@0 469 Services.obs.notifyObservers(null, "identity-DOM-state-reset", this._id);
michael@0 470 break;
michael@0 471 case "Identity:RP:Watch:OnLogin":
michael@0 472 // Do we have a watcher?
michael@0 473 if (!this._rpWatcher) {
michael@0 474 this._log("WARNING: Received OnLogin message, but there is no RP watcher");
michael@0 475 return;
michael@0 476 }
michael@0 477
michael@0 478 if (this._rpWatcher.onlogin) {
michael@0 479 if (this._rpWatcher._internal) {
michael@0 480 this._rpWatcher.onlogin(msg.assertion, msg._internalParams);
michael@0 481 } else {
michael@0 482 this._rpWatcher.onlogin(msg.assertion);
michael@0 483 }
michael@0 484 }
michael@0 485 break;
michael@0 486 case "Identity:RP:Watch:OnLogout":
michael@0 487 // Do we have a watcher?
michael@0 488 if (!this._rpWatcher) {
michael@0 489 this._log("WARNING: Received OnLogout message, but there is no RP watcher");
michael@0 490 return;
michael@0 491 }
michael@0 492
michael@0 493 if (this._rpWatcher.onlogout) {
michael@0 494 this._rpWatcher.onlogout();
michael@0 495 }
michael@0 496 break;
michael@0 497 case "Identity:RP:Watch:OnReady":
michael@0 498 // Do we have a watcher?
michael@0 499 if (!this._rpWatcher) {
michael@0 500 this._log("WARNING: Received OnReady message, but there is no RP watcher");
michael@0 501 return;
michael@0 502 }
michael@0 503
michael@0 504 if (this._rpWatcher.onready) {
michael@0 505 this._rpWatcher.onready();
michael@0 506 }
michael@0 507 break;
michael@0 508 case "Identity:RP:Watch:OnCancel":
michael@0 509 // Do we have a watcher?
michael@0 510 if (!this._rpWatcher) {
michael@0 511 this._log("WARNING: Received OnCancel message, but there is no RP watcher");
michael@0 512 return;
michael@0 513 }
michael@0 514
michael@0 515 if (this._onCancelRequestCallback) {
michael@0 516 this._onCancelRequestCallback();
michael@0 517 }
michael@0 518 break;
michael@0 519 case "Identity:RP:Watch:OnError":
michael@0 520 if (!this._rpWatcher) {
michael@0 521 this._log("WARNING: Received OnError message, but there is no RP watcher");
michael@0 522 return;
michael@0 523 }
michael@0 524
michael@0 525 if (this._rpWatcher.onerror) {
michael@0 526 this._rpWatcher.onerror(JSON.stringify({name: msg.message.error}));
michael@0 527 }
michael@0 528 break;
michael@0 529 case "Identity:IDP:CallBeginProvisioningCallback":
michael@0 530 this._callBeginProvisioningCallback(msg);
michael@0 531 break;
michael@0 532 case "Identity:IDP:CallGenKeyPairCallback":
michael@0 533 this._callGenKeyPairCallback(msg);
michael@0 534 break;
michael@0 535 case "Identity:IDP:CallBeginAuthenticationCallback":
michael@0 536 this._callBeginAuthenticationCallback(msg);
michael@0 537 break;
michael@0 538 }
michael@0 539 },
michael@0 540
michael@0 541 _log: function nsDOMIdentity__log(msg) {
michael@0 542 this._identityInternal._log(msg);
michael@0 543 },
michael@0 544
michael@0 545 _callGenKeyPairCallback: function nsDOMIdentity__callGenKeyPairCallback(message) {
michael@0 546 // create a pubkey object that works
michael@0 547 let chrome_pubkey = JSON.parse(message.publicKey);
michael@0 548
michael@0 549 // bunch of stuff to create a proper object in window context
michael@0 550 function genPropDesc(value) {
michael@0 551 return {
michael@0 552 enumerable: true, configurable: true, writable: true, value: value
michael@0 553 };
michael@0 554 }
michael@0 555
michael@0 556 let propList = {};
michael@0 557 for (let k in chrome_pubkey) {
michael@0 558 propList[k] = genPropDesc(chrome_pubkey[k]);
michael@0 559 }
michael@0 560
michael@0 561 let pubkey = Cu.createObjectIn(this._window);
michael@0 562 Object.defineProperties(pubkey, propList);
michael@0 563 Cu.makeObjectPropsNormal(pubkey);
michael@0 564
michael@0 565 // do the callback
michael@0 566 this._genKeyPairCallback(pubkey);
michael@0 567 },
michael@0 568
michael@0 569 _callBeginProvisioningCallback:
michael@0 570 function nsDOMIdentity__callBeginProvisioningCallback(message) {
michael@0 571 let identity = message.identity;
michael@0 572 let certValidityDuration = message.certDuration;
michael@0 573 this._beginProvisioningCallback(identity,
michael@0 574 certValidityDuration);
michael@0 575 },
michael@0 576
michael@0 577 _callBeginAuthenticationCallback:
michael@0 578 function nsDOMIdentity__callBeginAuthenticationCallback(message) {
michael@0 579 let identity = message.identity;
michael@0 580 this._beginAuthenticationCallback(identity);
michael@0 581 },
michael@0 582
michael@0 583 /**
michael@0 584 * Helper to create messages to send using a message manager.
michael@0 585 * Pass through user options if they are not functions. Always
michael@0 586 * overwrite id, origin, audience, and appStatus. The caller
michael@0 587 * does not get to set those.
michael@0 588 */
michael@0 589 DOMIdentityMessage: function DOMIdentityMessage(aOptions) {
michael@0 590 aOptions = aOptions || {};
michael@0 591 let message = {
michael@0 592 errors: []
michael@0 593 };
michael@0 594 let principal = Ci.nsIPrincipal;
michael@0 595
michael@0 596 objectCopy(aOptions, message);
michael@0 597
michael@0 598 // outer window id
michael@0 599 message.id = this._id;
michael@0 600
michael@0 601 // window origin
michael@0 602 message.origin = this._origin;
michael@0 603
michael@0 604 // On b2g, an app's status can be NOT_INSTALLED, INSTALLED, PRIVILEGED, or
michael@0 605 // CERTIFIED. Compare the appStatus value to the constants enumerated in
michael@0 606 // Ci.nsIPrincipal.APP_STATUS_*.
michael@0 607 message.appStatus = this._appStatus;
michael@0 608
michael@0 609 // Currently, we only permit certified and privileged apps to use
michael@0 610 // Firefox Accounts.
michael@0 611 if (aOptions.wantIssuer == "firefox-accounts" &&
michael@0 612 this._appStatus !== principal.APP_STATUS_PRIVILEGED &&
michael@0 613 this._appStatus !== principal.APP_STATUS_CERTIFIED) {
michael@0 614 message.errors.push("ERROR_NOT_AUTHORIZED_FOR_FIREFOX_ACCOUNTS");
michael@0 615 }
michael@0 616
michael@0 617 // Normally the window origin will be the audience in assertions. On b2g,
michael@0 618 // certified apps have the power to override this and declare any audience
michael@0 619 // the want. Privileged apps can also declare a different audience, as
michael@0 620 // long as it is the same as the origin specified in their manifest files.
michael@0 621 // All other apps are stuck with b2g origins of the form app://{guid}.
michael@0 622 // Since such an origin is meaningless for the purposes of verification,
michael@0 623 // they will have to jump through some hoops to sign in: Specifically, they
michael@0 624 // will have to host their sign-in flows and DOM API requests in an iframe,
michael@0 625 // have the iframe xhr post assertions up to their server for verification,
michael@0 626 // and then post-message the results down to their app.
michael@0 627 let _audience = message.origin;
michael@0 628 if (message.audience && message.audience != message.origin) {
michael@0 629 if (this._appStatus === principal.APP_STATUS_CERTIFIED) {
michael@0 630 _audience = message.audience;
michael@0 631 this._log("Certified app setting assertion audience: " + _audience);
michael@0 632 } else {
michael@0 633 message.errors.push("ERROR_INVALID_ASSERTION_AUDIENCE");
michael@0 634 }
michael@0 635 }
michael@0 636
michael@0 637 // Replace any audience supplied by the RP with one that has been sanitised
michael@0 638 message.audience = _audience;
michael@0 639
michael@0 640 this._log("DOMIdentityMessage: " + JSON.stringify(message));
michael@0 641
michael@0 642 return message;
michael@0 643 },
michael@0 644
michael@0 645 uninit: function DOMIdentity_uninit() {
michael@0 646 this._log("nsDOMIdentity uninit() " + this._id);
michael@0 647 this._identityInternal._mm.sendAsyncMessage(
michael@0 648 "Identity:RP:Unwatch",
michael@0 649 { id: this._id }
michael@0 650 );
michael@0 651 }
michael@0 652
michael@0 653 };
michael@0 654
michael@0 655 /**
michael@0 656 * Internal functions that shouldn't be exposed to content.
michael@0 657 */
michael@0 658 function nsDOMIdentityInternal() {
michael@0 659 }
michael@0 660 nsDOMIdentityInternal.prototype = {
michael@0 661
michael@0 662 // nsIMessageListener
michael@0 663 receiveMessage: function nsDOMIdentityInternal_receiveMessage(aMessage) {
michael@0 664 let msg = aMessage.json;
michael@0 665 // Is this message intended for this window?
michael@0 666 if (msg.id != this._id) {
michael@0 667 return;
michael@0 668 }
michael@0 669 this._identity._receiveMessage(aMessage);
michael@0 670 },
michael@0 671
michael@0 672 // nsIObserver
michael@0 673 observe: function nsDOMIdentityInternal_observe(aSubject, aTopic, aData) {
michael@0 674 let wId = aSubject.QueryInterface(Ci.nsISupportsPRUint64).data;
michael@0 675 if (wId != this._innerWindowID) {
michael@0 676 return;
michael@0 677 }
michael@0 678
michael@0 679 this._identity.uninit();
michael@0 680
michael@0 681 Services.obs.removeObserver(this, "inner-window-destroyed");
michael@0 682 this._identity._initializeState();
michael@0 683 this._identity = null;
michael@0 684
michael@0 685 // TODO: Also send message to DOMIdentity notifiying window is no longer valid
michael@0 686 // ie. in the case that the user closes the auth. window and we need to know.
michael@0 687
michael@0 688 try {
michael@0 689 for (let msgName of this._messages) {
michael@0 690 this._mm.removeMessageListener(msgName, this);
michael@0 691 }
michael@0 692 } catch (ex) {
michael@0 693 // Avoid errors when removing more than once.
michael@0 694 }
michael@0 695
michael@0 696 this._mm = null;
michael@0 697 },
michael@0 698
michael@0 699 // nsIDOMGlobalPropertyInitializer
michael@0 700 init: function nsDOMIdentityInternal_init(aWindow) {
michael@0 701 if (Services.prefs.getPrefType(PREF_ENABLED) != Ci.nsIPrefBranch.PREF_BOOL
michael@0 702 || !Services.prefs.getBoolPref(PREF_ENABLED)) {
michael@0 703 return null;
michael@0 704 }
michael@0 705
michael@0 706 this._debug =
michael@0 707 Services.prefs.getPrefType(PREF_DEBUG) == Ci.nsIPrefBranch.PREF_BOOL
michael@0 708 && Services.prefs.getBoolPref(PREF_DEBUG);
michael@0 709
michael@0 710 let util = aWindow.QueryInterface(Ci.nsIInterfaceRequestor)
michael@0 711 .getInterface(Ci.nsIDOMWindowUtils);
michael@0 712
michael@0 713 // To avoid cross-process windowId collisions, use a uuid as an
michael@0 714 // almost certainly unique identifier.
michael@0 715 //
michael@0 716 // XXX Bug 869182 - use a combination of child process id and
michael@0 717 // innerwindow id to construct the unique id.
michael@0 718 this._id = uuidgen.generateUUID().toString();
michael@0 719 this._innerWindowID = util.currentInnerWindowID;
michael@0 720
michael@0 721 // nsDOMIdentity needs to know our _id, so this goes after
michael@0 722 // its creation.
michael@0 723 this._identity = new nsDOMIdentity(this);
michael@0 724 this._identity._init(aWindow);
michael@0 725
michael@0 726 this._log("init was called from " + aWindow.document.location);
michael@0 727
michael@0 728 this._mm = cpmm;
michael@0 729
michael@0 730 // Setup listeners for messages from parent process.
michael@0 731 this._messages = [
michael@0 732 "Identity:ResetState",
michael@0 733 "Identity:RP:Watch:OnLogin",
michael@0 734 "Identity:RP:Watch:OnLogout",
michael@0 735 "Identity:RP:Watch:OnReady",
michael@0 736 "Identity:RP:Watch:OnCancel",
michael@0 737 "Identity:RP:Watch:OnError",
michael@0 738 "Identity:IDP:CallBeginProvisioningCallback",
michael@0 739 "Identity:IDP:CallGenKeyPairCallback",
michael@0 740 "Identity:IDP:CallBeginAuthenticationCallback"
michael@0 741 ];
michael@0 742 this._messages.forEach(function(msgName) {
michael@0 743 this._mm.addMessageListener(msgName, this);
michael@0 744 }, this);
michael@0 745
michael@0 746 // Setup observers so we can remove message listeners.
michael@0 747 Services.obs.addObserver(this, "inner-window-destroyed", false);
michael@0 748
michael@0 749 return this._identity;
michael@0 750 },
michael@0 751
michael@0 752 // Private.
michael@0 753 _log: function nsDOMIdentityInternal__log(msg) {
michael@0 754 if (!this._debug) {
michael@0 755 return;
michael@0 756 }
michael@0 757 dump("nsDOMIdentity (" + this._id + "): " + msg + "\n");
michael@0 758 },
michael@0 759
michael@0 760 // Component setup.
michael@0 761 classID: Components.ID("{210853d9-2c97-4669-9761-b1ab9cbf57ef}"),
michael@0 762
michael@0 763 QueryInterface: XPCOMUtils.generateQI(
michael@0 764 [Ci.nsIDOMGlobalPropertyInitializer, Ci.nsIMessageListener]
michael@0 765 ),
michael@0 766
michael@0 767 classInfo: XPCOMUtils.generateCI({
michael@0 768 classID: Components.ID("{210853d9-2c97-4669-9761-b1ab9cbf57ef}"),
michael@0 769 contractID: "@mozilla.org/dom/identity;1",
michael@0 770 interfaces: [],
michael@0 771 classDescription: "Identity DOM Implementation"
michael@0 772 })
michael@0 773 };
michael@0 774
michael@0 775 function assertCorrectCallbacks(aOptions) {
michael@0 776 // The relying party (RP) provides callbacks on watch().
michael@0 777 //
michael@0 778 // In the future, BrowserID will probably only require an onlogin()
michael@0 779 // callback, lifting the requirement that BrowserID handle logged-in
michael@0 780 // state management for RPs. See
michael@0 781 // https://github.com/mozilla/id-specs/blob/greenfield/browserid/api-rp.md
michael@0 782 //
michael@0 783 // However, Firefox Accounts requires callers to provide onlogout(),
michael@0 784 // onready(), and also supports an onerror() callback.
michael@0 785
michael@0 786 let requiredCallbacks = ["onlogin"];
michael@0 787 let optionalCallbacks = ["onlogout", "onready", "onerror"];
michael@0 788
michael@0 789 if (aOptions.wantIssuer == "firefox-accounts") {
michael@0 790 requiredCallbacks = ["onlogin", "onlogout", "onready"];
michael@0 791 optionalCallbacks = ["onerror"];
michael@0 792 }
michael@0 793
michael@0 794 for (let cbName of requiredCallbacks) {
michael@0 795 if (typeof(aOptions[cbName]) != "function") {
michael@0 796 throw new Error(cbName + " callback is required.");
michael@0 797 }
michael@0 798 }
michael@0 799
michael@0 800 for (let cbName of optionalCallbacks) {
michael@0 801 if (aOptions[cbName] && typeof(aOptions[cbName]) != "function") {
michael@0 802 throw new Error(cbName + " must be a function");
michael@0 803 }
michael@0 804 }
michael@0 805 }
michael@0 806
michael@0 807 this.NSGetFactory = XPCOMUtils.generateNSGetFactory([nsDOMIdentityInternal]);

mercurial