1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/toolkit/identity/MinimalIdentity.jsm Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,242 @@ 1.4 +/* -*- Mode: js2; js2-basic-offset: 2; indent-tabs-mode: nil; -*- */ 1.5 +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ 1.6 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.7 + * License, v. 2.0. If a copy of the MPL was not distributed with this file, 1.8 + * You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.9 + 1.10 +/* 1.11 + * This alternate implementation of IdentityService provides just the 1.12 + * channels for navigator.id, leaving the certificate storage to a 1.13 + * server-provided app. 1.14 + * 1.15 + * On b2g, the messages identity-controller-watch, -request, and 1.16 + * -logout, are observed by the component SignInToWebsite.jsm. 1.17 + */ 1.18 + 1.19 +"use strict"; 1.20 + 1.21 +this.EXPORTED_SYMBOLS = ["IdentityService"]; 1.22 + 1.23 +const Cu = Components.utils; 1.24 +const Ci = Components.interfaces; 1.25 +const Cc = Components.classes; 1.26 +const Cr = Components.results; 1.27 + 1.28 +Cu.import("resource://gre/modules/XPCOMUtils.jsm"); 1.29 +Cu.import("resource://gre/modules/Services.jsm"); 1.30 +Cu.import("resource://gre/modules/identity/LogUtils.jsm"); 1.31 + 1.32 +XPCOMUtils.defineLazyModuleGetter(this, "objectCopy", 1.33 + "resource://gre/modules/identity/IdentityUtils.jsm"); 1.34 + 1.35 +XPCOMUtils.defineLazyModuleGetter(this, "makeMessageObject", 1.36 + "resource://gre/modules/identity/IdentityUtils.jsm"); 1.37 + 1.38 +function log(...aMessageArgs) { 1.39 + Logger.log.apply(Logger, ["minimal core"].concat(aMessageArgs)); 1.40 +} 1.41 +function reportError(...aMessageArgs) { 1.42 + Logger.reportError.apply(Logger, ["core"].concat(aMessageArgs)); 1.43 +} 1.44 + 1.45 +function IDService() { 1.46 + Services.obs.addObserver(this, "quit-application-granted", false); 1.47 + 1.48 + // simplify, it's one object 1.49 + this.RP = this; 1.50 + this.IDP = this; 1.51 + 1.52 + // keep track of flows 1.53 + this._rpFlows = {}; 1.54 + this._authFlows = {}; 1.55 + this._provFlows = {}; 1.56 +} 1.57 + 1.58 +IDService.prototype = { 1.59 + QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports, Ci.nsIObserver]), 1.60 + 1.61 + observe: function observe(aSubject, aTopic, aData) { 1.62 + switch (aTopic) { 1.63 + case "quit-application-granted": 1.64 + this.shutdown(); 1.65 + break; 1.66 + } 1.67 + }, 1.68 + 1.69 + shutdown: function() { 1.70 + Services.obs.removeObserver(this, "quit-application-granted"); 1.71 + }, 1.72 + 1.73 + /** 1.74 + * Parse an email into username and domain if it is valid, else return null 1.75 + */ 1.76 + parseEmail: function parseEmail(email) { 1.77 + var match = email.match(/^([^@]+)@([^@^/]+.[a-z]+)$/); 1.78 + if (match) { 1.79 + return { 1.80 + username: match[1], 1.81 + domain: match[2] 1.82 + }; 1.83 + } 1.84 + return null; 1.85 + }, 1.86 + 1.87 + /** 1.88 + * Register a listener for a given windowID as a result of a call to 1.89 + * navigator.id.watch(). 1.90 + * 1.91 + * @param aCaller 1.92 + * (Object) an object that represents the caller document, and 1.93 + * is expected to have properties: 1.94 + * - id (unique, e.g. uuid) 1.95 + * - loggedInUser (string or null) 1.96 + * - origin (string) 1.97 + * 1.98 + * and a bunch of callbacks 1.99 + * - doReady() 1.100 + * - doLogin() 1.101 + * - doLogout() 1.102 + * - doError() 1.103 + * - doCancel() 1.104 + * 1.105 + */ 1.106 + watch: function watch(aRpCaller) { 1.107 + // store the caller structure and notify the UI observers 1.108 + this._rpFlows[aRpCaller.id] = aRpCaller; 1.109 + 1.110 + log("flows:", Object.keys(this._rpFlows).join(', ')); 1.111 + 1.112 + let options = makeMessageObject(aRpCaller); 1.113 + log("sending identity-controller-watch:", options); 1.114 + Services.obs.notifyObservers({wrappedJSObject: options},"identity-controller-watch", null); 1.115 + }, 1.116 + 1.117 + /* 1.118 + * The RP has gone away; remove handles to the hidden iframe. 1.119 + * It's probable that the frame will already have been cleaned up. 1.120 + */ 1.121 + unwatch: function unwatch(aRpId, aTargetMM) { 1.122 + let rp = this._rpFlows[aRpId]; 1.123 + if (!rp) { 1.124 + return; 1.125 + } 1.126 + 1.127 + let options = makeMessageObject({ 1.128 + id: aRpId, 1.129 + origin: rp.origin, 1.130 + messageManager: aTargetMM 1.131 + }); 1.132 + log("sending identity-controller-unwatch for id", options.id, options.origin); 1.133 + Services.obs.notifyObservers({wrappedJSObject: options}, "identity-controller-unwatch", null); 1.134 + 1.135 + // Stop sending messages to this window 1.136 + delete this._rpFlows[aRpId]; 1.137 + }, 1.138 + 1.139 + /** 1.140 + * Initiate a login with user interaction as a result of a call to 1.141 + * navigator.id.request(). 1.142 + * 1.143 + * @param aRPId 1.144 + * (integer) the id of the doc object obtained in .watch() 1.145 + * 1.146 + * @param aOptions 1.147 + * (Object) options including privacyPolicy, termsOfService 1.148 + */ 1.149 + request: function request(aRPId, aOptions) { 1.150 + let rp = this._rpFlows[aRPId]; 1.151 + if (!rp) { 1.152 + reportError("request() called before watch()"); 1.153 + return; 1.154 + } 1.155 + 1.156 + // Notify UX to display identity picker. 1.157 + // Pass the doc id to UX so it can pass it back to us later. 1.158 + let options = makeMessageObject(rp); 1.159 + objectCopy(aOptions, options); 1.160 + Services.obs.notifyObservers({wrappedJSObject: options}, "identity-controller-request", null); 1.161 + }, 1.162 + 1.163 + /** 1.164 + * Invoked when a user wishes to logout of a site (for instance, when clicking 1.165 + * on an in-content logout button). 1.166 + * 1.167 + * @param aRpCallerId 1.168 + * (integer) the id of the doc object obtained in .watch() 1.169 + * 1.170 + */ 1.171 + logout: function logout(aRpCallerId) { 1.172 + let rp = this._rpFlows[aRpCallerId]; 1.173 + if (!rp) { 1.174 + reportError("logout() called before watch()"); 1.175 + return; 1.176 + } 1.177 + 1.178 + let options = makeMessageObject(rp); 1.179 + Services.obs.notifyObservers({wrappedJSObject: options}, "identity-controller-logout", null); 1.180 + }, 1.181 + 1.182 + childProcessShutdown: function childProcessShutdown(messageManager) { 1.183 + Object.keys(this._rpFlows).forEach(function(key) { 1.184 + if (this._rpFlows[key]._mm === messageManager) { 1.185 + log("child process shutdown for rp", key, "- deleting flow"); 1.186 + delete this._rpFlows[key]; 1.187 + } 1.188 + }, this); 1.189 + }, 1.190 + 1.191 + /* 1.192 + * once the UI-and-display-logic components have received 1.193 + * notifications, they call back with direct invocation of the 1.194 + * following functions (doLogin, doLogout, or doReady) 1.195 + */ 1.196 + 1.197 + doLogin: function doLogin(aRpCallerId, aAssertion, aInternalParams) { 1.198 + let rp = this._rpFlows[aRpCallerId]; 1.199 + if (!rp) { 1.200 + log("WARNING: doLogin found no rp to go with callerId " + aRpCallerId); 1.201 + return; 1.202 + } 1.203 + 1.204 + rp.doLogin(aAssertion, aInternalParams); 1.205 + }, 1.206 + 1.207 + doLogout: function doLogout(aRpCallerId) { 1.208 + let rp = this._rpFlows[aRpCallerId]; 1.209 + if (!rp) { 1.210 + log("WARNING: doLogout found no rp to go with callerId " + aRpCallerId); 1.211 + return; 1.212 + } 1.213 + 1.214 + // Logout from every site with the same origin 1.215 + let origin = rp.origin; 1.216 + Object.keys(this._rpFlows).forEach(function(key) { 1.217 + let rp = this._rpFlows[key]; 1.218 + if (rp.origin === origin) { 1.219 + rp.doLogout(); 1.220 + } 1.221 + }.bind(this)); 1.222 + }, 1.223 + 1.224 + doReady: function doReady(aRpCallerId) { 1.225 + let rp = this._rpFlows[aRpCallerId]; 1.226 + if (!rp) { 1.227 + log("WARNING: doReady found no rp to go with callerId " + aRpCallerId); 1.228 + return; 1.229 + } 1.230 + 1.231 + rp.doReady(); 1.232 + }, 1.233 + 1.234 + doCancel: function doCancel(aRpCallerId) { 1.235 + let rp = this._rpFlows[aRpCallerId]; 1.236 + if (!rp) { 1.237 + log("WARNING: doCancel found no rp to go with callerId " + aRpCallerId); 1.238 + return; 1.239 + } 1.240 + 1.241 + rp.doCancel(); 1.242 + } 1.243 +}; 1.244 + 1.245 +this.IdentityService = new IDService();