michael@0: /* -*- Mode: js2; js2-basic-offset: 2; indent-tabs-mode: nil; -*- */ michael@0: /* vim: set ft=javascript ts=2 et sw=2 tw=80: */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this file, michael@0: * You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: /* michael@0: * This alternate implementation of IdentityService provides just the michael@0: * channels for navigator.id, leaving the certificate storage to a michael@0: * server-provided app. michael@0: * michael@0: * On b2g, the messages identity-controller-watch, -request, and michael@0: * -logout, are observed by the component SignInToWebsite.jsm. michael@0: */ michael@0: michael@0: "use strict"; michael@0: michael@0: this.EXPORTED_SYMBOLS = ["IdentityService"]; michael@0: michael@0: const Cu = Components.utils; michael@0: const Ci = Components.interfaces; michael@0: const Cc = Components.classes; michael@0: const Cr = Components.results; michael@0: michael@0: Cu.import("resource://gre/modules/XPCOMUtils.jsm"); michael@0: Cu.import("resource://gre/modules/Services.jsm"); michael@0: Cu.import("resource://gre/modules/identity/LogUtils.jsm"); michael@0: michael@0: XPCOMUtils.defineLazyModuleGetter(this, "objectCopy", michael@0: "resource://gre/modules/identity/IdentityUtils.jsm"); michael@0: michael@0: XPCOMUtils.defineLazyModuleGetter(this, "makeMessageObject", michael@0: "resource://gre/modules/identity/IdentityUtils.jsm"); michael@0: michael@0: function log(...aMessageArgs) { michael@0: Logger.log.apply(Logger, ["minimal core"].concat(aMessageArgs)); michael@0: } michael@0: function reportError(...aMessageArgs) { michael@0: Logger.reportError.apply(Logger, ["core"].concat(aMessageArgs)); michael@0: } michael@0: michael@0: function IDService() { michael@0: Services.obs.addObserver(this, "quit-application-granted", false); michael@0: michael@0: // simplify, it's one object michael@0: this.RP = this; michael@0: this.IDP = this; michael@0: michael@0: // keep track of flows michael@0: this._rpFlows = {}; michael@0: this._authFlows = {}; michael@0: this._provFlows = {}; michael@0: } michael@0: michael@0: IDService.prototype = { michael@0: QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports, Ci.nsIObserver]), michael@0: michael@0: observe: function observe(aSubject, aTopic, aData) { michael@0: switch (aTopic) { michael@0: case "quit-application-granted": michael@0: this.shutdown(); michael@0: break; michael@0: } michael@0: }, michael@0: michael@0: shutdown: function() { michael@0: Services.obs.removeObserver(this, "quit-application-granted"); michael@0: }, michael@0: michael@0: /** michael@0: * Parse an email into username and domain if it is valid, else return null michael@0: */ michael@0: parseEmail: function parseEmail(email) { michael@0: var match = email.match(/^([^@]+)@([^@^/]+.[a-z]+)$/); michael@0: if (match) { michael@0: return { michael@0: username: match[1], michael@0: domain: match[2] michael@0: }; michael@0: } michael@0: return null; michael@0: }, michael@0: michael@0: /** michael@0: * Register a listener for a given windowID as a result of a call to michael@0: * navigator.id.watch(). michael@0: * michael@0: * @param aCaller michael@0: * (Object) an object that represents the caller document, and michael@0: * is expected to have properties: michael@0: * - id (unique, e.g. uuid) michael@0: * - loggedInUser (string or null) michael@0: * - origin (string) michael@0: * michael@0: * and a bunch of callbacks michael@0: * - doReady() michael@0: * - doLogin() michael@0: * - doLogout() michael@0: * - doError() michael@0: * - doCancel() michael@0: * michael@0: */ michael@0: watch: function watch(aRpCaller) { michael@0: // store the caller structure and notify the UI observers michael@0: this._rpFlows[aRpCaller.id] = aRpCaller; michael@0: michael@0: log("flows:", Object.keys(this._rpFlows).join(', ')); michael@0: michael@0: let options = makeMessageObject(aRpCaller); michael@0: log("sending identity-controller-watch:", options); michael@0: Services.obs.notifyObservers({wrappedJSObject: options},"identity-controller-watch", null); michael@0: }, michael@0: michael@0: /* michael@0: * The RP has gone away; remove handles to the hidden iframe. michael@0: * It's probable that the frame will already have been cleaned up. michael@0: */ michael@0: unwatch: function unwatch(aRpId, aTargetMM) { michael@0: let rp = this._rpFlows[aRpId]; michael@0: if (!rp) { michael@0: return; michael@0: } michael@0: michael@0: let options = makeMessageObject({ michael@0: id: aRpId, michael@0: origin: rp.origin, michael@0: messageManager: aTargetMM michael@0: }); michael@0: log("sending identity-controller-unwatch for id", options.id, options.origin); michael@0: Services.obs.notifyObservers({wrappedJSObject: options}, "identity-controller-unwatch", null); michael@0: michael@0: // Stop sending messages to this window michael@0: delete this._rpFlows[aRpId]; michael@0: }, michael@0: michael@0: /** michael@0: * Initiate a login with user interaction as a result of a call to michael@0: * navigator.id.request(). michael@0: * michael@0: * @param aRPId michael@0: * (integer) the id of the doc object obtained in .watch() michael@0: * michael@0: * @param aOptions michael@0: * (Object) options including privacyPolicy, termsOfService michael@0: */ michael@0: request: function request(aRPId, aOptions) { michael@0: let rp = this._rpFlows[aRPId]; michael@0: if (!rp) { michael@0: reportError("request() called before watch()"); michael@0: return; michael@0: } michael@0: michael@0: // Notify UX to display identity picker. michael@0: // Pass the doc id to UX so it can pass it back to us later. michael@0: let options = makeMessageObject(rp); michael@0: objectCopy(aOptions, options); michael@0: Services.obs.notifyObservers({wrappedJSObject: options}, "identity-controller-request", null); michael@0: }, michael@0: michael@0: /** michael@0: * Invoked when a user wishes to logout of a site (for instance, when clicking michael@0: * on an in-content logout button). michael@0: * michael@0: * @param aRpCallerId michael@0: * (integer) the id of the doc object obtained in .watch() michael@0: * michael@0: */ michael@0: logout: function logout(aRpCallerId) { michael@0: let rp = this._rpFlows[aRpCallerId]; michael@0: if (!rp) { michael@0: reportError("logout() called before watch()"); michael@0: return; michael@0: } michael@0: michael@0: let options = makeMessageObject(rp); michael@0: Services.obs.notifyObservers({wrappedJSObject: options}, "identity-controller-logout", null); michael@0: }, michael@0: michael@0: childProcessShutdown: function childProcessShutdown(messageManager) { michael@0: Object.keys(this._rpFlows).forEach(function(key) { michael@0: if (this._rpFlows[key]._mm === messageManager) { michael@0: log("child process shutdown for rp", key, "- deleting flow"); michael@0: delete this._rpFlows[key]; michael@0: } michael@0: }, this); michael@0: }, michael@0: michael@0: /* michael@0: * once the UI-and-display-logic components have received michael@0: * notifications, they call back with direct invocation of the michael@0: * following functions (doLogin, doLogout, or doReady) michael@0: */ michael@0: michael@0: doLogin: function doLogin(aRpCallerId, aAssertion, aInternalParams) { michael@0: let rp = this._rpFlows[aRpCallerId]; michael@0: if (!rp) { michael@0: log("WARNING: doLogin found no rp to go with callerId " + aRpCallerId); michael@0: return; michael@0: } michael@0: michael@0: rp.doLogin(aAssertion, aInternalParams); michael@0: }, michael@0: michael@0: doLogout: function doLogout(aRpCallerId) { michael@0: let rp = this._rpFlows[aRpCallerId]; michael@0: if (!rp) { michael@0: log("WARNING: doLogout found no rp to go with callerId " + aRpCallerId); michael@0: return; michael@0: } michael@0: michael@0: // Logout from every site with the same origin michael@0: let origin = rp.origin; michael@0: Object.keys(this._rpFlows).forEach(function(key) { michael@0: let rp = this._rpFlows[key]; michael@0: if (rp.origin === origin) { michael@0: rp.doLogout(); michael@0: } michael@0: }.bind(this)); michael@0: }, michael@0: michael@0: doReady: function doReady(aRpCallerId) { michael@0: let rp = this._rpFlows[aRpCallerId]; michael@0: if (!rp) { michael@0: log("WARNING: doReady found no rp to go with callerId " + aRpCallerId); michael@0: return; michael@0: } michael@0: michael@0: rp.doReady(); michael@0: }, michael@0: michael@0: doCancel: function doCancel(aRpCallerId) { michael@0: let rp = this._rpFlows[aRpCallerId]; michael@0: if (!rp) { michael@0: log("WARNING: doCancel found no rp to go with callerId " + aRpCallerId); michael@0: return; michael@0: } michael@0: michael@0: rp.doCancel(); michael@0: } michael@0: }; michael@0: michael@0: this.IdentityService = new IDService();