1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168
|
from secrets import token_urlsafe
from unittest.mock import patch
from django.test import SimpleTestCase
from django.utils.csp import CSP, LazyNonce, build_policy
from django.utils.functional import empty
basic_config = {
"default-src": [CSP.SELF],
}
alt_config = {
"default-src": [CSP.SELF, CSP.UNSAFE_INLINE],
}
basic_policy = "default-src 'self'"
class CSPConstantsTests(SimpleTestCase):
def test_constants(self):
self.assertEqual(CSP.NONE, "'none'")
self.assertEqual(CSP.REPORT_SAMPLE, "'report-sample'")
self.assertEqual(CSP.SELF, "'self'")
self.assertEqual(CSP.STRICT_DYNAMIC, "'strict-dynamic'")
self.assertEqual(CSP.UNSAFE_EVAL, "'unsafe-eval'")
self.assertEqual(CSP.UNSAFE_HASHES, "'unsafe-hashes'")
self.assertEqual(CSP.UNSAFE_INLINE, "'unsafe-inline'")
self.assertEqual(CSP.WASM_UNSAFE_EVAL, "'wasm-unsafe-eval'")
self.assertEqual(CSP.NONCE, "<CSP_NONCE_SENTINEL>")
class CSPBuildPolicyTest(SimpleTestCase):
def assertPolicyEqual(self, a, b):
parts_a = sorted(a.split("; ")) if a is not None else None
parts_b = sorted(b.split("; ")) if b is not None else None
self.assertEqual(parts_a, parts_b, f"Policies not equal: {a!r} != {b!r}")
def test_config_empty(self):
self.assertPolicyEqual(build_policy({}), "")
def test_config_basic(self):
self.assertPolicyEqual(build_policy(basic_config), basic_policy)
def test_config_multiple_directives(self):
policy = {
"default-src": [CSP.SELF],
"script-src": [CSP.NONE],
}
self.assertPolicyEqual(
build_policy(policy), "default-src 'self'; script-src 'none'"
)
def test_config_value_as_string(self):
"""
Test that a single value can be passed as a string.
"""
policy = {"default-src": CSP.SELF}
self.assertPolicyEqual(build_policy(policy), "default-src 'self'")
def test_config_value_as_tuple(self):
"""
Test that a tuple can be passed as a value.
"""
policy = {"default-src": (CSP.SELF, "foo.com")}
self.assertPolicyEqual(build_policy(policy), "default-src 'self' foo.com")
def test_config_value_as_set(self):
"""
Test that a set can be passed as a value.
Sets are often used in Django settings to ensure uniqueness, however,
sets are unordered. The middleware ensures consistency via sorting if a
set is passed.
"""
policy = {"default-src": {CSP.SELF, "foo.com", "bar.com"}}
self.assertPolicyEqual(
build_policy(policy), "default-src 'self' bar.com foo.com"
)
def test_config_value_none(self):
"""
Test that `None` removes the directive from the policy.
Useful in cases where the CSP config is scripted in some way or
explicitly not wanting to set a directive.
"""
policy = {"default-src": [CSP.SELF], "script-src": None}
self.assertPolicyEqual(build_policy(policy), basic_policy)
def test_config_value_boolean_true(self):
policy = {"default-src": [CSP.SELF], "block-all-mixed-content": True}
self.assertPolicyEqual(
build_policy(policy), "default-src 'self'; block-all-mixed-content"
)
def test_config_value_boolean_false(self):
policy = {"default-src": [CSP.SELF], "block-all-mixed-content": False}
self.assertPolicyEqual(build_policy(policy), basic_policy)
def test_config_value_multiple_boolean(self):
policy = {
"default-src": [CSP.SELF],
"block-all-mixed-content": True,
"upgrade-insecure-requests": True,
}
self.assertPolicyEqual(
build_policy(policy),
"default-src 'self'; block-all-mixed-content; upgrade-insecure-requests",
)
def test_config_with_nonce_arg(self):
"""
Test when the `CSP.NONCE` is not in the defined policy, the nonce
argument has no effect.
"""
self.assertPolicyEqual(build_policy(basic_config, nonce="abc123"), basic_policy)
def test_config_with_nonce(self):
policy = {"default-src": [CSP.SELF, CSP.NONCE]}
self.assertPolicyEqual(
build_policy(policy, nonce="abc123"),
"default-src 'self' 'nonce-abc123'",
)
def test_config_with_multiple_nonces(self):
policy = {
"default-src": [CSP.SELF, CSP.NONCE],
"script-src": [CSP.SELF, CSP.NONCE],
}
self.assertPolicyEqual(
build_policy(policy, nonce="abc123"),
"default-src 'self' 'nonce-abc123'; script-src 'self' 'nonce-abc123'",
)
def test_config_with_empty_directive(self):
policy = {"default-src": []}
self.assertPolicyEqual(build_policy(policy), "")
class LazyNonceTests(SimpleTestCase):
def test_generates_on_usage(self):
generated_tokens = []
nonce = LazyNonce()
self.assertFalse(nonce)
self.assertIs(nonce._wrapped, empty)
def memento_token_urlsafe(size):
generated_tokens.append(result := token_urlsafe(size))
return result
with patch("django.utils.csp.secrets.token_urlsafe", memento_token_urlsafe):
# Force usage, similar to template rendering, to generate the
# nonce.
val = str(nonce)
self.assertTrue(nonce)
self.assertEqual(nonce, val)
self.assertIsInstance(nonce, str)
self.assertEqual(len(val), 22) # Based on secrets.token_urlsafe of 16 bytes.
self.assertEqual(generated_tokens, [nonce])
# Also test the wrapped value.
self.assertEqual(nonce._wrapped, val)
def test_returns_same_value(self):
nonce = LazyNonce()
first = str(nonce)
second = str(nonce)
self.assertEqual(first, second)
|