1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/dom/identity/nsDOMIdentity.js Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,807 @@ 1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this file, 1.6 + * You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.7 + 1.8 +"use strict"; 1.9 + 1.10 +const {classes: Cc, interfaces: Ci, utils: Cu} = Components; 1.11 + 1.12 +const PREF_DEBUG = "toolkit.identity.debug"; 1.13 +const PREF_ENABLED = "dom.identity.enabled"; 1.14 + 1.15 +// Bug 822450: Workaround for Bug 821740. When testing with marionette, 1.16 +// relax navigator.id.request's requirement that it be handling native 1.17 +// events. Synthetic marionette events are ok. 1.18 +const PREF_SYNTHETIC_EVENTS_OK = "dom.identity.syntheticEventsOk"; 1.19 + 1.20 +// Maximum length of a string that will go through IPC 1.21 +const MAX_STRING_LENGTH = 2048; 1.22 +// Maximum number of times navigator.id.request can be called for a document 1.23 +const MAX_RP_CALLS = 100; 1.24 + 1.25 +Cu.import("resource://gre/modules/Services.jsm"); 1.26 +Cu.import("resource://gre/modules/XPCOMUtils.jsm"); 1.27 + 1.28 +XPCOMUtils.defineLazyModuleGetter(this, "checkDeprecated", 1.29 + "resource://gre/modules/identity/IdentityUtils.jsm"); 1.30 +XPCOMUtils.defineLazyModuleGetter(this, "checkRenamed", 1.31 + "resource://gre/modules/identity/IdentityUtils.jsm"); 1.32 +XPCOMUtils.defineLazyModuleGetter(this, "objectCopy", 1.33 + "resource://gre/modules/identity/IdentityUtils.jsm"); 1.34 + 1.35 +XPCOMUtils.defineLazyServiceGetter(this, "uuidgen", 1.36 + "@mozilla.org/uuid-generator;1", 1.37 + "nsIUUIDGenerator"); 1.38 + 1.39 +// This is the child process corresponding to nsIDOMIdentity 1.40 +XPCOMUtils.defineLazyServiceGetter(this, "cpmm", 1.41 + "@mozilla.org/childprocessmessagemanager;1", 1.42 + "nsIMessageSender"); 1.43 + 1.44 + 1.45 +const ERRORS = { 1.46 + "ERROR_NOT_AUTHORIZED_FOR_FIREFOX_ACCOUNTS": 1.47 + "Only privileged and certified apps may use Firefox Accounts", 1.48 + "ERROR_INVALID_ASSERTION_AUDIENCE": 1.49 + "Assertion audience may not differ from origin", 1.50 + "ERROR_REQUEST_WHILE_NOT_HANDLING_USER_INPUT": 1.51 + "The request() method may only be invoked when handling user input", 1.52 +}; 1.53 + 1.54 +function nsDOMIdentity(aIdentityInternal) { 1.55 + this._identityInternal = aIdentityInternal; 1.56 +} 1.57 +nsDOMIdentity.prototype = { 1.58 + __exposedProps__: { 1.59 + // Relying Party (RP) 1.60 + watch: 'r', 1.61 + request: 'r', 1.62 + logout: 'r', 1.63 + get: 'r', 1.64 + getVerifiedEmail: 'r', 1.65 + 1.66 + // Provisioning 1.67 + beginProvisioning: 'r', 1.68 + genKeyPair: 'r', 1.69 + registerCertificate: 'r', 1.70 + raiseProvisioningFailure: 'r', 1.71 + 1.72 + // Authentication 1.73 + beginAuthentication: 'r', 1.74 + completeAuthentication: 'r', 1.75 + raiseAuthenticationFailure: 'r' 1.76 + }, 1.77 + 1.78 + // require native events unless syntheticEventsOk is set 1.79 + get nativeEventsRequired() { 1.80 + if (Services.prefs.prefHasUserValue(PREF_SYNTHETIC_EVENTS_OK) && 1.81 + (Services.prefs.getPrefType(PREF_SYNTHETIC_EVENTS_OK) === 1.82 + Ci.nsIPrefBranch.PREF_BOOL)) { 1.83 + return !Services.prefs.getBoolPref(PREF_SYNTHETIC_EVENTS_OK); 1.84 + } 1.85 + return true; 1.86 + }, 1.87 + 1.88 + reportErrors: function(message) { 1.89 + let onerror = function() {}; 1.90 + if (this._rpWatcher && this._rpWatcher.onerror) { 1.91 + onerror = this._rpWatcher.onerror; 1.92 + } 1.93 + 1.94 + message.errors.forEach((error) => { 1.95 + // Report an error string to content 1.96 + Cu.reportError(ERRORS[error]); 1.97 + 1.98 + // Report error code to RP callback, if available 1.99 + onerror(error); 1.100 + }); 1.101 + }, 1.102 + 1.103 + /** 1.104 + * Relying Party (RP) APIs 1.105 + */ 1.106 + 1.107 + watch: function nsDOMIdentity_watch(aOptions = {}) { 1.108 + if (this._rpWatcher) { 1.109 + // For the initial release of Firefox Accounts, we support callers who 1.110 + // invoke watch() either for Firefox Accounts, or Persona, but not both. 1.111 + // In the future, we may wish to support the dual invocation (say, for 1.112 + // packaged apps so they can sign users in who reject the app's request 1.113 + // to sign in with their Firefox Accounts identity). 1.114 + throw new Error("navigator.id.watch was already called"); 1.115 + } 1.116 + 1.117 + assertCorrectCallbacks(aOptions); 1.118 + 1.119 + let message = this.DOMIdentityMessage(aOptions); 1.120 + 1.121 + // loggedInUser vs loggedInEmail 1.122 + // https://developer.mozilla.org/en-US/docs/DOM/navigator.id.watch 1.123 + // This parameter, loggedInUser, was renamed from loggedInEmail in early 1.124 + // September, 2012. Both names will continue to work for the time being, 1.125 + // but code should be changed to use loggedInUser instead. 1.126 + checkRenamed(aOptions, "loggedInEmail", "loggedInUser"); 1.127 + message["loggedInUser"] = aOptions["loggedInUser"]; 1.128 + 1.129 + let emailType = typeof(aOptions["loggedInUser"]); 1.130 + if (aOptions["loggedInUser"] && aOptions["loggedInUser"] !== "undefined") { 1.131 + if (emailType !== "string") { 1.132 + throw new Error("loggedInUser must be a String or null"); 1.133 + } 1.134 + 1.135 + // TODO: Bug 767610 - check email format. 1.136 + // See HTMLInputElement::IsValidEmailAddress 1.137 + if (aOptions["loggedInUser"].indexOf("@") == -1 1.138 + || aOptions["loggedInUser"].length > MAX_STRING_LENGTH) { 1.139 + throw new Error("loggedInUser is not valid"); 1.140 + } 1.141 + // Set loggedInUser in this block that "undefined" doesn't get through. 1.142 + message.loggedInUser = aOptions.loggedInUser; 1.143 + } 1.144 + this._log("loggedInUser: " + message.loggedInUser); 1.145 + 1.146 + this._rpWatcher = aOptions; 1.147 + this._rpWatcher.audience = message.audience; 1.148 + 1.149 + if (message.errors.length) { 1.150 + this.reportErrors(message); 1.151 + // We don't delete the rpWatcher object, because we don't want the 1.152 + // broken client to be able to call watch() any more. It's broken. 1.153 + return; 1.154 + } 1.155 + this._identityInternal._mm.sendAsyncMessage("Identity:RP:Watch", message); 1.156 + }, 1.157 + 1.158 + request: function nsDOMIdentity_request(aOptions = {}) { 1.159 + this._log("request: " + JSON.stringify(aOptions)); 1.160 + 1.161 + // Has the caller called watch() before this? 1.162 + if (!this._rpWatcher) { 1.163 + throw new Error("navigator.id.request called before navigator.id.watch"); 1.164 + } 1.165 + if (this._rpCalls > MAX_RP_CALLS) { 1.166 + throw new Error("navigator.id.request called too many times"); 1.167 + } 1.168 + 1.169 + let util = this._window.QueryInterface(Ci.nsIInterfaceRequestor) 1.170 + .getInterface(Ci.nsIDOMWindowUtils); 1.171 + 1.172 + let message = this.DOMIdentityMessage(aOptions); 1.173 + 1.174 + // We permit calling of request() outside of a user input handler only when 1.175 + // a certified or privileged app is calling, or when we are handling the 1.176 + // (deprecated) get() or getVerifiedEmail() calls, which make use of an RP 1.177 + // context marked as _internal. 1.178 + 1.179 + if (!aOptions._internal && 1.180 + this._appStatus !== Ci.nsIPrincipal.APP_STATUS_CERTIFIED && 1.181 + this._appStatus !== Ci.nsIPrincipal.APP_STATUS_PRIVILEGED) { 1.182 + 1.183 + // If the caller is not special in one of those ways, see if the user has 1.184 + // preffed on 'syntheticEventsOk' (useful for testing); otherwise, if 1.185 + // this is a non-native event, reject it. 1.186 + let util = this._window.QueryInterface(Ci.nsIInterfaceRequestor) 1.187 + .getInterface(Ci.nsIDOMWindowUtils); 1.188 + 1.189 + if (!util.isHandlingUserInput && this.nativeEventsRequired) { 1.190 + message.errors.push("ERROR_REQUEST_WHILE_NOT_HANDLING_USER_INPUT"); 1.191 + } 1.192 + } 1.193 + 1.194 + // Report and fail hard on any errors. 1.195 + if (message.errors.length) { 1.196 + this.reportErrors(message); 1.197 + return; 1.198 + } 1.199 + 1.200 + if (aOptions) { 1.201 + // Optional string properties 1.202 + let optionalStringProps = ["privacyPolicy", "termsOfService"]; 1.203 + for (let propName of optionalStringProps) { 1.204 + if (!aOptions[propName] || aOptions[propName] === "undefined") 1.205 + continue; 1.206 + if (typeof(aOptions[propName]) !== "string") { 1.207 + throw new Error(propName + " must be a string representing a URL."); 1.208 + } 1.209 + if (aOptions[propName].length > MAX_STRING_LENGTH) { 1.210 + throw new Error(propName + " is invalid."); 1.211 + } 1.212 + message[propName] = aOptions[propName]; 1.213 + } 1.214 + 1.215 + if (aOptions["oncancel"] 1.216 + && typeof(aOptions["oncancel"]) !== "function") { 1.217 + throw new Error("oncancel is not a function"); 1.218 + } else { 1.219 + // Store optional cancel callback for later. 1.220 + this._onCancelRequestCallback = aOptions.oncancel; 1.221 + } 1.222 + } 1.223 + 1.224 + this._rpCalls++; 1.225 + this._identityInternal._mm.sendAsyncMessage("Identity:RP:Request", message); 1.226 + }, 1.227 + 1.228 + logout: function nsDOMIdentity_logout() { 1.229 + if (!this._rpWatcher) { 1.230 + throw new Error("navigator.id.logout called before navigator.id.watch"); 1.231 + } 1.232 + if (this._rpCalls > MAX_RP_CALLS) { 1.233 + throw new Error("navigator.id.logout called too many times"); 1.234 + } 1.235 + 1.236 + this._rpCalls++; 1.237 + let message = this.DOMIdentityMessage(); 1.238 + 1.239 + // Report and fail hard on any errors. 1.240 + if (message.errors.length) { 1.241 + this.reportErrors(message); 1.242 + return; 1.243 + } 1.244 + 1.245 + this._identityInternal._mm.sendAsyncMessage("Identity:RP:Logout", message); 1.246 + }, 1.247 + 1.248 + /* 1.249 + * Get an assertion. This function is deprecated. RPs are 1.250 + * encouraged to use the observer API instead (watch + request). 1.251 + */ 1.252 + get: function nsDOMIdentity_get(aCallback, aOptions) { 1.253 + var opts = {}; 1.254 + aOptions = aOptions || {}; 1.255 + 1.256 + // We use the observer API (watch + request) to implement get(). 1.257 + // Because the caller can call get() and getVerifiedEmail() as 1.258 + // many times as they want, we lift the restriction that watch() can 1.259 + // only be called once. 1.260 + this._rpWatcher = null; 1.261 + 1.262 + // This flag tells internal_api.js (in the shim) to record in the 1.263 + // login parameters whether the assertion was acquired silently or 1.264 + // with user interaction. 1.265 + opts._internal = true; 1.266 + 1.267 + opts.privacyPolicy = aOptions.privacyPolicy || undefined; 1.268 + opts.termsOfService = aOptions.termsOfService || undefined; 1.269 + opts.privacyURL = aOptions.privacyURL || undefined; 1.270 + opts.tosURL = aOptions.tosURL || undefined; 1.271 + opts.siteName = aOptions.siteName || undefined; 1.272 + opts.siteLogo = aOptions.siteLogo || undefined; 1.273 + 1.274 + opts.oncancel = function get_oncancel() { 1.275 + if (aCallback) { 1.276 + aCallback(null); 1.277 + aCallback = null; 1.278 + } 1.279 + }; 1.280 + 1.281 + if (checkDeprecated(aOptions, "silent")) { 1.282 + // Silent has been deprecated, do nothing. Placing the check here 1.283 + // prevents the callback from being called twice, once with null and 1.284 + // once after internalWatch has been called. See issue #1532: 1.285 + // https://github.com/mozilla/browserid/issues/1532 1.286 + if (aCallback) { 1.287 + setTimeout(function() { aCallback(null); }, 0); 1.288 + } 1.289 + return; 1.290 + } 1.291 + 1.292 + // Get an assertion by using our observer api: watch + request. 1.293 + var self = this; 1.294 + this.watch({ 1.295 + _internal: true, 1.296 + onlogin: function get_onlogin(assertion, internalParams) { 1.297 + if (assertion && aCallback && internalParams && !internalParams.silent) { 1.298 + aCallback(assertion); 1.299 + aCallback = null; 1.300 + } 1.301 + }, 1.302 + onlogout: function get_onlogout() {}, 1.303 + onready: function get_onready() { 1.304 + self.request(opts); 1.305 + } 1.306 + }); 1.307 + }, 1.308 + 1.309 + getVerifiedEmail: function nsDOMIdentity_getVerifiedEmail(aCallback) { 1.310 + Cu.reportError("WARNING: getVerifiedEmail has been deprecated"); 1.311 + this.get(aCallback, {}); 1.312 + }, 1.313 + 1.314 + /** 1.315 + * Identity Provider (IDP) Provisioning APIs 1.316 + */ 1.317 + 1.318 + beginProvisioning: function nsDOMIdentity_beginProvisioning(aCallback) { 1.319 + this._log("beginProvisioning"); 1.320 + if (this._beginProvisioningCallback) { 1.321 + throw new Error("navigator.id.beginProvisioning already called."); 1.322 + } 1.323 + if (!aCallback || typeof(aCallback) !== "function") { 1.324 + throw new Error("beginProvisioning callback is required."); 1.325 + } 1.326 + 1.327 + this._beginProvisioningCallback = aCallback; 1.328 + this._identityInternal._mm.sendAsyncMessage("Identity:IDP:BeginProvisioning", 1.329 + this.DOMIdentityMessage()); 1.330 + }, 1.331 + 1.332 + genKeyPair: function nsDOMIdentity_genKeyPair(aCallback) { 1.333 + this._log("genKeyPair"); 1.334 + if (!this._beginProvisioningCallback) { 1.335 + throw new Error("navigator.id.genKeyPair called outside of provisioning"); 1.336 + } 1.337 + if (this._genKeyPairCallback) { 1.338 + throw new Error("navigator.id.genKeyPair already called."); 1.339 + } 1.340 + if (!aCallback || typeof(aCallback) !== "function") { 1.341 + throw new Error("genKeyPair callback is required."); 1.342 + } 1.343 + 1.344 + this._genKeyPairCallback = aCallback; 1.345 + this._identityInternal._mm.sendAsyncMessage("Identity:IDP:GenKeyPair", 1.346 + this.DOMIdentityMessage()); 1.347 + }, 1.348 + 1.349 + registerCertificate: function nsDOMIdentity_registerCertificate(aCertificate) { 1.350 + this._log("registerCertificate"); 1.351 + if (!this._genKeyPairCallback) { 1.352 + throw new Error("navigator.id.registerCertificate called outside of provisioning"); 1.353 + } 1.354 + if (this._provisioningEnded) { 1.355 + throw new Error("Provisioning already ended"); 1.356 + } 1.357 + this._provisioningEnded = true; 1.358 + 1.359 + let message = this.DOMIdentityMessage(); 1.360 + message.cert = aCertificate; 1.361 + this._identityInternal._mm.sendAsyncMessage("Identity:IDP:RegisterCertificate", message); 1.362 + }, 1.363 + 1.364 + raiseProvisioningFailure: function nsDOMIdentity_raiseProvisioningFailure(aReason) { 1.365 + this._log("raiseProvisioningFailure '" + aReason + "'"); 1.366 + if (this._provisioningEnded) { 1.367 + throw new Error("Provisioning already ended"); 1.368 + } 1.369 + if (!aReason || typeof(aReason) != "string") { 1.370 + throw new Error("raiseProvisioningFailure reason is required"); 1.371 + } 1.372 + this._provisioningEnded = true; 1.373 + 1.374 + let message = this.DOMIdentityMessage(); 1.375 + message.reason = aReason; 1.376 + this._identityInternal._mm.sendAsyncMessage("Identity:IDP:ProvisioningFailure", message); 1.377 + }, 1.378 + 1.379 + /** 1.380 + * Identity Provider (IDP) Authentication APIs 1.381 + */ 1.382 + 1.383 + beginAuthentication: function nsDOMIdentity_beginAuthentication(aCallback) { 1.384 + this._log("beginAuthentication"); 1.385 + if (this._beginAuthenticationCallback) { 1.386 + throw new Error("navigator.id.beginAuthentication already called."); 1.387 + } 1.388 + if (typeof(aCallback) !== "function") { 1.389 + throw new Error("beginAuthentication callback is required."); 1.390 + } 1.391 + if (!aCallback || typeof(aCallback) !== "function") { 1.392 + throw new Error("beginAuthentication callback is required."); 1.393 + } 1.394 + 1.395 + this._beginAuthenticationCallback = aCallback; 1.396 + this._identityInternal._mm.sendAsyncMessage("Identity:IDP:BeginAuthentication", 1.397 + this.DOMIdentityMessage()); 1.398 + }, 1.399 + 1.400 + completeAuthentication: function nsDOMIdentity_completeAuthentication() { 1.401 + if (this._authenticationEnded) { 1.402 + throw new Error("Authentication already ended"); 1.403 + } 1.404 + if (!this._beginAuthenticationCallback) { 1.405 + throw new Error("navigator.id.completeAuthentication called outside of authentication"); 1.406 + } 1.407 + this._authenticationEnded = true; 1.408 + 1.409 + this._identityInternal._mm.sendAsyncMessage("Identity:IDP:CompleteAuthentication", 1.410 + this.DOMIdentityMessage()); 1.411 + }, 1.412 + 1.413 + raiseAuthenticationFailure: function nsDOMIdentity_raiseAuthenticationFailure(aReason) { 1.414 + if (this._authenticationEnded) { 1.415 + throw new Error("Authentication already ended"); 1.416 + } 1.417 + if (!aReason || typeof(aReason) != "string") { 1.418 + throw new Error("raiseProvisioningFailure reason is required"); 1.419 + } 1.420 + 1.421 + let message = this.DOMIdentityMessage(); 1.422 + message.reason = aReason; 1.423 + this._identityInternal._mm.sendAsyncMessage("Identity:IDP:AuthenticationFailure", message); 1.424 + }, 1.425 + 1.426 + // Private. 1.427 + _init: function nsDOMIdentity__init(aWindow) { 1.428 + 1.429 + this._initializeState(); 1.430 + 1.431 + // Store window and origin URI. 1.432 + this._window = aWindow; 1.433 + this._origin = aWindow.document.nodePrincipal.origin; 1.434 + this._appStatus = aWindow.document.nodePrincipal.appStatus; 1.435 + this._appId = aWindow.document.nodePrincipal.appId; 1.436 + 1.437 + // Setup identifiers for current window. 1.438 + let util = aWindow.QueryInterface(Ci.nsIInterfaceRequestor) 1.439 + .getInterface(Ci.nsIDOMWindowUtils); 1.440 + 1.441 + // We need to inherit the id from the internalIdentity service. 1.442 + // See comments below in that service's init. 1.443 + this._id = this._identityInternal._id; 1.444 + }, 1.445 + 1.446 + /** 1.447 + * Called during init and shutdown. 1.448 + */ 1.449 + _initializeState: function nsDOMIdentity__initializeState() { 1.450 + // Some state to prevent abuse 1.451 + // Limit the number of calls to .request 1.452 + this._rpCalls = 0; 1.453 + this._provisioningEnded = false; 1.454 + this._authenticationEnded = false; 1.455 + 1.456 + this._rpWatcher = null; 1.457 + this._onCancelRequestCallback = null; 1.458 + this._beginProvisioningCallback = null; 1.459 + this._genKeyPairCallback = null; 1.460 + this._beginAuthenticationCallback = null; 1.461 + }, 1.462 + 1.463 + _receiveMessage: function nsDOMIdentity_receiveMessage(aMessage) { 1.464 + let msg = aMessage.json; 1.465 + 1.466 + switch (aMessage.name) { 1.467 + case "Identity:ResetState": 1.468 + if (!this._identityInternal._debug) { 1.469 + return; 1.470 + } 1.471 + this._initializeState(); 1.472 + Services.obs.notifyObservers(null, "identity-DOM-state-reset", this._id); 1.473 + break; 1.474 + case "Identity:RP:Watch:OnLogin": 1.475 + // Do we have a watcher? 1.476 + if (!this._rpWatcher) { 1.477 + this._log("WARNING: Received OnLogin message, but there is no RP watcher"); 1.478 + return; 1.479 + } 1.480 + 1.481 + if (this._rpWatcher.onlogin) { 1.482 + if (this._rpWatcher._internal) { 1.483 + this._rpWatcher.onlogin(msg.assertion, msg._internalParams); 1.484 + } else { 1.485 + this._rpWatcher.onlogin(msg.assertion); 1.486 + } 1.487 + } 1.488 + break; 1.489 + case "Identity:RP:Watch:OnLogout": 1.490 + // Do we have a watcher? 1.491 + if (!this._rpWatcher) { 1.492 + this._log("WARNING: Received OnLogout message, but there is no RP watcher"); 1.493 + return; 1.494 + } 1.495 + 1.496 + if (this._rpWatcher.onlogout) { 1.497 + this._rpWatcher.onlogout(); 1.498 + } 1.499 + break; 1.500 + case "Identity:RP:Watch:OnReady": 1.501 + // Do we have a watcher? 1.502 + if (!this._rpWatcher) { 1.503 + this._log("WARNING: Received OnReady message, but there is no RP watcher"); 1.504 + return; 1.505 + } 1.506 + 1.507 + if (this._rpWatcher.onready) { 1.508 + this._rpWatcher.onready(); 1.509 + } 1.510 + break; 1.511 + case "Identity:RP:Watch:OnCancel": 1.512 + // Do we have a watcher? 1.513 + if (!this._rpWatcher) { 1.514 + this._log("WARNING: Received OnCancel message, but there is no RP watcher"); 1.515 + return; 1.516 + } 1.517 + 1.518 + if (this._onCancelRequestCallback) { 1.519 + this._onCancelRequestCallback(); 1.520 + } 1.521 + break; 1.522 + case "Identity:RP:Watch:OnError": 1.523 + if (!this._rpWatcher) { 1.524 + this._log("WARNING: Received OnError message, but there is no RP watcher"); 1.525 + return; 1.526 + } 1.527 + 1.528 + if (this._rpWatcher.onerror) { 1.529 + this._rpWatcher.onerror(JSON.stringify({name: msg.message.error})); 1.530 + } 1.531 + break; 1.532 + case "Identity:IDP:CallBeginProvisioningCallback": 1.533 + this._callBeginProvisioningCallback(msg); 1.534 + break; 1.535 + case "Identity:IDP:CallGenKeyPairCallback": 1.536 + this._callGenKeyPairCallback(msg); 1.537 + break; 1.538 + case "Identity:IDP:CallBeginAuthenticationCallback": 1.539 + this._callBeginAuthenticationCallback(msg); 1.540 + break; 1.541 + } 1.542 + }, 1.543 + 1.544 + _log: function nsDOMIdentity__log(msg) { 1.545 + this._identityInternal._log(msg); 1.546 + }, 1.547 + 1.548 + _callGenKeyPairCallback: function nsDOMIdentity__callGenKeyPairCallback(message) { 1.549 + // create a pubkey object that works 1.550 + let chrome_pubkey = JSON.parse(message.publicKey); 1.551 + 1.552 + // bunch of stuff to create a proper object in window context 1.553 + function genPropDesc(value) { 1.554 + return { 1.555 + enumerable: true, configurable: true, writable: true, value: value 1.556 + }; 1.557 + } 1.558 + 1.559 + let propList = {}; 1.560 + for (let k in chrome_pubkey) { 1.561 + propList[k] = genPropDesc(chrome_pubkey[k]); 1.562 + } 1.563 + 1.564 + let pubkey = Cu.createObjectIn(this._window); 1.565 + Object.defineProperties(pubkey, propList); 1.566 + Cu.makeObjectPropsNormal(pubkey); 1.567 + 1.568 + // do the callback 1.569 + this._genKeyPairCallback(pubkey); 1.570 + }, 1.571 + 1.572 + _callBeginProvisioningCallback: 1.573 + function nsDOMIdentity__callBeginProvisioningCallback(message) { 1.574 + let identity = message.identity; 1.575 + let certValidityDuration = message.certDuration; 1.576 + this._beginProvisioningCallback(identity, 1.577 + certValidityDuration); 1.578 + }, 1.579 + 1.580 + _callBeginAuthenticationCallback: 1.581 + function nsDOMIdentity__callBeginAuthenticationCallback(message) { 1.582 + let identity = message.identity; 1.583 + this._beginAuthenticationCallback(identity); 1.584 + }, 1.585 + 1.586 + /** 1.587 + * Helper to create messages to send using a message manager. 1.588 + * Pass through user options if they are not functions. Always 1.589 + * overwrite id, origin, audience, and appStatus. The caller 1.590 + * does not get to set those. 1.591 + */ 1.592 + DOMIdentityMessage: function DOMIdentityMessage(aOptions) { 1.593 + aOptions = aOptions || {}; 1.594 + let message = { 1.595 + errors: [] 1.596 + }; 1.597 + let principal = Ci.nsIPrincipal; 1.598 + 1.599 + objectCopy(aOptions, message); 1.600 + 1.601 + // outer window id 1.602 + message.id = this._id; 1.603 + 1.604 + // window origin 1.605 + message.origin = this._origin; 1.606 + 1.607 + // On b2g, an app's status can be NOT_INSTALLED, INSTALLED, PRIVILEGED, or 1.608 + // CERTIFIED. Compare the appStatus value to the constants enumerated in 1.609 + // Ci.nsIPrincipal.APP_STATUS_*. 1.610 + message.appStatus = this._appStatus; 1.611 + 1.612 + // Currently, we only permit certified and privileged apps to use 1.613 + // Firefox Accounts. 1.614 + if (aOptions.wantIssuer == "firefox-accounts" && 1.615 + this._appStatus !== principal.APP_STATUS_PRIVILEGED && 1.616 + this._appStatus !== principal.APP_STATUS_CERTIFIED) { 1.617 + message.errors.push("ERROR_NOT_AUTHORIZED_FOR_FIREFOX_ACCOUNTS"); 1.618 + } 1.619 + 1.620 + // Normally the window origin will be the audience in assertions. On b2g, 1.621 + // certified apps have the power to override this and declare any audience 1.622 + // the want. Privileged apps can also declare a different audience, as 1.623 + // long as it is the same as the origin specified in their manifest files. 1.624 + // All other apps are stuck with b2g origins of the form app://{guid}. 1.625 + // Since such an origin is meaningless for the purposes of verification, 1.626 + // they will have to jump through some hoops to sign in: Specifically, they 1.627 + // will have to host their sign-in flows and DOM API requests in an iframe, 1.628 + // have the iframe xhr post assertions up to their server for verification, 1.629 + // and then post-message the results down to their app. 1.630 + let _audience = message.origin; 1.631 + if (message.audience && message.audience != message.origin) { 1.632 + if (this._appStatus === principal.APP_STATUS_CERTIFIED) { 1.633 + _audience = message.audience; 1.634 + this._log("Certified app setting assertion audience: " + _audience); 1.635 + } else { 1.636 + message.errors.push("ERROR_INVALID_ASSERTION_AUDIENCE"); 1.637 + } 1.638 + } 1.639 + 1.640 + // Replace any audience supplied by the RP with one that has been sanitised 1.641 + message.audience = _audience; 1.642 + 1.643 + this._log("DOMIdentityMessage: " + JSON.stringify(message)); 1.644 + 1.645 + return message; 1.646 + }, 1.647 + 1.648 + uninit: function DOMIdentity_uninit() { 1.649 + this._log("nsDOMIdentity uninit() " + this._id); 1.650 + this._identityInternal._mm.sendAsyncMessage( 1.651 + "Identity:RP:Unwatch", 1.652 + { id: this._id } 1.653 + ); 1.654 + } 1.655 + 1.656 +}; 1.657 + 1.658 +/** 1.659 + * Internal functions that shouldn't be exposed to content. 1.660 + */ 1.661 +function nsDOMIdentityInternal() { 1.662 +} 1.663 +nsDOMIdentityInternal.prototype = { 1.664 + 1.665 + // nsIMessageListener 1.666 + receiveMessage: function nsDOMIdentityInternal_receiveMessage(aMessage) { 1.667 + let msg = aMessage.json; 1.668 + // Is this message intended for this window? 1.669 + if (msg.id != this._id) { 1.670 + return; 1.671 + } 1.672 + this._identity._receiveMessage(aMessage); 1.673 + }, 1.674 + 1.675 + // nsIObserver 1.676 + observe: function nsDOMIdentityInternal_observe(aSubject, aTopic, aData) { 1.677 + let wId = aSubject.QueryInterface(Ci.nsISupportsPRUint64).data; 1.678 + if (wId != this._innerWindowID) { 1.679 + return; 1.680 + } 1.681 + 1.682 + this._identity.uninit(); 1.683 + 1.684 + Services.obs.removeObserver(this, "inner-window-destroyed"); 1.685 + this._identity._initializeState(); 1.686 + this._identity = null; 1.687 + 1.688 + // TODO: Also send message to DOMIdentity notifiying window is no longer valid 1.689 + // ie. in the case that the user closes the auth. window and we need to know. 1.690 + 1.691 + try { 1.692 + for (let msgName of this._messages) { 1.693 + this._mm.removeMessageListener(msgName, this); 1.694 + } 1.695 + } catch (ex) { 1.696 + // Avoid errors when removing more than once. 1.697 + } 1.698 + 1.699 + this._mm = null; 1.700 + }, 1.701 + 1.702 + // nsIDOMGlobalPropertyInitializer 1.703 + init: function nsDOMIdentityInternal_init(aWindow) { 1.704 + if (Services.prefs.getPrefType(PREF_ENABLED) != Ci.nsIPrefBranch.PREF_BOOL 1.705 + || !Services.prefs.getBoolPref(PREF_ENABLED)) { 1.706 + return null; 1.707 + } 1.708 + 1.709 + this._debug = 1.710 + Services.prefs.getPrefType(PREF_DEBUG) == Ci.nsIPrefBranch.PREF_BOOL 1.711 + && Services.prefs.getBoolPref(PREF_DEBUG); 1.712 + 1.713 + let util = aWindow.QueryInterface(Ci.nsIInterfaceRequestor) 1.714 + .getInterface(Ci.nsIDOMWindowUtils); 1.715 + 1.716 + // To avoid cross-process windowId collisions, use a uuid as an 1.717 + // almost certainly unique identifier. 1.718 + // 1.719 + // XXX Bug 869182 - use a combination of child process id and 1.720 + // innerwindow id to construct the unique id. 1.721 + this._id = uuidgen.generateUUID().toString(); 1.722 + this._innerWindowID = util.currentInnerWindowID; 1.723 + 1.724 + // nsDOMIdentity needs to know our _id, so this goes after 1.725 + // its creation. 1.726 + this._identity = new nsDOMIdentity(this); 1.727 + this._identity._init(aWindow); 1.728 + 1.729 + this._log("init was called from " + aWindow.document.location); 1.730 + 1.731 + this._mm = cpmm; 1.732 + 1.733 + // Setup listeners for messages from parent process. 1.734 + this._messages = [ 1.735 + "Identity:ResetState", 1.736 + "Identity:RP:Watch:OnLogin", 1.737 + "Identity:RP:Watch:OnLogout", 1.738 + "Identity:RP:Watch:OnReady", 1.739 + "Identity:RP:Watch:OnCancel", 1.740 + "Identity:RP:Watch:OnError", 1.741 + "Identity:IDP:CallBeginProvisioningCallback", 1.742 + "Identity:IDP:CallGenKeyPairCallback", 1.743 + "Identity:IDP:CallBeginAuthenticationCallback" 1.744 + ]; 1.745 + this._messages.forEach(function(msgName) { 1.746 + this._mm.addMessageListener(msgName, this); 1.747 + }, this); 1.748 + 1.749 + // Setup observers so we can remove message listeners. 1.750 + Services.obs.addObserver(this, "inner-window-destroyed", false); 1.751 + 1.752 + return this._identity; 1.753 + }, 1.754 + 1.755 + // Private. 1.756 + _log: function nsDOMIdentityInternal__log(msg) { 1.757 + if (!this._debug) { 1.758 + return; 1.759 + } 1.760 + dump("nsDOMIdentity (" + this._id + "): " + msg + "\n"); 1.761 + }, 1.762 + 1.763 + // Component setup. 1.764 + classID: Components.ID("{210853d9-2c97-4669-9761-b1ab9cbf57ef}"), 1.765 + 1.766 + QueryInterface: XPCOMUtils.generateQI( 1.767 + [Ci.nsIDOMGlobalPropertyInitializer, Ci.nsIMessageListener] 1.768 + ), 1.769 + 1.770 + classInfo: XPCOMUtils.generateCI({ 1.771 + classID: Components.ID("{210853d9-2c97-4669-9761-b1ab9cbf57ef}"), 1.772 + contractID: "@mozilla.org/dom/identity;1", 1.773 + interfaces: [], 1.774 + classDescription: "Identity DOM Implementation" 1.775 + }) 1.776 +}; 1.777 + 1.778 +function assertCorrectCallbacks(aOptions) { 1.779 + // The relying party (RP) provides callbacks on watch(). 1.780 + // 1.781 + // In the future, BrowserID will probably only require an onlogin() 1.782 + // callback, lifting the requirement that BrowserID handle logged-in 1.783 + // state management for RPs. See 1.784 + // https://github.com/mozilla/id-specs/blob/greenfield/browserid/api-rp.md 1.785 + // 1.786 + // However, Firefox Accounts requires callers to provide onlogout(), 1.787 + // onready(), and also supports an onerror() callback. 1.788 + 1.789 + let requiredCallbacks = ["onlogin"]; 1.790 + let optionalCallbacks = ["onlogout", "onready", "onerror"]; 1.791 + 1.792 + if (aOptions.wantIssuer == "firefox-accounts") { 1.793 + requiredCallbacks = ["onlogin", "onlogout", "onready"]; 1.794 + optionalCallbacks = ["onerror"]; 1.795 + } 1.796 + 1.797 + for (let cbName of requiredCallbacks) { 1.798 + if (typeof(aOptions[cbName]) != "function") { 1.799 + throw new Error(cbName + " callback is required."); 1.800 + } 1.801 + } 1.802 + 1.803 + for (let cbName of optionalCallbacks) { 1.804 + if (aOptions[cbName] && typeof(aOptions[cbName]) != "function") { 1.805 + throw new Error(cbName + " must be a function"); 1.806 + } 1.807 + } 1.808 +} 1.809 + 1.810 +this.NSGetFactory = XPCOMUtils.generateNSGetFactory([nsDOMIdentityInternal]);