|
1 /* This Source Code Form is subject to the terms of the Mozilla Public |
|
2 * License, v. 2.0. If a copy of the MPL was not distributed with this file, |
|
3 * You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
4 |
|
5 "use strict"; |
|
6 |
|
7 this.EXPORTED_SYMBOLS = ["FirefoxAccounts"]; |
|
8 |
|
9 const {classes: Cc, interfaces: Ci, utils: Cu} = Components; |
|
10 |
|
11 Cu.import("resource://gre/modules/Log.jsm"); |
|
12 Cu.import("resource://gre/modules/XPCOMUtils.jsm"); |
|
13 Cu.import("resource://gre/modules/Services.jsm"); |
|
14 Cu.import("resource://gre/modules/identity/LogUtils.jsm"); |
|
15 |
|
16 XPCOMUtils.defineLazyModuleGetter(this, "objectCopy", |
|
17 "resource://gre/modules/identity/IdentityUtils.jsm"); |
|
18 |
|
19 XPCOMUtils.defineLazyModuleGetter(this, "makeMessageObject", |
|
20 "resource://gre/modules/identity/IdentityUtils.jsm"); |
|
21 |
|
22 // loglevel preference should be one of: "FATAL", "ERROR", "WARN", "INFO", |
|
23 // "CONFIG", "DEBUG", "TRACE" or "ALL". We will be logging error messages by |
|
24 // default. |
|
25 const PREF_LOG_LEVEL = "identity.fxaccounts.loglevel"; |
|
26 try { |
|
27 this.LOG_LEVEL = |
|
28 Services.prefs.getPrefType(PREF_LOG_LEVEL) == Ci.nsIPrefBranch.PREF_STRING |
|
29 && Services.prefs.getCharPref(PREF_LOG_LEVEL); |
|
30 } catch (e) { |
|
31 this.LOG_LEVEL = Log.Level.Error; |
|
32 } |
|
33 |
|
34 let log = Log.repository.getLogger("Identity.FxAccounts"); |
|
35 log.level = LOG_LEVEL; |
|
36 log.addAppender(new Log.ConsoleAppender(new Log.BasicFormatter())); |
|
37 |
|
38 #ifdef MOZ_B2G |
|
39 XPCOMUtils.defineLazyModuleGetter(this, "FxAccountsManager", |
|
40 "resource://gre/modules/FxAccountsManager.jsm", |
|
41 "FxAccountsManager"); |
|
42 #else |
|
43 log.warn("The FxAccountsManager is only functional in B2G at this time."); |
|
44 var FxAccountsManager = null; |
|
45 #endif |
|
46 |
|
47 function FxAccountsService() { |
|
48 Services.obs.addObserver(this, "quit-application-granted", false); |
|
49 |
|
50 // Maintain interface parity with Identity.jsm and MinimalIdentity.jsm |
|
51 this.RP = this; |
|
52 |
|
53 this._rpFlows = new Map(); |
|
54 |
|
55 // Enable us to mock FxAccountsManager service in testing |
|
56 this.fxAccountsManager = FxAccountsManager; |
|
57 } |
|
58 |
|
59 FxAccountsService.prototype = { |
|
60 QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports, Ci.nsIObserver]), |
|
61 |
|
62 observe: function observe(aSubject, aTopic, aData) { |
|
63 switch (aTopic) { |
|
64 case "quit-application-granted": |
|
65 Services.obs.removeObserver(this, "quit-application-granted"); |
|
66 break; |
|
67 } |
|
68 }, |
|
69 |
|
70 /** |
|
71 * Register a listener for a given windowID as a result of a call to |
|
72 * navigator.id.watch(). |
|
73 * |
|
74 * @param aCaller |
|
75 * (Object) an object that represents the caller document, and |
|
76 * is expected to have properties: |
|
77 * - id (unique, e.g. uuid) |
|
78 * - origin (string) |
|
79 * |
|
80 * and a bunch of callbacks |
|
81 * - doReady() |
|
82 * - doLogin() |
|
83 * - doLogout() |
|
84 * - doError() |
|
85 * - doCancel() |
|
86 * |
|
87 */ |
|
88 watch: function watch(aRpCaller) { |
|
89 this._rpFlows.set(aRpCaller.id, aRpCaller); |
|
90 log.debug("watch: " + aRpCaller.id); |
|
91 log.debug("Current rp flows: " + this._rpFlows.size); |
|
92 |
|
93 // Log the user in, if possible, and then call ready(). |
|
94 let runnable = { |
|
95 run: () => { |
|
96 this.fxAccountsManager.getAssertion(aRpCaller.audience, {silent:true}).then( |
|
97 data => { |
|
98 if (data) { |
|
99 this.doLogin(aRpCaller.id, data); |
|
100 } else { |
|
101 this.doLogout(aRpCaller.id); |
|
102 } |
|
103 this.doReady(aRpCaller.id); |
|
104 }, |
|
105 error => { |
|
106 log.error("get silent assertion failed: " + JSON.stringify(error)); |
|
107 this.doError(aRpCaller.id, error); |
|
108 } |
|
109 ); |
|
110 } |
|
111 }; |
|
112 Services.tm.currentThread.dispatch(runnable, |
|
113 Ci.nsIThread.DISPATCH_NORMAL); |
|
114 }, |
|
115 |
|
116 /** |
|
117 * Delete the flow when the screen is unloaded |
|
118 */ |
|
119 unwatch: function(aRpCallerId, aTargetMM) { |
|
120 log.debug("unwatching: " + aRpCallerId); |
|
121 this._rpFlows.delete(aRpCallerId); |
|
122 }, |
|
123 |
|
124 /** |
|
125 * Initiate a login with user interaction as a result of a call to |
|
126 * navigator.id.request(). |
|
127 * |
|
128 * @param aRPId |
|
129 * (integer) the id of the doc object obtained in .watch() |
|
130 * |
|
131 * @param aOptions |
|
132 * (Object) options including privacyPolicy, termsOfService |
|
133 */ |
|
134 request: function request(aRPId, aOptions) { |
|
135 aOptions = aOptions || {}; |
|
136 let rp = this._rpFlows.get(aRPId); |
|
137 if (!rp) { |
|
138 log.error("request() called before watch()"); |
|
139 return; |
|
140 } |
|
141 |
|
142 let options = makeMessageObject(rp); |
|
143 objectCopy(aOptions, options); |
|
144 |
|
145 log.debug("get assertion for " + rp.audience); |
|
146 |
|
147 this.fxAccountsManager.getAssertion(rp.audience, options).then( |
|
148 data => { |
|
149 log.debug("got assertion for " + rp.audience + ": " + data); |
|
150 this.doLogin(aRPId, data); |
|
151 }, |
|
152 error => { |
|
153 log.error("get assertion failed: " + JSON.stringify(error)); |
|
154 this.doError(aRPId, error); |
|
155 } |
|
156 ); |
|
157 }, |
|
158 |
|
159 /** |
|
160 * Invoked when a user wishes to logout of a site (for instance, when clicking |
|
161 * on an in-content logout button). |
|
162 * |
|
163 * @param aRpCallerId |
|
164 * (integer) the id of the doc object obtained in .watch() |
|
165 * |
|
166 */ |
|
167 logout: function logout(aRpCallerId) { |
|
168 // XXX Bug 945363 - Resolve the SSO story for FXA and implement |
|
169 // logout accordingly. |
|
170 // |
|
171 // For now, it makes no sense to logout from a specific RP in |
|
172 // Firefox Accounts, so just directly call the logout callback. |
|
173 if (!this._rpFlows.has(aRpCallerId)) { |
|
174 log.error("logout() called before watch()"); |
|
175 return; |
|
176 } |
|
177 |
|
178 // Call logout() on the next tick |
|
179 let runnable = { |
|
180 run: () => { |
|
181 this.fxAccountsManager.signOut().then(() => { |
|
182 this.doLogout(aRpCallerId); |
|
183 }); |
|
184 } |
|
185 }; |
|
186 Services.tm.currentThread.dispatch(runnable, |
|
187 Ci.nsIThread.DISPATCH_NORMAL); |
|
188 }, |
|
189 |
|
190 childProcessShutdown: function childProcessShutdown(messageManager) { |
|
191 for (let [key,] of this._rpFlows) { |
|
192 if (this._rpFlows.get(key)._mm === messageManager) { |
|
193 this._rpFlows.delete(key); |
|
194 } |
|
195 } |
|
196 }, |
|
197 |
|
198 doLogin: function doLogin(aRpCallerId, aAssertion) { |
|
199 let rp = this._rpFlows.get(aRpCallerId); |
|
200 if (!rp) { |
|
201 log.warn("doLogin found no rp to go with callerId " + aRpCallerId + "\n"); |
|
202 return; |
|
203 } |
|
204 |
|
205 rp.doLogin(aAssertion); |
|
206 }, |
|
207 |
|
208 doLogout: function doLogout(aRpCallerId) { |
|
209 let rp = this._rpFlows.get(aRpCallerId); |
|
210 if (!rp) { |
|
211 log.warn("doLogout found no rp to go with callerId " + aRpCallerId + "\n"); |
|
212 return; |
|
213 } |
|
214 |
|
215 rp.doLogout(); |
|
216 }, |
|
217 |
|
218 doReady: function doReady(aRpCallerId) { |
|
219 let rp = this._rpFlows.get(aRpCallerId); |
|
220 if (!rp) { |
|
221 log.warn("doReady found no rp to go with callerId " + aRpCallerId + "\n"); |
|
222 return; |
|
223 } |
|
224 |
|
225 rp.doReady(); |
|
226 }, |
|
227 |
|
228 doCancel: function doCancel(aRpCallerId) { |
|
229 let rp = this._rpFlows.get(aRpCallerId); |
|
230 if (!rp) { |
|
231 log.warn("doCancel found no rp to go with callerId " + aRpCallerId + "\n"); |
|
232 return; |
|
233 } |
|
234 |
|
235 rp.doCancel(); |
|
236 }, |
|
237 |
|
238 doError: function doError(aRpCallerId, aError) { |
|
239 let rp = this._rpFlows.get(aRpCallerId); |
|
240 if (!rp) { |
|
241 log.warn("doCancel found no rp to go with callerId " + aRpCallerId + "\n"); |
|
242 return; |
|
243 } |
|
244 |
|
245 rp.doError(aError); |
|
246 } |
|
247 }; |
|
248 |
|
249 this.FirefoxAccounts = new FxAccountsService(); |
|
250 |