dom/identity/tests/mochitest/test_declareAudience.html

branch
TOR_BUG_9701
changeset 15
b8a032363ba2
equal deleted inserted replaced
-1:000000000000 0:d36f39a707ae
1 <!DOCTYPE HTML>
2 <html>
3 <!--
4 https://bugzilla.mozilla.org/show_bug.cgi?id=947374
5 -->
6 <head>
7 <meta charset="utf-8">
8 <title>Certified apps can changed the default audience of an assertion -- Bug 947374</title>
9 <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
10 <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
11 </head>
12 <body>
13 <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=947374">Mozilla Bug 947374</a>
14 <p id="display"></p>
15 <div id="content">
16
17 </div>
18 <pre id="test">
19 <script type="application/javascript;version=1.8">
20
21 SimpleTest.waitForExplicitFinish();
22
23 Components.utils.import("resource://gre/modules/Promise.jsm");
24 Components.utils.import("resource://gre/modules/Services.jsm");
25 Components.utils.import("resource://gre/modules/identity/jwcrypto.jsm");
26 Components.utils.import("resource://gre/modules/identity/FirefoxAccounts.jsm");
27
28 // quick check to make sure we can test apps:
29 is("appStatus" in document.nodePrincipal, true,
30 "appStatus should be present in nsIPrincipal, if not the rest of this test will fail");
31
32 // Mock the Firefox Accounts manager to generate a keypair and provide a fake
33 // cert for the caller on each getAssertion request.
34 function MockFXAManager() {}
35
36 MockFXAManager.prototype = {
37 getAssertion: function(audience, options) {
38 // Always reject a request for a silent assertion, simulating the
39 // scenario in which there is no signed-in user to begin with.
40 if (options.silent) {
41 return Promise.resolve(null);
42 }
43
44 let deferred = Promise.defer();
45 jwcrypto.generateKeyPair("DS160", (err, kp) => {
46 if (err) {
47 return deferred.reject(err);
48 }
49 jwcrypto.generateAssertion("fake-cert", kp, audience, (err, assertion) => {
50 if (err) {
51 return deferred.reject(err);
52 }
53 return deferred.resolve(assertion);
54 });
55 });
56 return deferred.promise;
57 }
58 };
59
60 let originalManager = FirefoxAccounts.fxAccountsManager;
61 FirefoxAccounts.fxAccountsManager = new MockFXAManager();
62
63 // The manifests for these apps are all declared in
64 // /testing/profiles/webapps_mochitest.json. They are injected into the profile
65 // by /testing/mochitest/runtests.py with the appropriate appStatus. So we don't
66 // have to manually install any apps.
67 //
68 // For each app, we will use the file_declareAudience.html content to populate an
69 // iframe. The iframe will request() a firefox accounts assertion. It will then
70 // postMessage the results of this experiment back down to us, and we will
71 // compare it with the expected results.
72 let apps = [
73 {
74 title: "an installed app, which should neither be able to use FxA, nor change audience",
75 manifest: "https://example.com/manifest.webapp",
76 appStatus: Components.interfaces.nsIPrincipal.APP_STATUS_INSTALLED,
77 origin: "https://example.com",
78 wantAudience: "https://i-cant-have-this.com",
79 uri: "https://example.com/chrome/dom/identity/tests/mochitest/file_declareAudience.html",
80 expected: {
81 success: false,
82 underprivileged: true,
83 },
84 },
85 {
86 title: "an app's assertion audience should be its origin by default",
87 manifest: "https://example.com/manifest_priv.webapp",
88 appStatus: Components.interfaces.nsIPrincipal.APP_STATUS_PRIVILEGED,
89 origin: "https://example.com",
90 uri: "https://example.com/chrome/dom/identity/tests/mochitest/file_declareAudience.html",
91 expected: {
92 success: true,
93 underprivileged: false,
94 },
95 },
96 {
97 title: "a privileged app, which may not have an audience other than its origin",
98 manifest: "https://example.com/manifest_priv.webapp",
99 appStatus: Components.interfaces.nsIPrincipal.APP_STATUS_PRIVILEGED,
100 origin: "https://example.com",
101 wantAudience: "https://i-like-pie.com",
102 uri: "https://example.com/chrome/dom/identity/tests/mochitest/file_declareAudience.html",
103 expected: {
104 success: false,
105 underprivileged: false,
106 },
107 },
108 {
109 title: "a privileged app, which may declare an audience the same as its origin",
110 manifest: "https://example.com/manifest_priv.webapp",
111 appStatus: Components.interfaces.nsIPrincipal.APP_STATUS_PRIVILEGED,
112 origin: "https://example.com",
113 wantAudience: "https://example.com",
114 uri: "https://example.com/chrome/dom/identity/tests/mochitest/file_declareAudience.html",
115 expected: {
116 success: true,
117 },
118 },
119 {
120 title: "a certified app, which may do whatever it damn well pleases",
121 manifest: "https://example.com/manifest_cert.webapp",
122 appStatus: Components.interfaces.nsIPrincipal.APP_STATUS_CERTIFIED,
123 origin: "https://example.com",
124 wantAudience: "https://whatever-i-want.com",
125 uri: "https://example.com/chrome/dom/identity/tests/mochitest/file_declareAudience.html",
126 expected: {
127 success: true,
128 },
129 },
130 ];
131
132 let appIndex = 0;
133 let expectedErrors = 0;
134 let receivedErrors = [];
135 let testRunner = runTest();
136
137 // Successful tests will send exactly one message. But for error tests, we may
138 // have more than one message from the onerror handler in the client. So we keep
139 // track of received errors; once they reach the expected count, we are done.
140 function receiveMessage(event) {
141 let result = JSON.parse(event.data);
142 let app = apps[appIndex];
143 let expected = app.expected;
144
145 is(result.success, expected.success,
146 "Assertion request succeeds");
147
148 if (expected.success) {
149 // Confirm that the assertion audience and origin are as expected
150 let components = extractAssertionComponents(result.backedAssertion);
151 is(components.payload.aud, app.wantAudience || app.origin,
152 "Got desired assertion audience");
153
154 } else {
155 receivedErrors.push(result.error);
156 }
157
158 if (receivedErrors.length === expectedErrors) {
159
160 if (expected.underprivileged) {
161 ok(receivedErrors.indexOf("ERROR_NOT_AUTHORIZED_FOR_FIREFOX_ACCOUNTS") > -1,
162 "Expect a complaint that this app cannot use FxA.");
163 }
164 if (!expected.success) {
165 ok(receivedErrors.indexOf("ERROR_INVALID_ASSERTION_AUDIENCE") > -1,
166 "Expect an error getting an assertion");
167 }
168
169 appIndex += 1;
170
171 if (appIndex === apps.length) {
172 window.removeEventListener("message", receiveMessage);
173
174 FirefoxAccounts.fxAccountsManager = originalManager;
175
176 SimpleTest.finish();
177 return;
178 }
179
180 testRunner.next();
181 }
182 }
183
184 window.addEventListener("message", receiveMessage, false, true);
185
186 function runTest() {
187 for (let app of apps) {
188 dump("** Testing " + app.title + "\n");
189 // Set up state for message handler
190 expectedErrors = 0;
191 receivedErrors = [];
192 if (!app.expected.success) {
193 expectedErrors += 1;
194 }
195 if (app.expected.underprivileged) {
196 expectedErrors += 1;
197 }
198
199 let iframe = document.createElement("iframe");
200
201 iframe.setAttribute("mozapp", app.manifest);
202 iframe.setAttribute("mozbrowser", "true");
203 iframe.src = app.uri;
204
205 document.getElementById("content").appendChild(iframe);
206
207 iframe.addEventListener("load", function onLoad() {
208 iframe.removeEventListener("load", onLoad);
209
210 let principal = iframe.contentDocument.nodePrincipal;
211 is(principal.appStatus, app.appStatus,
212 "Iframe's document.nodePrincipal has expected appStatus");
213
214 // Because the <iframe mozapp> can't parent its way back to us, we
215 // provide this handle to our window so it can postMessage to us.
216 iframe.contentWindow.wrappedJSObject.realParent = window;
217
218 // Test what we want to test, viz. whether or not the app can request
219 // an assertion with an audience the same as or different from its
220 // origin. The client will post back its success or failure in procuring
221 // an identity assertion from Firefox Accounts.
222 iframe.contentWindow.postMessage({audience: app.wantAudience}, "*");
223 }, false);
224
225 yield undefined;
226 }
227 }
228
229 function extractAssertionComponents(backedAssertion) {
230 let [_, signedObject] = backedAssertion.split("~");
231 let parts = signedObject.split(".");
232
233 let headerSegment = parts[0];
234 let payloadSegment = parts[1];
235 let cryptoSegment = parts[2];
236
237 let header = JSON.parse(base64UrlDecode(headerSegment));
238 let payload = JSON.parse(base64UrlDecode(payloadSegment));
239
240 return {header: header,
241 payload: payload,
242 headerSegment: headerSegment,
243 payloadSegment: payloadSegment,
244 cryptoSegment: cryptoSegment};
245 };
246
247 function base64UrlDecode(s) {
248 s = s.replace(/-/g, "+");
249 s = s.replace(/_/g, "/");
250 // Don't need to worry about reintroducing padding ('=='), since
251 // jwcrypto provides that.
252 return atob(s);
253 }
254
255 SpecialPowers.pushPrefEnv({"set":
256 [
257 ["dom.mozBrowserFramesEnabled", true],
258 ["dom.identity.enabled", true],
259 ["identity.fxaccounts.enabled", true],
260 ["toolkit.identity.debug", true],
261 ["dom.identity.syntheticEventsOk", true],
262
263 ["security.apps.privileged.CSP.default", ""],
264 ["security.apps.certified.CSP.default", ""],
265 ]},
266 function() {
267 testRunner.next();
268 }
269 );
270
271
272 </script>
273 </pre>
274 </body>
275 </html>

mercurial