|
1 /* -*- Mode: js2; js2-basic-offset: 2; indent-tabs-mode: nil; -*- */ |
|
2 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */ |
|
3 /* This Source Code Form is subject to the terms of the Mozilla Public |
|
4 * License, v. 2.0. If a copy of the MPL was not distributed with this file, |
|
5 * You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
6 |
|
7 /* |
|
8 * This alternate implementation of IdentityService provides just the |
|
9 * channels for navigator.id, leaving the certificate storage to a |
|
10 * server-provided app. |
|
11 * |
|
12 * On b2g, the messages identity-controller-watch, -request, and |
|
13 * -logout, are observed by the component SignInToWebsite.jsm. |
|
14 */ |
|
15 |
|
16 "use strict"; |
|
17 |
|
18 this.EXPORTED_SYMBOLS = ["IdentityService"]; |
|
19 |
|
20 const Cu = Components.utils; |
|
21 const Ci = Components.interfaces; |
|
22 const Cc = Components.classes; |
|
23 const Cr = Components.results; |
|
24 |
|
25 Cu.import("resource://gre/modules/XPCOMUtils.jsm"); |
|
26 Cu.import("resource://gre/modules/Services.jsm"); |
|
27 Cu.import("resource://gre/modules/identity/LogUtils.jsm"); |
|
28 |
|
29 XPCOMUtils.defineLazyModuleGetter(this, "objectCopy", |
|
30 "resource://gre/modules/identity/IdentityUtils.jsm"); |
|
31 |
|
32 XPCOMUtils.defineLazyModuleGetter(this, "makeMessageObject", |
|
33 "resource://gre/modules/identity/IdentityUtils.jsm"); |
|
34 |
|
35 function log(...aMessageArgs) { |
|
36 Logger.log.apply(Logger, ["minimal core"].concat(aMessageArgs)); |
|
37 } |
|
38 function reportError(...aMessageArgs) { |
|
39 Logger.reportError.apply(Logger, ["core"].concat(aMessageArgs)); |
|
40 } |
|
41 |
|
42 function IDService() { |
|
43 Services.obs.addObserver(this, "quit-application-granted", false); |
|
44 |
|
45 // simplify, it's one object |
|
46 this.RP = this; |
|
47 this.IDP = this; |
|
48 |
|
49 // keep track of flows |
|
50 this._rpFlows = {}; |
|
51 this._authFlows = {}; |
|
52 this._provFlows = {}; |
|
53 } |
|
54 |
|
55 IDService.prototype = { |
|
56 QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports, Ci.nsIObserver]), |
|
57 |
|
58 observe: function observe(aSubject, aTopic, aData) { |
|
59 switch (aTopic) { |
|
60 case "quit-application-granted": |
|
61 this.shutdown(); |
|
62 break; |
|
63 } |
|
64 }, |
|
65 |
|
66 shutdown: function() { |
|
67 Services.obs.removeObserver(this, "quit-application-granted"); |
|
68 }, |
|
69 |
|
70 /** |
|
71 * Parse an email into username and domain if it is valid, else return null |
|
72 */ |
|
73 parseEmail: function parseEmail(email) { |
|
74 var match = email.match(/^([^@]+)@([^@^/]+.[a-z]+)$/); |
|
75 if (match) { |
|
76 return { |
|
77 username: match[1], |
|
78 domain: match[2] |
|
79 }; |
|
80 } |
|
81 return null; |
|
82 }, |
|
83 |
|
84 /** |
|
85 * Register a listener for a given windowID as a result of a call to |
|
86 * navigator.id.watch(). |
|
87 * |
|
88 * @param aCaller |
|
89 * (Object) an object that represents the caller document, and |
|
90 * is expected to have properties: |
|
91 * - id (unique, e.g. uuid) |
|
92 * - loggedInUser (string or null) |
|
93 * - origin (string) |
|
94 * |
|
95 * and a bunch of callbacks |
|
96 * - doReady() |
|
97 * - doLogin() |
|
98 * - doLogout() |
|
99 * - doError() |
|
100 * - doCancel() |
|
101 * |
|
102 */ |
|
103 watch: function watch(aRpCaller) { |
|
104 // store the caller structure and notify the UI observers |
|
105 this._rpFlows[aRpCaller.id] = aRpCaller; |
|
106 |
|
107 log("flows:", Object.keys(this._rpFlows).join(', ')); |
|
108 |
|
109 let options = makeMessageObject(aRpCaller); |
|
110 log("sending identity-controller-watch:", options); |
|
111 Services.obs.notifyObservers({wrappedJSObject: options},"identity-controller-watch", null); |
|
112 }, |
|
113 |
|
114 /* |
|
115 * The RP has gone away; remove handles to the hidden iframe. |
|
116 * It's probable that the frame will already have been cleaned up. |
|
117 */ |
|
118 unwatch: function unwatch(aRpId, aTargetMM) { |
|
119 let rp = this._rpFlows[aRpId]; |
|
120 if (!rp) { |
|
121 return; |
|
122 } |
|
123 |
|
124 let options = makeMessageObject({ |
|
125 id: aRpId, |
|
126 origin: rp.origin, |
|
127 messageManager: aTargetMM |
|
128 }); |
|
129 log("sending identity-controller-unwatch for id", options.id, options.origin); |
|
130 Services.obs.notifyObservers({wrappedJSObject: options}, "identity-controller-unwatch", null); |
|
131 |
|
132 // Stop sending messages to this window |
|
133 delete this._rpFlows[aRpId]; |
|
134 }, |
|
135 |
|
136 /** |
|
137 * Initiate a login with user interaction as a result of a call to |
|
138 * navigator.id.request(). |
|
139 * |
|
140 * @param aRPId |
|
141 * (integer) the id of the doc object obtained in .watch() |
|
142 * |
|
143 * @param aOptions |
|
144 * (Object) options including privacyPolicy, termsOfService |
|
145 */ |
|
146 request: function request(aRPId, aOptions) { |
|
147 let rp = this._rpFlows[aRPId]; |
|
148 if (!rp) { |
|
149 reportError("request() called before watch()"); |
|
150 return; |
|
151 } |
|
152 |
|
153 // Notify UX to display identity picker. |
|
154 // Pass the doc id to UX so it can pass it back to us later. |
|
155 let options = makeMessageObject(rp); |
|
156 objectCopy(aOptions, options); |
|
157 Services.obs.notifyObservers({wrappedJSObject: options}, "identity-controller-request", null); |
|
158 }, |
|
159 |
|
160 /** |
|
161 * Invoked when a user wishes to logout of a site (for instance, when clicking |
|
162 * on an in-content logout button). |
|
163 * |
|
164 * @param aRpCallerId |
|
165 * (integer) the id of the doc object obtained in .watch() |
|
166 * |
|
167 */ |
|
168 logout: function logout(aRpCallerId) { |
|
169 let rp = this._rpFlows[aRpCallerId]; |
|
170 if (!rp) { |
|
171 reportError("logout() called before watch()"); |
|
172 return; |
|
173 } |
|
174 |
|
175 let options = makeMessageObject(rp); |
|
176 Services.obs.notifyObservers({wrappedJSObject: options}, "identity-controller-logout", null); |
|
177 }, |
|
178 |
|
179 childProcessShutdown: function childProcessShutdown(messageManager) { |
|
180 Object.keys(this._rpFlows).forEach(function(key) { |
|
181 if (this._rpFlows[key]._mm === messageManager) { |
|
182 log("child process shutdown for rp", key, "- deleting flow"); |
|
183 delete this._rpFlows[key]; |
|
184 } |
|
185 }, this); |
|
186 }, |
|
187 |
|
188 /* |
|
189 * once the UI-and-display-logic components have received |
|
190 * notifications, they call back with direct invocation of the |
|
191 * following functions (doLogin, doLogout, or doReady) |
|
192 */ |
|
193 |
|
194 doLogin: function doLogin(aRpCallerId, aAssertion, aInternalParams) { |
|
195 let rp = this._rpFlows[aRpCallerId]; |
|
196 if (!rp) { |
|
197 log("WARNING: doLogin found no rp to go with callerId " + aRpCallerId); |
|
198 return; |
|
199 } |
|
200 |
|
201 rp.doLogin(aAssertion, aInternalParams); |
|
202 }, |
|
203 |
|
204 doLogout: function doLogout(aRpCallerId) { |
|
205 let rp = this._rpFlows[aRpCallerId]; |
|
206 if (!rp) { |
|
207 log("WARNING: doLogout found no rp to go with callerId " + aRpCallerId); |
|
208 return; |
|
209 } |
|
210 |
|
211 // Logout from every site with the same origin |
|
212 let origin = rp.origin; |
|
213 Object.keys(this._rpFlows).forEach(function(key) { |
|
214 let rp = this._rpFlows[key]; |
|
215 if (rp.origin === origin) { |
|
216 rp.doLogout(); |
|
217 } |
|
218 }.bind(this)); |
|
219 }, |
|
220 |
|
221 doReady: function doReady(aRpCallerId) { |
|
222 let rp = this._rpFlows[aRpCallerId]; |
|
223 if (!rp) { |
|
224 log("WARNING: doReady found no rp to go with callerId " + aRpCallerId); |
|
225 return; |
|
226 } |
|
227 |
|
228 rp.doReady(); |
|
229 }, |
|
230 |
|
231 doCancel: function doCancel(aRpCallerId) { |
|
232 let rp = this._rpFlows[aRpCallerId]; |
|
233 if (!rp) { |
|
234 log("WARNING: doCancel found no rp to go with callerId " + aRpCallerId); |
|
235 return; |
|
236 } |
|
237 |
|
238 rp.doCancel(); |
|
239 } |
|
240 }; |
|
241 |
|
242 this.IdentityService = new IDService(); |