diff options
author | Clark Tomlinson <fallen013@gmail.com> | 2015-02-18 10:27:29 -0500 |
---|---|---|
committer | Clark Tomlinson <fallen013@gmail.com> | 2015-02-18 10:27:29 -0500 |
commit | 8d09cc3b91a9689a6c95e06c8002288bdd8d5bbf (patch) | |
tree | 81e09b101401476c2de80460a994a34ff26b75d8 | |
parent | 84cc90a0ee81d32001ccaa38795cbcf4343ac2f0 (diff) | |
parent | a9d1a0144018e60ba2728708bf965b4d9855920b (diff) | |
download | nextcloud-server-8d09cc3b91a9689a6c95e06c8002288bdd8d5bbf.tar.gz nextcloud-server-8d09cc3b91a9689a6c95e06c8002288bdd8d5bbf.zip |
Merge pull request #13989 from owncloud/enhancment/security/11857
Allow AppFramework applications to specify a custom CSP header
-rw-r--r-- | config/config.sample.php | 9 | ||||
-rw-r--r-- | lib/private/response.php | 15 | ||||
-rw-r--r-- | lib/public/appframework/http/contentsecuritypolicy.php | 241 | ||||
-rw-r--r-- | lib/public/appframework/http/response.php | 30 | ||||
-rw-r--r-- | tests/lib/appframework/controller/ControllerTest.php | 7 | ||||
-rw-r--r-- | tests/lib/appframework/http/ContentSecurityPolicyTest.php | 215 | ||||
-rw-r--r-- | tests/lib/appframework/http/DataResponseTest.php | 5 | ||||
-rw-r--r-- | tests/lib/appframework/http/ResponseTest.php | 29 |
8 files changed, 529 insertions, 22 deletions
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 @@ -930,15 +930,6 @@ $CONFIG = array( ), /** - * 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 9be5d75c314..600b702810c 100644 --- a/lib/private/response.php +++ b/lib/private/response.php @@ -189,7 +189,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. @@ -204,17 +204,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..cb9a241d8af --- /dev/null +++ b/lib/public/appframework/http/contentsecuritypolicy.php @@ -0,0 +1,241 @@ +<?php +/** + * Copyright (c) 2015 Lukas Reschke lukas@owncloud.com + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace OCP\AppFramework\Http; + +use OCP\AppFramework\Http; + +/** + * Class ContentSecurityPolicy is a simple helper which allows applications to + * modify the Content-Security-Policy sent by ownCloud. Per default only JavaScript, + * stylesheets, images, fonts, media and connections from the same domain + * ('self') are allowed. + * + * Even if a value gets modified above defaults will still get appended. Please + * notice that ownCloud ships already with sensible defaults and those policies + * should require no modification at all for most use-cases. + * + * @package OCP\AppFramework\Http + */ +class ContentSecurityPolicy { + /** @var bool Whether inline JS snippets are allowed */ + private $inlineScriptAllowed = false; + /** + * @var bool Whether eval in JS scripts is allowed + * TODO: Disallow per default + * @link https://github.com/owncloud/core/issues/11925 + */ + private $evalScriptAllowed = true; + /** @var array Domains from which scripts can get loaded */ + private $allowedScriptDomains = [ + '\'self\'', + ]; + /** + * @var bool Whether inline CSS is allowed + * TODO: Disallow per default + * @link https://github.com/owncloud/core/issues/13458 + */ + private $inlineStyleAllowed = true; + /** @var array Domains from which CSS can get loaded */ + private $allowedStyleDomains = [ + '\'self\'', + ]; + /** @var array Domains from which images can get loaded */ + private $allowedImageDomains = [ + '\'self\'', + ]; + /** @var array Domains to which connections can be done */ + private $allowedConnectDomains = [ + '\'self\'', + ]; + /** @var array Domains from which media elements can be loaded */ + private $allowedMediaDomains = [ + '\'self\'', + ]; + /** @var array Domains from which object elements can be loaded */ + private $allowedObjectDomains = []; + /** @var array Domains from which iframes can be loaded */ + private $allowedFrameDomains = []; + /** @var array Domains from which fonts can be loaded */ + private $allowedFontDomains = [ + '\'self\'', + ]; + + /** + * Whether inline JavaScript snippets are allowed or forbidden + * @param bool $state + * @return $this + */ + public function allowInlineScript($state = false) { + $this->inlineScriptAllowed = $state; + return $this; + } + + /** + * Whether eval in JavaScript is allowed or forbidden + * @param bool $state + * @return $this + */ + public function allowEvalScript($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 <object>, <embed> or <applet> 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 78c0d9d15a1..ccc373f4d59 100644 --- a/tests/lib/appframework/controller/ControllerTest.php +++ b/tests/lib/appframework/controller/ControllerTest.php @@ -173,11 +173,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..739028cb3b5 --- /dev/null +++ b/tests/lib/appframework/http/ContentSecurityPolicyTest.php @@ -0,0 +1,215 @@ +<?php +/** + * Copyright (c) 2015 Lukas Reschke lukas@owncloud.com + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + + +namespace OC\AppFramework\Http; + +use OCP\AppFramework\Http; +use OCP\AppFramework\Http\ContentSecurityPolicy; + +/** + * Class ContentSecurityPolicyTest + * + * @package OC\AppFramework\Http + */ +class ContentSecurityPolicyTest extends \Test\TestCase { + + /** @var ContentSecurityPolicy */ + private $contentSecurityPolicy; + + public function setUp() { + parent::setUp(); + $this->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->allowEvalScript(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) + ->allowEvalScript(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())); } |