Bug 794680 - Connect gecko to gaia identity ui. r=benadida, r=cjones

This commit is contained in:
Jed Parsons 2012-10-26 07:39:38 -04:00
parent 8545ec6439
commit e4891fd894
25 changed files with 1355 additions and 1443 deletions

View File

@ -0,0 +1,186 @@
/* -*- Mode: js2; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- /
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
/* 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/. */
// This JS shim contains the callbacks to fire DOMRequest events for
// navigator.pay API within the payment processor's scope.
"use strict";
let { classes: Cc, interfaces: Ci, utils: Cu } = Components;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
XPCOMUtils.defineLazyServiceGetter(this, "cpmm",
"@mozilla.org/childprocessmessagemanager;1",
"nsIMessageSender");
XPCOMUtils.defineLazyServiceGetter(this, "uuidgen",
"@mozilla.org/uuid-generator;1",
"nsIUUIDGenerator");
XPCOMUtils.defineLazyModuleGetter(this, "Logger",
"resource://gre/modules/identity/LogUtils.jsm");
function log(...aMessageArgs) {
Logger.log.apply(Logger, ["injected identity.js"].concat(aMessageArgs));
}
log("\n\n======================= identity.js =======================\n\n");
// This script may be injected more than once into an iframe.
// Ensure we don't redefine contstants
if (typeof kIdentityJSLoaded === 'undefined') {
const kReceivedIdentityAssertion = "received-id-assertion";
const kIdentityDelegateWatch = "identity-delegate-watch";
const kIdentityDelegateRequest = "identity-delegate-request";
const kIdentityDelegateLogout = "identity-delegate-logout";
const kIdentityDelegateReady = "identity-delegate-ready";
const kIdentityDelegateFinished = "identity-delegate-finished";
const kIdentityControllerDoMethod = "identity-controller-doMethod";
const kIdentktyJSLoaded = true;
}
var showUI = false;
var options = null;
var isLoaded = false;
var func = null;
/*
* Message back to the SignInToWebsite pipe. Message should be an
* object with the following keys:
*
* method: one of 'login', 'logout', 'ready'
* assertion: optional assertion
*/
function identityCall(message) {
sendAsyncMessage(kIdentityControllerDoMethod, message);
}
function identityFinished() {
log("identity finished. closing dialog");
closeIdentityDialog(function notifySuccess() {
// get ready for next call with a reinit
func = null; options = null;
sendAsyncMessage(kIdentityDelegateFinished);
});
}
/*
* Notify the UI to close the dialog and return to the caller application
*/
function closeIdentityDialog(aCallback) {
let randomId = uuidgen.generateUUID().toString();
let id = kReceivedIdentityAssertion + "-" + randomId;
let browser = Services.wm.getMostRecentWindow("navigator:browser");
let detail = {
type: kReceivedIdentityAssertion,
id: id,
showUI: showUI
};
// In order to avoid race conditions, we wait for the UI to notify that
// it has successfully closed the identity flow and has recovered the
// caller app, before notifying the parent process.
content.addEventListener("mozContentEvent", function closeIdentityDialogFinished(evt) {
content.removeEventListener("mozContentEvent", closeIdentityDialogFinished);
if (evt.detail.id == id && aCallback) {
aCallback();
}
});
browser.shell.sendChromeEvent(detail);
}
/*
* doInternalWatch - call the internal.watch api and relay the results
* up to the controller.
*/
function doInternalWatch() {
log("doInternalWatch:", options, isLoaded);
if (options && isLoaded) {
log("internal watch options:", options);
let BrowserID = content.wrappedJSObject.BrowserID;
BrowserID.internal.watch(function(aParams) {
log("sending watch method message:", aParams.method);
identityCall(aParams);
if (aParams.method === "ready") {
log("watch finished.");
identityFinished();
}
},
JSON.stringify({loggedInUser: options.loggedInUser, origin: options.origin}),
function(...things) {
log("internal: ", things);
}
);
}
}
function doInternalRequest() {
log("doInternalRequest:", options && isLoaded);
if (options && isLoaded) {
content.wrappedJSObject.BrowserID.internal.get(
options.origin,
function(assertion) {
if (assertion) {
log("request -> assertion, so do login");
identityCall({method:'login',assertion:assertion});
}
identityFinished();
},
options);
}
}
function doInternalLogout(aOptions) {
log("doInternalLogout:", (options && isLoaded));
if (options && isLoaded) {
let BrowserID = content.wrappedJSObject.BrowserID;
log("logging you out of ", options.origin);
BrowserID.internal.logout(options.origin, function() {
identityCall({method:'logout'});
identityFinished();
});
}
}
addEventListener("DOMContentLoaded", function(e) {
content.addEventListener("load", function(e) {
isLoaded = true;
// bring da func
if (func) func();
});
});
// listen for request
addMessageListener(kIdentityDelegateRequest, function(aMessage) {
log("\n\n* * * * injected identity.js received", kIdentityDelegateRequest, "\n\n\n");
options = aMessage.json;
showUI = true;
func = doInternalRequest;
func();
});
// listen for watch
addMessageListener(kIdentityDelegateWatch, function(aMessage) {
log("\n\n* * * * injected identity.js received", kIdentityDelegateWatch, "\n\n\n");
options = aMessage.json;
showUI = false;
func = doInternalWatch;
func();
});
// listen for logout
addMessageListener(kIdentityDelegateLogout, function(aMessage) {
log("\n\n* * * * injected identity.js received", kIdentityDelegateLogout, "\n\n\n");
options = aMessage.json;
showUI = false;
func = doInternalLogout;
func();
});

View File

