From b20174bdad33f619054db08e320e5e546e2834b1 Mon Sep 17 00:00:00 2001 From: Lukas Reschke Date: Mon, 9 Feb 2015 16:30:01 +0100 Subject: [PATCH] Allow AppFramework applications to specify a custom CSP header This change allows AppFramework applications to specify a custom CSP header for example when the default policy is too strict. Furthermore this allows us to partially migrate away from CSS and allowed eval() in our JavaScript components. Legacy ownCloud components will still use the previous policy. Application developers can use this as following in their controllers: ```php $response = new TemplateResponse('activity', 'list', []); $cspHelper = new ContentSecurityPolicyHelper(); $cspHelper->addAllowedScriptDomain('www.owncloud.org'); $response->addHeader('Content-Security-Policy', $cspHelper->getPolicy()); return $response; ``` Fixes https://github.com/owncloud/core/issues/11857 which is a pre-requisite for https://github.com/owncloud/core/issues/13458 and https://github.com/owncloud/core/issues/11925 --- config/config.sample.php | 9 - lib/private/response.php | 15 +- .../http/contentsecuritypolicy.php | 241 ++++++++++++++++++ lib/public/appframework/http/response.php | 30 ++- .../controller/ControllerTest.php | 7 +- .../http/ContentSecurityPolicyTest.php | 215 ++++++++++++++++ .../appframework/http/DataResponseTest.php | 5 +- tests/lib/appframework/http/ResponseTest.php | 29 ++- 8 files changed, 529 insertions(+), 22 deletions(-) create mode 100644 lib/public/appframework/http/contentsecuritypolicy.php create mode 100644 tests/lib/appframework/http/ContentSecurityPolicyTest.php diff --git a/config/config.sample.php b/config/config.sample.php index 090e8f1f9fa..061f368b20e 100644 --- a/config/config.sample.php +++ b/config/config.sample.php @@ -929,15 +929,6 @@ $CONFIG = array( 'mssql' ), -/** - * Custom CSP policy, changing this will overwrite the standard policy - */ -'custom_csp_policy' => - "default-src 'self'; script-src 'self' 'unsafe-eval'; ". - "style-src 'self' 'unsafe-inline'; frame-src *; img-src *; ". - "font-src 'self' data:; media-src *; connect-src *", - - /** * All other config options */ diff --git a/lib/private/response.php b/lib/private/response.php index cf18115111a..8e4a7d309b0 100644 --- a/lib/private/response.php +++ b/lib/private/response.php @@ -188,7 +188,7 @@ class OC_Response { } } - /* + /** * This function adds some security related headers to all requests served via base.php * The implementation of this function has to happen here to ensure that all third-party * components (e.g. SabreDAV) also benefit from this headers. @@ -203,17 +203,20 @@ class OC_Response { header('X-Frame-Options: Sameorigin'); // Disallow iFraming from other domains } - // Content Security Policy - // If you change the standard policy, please also change it in config.sample.php - $policy = OC_Config::getValue('custom_csp_policy', - 'default-src \'self\'; ' + /** + * FIXME: Content Security Policy for legacy ownCloud components. This + * can be removed once \OCP\AppFramework\Http\Response from the AppFramework + * is used everywhere. + * @see \OCP\AppFramework\Http\Response::getHeaders + */ + $policy = 'default-src \'self\'; ' . 'script-src \'self\' \'unsafe-eval\'; ' . 'style-src \'self\' \'unsafe-inline\'; ' . 'frame-src *; ' . 'img-src *; ' . 'font-src \'self\' data:; ' . 'media-src *; ' - . 'connect-src *'); + . 'connect-src *'; header('Content-Security-Policy:' . $policy); // https://developers.google.com/webmasters/control-crawl-index/docs/robots_meta_tag diff --git a/lib/public/appframework/http/contentsecuritypolicy.php b/lib/public/appframework/http/contentsecuritypolicy.php new file mode 100644 index 00000000000..8132237a228 --- /dev/null +++ b/lib/public/appframework/http/contentsecuritypolicy.php @@ -0,0 +1,241 @@ +inlineScriptAllowed = $state; + return $this; + } + + /** + * Whether eval in JavaScript is allowed or forbidden + * @param bool $state + * @return $this + */ + public function evalScriptState($state = true) { + $this->evalScriptAllowed= $state; + return $this; + } + + /** + * Allows to execute JavaScript files from a specific domain. Use * to + * allow JavaScript from all domains. + * @param string $domain Domain to whitelist. Any passed value needs to be properly sanitized. + * @return $this + */ + public function addAllowedScriptDomain($domain) { + $this->allowedScriptDomains[] = $domain; + return $this; + } + + /** + * Whether inline CSS snippets are allowed or forbidden + * @param bool $state + * @return $this + */ + public function allowInlineStyle($state = true) { + $this->inlineStyleAllowed = $state; + return $this; + } + + /** + * Allows to execute CSS files from a specific domain. Use * to allow + * CSS from all domains. + * @param string $domain Domain to whitelist. Any passed value needs to be properly sanitized. + * @return $this + */ + public function addAllowedStyleDomain($domain) { + $this->allowedStyleDomains[] = $domain; + return $this; + } + + /** + * Allows using fonts from a specific domain. Use * to allow + * fonts from all domains. + * @param string $domain Domain to whitelist. Any passed value needs to be properly sanitized. + * @return $this + */ + public function addAllowedFontDomain($domain) { + $this->allowedFontDomains[] = $domain; + return $this; + } + + /** + * Allows embedding images from a specific domain. Use * to allow + * images from all domains. + * @param string $domain Domain to whitelist. Any passed value needs to be properly sanitized. + * @return $this + */ + public function addAllowedImageDomain($domain) { + $this->allowedImageDomains[] = $domain; + return $this; + } + + /** + * To which remote domains the JS connect to. + * @param string $domain Domain to whitelist. Any passed value needs to be properly sanitized. + * @return $this + */ + public function addAllowedConnectDomain($domain) { + $this->allowedConnectDomains[] = $domain; + return $this; + } + + /** + * From whoch domains media elements can be embedded. + * @param string $domain Domain to whitelist. Any passed value needs to be properly sanitized. + * @return $this + */ + public function addAllowedMediaDomain($domain) { + $this->allowedMediaDomains[] = $domain; + return $this; + } + + /** + * From which domains objects such as , or are executed + * @param string $domain Domain to whitelist. Any passed value needs to be properly sanitized. + * @return $this + */ + public function addAllowedObjectDomain($domain) { + $this->allowedObjectDomains[] = $domain; + return $this; + } + + /** + * Which domains can be embedded in an iframe + * @param string $domain Domain to whitelist. Any passed value needs to be properly sanitized. + * @return $this + */ + public function addAllowedFrameDomain($domain) { + $this->allowedFrameDomains[] = $domain; + return $this; + } + + /** + * Get the generated Content-Security-Policy as a string + * @return string + */ + public function buildPolicy() { + $policy = "default-src 'none';"; + + if(!empty($this->allowedScriptDomains)) { + $policy .= 'script-src ' . implode(' ', $this->allowedScriptDomains); + if($this->inlineScriptAllowed) { + $policy .= ' \'unsafe-inline\''; + } + if($this->evalScriptAllowed) { + $policy .= ' \'unsafe-eval\''; + } + $policy .= ';'; + } + + if(!empty($this->allowedStyleDomains)) { + $policy .= 'style-src ' . implode(' ', $this->allowedStyleDomains); + if($this->inlineStyleAllowed) { + $policy .= ' \'unsafe-inline\''; + } + $policy .= ';'; + } + + if(!empty($this->allowedImageDomains)) { + $policy .= 'img-src ' . implode(' ', $this->allowedImageDomains); + $policy .= ';'; + } + + if(!empty($this->allowedFontDomains)) { + $policy .= 'font-src ' . implode(' ', $this->allowedFontDomains); + $policy .= ';'; + } + + if(!empty($this->allowedConnectDomains)) { + $policy .= 'connect-src ' . implode(' ', $this->allowedConnectDomains); + $policy .= ';'; + } + + if(!empty($this->allowedMediaDomains)) { + $policy .= 'media-src ' . implode(' ', $this->allowedMediaDomains); + $policy .= ';'; + } + + if(!empty($this->allowedObjectDomains)) { + $policy .= 'object-src ' . implode(' ', $this->allowedObjectDomains); + $policy .= ';'; + } + + if(!empty($this->allowedFrameDomains)) { + $policy .= 'frame-src ' . implode(' ', $this->allowedFrameDomains); + $policy .= ';'; + } + + return rtrim($policy, ';'); + } +} diff --git a/lib/public/appframework/http/response.php b/lib/public/appframework/http/response.php index 67e72cff6d9..751c48b4ca9 100644 --- a/lib/public/appframework/http/response.php +++ b/lib/public/appframework/http/response.php @@ -72,6 +72,9 @@ class Response { */ private $ETag; + /** @var ContentSecurityPolicy|null Used Content-Security-Policy */ + private $contentSecurityPolicy = null; + /** * Caches the response @@ -186,13 +189,19 @@ class Response { * @return array the headers */ public function getHeaders() { - $mergeWith = array(); + $mergeWith = []; if($this->lastModified) { $mergeWith['Last-Modified'] = $this->lastModified->format(\DateTime::RFC2822); } + // Build Content-Security-Policy and use default if none has been specified + if(is_null($this->contentSecurityPolicy)) { + $this->setContentSecurityPolicy(new ContentSecurityPolicy()); + } + $this->headers['Content-Security-Policy'] = $this->contentSecurityPolicy->buildPolicy(); + if($this->ETag) { $mergeWith['ETag'] = '"' . $this->ETag . '"'; } @@ -221,6 +230,25 @@ class Response { return $this; } + /** + * Set a Content-Security-Policy + * @param ContentSecurityPolicy $csp Policy to set for the response object + * @return $this + */ + public function setContentSecurityPolicy(ContentSecurityPolicy $csp) { + $this->contentSecurityPolicy = $csp; + return $this; + } + + /** + * Get the currently used Content-Security-Policy + * @return ContentSecurityPolicy|null Used Content-Security-Policy or null if + * none specified. + */ + public function getContentSecurityPolicy() { + return $this->contentSecurityPolicy; + } + /** * Get response status diff --git a/tests/lib/appframework/controller/ControllerTest.php b/tests/lib/appframework/controller/ControllerTest.php index 58395d05914..3bf63d714a0 100644 --- a/tests/lib/appframework/controller/ControllerTest.php +++ b/tests/lib/appframework/controller/ControllerTest.php @@ -172,11 +172,12 @@ class ControllerTest extends \Test\TestCase { public function testFormatDataResponseJSON() { - $expectedHeaders = array( + $expectedHeaders = [ 'test' => 'something', 'Cache-Control' => 'no-cache, must-revalidate', - 'Content-Type' => 'application/json; charset=utf-8' - ); + 'Content-Type' => 'application/json; charset=utf-8', + 'Content-Security-Policy' => "default-src 'none';script-src 'self' 'unsafe-eval';style-src 'self' 'unsafe-inline';img-src 'self';font-src 'self';connect-src 'self';media-src 'self'", + ]; $response = $this->controller->customDataResponse(array('hi')); $response = $this->controller->buildResponse($response, 'json'); diff --git a/tests/lib/appframework/http/ContentSecurityPolicyTest.php b/tests/lib/appframework/http/ContentSecurityPolicyTest.php new file mode 100644 index 00000000000..3b30bba33d4 --- /dev/null +++ b/tests/lib/appframework/http/ContentSecurityPolicyTest.php @@ -0,0 +1,215 @@ +contentSecurityPolicy = new ContentSecurityPolicy(); + } + + public function testGetPolicyDefault() { + $defaultPolicy = "default-src 'none';script-src 'self' 'unsafe-eval';style-src 'self' 'unsafe-inline';img-src 'self';font-src 'self';connect-src 'self';media-src 'self'"; + $this->assertSame($defaultPolicy, $this->contentSecurityPolicy->buildPolicy()); + } + + public function testGetPolicyScriptDomainValid() { + $expectedPolicy = "default-src 'none';script-src 'self' www.owncloud.com 'unsafe-eval';style-src 'self' 'unsafe-inline';img-src 'self';font-src 'self';connect-src 'self';media-src 'self'"; + + $this->contentSecurityPolicy->addAllowedScriptDomain('www.owncloud.com'); + $this->assertSame($expectedPolicy, $this->contentSecurityPolicy->buildPolicy()); + } + + public function testGetPolicyScriptDomainValidMultiple() { + $expectedPolicy = "default-src 'none';script-src 'self' www.owncloud.com www.owncloud.org 'unsafe-eval';style-src 'self' 'unsafe-inline';img-src 'self';font-src 'self';connect-src 'self';media-src 'self'"; + + $this->contentSecurityPolicy->addAllowedScriptDomain('www.owncloud.com'); + $this->contentSecurityPolicy->addAllowedScriptDomain('www.owncloud.org'); + $this->assertSame($expectedPolicy, $this->contentSecurityPolicy->buildPolicy()); + } + + public function testGetPolicyScriptAllowInline() { + $expectedPolicy = "default-src 'none';script-src 'self' 'unsafe-inline' 'unsafe-eval';style-src 'self' 'unsafe-inline';img-src 'self';font-src 'self';connect-src 'self';media-src 'self'"; + + $this->contentSecurityPolicy->allowInlineScript(true); + $this->assertSame($expectedPolicy, $this->contentSecurityPolicy->buildPolicy()); + } + + public function testGetPolicyScriptAllowInlineWithDomain() { + $expectedPolicy = "default-src 'none';script-src 'self' www.owncloud.com 'unsafe-inline' 'unsafe-eval';style-src 'self' 'unsafe-inline';img-src 'self';font-src 'self';connect-src 'self';media-src 'self'"; + + $this->contentSecurityPolicy->addAllowedScriptDomain('www.owncloud.com'); + $this->contentSecurityPolicy->allowInlineScript(true); + $this->assertSame($expectedPolicy, $this->contentSecurityPolicy->buildPolicy()); + } + + public function testGetPolicyScriptDisallowInlineAndEval() { + $expectedPolicy = "default-src 'none';script-src 'self';style-src 'self' 'unsafe-inline';img-src 'self';font-src 'self';connect-src 'self';media-src 'self'"; + + $this->contentSecurityPolicy->allowInlineScript(false); + $this->contentSecurityPolicy->evalScriptState(false); + $this->assertSame($expectedPolicy, $this->contentSecurityPolicy->buildPolicy()); + } + + public function testGetPolicyStyleDomainValid() { + $expectedPolicy = "default-src 'none';script-src 'self' 'unsafe-eval';style-src 'self' www.owncloud.com 'unsafe-inline';img-src 'self';font-src 'self';connect-src 'self';media-src 'self'"; + + $this->contentSecurityPolicy->addAllowedStyleDomain('www.owncloud.com'); + $this->assertSame($expectedPolicy, $this->contentSecurityPolicy->buildPolicy()); + } + + public function testGetPolicyStyleDomainValidMultiple() { + $expectedPolicy = "default-src 'none';script-src 'self' 'unsafe-eval';style-src 'self' www.owncloud.com www.owncloud.org 'unsafe-inline';img-src 'self';font-src 'self';connect-src 'self';media-src 'self'"; + + $this->contentSecurityPolicy->addAllowedStyleDomain('www.owncloud.com'); + $this->contentSecurityPolicy->addAllowedStyleDomain('www.owncloud.org'); + $this->assertSame($expectedPolicy, $this->contentSecurityPolicy->buildPolicy()); + } + + public function testGetPolicyStyleAllowInline() { + $expectedPolicy = "default-src 'none';script-src 'self' 'unsafe-eval';style-src 'self' 'unsafe-inline';img-src 'self';font-src 'self';connect-src 'self';media-src 'self'"; + + $this->contentSecurityPolicy->allowInlineStyle(true); + $this->assertSame($expectedPolicy, $this->contentSecurityPolicy->buildPolicy()); + } + + public function testGetPolicyStyleAllowInlineWithDomain() { + $expectedPolicy = "default-src 'none';script-src 'self' 'unsafe-eval';style-src 'self' www.owncloud.com 'unsafe-inline';img-src 'self';font-src 'self';connect-src 'self';media-src 'self'"; + + $this->contentSecurityPolicy->addAllowedStyleDomain('www.owncloud.com'); + $this->assertSame($expectedPolicy, $this->contentSecurityPolicy->buildPolicy()); + } + + public function testGetPolicyStyleDisallowInline() { + $expectedPolicy = "default-src 'none';script-src 'self' 'unsafe-eval';style-src 'self';img-src 'self';font-src 'self';connect-src 'self';media-src 'self'"; + + $this->contentSecurityPolicy->allowInlineStyle(false); + $this->assertSame($expectedPolicy, $this->contentSecurityPolicy->buildPolicy()); + } + + public function testGetPolicyImageDomainValid() { + $expectedPolicy = "default-src 'none';script-src 'self' 'unsafe-eval';style-src 'self' 'unsafe-inline';img-src 'self' www.owncloud.com;font-src 'self';connect-src 'self';media-src 'self'"; + + $this->contentSecurityPolicy->addAllowedImageDomain('www.owncloud.com'); + $this->assertSame($expectedPolicy, $this->contentSecurityPolicy->buildPolicy()); + } + + public function testGetPolicyImageDomainValidMultiple() { + $expectedPolicy = "default-src 'none';script-src 'self' 'unsafe-eval';style-src 'self' 'unsafe-inline';img-src 'self' www.owncloud.com www.owncloud.org;font-src 'self';connect-src 'self';media-src 'self'"; + + $this->contentSecurityPolicy->addAllowedImageDomain('www.owncloud.com'); + $this->contentSecurityPolicy->addAllowedImageDomain('www.owncloud.org'); + $this->assertSame($expectedPolicy, $this->contentSecurityPolicy->buildPolicy()); + } + + public function testGetPolicyFontDomainValid() { + $expectedPolicy = "default-src 'none';script-src 'self' 'unsafe-eval';style-src 'self' 'unsafe-inline';img-src 'self';font-src 'self' www.owncloud.com;connect-src 'self';media-src 'self'"; + + $this->contentSecurityPolicy->addAllowedFontDomain('www.owncloud.com'); + $this->assertSame($expectedPolicy, $this->contentSecurityPolicy->buildPolicy()); + } + + public function testGetPolicyFontDomainValidMultiple() { + $expectedPolicy = "default-src 'none';script-src 'self' 'unsafe-eval';style-src 'self' 'unsafe-inline';img-src 'self';font-src 'self' www.owncloud.com www.owncloud.org;connect-src 'self';media-src 'self'"; + + $this->contentSecurityPolicy->addAllowedFontDomain('www.owncloud.com'); + $this->contentSecurityPolicy->addAllowedFontDomain('www.owncloud.org'); + $this->assertSame($expectedPolicy, $this->contentSecurityPolicy->buildPolicy()); + } + + public function testGetPolicyConnectDomainValid() { + $expectedPolicy = "default-src 'none';script-src 'self' 'unsafe-eval';style-src 'self' 'unsafe-inline';img-src 'self';font-src 'self';connect-src 'self' www.owncloud.com;media-src 'self'"; + + $this->contentSecurityPolicy->addAllowedConnectDomain('www.owncloud.com'); + $this->assertSame($expectedPolicy, $this->contentSecurityPolicy->buildPolicy()); + } + + public function testGetPolicyConnectDomainValidMultiple() { + $expectedPolicy = "default-src 'none';script-src 'self' 'unsafe-eval';style-src 'self' 'unsafe-inline';img-src 'self';font-src 'self';connect-src 'self' www.owncloud.com www.owncloud.org;media-src 'self'"; + + $this->contentSecurityPolicy->addAllowedConnectDomain('www.owncloud.com'); + $this->contentSecurityPolicy->addAllowedConnectDomain('www.owncloud.org'); + $this->assertSame($expectedPolicy, $this->contentSecurityPolicy->buildPolicy()); + } + + public function testGetPolicyMediaDomainValid() { + $expectedPolicy = "default-src 'none';script-src 'self' 'unsafe-eval';style-src 'self' 'unsafe-inline';img-src 'self';font-src 'self';connect-src 'self';media-src 'self' www.owncloud.com"; + + $this->contentSecurityPolicy->addAllowedMediaDomain('www.owncloud.com'); + $this->assertSame($expectedPolicy, $this->contentSecurityPolicy->buildPolicy()); + } + + public function testGetPolicyMediaDomainValidMultiple() { + $expectedPolicy = "default-src 'none';script-src 'self' 'unsafe-eval';style-src 'self' 'unsafe-inline';img-src 'self';font-src 'self';connect-src 'self';media-src 'self' www.owncloud.com www.owncloud.org"; + + $this->contentSecurityPolicy->addAllowedMediaDomain('www.owncloud.com'); + $this->contentSecurityPolicy->addAllowedMediaDomain('www.owncloud.org'); + $this->assertSame($expectedPolicy, $this->contentSecurityPolicy->buildPolicy()); + } + + public function testGetPolicyObjectDomainValid() { + $expectedPolicy = "default-src 'none';script-src 'self' 'unsafe-eval';style-src 'self' 'unsafe-inline';img-src 'self';font-src 'self';connect-src 'self';media-src 'self';object-src www.owncloud.com"; + + $this->contentSecurityPolicy->addAllowedObjectDomain('www.owncloud.com'); + $this->assertSame($expectedPolicy, $this->contentSecurityPolicy->buildPolicy()); + } + + public function testGetPolicyObjectDomainValidMultiple() { + $expectedPolicy = "default-src 'none';script-src 'self' 'unsafe-eval';style-src 'self' 'unsafe-inline';img-src 'self';font-src 'self';connect-src 'self';media-src 'self';object-src www.owncloud.com www.owncloud.org"; + + $this->contentSecurityPolicy->addAllowedObjectDomain('www.owncloud.com'); + $this->contentSecurityPolicy->addAllowedObjectDomain('www.owncloud.org'); + $this->assertSame($expectedPolicy, $this->contentSecurityPolicy->buildPolicy()); + } + + + public function testGetAllowedFrameDomain() { + $expectedPolicy = "default-src 'none';script-src 'self' 'unsafe-eval';style-src 'self' 'unsafe-inline';img-src 'self';font-src 'self';connect-src 'self';media-src 'self';frame-src www.owncloud.com"; + + $this->contentSecurityPolicy->addAllowedFrameDomain('www.owncloud.com'); + $this->assertSame($expectedPolicy, $this->contentSecurityPolicy->buildPolicy()); + } + + public function testGetPolicyFrameDomainValidMultiple() { + $expectedPolicy = "default-src 'none';script-src 'self' 'unsafe-eval';style-src 'self' 'unsafe-inline';img-src 'self';font-src 'self';connect-src 'self';media-src 'self';frame-src www.owncloud.com www.owncloud.org"; + + $this->contentSecurityPolicy->addAllowedFrameDomain('www.owncloud.com'); + $this->contentSecurityPolicy->addAllowedFrameDomain('www.owncloud.org'); + $this->assertSame($expectedPolicy, $this->contentSecurityPolicy->buildPolicy()); + } + + public function testConfigureStacked() { + $expectedPolicy = "default-src 'none';script-src 'self' script.owncloud.org;style-src 'self' style.owncloud.org;img-src 'self' img.owncloud.org;font-src 'self' font.owncloud.org;connect-src 'self' connect.owncloud.org;media-src 'self' media.owncloud.org;object-src objects.owncloud.org;frame-src frame.owncloud.org"; + + $this->contentSecurityPolicy->allowInlineStyle(false) + ->evalScriptState(false) + ->addAllowedScriptDomain('script.owncloud.org') + ->addAllowedStyleDomain('style.owncloud.org') + ->addAllowedFontDomain('font.owncloud.org') + ->addAllowedImageDomain('img.owncloud.org') + ->addAllowedConnectDomain('connect.owncloud.org') + ->addAllowedMediaDomain('media.owncloud.org') + ->addAllowedObjectDomain('objects.owncloud.org') + ->addAllowedFrameDomain('frame.owncloud.org'); + $this->assertSame($expectedPolicy, $this->contentSecurityPolicy->buildPolicy()); + } +} diff --git a/tests/lib/appframework/http/DataResponseTest.php b/tests/lib/appframework/http/DataResponseTest.php index e91d3cefea9..ca0582e10e5 100644 --- a/tests/lib/appframework/http/DataResponseTest.php +++ b/tests/lib/appframework/http/DataResponseTest.php @@ -66,7 +66,10 @@ class DataResponseTest extends \Test\TestCase { $headers = array('test' => 'something'); $response = new DataResponse($data, $code, $headers); - $expectedHeaders = array('Cache-Control' => 'no-cache, must-revalidate'); + $expectedHeaders = [ + 'Cache-Control' => 'no-cache, must-revalidate', + 'Content-Security-Policy' => "default-src 'none';script-src 'self' 'unsafe-eval';style-src 'self' 'unsafe-inline';img-src 'self';font-src 'self';connect-src 'self';media-src 'self'", + ]; $expectedHeaders = array_merge($expectedHeaders, $headers); $this->assertEquals($data, $response->getData()); diff --git a/tests/lib/appframework/http/ResponseTest.php b/tests/lib/appframework/http/ResponseTest.php index b4352348bae..77e9441b52c 100644 --- a/tests/lib/appframework/http/ResponseTest.php +++ b/tests/lib/appframework/http/ResponseTest.php @@ -49,7 +49,7 @@ class ResponseTest extends \Test\TestCase { } - function testSetHeaders(){ + public function testSetHeaders() { $expected = array( 'Last-Modified' => 1, 'ETag' => 3, @@ -58,15 +58,40 @@ class ResponseTest extends \Test\TestCase { $this->childResponse->setHeaders($expected); $headers = $this->childResponse->getHeaders(); + $expected['Content-Security-Policy'] = "default-src 'none';script-src 'self' 'unsafe-eval';style-src 'self' 'unsafe-inline';img-src 'self';font-src 'self';connect-src 'self';media-src 'self'"; $this->assertEquals($expected, $headers); } + public function testOverwriteCsp() { + $expected = [ + 'Content-Security-Policy' => "default-src 'none';script-src 'self' 'unsafe-inline' 'unsafe-eval';style-src 'self' 'unsafe-inline';img-src 'self';font-src 'self';connect-src 'self';media-src 'self'", + ]; + $policy = new Http\ContentSecurityPolicy(); + $policy->allowInlineScript(true); + + $this->childResponse->setContentSecurityPolicy($policy); + $headers = $this->childResponse->getHeaders(); + + $this->assertEquals(array_merge($expected, $headers), $headers); + } + + public function testGetCsp() { + $policy = new Http\ContentSecurityPolicy(); + $policy->allowInlineScript(true); + + $this->childResponse->setContentSecurityPolicy($policy); + $this->assertEquals($policy, $this->childResponse->getContentSecurityPolicy()); + } + + public function testGetCspEmpty() { + $this->assertNull($this->childResponse->getContentSecurityPolicy()); + } public function testAddHeaderValueNullDeletesIt(){ $this->childResponse->addHeader('hello', 'world'); $this->childResponse->addHeader('hello', null); - $this->assertEquals(1, count($this->childResponse->getHeaders())); + $this->assertEquals(2, count($this->childResponse->getHeaders())); } -- 2.39.5