mcp-security-framework 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- mcp_security_framework/__init__.py +96 -0
- mcp_security_framework/cli/__init__.py +18 -0
- mcp_security_framework/cli/cert_cli.py +511 -0
- mcp_security_framework/cli/security_cli.py +791 -0
- mcp_security_framework/constants.py +209 -0
- mcp_security_framework/core/__init__.py +61 -0
- mcp_security_framework/core/auth_manager.py +1011 -0
- mcp_security_framework/core/cert_manager.py +1663 -0
- mcp_security_framework/core/permission_manager.py +735 -0
- mcp_security_framework/core/rate_limiter.py +602 -0
- mcp_security_framework/core/security_manager.py +943 -0
- mcp_security_framework/core/ssl_manager.py +735 -0
- mcp_security_framework/examples/__init__.py +75 -0
- mcp_security_framework/examples/django_example.py +615 -0
- mcp_security_framework/examples/fastapi_example.py +472 -0
- mcp_security_framework/examples/flask_example.py +506 -0
- mcp_security_framework/examples/gateway_example.py +803 -0
- mcp_security_framework/examples/microservice_example.py +690 -0
- mcp_security_framework/examples/standalone_example.py +576 -0
- mcp_security_framework/middleware/__init__.py +250 -0
- mcp_security_framework/middleware/auth_middleware.py +292 -0
- mcp_security_framework/middleware/fastapi_auth_middleware.py +447 -0
- mcp_security_framework/middleware/fastapi_middleware.py +757 -0
- mcp_security_framework/middleware/flask_auth_middleware.py +465 -0
- mcp_security_framework/middleware/flask_middleware.py +591 -0
- mcp_security_framework/middleware/mtls_middleware.py +439 -0
- mcp_security_framework/middleware/rate_limit_middleware.py +403 -0
- mcp_security_framework/middleware/security_middleware.py +507 -0
- mcp_security_framework/schemas/__init__.py +109 -0
- mcp_security_framework/schemas/config.py +694 -0
- mcp_security_framework/schemas/models.py +709 -0
- mcp_security_framework/schemas/responses.py +686 -0
- mcp_security_framework/tests/__init__.py +0 -0
- mcp_security_framework/utils/__init__.py +121 -0
- mcp_security_framework/utils/cert_utils.py +525 -0
- mcp_security_framework/utils/crypto_utils.py +475 -0
- mcp_security_framework/utils/validation_utils.py +571 -0
- mcp_security_framework-0.1.0.dist-info/METADATA +411 -0
- mcp_security_framework-0.1.0.dist-info/RECORD +76 -0
- mcp_security_framework-0.1.0.dist-info/WHEEL +5 -0
- mcp_security_framework-0.1.0.dist-info/entry_points.txt +3 -0
- mcp_security_framework-0.1.0.dist-info/top_level.txt +2 -0
- tests/__init__.py +0 -0
- tests/test_cli/__init__.py +0 -0
- tests/test_cli/test_cert_cli.py +379 -0
- tests/test_cli/test_security_cli.py +657 -0
- tests/test_core/__init__.py +0 -0
- tests/test_core/test_auth_manager.py +582 -0
- tests/test_core/test_cert_manager.py +795 -0
- tests/test_core/test_permission_manager.py +395 -0
- tests/test_core/test_rate_limiter.py +626 -0
- tests/test_core/test_security_manager.py +841 -0
- tests/test_core/test_ssl_manager.py +532 -0
- tests/test_examples/__init__.py +8 -0
- tests/test_examples/test_fastapi_example.py +264 -0
- tests/test_examples/test_flask_example.py +238 -0
- tests/test_examples/test_standalone_example.py +292 -0
- tests/test_integration/__init__.py +0 -0
- tests/test_integration/test_auth_flow.py +502 -0
- tests/test_integration/test_certificate_flow.py +527 -0
- tests/test_integration/test_fastapi_integration.py +341 -0
- tests/test_integration/test_flask_integration.py +398 -0
- tests/test_integration/test_standalone_integration.py +493 -0
- tests/test_middleware/__init__.py +0 -0
- tests/test_middleware/test_fastapi_middleware.py +523 -0
- tests/test_middleware/test_flask_middleware.py +582 -0
- tests/test_middleware/test_security_middleware.py +493 -0
- tests/test_schemas/__init__.py +0 -0
- tests/test_schemas/test_config.py +811 -0
- tests/test_schemas/test_models.py +879 -0
- tests/test_schemas/test_responses.py +1054 -0
- tests/test_schemas/test_serialization.py +493 -0
- tests/test_utils/__init__.py +0 -0
- tests/test_utils/test_cert_utils.py +510 -0
- tests/test_utils/test_crypto_utils.py +603 -0
- tests/test_utils/test_validation_utils.py +477 -0
@@ -0,0 +1,811 @@
|
|
1
|
+
"""
|
2
|
+
Configuration Models Test Module
|
3
|
+
|
4
|
+
This module provides comprehensive unit tests for all configuration
|
5
|
+
models in the MCP Security Framework. It tests validation, default
|
6
|
+
values, and edge cases for all configuration classes.
|
7
|
+
|
8
|
+
Test Classes:
|
9
|
+
TestSSLConfig: Tests for SSL/TLS configuration
|
10
|
+
TestAuthConfig: Tests for authentication configuration
|
11
|
+
TestCertificateConfig: Tests for certificate configuration
|
12
|
+
TestPermissionConfig: Tests for permission configuration
|
13
|
+
TestRateLimitConfig: Tests for rate limiting configuration
|
14
|
+
TestLoggingConfig: Tests for logging configuration
|
15
|
+
TestSecurityConfig: Tests for main security configuration
|
16
|
+
TestCAConfig: Tests for CA configuration
|
17
|
+
TestClientCertConfig: Tests for client certificate configuration
|
18
|
+
TestServerCertConfig: Tests for server certificate configuration
|
19
|
+
|
20
|
+
Author: MCP Security Team
|
21
|
+
Version: 1.0.0
|
22
|
+
License: MIT
|
23
|
+
"""
|
24
|
+
|
25
|
+
import pytest
|
26
|
+
from pydantic import ValidationError
|
27
|
+
|
28
|
+
from mcp_security_framework.schemas.config import (
|
29
|
+
AuthConfig,
|
30
|
+
AuthMethod,
|
31
|
+
CAConfig,
|
32
|
+
CertificateConfig,
|
33
|
+
ClientCertConfig,
|
34
|
+
IntermediateCAConfig,
|
35
|
+
LoggingConfig,
|
36
|
+
LogLevel,
|
37
|
+
PermissionConfig,
|
38
|
+
RateLimitConfig,
|
39
|
+
SecurityConfig,
|
40
|
+
ServerCertConfig,
|
41
|
+
SSLConfig,
|
42
|
+
TLSVersion,
|
43
|
+
)
|
44
|
+
|
45
|
+
|
46
|
+
class TestSSLConfig:
|
47
|
+
"""Test suite for SSLConfig class."""
|
48
|
+
|
49
|
+
def test_ssl_config_defaults(self):
|
50
|
+
"""Test SSLConfig with default values."""
|
51
|
+
config = SSLConfig()
|
52
|
+
|
53
|
+
assert config.enabled is False
|
54
|
+
assert config.cert_file is None
|
55
|
+
assert config.key_file is None
|
56
|
+
assert config.ca_cert_file is None
|
57
|
+
assert config.verify_mode == "CERT_REQUIRED"
|
58
|
+
assert config.min_tls_version == TLSVersion.TLS_1_2
|
59
|
+
assert config.max_tls_version is None
|
60
|
+
assert config.cipher_suite is None
|
61
|
+
assert config.check_hostname is True
|
62
|
+
assert config.check_expiry is True
|
63
|
+
assert config.expiry_warning_days == 30
|
64
|
+
|
65
|
+
def test_ssl_config_enabled_without_certificates(self):
|
66
|
+
"""Test SSLConfig validation when enabled without certificates."""
|
67
|
+
with pytest.raises(ValidationError) as exc_info:
|
68
|
+
SSLConfig(enabled=True)
|
69
|
+
|
70
|
+
assert "SSL enabled but certificate and key files are required" in str(
|
71
|
+
exc_info.value
|
72
|
+
)
|
73
|
+
|
74
|
+
def test_ssl_config_invalid_verify_mode(self):
|
75
|
+
"""Test SSLConfig with invalid verify mode."""
|
76
|
+
with pytest.raises(ValidationError) as exc_info:
|
77
|
+
SSLConfig(verify_mode="INVALID_MODE")
|
78
|
+
|
79
|
+
assert "Invalid verify_mode" in str(exc_info.value)
|
80
|
+
|
81
|
+
def test_ssl_config_valid_verify_modes(self):
|
82
|
+
"""Test SSLConfig with valid verify modes."""
|
83
|
+
valid_modes = ["CERT_NONE", "CERT_OPTIONAL", "CERT_REQUIRED"]
|
84
|
+
|
85
|
+
for mode in valid_modes:
|
86
|
+
config = SSLConfig(verify_mode=mode)
|
87
|
+
assert config.verify_mode == mode
|
88
|
+
|
89
|
+
def test_ssl_config_tls_versions(self):
|
90
|
+
"""Test SSLConfig with different TLS versions."""
|
91
|
+
config = SSLConfig(
|
92
|
+
min_tls_version=TLSVersion.TLS_1_3, max_tls_version=TLSVersion.TLS_1_3
|
93
|
+
)
|
94
|
+
|
95
|
+
assert config.min_tls_version == TLSVersion.TLS_1_3
|
96
|
+
assert config.max_tls_version == TLSVersion.TLS_1_3
|
97
|
+
|
98
|
+
def test_ssl_config_expiry_warning_days_validation(self):
|
99
|
+
"""Test SSLConfig expiry warning days validation."""
|
100
|
+
# Valid range
|
101
|
+
config = SSLConfig(expiry_warning_days=1)
|
102
|
+
assert config.expiry_warning_days == 1
|
103
|
+
|
104
|
+
config = SSLConfig(expiry_warning_days=365)
|
105
|
+
assert config.expiry_warning_days == 365
|
106
|
+
|
107
|
+
# Invalid range
|
108
|
+
with pytest.raises(ValidationError):
|
109
|
+
SSLConfig(expiry_warning_days=0)
|
110
|
+
|
111
|
+
with pytest.raises(ValidationError):
|
112
|
+
SSLConfig(expiry_warning_days=366)
|
113
|
+
|
114
|
+
|
115
|
+
class TestAuthConfig:
|
116
|
+
"""Test suite for AuthConfig class."""
|
117
|
+
|
118
|
+
def test_auth_config_defaults(self):
|
119
|
+
"""Test AuthConfig with default values."""
|
120
|
+
config = AuthConfig()
|
121
|
+
|
122
|
+
assert config.enabled is True
|
123
|
+
assert config.methods == [AuthMethod.API_KEY]
|
124
|
+
assert config.api_keys == {}
|
125
|
+
assert config.jwt_secret is None
|
126
|
+
assert config.jwt_algorithm == "HS256"
|
127
|
+
assert config.jwt_expiry_hours == 24
|
128
|
+
assert config.certificate_auth is False
|
129
|
+
assert config.certificate_roles_oid == "1.3.6.1.4.1.99999.1.1"
|
130
|
+
assert config.certificate_permissions_oid == "1.3.6.1.4.1.99999.1.2"
|
131
|
+
assert config.basic_auth is False
|
132
|
+
assert config.oauth2_config is None
|
133
|
+
|
134
|
+
def test_auth_config_enabled_without_methods(self):
|
135
|
+
"""Test AuthConfig validation when enabled without methods."""
|
136
|
+
with pytest.raises(ValidationError) as exc_info:
|
137
|
+
AuthConfig(enabled=True, methods=[])
|
138
|
+
|
139
|
+
assert "Authentication enabled but no methods specified" in str(exc_info.value)
|
140
|
+
|
141
|
+
def test_auth_config_jwt_without_secret(self):
|
142
|
+
"""Test AuthConfig validation when JWT enabled without secret."""
|
143
|
+
with pytest.raises(ValidationError) as exc_info:
|
144
|
+
AuthConfig(methods=[AuthMethod.JWT])
|
145
|
+
|
146
|
+
assert "JWT authentication enabled but no JWT secret provided" in str(
|
147
|
+
exc_info.value
|
148
|
+
)
|
149
|
+
|
150
|
+
def test_auth_config_invalid_jwt_algorithm(self):
|
151
|
+
"""Test AuthConfig with invalid JWT algorithm."""
|
152
|
+
with pytest.raises(ValidationError) as exc_info:
|
153
|
+
AuthConfig(jwt_algorithm="INVALID_ALG")
|
154
|
+
|
155
|
+
assert "Invalid JWT algorithm" in str(exc_info.value)
|
156
|
+
|
157
|
+
def test_auth_config_valid_jwt_algorithms(self):
|
158
|
+
"""Test AuthConfig with valid JWT algorithms."""
|
159
|
+
valid_algorithms = ["HS256", "HS384", "HS512", "RS256", "RS384", "RS512"]
|
160
|
+
|
161
|
+
for algorithm in valid_algorithms:
|
162
|
+
config = AuthConfig(jwt_algorithm=algorithm)
|
163
|
+
assert config.jwt_algorithm == algorithm
|
164
|
+
|
165
|
+
def test_auth_config_jwt_expiry_hours_validation(self):
|
166
|
+
"""Test AuthConfig JWT expiry hours validation."""
|
167
|
+
# Valid range
|
168
|
+
config = AuthConfig(jwt_expiry_hours=1)
|
169
|
+
assert config.jwt_expiry_hours == 1
|
170
|
+
|
171
|
+
config = AuthConfig(jwt_expiry_hours=8760) # 1 year
|
172
|
+
assert config.jwt_expiry_hours == 8760
|
173
|
+
|
174
|
+
# Invalid range
|
175
|
+
with pytest.raises(ValidationError):
|
176
|
+
AuthConfig(jwt_expiry_hours=0)
|
177
|
+
|
178
|
+
with pytest.raises(ValidationError):
|
179
|
+
AuthConfig(jwt_expiry_hours=8761)
|
180
|
+
|
181
|
+
def test_auth_config_api_keys(self):
|
182
|
+
"""Test AuthConfig with API keys."""
|
183
|
+
api_keys = {"key1": "user1", "key2": "user2"}
|
184
|
+
config = AuthConfig(api_keys=api_keys)
|
185
|
+
|
186
|
+
assert config.api_keys == api_keys
|
187
|
+
assert len(config.api_keys) == 2
|
188
|
+
|
189
|
+
|
190
|
+
class TestCertificateConfig:
|
191
|
+
"""Test suite for CertificateConfig class."""
|
192
|
+
|
193
|
+
def test_certificate_config_defaults(self):
|
194
|
+
"""Test CertificateConfig with default values."""
|
195
|
+
config = CertificateConfig()
|
196
|
+
|
197
|
+
assert config.enabled is False
|
198
|
+
assert config.ca_cert_path is None
|
199
|
+
assert config.ca_key_path is None
|
200
|
+
assert config.cert_storage_path == "./certs"
|
201
|
+
assert config.key_storage_path == "./keys"
|
202
|
+
assert config.default_validity_days == 365
|
203
|
+
assert config.key_size == 2048
|
204
|
+
assert config.hash_algorithm == "sha256"
|
205
|
+
assert config.crl_enabled is False
|
206
|
+
assert config.crl_path is None
|
207
|
+
assert config.crl_validity_days == 30
|
208
|
+
assert config.auto_renewal is False
|
209
|
+
assert config.renewal_threshold_days == 30
|
210
|
+
|
211
|
+
def test_certificate_config_enabled_without_ca(self):
|
212
|
+
"""Test CertificateConfig validation when enabled without CA."""
|
213
|
+
with pytest.raises(ValidationError) as exc_info:
|
214
|
+
CertificateConfig(enabled=True)
|
215
|
+
|
216
|
+
assert (
|
217
|
+
"Certificate management enabled but CA certificate and key paths are required"
|
218
|
+
in str(exc_info.value)
|
219
|
+
)
|
220
|
+
|
221
|
+
def test_certificate_config_crl_enabled_without_path(self):
|
222
|
+
"""Test CertificateConfig validation when CRL enabled without path."""
|
223
|
+
with pytest.raises(ValidationError) as exc_info:
|
224
|
+
CertificateConfig(crl_enabled=True)
|
225
|
+
|
226
|
+
assert "CRL enabled but CRL path is required" in str(exc_info.value)
|
227
|
+
|
228
|
+
def test_certificate_config_invalid_hash_algorithm(self):
|
229
|
+
"""Test CertificateConfig with invalid hash algorithm."""
|
230
|
+
with pytest.raises(ValidationError) as exc_info:
|
231
|
+
CertificateConfig(hash_algorithm="INVALID_HASH")
|
232
|
+
|
233
|
+
assert "Invalid hash algorithm" in str(exc_info.value)
|
234
|
+
|
235
|
+
def test_certificate_config_valid_hash_algorithms(self):
|
236
|
+
"""Test CertificateConfig with valid hash algorithms."""
|
237
|
+
valid_algorithms = ["sha1", "sha256", "sha384", "sha512"]
|
238
|
+
|
239
|
+
for algorithm in valid_algorithms:
|
240
|
+
config = CertificateConfig(hash_algorithm=algorithm)
|
241
|
+
assert config.hash_algorithm == algorithm
|
242
|
+
|
243
|
+
def test_certificate_config_key_size_validation(self):
|
244
|
+
"""Test CertificateConfig key size validation."""
|
245
|
+
# Valid range
|
246
|
+
config = CertificateConfig(key_size=1024)
|
247
|
+
assert config.key_size == 1024
|
248
|
+
|
249
|
+
config = CertificateConfig(key_size=4096)
|
250
|
+
assert config.key_size == 4096
|
251
|
+
|
252
|
+
# Invalid range
|
253
|
+
with pytest.raises(ValidationError):
|
254
|
+
CertificateConfig(key_size=512)
|
255
|
+
|
256
|
+
with pytest.raises(ValidationError):
|
257
|
+
CertificateConfig(key_size=8192)
|
258
|
+
|
259
|
+
def test_certificate_config_validity_days_validation(self):
|
260
|
+
"""Test CertificateConfig validity days validation."""
|
261
|
+
# Valid range
|
262
|
+
config = CertificateConfig(default_validity_days=1)
|
263
|
+
assert config.default_validity_days == 1
|
264
|
+
|
265
|
+
config = CertificateConfig(default_validity_days=3650) # 10 years
|
266
|
+
assert config.default_validity_days == 3650
|
267
|
+
|
268
|
+
# Invalid range
|
269
|
+
with pytest.raises(ValidationError):
|
270
|
+
CertificateConfig(default_validity_days=0)
|
271
|
+
|
272
|
+
with pytest.raises(ValidationError):
|
273
|
+
CertificateConfig(default_validity_days=3651)
|
274
|
+
|
275
|
+
|
276
|
+
class TestPermissionConfig:
|
277
|
+
"""Test suite for PermissionConfig class."""
|
278
|
+
|
279
|
+
def test_permission_config_defaults(self):
|
280
|
+
"""Test PermissionConfig with default values."""
|
281
|
+
config = PermissionConfig()
|
282
|
+
|
283
|
+
assert config.enabled is True
|
284
|
+
assert config.roles_file is None
|
285
|
+
assert config.default_role == "guest"
|
286
|
+
assert config.admin_role == "admin"
|
287
|
+
assert config.role_hierarchy == {}
|
288
|
+
assert config.permission_cache_enabled is True
|
289
|
+
assert config.permission_cache_ttl == 300
|
290
|
+
assert config.wildcard_permissions is False
|
291
|
+
assert config.strict_mode is True
|
292
|
+
|
293
|
+
def test_permission_config_cache_ttl_validation(self):
|
294
|
+
"""Test PermissionConfig cache TTL validation."""
|
295
|
+
# Valid range
|
296
|
+
config = PermissionConfig(permission_cache_ttl=1)
|
297
|
+
assert config.permission_cache_ttl == 1
|
298
|
+
|
299
|
+
config = PermissionConfig(permission_cache_ttl=3600)
|
300
|
+
assert config.permission_cache_ttl == 3600
|
301
|
+
|
302
|
+
# Invalid range
|
303
|
+
with pytest.raises(ValidationError):
|
304
|
+
PermissionConfig(permission_cache_ttl=0)
|
305
|
+
|
306
|
+
with pytest.raises(ValidationError):
|
307
|
+
PermissionConfig(permission_cache_ttl=3601)
|
308
|
+
|
309
|
+
def test_permission_config_role_hierarchy(self):
|
310
|
+
"""Test PermissionConfig with role hierarchy."""
|
311
|
+
hierarchy = {"admin": ["user", "moderator"], "moderator": ["user"], "user": []}
|
312
|
+
config = PermissionConfig(role_hierarchy=hierarchy)
|
313
|
+
|
314
|
+
assert config.role_hierarchy == hierarchy
|
315
|
+
assert len(config.role_hierarchy) == 3
|
316
|
+
|
317
|
+
|
318
|
+
class TestRateLimitConfig:
|
319
|
+
"""Test suite for RateLimitConfig class."""
|
320
|
+
|
321
|
+
def test_rate_limit_config_defaults(self):
|
322
|
+
"""Test RateLimitConfig with default values."""
|
323
|
+
config = RateLimitConfig()
|
324
|
+
|
325
|
+
assert config.enabled is True
|
326
|
+
assert config.default_requests_per_minute == 60
|
327
|
+
assert config.default_requests_per_hour == 1000
|
328
|
+
assert config.burst_limit == 2
|
329
|
+
assert config.window_size_seconds == 60
|
330
|
+
assert config.storage_backend == "memory"
|
331
|
+
assert config.redis_config is None
|
332
|
+
assert config.cleanup_interval == 300
|
333
|
+
assert config.exempt_paths == []
|
334
|
+
assert config.exempt_roles == []
|
335
|
+
|
336
|
+
def test_rate_limit_config_invalid_storage_backend(self):
|
337
|
+
"""Test RateLimitConfig with invalid storage backend."""
|
338
|
+
with pytest.raises(ValidationError) as exc_info:
|
339
|
+
RateLimitConfig(storage_backend="INVALID_BACKEND")
|
340
|
+
|
341
|
+
assert "Invalid storage backend" in str(exc_info.value)
|
342
|
+
|
343
|
+
def test_rate_limit_config_valid_storage_backends(self):
|
344
|
+
"""Test RateLimitConfig with valid storage backends."""
|
345
|
+
valid_backends = ["memory", "redis", "database"]
|
346
|
+
|
347
|
+
for backend in valid_backends:
|
348
|
+
config = RateLimitConfig(storage_backend=backend)
|
349
|
+
assert config.storage_backend == backend
|
350
|
+
|
351
|
+
def test_rate_limit_config_requests_per_minute_validation(self):
|
352
|
+
"""Test RateLimitConfig requests per minute validation."""
|
353
|
+
# Valid range
|
354
|
+
config = RateLimitConfig(default_requests_per_minute=1)
|
355
|
+
assert config.default_requests_per_minute == 1
|
356
|
+
|
357
|
+
config = RateLimitConfig(default_requests_per_minute=10000)
|
358
|
+
assert config.default_requests_per_minute == 10000
|
359
|
+
|
360
|
+
# Invalid range
|
361
|
+
with pytest.raises(ValidationError):
|
362
|
+
RateLimitConfig(default_requests_per_minute=0)
|
363
|
+
|
364
|
+
with pytest.raises(ValidationError):
|
365
|
+
RateLimitConfig(default_requests_per_minute=10001)
|
366
|
+
|
367
|
+
def test_rate_limit_config_burst_limit_validation(self):
|
368
|
+
"""Test RateLimitConfig burst limit validation."""
|
369
|
+
# Valid range
|
370
|
+
config = RateLimitConfig(burst_limit=1)
|
371
|
+
assert config.burst_limit == 1
|
372
|
+
|
373
|
+
config = RateLimitConfig(burst_limit=10)
|
374
|
+
assert config.burst_limit == 10
|
375
|
+
|
376
|
+
# Invalid range
|
377
|
+
with pytest.raises(ValidationError):
|
378
|
+
RateLimitConfig(burst_limit=0)
|
379
|
+
|
380
|
+
with pytest.raises(ValidationError):
|
381
|
+
RateLimitConfig(burst_limit=11)
|
382
|
+
|
383
|
+
|
384
|
+
class TestLoggingConfig:
|
385
|
+
"""Test suite for LoggingConfig class."""
|
386
|
+
|
387
|
+
def test_logging_config_defaults(self):
|
388
|
+
"""Test LoggingConfig with default values."""
|
389
|
+
config = LoggingConfig()
|
390
|
+
|
391
|
+
assert config.enabled is True
|
392
|
+
assert config.level == LogLevel.INFO
|
393
|
+
assert config.format == "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
|
394
|
+
assert config.date_format == "%Y-%m-%d %H:%M:%S"
|
395
|
+
assert config.file_path is None
|
396
|
+
assert config.max_file_size == 10
|
397
|
+
assert config.backup_count == 5
|
398
|
+
assert config.console_output is True
|
399
|
+
assert config.json_format is False
|
400
|
+
assert config.include_timestamp is True
|
401
|
+
assert config.include_level is True
|
402
|
+
assert config.include_module is True
|
403
|
+
|
404
|
+
def test_logging_config_file_size_validation(self):
|
405
|
+
"""Test LoggingConfig file size validation."""
|
406
|
+
# Valid range
|
407
|
+
config = LoggingConfig(max_file_size=1)
|
408
|
+
assert config.max_file_size == 1
|
409
|
+
|
410
|
+
config = LoggingConfig(max_file_size=1000)
|
411
|
+
assert config.max_file_size == 1000
|
412
|
+
|
413
|
+
# Invalid range
|
414
|
+
with pytest.raises(ValidationError):
|
415
|
+
LoggingConfig(max_file_size=0)
|
416
|
+
|
417
|
+
with pytest.raises(ValidationError):
|
418
|
+
LoggingConfig(max_file_size=1001)
|
419
|
+
|
420
|
+
def test_logging_config_backup_count_validation(self):
|
421
|
+
"""Test LoggingConfig backup count validation."""
|
422
|
+
# Valid range
|
423
|
+
config = LoggingConfig(backup_count=0)
|
424
|
+
assert config.backup_count == 0
|
425
|
+
|
426
|
+
config = LoggingConfig(backup_count=100)
|
427
|
+
assert config.backup_count == 100
|
428
|
+
|
429
|
+
# Invalid range
|
430
|
+
with pytest.raises(ValidationError):
|
431
|
+
LoggingConfig(backup_count=-1)
|
432
|
+
|
433
|
+
with pytest.raises(ValidationError):
|
434
|
+
LoggingConfig(backup_count=101)
|
435
|
+
|
436
|
+
|
437
|
+
class TestSecurityConfig:
|
438
|
+
"""Test suite for SecurityConfig class."""
|
439
|
+
|
440
|
+
def test_security_config_defaults(self):
|
441
|
+
"""Test SecurityConfig with default values."""
|
442
|
+
config = SecurityConfig()
|
443
|
+
|
444
|
+
assert config.debug is False
|
445
|
+
assert config.environment == "dev"
|
446
|
+
assert config.version == "1.0.0"
|
447
|
+
assert isinstance(config.ssl, SSLConfig)
|
448
|
+
assert isinstance(config.auth, AuthConfig)
|
449
|
+
assert isinstance(config.certificates, CertificateConfig)
|
450
|
+
assert isinstance(config.permissions, PermissionConfig)
|
451
|
+
assert isinstance(config.rate_limit, RateLimitConfig)
|
452
|
+
assert isinstance(config.logging, LoggingConfig)
|
453
|
+
|
454
|
+
def test_security_config_invalid_environment(self):
|
455
|
+
"""Test SecurityConfig with invalid environment."""
|
456
|
+
with pytest.raises(ValidationError) as exc_info:
|
457
|
+
SecurityConfig(environment="INVALID_ENV")
|
458
|
+
|
459
|
+
assert "Invalid environment" in str(exc_info.value)
|
460
|
+
|
461
|
+
def test_security_config_valid_environments(self):
|
462
|
+
"""Test SecurityConfig with valid environments."""
|
463
|
+
valid_environments = [
|
464
|
+
"dev",
|
465
|
+
"development",
|
466
|
+
"staging",
|
467
|
+
"prod",
|
468
|
+
"production",
|
469
|
+
"test",
|
470
|
+
]
|
471
|
+
|
472
|
+
for env in valid_environments:
|
473
|
+
config = SecurityConfig(environment=env)
|
474
|
+
assert config.environment == env
|
475
|
+
|
476
|
+
def test_security_config_custom_components(self):
|
477
|
+
"""Test SecurityConfig with custom component configurations."""
|
478
|
+
# Create temporary files for testing
|
479
|
+
import os
|
480
|
+
import tempfile
|
481
|
+
|
482
|
+
with tempfile.NamedTemporaryFile(
|
483
|
+
mode="w", suffix=".crt", delete=False
|
484
|
+
) as cert_file:
|
485
|
+
cert_file.write("test certificate content")
|
486
|
+
cert_path = cert_file.name
|
487
|
+
|
488
|
+
with tempfile.NamedTemporaryFile(
|
489
|
+
mode="w", suffix=".key", delete=False
|
490
|
+
) as key_file:
|
491
|
+
key_file.write("test key content")
|
492
|
+
key_path = key_file.name
|
493
|
+
|
494
|
+
try:
|
495
|
+
ssl_config = SSLConfig(enabled=True, cert_file=cert_path, key_file=key_path)
|
496
|
+
finally:
|
497
|
+
# Clean up temporary files
|
498
|
+
os.unlink(cert_path)
|
499
|
+
os.unlink(key_path)
|
500
|
+
auth_config = AuthConfig(enabled=True, methods=[AuthMethod.API_KEY])
|
501
|
+
|
502
|
+
config = SecurityConfig(
|
503
|
+
ssl=ssl_config, auth=auth_config, debug=True, environment="prod"
|
504
|
+
)
|
505
|
+
|
506
|
+
assert config.ssl == ssl_config
|
507
|
+
assert config.auth == auth_config
|
508
|
+
assert config.debug is True
|
509
|
+
assert config.environment == "prod"
|
510
|
+
|
511
|
+
|
512
|
+
class TestCAConfig:
|
513
|
+
"""Test suite for CAConfig class."""
|
514
|
+
|
515
|
+
def test_ca_config_required_fields(self):
|
516
|
+
"""Test CAConfig with required fields."""
|
517
|
+
config = CAConfig(common_name="Test CA", organization="Test Organization")
|
518
|
+
|
519
|
+
assert config.common_name == "Test CA"
|
520
|
+
assert config.organization == "Test Organization"
|
521
|
+
assert config.country == "US"
|
522
|
+
assert config.validity_years == 10
|
523
|
+
assert config.key_size == 4096
|
524
|
+
assert config.hash_algorithm == "sha256"
|
525
|
+
|
526
|
+
def test_ca_config_all_fields(self):
|
527
|
+
"""Test CAConfig with all fields."""
|
528
|
+
config = CAConfig(
|
529
|
+
common_name="Test CA",
|
530
|
+
organization="Test Organization",
|
531
|
+
organizational_unit="IT Department",
|
532
|
+
country="CA",
|
533
|
+
state="Ontario",
|
534
|
+
locality="Toronto",
|
535
|
+
email="ca@test.com",
|
536
|
+
validity_years=5,
|
537
|
+
key_size=2048,
|
538
|
+
hash_algorithm="sha384",
|
539
|
+
)
|
540
|
+
|
541
|
+
assert config.common_name == "Test CA"
|
542
|
+
assert config.organization == "Test Organization"
|
543
|
+
assert config.organizational_unit == "IT Department"
|
544
|
+
assert config.country == "CA"
|
545
|
+
assert config.state == "Ontario"
|
546
|
+
assert config.locality == "Toronto"
|
547
|
+
assert config.email == "ca@test.com"
|
548
|
+
assert config.validity_years == 5
|
549
|
+
assert config.key_size == 2048
|
550
|
+
assert config.hash_algorithm == "sha384"
|
551
|
+
|
552
|
+
def test_ca_config_validity_years_validation(self):
|
553
|
+
"""Test CAConfig validity years validation."""
|
554
|
+
# Valid range
|
555
|
+
config = CAConfig(
|
556
|
+
common_name="Test CA", organization="Test Organization", validity_years=1
|
557
|
+
)
|
558
|
+
assert config.validity_years == 1
|
559
|
+
|
560
|
+
config = CAConfig(
|
561
|
+
common_name="Test CA", organization="Test Organization", validity_years=50
|
562
|
+
)
|
563
|
+
assert config.validity_years == 50
|
564
|
+
|
565
|
+
# Invalid range
|
566
|
+
with pytest.raises(ValidationError):
|
567
|
+
CAConfig(
|
568
|
+
common_name="Test CA",
|
569
|
+
organization="Test Organization",
|
570
|
+
validity_years=0,
|
571
|
+
)
|
572
|
+
|
573
|
+
with pytest.raises(ValidationError):
|
574
|
+
CAConfig(
|
575
|
+
common_name="Test CA",
|
576
|
+
organization="Test Organization",
|
577
|
+
validity_years=51,
|
578
|
+
)
|
579
|
+
|
580
|
+
def test_ca_config_key_size_validation(self):
|
581
|
+
"""Test CAConfig key size validation."""
|
582
|
+
# Valid range
|
583
|
+
config = CAConfig(
|
584
|
+
common_name="Test CA", organization="Test Organization", key_size=2048
|
585
|
+
)
|
586
|
+
assert config.key_size == 2048
|
587
|
+
|
588
|
+
config = CAConfig(
|
589
|
+
common_name="Test CA", organization="Test Organization", key_size=8192
|
590
|
+
)
|
591
|
+
assert config.key_size == 8192
|
592
|
+
|
593
|
+
# Invalid range
|
594
|
+
with pytest.raises(ValidationError):
|
595
|
+
CAConfig(
|
596
|
+
common_name="Test CA", organization="Test Organization", key_size=1024
|
597
|
+
)
|
598
|
+
|
599
|
+
with pytest.raises(ValidationError):
|
600
|
+
CAConfig(
|
601
|
+
common_name="Test CA", organization="Test Organization", key_size=16384
|
602
|
+
)
|
603
|
+
|
604
|
+
|
605
|
+
class TestClientCertConfig:
|
606
|
+
"""Test suite for ClientCertConfig class."""
|
607
|
+
|
608
|
+
def test_client_cert_config_required_fields(self):
|
609
|
+
"""Test ClientCertConfig with required fields."""
|
610
|
+
config = ClientCertConfig(
|
611
|
+
common_name="test.client.com",
|
612
|
+
organization="Test Organization",
|
613
|
+
ca_cert_path="/path/to/ca.crt",
|
614
|
+
ca_key_path="/path/to/ca.key",
|
615
|
+
)
|
616
|
+
|
617
|
+
assert config.common_name == "test.client.com"
|
618
|
+
assert config.organization == "Test Organization"
|
619
|
+
assert config.ca_cert_path == "/path/to/ca.crt"
|
620
|
+
assert config.ca_key_path == "/path/to/ca.key"
|
621
|
+
assert config.country == "US"
|
622
|
+
assert config.validity_days == 365
|
623
|
+
assert config.key_size == 2048
|
624
|
+
assert config.roles == []
|
625
|
+
assert config.permissions == []
|
626
|
+
|
627
|
+
def test_client_cert_config_all_fields(self):
|
628
|
+
"""Test ClientCertConfig with all fields."""
|
629
|
+
config = ClientCertConfig(
|
630
|
+
common_name="test.client.com",
|
631
|
+
organization="Test Organization",
|
632
|
+
organizational_unit="Development",
|
633
|
+
country="CA",
|
634
|
+
state="Ontario",
|
635
|
+
locality="Toronto",
|
636
|
+
email="client@test.com",
|
637
|
+
validity_days=730,
|
638
|
+
key_size=4096,
|
639
|
+
roles=["developer", "tester"],
|
640
|
+
permissions=["read", "write"],
|
641
|
+
ca_cert_path="/path/to/ca.crt",
|
642
|
+
ca_key_path="/path/to/ca.key",
|
643
|
+
)
|
644
|
+
|
645
|
+
assert config.common_name == "test.client.com"
|
646
|
+
assert config.organization == "Test Organization"
|
647
|
+
assert config.organizational_unit == "Development"
|
648
|
+
assert config.country == "CA"
|
649
|
+
assert config.state == "Ontario"
|
650
|
+
assert config.locality == "Toronto"
|
651
|
+
assert config.email == "client@test.com"
|
652
|
+
assert config.validity_days == 730
|
653
|
+
assert config.key_size == 4096
|
654
|
+
assert config.roles == ["developer", "tester"]
|
655
|
+
assert config.permissions == ["read", "write"]
|
656
|
+
assert config.ca_cert_path == "/path/to/ca.crt"
|
657
|
+
assert config.ca_key_path == "/path/to/ca.key"
|
658
|
+
|
659
|
+
def test_client_cert_config_validity_days_validation(self):
|
660
|
+
"""Test ClientCertConfig validity days validation."""
|
661
|
+
# Valid range
|
662
|
+
config = ClientCertConfig(
|
663
|
+
common_name="test.client.com",
|
664
|
+
organization="Test Organization",
|
665
|
+
ca_cert_path="/path/to/ca.crt",
|
666
|
+
ca_key_path="/path/to/ca.key",
|
667
|
+
validity_days=1,
|
668
|
+
)
|
669
|
+
assert config.validity_days == 1
|
670
|
+
|
671
|
+
config = ClientCertConfig(
|
672
|
+
common_name="test.client.com",
|
673
|
+
organization="Test Organization",
|
674
|
+
ca_cert_path="/path/to/ca.crt",
|
675
|
+
ca_key_path="/path/to/ca.key",
|
676
|
+
validity_days=3650,
|
677
|
+
)
|
678
|
+
assert config.validity_days == 3650
|
679
|
+
|
680
|
+
# Invalid range
|
681
|
+
with pytest.raises(ValidationError):
|
682
|
+
ClientCertConfig(
|
683
|
+
common_name="test.client.com",
|
684
|
+
organization="Test Organization",
|
685
|
+
ca_cert_path="/path/to/ca.crt",
|
686
|
+
ca_key_path="/path/to/ca.key",
|
687
|
+
validity_days=0,
|
688
|
+
)
|
689
|
+
|
690
|
+
with pytest.raises(ValidationError):
|
691
|
+
ClientCertConfig(
|
692
|
+
common_name="test.client.com",
|
693
|
+
organization="Test Organization",
|
694
|
+
ca_cert_path="/path/to/ca.crt",
|
695
|
+
ca_key_path="/path/to/ca.key",
|
696
|
+
validity_days=3651,
|
697
|
+
)
|
698
|
+
|
699
|
+
|
700
|
+
class TestServerCertConfig:
|
701
|
+
"""Test suite for ServerCertConfig class."""
|
702
|
+
|
703
|
+
def test_server_cert_config_inheritance(self):
|
704
|
+
"""Test ServerCertConfig inheritance from ClientCertConfig."""
|
705
|
+
config = ServerCertConfig(
|
706
|
+
common_name="test.server.com",
|
707
|
+
organization="Test Organization",
|
708
|
+
ca_cert_path="/path/to/ca.crt",
|
709
|
+
ca_key_path="/path/to/ca.key",
|
710
|
+
)
|
711
|
+
|
712
|
+
# Inherited fields
|
713
|
+
assert config.common_name == "test.server.com"
|
714
|
+
assert config.organization == "Test Organization"
|
715
|
+
assert config.ca_cert_path == "/path/to/ca.crt"
|
716
|
+
assert config.ca_key_path == "/path/to/ca.key"
|
717
|
+
|
718
|
+
# Server-specific fields
|
719
|
+
assert config.subject_alt_names == []
|
720
|
+
assert config.key_usage == ["digitalSignature", "keyEncipherment"]
|
721
|
+
assert config.extended_key_usage == ["serverAuth"]
|
722
|
+
|
723
|
+
def test_server_cert_config_with_san(self):
|
724
|
+
"""Test ServerCertConfig with subject alternative names."""
|
725
|
+
config = ServerCertConfig(
|
726
|
+
common_name="test.server.com",
|
727
|
+
organization="Test Organization",
|
728
|
+
ca_cert_path="/path/to/ca.crt",
|
729
|
+
ca_key_path="/path/to/ca.key",
|
730
|
+
subject_alt_names=["*.test.com", "test.com", "api.test.com"],
|
731
|
+
)
|
732
|
+
|
733
|
+
assert config.subject_alt_names == ["*.test.com", "test.com", "api.test.com"]
|
734
|
+
|
735
|
+
def test_server_cert_config_custom_key_usage(self):
|
736
|
+
"""Test ServerCertConfig with custom key usage."""
|
737
|
+
config = ServerCertConfig(
|
738
|
+
common_name="test.server.com",
|
739
|
+
organization="Test Organization",
|
740
|
+
ca_cert_path="/path/to/ca.crt",
|
741
|
+
ca_key_path="/path/to/ca.key",
|
742
|
+
key_usage=["digitalSignature"],
|
743
|
+
extended_key_usage=["serverAuth", "clientAuth"],
|
744
|
+
)
|
745
|
+
|
746
|
+
assert config.key_usage == ["digitalSignature"]
|
747
|
+
assert config.extended_key_usage == ["serverAuth", "clientAuth"]
|
748
|
+
|
749
|
+
|
750
|
+
class TestIntermediateCAConfig:
|
751
|
+
"""Test suite for IntermediateCAConfig class."""
|
752
|
+
|
753
|
+
def test_intermediate_ca_config_inheritance(self):
|
754
|
+
"""Test IntermediateCAConfig inheritance from CAConfig."""
|
755
|
+
config = IntermediateCAConfig(
|
756
|
+
common_name="Intermediate CA",
|
757
|
+
organization="Test Organization",
|
758
|
+
parent_ca_cert="/path/to/parent.crt",
|
759
|
+
parent_ca_key="/path/to/parent.key",
|
760
|
+
)
|
761
|
+
|
762
|
+
# Inherited fields
|
763
|
+
assert config.common_name == "Intermediate CA"
|
764
|
+
assert config.organization == "Test Organization"
|
765
|
+
assert config.validity_years == 10
|
766
|
+
assert config.key_size == 4096
|
767
|
+
|
768
|
+
# Intermediate-specific fields
|
769
|
+
assert config.parent_ca_cert == "/path/to/parent.crt"
|
770
|
+
assert config.parent_ca_key == "/path/to/parent.key"
|
771
|
+
assert config.path_length == 0
|
772
|
+
|
773
|
+
def test_intermediate_ca_config_path_length_validation(self):
|
774
|
+
"""Test IntermediateCAConfig path length validation."""
|
775
|
+
# Valid range
|
776
|
+
config = IntermediateCAConfig(
|
777
|
+
common_name="Intermediate CA",
|
778
|
+
organization="Test Organization",
|
779
|
+
parent_ca_cert="/path/to/parent.crt",
|
780
|
+
parent_ca_key="/path/to/parent.key",
|
781
|
+
path_length=0,
|
782
|
+
)
|
783
|
+
assert config.path_length == 0
|
784
|
+
|
785
|
+
config = IntermediateCAConfig(
|
786
|
+
common_name="Intermediate CA",
|
787
|
+
organization="Test Organization",
|
788
|
+
parent_ca_cert="/path/to/parent.crt",
|
789
|
+
parent_ca_key="/path/to/parent.key",
|
790
|
+
path_length=10,
|
791
|
+
)
|
792
|
+
assert config.path_length == 10
|
793
|
+
|
794
|
+
# Invalid range
|
795
|
+
with pytest.raises(ValidationError):
|
796
|
+
IntermediateCAConfig(
|
797
|
+
common_name="Intermediate CA",
|
798
|
+
organization="Test Organization",
|
799
|
+
parent_ca_cert="/path/to/parent.crt",
|
800
|
+
parent_ca_key="/path/to/parent.key",
|
801
|
+
path_length=-1,
|
802
|
+
)
|
803
|
+
|
804
|
+
with pytest.raises(ValidationError):
|
805
|
+
IntermediateCAConfig(
|
806
|
+
common_name="Intermediate CA",
|
807
|
+
organization="Test Organization",
|
808
|
+
parent_ca_cert="/path/to/parent.crt",
|
809
|
+
parent_ca_key="/path/to/parent.key",
|
810
|
+
path_length=11,
|
811
|
+
)
|