1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/toolkit/identity/FirefoxAccounts.jsm Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,250 @@ 1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this file, 1.6 + * You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.7 + 1.8 +"use strict"; 1.9 + 1.10 +this.EXPORTED_SYMBOLS = ["FirefoxAccounts"]; 1.11 + 1.12 +const {classes: Cc, interfaces: Ci, utils: Cu} = Components; 1.13 + 1.14 +Cu.import("resource://gre/modules/Log.jsm"); 1.15 +Cu.import("resource://gre/modules/XPCOMUtils.jsm"); 1.16 +Cu.import("resource://gre/modules/Services.jsm"); 1.17 +Cu.import("resource://gre/modules/identity/LogUtils.jsm"); 1.18 + 1.19 +XPCOMUtils.defineLazyModuleGetter(this, "objectCopy", 1.20 + "resource://gre/modules/identity/IdentityUtils.jsm"); 1.21 + 1.22 +XPCOMUtils.defineLazyModuleGetter(this, "makeMessageObject", 1.23 + "resource://gre/modules/identity/IdentityUtils.jsm"); 1.24 + 1.25 +// loglevel preference should be one of: "FATAL", "ERROR", "WARN", "INFO", 1.26 +// "CONFIG", "DEBUG", "TRACE" or "ALL". We will be logging error messages by 1.27 +// default. 1.28 +const PREF_LOG_LEVEL = "identity.fxaccounts.loglevel"; 1.29 +try { 1.30 + this.LOG_LEVEL = 1.31 + Services.prefs.getPrefType(PREF_LOG_LEVEL) == Ci.nsIPrefBranch.PREF_STRING 1.32 + && Services.prefs.getCharPref(PREF_LOG_LEVEL); 1.33 +} catch (e) { 1.34 + this.LOG_LEVEL = Log.Level.Error; 1.35 +} 1.36 + 1.37 +let log = Log.repository.getLogger("Identity.FxAccounts"); 1.38 +log.level = LOG_LEVEL; 1.39 +log.addAppender(new Log.ConsoleAppender(new Log.BasicFormatter())); 1.40 + 1.41 +#ifdef MOZ_B2G 1.42 +XPCOMUtils.defineLazyModuleGetter(this, "FxAccountsManager", 1.43 + "resource://gre/modules/FxAccountsManager.jsm", 1.44 + "FxAccountsManager"); 1.45 +#else 1.46 +log.warn("The FxAccountsManager is only functional in B2G at this time."); 1.47 +var FxAccountsManager = null; 1.48 +#endif 1.49 + 1.50 +function FxAccountsService() { 1.51 + Services.obs.addObserver(this, "quit-application-granted", false); 1.52 + 1.53 + // Maintain interface parity with Identity.jsm and MinimalIdentity.jsm 1.54 + this.RP = this; 1.55 + 1.56 + this._rpFlows = new Map(); 1.57 + 1.58 + // Enable us to mock FxAccountsManager service in testing 1.59 + this.fxAccountsManager = FxAccountsManager; 1.60 +} 1.61 + 1.62 +FxAccountsService.prototype = { 1.63 + QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports, Ci.nsIObserver]), 1.64 + 1.65 + observe: function observe(aSubject, aTopic, aData) { 1.66 + switch (aTopic) { 1.67 + case "quit-application-granted": 1.68 + Services.obs.removeObserver(this, "quit-application-granted"); 1.69 + break; 1.70 + } 1.71 + }, 1.72 + 1.73 + /** 1.74 + * Register a listener for a given windowID as a result of a call to 1.75 + * navigator.id.watch(). 1.76 + * 1.77 + * @param aCaller 1.78 + * (Object) an object that represents the caller document, and 1.79 + * is expected to have properties: 1.80 + * - id (unique, e.g. uuid) 1.81 + * - origin (string) 1.82 + * 1.83 + * and a bunch of callbacks 1.84 + * - doReady() 1.85 + * - doLogin() 1.86 + * - doLogout() 1.87 + * - doError() 1.88 + * - doCancel() 1.89 + * 1.90 + */ 1.91 + watch: function watch(aRpCaller) { 1.92 + this._rpFlows.set(aRpCaller.id, aRpCaller); 1.93 + log.debug("watch: " + aRpCaller.id); 1.94 + log.debug("Current rp flows: " + this._rpFlows.size); 1.95 + 1.96 + // Log the user in, if possible, and then call ready(). 1.97 + let runnable = { 1.98 + run: () => { 1.99 + this.fxAccountsManager.getAssertion(aRpCaller.audience, {silent:true}).then( 1.100 + data => { 1.101 + if (data) { 1.102 + this.doLogin(aRpCaller.id, data); 1.103 + } else { 1.104 + this.doLogout(aRpCaller.id); 1.105 + } 1.106 + this.doReady(aRpCaller.id); 1.107 + }, 1.108 + error => { 1.109 + log.error("get silent assertion failed: " + JSON.stringify(error)); 1.110 + this.doError(aRpCaller.id, error); 1.111 + } 1.112 + ); 1.113 + } 1.114 + }; 1.115 + Services.tm.currentThread.dispatch(runnable, 1.116 + Ci.nsIThread.DISPATCH_NORMAL); 1.117 + }, 1.118 + 1.119 + /** 1.120 + * Delete the flow when the screen is unloaded 1.121 + */ 1.122 + unwatch: function(aRpCallerId, aTargetMM) { 1.123 + log.debug("unwatching: " + aRpCallerId); 1.124 + this._rpFlows.delete(aRpCallerId); 1.125 + }, 1.126 + 1.127 + /** 1.128 + * Initiate a login with user interaction as a result of a call to 1.129 + * navigator.id.request(). 1.130 + * 1.131 + * @param aRPId 1.132 + * (integer) the id of the doc object obtained in .watch() 1.133 + * 1.134 + * @param aOptions 1.135 + * (Object) options including privacyPolicy, termsOfService 1.136 + */ 1.137 + request: function request(aRPId, aOptions) { 1.138 + aOptions = aOptions || {}; 1.139 + let rp = this._rpFlows.get(aRPId); 1.140 + if (!rp) { 1.141 + log.error("request() called before watch()"); 1.142 + return; 1.143 + } 1.144 + 1.145 + let options = makeMessageObject(rp); 1.146 + objectCopy(aOptions, options); 1.147 + 1.148 + log.debug("get assertion for " + rp.audience); 1.149 + 1.150 + this.fxAccountsManager.getAssertion(rp.audience, options).then( 1.151 + data => { 1.152 + log.debug("got assertion for " + rp.audience + ": " + data); 1.153 + this.doLogin(aRPId, data); 1.154 + }, 1.155 + error => { 1.156 + log.error("get assertion failed: " + JSON.stringify(error)); 1.157 + this.doError(aRPId, error); 1.158 + } 1.159 + ); 1.160 + }, 1.161 + 1.162 + /** 1.163 + * Invoked when a user wishes to logout of a site (for instance, when clicking 1.164 + * on an in-content logout button). 1.165 + * 1.166 + * @param aRpCallerId 1.167 + * (integer) the id of the doc object obtained in .watch() 1.168 + * 1.169 + */ 1.170 + logout: function logout(aRpCallerId) { 1.171 + // XXX Bug 945363 - Resolve the SSO story for FXA and implement 1.172 + // logout accordingly. 1.173 + // 1.174 + // For now, it makes no sense to logout from a specific RP in 1.175 + // Firefox Accounts, so just directly call the logout callback. 1.176 + if (!this._rpFlows.has(aRpCallerId)) { 1.177 + log.error("logout() called before watch()"); 1.178 + return; 1.179 + } 1.180 + 1.181 + // Call logout() on the next tick 1.182 + let runnable = { 1.183 + run: () => { 1.184 + this.fxAccountsManager.signOut().then(() => { 1.185 + this.doLogout(aRpCallerId); 1.186 + }); 1.187 + } 1.188 + }; 1.189 + Services.tm.currentThread.dispatch(runnable, 1.190 + Ci.nsIThread.DISPATCH_NORMAL); 1.191 + }, 1.192 + 1.193 + childProcessShutdown: function childProcessShutdown(messageManager) { 1.194 + for (let [key,] of this._rpFlows) { 1.195 + if (this._rpFlows.get(key)._mm === messageManager) { 1.196 + this._rpFlows.delete(key); 1.197 + } 1.198 + } 1.199 + }, 1.200 + 1.201 + doLogin: function doLogin(aRpCallerId, aAssertion) { 1.202 + let rp = this._rpFlows.get(aRpCallerId); 1.203 + if (!rp) { 1.204 + log.warn("doLogin found no rp to go with callerId " + aRpCallerId + "\n"); 1.205 + return; 1.206 + } 1.207 + 1.208 + rp.doLogin(aAssertion); 1.209 + }, 1.210 + 1.211 + doLogout: function doLogout(aRpCallerId) { 1.212 + let rp = this._rpFlows.get(aRpCallerId); 1.213 + if (!rp) { 1.214 + log.warn("doLogout found no rp to go with callerId " + aRpCallerId + "\n"); 1.215 + return; 1.216 + } 1.217 + 1.218 + rp.doLogout(); 1.219 + }, 1.220 + 1.221 + doReady: function doReady(aRpCallerId) { 1.222 + let rp = this._rpFlows.get(aRpCallerId); 1.223 + if (!rp) { 1.224 + log.warn("doReady found no rp to go with callerId " + aRpCallerId + "\n"); 1.225 + return; 1.226 + } 1.227 + 1.228 + rp.doReady(); 1.229 + }, 1.230 + 1.231 + doCancel: function doCancel(aRpCallerId) { 1.232 + let rp = this._rpFlows.get(aRpCallerId); 1.233 + if (!rp) { 1.234 + log.warn("doCancel found no rp to go with callerId " + aRpCallerId + "\n"); 1.235 + return; 1.236 + } 1.237 + 1.238 + rp.doCancel(); 1.239 + }, 1.240 + 1.241 + doError: function doError(aRpCallerId, aError) { 1.242 + let rp = this._rpFlows.get(aRpCallerId); 1.243 + if (!rp) { 1.244 + log.warn("doCancel found no rp to go with callerId " + aRpCallerId + "\n"); 1.245 + return; 1.246 + } 1.247 + 1.248 + rp.doError(aError); 1.249 + } 1.250 +}; 1.251 + 1.252 +this.FirefoxAccounts = new FxAccountsService(); 1.253 +