@ -0,0 +1,352 @@
/* 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/. */
/*
* SignInToWebsite.jsm - UX Controller and means for accessing identity
* cookies on behalf of relying parties.
*
* Currently, the b2g security architecture isolates web applications
* so that each window has access only to a local cookie jar:
*
* To prevent Web apps from interfering with one another, each one is
* hosted on a separate domain, and therefore may only access the
* resources associated with its domain. These resources include
* things such as IndexedDB databases, cookies, offline storage,
* and so forth.
*
* -- https://developer.mozilla.org/en-US/docs/Mozilla/Firefox_OS/Security/Security_model
*
* As a result, an authentication system like Persona cannot share its
* cookie jar with multiple relying parties, and so would require a
* fresh login request in every window. This would not be a good
* experience.
*
*
* In order for navigator.id.request() to maintain state in a single
* cookie jar, we cause all Persona interactions to take place in a
* gaia context that is launched by the system application, with the
* result that Persona has a single cookie jar that all Relying
* Parties can use. Since of course those Relying Parties cannot
* reach into the system cookie jar, the Controller in this module
* provides a way to get messages and data to and fro between the
* Relying Party in its window context, and the Persona internal api
* in its context.
*
* On the Relying Party's side, say a web page invokes
* navigator.id.watch(), to register callbacks, and then
* navigator.id.request() to request an assertion. The navigator.id
* calls are provided by nsDOMIdentity. nsDOMIdentity messages down
* to the privileged DOMIdentity code (using cpmm and ppmm message
* managers). DOMIdentity stores the state of Relying Party flows
* using an Identity service (MinimalIdentity.jsm), and emits messages
* requesting Persona functions (doWatch, doReady, doLogout).
*
* The Identity service sends these observer messages to the
* Controller in this module, which in turn triggers gaia to open a
* window to host the Persona js. If user interaction is required,
* gaia will open the trusty UI. If user interaction is not required,
* and we only need to get to Persona functions, gaia will open a
* hidden iframe. In either case, a window is opened into which the
* controller causes the script identity.js to be injected. This
* script provides the glue between the in-page javascript and the
* pipe back down to the Controller, translating navigator.internal
* function callbacks into messages sent back to the Controller.
*
* As a result, a navigator.internal function in the hosted popup or
* iframe can call back to the injected identity.js (doReady, doLogin,
* or doLogout). identity.js callbacks send messages back through the
* pipe to the Controller. The controller invokes the corresponding
* function on the Identity Service (doReady, doLogin, or doLogout).
* The IdentityService calls the corresponding callback for the
* correct Relying Party, which causes DOMIdentity to send a message
* up to the Relying Party through nsDOMIdentity
* (Identity:RP:Watch:OnLogin etc.), and finally, nsDOMIdentity
* receives these messages and calls the original callback that the
* Relying Party registered (navigator.id.watch(),
* navigator.id.request(), or navigator.id.logout()).
*/
"use strict";
const EXPORTED_SYMBOLS = ["SignInToWebsiteController"];
const Ci = Components.interfaces;
const Cu = Components.utils;
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "IdentityService",
"resource://gre/modules/identity/MinimalIdentity.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Logger",
"resource://gre/modules/identity/LogUtils.jsm");
// JS shim that contains the callback functions that
// live within the identity UI provisioning frame.
const kIdentityShimFile = "chrome://browser/content/identity.js";
// Type of MozChromeEvents to handle id dialogs.
const kOpenIdentityDialog = "open-id-dialog";
const kCloseIdentityDialog = "close-id-dialog";
// Observer messages to communicate to shim
const kIdentityDelegateWatch = "identity-delegate-watch";
const kIdentityDelegateRequest = "identity-delegate-request";
const kIdentityDelegateLogout = "identity-delegate-logout";
const kIdentityDelegateFinished = "identity-delegate-finished";
const kIdentityDelegateReady = "identity-delegate-ready";
const kIdentityControllerDoMethod = "identity-controller-doMethod";
XPCOMUtils.defineLazyServiceGetter(this, "uuidgen",
"@mozilla.org/uuid-generator;1",
"nsIUUIDGenerator");
function log(...aMessageArgs) {
Logger.log.apply(Logger, ["SignInToWebsiteController"].concat(aMessageArgs));
}
function getRandomId() {
return uuidgen.generateUUID().toString();
}
/*
* GaiaInterface encapsulates the our gaia functions. There are only two:
*
* getContent - return the current content window
* sendChromeEvent - send a chromeEvent from the browser shell
*/
let GaiaInterface = {
_getBrowser: function SignInToWebsiteController__getBrowser() {
return Services.wm.getMostRecentWindow("navigator:browser");
},
getContent: function SignInToWebsiteController_getContent() {
return this._getBrowser().getContentWindow();
},
sendChromeEvent: function SignInToWebsiteController_sendChromeEvent(detail) {
this._getBrowser().shell.sendChromeEvent(detail);
}
};
/*
* The Pipe abstracts the communcation channel between the Controller
* and the identity.js code running in the browser window.
*/
let Pipe = {
/*
* communicate - launch a gaia window with certain options and
* provide a callback for handling messages.
*
* @param aRpOptions options describing the Relying Party's
* (dicitonary) call, such as origin and loggedInEmail.
*
* @param aGaiaOptions showUI: boolean
* (dictionary) message: name of the message to emit
* (request, logout, watch)
*
* @param aMessageCallback function to call on receipt of a
* (function) do-method message. These messages name
* a method ('login', 'logout', etc.) and
* carry optional parameters. The Pipe does
* not know what the messages mean; it is
* up to the caller to interpret them and
* act on them.
*/
communicate: function(aRpOptions, aGaiaOptions, aMessageCallback) {
log("open gaia dialog with options:", aGaiaOptions);
// This content variable is injected into the scope of
// kIdentityShimFile, where it is used to access the BrowserID object
// and its internal API.
let content = GaiaInterface.getContent();
if (!content) {
log("ERROR: what the what? no content window?");
// aErrorCb.onresult("NO_CONTENT_WINDOW");
return;
}
// Prepare a message for gaia. The parameter showUI signals
// whether user interaction is needed. If it is, gaia will open a
// dialog; if not, a hidden iframe. In each case, BrowserID is
// available in the context.
let id = kOpenIdentityDialog + "-" + getRandomId();
let detail = {
type: kOpenIdentityDialog,
showUI: aGaiaOptions.showUI || false,
id: id
};
// When gaia signals back with a mozContentEvent containing the
// unique id we created, we know the window is ready. We then inject
// the magic javascript (kIdentityShimFile) that will give the content
// the superpowers it needs to communicate back with this code.
content.addEventListener("mozContentEvent", function getAssertion(evt) {
// Make sure the message is really for us
let msg = evt.detail;
if (msg.id != id) {
return;
}
// We only need to catch the first mozContentEvent from the
// iframe or popup, so we remove the listener right away.
content.removeEventListener("mozContentEvent", getAssertion);
// Try to load the identity shim file containing the callbacks
// in the content script. This could be either the visible
// popup that the user interacts with, or it could be an invisible
// frame.
let frame = evt.detail.frame;
let frameLoader = frame.QueryInterface(Ci.nsIFrameLoaderOwner).frameLoader;
let mm = frameLoader.messageManager;
try {
log("about to load frame script");
mm.loadFrameScript(kIdentityShimFile, true);
} catch (e) {
log("Error loading ", kIdentityShimFile, " as a frame script: ", e);
}
// There are two messages that the delegate can send back: a "do
// method" event, and a "finished" event. We pass the do-method
// events straight to the caller for interpretation and handling.
// If we receive a "finished" event, then the delegate is done, so
// we shut down the pipe and clean up.
mm.addMessageListener(kIdentityControllerDoMethod, aMessageCallback);
mm.addMessageListener(kIdentityDelegateFinished, function identityDelegateFinished(message) {
mm.removeMessageListener(kIdentityDelegateFinished, identityDelegateFinished);
mm.removeMessageListener(kIdentityControllerDoMethod, aMessageCallback);
});
mm.sendAsyncMessage(aGaiaOptions.message, aRpOptions);
});
// Tell gaia to open the identity iframe or trusty popup
GaiaInterface.sendChromeEvent(detail);
}
};
/*
* The controller sits between the IdentityService used by DOMIdentity
* and a gaia process launches an (invisible) iframe or (visible)
* trusty UI. Using an injected js script (identity.js), the
* controller enables the gaia window to access the persona identity
* storage in the system cookie jar and send events back via the
* controller into IdentityService and DOM, and ultimately up to the
* Relying Party, which is open in a different window context.
*/
let SignInToWebsiteController = {
/*
* Initialize the controller. To use a different gaia communication pipe,
* such as when mocking it in tests, pass aOptions.pipe.
*/
init: function SignInToWebsiteController_init(aOptions) {
aOptions = aOptions || {};
this.pipe = aOptions.pipe || Pipe;
Services.obs.addObserver(this, "identity-controller-watch", false);
Services.obs.addObserver(this, "identity-controller-request", false);
Services.obs.addObserver(this, "identity-controller-logout", false);
},
uninit: function SignInToWebsiteController_uninit() {
Services.obs.removeObserver(this, "identity-controller-watch");
Services.obs.removeObserver(this, "identity-controller-request");
Services.obs.removeObserver(this, "identity-controller-logout");
},
observe: function SignInToWebsiteController_observe(aSubject, aTopic, aData) {
log("observe: received", aTopic, "with", aData, "for", aSubject);
let options = null;
if (aSubject) {
options = aSubject.wrappedJSObject;
}
switch(aTopic) {
case "identity-controller-watch":
this.doWatch(options);
break;
case "identity-controller-request":
this.doRequest(options);
break;
case "identity-controller-logout":
this.doLogout(options);
break;
default:
Logger.reportError("SignInToWebsiteController", "Unknown observer notification:", aTopic);
break;
}
},
/*
* options: method required - name of method to invoke
* assertion optional
*/
_makeDoMethodCallback: function SignInToWebsiteController__makeDoMethodCallback(aRpId) {
return function SignInToWebsiteController_methodCallback(aOptions) {
log("doMethod:", aOptions);
let message = aOptions.json;
if (typeof message === 'string') {
message = JSON.parse(message);
}
switch(message.method) {
case "ready":
IdentityService.doReady(aRpId);
break;
case "login":
IdentityService.doLogin(aRpId, message.assertion);
break;
case "logout":
IdentityService.doLogout(aRpId);
break;
default:
log("WARNING: wonky method call:", message.method);
break;
}
};
},
doWatch: function SignInToWebsiteController_doWatch(aOptions) {
// dom prevents watch from being called twice
log("doWatch:", aOptions);
var gaiaOptions = {
message: kIdentityDelegateWatch,
showUI: false
};
this.pipe.communicate(aOptions, gaiaOptions, this._makeDoMethodCallback(aOptions.rpId));
},
/**
* The website is requesting login so the user must choose an identity to use.
*/
doRequest: function SignInToWebsiteController_doRequest(aOptions) {
log("doRequest", aOptions);
// tell gaia to open the identity popup
var gaiaOptions = {
message: kIdentityDelegateRequest,
showUI: true
};
this.pipe.communicate(aOptions, gaiaOptions, this._makeDoMethodCallback(aOptions.rpId));
},
/*
*
*/
doLogout: function SignInToWebsiteController_doLogout(aOptions) {
log("doLogout", aOptions);
var gaiaOptions = {
message: kIdentityDelegateLogout,
showUI: false
};
this.pipe.communicate(aOptions, gaiaOptions, this._makeDoMethodCallback(aOptions.rpId));
}
};

View File

