Bug 753239 - Implement a DOM component for navigator.id. r=jst

--HG--
extra : rebase_source : 8ec0f3a07dc6dc1991758d9c60f43a823f564b0c
This commit is contained in:
Matthew Noorenberghe 2012-06-29 16:12:00 -07:00
parent e14a0505c7
commit 5fa148618a
13 changed files with 1418 additions and 0 deletions

View File

@ -474,6 +474,9 @@
@BINPATH@/components/Webapps.manifest
@BINPATH@/components/AppsService.js
@BINPATH@/components/AppsService.manifest
@BINPATH@/components/nsDOMIdentity.js
@BINPATH@/components/nsIDService.js
@BINPATH@/components/Identity.manifest
@BINPATH@/components/ContactManager.js
@BINPATH@/components/ContactManager.manifest

View File

@ -67,6 +67,7 @@ DIRS += \
indexedDB \
system \
ipc \
identity \
workers \
$(NULL)

View File

@ -0,0 +1,266 @@
/* 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;
// This is the parent process corresponding to nsDOMIdentity.
let EXPORTED_SYMBOLS = ["DOMIdentity"];
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "IdentityService",
"resource://gre/modules/identity/Identity.jsm");
XPCOMUtils.defineLazyModuleGetter(this,
"Logger",
"resource://gre/modules/identity/LogUtils.jsm");
function log(...aMessageArgs) {
Logger.log(["DOMIdentity"].concat(aMessageArgs));
}
function IDDOMMessage(aID) {
this.id = aID;
}
function IDPProvisioningContext(aID, aOrigin, aTargetMM) {
this._id = aID;
this._origin = aOrigin;
this._mm = aTargetMM;
}
IDPProvisioningContext.prototype = {
get id() this._id,
get origin() this._origin,
doBeginProvisioningCallback: function IDPPC_doBeginProvCB(aID, aCertDuration) {
let message = new IDDOMMessage(this.id);
message.identity = aID;
message.certDuration = aCertDuration;
this._mm.sendAsyncMessage("Identity:IDP:CallBeginProvisioningCallback",
message);
},
doGenKeyPairCallback: function IDPPC_doGenKeyPairCallback(aPublicKey) {
log("doGenKeyPairCallback");
let message = new IDDOMMessage(this.id);
message.publicKey = aPublicKey;
this._mm.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() this._id,
get origin() this._origin,
doBeginAuthenticationCallback: function IDPAC_doBeginAuthCB(aIdentity) {
let message = new IDDOMMessage(this.id);
message.identity = aIdentity;
this._mm.sendAsyncMessage("Identity:IDP:CallBeginAuthenticationCallback",
message);
},
doError: function IDPAC_doError(msg) {
log("Authentication ERROR: " + msg);
},
};
function RPWatchContext(aID, aOrigin, aLoggedInEmail, aTargetMM) {
this._id = aID;
this._origin = aOrigin;
this._loggedInEmail = aLoggedInEmail;
this._mm = aTargetMM;
}
RPWatchContext.prototype = {
get id() this._id,
get origin() this._origin,
get loggedInEmail() this._loggedInEmail,
doLogin: function RPWatchContext_onlogin(aAssertion) {
log("doLogin: " + this.id);
let message = new IDDOMMessage(this.id);
message.assertion = aAssertion;
this._mm.sendAsyncMessage("Identity:RP:Watch:OnLogin", message);
},
doLogout: function RPWatchContext_onlogout() {
log("doLogout :" + this.id);
let message = new IDDOMMessage(this.id);
this._mm.sendAsyncMessage("Identity:RP:Watch:OnLogout", message);
},
doReady: function RPWatchContext_onready() {
log("doReady: " + this.id);
let message = new IDDOMMessage(this.id);
this._mm.sendAsyncMessage("Identity:RP:Watch:OnReady", message);
},
doError: function RPWatchContext_onerror(aMessage) {
log("doError: " + aMessage);
}
};
let DOMIdentity = {
// nsIFrameMessageListener
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
.QueryInterface(Ci.nsIFrameLoaderOwner)
.frameLoader.messageManager;
switch (aMessage.name) {
// RP
case "Identity:RP:Watch":
this._watch(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;
}
},
// nsIObserver
observe: function DOMIdentity_observe(aSubject, aTopic, aData) {
switch (aTopic) {
case "domwindowopened":
case "domwindowclosed":
let win = aSubject.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindow);
this._configureMessages(win, aTopic == "domwindowopened");
break;
case "xpcom-shutdown":
Services.ww.unregisterNotification(this);
Services.obs.removeObserver(this, "xpcom-shutdown");
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"],
// Private.
_init: function DOMIdentity__init() {
Services.ww.registerNotification(this);
Services.obs.addObserver(this, "xpcom-shutdown", false);
},
_configureMessages: function DOMIdentity__configureMessages(aWindow, aRegister) {
if (!aWindow.messageManager)
return;
let func = aWindow.messageManager[aRegister ? "addMessageListener"
: "removeMessageListener"];
for (let message of this.messages) {
func(message, this);
}
},
_resetFrameState: function(aContext) {
log("_resetFrameState: ", aContext.id);
if (!aContext._mm) {
throw new Error("ERROR: Trying to reset an invalid context");
}
let message = new IDDOMMessage(aContext.id);
aContext._mm.sendAsyncMessage("Identity:ResetState", message);
},
_watch: function DOMIdentity__watch(message, targetMM) {
log("DOMIdentity__watch: " + message.id);
// Pass an object with the watch members to Identity.jsm so it can call the
// callbacks.
let context = new RPWatchContext(message.id, message.origin,
message.loggedInEmail, targetMM);
IdentityService.RP.watch(context);
},
_request: function DOMIdentity__request(message) {
IdentityService.RP.request(message.id, message);
},
_logout: function DOMIdentity__logout(message) {
IdentityService.RP.logout(message.id, message.origin);
},
_beginProvisioning: function DOMIdentity__beginProvisioning(message, targetMM) {
let context = new IDPProvisioningContext(message.id, message.origin,
targetMM);
IdentityService.IDP.beginProvisioning(context);
},
_genKeyPair: function DOMIdentity__genKeyPair(message) {
IdentityService.IDP.genKeyPair(message.id);
},
_registerCertificate: function DOMIdentity__registerCertificate(message) {
IdentityService.IDP.registerCertificate(message.id, message.cert);
},
_provisioningFailure: function DOMIdentity__provisioningFailure(message) {
IdentityService.IDP.raiseProvisioningFailure(message.id, message.reason);
},
_beginAuthentication: function DOMIdentity__beginAuthentication(message, targetMM) {
let context = new IDPAuthenticationContext(message.id, message.origin,
targetMM);
IdentityService.IDP.beginAuthentication(context);
},
_completeAuthentication: function DOMIdentity__completeAuthentication(message) {
IdentityService.IDP.completeAuthentication(message.id);
},
_authenticationFailure: function DOMIdentity__authenticationFailure(message) {
IdentityService.IDP.cancelAuthentication(message.id);
},
};
// Object is initialized by nsIDService.js

View File

@ -0,0 +1,9 @@
# nsDOMIdentity.js
component {8bcac6a3-56a4-43a4-a44c-cdf42763002f} nsDOMIdentity.js
contract @mozilla.org/dom/identity;1 {8bcac6a3-56a4-43a4-a44c-cdf42763002f}
category JavaScript-navigator-property id @mozilla.org/dom/identity;1
# nsIDService.js (initialization on startup)
component {baa581e5-8e72-406c-8c9f-dcd4b23a6f82} nsIDService.js
contract @mozilla.org/dom/identity/service;1 {baa581e5-8e72-406c-8c9f-dcd4b23a6f82}
category app-startup IDService @mozilla.org/dom/identity/service;1

28
dom/identity/Makefile.in Normal file
View File

@ -0,0 +1,28 @@
# 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/.
DEPTH = ../..
topsrcdir = @top_srcdir@
srcdir = @srcdir@
VPATH = @srcdir@
relativesrcdir = dom/identity
include $(DEPTH)/config/autoconf.mk
EXTRA_COMPONENTS = \
nsDOMIdentity.js \
nsIDService.js \
Identity.manifest \
$(NULL)
EXTRA_JS_MODULES = \
DOMIdentity.jsm \
$(NULL)
ifdef ENABLE_TESTS
DIRS += tests
endif
include $(topsrcdir)/config/rules.mk

View File

@ -0,0 +1,530 @@
/* 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";
// 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");
// This is the child process corresponding to nsIDOMIdentity.
function nsDOMIdentity(aIdentityInternal) {
this._identityInternal = aIdentityInternal;
}
nsDOMIdentity.prototype = {
__exposedProps__: {
// Relying Party (RP)
watch: 'r',
request: 'r',
logout: 'r',
// Provisioning
beginProvisioning: 'r',
genKeyPair: 'r',
registerCertificate: 'r',
raiseProvisioningFailure: 'r',
// Authentication
beginAuthentication: 'r',
completeAuthentication: 'r',
raiseAuthenticationFailure: 'r',
},
// nsIDOMIdentity
/**
* Relying Party (RP) APIs
*/
watch: function nsDOMIdentity_watch(aOptions) {
this._log("watch");
if (this._rpWatcher) {
throw new Error("navigator.id.watch was already called");
}
if (!aOptions || typeof(aOptions) !== "object") {
throw new Error("options argument to watch is required");
}
// Check for required callbacks
let requiredCallbacks = ["onlogin", "onlogout"];
for (let cbName of requiredCallbacks) {
if ((!(cbName in aOptions))
|| typeof(aOptions[cbName]) !== "function") {
throw new Error(cbName + " callback is required.");
}
}
// Optional callback "onready"
if (aOptions["onready"]
&& typeof(aOptions['onready']) !== "function") {
throw new Error("onready must be a function");
}
let message = this.DOMIdentityMessage();
// loggedInEmail
message.loggedInEmail = null;
let emailType = typeof(aOptions["loggedInEmail"]);
if (aOptions["loggedInEmail"] && aOptions["loggedInEmail"] !== "undefined") {
if (emailType !== "string") {
throw new Error("loggedInEmail must be a String or null");
}
// TODO: Bug 767610 - check email format.
// See nsHTMLInputElement::IsValidEmailAddress
if (aOptions["loggedInEmail"].indexOf("@") == -1
|| aOptions["loggedInEmail"].length > MAX_STRING_LENGTH) {
throw new Error("loggedInEmail is not valid");
}
// Set loggedInEmail in this block that "undefined" doesn't get through.
message.loggedInEmail = aOptions.loggedInEmail;
}
this._log("loggedInEmail: " + message.loggedInEmail);
this._rpWatcher = aOptions;
this._identityInternal._mm.sendAsyncMessage("Identity:RP:Watch", message);
},
request: function nsDOMIdentity_request(aOptions) {
// TODO: Bug 769569 - "must be invoked from within a click handler"
// 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 message = this.DOMIdentityMessage();
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();
this._identityInternal._mm.sendAsyncMessage("Identity:RP:Logout", message);
},
/**
* 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;
// Setup identifiers for current window.
let util = aWindow.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindowUtils);
this._id = util.outerWindowID;
},
/**
* 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;
this._log("receiveMessage: " + aMessage.name);
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) {
return;
}
if (this._rpWatcher.onlogin) {
this._rpWatcher.onlogin(msg.assertion);
}
break;
case "Identity:RP:Watch:OnLogout":
// Do we have a watcher?
if (!this._rpWatcher) {
return;
}
if (this._rpWatcher.onlogout) {
this._rpWatcher.onlogout();
}
break;
case "Identity:RP:Watch:OnReady":
// Do we have a watcher?
if (!this._rpWatcher) {
return;
}
if (this._rpWatcher.onready) {
this._rpWatcher.onready();
}
break;
case "Identity:RP:Request:OnCancel":
// Do we have a watcher?
if (!this._rpWatcher) {
return;
}
if (this._onCancelRequestCallback) {
this._onCancelRequestCallback();
}
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
*/
DOMIdentityMessage: function DOMIdentityMessage() {
return {
id: this._id,
origin: this._origin,
};
},
};
/**
* Internal functions that shouldn't be exposed to content.
*/
function nsDOMIdentityInternal() {
}
nsDOMIdentityInternal.prototype = {
// nsIFrameMessageListener
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;
}
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);
this._identity = new nsDOMIdentity(this);
this._identity._init(aWindow);
let util = aWindow.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindowUtils);
this._id = util.outerWindowID;
this._innerWindowID = util.currentInnerWindowID;
this._log("init was called from " + aWindow.document.location);
this._mm = aWindow.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIWebNavigation)
.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIContentFrameMessageManager);
// 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:Request:OnCancel",
"Identity:IDP:CallBeginProvisioningCallback",
"Identity:IDP:CallGenKeyPairCallback",
"Identity:IDP:CallBeginAuthenticationCallback",
];
this._messages.forEach((function(msgName) {
this._mm.addMessageListener(msgName, this);
}).bind(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("{8bcac6a3-56a4-43a4-a44c-cdf42763002f}"),
QueryInterface: XPCOMUtils.generateQI(
[Ci.nsIDOMGlobalPropertyInitializer, Ci.nsIFrameMessageListener]
),
classInfo: XPCOMUtils.generateCI({
classID: Components.ID("{8bcac6a3-56a4-43a4-a44c-cdf42763002f}"),
contractID: "@mozilla.org/dom/identity;1",
interfaces: [],
classDescription: "Identity DOM Implementation"
})
};
const NSGetFactory = XPCOMUtils.generateNSGetFactory([nsDOMIdentityInternal]);

View File

@ -0,0 +1,34 @@
/* 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/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
function IDService() {
this.wrappedJSObject = this;
}
IDService.prototype = {
classID: Components.ID("{baa581e5-8e72-406c-8c9f-dcd4b23a6f82}"),
QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
Ci.nsISupportsWeakReference]),
observe: function observe(subject, topic, data) {
switch (topic) {
case "app-startup":
Services.obs.addObserver(this, "final-ui-startup", true);
break;
case "final-ui-startup":
// Startup DOMIdentity.jsm
Cu.import("resource://gre/modules/DOMIdentity.jsm");
DOMIdentity._init();
break;
}
}
};
const NSGetFactory = XPCOMUtils.generateNSGetFactory([IDService]);

View File

@ -0,0 +1,28 @@
# 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/.
DEPTH = ../../..
topsrcdir = @top_srcdir@
srcdir = @srcdir@
VPATH = @srcdir@
relativesrcdir = dom/identity/tests
include $(DEPTH)/config/autoconf.mk
DIRS = \
$(NULL)
include $(topsrcdir)/config/rules.mk
_TEST_FILES = \
head_identity.js \
test_identity_idp_auth_basics.html \
test_identity_idp_prov_basics.html \
test_identity_rp_basics.html \
$(NULL)
libs:: $(_TEST_FILES)
$(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/testing/mochitest/tests/$(relativesrcdir)

View File

@ -0,0 +1,83 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
const Ci = Components.interfaces;
const Cu = SpecialPowers.wrap(Components).utils;
SpecialPowers.setBoolPref("toolkit.identity.debug", true);
SpecialPowers.setBoolPref("dom.identity.enabled", true);
const Services = Cu.import("resource://gre/modules/Services.jsm").Services;
const DOMIdentity = Cu.import("resource://gre/modules/DOMIdentity.jsm")
.DOMIdentity;
let util = window.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindowUtils);
let outerWinId = util.outerWindowID;
const identity = navigator.id || navigator.mozId;
let index = 0;
// mimicking callback funtionality for ease of testing
// this observer auto-removes itself after the observe function
// is called, so this is meant to observe only ONE event.
function makeObserver(aObserveTopic, aObserveFunc) {
function observe(aSubject, aTopic, aData) {
if (aTopic == aObserveTopic) {
aObserveFunc(aSubject, aTopic, aData);
Services.obs.removeObserver(this, aObserveTopic);
}
}
Services.obs.addObserver(observe, aObserveTopic, false);
}
function expectException(aFunc, msg, aErrorType="Error") {
info("Expecting an exception: " + msg);
msg = msg || "";
let caughtEx = null;
try {
aFunc();
} catch (ex) {
let exProto = Object.getPrototypeOf(ex);
// Don't count NS_* exceptions since they shouldn't be exposed to content
if (exProto.toString() == aErrorType
&& ex.toString().indexOf("NS_ERROR_FAILURE") == -1) {
caughtEx = ex;
} else {
ok(false, ex);
return;
}
}
isnot(caughtEx, null, "Check for thrown exception.");
}
function next() {
if (!identity) {
todo(false, "DOM API is not available. Skipping tests.");
finish_tests();
return;
}
if (index >= steps.length) {
ok(false, "Shouldn't get here!");
return;
}
try {
let fn = steps[index];
info("Begin test " + index + " '" + steps[index].name + "'!");
fn();
} catch(ex) {
ok(false, "Caught exception", ex);
}
index += 1;
}
function finish_tests() {
info("all done");
SpecialPowers.clearUserPref("toolkit.identity.debug");
SpecialPowers.clearUserPref("dom.identity.enabled");
SimpleTest.finish();
}

View File

@ -0,0 +1,87 @@
<!DOCTYPE html>
<!-- Any copyright is dedicated to the Public Domain.
- http://creativecommons.org/publicdomain/zero/1.0/ -->
<html>
<head>
<meta charset="utf-8">
<title>Test for navigator.id identity provider (IDP) authentication basics</title>
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<script type="application/javascript;version=1.8" src="head_identity.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
</head>
<body>
<a target="_blank">navigator.id identity provider (IDP) authentication basics</a>
<p id="display"></p>
<div id="content" style="display: none">
</div>
<pre id="test">
<script type="application/javascript;version=1.8">
"use strict"
let steps = [
// completeAuthentication tests
function completeAuthenticationExists() {
is(typeof(identity.completeAuthentication), "function",
"Check completeAuthentication is a function");
SimpleTest.executeSoon(next);
},
function completeAuthenticationOutsideFlow() {
expectException(function() {
identity.completeAuthentication();
}, "Check completeAuthentication outside of an auth. flow");
SimpleTest.executeSoon(next);
},
// raiseAuthenticationFailure tests
function raiseAuthenticationFailureExists() {
is(typeof(identity.raiseAuthenticationFailure), "function",
"Check raiseAuthenticationFailure is a function");
SimpleTest.executeSoon(next);
},
function raiseAuthenticationFailureNoArgs() {
expectException(function() {
identity.raiseAuthenticationFailure();
}, "raiseAuthenticationFailure with no arguments");
SimpleTest.executeSoon(next);
},
// beginAuthentication tests
function beginAuthenticationExists() {
is(typeof(identity.beginAuthentication), "function",
"Check beginAuthentication is a function");
SimpleTest.executeSoon(next);
},
function beginAuthenticationNoArgs() {
expectException(function() {
identity.beginAuthentication();
}, "beginAuthentication with no arguments");
SimpleTest.executeSoon(next);
},
function beginAuthenticationInvalidArg() {
expectException(function() {
identity.beginAuthentication(999);
}, "beginAuthentication with a non-function argument");
SimpleTest.executeSoon(next);
},
function beginAuthenticationArgs() {
function beginAuthenticationCb() {
throw "beginAuthentication callback shouldn't have been called outside of an "
+ "auth flow";
}
is(identity.beginAuthentication(beginAuthenticationCb), undefined,
"Check minimum beginAuthentication arguments");
SimpleTest.executeSoon(next);
},
finish_tests,
];
SimpleTest.waitForExplicitFinish();
addLoadEvent(next);
</script>
</pre>
</body>
</html>

View File

@ -0,0 +1,160 @@
<!DOCTYPE html>
<!-- Any copyright is dedicated to the Public Domain.
- http://creativecommons.org/publicdomain/zero/1.0/ -->
<html>
<head>
<meta charset="utf-8">
<title>Test for navigator.id identity provider (IDP) provisioning basics</title>
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<script type="application/javascript;version=1.8" src="head_identity.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
</head>
<body>
<a target="_blank">navigator.id identity provider (IDP) provisioning basics</a>
<p id="display"></p>
<div id="content" style="display: none">
</div>
<pre id="test">
<script type="application/javascript;version=1.8">
"use strict"
let IDP = Cu.import("resource://gre/modules/identity/IdentityProvider.jsm").IdentityProvider;
function setupProv() {
info("setupProv");
// Add a provisioning flow so the DOM calls succeed
IDP._provisionFlows[outerWinId] = {
sandbox: {},
callback: function doCallback(aErr) {
info("provisioning callback: " + aErr);
},
}
}
function resetAndNext() {
info("resetAndNext");
// reset DOM state for the next test
// Give the flow some time to cross the IPC boundary
setTimeout(function() {
let provContext = IDP._provisionFlows[outerWinId];
if (!provContext) {
SimpleTest.executeSoon(next);
return;
}
makeObserver("identity-DOM-state-reset", function() {
info("reset done");
SimpleTest.executeSoon(next);
});
DOMIdentity._resetFrameState(provContext.caller);
}, 500);
}
let steps = [
// genKeyPair tests
function genKeyPairExists() {
is(typeof(identity.genKeyPair), "function",
"Check genKeyPair is a function");
SimpleTest.executeSoon(next);
},
function genKeyPairOutsideProv() {
expectException(function(){
identity.genKeyPair(function(){});
}, "Check genKeyPair outside of a prov. flow");
SimpleTest.executeSoon(next);
},
function genKeyPairNoArgs() {
setupProv();
identity.beginProvisioning(function() {
expectException(function() {
identity.genKeyPair();
}, "genKeyPair with no arguments");
SimpleTest.executeSoon(resetAndNext);
});
},
function genKeyPairInvalidArg() {
setupProv();
identity.beginProvisioning(function() {
expectException(function() {
identity.genKeyPair(999);
}, "Check genKeyPair with non-function object argument");
SimpleTest.executeSoon(resetAndNext);
});
},
// registerCertificate tests
function registerCertificateExists() {
is(typeof(identity.registerCertificate), "function",
"Check registerCertificate is a function");
SimpleTest.executeSoon(next);
},
function registerCertificateNoArgs() {
setupProv();
identity.beginProvisioning(function() {
expectException(function() {
identity.registerCertificate();
}, "Check registerCertificate with no arguments");
});
SimpleTest.executeSoon(resetAndNext);
},
function registerCertificateOutsideProv() {
expectException(function(){
identity.registerCertificate("foo");
}, "Check registerCertificate outside of a prov. flow");
SimpleTest.executeSoon(next);
},
// raiseProvisioningFailure tests
function raiseProvisioningFailureExists() {
is(typeof(identity.raiseProvisioningFailure), "function",
"Check raiseProvisioningFailure is a function");
SimpleTest.executeSoon(next);
},
function raiseProvisioningFailureNoArgs() {
expectException(function() {
identity.raiseProvisioningFailure();
}, "raiseProvisioningFailure with no arguments");
SimpleTest.executeSoon(next);
},
function raiseProvisioningFailureWithReason() {
identity.raiseProvisioningFailure("my test reason");
SimpleTest.executeSoon(next);
},
// beginProvisioning tests
function beginProvisioningExists() {
is(typeof(identity.beginProvisioning), "function",
"Check beginProvisioning is a function");
SimpleTest.executeSoon(next);
},
function beginProvisioningNoArgs() {
expectException(function() {
identity.beginProvisioning();
}, "beginProvisioning with no arguments");
SimpleTest.executeSoon(next);
},
function beginProvisioningInvalidArg() {
expectException(function() {
identity.beginProvisioning(999);
}, "beginProvisioning with a non-function argument");
SimpleTest.executeSoon(next);
},
function beginProvisioningArgs() {
function beginProvisioningCb() {
SimpleTest.executeSoon(resetAndNext);
}
is(identity.beginProvisioning(beginProvisioningCb), undefined,
"Check minimum beginProvisioning arguments");
},
finish_tests,
];
SimpleTest.waitForExplicitFinish();
addLoadEvent(next);
</script>
</pre>
</body>
</html>

View File

@ -0,0 +1,172 @@
<!DOCTYPE html>
<!-- Any copyright is dedicated to the Public Domain.
- http://creativecommons.org/publicdomain/zero/1.0/ -->
<html>
<head>
<meta charset="utf-8">
<title>Test for navigator.id relying party (RP) basics</title>
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<script type="application/javascript;version=1.8" src="head_identity.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
</head>
<body>
<a target="_blank">navigator.id RP basics</a>
<p id="display"></p>
<div id="content" style="display: none">
</div>
<pre id="test">
<script type="application/javascript;version=1.8">
"use strict";
const RP = Cu.import("resource://gre/modules/identity/RelyingParty.jsm").RelyingParty;
function resetAndNext() {
// reset DOM state for the next test
makeObserver("identity-DOM-state-reset", function() {
SimpleTest.executeSoon(next);
});
// Give the flow some time to cross the IPC boundary
setTimeout(function() {
let rpContext = RP._rpFlows[outerWinId];
if (!rpContext) {
SimpleTest.executeSoon(next);
return;
}
DOMIdentity._resetFrameState(rpContext);
}, 500);
}
let steps = [
function nonExistentProp() {
is(identity.foobarbaz, undefined, "Check that foobarbaz does not exist");
expectException(function() {
identity.foobarbaz()
}, "Check for exception calling non-existent method", "TypeError");
SimpleTest.executeSoon(next);
},
// test request before watch throws an exception
function requestBeforeWatch() {
expectException(function() {
identity.request();
});
SimpleTest.executeSoon(next);
},
// watch tests
function watchExists() {
is(typeof(identity.watch), "function", "Check watch is a function");
SimpleTest.executeSoon(next);
},
function watchNoArgs() {
expectException(function() {
identity.watch();
}, "watch with no arguments");
SimpleTest.executeSoon(next);
},
function watchEmptyObj() {
expectException(function() {
identity.watch({});
}, "watch with empty object argument");
SimpleTest.executeSoon(next);
},
function watchOnLoginBool() {
expectException(function() {
identity.watch({onlogin: true});
}, "watch with invalid onlogin member");
SimpleTest.executeSoon(next);
},
function watchOnLoginLogoutBool() {
expectException(function() {
identity.watch({onlogin: true, onlogout: false});
}, "watch with invalid onlogin and onlogout members");
SimpleTest.executeSoon(next);
},
function watchMinimumArgs() {
function onLoginLogoutCb() {
throw "onlogin/onlogout callback shouldn't have been called";
}
is(identity.watch({onlogin: onLoginLogoutCb, onlogout: onLoginLogoutCb}),
undefined, "Check minimum watch argument members");
resetAndNext();
},
function watchOnReadyType() {
function onLoginLogoutCb() {
throw "onlogin/onlogout callback shouldn't have been called";
}
let options = {
onlogin: onLoginLogoutCb,
onlogout: onLoginLogoutCb,
onready: 999,
}
expectException(function() {
identity.watch(options)
}, "Check onready type");
resetAndNext();
},
function watchLoggedInEmailType() {
function onLoginLogoutCb() {
throw "onlogin/onlogout callback shouldn't have been called";
}
let options = {
onlogin: onLoginLogoutCb,
onlogout: onLoginLogoutCb,
loggedInEmail: {},
}
expectException(function() {
identity.watch(options)
}, "Check loggedInEmail type");
resetAndNext();
},
function watchOnReadyCalled() {
let onLogoutCalled = false;
let options = {
loggedInEmail: "loggedOut@user.com",
onlogin: function onLoginCb(assertion) {
throw "onlogin/onlogout callback shouldn't have been called";
},
onlogout: function onLogoutCb() {
is(arguments.length, 0, "Check onlogout argument length");
onLogoutCalled = true;
},
onready: function onReady() {
is(arguments.length, 0, "Check onready argument length");
ok(onLogoutCalled, "onlogout callback should be called before onready");
SimpleTest.executeSoon(next);
},
}
is(identity.watch(options), undefined, "Check onready is called");
},
// request tests
function requestExists() {
is(typeof(identity.request), "function", "Check request is a function");
SimpleTest.executeSoon(next);
},
function requestNoArgs() {
is(identity.request(), undefined, "Check request with no arguments");
SimpleTest.executeSoon(next);
},
function requestEmptyObj() {
is(identity.request({}), undefined, "Check request with empty object argument");
SimpleTest.executeSoon(next);
},
// logout tests
function logoutExists() {
is(typeof(identity.logout), "function", "Check logout is a function");
SimpleTest.executeSoon(next);
},
finish_tests,
];
SimpleTest.waitForExplicitFinish();
addLoadEvent(next);
</script>
</pre>
</body>
</html>

View File

@ -42,6 +42,7 @@ function IdentityProviderService() {
IdentityProviderService.prototype = {
QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports, Ci.nsIObserver]),
_sandboxConfigured: false,
observe: function observe(aSubject, aTopic, aData) {
switch (aTopic) {
@ -83,6 +84,14 @@ IdentityProviderService.prototype = {
shutdown: function RP_shutdown() {
this.reset();
if (this._sandboxConfigured) {
// Tear down message manager listening on the hidden window
Cu.import("resource://gre/modules/DOMIdentity.jsm");
DOMIdentity._configureMessages(Services.appShell.hiddenDOMWindow, false);
this._sandboxConfigured = false;
}
Services.obs.removeObserver(this, "quit-application-granted");
},
@ -432,6 +441,14 @@ IdentityProviderService.prototype = {
*/
_createProvisioningSandbox: function _createProvisioningSandbox(aURL, aCallback) {
log("_createProvisioningSandbox:", aURL);
if (!this._sandboxConfigured) {
// Configure message manager listening on the hidden window
Cu.import("resource://gre/modules/DOMIdentity.jsm");
DOMIdentity._configureMessages(Services.appShell.hiddenDOMWindow, true);
this._sandboxConfigured = true;
}
new Sandbox(aURL, aCallback);
},