|
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 const {classes: Cc, interfaces: Ci, utils: Cu} = Components; |
|
8 |
|
9 Cu.import("resource://gre/modules/Services.jsm"); |
|
10 Cu.import("resource://gre/modules/XPCOMUtils.jsm"); |
|
11 |
|
12 const PREF_FXA_ENABLED = "identity.fxaccounts.enabled"; |
|
13 |
|
14 // This is the parent process corresponding to nsDOMIdentity. |
|
15 this.EXPORTED_SYMBOLS = ["DOMIdentity"]; |
|
16 |
|
17 XPCOMUtils.defineLazyModuleGetter(this, "objectCopy", |
|
18 "resource://gre/modules/identity/IdentityUtils.jsm"); |
|
19 |
|
20 XPCOMUtils.defineLazyModuleGetter(this, "IdentityService", |
|
21 #ifdef MOZ_B2G_VERSION |
|
22 "resource://gre/modules/identity/MinimalIdentity.jsm"); |
|
23 #else |
|
24 "resource://gre/modules/identity/Identity.jsm"); |
|
25 #endif |
|
26 |
|
27 XPCOMUtils.defineLazyModuleGetter(this, "FirefoxAccounts", |
|
28 "resource://gre/modules/identity/FirefoxAccounts.jsm"); |
|
29 |
|
30 XPCOMUtils.defineLazyModuleGetter(this, "makeMessageObject", |
|
31 "resource://gre/modules/identity/IdentityUtils.jsm"); |
|
32 |
|
33 XPCOMUtils.defineLazyModuleGetter(this, |
|
34 "Logger", |
|
35 "resource://gre/modules/identity/LogUtils.jsm"); |
|
36 |
|
37 XPCOMUtils.defineLazyServiceGetter(this, "ppmm", |
|
38 "@mozilla.org/parentprocessmessagemanager;1", |
|
39 "nsIMessageListenerManager"); |
|
40 |
|
41 function log(...aMessageArgs) { |
|
42 Logger.log.apply(Logger, ["DOMIdentity"].concat(aMessageArgs)); |
|
43 } |
|
44 |
|
45 function IDDOMMessage(aOptions) { |
|
46 objectCopy(aOptions, this); |
|
47 } |
|
48 |
|
49 function IDPProvisioningContext(aID, aOrigin, aTargetMM) { |
|
50 this._id = aID; |
|
51 this._origin = aOrigin; |
|
52 this._mm = aTargetMM; |
|
53 } |
|
54 |
|
55 IDPProvisioningContext.prototype = { |
|
56 get id() this._id, |
|
57 get origin() this._origin, |
|
58 |
|
59 doBeginProvisioningCallback: function IDPPC_doBeginProvCB(aID, aCertDuration) { |
|
60 let message = new IDDOMMessage({id: this.id}); |
|
61 message.identity = aID; |
|
62 message.certDuration = aCertDuration; |
|
63 this._mm.sendAsyncMessage("Identity:IDP:CallBeginProvisioningCallback", |
|
64 message); |
|
65 }, |
|
66 |
|
67 doGenKeyPairCallback: function IDPPC_doGenKeyPairCallback(aPublicKey) { |
|
68 log("doGenKeyPairCallback"); |
|
69 let message = new IDDOMMessage({id: this.id}); |
|
70 message.publicKey = aPublicKey; |
|
71 this._mm.sendAsyncMessage("Identity:IDP:CallGenKeyPairCallback", message); |
|
72 }, |
|
73 |
|
74 doError: function(msg) { |
|
75 log("Provisioning ERROR: " + msg); |
|
76 } |
|
77 }; |
|
78 |
|
79 function IDPAuthenticationContext(aID, aOrigin, aTargetMM) { |
|
80 this._id = aID; |
|
81 this._origin = aOrigin; |
|
82 this._mm = aTargetMM; |
|
83 } |
|
84 |
|
85 IDPAuthenticationContext.prototype = { |
|
86 get id() this._id, |
|
87 get origin() this._origin, |
|
88 |
|
89 doBeginAuthenticationCallback: function IDPAC_doBeginAuthCB(aIdentity) { |
|
90 let message = new IDDOMMessage({id: this.id}); |
|
91 message.identity = aIdentity; |
|
92 this._mm.sendAsyncMessage("Identity:IDP:CallBeginAuthenticationCallback", |
|
93 message); |
|
94 }, |
|
95 |
|
96 doError: function IDPAC_doError(msg) { |
|
97 log("Authentication ERROR: " + msg); |
|
98 } |
|
99 }; |
|
100 |
|
101 function RPWatchContext(aOptions, aTargetMM) { |
|
102 objectCopy(aOptions, this); |
|
103 |
|
104 // id and origin are required |
|
105 if (! (this.id && this.origin)) { |
|
106 throw new Error("id and origin are required for RP watch context"); |
|
107 } |
|
108 |
|
109 // default for no loggedInUser is undefined, not null |
|
110 this.loggedInUser = aOptions.loggedInUser; |
|
111 |
|
112 // Maybe internal. For hosted b2g identity shim. |
|
113 this._internal = aOptions._internal; |
|
114 |
|
115 this._mm = aTargetMM; |
|
116 } |
|
117 |
|
118 RPWatchContext.prototype = { |
|
119 doLogin: function RPWatchContext_onlogin(aAssertion, aMaybeInternalParams) { |
|
120 log("doLogin: " + this.id); |
|
121 let message = new IDDOMMessage({id: this.id, assertion: aAssertion}); |
|
122 if (aMaybeInternalParams) { |
|
123 message._internalParams = aMaybeInternalParams; |
|
124 } |
|
125 this._mm.sendAsyncMessage("Identity:RP:Watch:OnLogin", message); |
|
126 }, |
|
127 |
|
128 doLogout: function RPWatchContext_onlogout() { |
|
129 log("doLogout: " + this.id); |
|
130 let message = new IDDOMMessage({id: this.id}); |
|
131 this._mm.sendAsyncMessage("Identity:RP:Watch:OnLogout", message); |
|
132 }, |
|
133 |
|
134 doReady: function RPWatchContext_onready() { |
|
135 log("doReady: " + this.id); |
|
136 let message = new IDDOMMessage({id: this.id}); |
|
137 this._mm.sendAsyncMessage("Identity:RP:Watch:OnReady", message); |
|
138 }, |
|
139 |
|
140 doCancel: function RPWatchContext_oncancel() { |
|
141 log("doCancel: " + this.id); |
|
142 let message = new IDDOMMessage({id: this.id}); |
|
143 this._mm.sendAsyncMessage("Identity:RP:Watch:OnCancel", message); |
|
144 }, |
|
145 |
|
146 doError: function RPWatchContext_onerror(aMessage) { |
|
147 log("doError: " + this.id + ": " + JSON.stringify(aMessage)); |
|
148 let message = new IDDOMMessage({id: this.id, message: aMessage}); |
|
149 this._mm.sendAsyncMessage("Identity:RP:Watch:OnError", message); |
|
150 } |
|
151 }; |
|
152 |
|
153 this.DOMIdentity = { |
|
154 /* |
|
155 * When relying parties (RPs) invoke the watch() method, they can request |
|
156 * to use Firefox Accounts as their auth service or BrowserID (the default). |
|
157 * For each RP, we create an RPWatchContext to store the parameters given to |
|
158 * watch(), and to provide hooks to invoke the onlogin(), onlogout(), etc. |
|
159 * callbacks held in the nsDOMIdentity state. |
|
160 * |
|
161 * The serviceContexts map associates the window ID of the RP with the |
|
162 * context object. The mmContexts map associates a message manager with a |
|
163 * window ID. We use the mmContexts map when child-process-shutdown is |
|
164 * observed, and all we have is a message manager to identify the window in |
|
165 * question. |
|
166 */ |
|
167 _serviceContexts: new Map(), |
|
168 _mmContexts: new Map(), |
|
169 |
|
170 /* |
|
171 * Mockable, for testing |
|
172 */ |
|
173 _mockIdentityService: null, |
|
174 get IdentityService() { |
|
175 if (this._mockIdentityService) { |
|
176 log("Using a mocked identity service"); |
|
177 return this._mockIdentityService; |
|
178 } |
|
179 return IdentityService; |
|
180 }, |
|
181 |
|
182 /* |
|
183 * Create a new RPWatchContext, and update the context maps. |
|
184 */ |
|
185 newContext: function(message, targetMM) { |
|
186 let context = new RPWatchContext(message, targetMM); |
|
187 this._serviceContexts.set(message.id, context); |
|
188 this._mmContexts.set(targetMM, message.id); |
|
189 return context; |
|
190 }, |
|
191 |
|
192 /* |
|
193 * Get the identity service used for an RP. |
|
194 * |
|
195 * @object message |
|
196 * A message received from an RP. Will include the id of the window |
|
197 * whence the message originated. |
|
198 * |
|
199 * Returns FirefoxAccounts or IdentityService |
|
200 */ |
|
201 getService: function(message) { |
|
202 if (!this._serviceContexts.has(message.id)) { |
|
203 throw new Error("getService called before newContext for " + message.id); |
|
204 } |
|
205 |
|
206 let context = this._serviceContexts.get(message.id); |
|
207 if (context.wantIssuer == "firefox-accounts") { |
|
208 if (Services.prefs.getPrefType(PREF_FXA_ENABLED) === Ci.nsIPrefBranch.PREF_BOOL |
|
209 && Services.prefs.getBoolPref(PREF_FXA_ENABLED)) { |
|
210 return FirefoxAccounts; |
|
211 } |
|
212 log("WARNING: Firefox Accounts is not enabled; Defaulting to BrowserID"); |
|
213 } |
|
214 return this.IdentityService; |
|
215 }, |
|
216 |
|
217 /* |
|
218 * Get the RPWatchContext object for a given message manager. |
|
219 */ |
|
220 getContextForMM: function(targetMM) { |
|
221 return this._serviceContexts.get(this._mmContexts.get(targetMM)); |
|
222 }, |
|
223 |
|
224 hasContextForMM: function(targetMM) { |
|
225 return this._mmContexts.has(targetMM); |
|
226 }, |
|
227 |
|
228 /* |
|
229 * Delete the RPWatchContext object for a given message manager. Removes the |
|
230 * mapping both from _serviceContexts and _mmContexts. |
|
231 */ |
|
232 deleteContextForMM: function(targetMM) { |
|
233 this._serviceContexts.delete(this._mmContexts.get(targetMM)); |
|
234 this._mmContexts.delete(targetMM); |
|
235 }, |
|
236 |
|
237 // nsIMessageListener |
|
238 receiveMessage: function DOMIdentity_receiveMessage(aMessage) { |
|
239 let msg = aMessage.json; |
|
240 |
|
241 // Target is the frame message manager that called us and is |
|
242 // used to send replies back to the proper window. |
|
243 let targetMM = aMessage.target; |
|
244 |
|
245 switch (aMessage.name) { |
|
246 // RP |
|
247 case "Identity:RP:Watch": |
|
248 this._watch(msg, targetMM); |
|
249 break; |
|
250 case "Identity:RP:Unwatch": |
|
251 this._unwatch(msg, targetMM); |
|
252 break; |
|
253 case "Identity:RP:Request": |
|
254 this._request(msg, targetMM); |
|
255 break; |
|
256 case "Identity:RP:Logout": |
|
257 this._logout(msg, targetMM); |
|
258 break; |
|
259 // IDP |
|
260 case "Identity:IDP:BeginProvisioning": |
|
261 this._beginProvisioning(msg, targetMM); |
|
262 break; |
|
263 case "Identity:IDP:GenKeyPair": |
|
264 this._genKeyPair(msg); |
|
265 break; |
|
266 case "Identity:IDP:RegisterCertificate": |
|
267 this._registerCertificate(msg); |
|
268 break; |
|
269 case "Identity:IDP:ProvisioningFailure": |
|
270 this._provisioningFailure(msg); |
|
271 break; |
|
272 case "Identity:IDP:BeginAuthentication": |
|
273 this._beginAuthentication(msg, targetMM); |
|
274 break; |
|
275 case "Identity:IDP:CompleteAuthentication": |
|
276 this._completeAuthentication(msg); |
|
277 break; |
|
278 case "Identity:IDP:AuthenticationFailure": |
|
279 this._authenticationFailure(msg); |
|
280 break; |
|
281 case "child-process-shutdown": |
|
282 // we receive child-process-shutdown if the appliction crashes, |
|
283 // including if it is crashed by the OS (killed for out-of-memory, |
|
284 // for example) |
|
285 this._childProcessShutdown(targetMM); |
|
286 break; |
|
287 } |
|
288 }, |
|
289 |
|
290 // nsIObserver |
|
291 observe: function DOMIdentity_observe(aSubject, aTopic, aData) { |
|
292 switch (aTopic) { |
|
293 case "xpcom-shutdown": |
|
294 this._unsubscribeListeners(); |
|
295 Services.obs.removeObserver(this, "xpcom-shutdown"); |
|
296 Services.ww.unregisterNotification(this); |
|
297 break; |
|
298 } |
|
299 }, |
|
300 |
|
301 messages: ["Identity:RP:Watch", "Identity:RP:Request", "Identity:RP:Logout", |
|
302 "Identity:IDP:BeginProvisioning", "Identity:IDP:ProvisioningFailure", |
|
303 "Identity:IDP:RegisterCertificate", "Identity:IDP:GenKeyPair", |
|
304 "Identity:IDP:BeginAuthentication", |
|
305 "Identity:IDP:CompleteAuthentication", |
|
306 "Identity:IDP:AuthenticationFailure", |
|
307 "Identity:RP:Unwatch", |
|
308 "child-process-shutdown"], |
|
309 |
|
310 // Private. |
|
311 _init: function DOMIdentity__init() { |
|
312 Services.ww.registerNotification(this); |
|
313 Services.obs.addObserver(this, "xpcom-shutdown", false); |
|
314 this._subscribeListeners(); |
|
315 }, |
|
316 |
|
317 _subscribeListeners: function DOMIdentity__subscribeListeners() { |
|
318 if (!ppmm) return; |
|
319 for (let message of this.messages) { |
|
320 ppmm.addMessageListener(message, this); |
|
321 } |
|
322 }, |
|
323 |
|
324 _unsubscribeListeners: function DOMIdentity__unsubscribeListeners() { |
|
325 for (let message of this.messages) { |
|
326 ppmm.removeMessageListener(message, this); |
|
327 } |
|
328 ppmm = null; |
|
329 }, |
|
330 |
|
331 _watch: function DOMIdentity__watch(message, targetMM) { |
|
332 log("DOMIdentity__watch: " + message.id); |
|
333 let context = this.newContext(message, targetMM); |
|
334 this.getService(message).RP.watch(context); |
|
335 }, |
|
336 |
|
337 _unwatch: function DOMIdentity_unwatch(message, targetMM) { |
|
338 log("DOMIDentity__unwatch: " + message.id); |
|
339 // If watch failed for some reason (e.g., exception thrown because RP did |
|
340 // not have the right callbacks, we don't want unwatch to throw, because it |
|
341 // will break the process of releasing the page's resources and leak |
|
342 // memory. |
|
343 try { |
|
344 this.getService(message).RP.unwatch(message.id, targetMM); |
|
345 } catch(ex) { |
|
346 log("ERROR: can't unwatch " + message.id + ": " + ex); |
|
347 } |
|
348 }, |
|
349 |
|
350 _request: function DOMIdentity__request(message) { |
|
351 this.getService(message).RP.request(message.id, message); |
|
352 }, |
|
353 |
|
354 _logout: function DOMIdentity__logout(message) { |
|
355 log("logout " + message + "\n"); |
|
356 this.getService(message).RP.logout(message.id, message.origin, message); |
|
357 }, |
|
358 |
|
359 _childProcessShutdown: function DOMIdentity__childProcessShutdown(targetMM) { |
|
360 if (!this.hasContextForMM(targetMM)) { |
|
361 return; |
|
362 } |
|
363 |
|
364 this.getContextForMM(targetMM).RP.childProcessShutdown(targetMM); |
|
365 this.deleteContextForMM(targetMM); |
|
366 |
|
367 let options = makeMessageObject({messageManager: targetMM, id: null, origin: null}); |
|
368 Services.obs.notifyObservers({wrappedJSObject: options}, "identity-child-process-shutdown", null); |
|
369 }, |
|
370 |
|
371 _beginProvisioning: function DOMIdentity__beginProvisioning(message, targetMM) { |
|
372 let context = new IDPProvisioningContext(message.id, message.origin, |
|
373 targetMM); |
|
374 this.getService(message).IDP.beginProvisioning(context); |
|
375 }, |
|
376 |
|
377 _genKeyPair: function DOMIdentity__genKeyPair(message) { |
|
378 this.getService(message).IDP.genKeyPair(message.id); |
|
379 }, |
|
380 |
|
381 _registerCertificate: function DOMIdentity__registerCertificate(message) { |
|
382 this.getService(message).IDP.registerCertificate(message.id, message.cert); |
|
383 }, |
|
384 |
|
385 _provisioningFailure: function DOMIdentity__provisioningFailure(message) { |
|
386 this.getService(message).IDP.raiseProvisioningFailure(message.id, message.reason); |
|
387 }, |
|
388 |
|
389 _beginAuthentication: function DOMIdentity__beginAuthentication(message, targetMM) { |
|
390 let context = new IDPAuthenticationContext(message.id, message.origin, |
|
391 targetMM); |
|
392 this.getService(message).IDP.beginAuthentication(context); |
|
393 }, |
|
394 |
|
395 _completeAuthentication: function DOMIdentity__completeAuthentication(message) { |
|
396 this.getService(message).IDP.completeAuthentication(message.id); |
|
397 }, |
|
398 |
|
399 _authenticationFailure: function DOMIdentity__authenticationFailure(message) { |
|
400 this.getService(message).IDP.cancelAuthentication(message.id); |
|
401 } |
|
402 }; |
|
403 |
|
404 // Object is initialized by nsIDService.js |