@ -0,0 +1,134 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
const Ci = Components.interfaces;
const Cu = Components.utils;
// The following boilerplate makes sure that XPCom calls
// that use the profile directory work.
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "MinimalIDService",
"resource://gre/modules/identity/MinimalIdentity.jsm",
"IdentityService");
XPCOMUtils.defineLazyModuleGetter(this,
"Logger",
"resource://gre/modules/identity/LogUtils.jsm");
XPCOMUtils.defineLazyServiceGetter(this,
"uuidGenerator",
"@mozilla.org/uuid-generator;1",
"nsIUUIDGenerator");
const TEST_URL = "https://myfavoriteflan.com";
const TEST_USER = "uumellmahaye1969@hotmail.com";
const TEST_PRIVKEY = "i-am-a-secret";
const TEST_CERT = "i~like~pie";
// The following are utility functions for Identity testing
function log(...aMessageArgs) {
Logger.log.apply(Logger, ["test"].concat(aMessageArgs));
}
function partial(fn) {
let args = Array.prototype.slice.call(arguments, 1);
return function() {
return fn.apply(this, args.concat(Array.prototype.slice.call(arguments)));
};
}
function uuid() {
return uuidGenerator.generateUUID().toString();
}
// create a mock "doc" object, which the Identity Service
// uses as a pointer back into the doc object
function mockDoc(aIdentity, aOrigin, aDoFunc) {
let mockedDoc = {};
mockedDoc.id = uuid();
mockedDoc.loggedInEmail = aIdentity;
mockedDoc.origin = aOrigin;
mockedDoc['do'] = aDoFunc;
mockedDoc.doReady = partial(aDoFunc, 'ready');
mockedDoc.doLogin = partial(aDoFunc, 'login');
mockedDoc.doLogout = partial(aDoFunc, 'logout');
mockedDoc.doError = partial(aDoFunc, 'error');
mockedDoc.doCancel = partial(aDoFunc, 'cancel');
mockedDoc.doCoffee = partial(aDoFunc, 'coffee');
return mockedDoc;
}
// create a mock "pipe" object that would normally communicate
// messages up to gaia (either the trusty ui or the hidden iframe),
// and convey messages back down from gaia to the controller through
// the message callback.
function mockPipe() {
let MockedPipe = {
communicate: function(aRpOptions, aGaiaOptions, aMessageCallback) {
switch (aGaiaOptions.message) {
case "identity-delegate-watch":
aMessageCallback({json: {method: "ready"}});
break;
case "identity-delegate-request":
aMessageCallback({json: {method: "login", assertion: TEST_CERT}});
break;
case "identity-delegate-logout":
aMessageCallback({json: {method: "logout"}});
break;
default:
throw("what the what?? " + aGaiaOptions.message);
break;
}
}
};
return MockedPipe;
}
// 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) {
let observer = {
// nsISupports provides type management in C++
// nsIObserver is to be an observer
QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports, Ci.nsIObserver]),
observe: function (aSubject, aTopic, aData) {
if (aTopic == aObserveTopic) {
Services.obs.removeObserver(observer, aObserveTopic);
aObserveFunc(aSubject, aTopic, aData);
}
}
};
Services.obs.addObserver(observer, aObserveTopic, false);
}
// a hook to set up the ID service with an identity with keypair and all
// when ready, invoke callback with the identity. It's there if we need it.
function setup_test_identity(identity, cert, cb) {
cb();
}
// takes a list of functions and returns a function that
// when called the first time, calls the first func,
// then the next time the second, etc.
function call_sequentially() {
let numCalls = 0;
let funcs = arguments;
return function() {
if (!funcs[numCalls]) {
let argString = Array.prototype.slice.call(arguments).join(",");
do_throw("Too many calls: " + argString);
return;
}
funcs[numCalls].apply(funcs[numCalls],arguments);
numCalls += 1;
};
}

View File

@ -0,0 +1,162 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
// Tests for b2g/components/SignInToWebsite.jsm
"use strict";
XPCOMUtils.defineLazyModuleGetter(this, "MinimalIDService",
"resource://gre/modules/identity/MinimalIdentity.jsm",
"IdentityService");
XPCOMUtils.defineLazyModuleGetter(this, "SignInToWebsiteController",
"resource://gre/modules/SignInToWebsite.jsm",
"SignInToWebsiteController");
Cu.import("resource://gre/modules/identity/LogUtils.jsm");
function log(...aMessageArgs) {
Logger.log.apply(Logger, ["test_signintowebsite"].concat(aMessageArgs));
}
function test_overall() {
do_check_neq(MinimalIDService, null);
run_next_test();
}
function test_mock_doc() {
do_test_pending();
let mockedDoc = mockDoc(null, TEST_URL, function(action, params) {
do_check_eq(action, 'coffee');
do_test_finished();
run_next_test();
});
// A smoke test to ensure that mockedDoc is functioning correctly.
// There is presently no doCoffee method in Persona.
mockedDoc.doCoffee();
}
function test_watch() {
do_test_pending();
setup_test_identity("pie@food.gov", TEST_CERT, function() {
let controller = SignInToWebsiteController;
let mockedDoc = mockDoc(null, TEST_URL, function(action, params) {
do_check_eq(action, 'ready');
controller.uninit();
do_test_finished();
run_next_test();
});
controller.init({pipe: mockPipe()});
MinimalIDService.RP.watch(mockedDoc, {});
});
}
function test_request_login() {
do_test_pending();
setup_test_identity("flan@food.gov", TEST_CERT, function() {
let controller = SignInToWebsiteController;
let mockedDoc = mockDoc(null, TEST_URL, call_sequentially(
function(action, params) {
do_check_eq(action, 'ready');
do_check_eq(params, undefined);
},
function(action, params) {
do_check_eq(action, 'login');
do_check_eq(params, TEST_CERT);
controller.uninit();
do_test_finished();
run_next_test();
}
));
controller.init({pipe: mockPipe()});
MinimalIDService.RP.watch(mockedDoc, {});
MinimalIDService.RP.request(mockedDoc.id, {});
});
}
function test_request_logout() {
do_test_pending();
setup_test_identity("flan@food.gov", TEST_CERT, function() {
let controller = SignInToWebsiteController;
let mockedDoc = mockDoc(null, TEST_URL, call_sequentially(
function(action, params) {
do_check_eq(action, 'ready');
do_check_eq(params, undefined);
},
function(action, params) {
do_check_eq(action, 'logout');
do_check_eq(params, undefined);
controller.uninit();
do_test_finished();
run_next_test();
}
));
controller.init({pipe: mockPipe()});
MinimalIDService.RP.watch(mockedDoc, {});
MinimalIDService.RP.logout(mockedDoc.id, {});
});
}
function test_request_login_logout() {
do_test_pending();
setup_test_identity("unagi@food.gov", TEST_CERT, function() {
let controller = SignInToWebsiteController;
let mockedDoc = mockDoc(null, TEST_URL, call_sequentially(
function(action, params) {
do_check_eq(action, 'ready');
do_check_eq(params, undefined);
},
function(action, params) {
do_check_eq(action, 'login');
do_check_eq(params, TEST_CERT);
},
function(action, params) {
do_check_eq(action, 'logout');
do_check_eq(params, undefined);
controller.uninit();
do_test_finished();
run_next_test();
}
/*
,function(action, params) {
do_check_eq(action, 'ready');
do_test_finished();
run_next_test();
}
*/
));
controller.init({pipe: mockPipe()});
MinimalIDService.RP.watch(mockedDoc, {});
MinimalIDService.RP.request(mockedDoc.id, {});
MinimalIDService.RP.logout(mockedDoc.id, {});
});
}
let TESTS = [
test_overall,
test_mock_doc,
test_watch,
test_request_login,
test_request_logout,
test_request_login_logout
];
TESTS.forEach(add_test);
function run_test() {
run_next_test();
}

View File

@ -3,3 +3,9 @@ head =
tail = tail =
[test_bug793310.js] [test_bug793310.js]
[test_signintowebsite.js]
head = head_identity.js
tail =

View File

@ -225,6 +225,7 @@
@BINPATH@/components/hal.xpt @BINPATH@/components/hal.xpt
@BINPATH@/components/html5.xpt @BINPATH@/components/html5.xpt
@BINPATH@/components/htmlparser.xpt @BINPATH@/components/htmlparser.xpt
@BINPATH@/components/identity.xpt
@BINPATH@/components/imglib2.xpt @BINPATH@/components/imglib2.xpt
@BINPATH@/components/imgicon.xpt @BINPATH@/components/imgicon.xpt
@BINPATH@/components/inspector.xpt @BINPATH@/components/inspector.xpt
@ -492,6 +493,10 @@
@BINPATH@/components/AppsService.js @BINPATH@/components/AppsService.js
@BINPATH@/components/AppsService.manifest @BINPATH@/components/AppsService.manifest
@BINPATH@/components/nsDOMIdentity.js
@BINPATH@/components/nsIDService.js
@BINPATH@/components/Identity.manifest
@BINPATH@/components/SystemMessageInternal.js @BINPATH@/components/SystemMessageInternal.js
@BINPATH@/components/SystemMessageManager.js @BINPATH@/components/SystemMessageManager.js
@BINPATH@/components/SystemMessageManager.manifest @BINPATH@/components/SystemMessageManager.manifest

View File

