This is an automated email from the ASF dual-hosted git repository.

brondsem pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/allura.git


The following commit(s) were added to refs/heads/master by this push:
     new 45275ae41 [#8479] modified exisinting logic on settings and added 
support for script-src
45275ae41 is described below

commit 45275ae41bacf0143702ec736a2afde188e3918f
Author: Guillermo Cruz <[email protected]>
AuthorDate: Thu Nov 17 11:09:51 2022 -0600

    [#8479] modified exisinting logic on settings and added support for 
script-src
---
 Allura/allura/lib/custom_middleware.py      | 47 ++++++++++++++++++-----------
 Allura/allura/tests/functional/test_root.py | 47 ++++++++++++++++++-----------
 Allura/development.ini                      | 17 +++++++++--
 3 files changed, 74 insertions(+), 37 deletions(-)

diff --git a/Allura/allura/lib/custom_middleware.py 
b/Allura/allura/lib/custom_middleware.py
index 1968ad31c..4ea99c6d3 100644
--- a/Allura/allura/lib/custom_middleware.py
+++ b/Allura/allura/lib/custom_middleware.py
@@ -468,43 +468,56 @@ class ContentSecurityPolicyMiddleware:
     def __call__(self, environ, start_response):
         req = Request(environ)
         resp = req.get_response(self.app)
-        rules = resp.headers.getall('Content-Security-Policy')
-        report_rules = 
resp.headers.getall('Content-Security-Policy-Report-Only')
-        report_mode =  asbool(self.config.get('csp.report_mode',False))
-        report_enforce_mode = 
asbool(self.config.get('csp.report_enforce_mode',False))
+        rules = set(resp.headers.getall('Content-Security-Policy'))
+        report_rules = 
set(resp.headers.getall('Content-Security-Policy-Report-Only'))
         report_uri = self.config.get('csp.report_uri', None)
         report_uri_enforce = self.config.get('csp.report_uri_enforce', None)
         
         if rules:
             resp.headers.pop('Content-Security-Policy')
+
         if report_rules:
            resp.headers.pop('Content-Security-Policy-Report-Only')
 
-        if report_mode and report_uri:
-            report_rules.append(f'report-uri {report_uri}')
 
         if self.config['base_url'].startswith('https'):
-            rules.append('upgrade-insecure-requests')
+            rules.add('upgrade-insecure-requests')
 
-        if report_enforce_mode and report_uri_enforce:
-            rules.append(f'report-uri {report_uri_enforce}')
 
         if self.config.get('csp.frame_sources'):
-            if report_mode:
-                report_rules.append(f"frame-src 
{self.config['csp.frame_sources']}")
+            if asbool(self.config.get('csp.frame_sources_enforce',False)):
+                rules.add(f"frame-src {self.config['csp.frame_sources']}")
             else:
-                rules.append(f"frame-src {self.config['csp.frame_sources']}")
+                report_rules.add(f"frame-src 
{self.config['csp.frame_sources']}")
 
         if self.config.get('csp.form_action_urls'):
-            if report_mode:
-                report_rules.append(f"form-action 
{self.config['csp.form_action_urls']}")
+            if asbool(self.config.get('csp.form_actions_enforce',False)):
+                rules.add(f"form-action {self.config['csp.form_action_urls']}")
+            else:
+                report_rules.add(f"form-action 
{self.config['csp.form_action_urls']}")
+
+        if self.config.get('csp.script_src'):
+            script_srcs = self.config['csp.script_src']
+            """ 
+            Sometimes you might have the need to build custom values from 
inside a controller and pass it
+            to the middleware. In this case we pass a custom list of domains 
from google that can't be built
+            directly in here.
+            """
+            if environ.get('csp_script_domains',''):
+                script_srcs = f"{script_srcs} {' 
'.join(environ['csp_script_domains'])}"
+
+            if asbool(self.config.get('csp.script_src_enforce',False)):
+                rules.add(f"script-src {script_srcs} 
{self.config.get('csp.script_src.extras','')}")
             else:
-                rules.append(f"form-action 
{self.config['csp.form_action_urls']}")
+                report_rules.add(f"script-src {script_srcs} 
{self.config.get('csp.script_src.extras','')}")
+
 
-        rules.append("object-src 'none'")
-        rules.append("frame-ancestors 'self'")
+        rules.add("object-src 'none'")
+        rules.add("frame-ancestors 'self'")
         if rules:
+            rules.add(f'report-uri {report_uri_enforce}')
             resp.headers.add('Content-Security-Policy', '; '.join(rules))
         if report_rules:
+            report_rules.add(f'report-uri {report_uri}')
             resp.headers.add('Content-Security-Policy-Report-Only', '; 
'.join(report_rules))
         return resp(environ, start_response)
diff --git a/Allura/allura/tests/functional/test_root.py 
b/Allura/allura/tests/functional/test_root.py
index 6b5f56ca3..dc5dace07 100644
--- a/Allura/allura/tests/functional/test_root.py
+++ b/Allura/allura/tests/functional/test_root.py
@@ -186,33 +186,46 @@ class TestRootController(TestController):
         r = self.app.get('/error/document')
         r.mustcontain("We're sorry but we weren't able to process")
 
+    @mock.patch.dict(tg.config, {'csp.frame_sources_enforce': True, \
+                                 'csp.report_uri_enforce': 
'https://example.com/r/d/csp/enforce', \
+                                 'csp.form_actions_enforce': True,
+                                 'csp.script_src_enforce': True})
     def test_headers(self):
         resp = self.app.get('/p')
-        assert resp.headers.getall('Content-Security-Policy')[0] == '; 
'.join(["frame-src 'self' www.youtube-nocookie.com",
-                                                                  "form-action 
'self'",
-                                                                  "object-src 
'none'",
-                                                                  
"frame-ancestors 'self'"])
+        expected_headers = "form-action 'self'; frame-src 'self' 
www.youtube-nocookie.com; object-src 'none';"
+        expected_headers += "frame-ancestors 'self'; report-uri 
https://example.com/r/d/csp/enforce; script-src 'self;"
+        csp_headers = resp.headers.getall('Content-Security-Policy')[0]
+        assert all([h.strip() in csp_headers for h in 
expected_headers.split(';')])
 
+    @mock.patch.dict(tg.config, {'csp.frame_sources_enforce': True,
+                                 'csp.report_uri_enforce': 
'https://example.com/r/d/csp/enforce'})
     def test_headers_config(self):
         resp = self.app.get('/p')
-        assert "frame-src 'self' www.youtube-nocookie.com;" in 
resp.headers.getall('Content-Security-Policy')[0]
+        assert "frame-src 'self' www.youtube-nocookie.com" in 
resp.headers.getall('Content-Security-Policy')[0]
 
-    @mock.patch.dict(tg.config, {'csp.report_mode': True, 'csp.report_uri': 
'https://example.com/r/d/csp/reportOnly'})
+    @mock.patch.dict(tg.config, {'csp.report_uri': 
'https://example.com/r/d/csp/reportOnly'})
     def test_headers_report(self):
         resp = self.app.get('/p/wiki/Home/')
-        assert resp.headers.getall('Content-Security-Policy-Report-Only')[0] 
== '; '.join([
-            "report-uri https://example.com/r/d/csp/reportOnly";,
-            "frame-src 'self' www.youtube-nocookie.com",
-            "form-action 'self'",
-        ])
-
-    @mock.patch.dict(tg.config, {'csp.report_uri_enforce': 
'https://example.com/r/d/csp/enforce', 'csp.report_enforce_mode': True})
-    def test_headers_report_enforce(self):
-        resp = self.app.get('/p/wiki/Home/')
-        assert "report-uri https://example.com/r/d/csp/enforce; frame-src 
'self' www.youtube-nocookie.com;" \
-               in resp.headers.getall('Content-Security-Policy')[0]
+        expected_headers = "report-uri https://example.com/r/d/csp/reportOnly;";
+        expected_headers += "frame-src 'self' www.youtube-nocookie.com; 
script-src 'self' ;"
+        expected_headers += "form-action 'self'"
+
+        csp_headers = 
resp.headers.getall('Content-Security-Policy-Report-Only')[0]
+        assert all([h.strip() in csp_headers for h in 
expected_headers.split(';')])
 
 
+    @mock.patch.dict(tg.config, {'csp.report_uri_enforce': 
'https://example.com/r/d/csp/enforce', 'csp.frame_sources_enforce': True})
+    def test_headers_frame_sources_enforce(self):
+        resp = self.app.get('/p/wiki/Home/')
+        expected_headers = "report-uri https://example.com/r/d/csp/enforce;";
+        expected_headers += "frame-src 'self' www.youtube-nocookie.com;"
+        expected_headers += "object-src 'none'"
+        expected_report_headers = "script-src 'self' ;  form-action 'self'; 
report-uri None"
+        csp_headers = resp.headers.getall('Content-Security-Policy')[0]
+        csp_report_headers = 
resp.headers.getall('Content-Security-Policy-Report-Only')[0]
+        assert all([h.strip() in csp_headers for h in 
expected_headers.split(';')])
+        assert all([h.strip() in csp_report_headers for h in 
expected_report_headers.split(';')])
+
 class TestRootWithSSLPattern(TestController):
     def setup_method(self, method):
         with td.patch_middleware_config({'force_ssl.pattern': '^/auth'}):
diff --git a/Allura/development.ini b/Allura/development.ini
index 3ded52b9b..171240692 100644
--- a/Allura/development.ini
+++ b/Allura/development.ini
@@ -658,20 +658,31 @@ userstats.count_lines_of_code = true
 ; Minutes to cache saved search "bins" numbers.  0 will disable entirely, so 
caches are permanent
 ;forgetracker.bin_cache_expire = 60
 
+;
 ; CSP Headers
-; enable report mode
-; csp.report_mode = false
-; csp.report_enforce_mode = false
+;
+
 ; csp.report_uri = https://example.com/r/d/csp/reportOnly
 ; csp.report_uri_enforce = https://example.com/r/d/csp/enforce
 
 
+; to enable enforce mode on frame-src
+;csp.frame_sources_enforce = true
+
 ; frame-src list of valid sources for loading frames
 csp.frame_sources = 'self' www.youtube-nocookie.com
 
+; to enable enforce mode on form-action
+; csp.form_actions_enforce = true
+
 ; form-action valid sources that can be used as an HTML <form> action
 csp.form_action_urls = 'self'
 
+; to enable enforce mode on script-src
+; csp.script_src_enforce = true
+
+csp.script_src = 'self'
+
 ;
 ; Settings for comment reactions
 ;

Reply via email to