1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/dom/identity/tests/mochitest/test_declareAudience.html Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,275 @@ 1.4 +<!DOCTYPE HTML> 1.5 +<html> 1.6 +<!-- 1.7 + https://bugzilla.mozilla.org/show_bug.cgi?id=947374 1.8 +--> 1.9 +<head> 1.10 + <meta charset="utf-8"> 1.11 + <title>Certified apps can changed the default audience of an assertion -- Bug 947374</title> 1.12 + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> 1.13 + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/> 1.14 +</head> 1.15 +<body> 1.16 +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=947374">Mozilla Bug 947374</a> 1.17 +<p id="display"></p> 1.18 +<div id="content"> 1.19 + 1.20 +</div> 1.21 +<pre id="test"> 1.22 +<script type="application/javascript;version=1.8"> 1.23 + 1.24 +SimpleTest.waitForExplicitFinish(); 1.25 + 1.26 +Components.utils.import("resource://gre/modules/Promise.jsm"); 1.27 +Components.utils.import("resource://gre/modules/Services.jsm"); 1.28 +Components.utils.import("resource://gre/modules/identity/jwcrypto.jsm"); 1.29 +Components.utils.import("resource://gre/modules/identity/FirefoxAccounts.jsm"); 1.30 + 1.31 +// quick check to make sure we can test apps: 1.32 +is("appStatus" in document.nodePrincipal, true, 1.33 + "appStatus should be present in nsIPrincipal, if not the rest of this test will fail"); 1.34 + 1.35 +// Mock the Firefox Accounts manager to generate a keypair and provide a fake 1.36 +// cert for the caller on each getAssertion request. 1.37 +function MockFXAManager() {} 1.38 + 1.39 +MockFXAManager.prototype = { 1.40 + getAssertion: function(audience, options) { 1.41 + // Always reject a request for a silent assertion, simulating the 1.42 + // scenario in which there is no signed-in user to begin with. 1.43 + if (options.silent) { 1.44 + return Promise.resolve(null); 1.45 + } 1.46 + 1.47 + let deferred = Promise.defer(); 1.48 + jwcrypto.generateKeyPair("DS160", (err, kp) => { 1.49 + if (err) { 1.50 + return deferred.reject(err); 1.51 + } 1.52 + jwcrypto.generateAssertion("fake-cert", kp, audience, (err, assertion) => { 1.53 + if (err) { 1.54 + return deferred.reject(err); 1.55 + } 1.56 + return deferred.resolve(assertion); 1.57 + }); 1.58 + }); 1.59 + return deferred.promise; 1.60 + } 1.61 +}; 1.62 + 1.63 +let originalManager = FirefoxAccounts.fxAccountsManager; 1.64 +FirefoxAccounts.fxAccountsManager = new MockFXAManager(); 1.65 + 1.66 +// The manifests for these apps are all declared in 1.67 +// /testing/profiles/webapps_mochitest.json. They are injected into the profile 1.68 +// by /testing/mochitest/runtests.py with the appropriate appStatus. So we don't 1.69 +// have to manually install any apps. 1.70 +// 1.71 +// For each app, we will use the file_declareAudience.html content to populate an 1.72 +// iframe. The iframe will request() a firefox accounts assertion. It will then 1.73 +// postMessage the results of this experiment back down to us, and we will 1.74 +// compare it with the expected results. 1.75 +let apps = [ 1.76 + { 1.77 + title: "an installed app, which should neither be able to use FxA, nor change audience", 1.78 + manifest: "https://example.com/manifest.webapp", 1.79 + appStatus: Components.interfaces.nsIPrincipal.APP_STATUS_INSTALLED, 1.80 + origin: "https://example.com", 1.81 + wantAudience: "https://i-cant-have-this.com", 1.82 + uri: "https://example.com/chrome/dom/identity/tests/mochitest/file_declareAudience.html", 1.83 + expected: { 1.84 + success: false, 1.85 + underprivileged: true, 1.86 + }, 1.87 + }, 1.88 + { 1.89 + title: "an app's assertion audience should be its origin by default", 1.90 + manifest: "https://example.com/manifest_priv.webapp", 1.91 + appStatus: Components.interfaces.nsIPrincipal.APP_STATUS_PRIVILEGED, 1.92 + origin: "https://example.com", 1.93 + uri: "https://example.com/chrome/dom/identity/tests/mochitest/file_declareAudience.html", 1.94 + expected: { 1.95 + success: true, 1.96 + underprivileged: false, 1.97 + }, 1.98 + }, 1.99 + { 1.100 + title: "a privileged app, which may not have an audience other than its origin", 1.101 + manifest: "https://example.com/manifest_priv.webapp", 1.102 + appStatus: Components.interfaces.nsIPrincipal.APP_STATUS_PRIVILEGED, 1.103 + origin: "https://example.com", 1.104 + wantAudience: "https://i-like-pie.com", 1.105 + uri: "https://example.com/chrome/dom/identity/tests/mochitest/file_declareAudience.html", 1.106 + expected: { 1.107 + success: false, 1.108 + underprivileged: false, 1.109 + }, 1.110 + }, 1.111 + { 1.112 + title: "a privileged app, which may declare an audience the same as its origin", 1.113 + manifest: "https://example.com/manifest_priv.webapp", 1.114 + appStatus: Components.interfaces.nsIPrincipal.APP_STATUS_PRIVILEGED, 1.115 + origin: "https://example.com", 1.116 + wantAudience: "https://example.com", 1.117 + uri: "https://example.com/chrome/dom/identity/tests/mochitest/file_declareAudience.html", 1.118 + expected: { 1.119 + success: true, 1.120 + }, 1.121 + }, 1.122 + { 1.123 + title: "a certified app, which may do whatever it damn well pleases", 1.124 + manifest: "https://example.com/manifest_cert.webapp", 1.125 + appStatus: Components.interfaces.nsIPrincipal.APP_STATUS_CERTIFIED, 1.126 + origin: "https://example.com", 1.127 + wantAudience: "https://whatever-i-want.com", 1.128 + uri: "https://example.com/chrome/dom/identity/tests/mochitest/file_declareAudience.html", 1.129 + expected: { 1.130 + success: true, 1.131 + }, 1.132 + }, 1.133 +]; 1.134 + 1.135 +let appIndex = 0; 1.136 +let expectedErrors = 0; 1.137 +let receivedErrors = []; 1.138 +let testRunner = runTest(); 1.139 + 1.140 +// Successful tests will send exactly one message. But for error tests, we may 1.141 +// have more than one message from the onerror handler in the client. So we keep 1.142 +// track of received errors; once they reach the expected count, we are done. 1.143 +function receiveMessage(event) { 1.144 + let result = JSON.parse(event.data); 1.145 + let app = apps[appIndex]; 1.146 + let expected = app.expected; 1.147 + 1.148 + is(result.success, expected.success, 1.149 + "Assertion request succeeds"); 1.150 + 1.151 + if (expected.success) { 1.152 + // Confirm that the assertion audience and origin are as expected 1.153 + let components = extractAssertionComponents(result.backedAssertion); 1.154 + is(components.payload.aud, app.wantAudience || app.origin, 1.155 + "Got desired assertion audience"); 1.156 + 1.157 + } else { 1.158 + receivedErrors.push(result.error); 1.159 + } 1.160 + 1.161 + if (receivedErrors.length === expectedErrors) { 1.162 + 1.163 + if (expected.underprivileged) { 1.164 + ok(receivedErrors.indexOf("ERROR_NOT_AUTHORIZED_FOR_FIREFOX_ACCOUNTS") > -1, 1.165 + "Expect a complaint that this app cannot use FxA."); 1.166 + } 1.167 + if (!expected.success) { 1.168 + ok(receivedErrors.indexOf("ERROR_INVALID_ASSERTION_AUDIENCE") > -1, 1.169 + "Expect an error getting an assertion"); 1.170 + } 1.171 + 1.172 + appIndex += 1; 1.173 + 1.174 + if (appIndex === apps.length) { 1.175 + window.removeEventListener("message", receiveMessage); 1.176 + 1.177 + FirefoxAccounts.fxAccountsManager = originalManager; 1.178 + 1.179 + SimpleTest.finish(); 1.180 + return; 1.181 + } 1.182 + 1.183 + testRunner.next(); 1.184 + } 1.185 +} 1.186 + 1.187 +window.addEventListener("message", receiveMessage, false, true); 1.188 + 1.189 +function runTest() { 1.190 + for (let app of apps) { 1.191 + dump("** Testing " + app.title + "\n"); 1.192 + // Set up state for message handler 1.193 + expectedErrors = 0; 1.194 + receivedErrors = []; 1.195 + if (!app.expected.success) { 1.196 + expectedErrors += 1; 1.197 + } 1.198 + if (app.expected.underprivileged) { 1.199 + expectedErrors += 1; 1.200 + } 1.201 + 1.202 + let iframe = document.createElement("iframe"); 1.203 + 1.204 + iframe.setAttribute("mozapp", app.manifest); 1.205 + iframe.setAttribute("mozbrowser", "true"); 1.206 + iframe.src = app.uri; 1.207 + 1.208 + document.getElementById("content").appendChild(iframe); 1.209 + 1.210 + iframe.addEventListener("load", function onLoad() { 1.211 + iframe.removeEventListener("load", onLoad); 1.212 + 1.213 + let principal = iframe.contentDocument.nodePrincipal; 1.214 + is(principal.appStatus, app.appStatus, 1.215 + "Iframe's document.nodePrincipal has expected appStatus"); 1.216 + 1.217 + // Because the <iframe mozapp> can't parent its way back to us, we 1.218 + // provide this handle to our window so it can postMessage to us. 1.219 + iframe.contentWindow.wrappedJSObject.realParent = window; 1.220 + 1.221 + // Test what we want to test, viz. whether or not the app can request 1.222 + // an assertion with an audience the same as or different from its 1.223 + // origin. The client will post back its success or failure in procuring 1.224 + // an identity assertion from Firefox Accounts. 1.225 + iframe.contentWindow.postMessage({audience: app.wantAudience}, "*"); 1.226 + }, false); 1.227 + 1.228 + yield undefined; 1.229 + } 1.230 +} 1.231 + 1.232 +function extractAssertionComponents(backedAssertion) { 1.233 + let [_, signedObject] = backedAssertion.split("~"); 1.234 + let parts = signedObject.split("."); 1.235 + 1.236 + let headerSegment = parts[0]; 1.237 + let payloadSegment = parts[1]; 1.238 + let cryptoSegment = parts[2]; 1.239 + 1.240 + let header = JSON.parse(base64UrlDecode(headerSegment)); 1.241 + let payload = JSON.parse(base64UrlDecode(payloadSegment)); 1.242 + 1.243 + return {header: header, 1.244 + payload: payload, 1.245 + headerSegment: headerSegment, 1.246 + payloadSegment: payloadSegment, 1.247 + cryptoSegment: cryptoSegment}; 1.248 +}; 1.249 + 1.250 +function base64UrlDecode(s) { 1.251 + s = s.replace(/-/g, "+"); 1.252 + s = s.replace(/_/g, "/"); 1.253 + // Don't need to worry about reintroducing padding ('=='), since 1.254 + // jwcrypto provides that. 1.255 + return atob(s); 1.256 +} 1.257 + 1.258 +SpecialPowers.pushPrefEnv({"set": 1.259 + [ 1.260 + ["dom.mozBrowserFramesEnabled", true], 1.261 + ["dom.identity.enabled", true], 1.262 + ["identity.fxaccounts.enabled", true], 1.263 + ["toolkit.identity.debug", true], 1.264 + ["dom.identity.syntheticEventsOk", true], 1.265 + 1.266 + ["security.apps.privileged.CSP.default", ""], 1.267 + ["security.apps.certified.CSP.default", ""], 1.268 + ]}, 1.269 + function() { 1.270 + testRunner.next(); 1.271 + } 1.272 +); 1.273 + 1.274 + 1.275 +</script> 1.276 +</pre> 1.277 +</body> 1.278 +</html>