]> source.dussan.org Git - nextcloud-server.git/commitdiff
Allow to set a strict-dynamic CSP through the API 31503/head
authorJulius Härtl <jus@bitgrid.net>
Wed, 9 Mar 2022 13:25:36 +0000 (14:25 +0100)
committerJulius Härtl <jus@bitgrid.net>
Wed, 9 Mar 2022 14:10:27 +0000 (15:10 +0100)
Signed-off-by: Julius Härtl <jus@bitgrid.net>
lib/private/Security/CSP/ContentSecurityPolicy.php
lib/public/AppFramework/Http/ContentSecurityPolicy.php
lib/public/AppFramework/Http/EmptyContentSecurityPolicy.php
tests/lib/AppFramework/Http/ContentSecurityPolicyTest.php
tests/lib/Security/CSP/ContentSecurityPolicyManagerTest.php

index 78517f639a762dd33edcb509eb79833ccf6ceb50..8a72934d4c98432c831f1e73910a1825985e1bc4 100644 (file)
@@ -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;
+       }
 }
index d30e3b50c7f7961be9abb0afd3061b61cfbe74f8..3a91e3dc2a7a0efd1c9d8d785c5297da39f91c78 100644 (file)
@@ -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\'',
index e0ef79049a42224c749fd9deed3eebbcbf93ccac..98a42aeabb5e45a62d5a045d75647b5975e4069a 100644 (file)
@@ -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
@@ -93,6 +95,16 @@ class EmptyContentSecurityPolicy {
                return $this;
        }
 
+       /**
+        * @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\'']);
index 0c8d125d96045f76c9d8bfbd848e7d91dcda7810..a96cd4a163b8a1cb93f5ee107a35605b38b29fc0 100644 (file)
@@ -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());
+       }
 }
index b91482ab2c6a940fcf060afc3b7f53f1cee74d11..e5cf9ea9e1f90dcf3af9f0f1407766cf14e1720f 100644 (file)
@@ -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());