Thu, 22 Jan 2015 13:21:57 +0100
Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6
michael@0 | 1 | <!DOCTYPE HTML> |
michael@0 | 2 | <html> |
michael@0 | 3 | <!-- |
michael@0 | 4 | https://bugzilla.mozilla.org/show_bug.cgi?id=768029 |
michael@0 | 5 | --> |
michael@0 | 6 | <head> |
michael@0 | 7 | <meta charset="utf-8"> |
michael@0 | 8 | <title>Test for CSP on trusted/certified and installed apps -- bug 773891</title> |
michael@0 | 9 | <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> |
michael@0 | 10 | <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/> |
michael@0 | 11 | </head> |
michael@0 | 12 | <body> |
michael@0 | 13 | <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=773891">Mozilla Bug 773891</a> |
michael@0 | 14 | <p id="display"></p> |
michael@0 | 15 | <div id="content"> |
michael@0 | 16 | |
michael@0 | 17 | </div> |
michael@0 | 18 | <pre id="test"> |
michael@0 | 19 | <script type="application/javascript;version=1.7"> |
michael@0 | 20 | |
michael@0 | 21 | Components.utils.import("resource://gre/modules/Services.jsm"); |
michael@0 | 22 | |
michael@0 | 23 | /** Test for Bug 773891 **/ |
michael@0 | 24 | |
michael@0 | 25 | // Note: we don't have to inspect all the different operations of CSP, |
michael@0 | 26 | // we're just looking for specific differences in behavior that indicate |
michael@0 | 27 | // a default CSP got applied. |
michael@0 | 28 | const DEFAULT_CSP_PRIV = "default-src *; script-src *; style-src 'self' 'unsafe-inline'; object-src 'none'"; |
michael@0 | 29 | const DEFAULT_CSP_CERT = "default-src *; script-src *; style-src 'self'; object-src 'none'"; |
michael@0 | 30 | |
michael@0 | 31 | const MANIFEST_CSP_PRIV = "default-src *; script-src 'self'; object-src 'none'; style-src 'self' 'unsafe-inline'"; |
michael@0 | 32 | const MANIFEST_CSP_INST = "default-src *; script-src 'self'; object-src 'none'; style-src 'self' 'unsafe-inline'"; |
michael@0 | 33 | const MANIFEST_CSP_CERT = "default-src *; script-src 'self'; object-src 'none'; style-src 'self' 'unsafe-inline'"; |
michael@0 | 34 | |
michael@0 | 35 | SimpleTest.waitForExplicitFinish(); |
michael@0 | 36 | |
michael@0 | 37 | var gData = [ |
michael@0 | 38 | |
michael@0 | 39 | { |
michael@0 | 40 | app: "https://example.com/manifest_csp_inst.webapp", |
michael@0 | 41 | appStatus: Components.interfaces.nsIPrincipal.APP_STATUS_INSTALLED, |
michael@0 | 42 | csp: MANIFEST_CSP_INST, |
michael@0 | 43 | origin: "https://example.com", |
michael@0 | 44 | uri: "https://example.com/tests/content/base/test/csp/file_csp_bug773891.html", |
michael@0 | 45 | statusString: "installed app", |
michael@0 | 46 | expectedTestResults: { |
michael@0 | 47 | max_tests: 7, /* number of bools below plus one for the status check */ |
michael@0 | 48 | cross_origin: { img: true, script: false, style: false }, |
michael@0 | 49 | same_origin: { img: true, script: true, style: true }, |
michael@0 | 50 | }, |
michael@0 | 51 | }, |
michael@0 | 52 | { |
michael@0 | 53 | app: "https://example.com/manifest_csp_cert.webapp", |
michael@0 | 54 | appStatus: Components.interfaces.nsIPrincipal.APP_STATUS_CERTIFIED, |
michael@0 | 55 | csp: MANIFEST_CSP_CERT, |
michael@0 | 56 | origin: "https://example.com", |
michael@0 | 57 | uri: "https://example.com/tests/content/base/test/csp/file_csp_bug773891.html", |
michael@0 | 58 | statusString: "certified app", |
michael@0 | 59 | expectedTestResults: { |
michael@0 | 60 | max_tests: 7, /* number of bools below plus one for the status check */ |
michael@0 | 61 | cross_origin: { img: true, script: false, style: false }, |
michael@0 | 62 | same_origin: { img: true, script: true, style: true }, |
michael@0 | 63 | }, |
michael@0 | 64 | }, |
michael@0 | 65 | { |
michael@0 | 66 | app: "https://example.com/manifest_csp_priv.webapp", |
michael@0 | 67 | appStatus: Components.interfaces.nsIPrincipal.APP_STATUS_PRIVILEGED, |
michael@0 | 68 | csp: MANIFEST_CSP_PRIV, |
michael@0 | 69 | origin: "https://example.com", |
michael@0 | 70 | uri: "https://example.com/tests/content/base/test/csp/file_csp_bug773891.html", |
michael@0 | 71 | statusString: "privileged app", |
michael@0 | 72 | expectedTestResults: { |
michael@0 | 73 | max_tests: 7, /* number of bools below plus one for the status check */ |
michael@0 | 74 | cross_origin: { img: true, script: false, style: false }, |
michael@0 | 75 | same_origin: { img: true, script: true, style: true }, |
michael@0 | 76 | }, |
michael@0 | 77 | }, |
michael@0 | 78 | ]; |
michael@0 | 79 | |
michael@0 | 80 | // Observer for watching allowed loads and blocked attempts |
michael@0 | 81 | function ThingyListener(app, iframe) { |
michael@0 | 82 | Services.obs.addObserver(this, "csp-on-violate-policy", false); |
michael@0 | 83 | Services.obs.addObserver(this, "http-on-modify-request", false); |
michael@0 | 84 | dump("added observers\n"); |
michael@0 | 85 | // keep track of which app ID this test is monitoring. |
michael@0 | 86 | this._testData = app; |
michael@0 | 87 | this._expectedResults = app.expectedTestResults; |
michael@0 | 88 | this._resultsRecorded = { cross_origin: {}, same_origin: {}}; |
michael@0 | 89 | this._iframe = iframe; |
michael@0 | 90 | this._countedTests = 0; |
michael@0 | 91 | } |
michael@0 | 92 | ThingyListener.prototype = { |
michael@0 | 93 | |
michael@0 | 94 | observe: function(subject, topic, data) { |
michael@0 | 95 | // make sure to only observe app-generated calls to the helper for this test. |
michael@0 | 96 | var testpat = new RegExp("file_csp_bug773891\\.sjs"); |
michael@0 | 97 | |
michael@0 | 98 | // used to extract which kind of load this is (img, script, etc). |
michael@0 | 99 | var typepat = new RegExp("type=([\\_a-z0-9]+)"); |
michael@0 | 100 | |
michael@0 | 101 | // used to identify whether it's cross-origin or same-origin loads |
michael@0 | 102 | // (the applied CSP allows same-origin loads). |
michael@0 | 103 | var originpat = new RegExp("origin=([\\_a-z0-9]+)"); |
michael@0 | 104 | |
michael@0 | 105 | if (topic === "http-on-modify-request") { |
michael@0 | 106 | // Matching requests on this topic were allowed by the csp |
michael@0 | 107 | var chan = subject.QueryInterface(Components.interfaces.nsIHttpChannel); |
michael@0 | 108 | var uri = chan.URI; |
michael@0 | 109 | // ignore irrelevent URIs |
michael@0 | 110 | if (!testpat.test(uri.asciiSpec)) return; |
michael@0 | 111 | |
michael@0 | 112 | var loadType = typepat.exec(uri.asciiSpec)[1]; |
michael@0 | 113 | var originType = originpat.exec(uri.asciiSpec)[1]; |
michael@0 | 114 | |
michael@0 | 115 | // skip duplicate hits to this topic (potentially document loads |
michael@0 | 116 | // may generate duplicate loads. |
michael@0 | 117 | if (this._resultsRecorded[originType] && |
michael@0 | 118 | this._resultsRecorded[originType][loadType]) { |
michael@0 | 119 | return; |
michael@0 | 120 | } |
michael@0 | 121 | var message = originType + " : " + loadType + " should be " + |
michael@0 | 122 | (this._expectedResults[originType][loadType] ? "allowed" : "blocked"); |
michael@0 | 123 | ok(this._expectedResults[originType][loadType] == true, message); |
michael@0 | 124 | this._resultsRecorded[originType][loadType] = true; |
michael@0 | 125 | this._countedTests++; |
michael@0 | 126 | } |
michael@0 | 127 | else if (topic === "csp-on-violate-policy") { |
michael@0 | 128 | // Matching hits on this topic were blocked by the csp |
michael@0 | 129 | var uri = subject.QueryInterface(Components.interfaces.nsIURI); |
michael@0 | 130 | // ignore irrelevent URIs |
michael@0 | 131 | if (!testpat.test(uri.asciiSpec)) return; |
michael@0 | 132 | |
michael@0 | 133 | var loadType = typepat.exec(uri.asciiSpec)[1]; |
michael@0 | 134 | var originType = originpat.exec(uri.asciiSpec)[1]; |
michael@0 | 135 | |
michael@0 | 136 | // skip duplicate hits to this topic (potentially document loads |
michael@0 | 137 | // may generate duplicate loads. |
michael@0 | 138 | if (this._resultsRecorded[originType] && |
michael@0 | 139 | this._resultsRecorded[originType][loadType]) { |
michael@0 | 140 | return; |
michael@0 | 141 | } |
michael@0 | 142 | |
michael@0 | 143 | var message = originType + " : " + loadType + " should be " + |
michael@0 | 144 | (this._expectedResults[originType][loadType] ? "allowed" : "blocked"); |
michael@0 | 145 | ok(this._expectedResults[originType][loadType] == false, message); |
michael@0 | 146 | this._resultsRecorded[originType][loadType] = true; |
michael@0 | 147 | this._countedTests++; |
michael@0 | 148 | } |
michael@0 | 149 | else { |
michael@0 | 150 | // wrong topic! Nothing to do. |
michael@0 | 151 | return; |
michael@0 | 152 | } |
michael@0 | 153 | |
michael@0 | 154 | this._checkForFinish(); |
michael@0 | 155 | }, |
michael@0 | 156 | |
michael@0 | 157 | _checkForFinish: function() { |
michael@0 | 158 | // check to see if there are load tests still pending. |
michael@0 | 159 | // (All requests triggered by the app should hit one of the |
michael@0 | 160 | // two observer topics.) |
michael@0 | 161 | if (this._countedTests == this._expectedResults.max_tests) { |
michael@0 | 162 | Services.obs.removeObserver(this, "csp-on-violate-policy"); |
michael@0 | 163 | Services.obs.removeObserver(this, "http-on-modify-request"); |
michael@0 | 164 | dump("removed observers\n"); |
michael@0 | 165 | checkedCount++; |
michael@0 | 166 | if (checkedCount == checksTodo) { |
michael@0 | 167 | SpecialPowers.removePermission("browser", "https://example.com"); |
michael@0 | 168 | SimpleTest.finish(); |
michael@0 | 169 | } else { |
michael@0 | 170 | gTestRunner.next(); |
michael@0 | 171 | } |
michael@0 | 172 | } |
michael@0 | 173 | }, |
michael@0 | 174 | |
michael@0 | 175 | // verify the status of the app |
michael@0 | 176 | checkAppStatus: function() { |
michael@0 | 177 | var principal = this._iframe.contentDocument.nodePrincipal; |
michael@0 | 178 | if (this._testData.app) { |
michael@0 | 179 | is(principal.appStatus, this._testData.appStatus, |
michael@0 | 180 | "iframe principal's app status doesn't match the expected app status."); |
michael@0 | 181 | this._countedTests++; |
michael@0 | 182 | this._checkForFinish(); |
michael@0 | 183 | } |
michael@0 | 184 | } |
michael@0 | 185 | } |
michael@0 | 186 | |
michael@0 | 187 | var content = document.getElementById('content'); |
michael@0 | 188 | var checkedCount = 0; // number of apps checked |
michael@0 | 189 | var checksTodo = gData.length; |
michael@0 | 190 | |
michael@0 | 191 | // quick check to make sure we can test apps: |
michael@0 | 192 | is('appStatus' in document.nodePrincipal, true, |
michael@0 | 193 | 'appStatus should be present in nsIPrincipal, if not the rest of this test will fail'); |
michael@0 | 194 | |
michael@0 | 195 | function runTest() { |
michael@0 | 196 | for (var i = 0; i < gData.length; i++) { |
michael@0 | 197 | let data = gData[i]; |
michael@0 | 198 | var iframe = document.createElement('iframe'); |
michael@0 | 199 | |
michael@0 | 200 | // watch for successes and failures |
michael@0 | 201 | var examiner = new ThingyListener(data, iframe); |
michael@0 | 202 | |
michael@0 | 203 | iframe.setAttribute('mozapp', data.app); |
michael@0 | 204 | iframe.setAttribute('mozbrowser', 'true'); |
michael@0 | 205 | iframe.addEventListener('load', examiner.checkAppStatus.bind(examiner)); |
michael@0 | 206 | iframe.src = data.uri; |
michael@0 | 207 | |
michael@0 | 208 | content.appendChild(iframe); |
michael@0 | 209 | |
michael@0 | 210 | yield undefined; |
michael@0 | 211 | } |
michael@0 | 212 | } |
michael@0 | 213 | |
michael@0 | 214 | var gTestRunner = runTest(); |
michael@0 | 215 | |
michael@0 | 216 | // load the default CSP and pref it on |
michael@0 | 217 | SpecialPowers.addPermission("browser", true, "https://example.com"); |
michael@0 | 218 | |
michael@0 | 219 | SpecialPowers.pushPrefEnv({'set': [["dom.mozBrowserFramesEnabled", true], |
michael@0 | 220 | ["security.apps.privileged.CSP.default", DEFAULT_CSP_PRIV], |
michael@0 | 221 | ["security.apps.certified.CSP.default", DEFAULT_CSP_CERT]]}, |
michael@0 | 222 | function() { gTestRunner.next(); }); |
michael@0 | 223 | |
michael@0 | 224 | |
michael@0 | 225 | </script> |
michael@0 | 226 | </pre> |
michael@0 | 227 | </body> |
michael@0 | 228 | </html> |