File: test_csp.py

package info (click to toggle)
python-django 3%3A6.0~alpha1-1
  • links: PTS, VCS
  • area: main
  • in suites: experimental
  • size: 62,204 kB
  • sloc: python: 370,694; javascript: 19,376; xml: 211; makefile: 187; sh: 28
file content (168 lines) | stat: -rw-r--r-- 5,957 bytes parent folder | download
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)