@ -13,12 +13,20 @@ Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm"); Cu.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "IdentityService", XPCOMUtils.defineLazyModuleGetter(this, "IdentityService",
#ifdef MOZ_B2G_VERSION
"resource://gre/modules/identity/MinimalIdentity.jsm");
#else
"resource://gre/modules/identity/Identity.jsm"); "resource://gre/modules/identity/Identity.jsm");
#endif
XPCOMUtils.defineLazyModuleGetter(this, XPCOMUtils.defineLazyModuleGetter(this,
"Logger", "Logger",
"resource://gre/modules/identity/LogUtils.jsm"); "resource://gre/modules/identity/LogUtils.jsm");
XPCOMUtils.defineLazyServiceGetter(this, "ppmm",
"@mozilla.org/parentprocessmessagemanager;1",
"nsIMessageListenerManager");
function log(...aMessageArgs) { function log(...aMessageArgs) {
Logger.log.apply(Logger, ["DOMIdentity"].concat(aMessageArgs)); Logger.log.apply(Logger, ["DOMIdentity"].concat(aMessageArgs));
} }
@ -122,9 +130,7 @@ let DOMIdentity = {
// Target is the frame message manager that called us and is // Target is the frame message manager that called us and is
// used to send replies back to the proper window. // used to send replies back to the proper window.
let targetMM = aMessage.target let targetMM = aMessage.target;
.QueryInterface(Ci.nsIFrameLoaderOwner)
.frameLoader.messageManager;
switch (aMessage.name) { switch (aMessage.name) {
// RP // RP
@ -165,16 +171,10 @@ let DOMIdentity = {
// nsIObserver // nsIObserver
observe: function DOMIdentity_observe(aSubject, aTopic, aData) { observe: function DOMIdentity_observe(aSubject, aTopic, aData) {
switch (aTopic) { switch (aTopic) {
case "domwindowopened":
case "domwindowclosed":
let win = aSubject.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindow);
this._configureMessages(win, aTopic == "domwindowopened");
break;
case "xpcom-shutdown": case "xpcom-shutdown":
Services.ww.unregisterNotification(this); this._unsubscribeListeners();
Services.obs.removeObserver(this, "xpcom-shutdown"); Services.obs.removeObserver(this, "xpcom-shutdown");
Services.ww.unregisterNotification(this);
break; break;
} }
}, },
@ -190,20 +190,23 @@ let DOMIdentity = {
_init: function DOMIdentity__init() { _init: function DOMIdentity__init() {
Services.ww.registerNotification(this); Services.ww.registerNotification(this);
Services.obs.addObserver(this, "xpcom-shutdown", false); Services.obs.addObserver(this, "xpcom-shutdown", false);
this._subscribeListeners();
}, },
_configureMessages: function DOMIdentity__configureMessages(aWindow, aRegister) { _subscribeListeners: function DOMIdentity__subscribeListeners() {
if (!aWindow.messageManager) if (!ppmm) return;
return;
let func = aWindow.messageManager[aRegister ? "addMessageListener"
: "removeMessageListener"];
for (let message of this.messages) { for (let message of this.messages) {
func.call(aWindow.messageManager, message, this); ppmm.addMessageListener(message, this);
} }
}, },
_unsubscribeListeners: function DOMIdentity__unsubscribeListeners() {
for (let message of this.messages) {
ppmm.removeMessageListener(message, this);
}
ppmm = null;
},
_resetFrameState: function(aContext) { _resetFrameState: function(aContext) {
log("_resetFrameState: ", aContext.id); log("_resetFrameState: ", aContext.id);
if (!aContext._mm) { if (!aContext._mm) {

View File

@ -17,10 +17,13 @@ EXTRA_COMPONENTS = \
Identity.manifest \ Identity.manifest \
$(NULL) $(NULL)
EXTRA_JS_MODULES = \ EXTRA_PP_JS_MODULES = \
DOMIdentity.jsm \ DOMIdentity.jsm \
$(NULL) $(NULL)
EXTRA_JS_MODULES = \
$(NULL)
ifdef ENABLE_TESTS ifdef ENABLE_TESTS
DIRS += tests DIRS += tests
endif endif

View File

@ -17,6 +17,10 @@ const MAX_RP_CALLS = 100;
Cu.import("resource://gre/modules/Services.jsm"); Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm"); Cu.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyServiceGetter(this, "cpmm",
"@mozilla.org/childprocessmessagemanager;1",
"nsIMessageSender");
// This is the child process corresponding to nsIDOMIdentity. // This is the child process corresponding to nsIDOMIdentity.
@ -483,10 +487,7 @@ nsDOMIdentityInternal.prototype = {
this._log("init was called from " + aWindow.document.location); this._log("init was called from " + aWindow.document.location);
this._mm = aWindow.QueryInterface(Ci.nsIInterfaceRequestor) this._mm = cpmm;
.getInterface(Ci.nsIWebNavigation)
.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIContentFrameMessageManager);
// Setup listeners for messages from parent process. // Setup listeners for messages from parent process.
this._messages = [ this._messages = [

View File

@ -11,11 +11,6 @@ relativesrcdir = @relativesrcdir@
include $(DEPTH)/config/autoconf.mk include $(DEPTH)/config/autoconf.mk
MOCHITEST_FILES = \ # XXX bug 805602 update and restore mochitests
head_identity.js \
test_identity_idp_auth_basics.html \
test_identity_idp_prov_basics.html \
test_identity_rp_basics.html \
$(NULL)
include $(topsrcdir)/config/rules.mk include $(topsrcdir)/config/rules.mk

View File

@ -1,82 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
const Ci = SpecialPowers.Ci;
const Cu = SpecialPowers.Cu;
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 = SpecialPowers.getDOMWindowUtils(window);
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

@ -1,87 +0,0 @@
<!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

@ -1,160 +0,0 @@
<!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);
}, 700);
}
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

@ -1,177 +0,0 @@
<!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">
<button id='request'>request</button>
</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);
}, 700);
}
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() {
var button = document.getElementById('request');
button.addEventListener('click', function requestHandler() {
button.removeEventListener('click', requestHandler);
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

@ -358,6 +358,7 @@ pref("toolkit.telemetry.infoURL", "http://www.mozilla.com/legal/privacy/firefox.
pref("toolkit.telemetry.debugSlowSql", false); pref("toolkit.telemetry.debugSlowSql", false);
// Identity module // Identity module
pref("toolkit.identity.enabled", false);
pref("toolkit.identity.debug", false); pref("toolkit.identity.debug", false);
// Disable remote debugging protocol logging // Disable remote debugging protocol logging
@ -3797,8 +3798,6 @@ pref("social.enabled", false);
// observers (bug 780507). // observers (bug 780507).
pref("dom.idle-observers-api.fuzz_time.disabled", true); pref("dom.idle-observers-api.fuzz_time.disabled", true);
pref("toolkit.identity.debug", false);
// Setting that to true grant elevated privileges to apps that ask // Setting that to true grant elevated privileges to apps that ask
// for them in their manifest. // for them in their manifest.
pref("dom.mozApps.dev_mode", false); pref("dom.mozApps.dev_mode", false);

View File

@ -35,6 +35,7 @@ EXTRA_JS_MODULES = \
IdentityStore.jsm \ IdentityStore.jsm \
jwcrypto.jsm \ jwcrypto.jsm \
LogUtils.jsm \ LogUtils.jsm \
MinimalIdentity.jsm \
RelyingParty.jsm \ RelyingParty.jsm \
Sandbox.jsm \ Sandbox.jsm \
$(NULL) $(NULL)

View File

@ -0,0 +1,376 @@
/* -*- Mode: js2; js2-basic-offset: 2; indent-tabs-mode: nil; -*- */
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
/* 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/. */
/*
* This alternate implementation of IdentityService provides just the
* channels for navigator.id, leaving the certificate storage to a
* server-provided app.
*
* On b2g, the messages identity-controller-watch, -request, and
* -logout, are observed by the component SignInToWebsite.jsm.
*/
"use strict";
const EXPORTED_SYMBOLS = ["IdentityService"];
const Cu = Components.utils;
const Ci = Components.interfaces;
const Cc = Components.classes;
const Cr = Components.results;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/identity/LogUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this,
"jwcrypto",
"resource://gre/modules/identity/jwcrypto.jsm");
function log(...aMessageArgs) {
Logger.log.apply(Logger, ["minimal core"].concat(aMessageArgs));
}
function reportError(...aMessageArgs) {
Logger.reportError.apply(Logger, ["core"].concat(aMessageArgs));
}
function IDService() {
Services.obs.addObserver(this, "quit-application-granted", false);
// Services.obs.addObserver(this, "identity-auth-complete", false);
// simplify, it's one object
this.RP = this;
this.IDP = this;
// keep track of flows
this._rpFlows = {};
this._authFlows = {};
this._provFlows = {};
}
IDService.prototype = {
QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports, Ci.nsIObserver]),
observe: function observe(aSubject, aTopic, aData) {
switch (aTopic) {
case "quit-application-granted":
Services.obs.removeObserver(this, "quit-application-granted");
// Services.obs.removeObserver(this, "identity-auth-complete");
break;
}
},
/**
* Parse an email into username and domain if it is valid, else return null
*/
parseEmail: function parseEmail(email) {
var match = email.match(/^([^@]+)@([^@^/]+.[a-z]+)$/);
if (match) {
return {
username: match[1],
domain: match[2]
};
}
return null;
},
/**
* Register a listener for a given windowID as a result of a call to
* navigator.id.watch().
*
* @param aCaller
* (Object) an object that represents the caller document, and
* is expected to have properties:
* - id (unique, e.g. uuid)
* - loggedInEmail (string or null)
* - origin (string)
*
* and a bunch of callbacks
* - doReady()
* - doLogin()
* - doLogout()
* - doError()
* - doCancel()
*
*/
watch: function watch(aRpCaller) {
log("watch: caller keys:", Object.keys(aRpCaller));
log("watch: rpcaller:", aRpCaller);
// store the caller structure and notify the UI observers
// note that, unfortunately, what here is loggedInEmail is called
// loggedInUser in the native API.
this._rpFlows[aRpCaller.id] = aRpCaller;
let options = {rpId: aRpCaller.id,
origin: aRpCaller.origin,
loggedInUser: aRpCaller.loggedInEmail || null};
log("sending identity-controller-watch:", options);
Services.obs.notifyObservers({wrappedJSObject: options},"identity-controller-watch", null);
},
/**
* Initiate a login with user interaction as a result of a call to
* navigator.id.request().
*
* @param aRPId
* (integer) the id of the doc object obtained in .watch()
*
* @param aOptions
* (Object) options including privacyPolicy, termsOfService
*/
request: function request(aRPId, aOptions) {
log("request: rpId:", aRPId);
let rp = this._rpFlows[aRPId];
// Notify UX to display identity picker.
// Pass the doc id to UX so it can pass it back to us later.
let options = {rpId: aRPId, origin: rp.origin};
Services.obs.notifyObservers({wrappedJSObject: options}, "identity-controller-request", null);
},
/**
* Invoked when a user wishes to logout of a site (for instance, when clicking
* on an in-content logout button).
*
* @param aRpCallerId
* (integer) the id of the doc object obtained in .watch()
*
*/
logout: function logout(aRpCallerId) {
log("logout: RP caller id:", aRpCallerId);
let rp = this._rpFlows[aRpCallerId];
let options = {rpId: aRpCallerId, origin: rp.origin};
Services.obs.notifyObservers({wrappedJSObject: options}, "identity-controller-logout", null);
},
/*
* once the UI-and-display-logic components have received
* notifications, they call back with direct invocation of the
* following functions (doLogin, doLogout, or doReady)
*/
doLogin: function doLogin(aRpCallerId, aAssertion) {
let rp = this._rpFlows[aRpCallerId];
if (!rp)
return;
rp.doLogin(aAssertion);
},
doLogout: function doLogout(aRpCallerId) {
let rp = this._rpFlows[aRpCallerId];
if (!rp)
return;
rp.doLogout();
},
doReady: function doReady(aRpCallerId) {
let rp = this._rpFlows[aRpCallerId];
if (!rp)
return;
rp.doReady();
},
/*
* XXX Bug 804229: Implement Identity Provider Functions
*
* Stubs for Identity Provider functions follow
*/
/**
* the provisioning iframe sandbox has called navigator.id.beginProvisioning()
*
* @param aCaller
* (object) the iframe sandbox caller with all callbacks and
* other information. Callbacks include:
* - doBeginProvisioningCallback(id, duration_s)
* - doGenKeyPairCallback(pk)
*/
beginProvisioning: function beginProvisioning(aCaller) {
},
/**
* the provisioning iframe sandbox has called
* navigator.id.raiseProvisioningFailure()
*
* @param aProvId
* (int) the identifier of the provisioning flow tied to that sandbox
* @param aReason
*/
raiseProvisioningFailure: function raiseProvisioningFailure(aProvId, aReason) {
reportError("Provisioning failure", aReason);
},
/**
* When navigator.id.genKeyPair is called from provisioning iframe sandbox.
* Generates a keypair for the current user being provisioned.
*
* @param aProvId
* (int) the identifier of the provisioning caller tied to that sandbox
*
* It is an error to call genKeypair without receiving the callback for
* the beginProvisioning() call first.
*/
genKeyPair: function genKeyPair(aProvId) {
},
/**
* When navigator.id.registerCertificate is called from provisioning iframe
* sandbox.
*
* Sets the certificate for the user for which a certificate was requested
* via a preceding call to beginProvisioning (and genKeypair).
*
* @param aProvId
* (integer) the identifier of the provisioning caller tied to that
* sandbox
*
* @param aCert
* (String) A JWT representing the signed certificate for the user
* being provisioned, provided by the IdP.
*/
registerCertificate: function registerCertificate(aProvId, aCert) {
},
/**
* The authentication frame has called navigator.id.beginAuthentication
*
* IMPORTANT: the aCaller is *always* non-null, even if this is called from
* a regular content page. We have to make sure, on every DOM call, that
* aCaller is an expected authentication-flow identifier. If not, we throw
* an error or something.
*
* @param aCaller
* (object) the authentication caller
*
*/
beginAuthentication: function beginAuthentication(aCaller) {
},
/**
* The auth frame has called navigator.id.completeAuthentication
*
* @param aAuthId
* (int) the identifier of the authentication caller tied to that sandbox
*
*/
completeAuthentication: function completeAuthentication(aAuthId) {
},
/**
* The auth frame has called navigator.id.cancelAuthentication
*
* @param aAuthId
* (int) the identifier of the authentication caller
*
*/
cancelAuthentication: function cancelAuthentication(aAuthId) {
},
// methods for chrome and add-ons
/**
* Discover the IdP for an identity
*
* @param aIdentity
* (string) the email we're logging in with
*
* @param aCallback
* (function) callback to invoke on completion
* with first-positional parameter the error.
*/
_discoverIdentityProvider: function _discoverIdentityProvider(aIdentity, aCallback) {
// XXX bug 767610 - validate email address call
// When that is available, we can remove this custom parser
var parsedEmail = this.parseEmail(aIdentity);
if (parsedEmail === null) {
return aCallback("Could not parse email: " + aIdentity);
}
log("_discoverIdentityProvider: identity:", aIdentity, "domain:", parsedEmail.domain);
this._fetchWellKnownFile(parsedEmail.domain, function fetchedWellKnown(err, idpParams) {
// idpParams includes the pk, authorization url, and
// provisioning url.
// XXX bug 769861 follow any authority delegations
// if no well-known at any point in the delegation
// fall back to browserid.org as IdP
return aCallback(err, idpParams);
});
},
/**
* Fetch the well-known file from the domain.
*
* @param aDomain
*
* @param aScheme
* (string) (optional) Protocol to use. Default is https.
* This is necessary because we are unable to test
* https.
*
* @param aCallback
*
*/
_fetchWellKnownFile: function _fetchWellKnownFile(aDomain, aCallback, aScheme='https') {
// XXX bug 769854 make tests https and remove aScheme option
let url = aScheme + '://' + aDomain + "/.well-known/browserid";
log("_fetchWellKnownFile:", url);
// this appears to be a more successful way to get at xmlhttprequest (which supposedly will close with a window
let req = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"]
.createInstance(Ci.nsIXMLHttpRequest);
// XXX bug 769865 gracefully handle being off-line
// XXX bug 769866 decide on how to handle redirects
req.open("GET", url, true);
req.responseType = "json";
req.mozBackgroundRequest = true;
req.onload = function _fetchWellKnownFile_onload() {
if (req.status < 200 || req.status >= 400) {
log("_fetchWellKnownFile", url, ": server returned status:", req.status);
return aCallback("Error");
}
try {
let idpParams = req.response;
// Verify that the IdP returned a valid configuration
if (! (idpParams.provisioning &&
idpParams.authentication &&
idpParams['public-key'])) {
let errStr= "Invalid well-known file from: " + aDomain;
log("_fetchWellKnownFile:", errStr);
return aCallback(errStr);
}
let callbackObj = {
domain: aDomain,
idpParams: idpParams,
};
log("_fetchWellKnownFile result: ", callbackObj);
// Yay. Valid IdP configuration for the domain.
return aCallback(null, callbackObj);
} catch (err) {
reportError("_fetchWellKnownFile", "Bad configuration from", aDomain, err);
return aCallback(err.toString());
}
};
req.onerror = function _fetchWellKnownFile_onerror() {
log("_fetchWellKnownFile", "ERROR:", req.status, req.statusText);
log("ERROR: _fetchWellKnownFile:", err);
return aCallback("Error");
};
req.send(null);
},
};
let IdentityService = new IDService();

View File

@ -10,11 +10,6 @@ relativesrcdir = @relativesrcdir@
include $(DEPTH)/config/autoconf.mk include $(DEPTH)/config/autoconf.mk
MOCHITEST_FILES = \ # XXX bug 805602 update and restore mochitests
head_identity.js \
test_authentication.html \
test_provisioning.html \
test_relying_party.html \
$(NULL)
include $(topsrcdir)/config/rules.mk include $(topsrcdir)/config/rules.mk

View File

@ -1,221 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
/** Helper functions for identity mochitests **/
/** Please keep functions in-sync with unit/head_identity.js **/
"use strict";
const Cc = SpecialPowers.Cc;
const Ci = SpecialPowers.Ci;
const Cu = SpecialPowers.Cu;
const TEST_URL = "http://mochi.test:8888";
const TEST_URL2 = "https://myfavoritebaconinacan.com";
const TEST_USER = "user@example.com";
const TEST_PRIVKEY = "fake-privkey";
const TEST_CERT = "fake-cert";
const TEST_IDPPARAMS = {
domain: "example.com",
idpParams: {
authentication: "/foo/authenticate.html",
provisioning: "/foo/provision.html"
},
};
const Services = Cu.import("resource://gre/modules/Services.jsm").Services;
// Set the debug pref before loading other modules
SpecialPowers.setBoolPref("toolkit.identity.debug", true);
SpecialPowers.setBoolPref("dom.identity.enabled", true);
// Shutdown the UX if it exists so that it won't interfere with tests by also responding to
// observer notifications.
try {
const SignInToWebsiteUX = Cu.import("resource:///modules/SignInToWebsite.jsm").SignInToWebsiteUX;
if (SignInToWebsiteUX) {
SignInToWebsiteUX.uninit();
}
} catch (ex) {
// The module doesn't exist
}
const jwcrypto = Cu.import("resource://gre/modules/identity/jwcrypto.jsm").jwcrypto;
const IdentityStore = Cu.import("resource://gre/modules/identity/IdentityStore.jsm").IdentityStore;
const RelyingParty = Cu.import("resource://gre/modules/identity/RelyingParty.jsm").RelyingParty;
const XPCOMUtils = Cu.import("resource://gre/modules/XPCOMUtils.jsm").XPCOMUtils;
const IDService = Cu.import("resource://gre/modules/identity/Identity.jsm").IdentityService;
const IdentityProvider = Cu.import("resource://gre/modules/identity/IdentityProvider.jsm").IdentityProvider;
const identity = navigator.id || navigator.mozId;
function do_check_null(aVal, aMsg) {
is(aVal, null, aMsg);
}
function do_timeout(aDelay, aFunc) {
setTimeout(aFunc, aDelay);
}
function get_idstore() {
return IdentityStore;
}
// create a mock "watch" object, which the Identity Service
function mock_watch(aIdentity, aDoFunc) {
function partial(fn) {
let args = Array.prototype.slice.call(arguments, 1);
return function() {
return fn.apply(this, args.concat(Array.prototype.slice.call(arguments)));
};
}
let mockedWatch = {};
mockedWatch.loggedInEmail = aIdentity;
mockedWatch['do'] = aDoFunc;
mockedWatch.onready = partial(aDoFunc, 'ready');
mockedWatch.onlogin = partial(aDoFunc, 'login');
mockedWatch.onlogout = partial(aDoFunc, 'logout');
return mockedWatch;
}
// 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) {
Services.obs.removeObserver(this, aObserveTopic);
try {
aObserveFunc(SpecialPowers.wrap(aSubject), aTopic, aData);
} catch (ex) {
ok(false, ex);
}
}
}
Services.obs.addObserver(observe, aObserveTopic, false);
}
// set up the ID service with an identity with keypair and all
// when ready, invoke callback with the identity
function setup_test_identity(identity, cert, cb) {
// set up the store so that we're supposed to be logged in
let store = get_idstore();
function keyGenerated(err, kpo) {
store.addIdentity(identity, kpo, cert);
cb();
};
jwcrypto.generateKeyPair("DS160", keyGenerated);
}
// takes a list of functions and returns a function that
// when called the first time, calls the first func,
// then the next time the second, etc.
function call_sequentially() {
let numCalls = 0;
let funcs = arguments;
return function() {
if (!funcs[numCalls]) {
let argString = Array.prototype.slice.call(arguments).join(",");
ok(false, "Too many calls: " + argString);
return;
}
funcs[numCalls].apply(funcs[numCalls], arguments);
numCalls += 1;
};
}
/*
* Setup a provisioning workflow with appropriate callbacks
*
* identity is the email we're provisioning.
*
* afterSetupCallback is required.
*
* doneProvisioningCallback is optional, if the caller
* wants to be notified when the whole provisioning workflow is done
*
* frameCallbacks is optional, contains the callbacks that the sandbox
* frame would provide in response to DOM calls.
*/
function setup_provisioning(identity, afterSetupCallback, doneProvisioningCallback, callerCallbacks) {
IDService.reset();
let util = SpecialPowers.getDOMWindowUtils(window);
let provId = util.outerWindowID;
IDService.IDP._provisionFlows[provId] = {
identity : identity,
idpParams: TEST_IDPPARAMS,
callback: function(err) {
if (doneProvisioningCallback)
doneProvisioningCallback(err);
},
sandbox: {
// Emulate the free() method on the iframe sandbox
free: function() {}
}
};
let caller = {};
caller.id = callerCallbacks.id = provId;
caller.doBeginProvisioningCallback = function(id, duration_s) {
if (callerCallbacks && callerCallbacks.beginProvisioningCallback)
callerCallbacks.beginProvisioningCallback(id, duration_s);
};
caller.doGenKeyPairCallback = function(pk) {
if (callerCallbacks && callerCallbacks.genKeyPairCallback)
callerCallbacks.genKeyPairCallback(pk);
};
afterSetupCallback(callerCallbacks);
}
function resetState() {
IDService.reset();
}
function cleanup() {
resetState();
SpecialPowers.clearUserPref("toolkit.identity.debug");
SpecialPowers.clearUserPref("dom.identity.enabled");
// Re-init the UX that we uninit
try {
const SignInToWebsiteUX = Cu.import("resource:///modules/SignInToWebsite.jsm").SignInToWebsiteUX;
if (SignInToWebsiteUX) {
SignInToWebsiteUX.init();
}
} catch (ex) {
// The module doesn't exist
}
}
var TESTS = [];
function run_next_test() {
if (!identity) {
todo(false, "DOM API is not available. Skipping tests.");
cleanup();
SimpleTest.finish();
return;
}
if (TESTS.length) {
let test = TESTS.shift();
info(test.name);
try {
test();
} catch (ex) {
ok(false, ex);
}
} else {
cleanup();
info("all done");
SimpleTest.finish();
}
}

