Open dgabrahams opened 1 year ago
from csp.utils import build_policy, get_nonce_generator
def custom_build_policy(
policies, overrides=None, nonce_generator=get_nonce_generator(),
report_only=False):
# Include your custom logic here to manipulate policies and overrides as desired
csp = build_policy(policies, overrides=overrides, report_only=report_only)
return csp
def construct_final_string(policy_parts):
# Include your custom logic here to construct the final string
# based on policy_parts
final = "; ".join(policy_parts)
final = " ".join([final, "report-uri /csp_reports/"])
return final
def add_nonce(policy, source):
# Include logic here to add a nonce to the specified source if necessary
return source
# Update the original build_policy function to call the new functions
def build_policy(policies, overrides=None, nonce_generator=get_nonce_generator(),
report_only=False):
policy_parts = []
for policy in policies:
for name, value in policy.items():
# Handle the nonce directive here
if name == 'nonce':
continue
elif name in ('default-src', 'script-src', 'style-src', 'img-src', 'connect-src',
'font-src','media-src', 'object-src', 'child-src', 'frame-src', 'worker-src',
'manifest-src', 'base-uri', 'form-action', 'frame-ancestors',
'plugin-types', 'sandbox', 'navigate-to', 'block-all-mixed-content',
'upgrade-insecure-requests', 'require-sri-for'):
# Split multiple directives where necessary
values = [v.strip() for v in value.split(';') if v.strip()]
# Add nonces to sources if necessary
if nonce_generator and name in ('script-src', 'style-src', 'default-src'):
values = [add_nonce(policy, v) if 'nonce' not in v else v for v in values]
# Append the directive with its values
policy_parts.append(' '.join([name] + values))
final = construct_final_string(policy_parts)
return final
The custom_build_policy function takes a list of policy dictionaries and some optional arguments, and returns a CSP header string. The build_policy function is called by custom_build_policy and constructs the policy header by iterating over the policy dictionaries and their key-value pairs.
The construct_final_string function takes a list of policy strings and constructs the final CSP header string by joining them together with semicolons and adding a report URI.
The add_nonce function is called by build_policy for certain policy types and adds a nonce value to the allowed sources to prevent script injection attacks.
from django.test import TestCase, RequestFactory
from django.http import HttpResponse
from django.urls import reverse
from django_csp.middleware import CSPMiddleware
class CSPMiddlewareTests(TestCase):
def setUp(self):
self.factory = RequestFactory()
def test_can_process_request(self):
request = self.factory.get(reverse('home'))
middleware = CSPMiddleware(None)
response = middleware.process_request(request)
self.assertIsNone(response)
def test_can_pre_process_response_with_empty_policy(self):
request = self.factory.get(reverse('home'))
middleware = CSPMiddleware(lambda r: HttpResponse())
response = middleware(request)
self.assertIn('Content-Security-Policy', response)
self.assertEqual(response['Content-Security-Policy'], "default-src 'self'")
def test_can_pre_process_response_with_non_empty_policy(self):
request = self.factory.get(reverse('home'))
middleware = CSPMiddleware(lambda r: HttpResponse())
middleware.policy = {'default-src': ['www.example.com', 'maxcdn.bootstrapcdn.com']}
response = middleware(request)
self.assertIn('Content-Security-Policy', response)
self.assertEqual(response['Content-Security-Policy'], "default-src www.example.com maxcdn.bootstrapcdn.com")
To allow editing of the build policy layout for django-csp, you can modify the build_policy
function to return the csp
variable before it is changed into policy_parts
.
You can then refactor the addition of nonce and final string construction into new functions, such as add_nonce_to_policy
and construct_csp_header
, respectively. These functions can be called by build_policy
to add the nonce and construct the final CSP header string.
Developers can then hook into the output of build_policy
to order or append policy parts as required, and then return the outputs of add_nonce_to_policy
and construct_csp_header
as required.
Here's an example implementation:
def build_policy(policies, overrides=None, nonce_generator=get_nonce_generator(),
report_only=False):
csp = {}
for policy in policies:
for name, value in policy.items():
if name == 'nonce':
continue
elif name in ('default-src', 'script-src', 'style-src', 'img-src', 'connect-src',
'font-src','media-src', 'object-src', 'child-src', 'frame-src', 'worker-src',
'manifest-src', 'base-uri', 'form-action', 'frame-ancestors',
'plugin-types', 'sandbox', 'navigate-to', 'block-all-mixed-content',
'upgrade-insecure-requests', 'require-sri-for'):
values = [v.strip() for v in value.split(';') if v.strip()]
if nonce_generator and name in ('script-src', 'style-src', 'default-src'):
values = [add_nonce(policy, v) if 'nonce' not in v else v for v in values]
csp[name] = values
return csp
def add_nonce_to_policy(policy, source):
return source
def construct_csp_header(policy_parts):
final = "; ".join(policy_parts)
final = " ".join([final, "report-uri /csp_reports/"])
return final
from django.test import TestCase
from django_csp.utils import build_policy
class BuildPolicyTestCase(TestCase):
def test_build_policy(self):
policies = [
{'default-src': "'self'"},
{'script-src': "'self'"},
{'style-src': "'self'"},
{'img-src': "'self'"},
]
overrides = {'script-src': "'unsafe-inline'"}
csp = build_policy(policies, overrides=overrides)
expected_csp = {
'default-src': ["'self'"],
'script-src': ["'self'", "'unsafe-inline'"],
'style-src': ["'self'"],
'img-src': ["'self'"],
}
self.assertEqual(csp, expected_csp)
This test case creates a list of policies and an override for the script-src policy. It then calls the build_policy function with these policies and the override, and compares the resulting CSP with the expected CSP. This test ensures that the build_policy function correctly combines the policies and applies the override.
You can then use these functions in your Django views or middleware to generate customized CSP headers.
It will allow greater versatility and customisation if the
build_policy
function was made to return thecsp
variable before it is changed intopolicy_parts
(https://github.com/mozilla/django-csp/blob/main/csp/utils.py#L90). It would be required to refactor the addition of nonce and final string construction (https://github.com/mozilla/django-csp/blob/main/csp/utils.py#L111) to new functions. Developers could then hook into the output ofbuild_policy
ordering/appending as required and then returning the nonce and string construction function outputs as required.