mcp-security-framework 0.1.0__py3-none-any.whl → 1.1.1__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 +26 -15
- mcp_security_framework/cli/__init__.py +1 -1
- mcp_security_framework/cli/cert_cli.py +233 -197
- mcp_security_framework/cli/security_cli.py +324 -234
- mcp_security_framework/constants.py +21 -27
- mcp_security_framework/core/auth_manager.py +49 -20
- mcp_security_framework/core/cert_manager.py +398 -104
- mcp_security_framework/core/permission_manager.py +13 -9
- mcp_security_framework/core/rate_limiter.py +10 -0
- mcp_security_framework/core/security_manager.py +286 -229
- mcp_security_framework/examples/__init__.py +6 -0
- mcp_security_framework/examples/comprehensive_example.py +954 -0
- mcp_security_framework/examples/django_example.py +276 -202
- mcp_security_framework/examples/fastapi_example.py +897 -393
- mcp_security_framework/examples/flask_example.py +311 -200
- mcp_security_framework/examples/gateway_example.py +373 -214
- mcp_security_framework/examples/microservice_example.py +337 -172
- mcp_security_framework/examples/standalone_example.py +719 -478
- mcp_security_framework/examples/test_all_examples.py +572 -0
- mcp_security_framework/middleware/__init__.py +46 -55
- mcp_security_framework/middleware/auth_middleware.py +62 -63
- mcp_security_framework/middleware/fastapi_auth_middleware.py +179 -110
- mcp_security_framework/middleware/fastapi_middleware.py +156 -148
- mcp_security_framework/middleware/flask_auth_middleware.py +267 -107
- mcp_security_framework/middleware/flask_middleware.py +183 -157
- mcp_security_framework/middleware/mtls_middleware.py +106 -117
- mcp_security_framework/middleware/rate_limit_middleware.py +105 -101
- mcp_security_framework/middleware/security_middleware.py +109 -124
- mcp_security_framework/schemas/config.py +2 -1
- mcp_security_framework/schemas/models.py +19 -6
- mcp_security_framework/utils/cert_utils.py +14 -8
- mcp_security_framework/utils/datetime_compat.py +116 -0
- {mcp_security_framework-0.1.0.dist-info → mcp_security_framework-1.1.1.dist-info}/METADATA +2 -1
- mcp_security_framework-1.1.1.dist-info/RECORD +84 -0
- tests/conftest.py +303 -0
- tests/test_cli/test_cert_cli.py +194 -174
- tests/test_cli/test_security_cli.py +274 -247
- tests/test_core/test_cert_manager.py +33 -19
- tests/test_core/test_security_manager.py +2 -2
- tests/test_examples/test_comprehensive_example.py +613 -0
- tests/test_examples/test_fastapi_example.py +290 -169
- tests/test_examples/test_flask_example.py +304 -162
- tests/test_examples/test_standalone_example.py +106 -168
- tests/test_integration/test_auth_flow.py +214 -198
- tests/test_integration/test_certificate_flow.py +181 -150
- tests/test_integration/test_fastapi_integration.py +140 -149
- tests/test_integration/test_flask_integration.py +144 -141
- tests/test_integration/test_standalone_integration.py +331 -300
- tests/test_middleware/test_fastapi_auth_middleware.py +745 -0
- tests/test_middleware/test_fastapi_middleware.py +147 -132
- tests/test_middleware/test_flask_auth_middleware.py +696 -0
- tests/test_middleware/test_flask_middleware.py +201 -179
- tests/test_middleware/test_security_middleware.py +151 -130
- tests/test_utils/test_datetime_compat.py +147 -0
- mcp_security_framework-0.1.0.dist-info/RECORD +0 -76
- {mcp_security_framework-0.1.0.dist-info → mcp_security_framework-1.1.1.dist-info}/WHEEL +0 -0
- {mcp_security_framework-0.1.0.dist-info → mcp_security_framework-1.1.1.dist-info}/entry_points.txt +0 -0
- {mcp_security_framework-0.1.0.dist-info → mcp_security_framework-1.1.1.dist-info}/top_level.txt +0 -0
@@ -40,6 +40,11 @@ from cryptography.hazmat.primitives import hashes, serialization
|
|
40
40
|
from cryptography.hazmat.primitives.asymmetric import rsa
|
41
41
|
from cryptography.x509.oid import ExtensionOID, NameOID
|
42
42
|
|
43
|
+
from mcp_security_framework.utils.datetime_compat import (
|
44
|
+
get_not_valid_after_utc,
|
45
|
+
get_not_valid_before_utc,
|
46
|
+
)
|
47
|
+
|
43
48
|
|
44
49
|
class CertificateError(Exception):
|
45
50
|
"""Raised when certificate operations fail."""
|
@@ -125,8 +130,8 @@ def extract_certificate_info(cert_data: Union[str, bytes, Path]) -> Dict:
|
|
125
130
|
"issuer": str(cert.issuer),
|
126
131
|
"serial_number": str(cert.serial_number),
|
127
132
|
"version": cert.version.name,
|
128
|
-
"not_before": cert
|
129
|
-
"not_after": cert
|
133
|
+
"not_before": get_not_valid_before_utc(cert),
|
134
|
+
"not_after": get_not_valid_after_utc(cert),
|
130
135
|
"signature_algorithm": cert.signature_algorithm_oid._name,
|
131
136
|
"public_key_algorithm": cert.public_key_algorithm_oid._name,
|
132
137
|
"key_size": _get_key_size(cert.public_key()),
|
@@ -266,7 +271,8 @@ def extract_permissions_from_certificate(
|
|
266
271
|
|
267
272
|
|
268
273
|
def validate_certificate_chain(
|
269
|
-
cert_data: Union[str, bytes, Path],
|
274
|
+
cert_data: Union[str, bytes, Path],
|
275
|
+
ca_cert_data: Union[str, bytes, Path, List[Union[str, bytes, Path]]],
|
270
276
|
) -> bool:
|
271
277
|
"""
|
272
278
|
Validate certificate chain against CA certificate(s).
|
@@ -283,7 +289,7 @@ def validate_certificate_chain(
|
|
283
289
|
"""
|
284
290
|
try:
|
285
291
|
cert = parse_certificate(cert_data)
|
286
|
-
|
292
|
+
|
287
293
|
# Handle single CA certificate or list of CA certificates
|
288
294
|
if isinstance(ca_cert_data, list):
|
289
295
|
ca_certs = [parse_certificate(ca_cert) for ca_cert in ca_cert_data]
|
@@ -295,7 +301,7 @@ def validate_certificate_chain(
|
|
295
301
|
for ca_cert in ca_certs:
|
296
302
|
if cert.issuer == ca_cert.subject:
|
297
303
|
return True
|
298
|
-
|
304
|
+
|
299
305
|
return False
|
300
306
|
except Exception as e:
|
301
307
|
return False
|
@@ -319,7 +325,7 @@ def get_certificate_expiry(cert_data: Union[str, bytes, Path]) -> Dict:
|
|
319
325
|
now = datetime.now(timezone.utc)
|
320
326
|
|
321
327
|
# Calculate time until expiry
|
322
|
-
time_until_expiry = cert
|
328
|
+
time_until_expiry = get_not_valid_after_utc(cert) - now
|
323
329
|
days_until_expiry = time_until_expiry.days
|
324
330
|
|
325
331
|
# Determine expiry status
|
@@ -331,8 +337,8 @@ def get_certificate_expiry(cert_data: Union[str, bytes, Path]) -> Dict:
|
|
331
337
|
status = "valid"
|
332
338
|
|
333
339
|
return {
|
334
|
-
"not_after": cert
|
335
|
-
"not_before": cert
|
340
|
+
"not_after": get_not_valid_after_utc(cert),
|
341
|
+
"not_before": get_not_valid_before_utc(cert),
|
336
342
|
"days_until_expiry": days_until_expiry,
|
337
343
|
"is_expired": time_until_expiry.total_seconds() < 0,
|
338
344
|
"expires_soon": days_until_expiry <= 30,
|
@@ -0,0 +1,116 @@
|
|
1
|
+
"""
|
2
|
+
Datetime Compatibility Module
|
3
|
+
|
4
|
+
This module provides compatibility functions for working with certificate
|
5
|
+
datetime fields across different versions of the cryptography library.
|
6
|
+
|
7
|
+
The module handles the transition from naive datetime fields to UTC-aware
|
8
|
+
fields introduced in cryptography>=42, ensuring backward compatibility with
|
9
|
+
older versions.
|
10
|
+
|
11
|
+
Key Features:
|
12
|
+
- Compatible datetime field access for certificates
|
13
|
+
- Automatic timezone normalization
|
14
|
+
- Backward compatibility with cryptography<42
|
15
|
+
- Forward compatibility with cryptography>=42
|
16
|
+
|
17
|
+
Author: Vasiliy Zdanovskiy
|
18
|
+
email: vasilyvz@gmail.com
|
19
|
+
"""
|
20
|
+
|
21
|
+
from datetime import datetime, timezone
|
22
|
+
from typing import Optional
|
23
|
+
|
24
|
+
from cryptography.x509 import Certificate
|
25
|
+
|
26
|
+
|
27
|
+
def get_not_valid_before_utc(cert: Certificate) -> datetime:
|
28
|
+
"""
|
29
|
+
Get certificate not_valid_before field in UTC timezone.
|
30
|
+
|
31
|
+
This function provides compatibility across different cryptography
|
32
|
+
versions:
|
33
|
+
- For cryptography>=42: Returns cert.not_valid_before_utc directly
|
34
|
+
- For cryptography<42: Returns cert.not_valid_before normalized to UTC
|
35
|
+
|
36
|
+
Args:
|
37
|
+
cert (Certificate): Certificate object from cryptography library
|
38
|
+
|
39
|
+
Returns:
|
40
|
+
datetime: UTC timezone-aware datetime representing certificate
|
41
|
+
start date
|
42
|
+
|
43
|
+
Example:
|
44
|
+
>>> from cryptography import x509
|
45
|
+
>>> cert = x509.load_pem_x509_certificate(cert_data)
|
46
|
+
>>> start_date = get_not_valid_before_utc(cert)
|
47
|
+
>>> print(f"Certificate valid from: {start_date}")
|
48
|
+
"""
|
49
|
+
# Try to access the new UTC field first (cryptography>=42)
|
50
|
+
if hasattr(cert, "not_valid_before_utc"):
|
51
|
+
val = getattr(cert, "not_valid_before_utc")
|
52
|
+
if val is not None:
|
53
|
+
return val
|
54
|
+
|
55
|
+
# Fall back to the old field and normalize to UTC (cryptography<42)
|
56
|
+
v = cert.not_valid_before
|
57
|
+
return v if v.tzinfo else v.replace(tzinfo=timezone.utc)
|
58
|
+
|
59
|
+
|
60
|
+
def get_not_valid_after_utc(cert: Certificate) -> datetime:
|
61
|
+
"""
|
62
|
+
Get certificate not_valid_after field in UTC timezone.
|
63
|
+
|
64
|
+
This function provides compatibility across different cryptography
|
65
|
+
versions:
|
66
|
+
- For cryptography>=42: Returns cert.not_valid_after_utc directly
|
67
|
+
- For cryptography<42: Returns cert.not_valid_after normalized to UTC
|
68
|
+
|
69
|
+
Args:
|
70
|
+
cert (Certificate): Certificate object from cryptography library
|
71
|
+
|
72
|
+
Returns:
|
73
|
+
datetime: UTC timezone-aware datetime representing certificate
|
74
|
+
expiry date
|
75
|
+
|
76
|
+
Example:
|
77
|
+
>>> from cryptography import x509
|
78
|
+
>>> cert = x509.load_pem_x509_certificate(cert_data)
|
79
|
+
>>> expiry_date = get_not_valid_after_utc(cert)
|
80
|
+
>>> print(f"Certificate expires: {expiry_date}")
|
81
|
+
"""
|
82
|
+
# Try to access the new UTC field first (cryptography>=42)
|
83
|
+
if hasattr(cert, "not_valid_after_utc"):
|
84
|
+
val = getattr(cert, "not_valid_after_utc")
|
85
|
+
if val is not None:
|
86
|
+
return val
|
87
|
+
|
88
|
+
# Fall back to the old field and normalize to UTC (cryptography<42)
|
89
|
+
v = cert.not_valid_after
|
90
|
+
return v if v.tzinfo else v.replace(tzinfo=timezone.utc)
|
91
|
+
|
92
|
+
|
93
|
+
def is_cryptography_42_plus() -> bool:
|
94
|
+
"""
|
95
|
+
Check if cryptography version is 42 or higher.
|
96
|
+
|
97
|
+
This function checks if the current cryptography version supports
|
98
|
+
the new UTC datetime fields (not_valid_before_utc, not_valid_after_utc).
|
99
|
+
|
100
|
+
Returns:
|
101
|
+
bool: True if cryptography>=42, False otherwise
|
102
|
+
|
103
|
+
Example:
|
104
|
+
>>> if is_cryptography_42_plus():
|
105
|
+
... print("Using new UTC datetime fields")
|
106
|
+
... else:
|
107
|
+
... print("Using legacy datetime fields with normalization")
|
108
|
+
"""
|
109
|
+
try:
|
110
|
+
import cryptography
|
111
|
+
from packaging import version
|
112
|
+
|
113
|
+
return version.parse(cryptography.__version__) >= version.parse("42.0.0")
|
114
|
+
except (ImportError, AttributeError):
|
115
|
+
# If we can't determine version, assume older version for safety
|
116
|
+
return False
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: mcp-security-framework
|
3
|
-
Version:
|
3
|
+
Version: 1.1.1
|
4
4
|
Summary: Universal security framework for microservices with SSL/TLS, authentication, authorization, and rate limiting
|
5
5
|
Author-email: Vasiliy Zdanovskiy <vasilyvz@gmail.com>
|
6
6
|
Maintainer-email: Vasiliy Zdanovskiy <vasilyvz@gmail.com>
|
@@ -31,6 +31,7 @@ Requires-Dist: pydantic<3.0.0,>=1.8.0
|
|
31
31
|
Requires-Dist: PyJWT>=2.0.0
|
32
32
|
Requires-Dist: click>=8.0.0
|
33
33
|
Requires-Dist: typing-extensions>=4.0.0
|
34
|
+
Requires-Dist: packaging>=20.0
|
34
35
|
Provides-Extra: fastapi
|
35
36
|
Requires-Dist: fastapi>=0.68.0; extra == "fastapi"
|
36
37
|
Requires-Dist: uvicorn[standard]>=0.15.0; extra == "fastapi"
|
@@ -0,0 +1,84 @@
|
|
1
|
+
mcp_security_framework/__init__.py,sha256=TM8y71Navd_6woEab2cO07MefRsL0tRBItEYfVO7DS8,3172
|
2
|
+
mcp_security_framework/constants.py,sha256=k7NMSrgc83Cci8aoilybQxdC7jir7J-mVFE_EpqVrDk,5307
|
3
|
+
mcp_security_framework/cli/__init__.py,sha256=plpWdiWMp2dcLvUuGwXynRg5CDjz8YKnNTBn7lcta08,369
|
4
|
+
mcp_security_framework/cli/cert_cli.py,sha256=ME8RgTBrjNvOAMPVnXGLr3cbTpOZ2NN4Ei1SouGRPQs,18297
|
5
|
+
mcp_security_framework/cli/security_cli.py,sha256=Thine_Zzfesz7j29y2k_XZFYUK5YSrhCc6w2FilgEiE,28486
|
6
|
+
mcp_security_framework/core/__init__.py,sha256=LiX8_M5qWiTXccJFjSLxup9emhklp-poq57SvznsKEg,1729
|
7
|
+
mcp_security_framework/core/auth_manager.py,sha256=vCa6YBlz7P_vmif-KGWrCCUE54bHO5F3jN-bDJF2o9Y,38909
|
8
|
+
mcp_security_framework/core/cert_manager.py,sha256=s625nyMsmrglT7QaqRZtX4oAJVKla5zu0sptHmXJnh4,78750
|
9
|
+
mcp_security_framework/core/permission_manager.py,sha256=SADS_oXpwp9MhXHKJMCsvjEq8KWcz7vPYL05Yr-zfio,26478
|
10
|
+
mcp_security_framework/core/rate_limiter.py,sha256=6qjVBxK2YHouSxQuCcbr0PBpRqA5toQss_Ce178RElY,20682
|
11
|
+
mcp_security_framework/core/security_manager.py,sha256=mAF-5znqxin-MSSgXISB7t1kTkqHltEqGzzmlLAhRGs,37766
|
12
|
+
mcp_security_framework/core/ssl_manager.py,sha256=nCDrukaELONQs_uAfb30g69HDxRp2u4k0Bpq8eJH6Zo,27718
|
13
|
+
mcp_security_framework/examples/__init__.py,sha256=nfYPVvIQ5wHuDkQvyCYDd1VjCsZMw9HnvjeUORqKyuQ,1915
|
14
|
+
mcp_security_framework/examples/comprehensive_example.py,sha256=6CXkqLFjWIQ2rRMYPnDrBdBuWFKbeu2gd2GI_SFVjds,35421
|
15
|
+
mcp_security_framework/examples/django_example.py,sha256=IHk-aHsah-cEHjvsngUx91lup1aRC8W9XHzK6jfOMdA,24628
|
16
|
+
mcp_security_framework/examples/fastapi_example.py,sha256=8uVze78eyEZpnzW0lNLxAUi27amXY7RJtu4GcGGpFJE,35598
|
17
|
+
mcp_security_framework/examples/flask_example.py,sha256=jrqnBxr1eu91SLt0Zcf5949DX-ZyQirqMdKVclO5RSI,21636
|
18
|
+
mcp_security_framework/examples/gateway_example.py,sha256=u9CSdnBm3nB89n5uyTarMDys5xsZcVi0wLATBSBvSog,33390
|
19
|
+
mcp_security_framework/examples/microservice_example.py,sha256=7xw8mOIT1ZMxH4TQNXMv9DuTquitZou1DatO2_OE7DU,30358
|
20
|
+
mcp_security_framework/examples/standalone_example.py,sha256=jtrLJPrsgD8w3hNUv5SbCq4KLMu4z6WgF4deMPpehQM,29786
|
21
|
+
mcp_security_framework/examples/test_all_examples.py,sha256=9tRDD6lOCx69M27ESRTr5V5h6VnRh1EPuSfFCbQrvxM,19608
|
22
|
+
mcp_security_framework/middleware/__init__.py,sha256=Bx2DBPbhkikWNPXg1WWFoalp2omFPh7rbMYHYQZoZsk,7803
|
23
|
+
mcp_security_framework/middleware/auth_middleware.py,sha256=wMxJtlrTrlK7KSAXJ6yZawwqSpCsxXf-U87J05ogdKg,9833
|
24
|
+
mcp_security_framework/middleware/fastapi_auth_middleware.py,sha256=LWVEn90I1XpVkgu4q2LFqvjeVinCMAmI2-19UIcgKpw,16904
|
25
|
+
mcp_security_framework/middleware/fastapi_middleware.py,sha256=Ye0qJsEMwgeUqVJpqXgbjJJbbf-ZU-6SysMQPmQba-s,26658
|
26
|
+
mcp_security_framework/middleware/flask_auth_middleware.py,sha256=ubBlKO0ponOV_KuxkUK4xGcSoslXTaikrdsIZQtGeV0,20228
|
27
|
+
mcp_security_framework/middleware/flask_middleware.py,sha256=Ag0zYDKwlvU78LBQ-7Za14IYOAlEVZaXJPD0Qc4MUvA,20666
|
28
|
+
mcp_security_framework/middleware/mtls_middleware.py,sha256=iCOX3PVro49GMlTSUtYQFaWLrqtqoWPgI5DEa6Cnst4,13881
|
29
|
+
mcp_security_framework/middleware/rate_limit_middleware.py,sha256=deCwwigI0Pt7pBUnk2jDurI9ZyjujWTsexEWWndXm3g,13177
|
30
|
+
mcp_security_framework/middleware/security_middleware.py,sha256=PQ251Fr2UrYVPgGfhXq6QJyqK2tRk0WCIg9_FBvfVkg,16844
|
31
|
+
mcp_security_framework/schemas/__init__.py,sha256=lefkbRlbj2ICfasSj51MQ04o3z1YycnbnknSJCFfXbU,2590
|
32
|
+
mcp_security_framework/schemas/config.py,sha256=kpJJBupR4ZpQyouZyxiZqm7U7AHfLoGncuRltpo_QEM,25825
|
33
|
+
mcp_security_framework/schemas/models.py,sha256=n-Ug8O1cpMeA0mIdOd9h1i3kkBZOJsCQMxj3IcNpBFY,26957
|
34
|
+
mcp_security_framework/schemas/responses.py,sha256=nVXaqF5GTSprXTa_wiUEu38nvSw9WAXtKViAJNbO-Xg,23206
|
35
|
+
mcp_security_framework/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
36
|
+
mcp_security_framework/utils/__init__.py,sha256=wwwdmQYHTSz0Puvs9FD6aIKmWp3NFARe3JPWNH-b_wk,3098
|
37
|
+
mcp_security_framework/utils/cert_utils.py,sha256=DNzCqFKbFgYvQxxUUJPgeWe1XCnM4DpUBDqQYRYtlOQ,17266
|
38
|
+
mcp_security_framework/utils/crypto_utils.py,sha256=OH2V7_C3FjStxFTIXMUPfNXZuWG2-QjgoBrIH4Lv4p0,12392
|
39
|
+
mcp_security_framework/utils/datetime_compat.py,sha256=ool-xs-EevhuYygdzhiAenLAacLuZwGwjPkF43i-9gg,3859
|
40
|
+
mcp_security_framework/utils/validation_utils.py,sha256=e9BX3kw9gdXSmFsc7lmG-qnzSlK0-Ynn7Xs4uKHquF4,16279
|
41
|
+
tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
42
|
+
tests/conftest.py,sha256=4DKwzXAOqikfPAUNDHRcn-C3ZoWmLRcn9Kz_3nJ9hZ8,8812
|
43
|
+
tests/test_cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
44
|
+
tests/test_cli/test_cert_cli.py,sha256=Rm7z-20VAvnmYKY3sgxS-qVNks1vbniQJSpSxjsx_wo,14677
|
45
|
+
tests/test_cli/test_security_cli.py,sha256=Bpd31IPJSUl_V1Xzy74ZCOvQpwlbj8Da83C46T8Jewg,25569
|
46
|
+
tests/test_core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
47
|
+
tests/test_core/test_auth_manager.py,sha256=tyJUe3yBP95SFwbhSr_IloKhEQw0BuEdvjAC1Hnwu4s,22540
|
48
|
+
tests/test_core/test_cert_manager.py,sha256=4YMAkRedkAZW3PEZYEbo1PyrzMntUrKfl7arPsHXDCE,36356
|
49
|
+
tests/test_core/test_permission_manager.py,sha256=0XeghWXZqVpKyyRuhuDu1dkLUSwuZaFWkRQxQhkkFVI,14966
|
50
|
+
tests/test_core/test_rate_limiter.py,sha256=YzzlhlxZm-A7YGMiIV8LXDA0zmb_6uRF9GRx9s21Q0U,22544
|
51
|
+
tests/test_core/test_security_manager.py,sha256=C5uPFALAkitmHbi-L8xF1OyfOmVHQSq1g-PLkwl_LDU,35007
|
52
|
+
tests/test_core/test_ssl_manager.py,sha256=Vm_Nw4SoVro_iwPPc_uD9CwzXpVBkGyVH7EqDtHawvU,20362
|
53
|
+
tests/test_examples/__init__.py,sha256=VC0N1wB9d01Acnqfos-9x68o23xxHkRhAh4-1qJKUTc,157
|
54
|
+
tests/test_examples/test_comprehensive_example.py,sha256=2Q9ZfyEt42dDuwPbxwiJbJjRfLp68l5KClALeVO9JOQ,26020
|
55
|
+
tests/test_examples/test_fastapi_example.py,sha256=J_dpdq7ZIvi1DfcYpEAltTEfbHbLqk-Q9B5XpkrgMOk,12978
|
56
|
+
tests/test_examples/test_flask_example.py,sha256=makDlkLEkCPU94ZIomrw-5-ff4NDP7uhWLTDuk34Jbg,12540
|
57
|
+
tests/test_examples/test_standalone_example.py,sha256=nzSMJ4kglMcFYHWJ130_cNfHghpBcMgNoNU7PMQ7NII,8165
|
58
|
+
tests/test_integration/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
59
|
+
tests/test_integration/test_auth_flow.py,sha256=lzfrI2Yl-nDDQX5Y3TYK6CrUKtkf4oXk0zYPPdoVjSs,18656
|
60
|
+
tests/test_integration/test_certificate_flow.py,sha256=6eyMjIKEviPJtkFaRmG7v9Ua-szvlOF6gfYnHF3JUeA,19612
|
61
|
+
tests/test_integration/test_fastapi_integration.py,sha256=HFXNRfm0x0ihzVeBZFD6L1fl19mx5Bg5wvWcXmIzjXw,12957
|
62
|
+
tests/test_integration/test_flask_integration.py,sha256=mqb9g3H7lwwKajsG0Ee6eeWH25mNLtI6-ZmLiPKvO2g,15087
|
63
|
+
tests/test_integration/test_standalone_integration.py,sha256=chMzo1tG4p0DNCdsWbkhFvSAWqNCEj3Tuoi8miF6C3o,19389
|
64
|
+
tests/test_middleware/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
65
|
+
tests/test_middleware/test_fastapi_auth_middleware.py,sha256=6pKnVDrr-inr51bHqije6oa6m44hAqM1nJovInBJcNg,29386
|
66
|
+
tests/test_middleware/test_fastapi_middleware.py,sha256=qX0nXM9SrBoAuPs2-LRKT-EzHiwnGCD0c89FYAYnPMw,20607
|
67
|
+
tests/test_middleware/test_flask_auth_middleware.py,sha256=NA74wnBq7AR-YsUqlibMSs4B60q7lWTziTPKZtfq_l0,25034
|
68
|
+
tests/test_middleware/test_flask_middleware.py,sha256=JqWr5MknE6AvnUUf2Cr0ME6l_wSbze0BqbEIQO8B5qs,22731
|
69
|
+
tests/test_middleware/test_security_middleware.py,sha256=J69rVgsnohQp2ucUnGRyWCWZxt6RF2tQ9vQNLFlDXEg,19199
|
70
|
+
tests/test_schemas/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
71
|
+
tests/test_schemas/test_config.py,sha256=Bp_yZ_HBIl7jcR7Wb1S9iCB0tCu_Ov26YKHTulDs1RU,29430
|
72
|
+
tests/test_schemas/test_models.py,sha256=bBeZOPqveuVJuEi_BTVWdVsdj08JXJTEFwvBM4eFRVU,34311
|
73
|
+
tests/test_schemas/test_responses.py,sha256=ZSbO7A3ThPBovTXO8PFF-2ONWAjJx2dMOoV2lQIfd8s,40774
|
74
|
+
tests/test_schemas/test_serialization.py,sha256=jCugAyrdD6Mw1U7Kxni9oTukarZmMMl6KUcl6cq_NTk,18599
|
75
|
+
tests/test_utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
76
|
+
tests/test_utils/test_cert_utils.py,sha256=2k1kUCJy0T2HPBwOcU5USGfEBQZ_rGzumAGkDdywLak,21232
|
77
|
+
tests/test_utils/test_crypto_utils.py,sha256=yEb4hzG6-irj2DPoXY0DUboJfbeR87ussgTuBpxLGz4,20737
|
78
|
+
tests/test_utils/test_datetime_compat.py,sha256=n8S4X5HN-_ejSNpgymDXRyZkmxhnyxwwjxFPdX23I40,5656
|
79
|
+
tests/test_utils/test_validation_utils.py,sha256=lus_wHJ2WyVnBGQ28S7dSv78uWcCIuLhn5uflJw-uGw,18569
|
80
|
+
mcp_security_framework-1.1.1.dist-info/METADATA,sha256=3Kh50icpzF53SGGXCU8XWt1Ov2xJAiTMoyLLkGqyuvk,11711
|
81
|
+
mcp_security_framework-1.1.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
82
|
+
mcp_security_framework-1.1.1.dist-info/entry_points.txt,sha256=qBh92fVDmd1m2f3xeW0hTu3Ksg8QfGJyV8UEkdA2itg,142
|
83
|
+
mcp_security_framework-1.1.1.dist-info/top_level.txt,sha256=ifUiGrTDcD574MXSOoAN2rp2wpUvWlb4jD9LTUgDWCA,29
|
84
|
+
mcp_security_framework-1.1.1.dist-info/RECORD,,
|
tests/conftest.py
ADDED
@@ -0,0 +1,303 @@
|
|
1
|
+
"""
|
2
|
+
Test Configuration and Fixtures
|
3
|
+
|
4
|
+
This module provides test configuration, fixtures, and mock objects
|
5
|
+
for the MCP Security Framework test suite.
|
6
|
+
|
7
|
+
Author: Vasiliy Zdanovskiy
|
8
|
+
email: vasilyvz@gmail.com
|
9
|
+
"""
|
10
|
+
|
11
|
+
from typing import Any, Dict, Optional
|
12
|
+
from unittest.mock import MagicMock, Mock, patch
|
13
|
+
|
14
|
+
import pytest
|
15
|
+
|
16
|
+
from mcp_security_framework.core.security_manager import SecurityManager
|
17
|
+
from mcp_security_framework.schemas.models import (
|
18
|
+
AuthMethod,
|
19
|
+
AuthResult,
|
20
|
+
AuthStatus,
|
21
|
+
ValidationResult,
|
22
|
+
ValidationStatus,
|
23
|
+
)
|
24
|
+
|
25
|
+
|
26
|
+
@pytest.fixture
|
27
|
+
def mock_security_manager():
|
28
|
+
"""
|
29
|
+
Create a mock SecurityManager instance for testing.
|
30
|
+
|
31
|
+
This fixture provides a properly configured mock SecurityManager
|
32
|
+
that can be used in tests without conflicts with the real implementation.
|
33
|
+
|
34
|
+
Returns:
|
35
|
+
Mock: Mock SecurityManager instance
|
36
|
+
"""
|
37
|
+
mock_manager = Mock(spec=SecurityManager)
|
38
|
+
|
39
|
+
# Mock authentication methods
|
40
|
+
def mock_authenticate_api_key(api_key: str) -> AuthResult:
|
41
|
+
if api_key == "admin_key_123":
|
42
|
+
return AuthResult(
|
43
|
+
is_valid=True,
|
44
|
+
status=AuthStatus.SUCCESS,
|
45
|
+
username="admin",
|
46
|
+
roles=["admin"],
|
47
|
+
auth_method=AuthMethod.API_KEY,
|
48
|
+
)
|
49
|
+
elif api_key == "user_key_456":
|
50
|
+
return AuthResult(
|
51
|
+
is_valid=True,
|
52
|
+
status=AuthStatus.SUCCESS,
|
53
|
+
username="user",
|
54
|
+
roles=["user"],
|
55
|
+
auth_method=AuthMethod.API_KEY,
|
56
|
+
)
|
57
|
+
elif api_key == "readonly_key_789":
|
58
|
+
return AuthResult(
|
59
|
+
is_valid=True,
|
60
|
+
status=AuthStatus.SUCCESS,
|
61
|
+
username="readonly",
|
62
|
+
roles=["readonly"],
|
63
|
+
auth_method=AuthMethod.API_KEY,
|
64
|
+
)
|
65
|
+
else:
|
66
|
+
return AuthResult(
|
67
|
+
is_valid=False,
|
68
|
+
status=AuthStatus.FAILED,
|
69
|
+
username=None,
|
70
|
+
roles=[],
|
71
|
+
auth_method=None,
|
72
|
+
error_code=-32002,
|
73
|
+
error_message="Invalid API key",
|
74
|
+
)
|
75
|
+
|
76
|
+
def mock_authenticate_jwt_token(token: str) -> AuthResult:
|
77
|
+
if token == "valid_jwt_token":
|
78
|
+
return AuthResult(
|
79
|
+
is_valid=True,
|
80
|
+
status=AuthStatus.SUCCESS,
|
81
|
+
username="jwt_user",
|
82
|
+
roles=["user"],
|
83
|
+
auth_method=AuthMethod.JWT,
|
84
|
+
)
|
85
|
+
else:
|
86
|
+
return AuthResult(
|
87
|
+
is_valid=False,
|
88
|
+
status=AuthStatus.FAILED,
|
89
|
+
username=None,
|
90
|
+
roles=[],
|
91
|
+
auth_method=None,
|
92
|
+
error_code=-32003,
|
93
|
+
error_message="Invalid JWT token",
|
94
|
+
)
|
95
|
+
|
96
|
+
def mock_check_permissions(
|
97
|
+
user_roles: list, required_permissions: list
|
98
|
+
) -> ValidationResult:
|
99
|
+
if "admin" in user_roles:
|
100
|
+
return ValidationResult(is_valid=True, status=ValidationStatus.VALID)
|
101
|
+
elif "user" in user_roles and "read" in required_permissions:
|
102
|
+
return ValidationResult(is_valid=True, status=ValidationStatus.VALID)
|
103
|
+
elif "readonly" in user_roles and "read" in required_permissions:
|
104
|
+
return ValidationResult(is_valid=True, status=ValidationStatus.VALID)
|
105
|
+
else:
|
106
|
+
return ValidationResult(
|
107
|
+
is_valid=False,
|
108
|
+
status=ValidationStatus.INVALID,
|
109
|
+
error_code=-32007,
|
110
|
+
error_message="Insufficient permissions",
|
111
|
+
)
|
112
|
+
|
113
|
+
def mock_check_rate_limit(identifier: str) -> bool:
|
114
|
+
# Simple rate limiting logic for testing
|
115
|
+
if not hasattr(mock_manager, "_request_counts"):
|
116
|
+
mock_manager._request_counts = {}
|
117
|
+
|
118
|
+
if identifier not in mock_manager._request_counts:
|
119
|
+
mock_manager._request_counts[identifier] = 0
|
120
|
+
|
121
|
+
mock_manager._request_counts[identifier] += 1
|
122
|
+
|
123
|
+
# Allow up to 100 requests per identifier
|
124
|
+
return mock_manager._request_counts[identifier] <= 100
|
125
|
+
|
126
|
+
# Assign mock methods
|
127
|
+
mock_manager.authenticate_api_key = mock_authenticate_api_key
|
128
|
+
mock_manager.authenticate_jwt_token = mock_authenticate_jwt_token
|
129
|
+
mock_manager.check_permissions = mock_check_permissions
|
130
|
+
mock_manager.check_rate_limit = mock_check_rate_limit
|
131
|
+
|
132
|
+
# Mock other properties and methods
|
133
|
+
mock_manager.is_authenticated = True
|
134
|
+
mock_manager.user_roles = ["user"]
|
135
|
+
mock_manager.effective_permissions = {"read", "write"}
|
136
|
+
|
137
|
+
return mock_manager
|
138
|
+
|
139
|
+
|
140
|
+
@pytest.fixture
|
141
|
+
def mock_security_manager_class():
|
142
|
+
"""
|
143
|
+
Create a mock SecurityManager class for testing.
|
144
|
+
|
145
|
+
This fixture provides a mock class that can be used to patch
|
146
|
+
SecurityManager imports in tests.
|
147
|
+
|
148
|
+
Returns:
|
149
|
+
Mock: Mock SecurityManager class
|
150
|
+
"""
|
151
|
+
mock_class = Mock()
|
152
|
+
mock_class.return_value = Mock(spec=SecurityManager)
|
153
|
+
return mock_class
|
154
|
+
|
155
|
+
|
156
|
+
@pytest.fixture
|
157
|
+
def mock_certificate_manager():
|
158
|
+
"""
|
159
|
+
Create a mock CertificateManager instance for testing.
|
160
|
+
|
161
|
+
Returns:
|
162
|
+
Mock: Mock CertificateManager instance
|
163
|
+
"""
|
164
|
+
mock_manager = Mock()
|
165
|
+
|
166
|
+
# Mock certificate validation
|
167
|
+
mock_manager.validate_certificate_chain.return_value = True
|
168
|
+
mock_manager.get_certificate_info.return_value = Mock(
|
169
|
+
subject={"CN": "test.example.com"},
|
170
|
+
issuer={"CN": "Test CA"},
|
171
|
+
serial_number="123456789",
|
172
|
+
not_before="2023-01-01",
|
173
|
+
not_after="2024-01-01",
|
174
|
+
key_size=2048,
|
175
|
+
certificate_type="SERVER",
|
176
|
+
subject_alt_names=["test.example.com"],
|
177
|
+
)
|
178
|
+
mock_manager.revoke_certificate.return_value = True
|
179
|
+
|
180
|
+
return mock_manager
|
181
|
+
|
182
|
+
|
183
|
+
@pytest.fixture
|
184
|
+
def mock_ssl_manager():
|
185
|
+
"""
|
186
|
+
Create a mock SSLManager instance for testing.
|
187
|
+
|
188
|
+
Returns:
|
189
|
+
Mock: Mock SSLManager instance
|
190
|
+
"""
|
191
|
+
mock_manager = Mock()
|
192
|
+
mock_manager.create_server_context.return_value = Mock()
|
193
|
+
mock_manager.create_client_context.return_value = Mock()
|
194
|
+
mock_manager.validate_certificate.return_value = True
|
195
|
+
return mock_manager
|
196
|
+
|
197
|
+
|
198
|
+
@pytest.fixture
|
199
|
+
def test_config():
|
200
|
+
"""
|
201
|
+
Create a test configuration for testing.
|
202
|
+
|
203
|
+
Returns:
|
204
|
+
Dict[str, Any]: Test configuration
|
205
|
+
"""
|
206
|
+
return {
|
207
|
+
"auth": {
|
208
|
+
"enabled": True,
|
209
|
+
"methods": ["api_key", "jwt"],
|
210
|
+
"api_keys": {
|
211
|
+
"admin_key_123": {
|
212
|
+
"username": "admin",
|
213
|
+
"roles": ["admin"],
|
214
|
+
"permissions": ["read", "write", "delete", "admin"],
|
215
|
+
},
|
216
|
+
"user_key_456": {
|
217
|
+
"username": "user",
|
218
|
+
"roles": ["user"],
|
219
|
+
"permissions": ["read", "write"],
|
220
|
+
},
|
221
|
+
"readonly_key_789": {
|
222
|
+
"username": "readonly",
|
223
|
+
"roles": ["readonly"],
|
224
|
+
"permissions": ["read"],
|
225
|
+
},
|
226
|
+
},
|
227
|
+
"jwt": {
|
228
|
+
"secret": "test_secret_key",
|
229
|
+
"algorithm": "HS256",
|
230
|
+
"expiry_hours": 24,
|
231
|
+
},
|
232
|
+
},
|
233
|
+
"ssl": {
|
234
|
+
"enabled": False,
|
235
|
+
"cert_file": None,
|
236
|
+
"key_file": None,
|
237
|
+
"ca_cert_file": None,
|
238
|
+
"verify_mode": "CERT_NONE",
|
239
|
+
"min_version": "TLSv1.2",
|
240
|
+
},
|
241
|
+
"rate_limiting": {
|
242
|
+
"enabled": True,
|
243
|
+
"requests_per_minute": 100,
|
244
|
+
"window_seconds": 60,
|
245
|
+
},
|
246
|
+
"logging": {
|
247
|
+
"level": "INFO",
|
248
|
+
"format": "%(asctime)s - %(name)s - %(levelname)s - %(message)s",
|
249
|
+
},
|
250
|
+
}
|
251
|
+
|
252
|
+
|
253
|
+
@pytest.fixture
|
254
|
+
def temp_config_file(tmp_path, test_config):
|
255
|
+
"""
|
256
|
+
Create a temporary configuration file for testing.
|
257
|
+
|
258
|
+
Args:
|
259
|
+
tmp_path: Pytest temporary directory fixture
|
260
|
+
test_config: Test configuration fixture
|
261
|
+
|
262
|
+
Returns:
|
263
|
+
str: Path to temporary configuration file
|
264
|
+
"""
|
265
|
+
import json
|
266
|
+
import os
|
267
|
+
|
268
|
+
config_file = tmp_path / "test_config.json"
|
269
|
+
with open(config_file, "w") as f:
|
270
|
+
json.dump(test_config, f)
|
271
|
+
|
272
|
+
return str(config_file)
|
273
|
+
|
274
|
+
|
275
|
+
# Patch decorators for common mocks
|
276
|
+
def patch_security_manager():
|
277
|
+
"""
|
278
|
+
Create a patch decorator for SecurityManager.
|
279
|
+
|
280
|
+
Returns:
|
281
|
+
function: Patch decorator
|
282
|
+
"""
|
283
|
+
return patch("mcp_security_framework.core.security_manager.SecurityManager")
|
284
|
+
|
285
|
+
|
286
|
+
def patch_certificate_manager():
|
287
|
+
"""
|
288
|
+
Create a patch decorator for CertificateManager.
|
289
|
+
|
290
|
+
Returns:
|
291
|
+
function: Patch decorator
|
292
|
+
"""
|
293
|
+
return patch("mcp_security_framework.core.cert_manager.CertificateManager")
|
294
|
+
|
295
|
+
|
296
|
+
def patch_ssl_manager():
|
297
|
+
"""
|
298
|
+
Create a patch decorator for SSLManager.
|
299
|
+
|
300
|
+
Returns:
|
301
|
+
function: Patch decorator
|
302
|
+
"""
|
303
|
+
return patch("mcp_security_framework.core.ssl_manager.SSLManager")
|