dom/identity/DOMIdentity.jsm

changeset 0
6474c204b198
equal deleted inserted replaced
-1:000000000000 0:6610c88c1ffc
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

mercurial