From b20174bdad33f619054db08e320e5e546e2834b1 Mon Sep 17 00:00:00 2001 From: Lukas Reschke Date: Mon, 9 Feb 2015 16:30:01 +0100 Subject: 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 --- .../lib/appframework/controller/ControllerTest.php | 7 +- .../http/ContentSecurityPolicyTest.php | 215 +++++++++++++++++++++ tests/lib/appframework/http/DataResponseTest.php | 5 +- tests/lib/appframework/http/ResponseTest.php | 29 ++- 4 files changed, 250 insertions(+), 6 deletions(-) create mode 100644 tests/lib/appframework/http/ContentSecurityPolicyTest.php (limited to 'tests') 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())); } -- cgit v1.2.3 From a9d1a0144018e60ba2728708bf965b4d9855920b Mon Sep 17 00:00:00 2001 From: Lukas Reschke Date: Mon, 16 Feb 2015 12:30:21 +0100 Subject: Rename to allowEval --- lib/public/appframework/http/contentsecuritypolicy.php | 2 +- tests/lib/appframework/http/ContentSecurityPolicyTest.php | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) (limited to 'tests') diff --git a/lib/public/appframework/http/contentsecuritypolicy.php b/lib/public/appframework/http/contentsecuritypolicy.php index 8132237a228..cb9a241d8af 100644 --- a/lib/public/appframework/http/contentsecuritypolicy.php +++ b/lib/public/appframework/http/contentsecuritypolicy.php @@ -81,7 +81,7 @@ class ContentSecurityPolicy { * @param bool $state * @return $this */ - public function evalScriptState($state = true) { + public function allowEvalScript($state = true) { $this->evalScriptAllowed= $state; return $this; } diff --git a/tests/lib/appframework/http/ContentSecurityPolicyTest.php b/tests/lib/appframework/http/ContentSecurityPolicyTest.php index 3b30bba33d4..739028cb3b5 100644 --- a/tests/lib/appframework/http/ContentSecurityPolicyTest.php +++ b/tests/lib/appframework/http/ContentSecurityPolicyTest.php @@ -66,7 +66,7 @@ class ContentSecurityPolicyTest extends \Test\TestCase { $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->contentSecurityPolicy->allowEvalScript(false); $this->assertSame($expectedPolicy, $this->contentSecurityPolicy->buildPolicy()); } @@ -201,7 +201,7 @@ class ContentSecurityPolicyTest extends \Test\TestCase { $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) + ->allowEvalScript(false) ->addAllowedScriptDomain('script.owncloud.org') ->addAllowedStyleDomain('style.owncloud.org') ->addAllowedFontDomain('font.owncloud.org') -- cgit v1.2.3