1<?php 2 3use MediaWiki\MediaWikiServices; 4use Wikimedia\TestingAccessWrapper; 5 6class ContentSecurityPolicyTest extends MediaWikiIntegrationTestCase { 7 /** @var ContentSecurityPolicy */ 8 private $csp; 9 10 protected function setUp() : void { 11 global $wgUploadDirectory; 12 13 parent::setUp(); 14 15 $this->setMwGlobals( [ 16 'wgAllowExternalImages' => false, 17 'wgAllowExternalImagesFrom' => [], 18 'wgAllowImageTag' => false, 19 'wgEnableImageWhitelist' => false, 20 'wgLoadScript' => false, 21 'wgExtensionAssetsPath' => false, 22 'wgStylePath' => false, 23 'wgResourceBasePath' => null, 24 'wgCrossSiteAJAXdomains' => [ 25 'sister-site.somewhere.com', 26 '*.wikipedia.org', 27 '??.wikinews.org' 28 ], 29 'wgScriptPath' => '/w', 30 'wgForeignFileRepos' => [ [ 31 'class' => ForeignAPIRepo::class, 32 'name' => 'wikimediacommons', 33 'apibase' => 'https://commons.wikimedia.org/w/api.php', 34 'url' => 'https://upload.wikimedia.org/wikipedia/commons', 35 'thumbUrl' => 'https://upload.wikimedia.org/wikipedia/commons/thumb', 36 'hashLevels' => 2, 37 'transformVia404' => true, 38 'fetchDescription' => true, 39 'descriptionCacheExpiry' => 43200, 40 'apiThumbCacheExpiry' => 0, 41 'directory' => $wgUploadDirectory, 42 'backend' => 'wikimediacommons-backend', 43 ] ], 44 'wgCSPHeader' => true, // enable nonce by default 45 ] ); 46 // Note, there are some obscure globals which 47 // could affect the results which aren't included above. 48 49 $context = RequestContext::getMain(); 50 $resp = $context->getRequest()->response(); 51 $conf = $context->getConfig(); 52 $hookContainer = MediaWikiServices::getInstance()->getHookContainer(); 53 $csp = new ContentSecurityPolicy( $resp, $conf, $hookContainer ); 54 $this->csp = TestingAccessWrapper::newFromObject( $csp ); 55 $this->csp->nonce = 'secret'; 56 } 57 58 /** 59 * @covers ContentSecurityPolicy::getAdditionalSelfUrls 60 */ 61 public function testGetAdditionalSelfUrlsRespectsUrlSettings() { 62 $this->setMwGlobals( 'wgLoadScript', 'https://wgLoadScript.example.org/load.php' ); 63 $this->setMwGlobals( 'wgExtensionAssetsPath', 64 'https://wgExtensionAssetsPath.example.org/assets/' ); 65 $this->setMwGlobals( 'wgStylePath', 'https://wgStylePath.example.org/style/' ); 66 $this->setMwGlobals( 'wgResourceBasePath', 'https://wgResourceBasePath.example.org/resources/' ); 67 68 $this->assertEquals( 69 [ 70 'https://upload.wikimedia.org', 71 'https://commons.wikimedia.org', 72 'https://wgLoadScript.example.org', 73 'https://wgExtensionAssetsPath.example.org', 74 'https://wgStylePath.example.org', 75 'https://wgResourceBasePath.example.org', 76 ], 77 array_values( $this->csp->getAdditionalSelfUrls() ) 78 ); 79 } 80 81 /** 82 * @dataProvider providerFalsePositiveBrowser 83 * @covers ContentSecurityPolicy::falsePositiveBrowser 84 */ 85 public function testFalsePositiveBrowser( $ua, $expected ) { 86 $actual = ContentSecurityPolicy::falsePositiveBrowser( $ua ); 87 $this->assertSame( $expected, $actual, $ua ); 88 } 89 90 public function providerFalsePositiveBrowser() { 91 return [ 92 [ 93 'Mozilla/5.0 (X11; Linux i686; rv:41.0) Gecko/20100101 Firefox/41.0', 94 true 95 ], 96 [ 97 'Mozilla/5.0 (X11; U; Linux i686; en-ca) AppleWebKit/531.2+ (KHTML, like Gecko) ' . 98 'Version/5.0 Safari/531.2+ Debian/squeeze (2.30.6-1) Epiphany/2.30.6', 99 false 100 ], 101 ]; 102 } 103 104 /** 105 * @covers ContentSecurityPolicy::addScriptSrc 106 * @covers ContentSecurityPolicy::makeCSPDirectives 107 */ 108 public function testAddScriptSrc() { 109 $this->csp->addScriptSrc( 'https://example.com:71' ); 110 $actual = $this->csp->makeCSPDirectives( true, ContentSecurityPolicy::FULL_MODE ); 111 $expected = "script-src 'unsafe-eval' blob: 'self' 'nonce-secret' 'unsafe-inline'" . 112 " sister-site.somewhere.com *.wikipedia.org https://example.com:71; default-src *" . 113 " data: blob:; style-src * data: blob: 'unsafe-inline'; object-src 'none'; report-uri" . 114 " /w/api.php?action=cspreport&format=json"; 115 $this->assertSame( $expected, $actual ); 116 } 117 118 /** 119 * @covers ContentSecurityPolicy::addStyleSrc 120 * @covers ContentSecurityPolicy::makeCSPDirectives 121 */ 122 public function testAddStyleSrc() { 123 $this->csp->addStyleSrc( 'style.example.com' ); 124 $actual = $this->csp->makeCSPDirectives( true, ContentSecurityPolicy::REPORT_ONLY_MODE ); 125 $expected = "script-src 'unsafe-eval' blob: 'self' 'nonce-secret' 'unsafe-inline'" . 126 " sister-site.somewhere.com *.wikipedia.org; default-src * data: blob:;" . 127 " style-src * data: blob: style.example.com 'unsafe-inline'; object-src 'none'; report-uri" . 128 " /w/api.php?action=cspreport&format=json&reportonly=1"; 129 $this->assertSame( $expected, $actual ); 130 } 131 132 /** 133 * @covers ContentSecurityPolicy::addDefaultSrc 134 * @covers ContentSecurityPolicy::makeCSPDirectives 135 */ 136 public function testAddDefaultSrc() { 137 $this->csp->addDefaultSrc( '*.example.com' ); 138 $actual = $this->csp->makeCSPDirectives( true, ContentSecurityPolicy::FULL_MODE ); 139 $expected = "script-src 'unsafe-eval' blob: 'self' 'nonce-secret' 'unsafe-inline'" . 140 " sister-site.somewhere.com *.wikipedia.org; default-src * data: blob:" . 141 " *.example.com; style-src * data: blob: *.example.com 'unsafe-inline';" . 142 " object-src 'none'; report-uri /w/api.php?action=cspreport&format=json"; 143 $this->assertSame( $expected, $actual ); 144 } 145 146 /** 147 * @dataProvider providerMakeCSPDirectives 148 * @covers ContentSecurityPolicy::makeCSPDirectives 149 */ 150 public function testMakeCSPDirectives( 151 $policy, 152 $expectedFull, 153 $expectedReport 154 ) { 155 $actualFull = $this->csp->makeCSPDirectives( $policy, ContentSecurityPolicy::FULL_MODE ); 156 $actualReport = $this->csp->makeCSPDirectives( 157 $policy, ContentSecurityPolicy::REPORT_ONLY_MODE 158 ); 159 $policyJson = FormatJson::encode( $policy ); 160 $this->assertSame( $expectedFull, $actualFull, "full: " . $policyJson ); 161 $this->assertSame( $expectedReport, $actualReport, "report: " . $policyJson ); 162 } 163 164 public function providerMakeCSPDirectives() { 165 // phpcs:disable Generic.Files.LineLength 166 return [ 167 [ false, '', '' ], 168 [ 169 [ 'useNonces' => false ], 170 "script-src 'unsafe-eval' blob: 'self' 'unsafe-inline' sister-site.somewhere.com *.wikipedia.org; default-src * data: blob:; style-src * data: blob: 'unsafe-inline'; object-src 'none'; report-uri /w/api.php?action=cspreport&format=json", 171 "script-src 'unsafe-eval' blob: 'self' 'unsafe-inline' sister-site.somewhere.com *.wikipedia.org; default-src * data: blob:; style-src * data: blob: 'unsafe-inline'; object-src 'none'; report-uri /w/api.php?action=cspreport&format=json&reportonly=1", 172 "script-src 'unsafe-eval' blob: 'self' sister-site.somewhere.com *.wikipedia.org; default-src * data: blob:; style-src * data: blob: 'unsafe-inline'" 173 ], 174 [ 175 true, 176 "script-src 'unsafe-eval' blob: 'self' 'nonce-secret' 'unsafe-inline' sister-site.somewhere.com *.wikipedia.org; default-src * data: blob:; style-src * data: blob: 'unsafe-inline'; object-src 'none'; report-uri /w/api.php?action=cspreport&format=json", 177 "script-src 'unsafe-eval' blob: 'self' 'nonce-secret' 'unsafe-inline' sister-site.somewhere.com *.wikipedia.org; default-src * data: blob:; style-src * data: blob: 'unsafe-inline'; object-src 'none'; report-uri /w/api.php?action=cspreport&format=json&reportonly=1", 178 ], 179 [ 180 [], 181 "script-src 'unsafe-eval' blob: 'self' 'nonce-secret' 'unsafe-inline' sister-site.somewhere.com *.wikipedia.org; default-src * data: blob:; style-src * data: blob: 'unsafe-inline'; object-src 'none'; report-uri /w/api.php?action=cspreport&format=json", 182 "script-src 'unsafe-eval' blob: 'self' 'nonce-secret' 'unsafe-inline' sister-site.somewhere.com *.wikipedia.org; default-src * data: blob:; style-src * data: blob: 'unsafe-inline'; object-src 'none'; report-uri /w/api.php?action=cspreport&format=json&reportonly=1", 183 ], 184 [ 185 [ 'script-src' => [ 'http://example.com', 'http://something,else.com' ] ], 186 "script-src 'unsafe-eval' blob: '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'; object-src 'none'; report-uri /w/api.php?action=cspreport&format=json", 187 "script-src 'unsafe-eval' blob: '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'; object-src 'none'; report-uri /w/api.php?action=cspreport&format=json&reportonly=1", 188 ], 189 [ 190 [ 'unsafeFallback' => false ], 191 "script-src 'unsafe-eval' blob: 'self' 'nonce-secret' sister-site.somewhere.com *.wikipedia.org; default-src * data: blob:; style-src * data: blob: 'unsafe-inline'; object-src 'none'; report-uri /w/api.php?action=cspreport&format=json", 192 "script-src 'unsafe-eval' blob: 'self' 'nonce-secret' sister-site.somewhere.com *.wikipedia.org; default-src * data: blob:; style-src * data: blob: 'unsafe-inline'; object-src 'none'; report-uri /w/api.php?action=cspreport&format=json&reportonly=1", 193 ], 194 [ 195 [ 'unsafeFallback' => true ], 196 "script-src 'unsafe-eval' blob: 'self' 'nonce-secret' 'unsafe-inline' sister-site.somewhere.com *.wikipedia.org; default-src * data: blob:; style-src * data: blob: 'unsafe-inline'; object-src 'none'; report-uri /w/api.php?action=cspreport&format=json", 197 "script-src 'unsafe-eval' blob: 'self' 'nonce-secret' 'unsafe-inline' sister-site.somewhere.com *.wikipedia.org; default-src * data: blob:; style-src * data: blob: 'unsafe-inline'; object-src 'none'; report-uri /w/api.php?action=cspreport&format=json&reportonly=1", 198 ], 199 [ 200 [ 'default-src' => false ], 201 "script-src 'unsafe-eval' blob: 'self' 'nonce-secret' 'unsafe-inline' sister-site.somewhere.com *.wikipedia.org; default-src * data: blob:; style-src * data: blob: 'unsafe-inline'; object-src 'none'; report-uri /w/api.php?action=cspreport&format=json", 202 "script-src 'unsafe-eval' blob: 'self' 'nonce-secret' 'unsafe-inline' sister-site.somewhere.com *.wikipedia.org; default-src * data: blob:; style-src * data: blob: 'unsafe-inline'; object-src 'none'; report-uri /w/api.php?action=cspreport&format=json&reportonly=1", 203 ], 204 [ 205 [ 'default-src' => true ], 206 "script-src 'unsafe-eval' blob: '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'; object-src 'none'; report-uri /w/api.php?action=cspreport&format=json", 207 "script-src 'unsafe-eval' blob: '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'; object-src 'none'; report-uri /w/api.php?action=cspreport&format=json&reportonly=1", 208 ], 209 [ 210 [ 'default-src' => [ 'https://foo.com', 'http://bar.com', 'baz.de' ] ], 211 "script-src 'unsafe-eval' blob: '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'; object-src 'none'; report-uri /w/api.php?action=cspreport&format=json", 212 "script-src 'unsafe-eval' blob: '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'; object-src 'none'; report-uri /w/api.php?action=cspreport&format=json&reportonly=1", 213 ], 214 [ 215 [ 'includeCORS' => false ], 216 "script-src 'unsafe-eval' blob: 'self' 'nonce-secret' 'unsafe-inline'; default-src * data: blob:; style-src * data: blob: 'unsafe-inline'; object-src 'none'; report-uri /w/api.php?action=cspreport&format=json", 217 "script-src 'unsafe-eval' blob: 'self' 'nonce-secret' 'unsafe-inline'; default-src * data: blob:; style-src * data: blob: 'unsafe-inline'; object-src 'none'; report-uri /w/api.php?action=cspreport&format=json&reportonly=1", 218 ], 219 [ 220 [ 'includeCORS' => false, 'default-src' => true ], 221 "script-src 'unsafe-eval' blob: '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'; object-src 'none'; report-uri /w/api.php?action=cspreport&format=json", 222 "script-src 'unsafe-eval' blob: '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'; object-src 'none'; report-uri /w/api.php?action=cspreport&format=json&reportonly=1", 223 ], 224 [ 225 [ 'includeCORS' => true ], 226 "script-src 'unsafe-eval' blob: 'self' 'nonce-secret' 'unsafe-inline' sister-site.somewhere.com *.wikipedia.org; default-src * data: blob:; style-src * data: blob: 'unsafe-inline'; object-src 'none'; report-uri /w/api.php?action=cspreport&format=json", 227 "script-src 'unsafe-eval' blob: 'self' 'nonce-secret' 'unsafe-inline' sister-site.somewhere.com *.wikipedia.org; default-src * data: blob:; style-src * data: blob: 'unsafe-inline'; object-src 'none'; report-uri /w/api.php?action=cspreport&format=json&reportonly=1", 228 ], 229 [ 230 [ 'report-uri' => false ], 231 "script-src 'unsafe-eval' blob: 'self' 'nonce-secret' 'unsafe-inline' sister-site.somewhere.com *.wikipedia.org; default-src * data: blob:; style-src * data: blob: 'unsafe-inline'; object-src 'none'", 232 "script-src 'unsafe-eval' blob: 'self' 'nonce-secret' 'unsafe-inline' sister-site.somewhere.com *.wikipedia.org; default-src * data: blob:; style-src * data: blob: 'unsafe-inline'; object-src 'none'", 233 ], 234 [ 235 [ 'report-uri' => true ], 236 "script-src 'unsafe-eval' blob: 'self' 'nonce-secret' 'unsafe-inline' sister-site.somewhere.com *.wikipedia.org; default-src * data: blob:; style-src * data: blob: 'unsafe-inline'; object-src 'none'; report-uri /w/api.php?action=cspreport&format=json", 237 "script-src 'unsafe-eval' blob: 'self' 'nonce-secret' 'unsafe-inline' sister-site.somewhere.com *.wikipedia.org; default-src * data: blob:; style-src * data: blob: 'unsafe-inline'; object-src 'none'; report-uri /w/api.php?action=cspreport&format=json&reportonly=1", 238 ], 239 [ 240 [ 'report-uri' => 'https://example.com/index.php?foo;report=csp' ], 241 "script-src 'unsafe-eval' blob: 'self' 'nonce-secret' 'unsafe-inline' sister-site.somewhere.com *.wikipedia.org; default-src * data: blob:; style-src * data: blob: 'unsafe-inline'; object-src 'none'; report-uri https://example.com/index.php?foo%3Breport=csp", 242 "script-src 'unsafe-eval' blob: 'self' 'nonce-secret' 'unsafe-inline' sister-site.somewhere.com *.wikipedia.org; default-src * data: blob:; style-src * data: blob: 'unsafe-inline'; object-src 'none'; report-uri https://example.com/index.php?foo%3Breport=csp", 243 ], 244 [ 245 [ 'object-src' => false ], 246 "script-src 'unsafe-eval' blob: '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", 247 "script-src 'unsafe-eval' blob: '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", 248 ], 249 [ 250 [ 'object-src' => true ], 251 "script-src 'unsafe-eval' blob: 'self' 'nonce-secret' 'unsafe-inline' sister-site.somewhere.com *.wikipedia.org; default-src * data: blob:; style-src * data: blob: 'unsafe-inline'; object-src 'none'; report-uri /w/api.php?action=cspreport&format=json", 252 "script-src 'unsafe-eval' blob: 'self' 'nonce-secret' 'unsafe-inline' sister-site.somewhere.com *.wikipedia.org; default-src * data: blob:; style-src * data: blob: 'unsafe-inline'; object-src 'none'; report-uri /w/api.php?action=cspreport&format=json&reportonly=1", 253 ], 254 [ 255 [ 'object-src' => "'self'" ], 256 "script-src 'unsafe-eval' blob: 'self' 'nonce-secret' 'unsafe-inline' sister-site.somewhere.com *.wikipedia.org; default-src * data: blob:; style-src * data: blob: 'unsafe-inline'; object-src 'self'; report-uri /w/api.php?action=cspreport&format=json", 257 "script-src 'unsafe-eval' blob: 'self' 'nonce-secret' 'unsafe-inline' sister-site.somewhere.com *.wikipedia.org; default-src * data: blob:; style-src * data: blob: 'unsafe-inline'; object-src 'self'; report-uri /w/api.php?action=cspreport&format=json&reportonly=1", 258 ], 259 [ 260 [ 'object-src' => [ "'self'", 'https://example.com/f;d' ] ], 261 "script-src 'unsafe-eval' blob: 'self' 'nonce-secret' 'unsafe-inline' sister-site.somewhere.com *.wikipedia.org; default-src * data: blob:; style-src * data: blob: 'unsafe-inline'; object-src 'self' https://example.com/f%3Bd; report-uri /w/api.php?action=cspreport&format=json", 262 "script-src 'unsafe-eval' blob: 'self' 'nonce-secret' 'unsafe-inline' sister-site.somewhere.com *.wikipedia.org; default-src * data: blob:; style-src * data: blob: 'unsafe-inline'; object-src 'self' https://example.com/f%3Bd; report-uri /w/api.php?action=cspreport&format=json&reportonly=1", 263 ], 264 ]; 265 // phpcs:enable 266 } 267 268 /** 269 * @covers ContentSecurityPolicy::makeCSPDirectives 270 */ 271 public function testMakeCSPDirectivesImage() { 272 global $wgAllowImageTag; 273 $origImg = wfSetVar( $wgAllowImageTag, true ); 274 275 $actual = $this->csp->makeCSPDirectives( true, ContentSecurityPolicy::FULL_MODE ); 276 277 $wgAllowImageTag = $origImg; 278 279 // phpcs:ignore Generic.Files.LineLength 280 $expected = "script-src 'unsafe-eval' blob: 'self' 'nonce-secret' 'unsafe-inline' sister-site.somewhere.com *.wikipedia.org; default-src * data: blob:; style-src * data: blob: 'unsafe-inline'; object-src 'none'; report-uri /w/api.php?action=cspreport&format=json"; 281 $this->assertSame( $expected, $actual ); 282 } 283 284 /** 285 * @covers ContentSecurityPolicy::makeCSPDirectives 286 */ 287 public function testMakeCSPDirectivesReportUri() { 288 $actual = $this->csp->makeCSPDirectives( 289 true, 290 ContentSecurityPolicy::REPORT_ONLY_MODE 291 ); 292 // phpcs:ignore Generic.Files.LineLength 293 $expected = "script-src 'unsafe-eval' blob: 'self' 'nonce-secret' 'unsafe-inline' sister-site.somewhere.com *.wikipedia.org; default-src * data: blob:; style-src * data: blob: 'unsafe-inline'; object-src 'none'; report-uri /w/api.php?action=cspreport&format=json&reportonly=1"; 294 $this->assertSame( $expected, $actual ); 295 } 296 297 /** 298 * @covers ContentSecurityPolicy::getHeaderName 299 */ 300 public function testGetHeaderName() { 301 $this->assertSame( 302 'Content-Security-Policy-Report-Only', 303 $this->csp->getHeaderName( ContentSecurityPolicy::REPORT_ONLY_MODE ) 304 ); 305 $this->assertSame( 306 'Content-Security-Policy', 307 $this->csp->getHeaderName( ContentSecurityPolicy::FULL_MODE ) 308 ); 309 } 310 311 /** 312 * @covers ContentSecurityPolicy::getReportUri 313 */ 314 public function testGetReportUri() { 315 $full = $this->csp->getReportUri( ContentSecurityPolicy::FULL_MODE ); 316 $fullExpected = '/w/api.php?action=cspreport&format=json'; 317 $this->assertSame( $fullExpected, $full, 'normal report uri' ); 318 319 $report = $this->csp->getReportUri( ContentSecurityPolicy::REPORT_ONLY_MODE ); 320 $reportExpected = $fullExpected . '&reportonly=1'; 321 $this->assertSame( $reportExpected, $report, 'report only' ); 322 323 global $wgScriptPath; 324 $origPath = wfSetVar( $wgScriptPath, '/tl;dr/a,%20wiki' ); 325 $esc = $this->csp->getReportUri( ContentSecurityPolicy::FULL_MODE ); 326 $escExpected = '/tl%3Bdr/a%2C%20wiki/api.php?action=cspreport&format=json'; 327 $wgScriptPath = $origPath; 328 $this->assertSame( $escExpected, $esc, 'test esc rules' ); 329 } 330 331 /** 332 * @dataProvider providerPrepareUrlForCSP 333 * @covers ContentSecurityPolicy::prepareUrlForCSP 334 */ 335 public function testPrepareUrlForCSP( $url, $expected ) { 336 $actual = $this->csp->prepareUrlForCSP( $url ); 337 $this->assertSame( $expected, $actual, $url ); 338 } 339 340 public function providerPrepareUrlForCSP() { 341 global $wgServer; 342 return [ 343 [ $wgServer, false ], 344 [ 'https://example.com', 'https://example.com' ], 345 [ 'https://example.com:200', 'https://example.com:200' ], 346 [ 'http://example.com', 'http://example.com' ], 347 [ 'example.com', 'example.com' ], 348 [ '*.example.com', '*.example.com' ], 349 [ 'https://*.example.com', 'https://*.example.com' ], 350 [ '//example.com', 'example.com' ], 351 [ 'https://example.com/path', 'https://example.com' ], 352 [ 'https://example.com/path:', 'https://example.com' ], 353 [ 'https://example.com/Wikipedia:NPOV', 'https://example.com' ], 354 [ 'https://tl;dr.com', 'https://tl%3Bdr.com' ], 355 [ 'yes,no.com', 'yes%2Cno.com' ], 356 [ '/relative-url', false ], 357 [ '/relativeUrl:withColon', false ], 358 [ 'data:', 'data:' ], 359 [ 'blob:', 'blob:' ], 360 ]; 361 } 362 363 /** 364 * @covers ContentSecurityPolicy::escapeUrlForCSP 365 */ 366 public function testEscapeUrlForCSP() { 367 $escaped = $this->csp->escapeUrlForCSP( ',;%2B' ); 368 $this->assertSame( '%2C%3B%2B', $escaped ); 369 } 370 371 /** 372 * @dataProvider providerCSPIsEnabled 373 * @covers ContentSecurityPolicy::isNonceRequired 374 */ 375 public function testCSPIsEnabled( $main, $reportOnly, $expected ) { 376 $this->setMwGlobals( 'wgCSPReportOnlyHeader', $reportOnly ); 377 $this->setMwGlobals( 'wgCSPHeader', $main ); 378 $res = ContentSecurityPolicy::isNonceRequired( RequestContext::getMain()->getConfig() ); 379 $this->assertSame( $expected, $res ); 380 } 381 382 public function providerCSPIsEnabled() { 383 return [ 384 [ true, true, true ], 385 [ false, true, true ], 386 [ true, false, true ], 387 [ false, false, false ], 388 [ false, [], true ], 389 [ [], false, true ], 390 [ [ 'default-src' => [ 'foo.example.com' ] ], false, true ], 391 [ [ 'useNonces' => false ], [ 'useNonces' => false ], false ], 392 [ [ 'useNonces' => true ], [ 'useNonces' => false ], true ], 393 [ [ 'useNonces' => false ], [ 'useNonces' => true ], true ], 394 ]; 395 } 396} 397