View File

@ -1,161 +0,0 @@
<!DOCTYPE html>
<html>
<!--
Test of Identity Provider (IDP) Authentication using the DOM APIs
-->
<head>
<meta charset="utf-8">
<title>Test of Identity Provider (IDP) Authentication using the DOM APIs</title>
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
<script type="application/javascript;version=1.8" src="head_identity.js"></script>
</head>
<body onload="run_next_test()">
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=753238">Test of Identity Provider (IDP) Authentication using the DOM APIs</a>
<p id="display"></p>
<div id="content" style="display: none">
</div>
<pre id="test">
<script type="application/javascript;version=1.8">
/** Test of Identity Provider (IDP) Authentication using the DOM APIs **/
/** Most tests are ported from test_authentication.js */
"use strict";
SimpleTest.waitForExplicitFinish();
const DOMIdentity = Cu.import("resource://gre/modules/DOMIdentity.jsm")
.DOMIdentity;
let outerWinId = SpecialPowers.getDOMWindowUtils(window).outerWindowID;
function run_next_auth_test() {
// Reset the DOM state then run the next test
let provContext = IdentityProvider._provisionFlows[outerWinId];
if (provContext && provContext.caller) {
makeObserver("identity-DOM-state-reset", function() {
SimpleTest.executeSoon(run_next_test);
});
DOMIdentity._resetFrameState(provContext.caller);
} else {
SimpleTest.executeSoon(run_next_test);
}
}
function test_begin_authentication_flow() {
let _provId = null;
// set up a watch, to be consistent
let mockedDoc = mock_watch(null, function(action, params) {});
identity.watch(mockedDoc);
// The identity-auth notification is sent up to the UX from the
// _doAuthentication function. Be ready to receive it and call
// beginAuthentication
makeObserver("identity-auth", function (aSubject, aTopic, aData) {
isnot(aSubject, null);
is(aSubject.wrappedJSObject.provId, _provId);
run_next_auth_test();
});
setup_provisioning(
TEST_USER,
function(caller) {
_provId = caller.id;
identity.beginProvisioning(caller.beginProvisioningCallback);
}, function() {},
{
beginProvisioningCallback: function(email, duration_s) {
// let's say this user needs to authenticate
IDService.IDP._doAuthentication(_provId, TEST_IDPPARAMS);
}
});
}
function test_complete_authentication_flow() {
let _provId = null;
let _authId = null;
let id = TEST_USER;
let callbacksFired = false;
let topicObserved = false;
// The result of authentication should be a successful login
IDService.reset();
let mockedDoc = mock_watch(null, call_sequentially(
function(action, params) {
is(action, 'ready');
is(params, undefined);
}
));
identity.watch(mockedDoc);
// A mock calling context
let authCaller = {
doBeginAuthenticationCallback: function doBeginAuthCallback(aIdentity) {
is(aIdentity, TEST_USER);
identity.completeAuthentication();
},
doError: function(err) {
ok(false, "doError called: " + err);
},
};
makeObserver("identity-auth-complete", function (aSubject, aTopic, aData) {
info("identity-auth-complete");
is(aSubject.wrappedJSObject.identity, TEST_USER);
run_next_test();
});
// Create a provisioning flow for our auth flow to attach to
setup_provisioning(
TEST_USER,
function(provFlow) {
_provId = provFlow.id;
info("after setup: " + _provId);
identity.beginProvisioning(provFlow.beginProvisioningCallback);
},
function() {
info("doneProvisioningCallback");
// let's say this user needs to authenticate
IDService.IDP._doAuthentication(_provId, TEST_IDPPARAMS);
// test_begin_authentication_flow verifies that the right
// message is sent to the UI. So that works. Moving on,
// the UI calls setAuthenticationFlow ...
_authId = outerWinId;
IDService.IDP.setAuthenticationFlow(_authId, _provId);
IDService.IDP._provisionFlows[_provId].rpId = outerWinId;
// ... then the UI calls beginAuthentication ...
authCaller.id = _authId;
IDService.IDP._provisionFlows[_provId].caller = authCaller;
identity.beginAuthentication(function bac() {
info("beginAuthentication callback");
identity.completeAuthentication();
});
},
{
beginProvisioningCallback: function(email, duration_s) {
info("beginProvisioningCallback");
identity.raiseProvisioningFailure("Prov. failed");
}
}
);
}
TESTS.push(test_begin_authentication_flow);
TESTS.push(test_complete_authentication_flow);
</script>
</pre>
</body>
</html>

