зеркало из https://github.com/mozilla/gecko-dev.git
809 строки
27 KiB
JavaScript
809 строки
27 KiB
JavaScript
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
|
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
"use strict";
|
|
|
|
const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
|
|
|
|
const PREF_DEBUG = "toolkit.identity.debug";
|
|
const PREF_ENABLED = "dom.identity.enabled";
|
|
|
|
// Bug 822450: Workaround for Bug 821740. When testing with marionette,
|
|
// relax navigator.id.request's requirement that it be handling native
|
|
// events. Synthetic marionette events are ok.
|
|
const PREF_SYNTHETIC_EVENTS_OK = "dom.identity.syntheticEventsOk";
|
|
|
|
// Maximum length of a string that will go through IPC
|
|
const MAX_STRING_LENGTH = 2048;
|
|
// Maximum number of times navigator.id.request can be called for a document
|
|
const MAX_RP_CALLS = 100;
|
|
|
|
Cu.import("resource://gre/modules/Services.jsm");
|
|
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
|
|
|
XPCOMUtils.defineLazyModuleGetter(this, "checkDeprecated",
|
|
"resource://gre/modules/identity/IdentityUtils.jsm");
|
|
XPCOMUtils.defineLazyModuleGetter(this, "checkRenamed",
|
|
"resource://gre/modules/identity/IdentityUtils.jsm");
|
|
XPCOMUtils.defineLazyModuleGetter(this, "objectCopy",
|
|
"resource://gre/modules/identity/IdentityUtils.jsm");
|
|
|
|
XPCOMUtils.defineLazyServiceGetter(this, "uuidgen",
|
|
"@mozilla.org/uuid-generator;1",
|
|
"nsIUUIDGenerator");
|
|
|
|
// This is the child process corresponding to nsIDOMIdentity
|
|
XPCOMUtils.defineLazyServiceGetter(this, "cpmm",
|
|
"@mozilla.org/childprocessmessagemanager;1",
|
|
"nsIMessageSender");
|
|
|
|
|
|
const ERRORS = {
|
|
"ERROR_NOT_AUTHORIZED_FOR_FIREFOX_ACCOUNTS":
|
|
"Only privileged and certified apps may use Firefox Accounts",
|
|
"ERROR_INVALID_ASSERTION_AUDIENCE":
|
|
"Assertion audience may not differ from origin",
|
|
"ERROR_REQUEST_WHILE_NOT_HANDLING_USER_INPUT":
|
|
"The request() method may only be invoked when handling user input",
|
|
};
|
|
|
|
function nsDOMIdentity(aIdentityInternal) {
|
|
this._identityInternal = aIdentityInternal;
|
|
}
|
|
nsDOMIdentity.prototype = {
|
|
__exposedProps__: {
|
|
// Relying Party (RP)
|
|
watch: 'r',
|
|
request: 'r',
|
|
logout: 'r',
|
|
get: 'r',
|
|
getVerifiedEmail: 'r',
|
|
|
|
// Provisioning
|
|
beginProvisioning: 'r',
|
|
genKeyPair: 'r',
|
|
registerCertificate: 'r',
|
|
raiseProvisioningFailure: 'r',
|
|
|
|
// Authentication
|
|
beginAuthentication: 'r',
|
|
completeAuthentication: 'r',
|
|
raiseAuthenticationFailure: 'r'
|
|
},
|
|
|
|
// require native events unless syntheticEventsOk is set
|
|
get nativeEventsRequired() {
|
|
if (Services.prefs.prefHasUserValue(PREF_SYNTHETIC_EVENTS_OK) &&
|
|
(Services.prefs.getPrefType(PREF_SYNTHETIC_EVENTS_OK) ===
|
|
Ci.nsIPrefBranch.PREF_BOOL)) {
|
|
return !Services.prefs.getBoolPref(PREF_SYNTHETIC_EVENTS_OK);
|
|
}
|
|
return true;
|
|
},
|
|
|
|
reportErrors: function(message) {
|
|
let onerror = function() {};
|
|
if (this._rpWatcher && this._rpWatcher.onerror) {
|
|
onerror = this._rpWatcher.onerror;
|
|
}
|
|
|
|
message.errors.forEach((error) => {
|
|
// Report an error string to content
|
|
Cu.reportError(ERRORS[error]);
|
|
|
|
// Report error code to RP callback, if available
|
|
onerror(error);
|
|
});
|
|
},
|
|
|
|
/**
|
|
* Relying Party (RP) APIs
|
|
*/
|
|
|
|
watch: function nsDOMIdentity_watch(aOptions = {}) {
|
|
if (this._rpWatcher) {
|
|
// For the initial release of Firefox Accounts, we support callers who
|
|
// invoke watch() either for Firefox Accounts, or Persona, but not both.
|
|
// In the future, we may wish to support the dual invocation (say, for
|
|
// packaged apps so they can sign users in who reject the app's request
|
|
// to sign in with their Firefox Accounts identity).
|
|
throw new Error("navigator.id.watch was already called");
|
|
}
|
|
|
|
assertCorrectCallbacks(aOptions);
|
|
|
|
let message = this.DOMIdentityMessage(aOptions);
|
|
|
|
// loggedInUser vs loggedInEmail
|
|
// https://developer.mozilla.org/en-US/docs/DOM/navigator.id.watch
|
|
// This parameter, loggedInUser, was renamed from loggedInEmail in early
|
|
// September, 2012. Both names will continue to work for the time being,
|
|
// but code should be changed to use loggedInUser instead.
|
|
checkRenamed(aOptions, "loggedInEmail", "loggedInUser");
|
|
message["loggedInUser"] = aOptions["loggedInUser"];
|
|
|
|
let emailType = typeof(aOptions["loggedInUser"]);
|
|
if (aOptions["loggedInUser"] && aOptions["loggedInUser"] !== "undefined") {
|
|
if (emailType !== "string") {
|
|
throw new Error("loggedInUser must be a String or null");
|
|
}
|
|
|
|
// TODO: Bug 767610 - check email format.
|
|
// See HTMLInputElement::IsValidEmailAddress
|
|
if (aOptions["loggedInUser"].indexOf("@") == -1
|
|
|| aOptions["loggedInUser"].length > MAX_STRING_LENGTH) {
|
|
throw new Error("loggedInUser is not valid");
|
|
}
|
|
// Set loggedInUser in this block that "undefined" doesn't get through.
|
|
message.loggedInUser = aOptions.loggedInUser;
|
|
}
|
|
this._log("loggedInUser: " + message.loggedInUser);
|
|
|
|
this._rpWatcher = aOptions;
|
|
this._rpWatcher.audience = message.audience;
|
|
|
|
if (message.errors.length) {
|
|
this.reportErrors(message);
|
|
// We don't delete the rpWatcher object, because we don't want the
|
|
// broken client to be able to call watch() any more. It's broken.
|
|
return;
|
|
}
|
|
this._identityInternal._mm.sendAsyncMessage("Identity:RP:Watch", message);
|
|
},
|
|
|
|
request: function nsDOMIdentity_request(aOptions = {}) {
|
|
this._log("request: " + JSON.stringify(aOptions));
|
|
|
|
// Has the caller called watch() before this?
|
|
if (!this._rpWatcher) {
|
|
throw new Error("navigator.id.request called before navigator.id.watch");
|
|
}
|
|
if (this._rpCalls > MAX_RP_CALLS) {
|
|
throw new Error("navigator.id.request called too many times");
|
|
}
|
|
|
|
let util = this._window.QueryInterface(Ci.nsIInterfaceRequestor)
|
|
.getInterface(Ci.nsIDOMWindowUtils);
|
|
|
|
let message = this.DOMIdentityMessage(aOptions);
|
|
|
|
// We permit calling of request() outside of a user input handler only when
|
|
// we are handling the (deprecated) get() or getVerifiedEmail() calls,
|
|
// which make use of an RP context marked as _internal, or when a certified
|
|
// app is calling.
|
|
//
|
|
// XXX Bug 982460 - grant the same privilege to packaged apps
|
|
|
|
if (!aOptions._internal &&
|
|
this._appStatus !== Ci.nsIPrincipal.APP_STATUS_CERTIFIED) {
|
|
|
|
// If the caller is not special in one of those ways, see if the user has
|
|
// preffed on 'syntheticEventsOk' (useful for testing); otherwise, if
|
|
// this is a non-native event, reject it.
|
|
let util = this._window.QueryInterface(Ci.nsIInterfaceRequestor)
|
|
.getInterface(Ci.nsIDOMWindowUtils);
|
|
|
|
if (!util.isHandlingUserInput && this.nativeEventsRequired) {
|
|
message.errors.push("ERROR_REQUEST_WHILE_NOT_HANDLING_USER_INPUT");
|
|
}
|
|
}
|
|
|
|
// Report and fail hard on any errors.
|
|
if (message.errors.length) {
|
|
this.reportErrors(message);
|
|
return;
|
|
}
|
|
|
|
if (aOptions) {
|
|
// Optional string properties
|
|
let optionalStringProps = ["privacyPolicy", "termsOfService"];
|
|
for (let propName of optionalStringProps) {
|
|
if (!aOptions[propName] || aOptions[propName] === "undefined")
|
|
continue;
|
|
if (typeof(aOptions[propName]) !== "string") {
|
|
throw new Error(propName + " must be a string representing a URL.");
|
|
}
|
|
if (aOptions[propName].length > MAX_STRING_LENGTH) {
|
|
throw new Error(propName + " is invalid.");
|
|
}
|
|
message[propName] = aOptions[propName];
|
|
}
|
|
|
|
if (aOptions["oncancel"]
|
|
&& typeof(aOptions["oncancel"]) !== "function") {
|
|
throw new Error("oncancel is not a function");
|
|
} else {
|
|
// Store optional cancel callback for later.
|
|
this._onCancelRequestCallback = aOptions.oncancel;
|
|
}
|
|
}
|
|
|
|
this._rpCalls++;
|
|
this._identityInternal._mm.sendAsyncMessage("Identity:RP:Request", message);
|
|
},
|
|
|
|
logout: function nsDOMIdentity_logout() {
|
|
if (!this._rpWatcher) {
|
|
throw new Error("navigator.id.logout called before navigator.id.watch");
|
|
}
|
|
if (this._rpCalls > MAX_RP_CALLS) {
|
|
throw new Error("navigator.id.logout called too many times");
|
|
}
|
|
|
|
this._rpCalls++;
|
|
let message = this.DOMIdentityMessage();
|
|
|
|
// Report and fail hard on any errors.
|
|
if (message.errors.length) {
|
|
this.reportErrors(message);
|
|
return;
|
|
}
|
|
|
|
this._identityInternal._mm.sendAsyncMessage("Identity:RP:Logout", message);
|
|
},
|
|
|
|
/*
|
|
* Get an assertion. This function is deprecated. RPs are
|
|
* encouraged to use the observer API instead (watch + request).
|
|
*/
|
|
get: function nsDOMIdentity_get(aCallback, aOptions) {
|
|
var opts = {};
|
|
aOptions = aOptions || {};
|
|
|
|
// We use the observer API (watch + request) to implement get().
|
|
// Because the caller can call get() and getVerifiedEmail() as
|
|
// many times as they want, we lift the restriction that watch() can
|
|
// only be called once.
|
|
this._rpWatcher = null;
|
|
|
|
// This flag tells internal_api.js (in the shim) to record in the
|
|
// login parameters whether the assertion was acquired silently or
|
|
// with user interaction.
|
|
opts._internal = true;
|
|
|
|
opts.privacyPolicy = aOptions.privacyPolicy || undefined;
|
|
opts.termsOfService = aOptions.termsOfService || undefined;
|
|
opts.privacyURL = aOptions.privacyURL || undefined;
|
|
opts.tosURL = aOptions.tosURL || undefined;
|
|
opts.siteName = aOptions.siteName || undefined;
|
|
opts.siteLogo = aOptions.siteLogo || undefined;
|
|
|
|
opts.oncancel = function get_oncancel() {
|
|
if (aCallback) {
|
|
aCallback(null);
|
|
aCallback = null;
|
|
}
|
|
};
|
|
|
|
if (checkDeprecated(aOptions, "silent")) {
|
|
// Silent has been deprecated, do nothing. Placing the check here
|
|
// prevents the callback from being called twice, once with null and
|
|
// once after internalWatch has been called. See issue #1532:
|
|
// https://github.com/mozilla/browserid/issues/1532
|
|
if (aCallback) {
|
|
setTimeout(function() { aCallback(null); }, 0);
|
|
}
|
|
return;
|
|
}
|
|
|
|
// Get an assertion by using our observer api: watch + request.
|
|
var self = this;
|
|
this.watch({
|
|
_internal: true,
|
|
onlogin: function get_onlogin(assertion, internalParams) {
|
|
if (assertion && aCallback && internalParams && !internalParams.silent) {
|
|
aCallback(assertion);
|
|
aCallback = null;
|
|
}
|
|
},
|
|
onlogout: function get_onlogout() {},
|
|
onready: function get_onready() {
|
|
self.request(opts);
|
|
}
|
|
});
|
|
},
|
|
|
|
getVerifiedEmail: function nsDOMIdentity_getVerifiedEmail(aCallback) {
|
|
Cu.reportError("WARNING: getVerifiedEmail has been deprecated");
|
|
this.get(aCallback, {});
|
|
},
|
|
|
|
/**
|
|
* Identity Provider (IDP) Provisioning APIs
|
|
*/
|
|
|
|
beginProvisioning: function nsDOMIdentity_beginProvisioning(aCallback) {
|
|
this._log("beginProvisioning");
|
|
if (this._beginProvisioningCallback) {
|
|
throw new Error("navigator.id.beginProvisioning already called.");
|
|
}
|
|
if (!aCallback || typeof(aCallback) !== "function") {
|
|
throw new Error("beginProvisioning callback is required.");
|
|
}
|
|
|
|
this._beginProvisioningCallback = aCallback;
|
|
this._identityInternal._mm.sendAsyncMessage("Identity:IDP:BeginProvisioning",
|
|
this.DOMIdentityMessage());
|
|
},
|
|
|
|
genKeyPair: function nsDOMIdentity_genKeyPair(aCallback) {
|
|
this._log("genKeyPair");
|
|
if (!this._beginProvisioningCallback) {
|
|
throw new Error("navigator.id.genKeyPair called outside of provisioning");
|
|
}
|
|
if (this._genKeyPairCallback) {
|
|
throw new Error("navigator.id.genKeyPair already called.");
|
|
}
|
|
if (!aCallback || typeof(aCallback) !== "function") {
|
|
throw new Error("genKeyPair callback is required.");
|
|
}
|
|
|
|
this._genKeyPairCallback = aCallback;
|
|
this._identityInternal._mm.sendAsyncMessage("Identity:IDP:GenKeyPair",
|
|
this.DOMIdentityMessage());
|
|
},
|
|
|
|
registerCertificate: function nsDOMIdentity_registerCertificate(aCertificate) {
|
|
this._log("registerCertificate");
|
|
if (!this._genKeyPairCallback) {
|
|
throw new Error("navigator.id.registerCertificate called outside of provisioning");
|
|
}
|
|
if (this._provisioningEnded) {
|
|
throw new Error("Provisioning already ended");
|
|
}
|
|
this._provisioningEnded = true;
|
|
|
|
let message = this.DOMIdentityMessage();
|
|
message.cert = aCertificate;
|
|
this._identityInternal._mm.sendAsyncMessage("Identity:IDP:RegisterCertificate", message);
|
|
},
|
|
|
|
raiseProvisioningFailure: function nsDOMIdentity_raiseProvisioningFailure(aReason) {
|
|
this._log("raiseProvisioningFailure '" + aReason + "'");
|
|
if (this._provisioningEnded) {
|
|
throw new Error("Provisioning already ended");
|
|
}
|
|
if (!aReason || typeof(aReason) != "string") {
|
|
throw new Error("raiseProvisioningFailure reason is required");
|
|
}
|
|
this._provisioningEnded = true;
|
|
|
|
let message = this.DOMIdentityMessage();
|
|
message.reason = aReason;
|
|
this._identityInternal._mm.sendAsyncMessage("Identity:IDP:ProvisioningFailure", message);
|
|
},
|
|
|
|
/**
|
|
* Identity Provider (IDP) Authentication APIs
|
|
*/
|
|
|
|
beginAuthentication: function nsDOMIdentity_beginAuthentication(aCallback) {
|
|
this._log("beginAuthentication");
|
|
if (this._beginAuthenticationCallback) {
|
|
throw new Error("navigator.id.beginAuthentication already called.");
|
|
}
|
|
if (typeof(aCallback) !== "function") {
|
|
throw new Error("beginAuthentication callback is required.");
|
|
}
|
|
if (!aCallback || typeof(aCallback) !== "function") {
|
|
throw new Error("beginAuthentication callback is required.");
|
|
}
|
|
|
|
this._beginAuthenticationCallback = aCallback;
|
|
this._identityInternal._mm.sendAsyncMessage("Identity:IDP:BeginAuthentication",
|
|
this.DOMIdentityMessage());
|
|
},
|
|
|
|
completeAuthentication: function nsDOMIdentity_completeAuthentication() {
|
|
if (this._authenticationEnded) {
|
|
throw new Error("Authentication already ended");
|
|
}
|
|
if (!this._beginAuthenticationCallback) {
|
|
throw new Error("navigator.id.completeAuthentication called outside of authentication");
|
|
}
|
|
this._authenticationEnded = true;
|
|
|
|
this._identityInternal._mm.sendAsyncMessage("Identity:IDP:CompleteAuthentication",
|
|
this.DOMIdentityMessage());
|
|
},
|
|
|
|
raiseAuthenticationFailure: function nsDOMIdentity_raiseAuthenticationFailure(aReason) {
|
|
if (this._authenticationEnded) {
|
|
throw new Error("Authentication already ended");
|
|
}
|
|
if (!aReason || typeof(aReason) != "string") {
|
|
throw new Error("raiseProvisioningFailure reason is required");
|
|
}
|
|
|
|
let message = this.DOMIdentityMessage();
|
|
message.reason = aReason;
|
|
this._identityInternal._mm.sendAsyncMessage("Identity:IDP:AuthenticationFailure", message);
|
|
},
|
|
|
|
// Private.
|
|
_init: function nsDOMIdentity__init(aWindow) {
|
|
|
|
this._initializeState();
|
|
|
|
// Store window and origin URI.
|
|
this._window = aWindow;
|
|
this._origin = aWindow.document.nodePrincipal.origin;
|
|
this._appStatus = aWindow.document.nodePrincipal.appStatus;
|
|
this._appId = aWindow.document.nodePrincipal.appId;
|
|
|
|
// Setup identifiers for current window.
|
|
let util = aWindow.QueryInterface(Ci.nsIInterfaceRequestor)
|
|
.getInterface(Ci.nsIDOMWindowUtils);
|
|
|
|
// We need to inherit the id from the internalIdentity service.
|
|
// See comments below in that service's init.
|
|
this._id = this._identityInternal._id;
|
|
},
|
|
|
|
/**
|
|
* Called during init and shutdown.
|
|
*/
|
|
_initializeState: function nsDOMIdentity__initializeState() {
|
|
// Some state to prevent abuse
|
|
// Limit the number of calls to .request
|
|
this._rpCalls = 0;
|
|
this._provisioningEnded = false;
|
|
this._authenticationEnded = false;
|
|
|
|
this._rpWatcher = null;
|
|
this._onCancelRequestCallback = null;
|
|
this._beginProvisioningCallback = null;
|
|
this._genKeyPairCallback = null;
|
|
this._beginAuthenticationCallback = null;
|
|
},
|
|
|
|
_receiveMessage: function nsDOMIdentity_receiveMessage(aMessage) {
|
|
let msg = aMessage.json;
|
|
|
|
switch (aMessage.name) {
|
|
case "Identity:ResetState":
|
|
if (!this._identityInternal._debug) {
|
|
return;
|
|
}
|
|
this._initializeState();
|
|
Services.obs.notifyObservers(null, "identity-DOM-state-reset", this._id);
|
|
break;
|
|
case "Identity:RP:Watch:OnLogin":
|
|
// Do we have a watcher?
|
|
if (!this._rpWatcher) {
|
|
this._log("WARNING: Received OnLogin message, but there is no RP watcher");
|
|
return;
|
|
}
|
|
|
|
if (this._rpWatcher.onlogin) {
|
|
if (this._rpWatcher._internal) {
|
|
this._rpWatcher.onlogin(msg.assertion, msg._internalParams);
|
|
} else {
|
|
this._rpWatcher.onlogin(msg.assertion);
|
|
}
|
|
}
|
|
break;
|
|
case "Identity:RP:Watch:OnLogout":
|
|
// Do we have a watcher?
|
|
if (!this._rpWatcher) {
|
|
this._log("WARNING: Received OnLogout message, but there is no RP watcher");
|
|
return;
|
|
}
|
|
|
|
if (this._rpWatcher.onlogout) {
|
|
this._rpWatcher.onlogout();
|
|
}
|
|
break;
|
|
case "Identity:RP:Watch:OnReady":
|
|
// Do we have a watcher?
|
|
if (!this._rpWatcher) {
|
|
this._log("WARNING: Received OnReady message, but there is no RP watcher");
|
|
return;
|
|
}
|
|
|
|
if (this._rpWatcher.onready) {
|
|
this._rpWatcher.onready();
|
|
}
|
|
break;
|
|
case "Identity:RP:Watch:OnCancel":
|
|
// Do we have a watcher?
|
|
if (!this._rpWatcher) {
|
|
this._log("WARNING: Received OnCancel message, but there is no RP watcher");
|
|
return;
|
|
}
|
|
|
|
if (this._onCancelRequestCallback) {
|
|
this._onCancelRequestCallback();
|
|
}
|
|
break;
|
|
case "Identity:RP:Watch:OnError":
|
|
if (!this._rpWatcher) {
|
|
this._log("WARNING: Received OnError message, but there is no RP watcher");
|
|
return;
|
|
}
|
|
|
|
if (this._rpWatcher.onerror) {
|
|
this._rpWatcher.onerror(msg.message);
|
|
}
|
|
break;
|
|
case "Identity:IDP:CallBeginProvisioningCallback":
|
|
this._callBeginProvisioningCallback(msg);
|
|
break;
|
|
case "Identity:IDP:CallGenKeyPairCallback":
|
|
this._callGenKeyPairCallback(msg);
|
|
break;
|
|
case "Identity:IDP:CallBeginAuthenticationCallback":
|
|
this._callBeginAuthenticationCallback(msg);
|
|
break;
|
|
}
|
|
},
|
|
|
|
_log: function nsDOMIdentity__log(msg) {
|
|
this._identityInternal._log(msg);
|
|
},
|
|
|
|
_callGenKeyPairCallback: function nsDOMIdentity__callGenKeyPairCallback(message) {
|
|
// create a pubkey object that works
|
|
let chrome_pubkey = JSON.parse(message.publicKey);
|
|
|
|
// bunch of stuff to create a proper object in window context
|
|
function genPropDesc(value) {
|
|
return {
|
|
enumerable: true, configurable: true, writable: true, value: value
|
|
};
|
|
}
|
|
|
|
let propList = {};
|
|
for (let k in chrome_pubkey) {
|
|
propList[k] = genPropDesc(chrome_pubkey[k]);
|
|
}
|
|
|
|
let pubkey = Cu.createObjectIn(this._window);
|
|
Object.defineProperties(pubkey, propList);
|
|
Cu.makeObjectPropsNormal(pubkey);
|
|
|
|
// do the callback
|
|
this._genKeyPairCallback(pubkey);
|
|
},
|
|
|
|
_callBeginProvisioningCallback:
|
|
function nsDOMIdentity__callBeginProvisioningCallback(message) {
|
|
let identity = message.identity;
|
|
let certValidityDuration = message.certDuration;
|
|
this._beginProvisioningCallback(identity,
|
|
certValidityDuration);
|
|
},
|
|
|
|
_callBeginAuthenticationCallback:
|
|
function nsDOMIdentity__callBeginAuthenticationCallback(message) {
|
|
let identity = message.identity;
|
|
this._beginAuthenticationCallback(identity);
|
|
},
|
|
|
|
/**
|
|
* Helper to create messages to send using a message manager.
|
|
* Pass through user options if they are not functions. Always
|
|
* overwrite id, origin, audience, and appStatus. The caller
|
|
* does not get to set those.
|
|
*/
|
|
DOMIdentityMessage: function DOMIdentityMessage(aOptions) {
|
|
aOptions = aOptions || {};
|
|
let message = {
|
|
errors: []
|
|
};
|
|
let principal = Ci.nsIPrincipal;
|
|
|
|
objectCopy(aOptions, message);
|
|
|
|
// outer window id
|
|
message.id = this._id;
|
|
|
|
// window origin
|
|
message.origin = this._origin;
|
|
|
|
// On b2g, an app's status can be NOT_INSTALLED, INSTALLED, PRIVILEGED, or
|
|
// CERTIFIED. Compare the appStatus value to the constants enumerated in
|
|
// Ci.nsIPrincipal.APP_STATUS_*.
|
|
message.appStatus = this._appStatus;
|
|
|
|
// Currently, we only permit certified and privileged apps to use
|
|
// Firefox Accounts.
|
|
if (aOptions.wantIssuer == "firefox-accounts" &&
|
|
this._appStatus !== principal.APP_STATUS_PRIVILEGED &&
|
|
this._appStatus !== principal.APP_STATUS_CERTIFIED) {
|
|
message.errors.push("ERROR_NOT_AUTHORIZED_FOR_FIREFOX_ACCOUNTS");
|
|
}
|
|
|
|
// Normally the window origin will be the audience in assertions. On b2g,
|
|
// certified apps have the power to override this and declare any audience
|
|
// the want. Privileged apps can also declare a different audience, as
|
|
// long as it is the same as the origin specified in their manifest files.
|
|
// All other apps are stuck with b2g origins of the form app://{guid}.
|
|
// Since such an origin is meaningless for the purposes of verification,
|
|
// they will have to jump through some hoops to sign in: Specifically, they
|
|
// will have to host their sign-in flows and DOM API requests in an iframe,
|
|
// have the iframe xhr post assertions up to their server for verification,
|
|
// and then post-message the results down to their app.
|
|
let _audience = message.origin;
|
|
if (message.audience && message.audience != message.origin) {
|
|
if (this._appStatus === principal.APP_STATUS_CERTIFIED) {
|
|
_audience = message.audience;
|
|
this._log("Certified app setting assertion audience: " + _audience);
|
|
} else {
|
|
message.errors.push("ERROR_INVALID_ASSERTION_AUDIENCE");
|
|
}
|
|
}
|
|
|
|
// Replace any audience supplied by the RP with one that has been sanitised
|
|
message.audience = _audience;
|
|
|
|
this._log("DOMIdentityMessage: " + JSON.stringify(message));
|
|
|
|
return message;
|
|
},
|
|
|
|
uninit: function DOMIdentity_uninit() {
|
|
this._log("nsDOMIdentity uninit() " + this._id);
|
|
this._identityInternal._mm.sendAsyncMessage(
|
|
"Identity:RP:Unwatch",
|
|
{ id: this._id }
|
|
);
|
|
}
|
|
|
|
};
|
|
|
|
/**
|
|
* Internal functions that shouldn't be exposed to content.
|
|
*/
|
|
function nsDOMIdentityInternal() {
|
|
}
|
|
nsDOMIdentityInternal.prototype = {
|
|
|
|
// nsIMessageListener
|
|
receiveMessage: function nsDOMIdentityInternal_receiveMessage(aMessage) {
|
|
let msg = aMessage.json;
|
|
// Is this message intended for this window?
|
|
if (msg.id != this._id) {
|
|
return;
|
|
}
|
|
this._identity._receiveMessage(aMessage);
|
|
},
|
|
|
|
// nsIObserver
|
|
observe: function nsDOMIdentityInternal_observe(aSubject, aTopic, aData) {
|
|
let wId = aSubject.QueryInterface(Ci.nsISupportsPRUint64).data;
|
|
if (wId != this._innerWindowID) {
|
|
return;
|
|
}
|
|
|
|
this._identity.uninit();
|
|
|
|
Services.obs.removeObserver(this, "inner-window-destroyed");
|
|
this._identity._initializeState();
|
|
this._identity = null;
|
|
|
|
// TODO: Also send message to DOMIdentity notifiying window is no longer valid
|
|
// ie. in the case that the user closes the auth. window and we need to know.
|
|
|
|
try {
|
|
for (let msgName of this._messages) {
|
|
this._mm.removeMessageListener(msgName, this);
|
|
}
|
|
} catch (ex) {
|
|
// Avoid errors when removing more than once.
|
|
}
|
|
|
|
this._mm = null;
|
|
},
|
|
|
|
// nsIDOMGlobalPropertyInitializer
|
|
init: function nsDOMIdentityInternal_init(aWindow) {
|
|
if (Services.prefs.getPrefType(PREF_ENABLED) != Ci.nsIPrefBranch.PREF_BOOL
|
|
|| !Services.prefs.getBoolPref(PREF_ENABLED)) {
|
|
return null;
|
|
}
|
|
|
|
this._debug =
|
|
Services.prefs.getPrefType(PREF_DEBUG) == Ci.nsIPrefBranch.PREF_BOOL
|
|
&& Services.prefs.getBoolPref(PREF_DEBUG);
|
|
|
|
let util = aWindow.QueryInterface(Ci.nsIInterfaceRequestor)
|
|
.getInterface(Ci.nsIDOMWindowUtils);
|
|
|
|
// To avoid cross-process windowId collisions, use a uuid as an
|
|
// almost certainly unique identifier.
|
|
//
|
|
// XXX Bug 869182 - use a combination of child process id and
|
|
// innerwindow id to construct the unique id.
|
|
this._id = uuidgen.generateUUID().toString();
|
|
this._innerWindowID = util.currentInnerWindowID;
|
|
|
|
// nsDOMIdentity needs to know our _id, so this goes after
|
|
// its creation.
|
|
this._identity = new nsDOMIdentity(this);
|
|
this._identity._init(aWindow);
|
|
|
|
this._log("init was called from " + aWindow.document.location);
|
|
|
|
this._mm = cpmm;
|
|
|
|
// Setup listeners for messages from parent process.
|
|
this._messages = [
|
|
"Identity:ResetState",
|
|
"Identity:RP:Watch:OnLogin",
|
|
"Identity:RP:Watch:OnLogout",
|
|
"Identity:RP:Watch:OnReady",
|
|
"Identity:RP:Watch:OnCancel",
|
|
"Identity:RP:Watch:OnError",
|
|
"Identity:IDP:CallBeginProvisioningCallback",
|
|
"Identity:IDP:CallGenKeyPairCallback",
|
|
"Identity:IDP:CallBeginAuthenticationCallback"
|
|
];
|
|
this._messages.forEach(function(msgName) {
|
|
this._mm.addMessageListener(msgName, this);
|
|
}, this);
|
|
|
|
// Setup observers so we can remove message listeners.
|
|
Services.obs.addObserver(this, "inner-window-destroyed", false);
|
|
|
|
return this._identity;
|
|
},
|
|
|
|
// Private.
|
|
_log: function nsDOMIdentityInternal__log(msg) {
|
|
if (!this._debug) {
|
|
return;
|
|
}
|
|
dump("nsDOMIdentity (" + this._id + "): " + msg + "\n");
|
|
},
|
|
|
|
// Component setup.
|
|
classID: Components.ID("{210853d9-2c97-4669-9761-b1ab9cbf57ef}"),
|
|
|
|
QueryInterface: XPCOMUtils.generateQI(
|
|
[Ci.nsIDOMGlobalPropertyInitializer, Ci.nsIMessageListener]
|
|
),
|
|
|
|
classInfo: XPCOMUtils.generateCI({
|
|
classID: Components.ID("{210853d9-2c97-4669-9761-b1ab9cbf57ef}"),
|
|
contractID: "@mozilla.org/dom/identity;1",
|
|
interfaces: [],
|
|
classDescription: "Identity DOM Implementation"
|
|
})
|
|
};
|
|
|
|
function assertCorrectCallbacks(aOptions) {
|
|
// The relying party (RP) provides callbacks on watch().
|
|
//
|
|
// In the future, BrowserID will probably only require an onlogin()
|
|
// callback, lifting the requirement that BrowserID handle logged-in
|
|
// state management for RPs. See
|
|
// https://github.com/mozilla/id-specs/blob/greenfield/browserid/api-rp.md
|
|
//
|
|
// However, Firefox Accounts requires callers to provide onlogout(),
|
|
// onready(), and also supports an onerror() callback.
|
|
|
|
let requiredCallbacks = ["onlogin"];
|
|
let optionalCallbacks = ["onlogout", "onready", "onerror"];
|
|
|
|
if (aOptions.wantIssuer == "firefox-accounts") {
|
|
requiredCallbacks = ["onlogin", "onlogout", "onready"];
|
|
optionalCallbacks = ["onerror"];
|
|
}
|
|
|
|
for (let cbName of requiredCallbacks) {
|
|
if (typeof(aOptions[cbName]) != "function") {
|
|
throw new Error(cbName + " callback is required.");
|
|
}
|
|
}
|
|
|
|
for (let cbName of optionalCallbacks) {
|
|
if (aOptions[cbName] && typeof(aOptions[cbName]) != "function") {
|
|
throw new Error(cbName + " must be a function");
|
|
}
|
|
}
|
|
}
|
|
|
|
this.NSGetFactory = XPCOMUtils.generateNSGetFactory([nsDOMIdentityInternal]);
|