summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorClark Tomlinson <fallen013@gmail.com>2015-02-18 10:27:29 -0500
committerClark Tomlinson <fallen013@gmail.com>2015-02-18 10:27:29 -0500
commit8d09cc3b91a9689a6c95e06c8002288bdd8d5bbf (patch)
tree81e09b101401476c2de80460a994a34ff26b75d8
parent84cc90a0ee81d32001ccaa38795cbcf4343ac2f0 (diff)
parenta9d1a0144018e60ba2728708bf965b4d9855920b (diff)
downloadnextcloud-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.php9
-rw-r--r--lib/private/response.php15
-rw-r--r--lib/public/appframework/http/contentsecuritypolicy.php241
-rw-r--r--lib/public/appframework/http/response.php30
-rw-r--r--tests/lib/appframework/controller/ControllerTest.php7
-rw-r--r--tests/lib/appframework/http/ContentSecurityPolicyTest.php215
-rw-r--r--tests/lib/appframework/http/DataResponseTest.php5
-rw-r--r--tests/lib/appframework/http/ResponseTest.php29
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()));
}