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 apps -- bug 768029</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=768029">Mozilla Bug 768029</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 768029 **/ |
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 'self'; style-src 'self' 'unsafe-inline'; object-src 'none'"; |
michael@0 | 29 | const DEFAULT_CSP_CERT = "default-src *; script-src 'self'; style-src 'self'; object-src 'none'"; |
michael@0 | 30 | |
michael@0 | 31 | SimpleTest.waitForExplicitFinish(); |
michael@0 | 32 | |
michael@0 | 33 | var gData = [ |
michael@0 | 34 | { |
michael@0 | 35 | app: "https://example.com/manifest.webapp", |
michael@0 | 36 | appStatus: Components.interfaces.nsIPrincipal.APP_STATUS_INSTALLED, |
michael@0 | 37 | origin: "https://example.com", |
michael@0 | 38 | uri: "https://example.com/tests/content/base/test/csp/file_csp_bug768029.html", |
michael@0 | 39 | statusString: "installed app", |
michael@0 | 40 | expectedTestResults: { |
michael@0 | 41 | max_tests: 7, /* number of bools below plus one for the status check */ |
michael@0 | 42 | cross_origin: { img: true, script: true, style: true }, |
michael@0 | 43 | same_origin: { img: true, script: true, style: true }, |
michael@0 | 44 | }, |
michael@0 | 45 | }, |
michael@0 | 46 | |
michael@0 | 47 | { |
michael@0 | 48 | app: "https://example.com/manifest_priv.webapp", |
michael@0 | 49 | appStatus: Components.interfaces.nsIPrincipal.APP_STATUS_PRIVILEGED, |
michael@0 | 50 | origin: "https://example.com", |
michael@0 | 51 | uri: "https://example.com/tests/content/base/test/csp/file_csp_bug768029.html", |
michael@0 | 52 | statusString: "privileged app", |
michael@0 | 53 | expectedTestResults: { |
michael@0 | 54 | max_tests: 7, /* number of bools below plus one for the status check */ |
michael@0 | 55 | cross_origin: { img: true, script: false, style: false }, |
michael@0 | 56 | same_origin: { img: true, script: true, style: true }, |
michael@0 | 57 | }, |
michael@0 | 58 | }, |
michael@0 | 59 | |
michael@0 | 60 | { |
michael@0 | 61 | app: "https://example.com/manifest_cert.webapp", |
michael@0 | 62 | appStatus: Components.interfaces.nsIPrincipal.APP_STATUS_CERTIFIED, |
michael@0 | 63 | origin: "https://example.com", |
michael@0 | 64 | uri: "https://example.com/tests/content/base/test/csp/file_csp_bug768029.html", |
michael@0 | 65 | statusString: "certified app", |
michael@0 | 66 | expectedTestResults: { |
michael@0 | 67 | max_tests: 7, /* number of bools below plus one for the status check */ |
michael@0 | 68 | cross_origin: { img: true, script: false, style: false }, |
michael@0 | 69 | same_origin: { img: true, script: true, style: true }, |
michael@0 | 70 | }, |
michael@0 | 71 | }, |
michael@0 | 72 | ]; |
michael@0 | 73 | |
michael@0 | 74 | // Observer for watching allowed loads and blocked attempts |
michael@0 | 75 | function ThingyListener(app, iframe) { |
michael@0 | 76 | Services.obs.addObserver(this, "csp-on-violate-policy", false); |
michael@0 | 77 | Services.obs.addObserver(this, "http-on-modify-request", false); |
michael@0 | 78 | dump("added observers\n"); |
michael@0 | 79 | // keep track of which app ID this test is monitoring. |
michael@0 | 80 | this._testData = app; |
michael@0 | 81 | this._expectedResults = app.expectedTestResults; |
michael@0 | 82 | this._resultsRecorded = { cross_origin: {}, same_origin: {}}; |
michael@0 | 83 | this._iframe = iframe; |
michael@0 | 84 | this._countedTests = 0; |
michael@0 | 85 | } |
michael@0 | 86 | ThingyListener.prototype = { |
michael@0 | 87 | |
michael@0 | 88 | observe: function(subject, topic, data) { |
michael@0 | 89 | // make sure to only observe app-generated calls to the helper for this test. |
michael@0 | 90 | var testpat = new RegExp("file_csp_bug768029\\.sjs"); |
michael@0 | 91 | |
michael@0 | 92 | // used to extract which kind of load this is (img, script, etc). |
michael@0 | 93 | var typepat = new RegExp("type=([\\_a-z0-9]+)"); |
michael@0 | 94 | |
michael@0 | 95 | // used to identify whether it's cross-origin or same-origin loads |
michael@0 | 96 | // (the applied CSP allows same-origin loads). |
michael@0 | 97 | var originpat = new RegExp("origin=([\\_a-z0-9]+)"); |
michael@0 | 98 | |
michael@0 | 99 | if (topic === "http-on-modify-request") { |
michael@0 | 100 | // Matching requests on this topic were allowed by the csp |
michael@0 | 101 | var chan = subject.QueryInterface(Components.interfaces.nsIHttpChannel); |
michael@0 | 102 | var uri = chan.URI; |
michael@0 | 103 | // ignore irrelevent URIs |
michael@0 | 104 | if (!testpat.test(uri.asciiSpec)) return; |
michael@0 | 105 | |
michael@0 | 106 | var loadType = typepat.exec(uri.asciiSpec)[1]; |
michael@0 | 107 | var originType = originpat.exec(uri.asciiSpec)[1]; |
michael@0 | 108 | |
michael@0 | 109 | // skip duplicate hits to this topic (potentially document loads |
michael@0 | 110 | // may generate duplicate loads. |
michael@0 | 111 | if (this._resultsRecorded[originType] && |
michael@0 | 112 | this._resultsRecorded[originType][loadType]) { |
michael@0 | 113 | return; |
michael@0 | 114 | } |
michael@0 | 115 | var message = originType + " : " + loadType + " should be " + |
michael@0 | 116 | (this._expectedResults[originType][loadType] ? "allowed" : "blocked"); |
michael@0 | 117 | ok(this._expectedResults[originType][loadType] == true, message); |
michael@0 | 118 | this._resultsRecorded[originType][loadType] = true; |
michael@0 | 119 | this._countedTests++; |
michael@0 | 120 | } |
michael@0 | 121 | else if (topic === "csp-on-violate-policy") { |
michael@0 | 122 | // Matching hits on this topic were blocked by the csp |
michael@0 | 123 | var uri = subject.QueryInterface(Components.interfaces.nsIURI); |
michael@0 | 124 | // ignore irrelevent URIs |
michael@0 | 125 | if (!testpat.test(uri.asciiSpec)) return; |
michael@0 | 126 | |
michael@0 | 127 | var loadType = typepat.exec(uri.asciiSpec)[1]; |
michael@0 | 128 | var originType = originpat.exec(uri.asciiSpec)[1]; |
michael@0 | 129 | |
michael@0 | 130 | // skip duplicate hits to this topic (potentially document loads |
michael@0 | 131 | // may generate duplicate loads. |
michael@0 | 132 | if (this._resultsRecorded[originType] && |
michael@0 | 133 | this._resultsRecorded[originType][loadType]) { |
michael@0 | 134 | return; |
michael@0 | 135 | } |
michael@0 | 136 | |
michael@0 | 137 | var message = originType + " : " + loadType + " should be " + |
michael@0 | 138 | (this._expectedResults[originType][loadType] ? "allowed" : "blocked"); |
michael@0 | 139 | ok(this._expectedResults[originType][loadType] == false, message); |
michael@0 | 140 | this._resultsRecorded[originType][loadType] = true; |
michael@0 | 141 | this._countedTests++; |
michael@0 | 142 | } |
michael@0 | 143 | else { |
michael@0 | 144 | // wrong topic! Nothing to do. |
michael@0 | 145 | return; |
michael@0 | 146 | } |
michael@0 | 147 | |
michael@0 | 148 | this._checkForFinish(); |
michael@0 | 149 | }, |
michael@0 | 150 | |
michael@0 | 151 | _checkForFinish: function() { |
michael@0 | 152 | // check to see if there are load tests still pending. |
michael@0 | 153 | // (All requests triggered by the app should hit one of the |
michael@0 | 154 | // two observer topics.) |
michael@0 | 155 | if (this._countedTests == this._expectedResults.max_tests) { |
michael@0 | 156 | Services.obs.removeObserver(this, "csp-on-violate-policy"); |
michael@0 | 157 | Services.obs.removeObserver(this, "http-on-modify-request"); |
michael@0 | 158 | dump("removed observers\n"); |
michael@0 | 159 | checkedCount++; |
michael@0 | 160 | if (checkedCount == checksTodo) { |
michael@0 | 161 | SpecialPowers.removePermission("browser", "https://example.com"); |
michael@0 | 162 | SimpleTest.finish(); |
michael@0 | 163 | } else { |
michael@0 | 164 | gTestRunner.next(); |
michael@0 | 165 | } |
michael@0 | 166 | } |
michael@0 | 167 | }, |
michael@0 | 168 | |
michael@0 | 169 | // verify the status of the app |
michael@0 | 170 | checkAppStatus: function() { |
michael@0 | 171 | var principal = this._iframe.contentDocument.nodePrincipal; |
michael@0 | 172 | if (this._testData.app) { |
michael@0 | 173 | is(principal.appStatus, this._testData.appStatus, |
michael@0 | 174 | "iframe principal's app status doesn't match the expected app status."); |
michael@0 | 175 | this._countedTests++; |
michael@0 | 176 | this._checkForFinish(); |
michael@0 | 177 | } |
michael@0 | 178 | } |
michael@0 | 179 | } |
michael@0 | 180 | |
michael@0 | 181 | var content = document.getElementById('content'); |
michael@0 | 182 | var checkedCount = 0; // number of apps checked |
michael@0 | 183 | var checksTodo = gData.length; |
michael@0 | 184 | |
michael@0 | 185 | // quick check to make sure we can test apps: |
michael@0 | 186 | is('appStatus' in document.nodePrincipal, true, |
michael@0 | 187 | 'appStatus should be present in nsIPrincipal, if not the rest of this test will fail'); |
michael@0 | 188 | |
michael@0 | 189 | function runTest() { |
michael@0 | 190 | for (var i = 0; i < gData.length; i++) { |
michael@0 | 191 | let data = gData[i]; |
michael@0 | 192 | var iframe = document.createElement('iframe'); |
michael@0 | 193 | |
michael@0 | 194 | // watch for successes and failures |
michael@0 | 195 | var examiner = new ThingyListener(data, iframe); |
michael@0 | 196 | |
michael@0 | 197 | iframe.setAttribute('mozapp', data.app); |
michael@0 | 198 | iframe.setAttribute('mozbrowser', 'true'); |
michael@0 | 199 | iframe.addEventListener('load', examiner.checkAppStatus.bind(examiner)); |
michael@0 | 200 | iframe.src = data.uri; |
michael@0 | 201 | |
michael@0 | 202 | content.appendChild(iframe); |
michael@0 | 203 | |
michael@0 | 204 | yield undefined; |
michael@0 | 205 | } |
michael@0 | 206 | } |
michael@0 | 207 | |
michael@0 | 208 | var gTestRunner = runTest(); |
michael@0 | 209 | |
michael@0 | 210 | // load the default CSP and pref it on |
michael@0 | 211 | SpecialPowers.addPermission("browser", true, "https://example.com"); |
michael@0 | 212 | SpecialPowers.pushPrefEnv({'set': [["dom.mozBrowserFramesEnabled", true], |
michael@0 | 213 | ["security.apps.privileged.CSP.default", DEFAULT_CSP_PRIV], |
michael@0 | 214 | ["security.apps.certified.CSP.default", DEFAULT_CSP_CERT], |
michael@0 | 215 | ["security.mixed_content.block_active_content", false], |
michael@0 | 216 | ["security.mixed_content.block_display_content", false]]}, |
michael@0 | 217 | function() { gTestRunner.next(); }); |
michael@0 | 218 | |
michael@0 | 219 | |
michael@0 | 220 | </script> |
michael@0 | 221 | </pre> |
michael@0 | 222 | </body> |
michael@0 | 223 | </html> |