mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-26 06:11:37 +00:00
0db1c9d597
--HG-- extra : commitid : 7fEg2fmywy6 extra : rebase_source : c9617a1421d4777ba3ac1d410ca628ba7903af2c
479 lines
16 KiB
JavaScript
479 lines
16 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,
|
|
results: Cr} = Components;
|
|
|
|
Cu.import("resource://gre/modules/Services.jsm");
|
|
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
|
|
|
const PREF_FXA_ENABLED = "identity.fxaccounts.enabled";
|
|
const FXA_PERMISSION = "firefox-accounts";
|
|
|
|
// This is the parent process corresponding to nsDOMIdentity.
|
|
this.EXPORTED_SYMBOLS = ["DOMIdentity"];
|
|
|
|
XPCOMUtils.defineLazyModuleGetter(this, "objectCopy",
|
|
"resource://gre/modules/identity/IdentityUtils.jsm");
|
|
|
|
/* jshint ignore:start */
|
|
XPCOMUtils.defineLazyModuleGetter(this, "IdentityService",
|
|
#ifdef MOZ_B2G_VERSION
|
|
"resource://gre/modules/identity/MinimalIdentity.jsm");
|
|
#else
|
|
"resource://gre/modules/identity/Identity.jsm");
|
|
#endif
|
|
/* jshint ignore:end */
|
|
|
|
XPCOMUtils.defineLazyModuleGetter(this, "FirefoxAccounts",
|
|
"resource://gre/modules/identity/FirefoxAccounts.jsm");
|
|
|
|
XPCOMUtils.defineLazyModuleGetter(this, "makeMessageObject",
|
|
"resource://gre/modules/identity/IdentityUtils.jsm");
|
|
|
|
XPCOMUtils.defineLazyModuleGetter(this,
|
|
"Logger",
|
|
"resource://gre/modules/identity/LogUtils.jsm");
|
|
|
|
XPCOMUtils.defineLazyServiceGetter(this, "ppmm",
|
|
"@mozilla.org/parentprocessmessagemanager;1",
|
|
"nsIMessageListenerManager");
|
|
|
|
XPCOMUtils.defineLazyServiceGetter(this, "permissionManager",
|
|
"@mozilla.org/permissionmanager;1",
|
|
"nsIPermissionManager");
|
|
|
|
function log(...aMessageArgs) {
|
|
Logger.log.apply(Logger, ["DOMIdentity"].concat(aMessageArgs));
|
|
}
|
|
|
|
function IDDOMMessage(aOptions) {
|
|
objectCopy(aOptions, this);
|
|
}
|
|
|
|
function _sendAsyncMessage(identifier, message) {
|
|
if (this._mm) {
|
|
try {
|
|
this._mm.sendAsyncMessage(identifier, message);
|
|
} catch(err) {
|
|
// We may receive a NS_ERROR_NOT_INITIALIZED if the target window has
|
|
// been closed. This can legitimately happen if an app has been killed
|
|
// while we are in the midst of a sign-in flow.
|
|
if (err.result == Cr.NS_ERROR_NOT_INITIALIZED) {
|
|
log("Cannot sendAsyncMessage because the recipient frame has closed");
|
|
return;
|
|
}
|
|
log("ERROR: sendAsyncMessage: " + err);
|
|
}
|
|
}
|
|
};
|
|
|
|
function IDPProvisioningContext(aID, aOrigin, aTargetMM) {
|
|
this._id = aID;
|
|
this._origin = aOrigin;
|
|
this._mm = aTargetMM;
|
|
}
|
|
|
|
IDPProvisioningContext.prototype = {
|
|
get id() { return this._id; },
|
|
get origin() { return this._origin; },
|
|
|
|
sendAsyncMessage: _sendAsyncMessage,
|
|
|
|
doBeginProvisioningCallback: function IDPPC_doBeginProvCB(aID, aCertDuration) {
|
|
let message = new IDDOMMessage({id: this.id});
|
|
message.identity = aID;
|
|
message.certDuration = aCertDuration;
|
|
this.sendAsyncMessage("Identity:IDP:CallBeginProvisioningCallback",
|
|
message);
|
|
},
|
|
|
|
doGenKeyPairCallback: function IDPPC_doGenKeyPairCallback(aPublicKey) {
|
|
log("doGenKeyPairCallback");
|
|
let message = new IDDOMMessage({id: this.id});
|
|
message.publicKey = aPublicKey;
|
|
this.sendAsyncMessage("Identity:IDP:CallGenKeyPairCallback", message);
|
|
},
|
|
|
|
doError: function(msg) {
|
|
log("Provisioning ERROR: " + msg);
|
|
}
|
|
};
|
|
|
|
function IDPAuthenticationContext(aID, aOrigin, aTargetMM) {
|
|
this._id = aID;
|
|
this._origin = aOrigin;
|
|
this._mm = aTargetMM;
|
|
}
|
|
|
|
IDPAuthenticationContext.prototype = {
|
|
get id() { return this._id; },
|
|
get origin() { return this._origin; },
|
|
|
|
sendAsyncMessage: _sendAsyncMessage,
|
|
|
|
doBeginAuthenticationCallback: function IDPAC_doBeginAuthCB(aIdentity) {
|
|
let message = new IDDOMMessage({id: this.id});
|
|
message.identity = aIdentity;
|
|
this.sendAsyncMessage("Identity:IDP:CallBeginAuthenticationCallback",
|
|
message);
|
|
},
|
|
|
|
doError: function IDPAC_doError(msg) {
|
|
log("Authentication ERROR: " + msg);
|
|
}
|
|
};
|
|
|
|
function RPWatchContext(aOptions, aTargetMM, aPrincipal) {
|
|
objectCopy(aOptions, this);
|
|
|
|
// id and origin are required
|
|
if (! (this.id && this.origin)) {
|
|
throw new Error("id and origin are required for RP watch context");
|
|
}
|
|
|
|
this.principal = aPrincipal;
|
|
|
|
// default for no loggedInUser is undefined, not null
|
|
this.loggedInUser = aOptions.loggedInUser;
|
|
|
|
// Maybe internal. For hosted b2g identity shim.
|
|
this._internal = aOptions._internal;
|
|
|
|
this._mm = aTargetMM;
|
|
}
|
|
|
|
RPWatchContext.prototype = {
|
|
sendAsyncMessage: _sendAsyncMessage,
|
|
|
|
doLogin: function RPWatchContext_onlogin(aAssertion, aMaybeInternalParams) {
|
|
log("doLogin: " + this.id);
|
|
let message = new IDDOMMessage({id: this.id, assertion: aAssertion});
|
|
if (aMaybeInternalParams) {
|
|
message._internalParams = aMaybeInternalParams;
|
|
}
|
|
this.sendAsyncMessage("Identity:RP:Watch:OnLogin", message);
|
|
},
|
|
|
|
doLogout: function RPWatchContext_onlogout() {
|
|
log("doLogout: " + this.id);
|
|
let message = new IDDOMMessage({id: this.id});
|
|
this.sendAsyncMessage("Identity:RP:Watch:OnLogout", message);
|
|
},
|
|
|
|
doReady: function RPWatchContext_onready() {
|
|
log("doReady: " + this.id);
|
|
let message = new IDDOMMessage({id: this.id});
|
|
this.sendAsyncMessage("Identity:RP:Watch:OnReady", message);
|
|
},
|
|
|
|
doCancel: function RPWatchContext_oncancel() {
|
|
log("doCancel: " + this.id);
|
|
let message = new IDDOMMessage({id: this.id});
|
|
this.sendAsyncMessage("Identity:RP:Watch:OnCancel", message);
|
|
},
|
|
|
|
doError: function RPWatchContext_onerror(aMessage) {
|
|
log("doError: " + this.id + ": " + JSON.stringify(aMessage));
|
|
let message = new IDDOMMessage({id: this.id, message: aMessage});
|
|
this.sendAsyncMessage("Identity:RP:Watch:OnError", message);
|
|
}
|
|
};
|
|
|
|
this.DOMIdentity = {
|
|
/*
|
|
* When relying parties (RPs) invoke the watch() method, they can request
|
|
* to use Firefox Accounts as their auth service or BrowserID (the default).
|
|
* For each RP, we create an RPWatchContext to store the parameters given to
|
|
* watch(), and to provide hooks to invoke the onlogin(), onlogout(), etc.
|
|
* callbacks held in the nsDOMIdentity state.
|
|
*
|
|
* The serviceContexts map associates the window ID of the RP with the
|
|
* context object. The mmContexts map associates a message manager with a
|
|
* window ID. We use the mmContexts map when child-process-shutdown is
|
|
* observed, and all we have is a message manager to identify the window in
|
|
* question.
|
|
*/
|
|
_serviceContexts: new Map(),
|
|
_mmContexts: new Map(),
|
|
|
|
/*
|
|
* Mockable, for testing
|
|
*/
|
|
_mockIdentityService: null,
|
|
get IdentityService() {
|
|
if (this._mockIdentityService) {
|
|
log("Using a mocked identity service");
|
|
return this._mockIdentityService;
|
|
}
|
|
return IdentityService;
|
|
},
|
|
|
|
/*
|
|
* Create a new RPWatchContext, and update the context maps.
|
|
*/
|
|
newContext: function(message, targetMM, principal) {
|
|
let context = new RPWatchContext(message, targetMM, principal);
|
|
this._serviceContexts.set(message.id, context);
|
|
this._mmContexts.set(targetMM, message.id);
|
|
return context;
|
|
},
|
|
|
|
/*
|
|
* Get the identity service used for an RP.
|
|
*
|
|
* @object message
|
|
* A message received from an RP. Will include the id of the window
|
|
* whence the message originated.
|
|
*
|
|
* Returns FirefoxAccounts or IdentityService
|
|
*/
|
|
getService: function(message) {
|
|
if (!this._serviceContexts.has(message.id)) {
|
|
log("ERROR: getService called before newContext for " + message.id);
|
|
return null;
|
|
}
|
|
|
|
let context = this._serviceContexts.get(message.id);
|
|
if (context.wantIssuer == "firefox-accounts") {
|
|
if (Services.prefs.getPrefType(PREF_FXA_ENABLED) === Ci.nsIPrefBranch.PREF_BOOL
|
|
&& Services.prefs.getBoolPref(PREF_FXA_ENABLED)) {
|
|
return FirefoxAccounts;
|
|
}
|
|
log("WARNING: Firefox Accounts is not enabled; Defaulting to BrowserID");
|
|
}
|
|
return this.IdentityService;
|
|
},
|
|
|
|
/*
|
|
* Get the RPWatchContext object for a given message manager.
|
|
*/
|
|
getContextForMM: function(targetMM) {
|
|
return this._serviceContexts.get(this._mmContexts.get(targetMM));
|
|
},
|
|
|
|
hasContextForMM: function(targetMM) {
|
|
return this._mmContexts.has(targetMM);
|
|
},
|
|
|
|
/*
|
|
* Delete the RPWatchContext object for a given message manager. Removes the
|
|
* mapping both from _serviceContexts and _mmContexts.
|
|
*/
|
|
deleteContextForMM: function(targetMM) {
|
|
this._serviceContexts.delete(this._mmContexts.get(targetMM));
|
|
this._mmContexts.delete(targetMM);
|
|
},
|
|
|
|
hasPermission: function(aMessage) {
|
|
// We only check that the firefox accounts permission is present in the
|
|
// manifest.
|
|
if (aMessage.json && aMessage.json.wantIssuer == "firefox-accounts") {
|
|
if (!aMessage.principal) {
|
|
return false;
|
|
}
|
|
|
|
let permission =
|
|
permissionManager.testPermissionFromPrincipal(aMessage.principal,
|
|
FXA_PERMISSION);
|
|
return permission != Ci.nsIPermissionManager.UNKNOWN_ACTION &&
|
|
permission != Ci.nsIPermissionManager.DENY_ACTION;
|
|
}
|
|
return true;
|
|
},
|
|
|
|
// nsIMessageListener
|
|
receiveMessage: function DOMIdentity_receiveMessage(aMessage) {
|
|
let msg = aMessage.json;
|
|
|
|
// Target is the frame message manager that called us and is
|
|
// used to send replies back to the proper window.
|
|
let targetMM = aMessage.target;
|
|
|
|
if (!this.hasPermission(aMessage)) {
|
|
throw new Error("PERMISSION_DENIED");
|
|
}
|
|
|
|
switch (aMessage.name) {
|
|
// RP
|
|
case "Identity:RP:Watch":
|
|
this._watch(msg, targetMM, aMessage.principal);
|
|
break;
|
|
case "Identity:RP:Unwatch":
|
|
this._unwatch(msg, targetMM);
|
|
break;
|
|
case "Identity:RP:Request":
|
|
this._request(msg);
|
|
break;
|
|
case "Identity:RP:Logout":
|
|
this._logout(msg);
|
|
break;
|
|
// IDP
|
|
case "Identity:IDP:BeginProvisioning":
|
|
this._beginProvisioning(msg, targetMM);
|
|
break;
|
|
case "Identity:IDP:GenKeyPair":
|
|
this._genKeyPair(msg);
|
|
break;
|
|
case "Identity:IDP:RegisterCertificate":
|
|
this._registerCertificate(msg);
|
|
break;
|
|
case "Identity:IDP:ProvisioningFailure":
|
|
this._provisioningFailure(msg);
|
|
break;
|
|
case "Identity:IDP:BeginAuthentication":
|
|
this._beginAuthentication(msg, targetMM);
|
|
break;
|
|
case "Identity:IDP:CompleteAuthentication":
|
|
this._completeAuthentication(msg);
|
|
break;
|
|
case "Identity:IDP:AuthenticationFailure":
|
|
this._authenticationFailure(msg);
|
|
break;
|
|
case "child-process-shutdown":
|
|
// we receive child-process-shutdown if the appliction crashes,
|
|
// including if it is crashed by the OS (killed for out-of-memory,
|
|
// for example)
|
|
this._childProcessShutdown(targetMM);
|
|
break;
|
|
}
|
|
},
|
|
|
|
// nsIObserver
|
|
observe: function DOMIdentity_observe(aSubject, aTopic, aData) {
|
|
switch (aTopic) {
|
|
case "xpcom-shutdown":
|
|
this._unsubscribeListeners();
|
|
Services.obs.removeObserver(this, "xpcom-shutdown");
|
|
Services.ww.unregisterNotification(this);
|
|
break;
|
|
}
|
|
},
|
|
|
|
messages: ["Identity:RP:Watch", "Identity:RP:Request", "Identity:RP:Logout",
|
|
"Identity:IDP:BeginProvisioning", "Identity:IDP:ProvisioningFailure",
|
|
"Identity:IDP:RegisterCertificate", "Identity:IDP:GenKeyPair",
|
|
"Identity:IDP:BeginAuthentication",
|
|
"Identity:IDP:CompleteAuthentication",
|
|
"Identity:IDP:AuthenticationFailure",
|
|
"Identity:RP:Unwatch",
|
|
"child-process-shutdown"],
|
|
|
|
// Private.
|
|
_init: function DOMIdentity__init() {
|
|
Services.ww.registerNotification(this);
|
|
Services.obs.addObserver(this, "xpcom-shutdown", false);
|
|
this._subscribeListeners();
|
|
},
|
|
|
|
_subscribeListeners: function DOMIdentity__subscribeListeners() {
|
|
if (!ppmm) {
|
|
return;
|
|
}
|
|
for (let message of this.messages) {
|
|
ppmm.addMessageListener(message, this);
|
|
}
|
|
},
|
|
|
|
_unsubscribeListeners: function DOMIdentity__unsubscribeListeners() {
|
|
for (let message of this.messages) {
|
|
ppmm.removeMessageListener(message, this);
|
|
}
|
|
ppmm = null;
|
|
},
|
|
|
|
_watch: function DOMIdentity__watch(message, targetMM, principal) {
|
|
log("DOMIdentity__watch: " + message.id + " - " + principal);
|
|
let context = this.newContext(message, targetMM, principal);
|
|
this.getService(message).RP.watch(context);
|
|
},
|
|
|
|
_unwatch: function DOMIdentity_unwatch(message, targetMM) {
|
|
log("DOMIDentity__unwatch: " + message.id);
|
|
// If watch failed for some reason (e.g., exception thrown because RP did
|
|
// not have the right callbacks, we don't want unwatch to throw, because it
|
|
// will break the process of releasing the page's resources and leak
|
|
// memory.
|
|
let service = this.getService(message);
|
|
if (service && service.RP) {
|
|
service.RP.unwatch(message.id, targetMM);
|
|
this.deleteContextForMM(targetMM);
|
|
return;
|
|
}
|
|
log("Can't find a service to unwatch() for " + message.id);
|
|
},
|
|
|
|
_request: function DOMIdentity__request(message) {
|
|
let service = this.getService(message);
|
|
if (service && service.RP) {
|
|
service.RP.request(message.id, message);
|
|
return;
|
|
}
|
|
log("No context in which to call request(); Did you call watch() first?");
|
|
},
|
|
|
|
_logout: function DOMIdentity__logout(message) {
|
|
let service = this.getService(message);
|
|
if (service && service.RP) {
|
|
service.RP.logout(message.id, message.origin, message);
|
|
return;
|
|
}
|
|
log("No context in which to call logout(); Did you call watch() first?");
|
|
},
|
|
|
|
_childProcessShutdown: function DOMIdentity__childProcessShutdown(targetMM) {
|
|
if (!this.hasContextForMM(targetMM)) {
|
|
return;
|
|
}
|
|
|
|
let service = this.getContextForMM(targetMM);
|
|
if (service && service.RP) {
|
|
service.RP.childProcessShutdown(targetMM);
|
|
}
|
|
|
|
this.deleteContextForMM(targetMM);
|
|
|
|
let options = makeMessageObject({messageManager: targetMM, id: null, origin: null});
|
|
Services.obs.notifyObservers({wrappedJSObject: options}, "identity-child-process-shutdown", null);
|
|
},
|
|
|
|
_beginProvisioning: function DOMIdentity__beginProvisioning(message, targetMM) {
|
|
let context = new IDPProvisioningContext(message.id, message.origin,
|
|
targetMM);
|
|
this.getService(message).IDP.beginProvisioning(context);
|
|
},
|
|
|
|
_genKeyPair: function DOMIdentity__genKeyPair(message) {
|
|
this.getService(message).IDP.genKeyPair(message.id);
|
|
},
|
|
|
|
_registerCertificate: function DOMIdentity__registerCertificate(message) {
|
|
this.getService(message).IDP.registerCertificate(message.id, message.cert);
|
|
},
|
|
|
|
_provisioningFailure: function DOMIdentity__provisioningFailure(message) {
|
|
this.getService(message).IDP.raiseProvisioningFailure(message.id, message.reason);
|
|
},
|
|
|
|
_beginAuthentication: function DOMIdentity__beginAuthentication(message, targetMM) {
|
|
let context = new IDPAuthenticationContext(message.id, message.origin,
|
|
targetMM);
|
|
this.getService(message).IDP.beginAuthentication(context);
|
|
},
|
|
|
|
_completeAuthentication: function DOMIdentity__completeAuthentication(message) {
|
|
this.getService(message).IDP.completeAuthentication(message.id);
|
|
},
|
|
|
|
_authenticationFailure: function DOMIdentity__authenticationFailure(message) {
|
|
this.getService(message).IDP.cancelAuthentication(message.id);
|
|
}
|
|
};
|
|
|
|
// Object is initialized by nsIDService.js
|