diff options
Diffstat (limited to 'b2g/components/SignInToWebsite.jsm')
-rw-r--r-- | b2g/components/SignInToWebsite.jsm | 444 |
1 files changed, 0 insertions, 444 deletions
diff --git a/b2g/components/SignInToWebsite.jsm b/b2g/components/SignInToWebsite.jsm deleted file mode 100644 index fd1349d46..000000000 --- a/b2g/components/SignInToWebsite.jsm +++ /dev/null @@ -1,444 +0,0 @@ -/* 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 - * content 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 content to open a - * window to host the Persona js. If user interaction is required, - * content will open the trusty UI. If user interaction is not required, - * and we only need to get to Persona functions, content 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"; - -this.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, "getRandomId", - "resource://gre/modules/identity/IdentityUtils.jsm"); - -XPCOMUtils.defineLazyModuleGetter(this, "IdentityService", - "resource://gre/modules/identity/MinimalIdentity.jsm"); - -XPCOMUtils.defineLazyModuleGetter(this, "Logger", - "resource://gre/modules/identity/LogUtils.jsm"); - -XPCOMUtils.defineLazyModuleGetter(this, "SystemAppProxy", - "resource://gre/modules/SystemAppProxy.jsm"); - -// The default persona uri; can be overwritten with toolkit.identity.uri pref. -// Do this if you want to repoint to a different service for testing. -// There's no point in setting up an observer to monitor the pref, as b2g prefs -// can only be overwritten when the profie is recreated. So just get the value -// on start-up. -var kPersonaUri = "https://firefoxos.persona.org"; -try { - kPersonaUri = Services.prefs.getCharPref("toolkit.identity.uri"); -} catch(noSuchPref) { - // stick with the default value -} - -// JS shim that contains the callback functions that -// live within the identity UI provisioning frame. -const kIdentityShimFile = "chrome://b2g/content/identity.js"; - -// Type of MozChromeEvents to handle id dialogs. -const kOpenIdentityDialog = "id-dialog-open"; -const kDoneIdentityDialog = "id-dialog-done"; -const kCloseIdentityDialog = "id-dialog-close-iframe"; - -// 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"; - -function log(...aMessageArgs) { - Logger.log.apply(Logger, ["SignInToWebsiteController"].concat(aMessageArgs)); -} - -log("persona uri =", kPersonaUri); - -function sendChromeEvent(details) { - details.uri = kPersonaUri; - SystemAppProxy.dispatchEvent(details); -} - -function Pipe() { - this._watchers = []; -} - -Pipe.prototype = { - init: function pipe_init() { - Services.obs.addObserver(this, "identity-child-process-shutdown", false); - Services.obs.addObserver(this, "identity-controller-unwatch", false); - }, - - uninit: function pipe_uninit() { - Services.obs.removeObserver(this, "identity-child-process-shutdown"); - Services.obs.removeObserver(this, "identity-controller-unwatch"); - }, - - observe: function Pipe_observe(aSubject, aTopic, aData) { - let options = {}; - if (aSubject) { - options = aSubject.wrappedJSObject; - } - switch (aTopic) { - case "identity-child-process-shutdown": - log("pipe removing watchers by message manager"); - this._removeWatchers(null, options.messageManager); - break; - - case "identity-controller-unwatch": - log("unwatching", options.id); - this._removeWatchers(options.id, options.messageManager); - break; - } - }, - - _addWatcher: function Pipe__addWatcher(aId, aMm) { - log("Adding watcher with id", aId); - for (let i = 0; i < this._watchers.length; ++i) { - let watcher = this._watchers[i]; - if (this._watcher.id === aId) { - watcher.count++; - return; - } - } - this._watchers.push({id: aId, count: 1, mm: aMm}); - }, - - _removeWatchers: function Pipe__removeWatcher(aId, aMm) { - let checkId = aId !== null; - let index = -1; - for (let i = 0; i < this._watchers.length; ++i) { - let watcher = this._watchers[i]; - if (watcher.mm === aMm && - (!checkId || (checkId && watcher.id === aId))) { - index = i; - break; - } - } - - if (index !== -1) { - if (checkId) { - if (--(this._watchers[index].count) === 0) { - this._watchers.splice(index, 1); - } - } else { - this._watchers.splice(index, 1); - } - } - - if (this._watchers.length === 0) { - log("No more watchers; clean up persona host iframe"); - let detail = { - type: kCloseIdentityDialog - }; - log('telling content to close the dialog'); - // tell content to close the dialog - sendChromeEvent(detail); - } - }, - - communicate: function(aRpOptions, aContentOptions, aMessageCallback) { - let rpID = aRpOptions.id; - let rpMM = aRpOptions.mm; - if (rpMM) { - this._addWatcher(rpID, rpMM); - } - - log("RP options:", aRpOptions, "\n content options:", aContentOptions); - - // This content variable is injected into the scope of - // kIdentityShimFile, where it is used to access the BrowserID object - // and its internal API. - let mm = null; - let uuid = getRandomId(); - let self = this; - - function removeMessageListeners() { - if (mm) { - mm.removeMessageListener(kIdentityDelegateFinished, identityDelegateFinished); - mm.removeMessageListener(kIdentityControllerDoMethod, aMessageCallback); - } - } - - function identityDelegateFinished() { - removeMessageListeners(); - - let detail = { - type: kDoneIdentityDialog, - showUI: aContentOptions.showUI || false, - id: kDoneIdentityDialog + "-" + uuid, - requestId: aRpOptions.id - }; - log('received delegate finished; telling content to close the dialog'); - sendChromeEvent(detail); - self._removeWatchers(rpID, rpMM); - } - - SystemAppProxy.addEventListener("mozContentEvent", function getAssertion(evt) { - let msg = evt.detail; - if (!msg.id.match(uuid)) { - return; - } - - switch (msg.id) { - case kOpenIdentityDialog + '-' + uuid: - if (msg.type === 'cancel') { - // The user closed the dialog. Clean up and call cancel. - SystemAppProxy.removeEventListener("mozContentEvent", getAssertion); - removeMessageListeners(); - aMessageCallback({json: {method: "cancel"}}); - } else { - // The window has opened. Inject 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; - mm = frameLoader.messageManager; - try { - mm.loadFrameScript(kIdentityShimFile, true, true); - log("Loaded shim", kIdentityShimFile); - } 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, identityDelegateFinished); - - mm.sendAsyncMessage(aContentOptions.message, aRpOptions); - } - break; - - case kDoneIdentityDialog + '-' + uuid: - // Received our assertion. The message manager callbacks will handle - // communicating back to the IDService. All we have to do is remove - // this listener. - SystemAppProxy.removeEventListener("mozContentEvent", getAssertion); - break; - - default: - log("ERROR - Unexpected message: id=" + msg.id + ", type=" + msg.type + ", errorMsg=" + msg.errorMsg); - break; - } - - }); - - // Tell content to open the identity iframe or trusty popup. The parameter - // showUI signals whether user interaction is needed. If it is, content will - // open a dialog; if not, a hidden iframe. In each case, BrowserID is - // available in the context. - let detail = { - type: kOpenIdentityDialog, - showUI: aContentOptions.showUI || false, - id: kOpenIdentityDialog + "-" + uuid, - requestId: aRpOptions.id - }; - - sendChromeEvent(detail); - } - -}; - -/* - * The controller sits between the IdentityService used by DOMIdentity - * and a content process launches an (invisible) iframe or (visible) - * trusty UI. Using an injected js script (identity.js), the - * controller enables the content 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. - */ -this.SignInToWebsiteController = { - - /* - * Initialize the controller. To use a different content communication pipe, - * such as when mocking it in tests, pass aOptions.pipe. - */ - init: function SignInToWebsiteController_init(aOptions) { - aOptions = aOptions || {}; - this.pipe = aOptions.pipe || new 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) { - let message = aOptions.json; - if (typeof message === 'string') { - message = JSON.parse(message); - } - - switch (message.method) { - case "ready": - IdentityService.doReady(aRpId); - break; - - case "login": - if (message._internalParams) { - IdentityService.doLogin(aRpId, message.assertion, message._internalParams); - } else { - IdentityService.doLogin(aRpId, message.assertion); - } - break; - - case "logout": - IdentityService.doLogout(aRpId); - break; - - case "cancel": - IdentityService.doCancel(aRpId); - break; - - default: - log("WARNING: wonky method call:", message.method); - break; - } - }; - }, - - doWatch: function SignInToWebsiteController_doWatch(aRpOptions) { - // dom prevents watch from being called twice - let contentOptions = { - message: kIdentityDelegateWatch, - showUI: false - }; - this.pipe.communicate(aRpOptions, contentOptions, - this._makeDoMethodCallback(aRpOptions.id)); - }, - - /** - * The website is requesting login so the user must choose an identity to use. - */ - doRequest: function SignInToWebsiteController_doRequest(aRpOptions) { - log("doRequest", aRpOptions); - let contentOptions = { - message: kIdentityDelegateRequest, - showUI: true - }; - this.pipe.communicate(aRpOptions, contentOptions, - this._makeDoMethodCallback(aRpOptions.id)); - }, - - /* - * - */ - doLogout: function SignInToWebsiteController_doLogout(aRpOptions) { - log("doLogout", aRpOptions); - let contentOptions = { - message: kIdentityDelegateLogout, - showUI: false - }; - this.pipe.communicate(aRpOptions, contentOptions, - this._makeDoMethodCallback(aRpOptions.id)); - } - -}; |