View File

@ -1,285 +0,0 @@
<!DOCTYPE html>
<html>
<!--
Test of Identity Provider (IDP) Provisioning using the DOM APIs
-->
<head>
<meta charset="utf-8">
<title>Test of Identity Provider (IDP) Provisioning using the DOM APIs</title>
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
<script type="application/javascript;version=1.8" src="head_identity.js"></script>
</head>
<body onload="run_next_test()">
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=753238">Test of Identity Provider (IDP) Provisioning using the DOM APIs</a>
<p id="display"></p>
<div id="content" style="display: none">
</div>
<pre id="test">
<script type="application/javascript;version=1.8">
/** Test of Identity Provider (IDP) Provisioning using the DOM APIs **/
/** Most tests are ported from test_provisioning.js */
"use strict";
SimpleTest.waitForExplicitFinish();
const DOMIdentity = Cu.import("resource://gre/modules/DOMIdentity.jsm")
.DOMIdentity;
let outerWinId = SpecialPowers.getDOMWindowUtils(window).outerWindowID;
function check_provision_flow_done(provId) {
do_check_null(IdentityProvider._provisionFlows[provId]);
}
/**
* Allow specifying aProvFlow so we can reset after the _provisionFlows is cleaned up.
*/
function run_next_prov_test(aProvFlow) {
// Reset the DOM state then run the next test
let provContext = aProvFlow || IdentityProvider._provisionFlows[outerWinId];
if (provContext && provContext.caller) {
makeObserver("identity-DOM-state-reset", function() {
SimpleTest.executeSoon(run_next_test);
});
DOMIdentity._resetFrameState(provContext.caller);
} else {
SimpleTest.executeSoon(run_next_test);
}
}
function test_begin_provisioning() {
setup_provisioning(
TEST_USER,
function(caller) {
// call .beginProvisioning()
// TODO: should probably throw outside of a prov. sandbox?
identity.beginProvisioning(caller.beginProvisioningCallback);
}, function() {},
{
beginProvisioningCallback: function(email, duration_s) {
is(email, TEST_USER);
ok(duration_s > 0);
ok(duration_s <= (24 * 3600));
run_next_prov_test();
}
});
}
function test_raise_provisioning_failure() {
let _callerId = null;
setup_provisioning(
TEST_USER,
function(caller) {
// call .beginProvisioning()
_callerId = caller.id;
identity.beginProvisioning(caller.beginProvisioningCallback);
}, function(err) {
// this should be invoked with a populated error
isnot(err, null);
ok(err.indexOf("can't authenticate this email") > -1);
run_next_prov_test();
},
{
beginProvisioningCallback: function(email, duration_s) {
// raise the failure as if we can't provision this email
identity.raiseProvisioningFailure("can't authenticate this email");
}
});
}
function test_genkeypair_before_begin_provisioning() {
setup_provisioning(
TEST_USER,
function(caller) {
try {
// call genKeyPair without beginProvisioning
identity.genKeyPair(caller.genKeyPairCallback);
} catch (ex) {
ok(ex, "Caught exception for calling genKeyPair without beginProvisioning: " + ex);
run_next_prov_test();
}
},
// expect this to be called with an error
function(err) {
ok(false, "Shoudn't reach here as DOM code should have caught the problem");
run_next_prov_test();
},
{
// this should not be called at all!
genKeyPairCallback: function(pk) {
// a test that will surely fail because we shouldn't be here.
ok(false);
run_next_prov_test();
}
}
);
}
function test_genkeypair() {
let _callerId = null;
function gkpCallback(kp) {
isnot(kp, null);
// yay!
run_next_prov_test();
}
setup_provisioning(
TEST_USER,
function(caller) {
_callerId = caller.id;
identity.beginProvisioning(caller.beginProvisioningCallback);
},
function(err) {
// should not be called!
ok(false);
run_next_prov_test();
},
{
beginProvisioningCallback: function(email, time_s) {
identity.genKeyPair(gkpCallback);
},
genKeyPairCallback: gkpCallback,
}
);
}
// we've already ensured that genkeypair can't be called
// before beginProvisioning, so this test should be enough
// to ensure full sequential call of the 3 APIs.
function test_register_certificate_before_genkeypair() {
let _callerID = null;
setup_provisioning(
TEST_USER,
function(caller) {
// do the right thing for beginProvisioning
_callerID = caller.id;
identity.beginProvisioning(caller.beginProvisioningCallback);
},
// expect this to be called with an error
function(err) {
ok(false, "Shoudn't reach here as DOM code should have caught the problem");
run_next_prov_test();
},
{
beginProvisioningCallback: function(email, duration_s) {
try {
// now we try to register cert but no keygen has been done
identity.registerCertificate("fake-cert");
} catch (ex) {
ok(ex, "Caught exception for calling genKeyPair without beginProvisioning: " + ex);
run_next_prov_test();
}
}
}
);
}
function test_register_certificate() {
let _callerId = null;
let provFlow = null;
function gkpCallback(pk) {
// Hold on to the provFlow so we have access to .caller to cleanup later
provFlow = IdentityProvider._provisionFlows[outerWinId];
identity.registerCertificate("fake-cert-42");
}
setup_provisioning(
TEST_USER,
function(caller) {
_callerId = caller.id;
identity.beginProvisioning(caller.beginProvisioningCallback);
},
function(err) {
// we should be cool!
do_check_null(err);
// check that the cert is there
let identity = get_idstore().fetchIdentity(TEST_USER);
isnot(identity,null);
is(identity.cert, "fake-cert-42");
SimpleTest.executeSoon(function check_done() {
// cleanup will happen after the callback is called
check_provision_flow_done(_callerId);
run_next_prov_test(provFlow);
});
},
{
beginProvisioningCallback: function(email, duration_s) {
identity.genKeyPair(gkpCallback);
},
genKeyPairCallback: gkpCallback,
}
);
}
function test_get_assertion_after_provision() {
let _callerId = null;
let provFlow = null;
function gkpCallback(pk) {
// Hold on to the provFlow so we have access to .caller to cleanup later
provFlow = IdentityProvider._provisionFlows[outerWinId];
identity.registerCertificate("fake-cert-42");
}
setup_provisioning(
TEST_USER,
function(caller) {
_callerId = caller.id;
identity.beginProvisioning(caller.beginProvisioningCallback);
},
function(err) {
// we should be cool!
do_check_null(err);
// check that the cert is there
let identity = get_idstore().fetchIdentity(TEST_USER);
isnot(identity,null);
is(identity.cert, "fake-cert-42");
SimpleTest.executeSoon(function check_done() {
// cleanup will happen after the callback is called
check_provision_flow_done(_callerId);
run_next_prov_test(provFlow);
});
},
{
beginProvisioningCallback: function(email, duration_s) {
identity.genKeyPair(gkpCallback);
},
genKeyPairCallback: gkpCallback,
}
);
}
TESTS.push(test_genkeypair_before_begin_provisioning);
TESTS.push(test_begin_provisioning);
TESTS.push(test_raise_provisioning_failure);
TESTS.push(test_register_certificate_before_genkeypair);
TESTS.push(test_genkeypair);
TESTS.push(test_register_certificate);
TESTS.push(test_get_assertion_after_provision);
</script>
</pre>
</body>
</html>

