|
1 /* Any copyright is dedicated to the Public Domain. |
|
2 * http://creativecommons.org/publicdomain/zero/1.0/ */ |
|
3 |
|
4 const Cc = Components.classes; |
|
5 const Ci = Components.interfaces; |
|
6 const Cu = Components.utils; |
|
7 const Cr = Components.results; |
|
8 |
|
9 Cu.import("resource://testing-common/httpd.js"); |
|
10 |
|
11 // XXX until bug 937114 is fixed |
|
12 Cu.importGlobalProperties(["atob"]); |
|
13 |
|
14 // The following boilerplate makes sure that XPCom calls |
|
15 // that use the profile directory work. |
|
16 |
|
17 Cu.import("resource://gre/modules/XPCOMUtils.jsm"); |
|
18 Cu.import("resource://gre/modules/Services.jsm"); |
|
19 |
|
20 XPCOMUtils.defineLazyModuleGetter(this, "jwcrypto", |
|
21 "resource://gre/modules/identity/jwcrypto.jsm"); |
|
22 |
|
23 XPCOMUtils.defineLazyModuleGetter(this, "IDService", |
|
24 "resource://gre/modules/identity/Identity.jsm", |
|
25 "IdentityService"); |
|
26 |
|
27 XPCOMUtils.defineLazyModuleGetter(this, |
|
28 "IdentityStore", |
|
29 "resource://gre/modules/identity/IdentityStore.jsm"); |
|
30 |
|
31 XPCOMUtils.defineLazyModuleGetter(this, |
|
32 "Logger", |
|
33 "resource://gre/modules/identity/LogUtils.jsm"); |
|
34 |
|
35 XPCOMUtils.defineLazyServiceGetter(this, |
|
36 "uuidGenerator", |
|
37 "@mozilla.org/uuid-generator;1", |
|
38 "nsIUUIDGenerator"); |
|
39 |
|
40 const TEST_MESSAGE_MANAGER = "Mr McFeeley"; |
|
41 const TEST_URL = "https://myfavoritebacon.com"; |
|
42 const TEST_URL2 = "https://myfavoritebaconinacan.com"; |
|
43 const TEST_USER = "user@mozilla.com"; |
|
44 const TEST_PRIVKEY = "fake-privkey"; |
|
45 const TEST_CERT = "fake-cert"; |
|
46 const TEST_ASSERTION = "fake-assertion"; |
|
47 const TEST_IDPPARAMS = { |
|
48 domain: "myfavoriteflan.com", |
|
49 authentication: "/foo/authenticate.html", |
|
50 provisioning: "/foo/provision.html" |
|
51 }; |
|
52 |
|
53 // The following are utility functions for Identity testing |
|
54 |
|
55 function log(...aMessageArgs) { |
|
56 Logger.log.apply(Logger, ["test"].concat(aMessageArgs)); |
|
57 } |
|
58 |
|
59 function get_idstore() { |
|
60 return IdentityStore; |
|
61 } |
|
62 |
|
63 function partial(fn) { |
|
64 let args = Array.prototype.slice.call(arguments, 1); |
|
65 return function() { |
|
66 return fn.apply(this, args.concat(Array.prototype.slice.call(arguments))); |
|
67 }; |
|
68 } |
|
69 |
|
70 function uuid() { |
|
71 return uuidGenerator.generateUUID().toString(); |
|
72 } |
|
73 |
|
74 function base64UrlDecode(s) { |
|
75 s = s.replace(/-/g, "+"); |
|
76 s = s.replace(/_/g, "/"); |
|
77 |
|
78 // Replace padding if it was stripped by the sender. |
|
79 // See http://tools.ietf.org/html/rfc4648#section-4 |
|
80 switch (s.length % 4) { |
|
81 case 0: |
|
82 break; // No pad chars in this case |
|
83 case 2: |
|
84 s += "=="; |
|
85 break; // Two pad chars |
|
86 case 3: |
|
87 s += "="; |
|
88 break; // One pad char |
|
89 default: |
|
90 throw new InputException("Illegal base64url string!"); |
|
91 } |
|
92 |
|
93 // With correct padding restored, apply the standard base64 decoder |
|
94 return atob(s); |
|
95 } |
|
96 |
|
97 // create a mock "doc" object, which the Identity Service |
|
98 // uses as a pointer back into the doc object |
|
99 function mock_doc(aIdentity, aOrigin, aDoFunc) { |
|
100 let mockedDoc = {}; |
|
101 mockedDoc.id = uuid(); |
|
102 mockedDoc.loggedInUser = aIdentity; |
|
103 mockedDoc.origin = aOrigin; |
|
104 mockedDoc["do"] = aDoFunc; |
|
105 mockedDoc._mm = TEST_MESSAGE_MANAGER; |
|
106 mockedDoc.doReady = partial(aDoFunc, "ready"); |
|
107 mockedDoc.doLogin = partial(aDoFunc, "login"); |
|
108 mockedDoc.doLogout = partial(aDoFunc, "logout"); |
|
109 mockedDoc.doError = partial(aDoFunc, "error"); |
|
110 mockedDoc.doCancel = partial(aDoFunc, "cancel"); |
|
111 mockedDoc.doCoffee = partial(aDoFunc, "coffee"); |
|
112 mockedDoc.childProcessShutdown = partial(aDoFunc, "child-process-shutdown"); |
|
113 |
|
114 mockedDoc.RP = mockedDoc; |
|
115 |
|
116 return mockedDoc; |
|
117 } |
|
118 |
|
119 function mock_fxa_rp(aIdentity, aOrigin, aDoFunc) { |
|
120 let mockedDoc = {}; |
|
121 mockedDoc.id = uuid(); |
|
122 mockedDoc.emailHint = aIdentity; |
|
123 mockedDoc.origin = aOrigin; |
|
124 mockedDoc.wantIssuer = "firefox-accounts"; |
|
125 mockedDoc._mm = TEST_MESSAGE_MANAGER; |
|
126 |
|
127 mockedDoc.doReady = partial(aDoFunc, "ready"); |
|
128 mockedDoc.doLogin = partial(aDoFunc, "login"); |
|
129 mockedDoc.doLogout = partial(aDoFunc, "logout"); |
|
130 mockedDoc.doError = partial(aDoFunc, "error"); |
|
131 mockedDoc.doCancel = partial(aDoFunc, "cancel"); |
|
132 mockedDoc.childProcessShutdown = partial(aDoFunc, "child-process-shutdown"); |
|
133 |
|
134 mockedDoc.RP = mockedDoc; |
|
135 |
|
136 return mockedDoc; |
|
137 } |
|
138 |
|
139 // mimicking callback funtionality for ease of testing |
|
140 // this observer auto-removes itself after the observe function |
|
141 // is called, so this is meant to observe only ONE event. |
|
142 function makeObserver(aObserveTopic, aObserveFunc) { |
|
143 let observer = { |
|
144 // nsISupports provides type management in C++ |
|
145 // nsIObserver is to be an observer |
|
146 QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports, Ci.nsIObserver]), |
|
147 |
|
148 observe: function (aSubject, aTopic, aData) { |
|
149 if (aTopic == aObserveTopic) { |
|
150 aObserveFunc(aSubject, aTopic, aData); |
|
151 Services.obs.removeObserver(observer, aObserveTopic); |
|
152 } |
|
153 } |
|
154 }; |
|
155 |
|
156 Services.obs.addObserver(observer, aObserveTopic, false); |
|
157 } |
|
158 |
|
159 // set up the ID service with an identity with keypair and all |
|
160 // when ready, invoke callback with the identity |
|
161 function setup_test_identity(identity, cert, cb) { |
|
162 // set up the store so that we're supposed to be logged in |
|
163 let store = get_idstore(); |
|
164 |
|
165 function keyGenerated(err, kpo) { |
|
166 store.addIdentity(identity, kpo, cert); |
|
167 cb(); |
|
168 }; |
|
169 |
|
170 jwcrypto.generateKeyPair("DS160", keyGenerated); |
|
171 } |
|
172 |
|
173 // takes a list of functions and returns a function that |
|
174 // when called the first time, calls the first func, |
|
175 // then the next time the second, etc. |
|
176 function call_sequentially() { |
|
177 let numCalls = 0; |
|
178 let funcs = arguments; |
|
179 |
|
180 return function() { |
|
181 if (!funcs[numCalls]) { |
|
182 let argString = Array.prototype.slice.call(arguments).join(","); |
|
183 do_throw("Too many calls: " + argString); |
|
184 return; |
|
185 } |
|
186 funcs[numCalls].apply(funcs[numCalls],arguments); |
|
187 numCalls += 1; |
|
188 }; |
|
189 } |
|
190 |
|
191 /* |
|
192 * Setup a provisioning workflow with appropriate callbacks |
|
193 * |
|
194 * identity is the email we're provisioning. |
|
195 * |
|
196 * afterSetupCallback is required. |
|
197 * |
|
198 * doneProvisioningCallback is optional, if the caller |
|
199 * wants to be notified when the whole provisioning workflow is done |
|
200 * |
|
201 * frameCallbacks is optional, contains the callbacks that the sandbox |
|
202 * frame would provide in response to DOM calls. |
|
203 */ |
|
204 function setup_provisioning(identity, afterSetupCallback, doneProvisioningCallback, callerCallbacks) { |
|
205 IDService.reset(); |
|
206 |
|
207 let provId = uuid(); |
|
208 IDService.IDP._provisionFlows[provId] = { |
|
209 identity : identity, |
|
210 idpParams: TEST_IDPPARAMS, |
|
211 callback: function(err) { |
|
212 if (doneProvisioningCallback) |
|
213 doneProvisioningCallback(err); |
|
214 }, |
|
215 sandbox: { |
|
216 // Emulate the free() method on the iframe sandbox |
|
217 free: function() {} |
|
218 } |
|
219 }; |
|
220 |
|
221 let caller = {}; |
|
222 caller.id = provId; |
|
223 caller.doBeginProvisioningCallback = function(id, duration_s) { |
|
224 if (callerCallbacks && callerCallbacks.beginProvisioningCallback) |
|
225 callerCallbacks.beginProvisioningCallback(id, duration_s); |
|
226 }; |
|
227 caller.doGenKeyPairCallback = function(pk) { |
|
228 if (callerCallbacks && callerCallbacks.genKeyPairCallback) |
|
229 callerCallbacks.genKeyPairCallback(pk); |
|
230 }; |
|
231 |
|
232 afterSetupCallback(caller); |
|
233 } |
|
234 |
|
235 // Switch debug messages on by default |
|
236 let initialPrefDebugValue = false; |
|
237 try { |
|
238 initialPrefDebugValue = Services.prefs.getBoolPref("toolkit.identity.debug"); |
|
239 } catch(noPref) {} |
|
240 Services.prefs.setBoolPref("toolkit.identity.debug", true); |
|
241 |
|
242 // Switch on firefox accounts |
|
243 let initialPrefFXAValue = false; |
|
244 try { |
|
245 initialPrefFXAValue = Services.prefs.getBoolPref("identity.fxaccounts.enabled"); |
|
246 } catch(noPref) {} |
|
247 Services.prefs.setBoolPref("identity.fxaccounts.enabled", true); |
|
248 |
|
249 // after execution, restore prefs |
|
250 do_register_cleanup(function() { |
|
251 log("restoring prefs to their initial values"); |
|
252 Services.prefs.setBoolPref("toolkit.identity.debug", initialPrefDebugValue); |
|
253 Services.prefs.setBoolPref("identity.fxaccounts.enabled", initialPrefFXAValue); |
|
254 }); |
|
255 |
|
256 |