diff options
author | Julius Härtl <jus@bitgrid.net> | 2022-03-09 14:25:36 +0100 |
---|---|---|
committer | Julius Härtl <jus@bitgrid.net> | 2022-03-09 15:10:27 +0100 |
commit | bd03dd37bec6e94bf1bb8f7329d370c5a68ecb6b (patch) | |
tree | 54b371050de801f253fde867706529bc7bee6da9 | |
parent | 0825c3ea34675b12bf14dc00354e0ab1c77ecf11 (diff) | |
download | nextcloud-server-bd03dd37bec6e94bf1bb8f7329d370c5a68ecb6b.tar.gz nextcloud-server-bd03dd37bec6e94bf1bb8f7329d370c5a68ecb6b.zip |
Allow to set a strict-dynamic CSP through the API
Signed-off-by: Julius Härtl <jus@bitgrid.net>
5 files changed, 43 insertions, 0 deletions
diff --git a/lib/private/Security/CSP/ContentSecurityPolicy.php b/lib/private/Security/CSP/ContentSecurityPolicy.php index 78517f639a7..8a72934d4c9 100644 --- a/lib/private/Security/CSP/ContentSecurityPolicy.php +++ b/lib/private/Security/CSP/ContentSecurityPolicy.php @@ -244,4 +244,11 @@ class ContentSecurityPolicy extends \OCP\AppFramework\Http\ContentSecurityPolicy public function setReportTo(array $reportTo) { $this->reportTo = $reportTo; } + + /** + * @param boolean $strictDynamicAllowed + */ + public function setStrictDynamicAllowed(bool $strictDynamicAllowed) { + $this->strictDynamicAllowed = $strictDynamicAllowed; + } } diff --git a/lib/public/AppFramework/Http/ContentSecurityPolicy.php b/lib/public/AppFramework/Http/ContentSecurityPolicy.php index d30e3b50c7f..3a91e3dc2a7 100644 --- a/lib/public/AppFramework/Http/ContentSecurityPolicy.php +++ b/lib/public/AppFramework/Http/ContentSecurityPolicy.php @@ -44,6 +44,8 @@ class ContentSecurityPolicy extends EmptyContentSecurityPolicy { protected $inlineScriptAllowed = false; /** @var bool Whether eval in JS scripts is allowed */ protected $evalScriptAllowed = false; + /** @var bool Whether strict-dynamic should be set */ + protected $strictDynamicAllowed = null; /** @var array Domains from which scripts can get loaded */ protected $allowedScriptDomains = [ '\'self\'', diff --git a/lib/public/AppFramework/Http/EmptyContentSecurityPolicy.php b/lib/public/AppFramework/Http/EmptyContentSecurityPolicy.php index e0ef79049a4..98a42aeabb5 100644 --- a/lib/public/AppFramework/Http/EmptyContentSecurityPolicy.php +++ b/lib/public/AppFramework/Http/EmptyContentSecurityPolicy.php @@ -41,6 +41,8 @@ class EmptyContentSecurityPolicy { protected $inlineScriptAllowed = null; /** @var string Whether JS nonces should be used */ protected $useJsNonce = null; + /** @var bool Whether strict-dynamic should be used */ + protected $strictDynamicAllowed = null; /** * @var bool Whether eval in JS scripts is allowed * TODO: Disallow per default @@ -94,6 +96,16 @@ class EmptyContentSecurityPolicy { } /** + * @param bool $state + * @return EmptyContentSecurityPolicy + * @since 24.0.0 + */ + public function useStrictDynamic(bool $state = false): self { + $this->strictDynamicAllowed = $state; + return $this; + } + + /** * Use the according JS nonce * This method is only for CSPMiddleware, custom values are ignored in mergePolicies of ContentSecurityPolicyManager * @@ -438,6 +450,9 @@ class EmptyContentSecurityPolicy { if (!empty($this->allowedScriptDomains) || $this->inlineScriptAllowed || $this->evalScriptAllowed) { $policy .= 'script-src '; if (is_string($this->useJsNonce)) { + if ($this->strictDynamicAllowed) { + $policy .= '\'strict-dynamic\' '; + } $policy .= '\'nonce-'.base64_encode($this->useJsNonce).'\''; $allowedScriptDomains = array_flip($this->allowedScriptDomains); unset($allowedScriptDomains['\'self\'']); diff --git a/tests/lib/AppFramework/Http/ContentSecurityPolicyTest.php b/tests/lib/AppFramework/Http/ContentSecurityPolicyTest.php index 0c8d125d960..a96cd4a163b 100644 --- a/tests/lib/AppFramework/Http/ContentSecurityPolicyTest.php +++ b/tests/lib/AppFramework/Http/ContentSecurityPolicyTest.php @@ -472,4 +472,21 @@ class ContentSecurityPolicyTest extends \Test\TestCase { $this->contentSecurityPolicy->allowEvalScript(true); $this->assertSame($expectedPolicy, $this->contentSecurityPolicy->buildPolicy()); } + + public function testGetPolicyNonce() { + $nonce = 'my-nonce'; + $expectedPolicy = "default-src 'none';base-uri 'none';manifest-src 'self';script-src 'nonce-".base64_encode($nonce) . "';style-src 'self' 'unsafe-inline';img-src 'self' data: blob:;font-src 'self' data:;connect-src 'self';media-src 'self';frame-ancestors 'self';form-action 'self'"; + + $this->contentSecurityPolicy->useJsNonce($nonce); + $this->assertSame($expectedPolicy, $this->contentSecurityPolicy->buildPolicy()); + } + + public function testGetPolicyNonceStrictDynamic() { + $nonce = 'my-nonce'; + $expectedPolicy = "default-src 'none';base-uri 'none';manifest-src 'self';script-src 'strict-dynamic' 'nonce-".base64_encode($nonce) . "';style-src 'self' 'unsafe-inline';img-src 'self' data: blob:;font-src 'self' data:;connect-src 'self';media-src 'self';frame-ancestors 'self';form-action 'self'"; + + $this->contentSecurityPolicy->useJsNonce($nonce); + $this->contentSecurityPolicy->useStrictDynamic(true); + $this->assertSame($expectedPolicy, $this->contentSecurityPolicy->buildPolicy()); + } } diff --git a/tests/lib/Security/CSP/ContentSecurityPolicyManagerTest.php b/tests/lib/Security/CSP/ContentSecurityPolicyManagerTest.php index b91482ab2c6..e5cf9ea9e1f 100644 --- a/tests/lib/Security/CSP/ContentSecurityPolicyManagerTest.php +++ b/tests/lib/Security/CSP/ContentSecurityPolicyManagerTest.php @@ -86,6 +86,7 @@ class ContentSecurityPolicyManagerTest extends TestCase { $policy = new \OCP\AppFramework\Http\ContentSecurityPolicy(); $policy->addAllowedFontDomain('mydomain.com'); $policy->addAllowedImageDomain('anotherdomain.de'); + $policy->useStrictDynamic(true); $e->addPolicy($policy); }); @@ -117,6 +118,7 @@ class ContentSecurityPolicyManagerTest extends TestCase { $expected->addAllowedImageDomain('example.org'); $expected->addAllowedChildSrcDomain('childdomain'); $expected->addAllowedFormActionDomain('thirdDomain'); + $expected->useStrictDynamic(true); $expectedStringPolicy = "default-src 'none';base-uri 'none';manifest-src 'self';script-src 'self' 'unsafe-inline' 'unsafe-eval';style-src 'self' 'unsafe-inline';img-src 'self' data: blob: anotherdomain.de example.org;font-src 'self' data: mydomain.com example.com anotherFontDomain;connect-src 'self';media-src 'self';child-src childdomain;frame-ancestors 'self';form-action 'self' thirdDomain"; $this->assertEquals($expected, $this->contentSecurityPolicyManager->getDefaultPolicy()); |