View File

@ -1,229 +0,0 @@
<!DOCTYPE html>
<html>
<!--
Test of Relying Party (RP) using the DOM APIs
-->
<head>
<meta charset="utf-8">
<title>Test of Relying Party (RP) using the DOM APIs</title>
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
<script type="application/javascript;version=1.8" src="head_identity.js"></script>
</head>
<body onload="run_next_test()">
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=753238">Test of Relying Party (RP) using the DOM APIs</a>
<p id="display"></p>
<div id="content">
<button id='request'>request</button>
</div>
<pre id="test">
<script type="application/javascript;version=1.8">
/** Test of Relying Party (RP) using the DOM APIs **/
/** Most tests are ported from test_relying_party.js */
"use strict";
SimpleTest.waitForExplicitFinish();
const DOMIdentity = Cu.import("resource://gre/modules/DOMIdentity.jsm")
.DOMIdentity;
let outerWinId = SpecialPowers.getDOMWindowUtils(window).outerWindowID;
// Reset the DOM state then run the next test
function run_next_rp_test() {
let rpContext = RelyingParty._rpFlows[outerWinId];
if (rpContext) {
makeObserver("identity-DOM-state-reset", function() {
SimpleTest.executeSoon(run_next_test);
});
DOMIdentity._resetFrameState(rpContext);
} else {
SimpleTest.executeSoon(run_next_test);
}
}
function test_watch_loggedin_ready() {
resetState();
let id = TEST_USER;
setup_test_identity(id, TEST_CERT, function() {
let store = get_idstore();
// set it up so we're supposed to be logged in to TEST_URL
store.setLoginState(TEST_URL, true, id);
identity.watch(mock_watch(id, function(action, params) {
is(action, 'ready');
is(params, undefined);
run_next_rp_test();
}));
});
}
function test_watch_loggedin_login() {
resetState();
let id = TEST_USER;
setup_test_identity(id, TEST_CERT, function() {
let store = get_idstore();
// set it up so we're supposed to be logged in to TEST_URL
store.setLoginState(TEST_URL, true, id);
// check for first a login() call, then a ready() call
identity.watch(mock_watch(null, call_sequentially(
function(action, params) {
is(action, 'login');
isnot(params, null);
},
function(action, params) {
is(action, 'ready');
do_check_null(params);
run_next_rp_test();
}
)));
});
}
function test_watch_loggedin_logout() {
resetState();
let id = TEST_USER;
let other_id = "otherid@foo.com";
setup_test_identity(other_id, TEST_CERT, function() {
setup_test_identity(id, TEST_CERT, function() {
let store = get_idstore();
// set it up so we're supposed to be logged in to TEST_URL
// with id, not other_id
store.setLoginState(TEST_URL, true, id);
// this should cause a login with an assertion for id,
// not for other_id
identity.watch(mock_watch(other_id, call_sequentially(
function(action, params) {
is(action, 'login');
isnot(params, null);
},
function(action, params) {
is(action, 'ready');
do_check_null(params);
run_next_rp_test();
}
)));
});
});
}
function test_watch_notloggedin_ready() {
resetState();
identity.watch(mock_watch(null, function(action, params) {
is(action, 'ready');
is(params, undefined);
run_next_rp_test();
}));
}
function test_watch_notloggedin_logout() {
resetState();
identity.watch(mock_watch(TEST_USER, call_sequentially(
function(action, params) {
is(action, 'logout');
is(params, undefined);
let store = get_idstore();
do_check_null(store.getLoginState(TEST_URL));
},
function(action, params) {
is(action, 'ready');
is(params, undefined);
run_next_rp_test();
}
)));
}
function test_request() {
// set up a watch, to be consistent
let mockedDoc = mock_watch(null, function(action, params) {
// We're not checking anything here at the moment.
});
identity.watch(mockedDoc);
// be ready for the UX identity-request notification
makeObserver("identity-request", function (aSubject, aTopic, aData) {
isnot(aSubject, null);
run_next_rp_test();
});
var button = document.getElementById('request');
button.addEventListener('click', function requestHandler() {
button.removeEventListener('click', requestHandler);
identity.request();
});
synthesizeMouseAtCenter(button, {});
}
function test_logout() {
resetState();
let id = TEST_USER;
setup_test_identity(id, TEST_CERT, function() {
let store = get_idstore();
// set it up so we're supposed to be logged in to TEST_URL
store.setLoginState(TEST_URL, true, id);
let doLogout;
let mockedDoc = mock_watch(id, call_sequentially(
function(action, params) {
is(action, 'ready');
is(params, undefined);
SimpleTest.executeSoon(doLogout);
},
function(action, params) {
is(action, 'logout');
is(params, undefined);
},
function(action, params) {
is(action, 'ready');
is(params, undefined);
run_next_rp_test();
}));
doLogout = function() {
makeObserver("identity-login-state-changed", function (aSubject, aTopic, aData) {
isnot(aSubject.wrappedJSObject.rpId, null, "Check rpId is not null");
is(aData, null, "Check identity changed to nobody");
ok(!store.getLoginState(TEST_URL).isLoggedIn, "Check isLoggedIn is false");
is(store.getLoginState(TEST_URL).email, TEST_USER, "Check notification email");
});
identity.logout();
};
identity.watch(mockedDoc);
});
}
TESTS = TESTS.concat([test_watch_loggedin_ready, test_watch_loggedin_login, test_watch_loggedin_logout]);
TESTS = TESTS.concat([test_watch_notloggedin_ready, test_watch_notloggedin_logout]);
TESTS.push(test_request);
TESTS.push(test_logout);
</script>
</pre>
</body>
</html>

