-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathtest_config.py
More file actions
296 lines (245 loc) · 11.1 KB
/
Copy pathtest_config.py
File metadata and controls
296 lines (245 loc) · 11.1 KB
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
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
"""
Tests for config module.
Tests configuration data structures, constants, exception classes,
and utility functions.
"""
import pytest
from sha.config import (
DEFAULT_MAX_REDIRECTS,
DEFAULT_TIMEOUT,
PRIVATE_IP_RANGES,
SECURITY_HEADERS,
SEVERITY_LEVELS,
STATUS_ACCEPTABLE,
STATUS_BAD,
STATUS_GOOD,
STATUS_MISSING,
HTTPError,
InvalidURLError,
NetworkError,
SecurityHeaderAnalyzerError,
get_all_header_names,
get_header_config,
get_severity_rank,
is_valid_severity,
)
class TestSecurityHeadersConfiguration:
"""Test security headers configuration data structure."""
def test_all_headers_defined(self):
"""Test that all expected headers are defined."""
expected_headers = [
"strict-transport-security",
"x-frame-options",
"x-content-type-options",
"content-security-policy",
]
for header in expected_headers:
assert header in SECURITY_HEADERS, f"{header} not found in SECURITY_HEADERS"
def test_header_config_structure(self):
"""Test that each header config has required fields."""
required_fields = [
"display_name",
"severity_missing",
"description",
"validation",
"messages",
"recommendations",
]
for header_key, config in SECURITY_HEADERS.items():
for field in required_fields:
assert field in config, f"{header_key} missing required field: {field}"
def test_header_display_names(self):
"""Test that display names are properly formatted."""
assert (
SECURITY_HEADERS["strict-transport-security"]["display_name"]
== "Strict-Transport-Security"
)
assert SECURITY_HEADERS["x-frame-options"]["display_name"] == "X-Frame-Options"
assert (
SECURITY_HEADERS["x-content-type-options"]["display_name"] == "X-Content-Type-Options"
)
assert (
SECURITY_HEADERS["content-security-policy"]["display_name"] == "Content-Security-Policy"
)
def test_severity_levels_defined(self):
"""Test that all severity levels are defined for each header."""
for header_key, config in SECURITY_HEADERS.items():
severity_missing = config["severity_missing"]
assert (
severity_missing in SEVERITY_LEVELS
), f"Invalid severity_missing for {header_key}: {severity_missing}"
def test_messages_for_all_statuses(self):
"""Test that each header has messages for all statuses."""
for header_key, config in SECURITY_HEADERS.items():
messages = config["messages"]
# At minimum, should have messages for good, acceptable, bad, missing
assert STATUS_GOOD in messages or STATUS_ACCEPTABLE in messages
assert STATUS_BAD in messages or STATUS_MISSING in messages
assert STATUS_MISSING in messages
def test_hsts_validation_rules(self):
"""Test HSTS-specific validation rules."""
hsts_config = SECURITY_HEADERS["strict-transport-security"]
validation = hsts_config["validation"]
assert "min_max_age" in validation
assert validation["min_max_age"] == 10886400 # 126 days
assert "best_max_age" in validation
assert validation["best_max_age"] == 31536000 # 1 year
assert "required_directives" in validation
assert "includesubdomains" in validation["required_directives"]
def test_xframe_validation_rules(self):
"""Test X-Frame-Options validation rules."""
xframe_config = SECURITY_HEADERS["x-frame-options"]
validation = xframe_config["validation"]
assert "best_values" in validation
assert "deny" in validation["best_values"]
assert "acceptable_values" in validation
assert "sameorigin" in validation["acceptable_values"]
def test_content_type_validation_rules(self):
"""Test X-Content-Type-Options validation rules."""
content_type_config = SECURITY_HEADERS["x-content-type-options"]
validation = content_type_config["validation"]
assert "required_value" in validation
assert validation["required_value"] == "nosniff"
def test_csp_validation_rules(self):
"""Test CSP validation rules."""
csp_config = SECURITY_HEADERS["content-security-policy"]
validation = csp_config["validation"]
assert "dangerous_patterns" in validation
assert "good_patterns" in validation
# Check dangerous patterns
dangerous = validation["dangerous_patterns"]
assert "unsafe_inline_script" in dangerous
assert "unsafe_eval" in dangerous
assert "wildcard_script" in dangerous
class TestConstants:
"""Test configuration constants."""
def test_default_timeout(self):
"""Test default timeout is reasonable."""
assert DEFAULT_TIMEOUT == 10
assert DEFAULT_TIMEOUT > 0
def test_default_max_redirects(self):
"""Test default max redirects is reasonable."""
assert DEFAULT_MAX_REDIRECTS == 5
assert DEFAULT_MAX_REDIRECTS >= 0
def test_severity_levels_order(self):
"""Test severity levels are in correct order."""
expected_order = ["critical", "high", "medium-high", "medium", "low", "info"]
assert SEVERITY_LEVELS == expected_order
def test_status_constants(self):
"""Test status constants are defined."""
assert STATUS_GOOD == "good"
assert STATUS_ACCEPTABLE == "acceptable"
assert STATUS_BAD == "bad"
assert STATUS_MISSING == "missing"
def test_private_ip_ranges(self):
"""Test private IP ranges are defined."""
assert len(PRIVATE_IP_RANGES) > 0
assert "127.0.0.0/8" in PRIVATE_IP_RANGES # Loopback
assert "10.0.0.0/8" in PRIVATE_IP_RANGES # Private
assert "192.168.0.0/16" in PRIVATE_IP_RANGES # Private
class TestExceptionClasses:
"""Test custom exception classes."""
def test_base_exception(self):
"""Test base SecurityHeaderAnalyzerError can be instantiated."""
error = SecurityHeaderAnalyzerError("Test error")
assert str(error) == "Test error"
assert isinstance(error, Exception)
def test_network_error(self):
"""Test NetworkError can be instantiated."""
error = NetworkError("Connection failed")
assert str(error) == "Connection failed"
assert isinstance(error, SecurityHeaderAnalyzerError)
assert isinstance(error, Exception)
def test_invalid_url_error(self):
"""Test InvalidURLError can be instantiated."""
error = InvalidURLError("Bad URL")
assert str(error) == "Bad URL"
assert isinstance(error, SecurityHeaderAnalyzerError)
def test_http_error_basic(self):
"""Test HTTPError can be instantiated."""
error = HTTPError("404 Not Found")
assert str(error) == "404 Not Found"
assert isinstance(error, SecurityHeaderAnalyzerError)
def test_http_error_with_status_code(self):
"""Test HTTPError stores status code."""
error = HTTPError("Not found", status_code=404)
assert error.status_code == 404
def test_http_error_with_headers(self):
"""Test HTTPError stores headers."""
headers = {"content-type": "text/html"}
error = HTTPError("Error", status_code=500, headers=headers)
assert error.status_code == 500
assert error.headers == headers
def test_http_error_defaults(self):
"""Test HTTPError defaults."""
error = HTTPError("Error")
assert error.status_code is None
assert error.headers == {}
class TestUtilityFunctions:
"""Test configuration utility functions."""
def test_get_header_config_valid(self):
"""Test getting config for valid header."""
config = get_header_config("strict-transport-security")
assert config is not None
assert "display_name" in config
assert config["display_name"] == "Strict-Transport-Security"
def test_get_header_config_case_insensitive(self):
"""Test get_header_config is case insensitive."""
config1 = get_header_config("strict-transport-security")
config2 = get_header_config("Strict-Transport-Security")
config3 = get_header_config("STRICT-TRANSPORT-SECURITY")
assert config1 == config2 == config3
def test_get_header_config_invalid(self):
"""Test getting config for invalid header raises KeyError."""
with pytest.raises(KeyError):
get_header_config("invalid-header")
def test_get_all_header_names(self):
"""Test getting all header names."""
names = get_all_header_names()
assert isinstance(names, list)
assert len(names) == 15
assert "strict-transport-security" in names
assert "x-frame-options" in names
assert "x-content-type-options" in names
assert "content-security-policy" in names
assert "referrer-policy" in names
assert "permissions-policy" in names
assert "cross-origin-embedder-policy" in names
assert "cross-origin-opener-policy" in names
assert "cross-origin-resource-policy" in names
assert "x-xss-protection" in names
assert "x-download-options" in names
assert "x-permitted-cross-domain-policies" in names
def test_get_severity_rank_valid(self):
"""Test getting severity rank for valid severities."""
assert get_severity_rank("critical") == 0 # Most severe
assert get_severity_rank("high") == 1
assert get_severity_rank("medium-high") == 2
assert get_severity_rank("medium") == 3
assert get_severity_rank("low") == 4
assert get_severity_rank("info") == 5 # Least severe
def test_get_severity_rank_case_insensitive(self):
"""Test severity rank is case insensitive."""
assert get_severity_rank("critical") == get_severity_rank("Critical")
assert get_severity_rank("HIGH") == get_severity_rank("high")
def test_get_severity_rank_invalid(self):
"""Test invalid severity raises ValueError."""
with pytest.raises(ValueError, match="Unknown severity level"):
get_severity_rank("invalid")
def test_is_valid_severity_valid(self):
"""Test is_valid_severity for valid severities."""
assert is_valid_severity("critical") is True
assert is_valid_severity("high") is True
assert is_valid_severity("medium") is True
assert is_valid_severity("low") is True
assert is_valid_severity("info") is True
def test_is_valid_severity_case_insensitive(self):
"""Test is_valid_severity is case insensitive."""
assert is_valid_severity("Critical") is True
assert is_valid_severity("HIGH") is True
assert is_valid_severity("LoW") is True
def test_is_valid_severity_invalid(self):
"""Test is_valid_severity for invalid severities."""
assert is_valid_severity("invalid") is False
assert is_valid_severity("") is False
assert is_valid_severity("super-critical") is False