3 use Wikimedia\TestingAccessWrapper
;
5 class ContentSecurityPolicyTest
extends MediaWikiTestCase
{
6 /** @var ContentSecurityPolicy */
9 protected function setUp() {
10 global $wgUploadDirectory;
11 $this->setMwGlobals( [
12 'wgAllowExternalImages' => false,
13 'wgAllowExternalImagesFrom' => [],
14 'wgAllowImageTag' => false,
15 'wgEnableImageWhitelist' => false,
16 'wgCrossSiteAJAXdomains' => [
17 'sister-site.somewhere.com',
21 'wgScriptPath' => '/w',
22 'wgForeignFileRepos' => [ [
23 'class' => ForeignAPIRepo
::class,
24 'name' => 'wikimediacommons',
25 'apibase' => 'https://commons.wikimedia.org/w/api.php',
26 'url' => 'https://upload.wikimedia.org/wikipedia/commons',
27 'thumbUrl' => 'https://upload.wikimedia.org/wikipedia/commons/thumb',
29 'transformVia404' => true,
30 'fetchDescription' => true,
31 'descriptionCacheExpiry' => 43200,
32 'apiThumbCacheExpiry' => 0,
33 'directory' => $wgUploadDirectory,
34 'backend' => 'wikimediacommons-backend',
37 // Note, there are some obscure globals which
38 // could affect the results which aren't included above.
40 RepoGroup
::destroySingleton();
41 $context = RequestContext
::getMain();
42 $resp = $context->getRequest()->response();
43 $conf = $context->getConfig();
44 $csp = new ContentSecurityPolicy( 'secret', $resp, $conf );
45 $this->csp
= TestingAccessWrapper
::newFromObject( $csp );
47 return parent
::setUp();
51 * @dataProvider providerFalsePositiveBrowser
52 * @covers ContentSecurityPolicy::falsePositiveBrowser
54 public function testFalsePositiveBrowser( $ua, $expected ) {
55 $actual = ContentSecurityPolicy
::falsePositiveBrowser( $ua );
56 $this->assertEquals( $expected, $actual, $ua );
59 public function providerFalsePositiveBrowser() {
60 // @codingStandardsIgnoreStart Generic.Files.LineLength
62 [ 'Mozilla/5.0 (X11; Linux i686; rv:41.0) Gecko/20100101 Firefox/41.0', true ],
63 [ 'Mozilla/5.0 (X11; U; Linux i686; en-ca) AppleWebKit/531.2+ (KHTML, like Gecko) Version/5.0 Safari/531.2+ Debian/squeeze (2.30.6-1) Epiphany/2.30.6', false ]
65 // @codingStandardsIgnoreEnd Generic.Files.LineLength
69 * @dataProvider providerMakeCSPDirectives
70 * @covers ContentSecurityPolicy::makeCSPDirectives
72 public function testMakeCSPDirectives(
77 $actualFull = $this->csp
->makeCSPDirectives( $policy, ContentSecurityPolicy
::FULL_MODE
);
78 $actualReport = $this->csp
->makeCSPDirectives(
79 $policy, ContentSecurityPolicy
::REPORT_ONLY_MODE
81 $policyJson = formatJson
::encode( $policy );
82 $this->assertEquals( $expectedFull, $actualFull, "full: " . $policyJson );
83 $this->assertEquals( $expectedReport, $actualReport, "report: " . $policyJson );
86 public function providerMakeCSPDirectives() {
87 // @codingStandardsIgnoreStart Generic.Files.LineLength
91 [ 'useNonces' => false ],
92 "script-src 'unsafe-eval' 'self' 'unsafe-inline' sister-site.somewhere.com *.wikipedia.org; default-src * data: blob:; style-src * data: blob: 'unsafe-inline'; report-uri /w/api.php?action=cspreport&format=json&",
93 "script-src 'unsafe-eval' 'self' 'unsafe-inline' sister-site.somewhere.com *.wikipedia.org; default-src * data: blob:; style-src * data: blob: 'unsafe-inline'; report-uri /w/api.php?action=cspreport&format=json&reportonly=1&",
94 "script-src 'unsafe-eval' 'self' sister-site.somewhere.com *.wikipedia.org; default-src * data: blob:; style-src * data: blob: 'unsafe-inline'"
98 "script-src 'unsafe-eval' 'self' 'nonce-secret' 'unsafe-inline' sister-site.somewhere.com *.wikipedia.org; default-src * data: blob:; style-src * data: blob: 'unsafe-inline'; report-uri /w/api.php?action=cspreport&format=json&",
99 "script-src 'unsafe-eval' 'self' 'nonce-secret' 'unsafe-inline' sister-site.somewhere.com *.wikipedia.org; default-src * data: blob:; style-src * data: blob: 'unsafe-inline'; report-uri /w/api.php?action=cspreport&format=json&reportonly=1&",
103 "script-src 'unsafe-eval' 'self' 'nonce-secret' 'unsafe-inline' sister-site.somewhere.com *.wikipedia.org; default-src * data: blob:; style-src * data: blob: 'unsafe-inline'; report-uri /w/api.php?action=cspreport&format=json&",
104 "script-src 'unsafe-eval' 'self' 'nonce-secret' 'unsafe-inline' sister-site.somewhere.com *.wikipedia.org; default-src * data: blob:; style-src * data: blob: 'unsafe-inline'; report-uri /w/api.php?action=cspreport&format=json&reportonly=1&",
107 [ 'script-src' => [ 'http://example.com', 'http://something,else.com' ] ],
108 "script-src 'unsafe-eval' 'self' 'nonce-secret' http://example.com http://something%2Celse.com 'unsafe-inline' sister-site.somewhere.com *.wikipedia.org; default-src * data: blob:; style-src * data: blob: 'unsafe-inline'; report-uri /w/api.php?action=cspreport&format=json&",
109 "script-src 'unsafe-eval' 'self' 'nonce-secret' http://example.com http://something%2Celse.com 'unsafe-inline' sister-site.somewhere.com *.wikipedia.org; default-src * data: blob:; style-src * data: blob: 'unsafe-inline'; report-uri /w/api.php?action=cspreport&format=json&reportonly=1&",
112 [ 'unsafeFallback' => false ],
113 "script-src 'unsafe-eval' 'self' 'nonce-secret' sister-site.somewhere.com *.wikipedia.org; default-src * data: blob:; style-src * data: blob: 'unsafe-inline'; report-uri /w/api.php?action=cspreport&format=json&",
114 "script-src 'unsafe-eval' 'self' 'nonce-secret' sister-site.somewhere.com *.wikipedia.org; default-src * data: blob:; style-src * data: blob: 'unsafe-inline'; report-uri /w/api.php?action=cspreport&format=json&reportonly=1&",
117 [ 'unsafeFallback' => true ],
118 "script-src 'unsafe-eval' 'self' 'nonce-secret' 'unsafe-inline' sister-site.somewhere.com *.wikipedia.org; default-src * data: blob:; style-src * data: blob: 'unsafe-inline'; report-uri /w/api.php?action=cspreport&format=json&",
119 "script-src 'unsafe-eval' 'self' 'nonce-secret' 'unsafe-inline' sister-site.somewhere.com *.wikipedia.org; default-src * data: blob:; style-src * data: blob: 'unsafe-inline'; report-uri /w/api.php?action=cspreport&format=json&reportonly=1&",
122 [ 'default-src' => false ],
123 "script-src 'unsafe-eval' 'self' 'nonce-secret' 'unsafe-inline' sister-site.somewhere.com *.wikipedia.org; default-src * data: blob:; style-src * data: blob: 'unsafe-inline'; report-uri /w/api.php?action=cspreport&format=json&",
124 "script-src 'unsafe-eval' 'self' 'nonce-secret' 'unsafe-inline' sister-site.somewhere.com *.wikipedia.org; default-src * data: blob:; style-src * data: blob: 'unsafe-inline'; report-uri /w/api.php?action=cspreport&format=json&reportonly=1&",
127 [ 'default-src' => true ],
128 "script-src 'unsafe-eval' 'self' 'nonce-secret' 'unsafe-inline' sister-site.somewhere.com *.wikipedia.org; default-src 'self' data: blob: https://upload.wikimedia.org https://commons.wikimedia.org sister-site.somewhere.com *.wikipedia.org; style-src 'self' data: blob: https://upload.wikimedia.org https://commons.wikimedia.org sister-site.somewhere.com *.wikipedia.org 'unsafe-inline'; report-uri /w/api.php?action=cspreport&format=json&",
129 "script-src 'unsafe-eval' 'self' 'nonce-secret' 'unsafe-inline' sister-site.somewhere.com *.wikipedia.org; default-src 'self' data: blob: https://upload.wikimedia.org https://commons.wikimedia.org sister-site.somewhere.com *.wikipedia.org; style-src 'self' data: blob: https://upload.wikimedia.org https://commons.wikimedia.org sister-site.somewhere.com *.wikipedia.org 'unsafe-inline'; report-uri /w/api.php?action=cspreport&format=json&reportonly=1&",
132 [ 'default-src' => [ 'https://foo.com', 'http://bar.com', 'baz.de' ] ],
133 "script-src 'unsafe-eval' 'self' 'nonce-secret' 'unsafe-inline' sister-site.somewhere.com *.wikipedia.org; default-src 'self' data: blob: https://upload.wikimedia.org https://commons.wikimedia.org https://foo.com http://bar.com baz.de sister-site.somewhere.com *.wikipedia.org; style-src 'self' data: blob: https://upload.wikimedia.org https://commons.wikimedia.org https://foo.com http://bar.com baz.de sister-site.somewhere.com *.wikipedia.org 'unsafe-inline'; report-uri /w/api.php?action=cspreport&format=json&",
134 "script-src 'unsafe-eval' 'self' 'nonce-secret' 'unsafe-inline' sister-site.somewhere.com *.wikipedia.org; default-src 'self' data: blob: https://upload.wikimedia.org https://commons.wikimedia.org https://foo.com http://bar.com baz.de sister-site.somewhere.com *.wikipedia.org; style-src 'self' data: blob: https://upload.wikimedia.org https://commons.wikimedia.org https://foo.com http://bar.com baz.de sister-site.somewhere.com *.wikipedia.org 'unsafe-inline'; report-uri /w/api.php?action=cspreport&format=json&reportonly=1&",
137 [ 'includeCORS' => false ],
138 "script-src 'unsafe-eval' 'self' 'nonce-secret' 'unsafe-inline'; default-src * data: blob:; style-src * data: blob: 'unsafe-inline'; report-uri /w/api.php?action=cspreport&format=json&",
139 "script-src 'unsafe-eval' 'self' 'nonce-secret' 'unsafe-inline'; default-src * data: blob:; style-src * data: blob: 'unsafe-inline'; report-uri /w/api.php?action=cspreport&format=json&reportonly=1&",
142 [ 'includeCORS' => false, 'default-src' => true ],
143 "script-src 'unsafe-eval' 'self' 'nonce-secret' 'unsafe-inline'; default-src 'self' data: blob: https://upload.wikimedia.org https://commons.wikimedia.org; style-src 'self' data: blob: https://upload.wikimedia.org https://commons.wikimedia.org 'unsafe-inline'; report-uri /w/api.php?action=cspreport&format=json&",
144 "script-src 'unsafe-eval' 'self' 'nonce-secret' 'unsafe-inline'; default-src 'self' data: blob: https://upload.wikimedia.org https://commons.wikimedia.org; style-src 'self' data: blob: https://upload.wikimedia.org https://commons.wikimedia.org 'unsafe-inline'; report-uri /w/api.php?action=cspreport&format=json&reportonly=1&",
147 [ 'includeCORS' => true ],
148 "script-src 'unsafe-eval' 'self' 'nonce-secret' 'unsafe-inline' sister-site.somewhere.com *.wikipedia.org; default-src * data: blob:; style-src * data: blob: 'unsafe-inline'; report-uri /w/api.php?action=cspreport&format=json&",
149 "script-src 'unsafe-eval' 'self' 'nonce-secret' 'unsafe-inline' sister-site.somewhere.com *.wikipedia.org; default-src * data: blob:; style-src * data: blob: 'unsafe-inline'; report-uri /w/api.php?action=cspreport&format=json&reportonly=1&",
152 [ 'report-uri' => false ],
153 "script-src 'unsafe-eval' 'self' 'nonce-secret' 'unsafe-inline' sister-site.somewhere.com *.wikipedia.org; default-src * data: blob:; style-src * data: blob: 'unsafe-inline'",
154 "script-src 'unsafe-eval' 'self' 'nonce-secret' 'unsafe-inline' sister-site.somewhere.com *.wikipedia.org; default-src * data: blob:; style-src * data: blob: 'unsafe-inline'",
157 [ 'report-uri' => true ],
158 "script-src 'unsafe-eval' 'self' 'nonce-secret' 'unsafe-inline' sister-site.somewhere.com *.wikipedia.org; default-src * data: blob:; style-src * data: blob: 'unsafe-inline'; report-uri /w/api.php?action=cspreport&format=json&",
159 "script-src 'unsafe-eval' 'self' 'nonce-secret' 'unsafe-inline' sister-site.somewhere.com *.wikipedia.org; default-src * data: blob:; style-src * data: blob: 'unsafe-inline'; report-uri /w/api.php?action=cspreport&format=json&reportonly=1&",
162 [ 'report-uri' => 'https://example.com/index.php?foo;report=csp' ],
163 "script-src 'unsafe-eval' 'self' 'nonce-secret' 'unsafe-inline' sister-site.somewhere.com *.wikipedia.org; default-src * data: blob:; style-src * data: blob: 'unsafe-inline'; report-uri https://example.com/index.php?foo%3Breport=csp",
164 "script-src 'unsafe-eval' 'self' 'nonce-secret' 'unsafe-inline' sister-site.somewhere.com *.wikipedia.org; default-src * data: blob:; style-src * data: blob: 'unsafe-inline'; report-uri https://example.com/index.php?foo%3Breport=csp",
170 * @covers ContentSecurityPolicy::makeCSPDirectives
172 public function testMakeCSPDirectivesImage() {
173 global $wgAllowImageTag;
174 $origImg = wfSetVar( $wgAllowImageTag, true );
176 $actual = $this->csp
->makeCSPDirectives( true, ContentSecurityPolicy
::FULL_MODE
);
178 $wgAllowImageTag = $origImg;
180 $expected = "script-src 'unsafe-eval' 'self' 'nonce-secret' 'unsafe-inline' sister-site.somewhere.com *.wikipedia.org; default-src * data: blob:; style-src * data: blob: 'unsafe-inline'; report-uri /w/api.php?action=cspreport&format=json&";
181 $this->assertEquals( $expected, $actual );
185 * @covers ContentSecurityPolicy::makeCSPDirectives
187 public function testMakeCSPDirectivesReportUri() {
188 $actual = $this->csp
->makeCSPDirectives(
190 ContentSecurityPolicy
::REPORT_ONLY_MODE
192 $expected = "script-src 'unsafe-eval' 'self' 'nonce-secret' 'unsafe-inline' sister-site.somewhere.com *.wikipedia.org; default-src * data: blob:; style-src * data: blob: 'unsafe-inline'; report-uri /w/api.php?action=cspreport&format=json&reportonly=1&";
193 $this->assertEquals( $expected, $actual );
194 // @codingStandardsIgnoreEnd Generic.Files.LineLength
198 * @covers ContentSecurityPolicy::getHeaderName
200 public function testGetHeaderName() {
202 $this->csp
->getHeaderName( ContentSecurityPolicy
::REPORT_ONLY_MODE
),
203 'Content-Security-Policy-Report-Only'
206 $this->csp
->getHeaderName( ContentSecurityPolicy
::FULL_MODE
),
207 'Content-Security-Policy'
212 * @covers ContentSecurityPolicy::getReportUri
214 public function testGetReportUri() {
215 $full = $this->csp
->getReportUri( ContentSecurityPolicy
::FULL_MODE
);
216 $fullExpected = '/w/api.php?action=cspreport&format=json&';
217 $this->assertEquals( $full, $fullExpected, 'normal report uri' );
219 $report = $this->csp
->getReportUri( ContentSecurityPolicy
::REPORT_ONLY_MODE
);
220 $reportExpected = $fullExpected . 'reportonly=1&';
221 $this->assertEquals( $report, $reportExpected, 'report only' );
223 global $wgScriptPath;
224 $origPath = wfSetVar( $wgScriptPath, '/tl;dr/a,%20wiki' );
225 $esc = $this->csp
->getReportUri( ContentSecurityPolicy
::FULL_MODE
);
226 $escExpected = '/tl%3Bdr/a%2C%20wiki/api.php?action=cspreport&format=json&';
227 $wgScriptPath = $origPath;
228 $this->assertEquals( $esc, $escExpected, 'test esc rules' );
232 * @dataProvider providerPrepareUrlForCSP
233 * @covers ContentSecurityPolicy::prepareUrlForCSP
235 public function testPrepareUrlForCSP( $url, $expected ) {
236 $actual = $this->csp
->prepareUrlForCSP( $url );
237 $this->assertEquals( $actual, $expected, $url );
240 public function providerPrepareUrlForCSP() {
243 [ $wgServer, false ],
244 [ 'https://example.com', 'https://example.com' ],
245 [ 'https://example.com:200', 'https://example.com:200' ],
246 [ 'http://example.com', 'http://example.com' ],
247 [ 'example.com', 'example.com' ],
248 [ '*.example.com', '*.example.com' ],
249 [ 'https://*.example.com', 'https://*.example.com' ],
250 [ '//example.com', 'example.com' ],
251 [ 'https://example.com/path', 'https://example.com' ],
252 [ 'https://example.com/path:', 'https://example.com' ],
253 [ 'https://example.com/Wikipedia:NPOV', 'https://example.com' ],
254 [ 'https://tl;dr.com', 'https://tl%3Bdr.com' ],
255 [ 'yes,no.com', 'yes%2Cno.com' ],
256 [ '/relative-url', false ],
257 [ '/relativeUrl:withColon', false ],
258 [ 'data:', 'data:' ],
259 [ 'blob:', 'blob:' ],
264 * @covers ContentSecurityPolicy::escapeUrlForCSP
266 public function testEscapeUrlForCSP() {
267 $escaped = $this->csp
->escapeUrlForCSP( ',;%2B' );
268 $this->assertEquals( $escaped, '%2C%3B%2B' );
272 * @dataProvider providerCSPIsEnabled
273 * @covers ContentSecurityPolicy::isNonceRequired
275 public function testCSPIsEnabled( $main, $reportOnly, $expected ) {
276 $this->setMwGlobals( 'wgCSPReportOnlyHeader', $reportOnly );
277 $this->setMwGlobals( 'wgCSPHeader', $main );
278 $res = ContentSecurityPolicy
::isNonceRequired( RequestContext
::getMain()->getConfig() );
279 $this->assertEquals( $res, $expected );
282 public function providerCSPIsEnabled() {
284 [ true, true, true ],
285 [ false, true, true ],
286 [ true, false, true ],
287 [ false, false, false ],
290 [ [ 'default-src' => [ 'foo.example.com' ] ], false, true ],
291 [ [ 'useNonces' => false ], [ 'useNonces' => false ], false ],
292 [ [ 'useNonces' => true ], [ 'useNonces' => false ], true ],
293 [ [ 'useNonces' => false ], [ 'useNonces' => true ], true ],