View File

@ -46,8 +46,8 @@ function test_select_identity() {
let mockedDoc = mock_doc(null, TEST_URL, call_sequentially( let mockedDoc = mock_doc(null, TEST_URL, call_sequentially(
function(action, params) { function(action, params) {
// ready emitted from first watch() call // ready emitted from first watch() call
do_check_eq(action, 'ready'); do_check_eq(action, 'ready');
do_check_null(params); do_check_null(params);
}, },
// first the login call // first the login call
function(action, params) { function(action, params) {

View File

@ -0,0 +1,95 @@
"use strict";
XPCOMUtils.defineLazyModuleGetter(this, "MinimalIDService",
"resource://gre/modules/identity/MinimalIdentity.jsm",
"IdentityService");
Cu.import("resource://gre/modules/identity/LogUtils.jsm");
function log(...aMessageArgs) {
Logger.log.apply(Logger, ["test_minimalidentity"].concat(aMessageArgs));
}
function test_overall() {
do_check_neq(MinimalIDService, null);
run_next_test();
}
function test_mock_doc() {
do_test_pending();
let mockedDoc = mock_doc(null, TEST_URL, function(action, params) {
do_check_eq(action, 'coffee');
do_test_finished();
run_next_test();
});
mockedDoc.doCoffee();
}
/*
* Test that the "identity-controller-watch" signal is emitted correctly
*/
function test_watch() {
do_test_pending();
let mockedDoc = mock_doc(null, TEST_URL);
makeObserver("identity-controller-watch", function (aSubject, aTopic, aData) {
do_check_eq(aSubject.wrappedJSObject.rpId, mockedDoc.id);
do_check_eq(aSubject.wrappedJSObject.origin, TEST_URL);
do_test_finished();
run_next_test();
});
MinimalIDService.RP.watch(mockedDoc);
}
/*
* Test that the "identity-controller-request" signal is emitted correctly
*/
function test_request() {
do_test_pending();
let mockedDoc = mock_doc(null, TEST_URL);
makeObserver("identity-controller-request", function (aSubject, aTopic, aData) {
do_check_eq(aSubject.wrappedJSObject.rpId, mockedDoc.id);
do_check_eq(aSubject.wrappedJSObject.origin, TEST_URL);
do_test_finished();
run_next_test();
});
MinimalIDService.RP.watch(mockedDoc);
MinimalIDService.RP.request(mockedDoc.id, {});
}
/*
* Test that the "identity-controller-logout" signal is emitted correctly
*/
function test_logout() {
do_test_pending();
let mockedDoc = mock_doc(null, TEST_URL);
makeObserver("identity-controller-logout", function (aSubject, aTopic, aData) {
do_check_eq(aSubject.wrappedJSObject.rpId, mockedDoc.id);
do_test_finished();
run_next_test();
});
MinimalIDService.RP.watch(mockedDoc);
MinimalIDService.RP.logout(mockedDoc.id, {});
}
let TESTS = [
test_overall,
test_mock_doc,
test_watch,
test_request,
test_logout
];
TESTS.forEach(add_test);
function run_test() {
run_next_test();
}

View File

@ -4,8 +4,9 @@ tail = tail_identity.js
# Test load modules first so syntax failures are caught early. # Test load modules first so syntax failures are caught early.
[test_load_modules.js] [test_load_modules.js]
[test_log_utils.js] [test_minimalidentity.js]
[test_log_utils.js]
[test_authentication.js] [test_authentication.js]
[test_crypto_service.js] [test_crypto_service.js]
[test_identity.js] [test_identity.js]