mcp-security-framework 1.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 +41 -22
- mcp_security_framework/core/cert_manager.py +210 -147
- mcp_security_framework/core/permission_manager.py +9 -9
- mcp_security_framework/core/rate_limiter.py +2 -2
- mcp_security_framework/core/security_manager.py +284 -229
- mcp_security_framework/examples/__init__.py +6 -0
- mcp_security_framework/examples/comprehensive_example.py +349 -279
- mcp_security_framework/examples/django_example.py +247 -206
- mcp_security_framework/examples/fastapi_example.py +315 -283
- mcp_security_framework/examples/flask_example.py +274 -203
- mcp_security_framework/examples/gateway_example.py +304 -237
- mcp_security_framework/examples/microservice_example.py +258 -189
- mcp_security_framework/examples/standalone_example.py +255 -230
- mcp_security_framework/examples/test_all_examples.py +151 -135
- 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 +119 -118
- mcp_security_framework/middleware/fastapi_middleware.py +156 -148
- mcp_security_framework/middleware/flask_auth_middleware.py +160 -147
- 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 +18 -6
- mcp_security_framework/utils/cert_utils.py +14 -8
- mcp_security_framework/utils/datetime_compat.py +116 -0
- {mcp_security_framework-1.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 +63 -66
- tests/test_cli/test_cert_cli.py +184 -146
- tests/test_cli/test_security_cli.py +274 -247
- tests/test_core/test_cert_manager.py +24 -10
- tests/test_core/test_security_manager.py +2 -2
- tests/test_examples/test_comprehensive_example.py +190 -137
- tests/test_examples/test_fastapi_example.py +124 -101
- tests/test_examples/test_flask_example.py +124 -101
- tests/test_examples/test_standalone_example.py +73 -80
- tests/test_integration/test_auth_flow.py +213 -197
- tests/test_integration/test_certificate_flow.py +180 -149
- tests/test_integration/test_fastapi_integration.py +108 -111
- tests/test_integration/test_flask_integration.py +141 -140
- tests/test_integration/test_standalone_integration.py +290 -259
- tests/test_middleware/test_fastapi_auth_middleware.py +195 -174
- tests/test_middleware/test_fastapi_middleware.py +147 -132
- tests/test_middleware/test_flask_auth_middleware.py +260 -202
- tests/test_middleware/test_flask_middleware.py +201 -179
- tests/test_middleware/test_security_middleware.py +145 -130
- tests/test_utils/test_datetime_compat.py +147 -0
- mcp_security_framework-1.1.0.dist-info/RECORD +0 -82
- {mcp_security_framework-1.1.0.dist-info → mcp_security_framework-1.1.1.dist-info}/WHEEL +0 -0
- {mcp_security_framework-1.1.0.dist-info → mcp_security_framework-1.1.1.dist-info}/entry_points.txt +0 -0
- {mcp_security_framework-1.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: 1.1.
|
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
CHANGED
@@ -8,14 +8,18 @@ Author: Vasiliy Zdanovskiy
|
|
8
8
|
email: vasilyvz@gmail.com
|
9
9
|
"""
|
10
10
|
|
11
|
+
from typing import Any, Dict, Optional
|
12
|
+
from unittest.mock import MagicMock, Mock, patch
|
13
|
+
|
11
14
|
import pytest
|
12
|
-
from unittest.mock import Mock, MagicMock, patch
|
13
|
-
from typing import Dict, Any, Optional
|
14
15
|
|
15
16
|
from mcp_security_framework.core.security_manager import SecurityManager
|
16
17
|
from mcp_security_framework.schemas.models import (
|
17
|
-
|
18
|
-
|
18
|
+
AuthMethod,
|
19
|
+
AuthResult,
|
20
|
+
AuthStatus,
|
21
|
+
ValidationResult,
|
22
|
+
ValidationStatus,
|
19
23
|
)
|
20
24
|
|
21
25
|
|
@@ -23,15 +27,15 @@ from mcp_security_framework.schemas.models import (
|
|
23
27
|
def mock_security_manager():
|
24
28
|
"""
|
25
29
|
Create a mock SecurityManager instance for testing.
|
26
|
-
|
30
|
+
|
27
31
|
This fixture provides a properly configured mock SecurityManager
|
28
32
|
that can be used in tests without conflicts with the real implementation.
|
29
|
-
|
33
|
+
|
30
34
|
Returns:
|
31
35
|
Mock: Mock SecurityManager instance
|
32
36
|
"""
|
33
37
|
mock_manager = Mock(spec=SecurityManager)
|
34
|
-
|
38
|
+
|
35
39
|
# Mock authentication methods
|
36
40
|
def mock_authenticate_api_key(api_key: str) -> AuthResult:
|
37
41
|
if api_key == "admin_key_123":
|
@@ -40,7 +44,7 @@ def mock_security_manager():
|
|
40
44
|
status=AuthStatus.SUCCESS,
|
41
45
|
username="admin",
|
42
46
|
roles=["admin"],
|
43
|
-
auth_method=AuthMethod.API_KEY
|
47
|
+
auth_method=AuthMethod.API_KEY,
|
44
48
|
)
|
45
49
|
elif api_key == "user_key_456":
|
46
50
|
return AuthResult(
|
@@ -48,7 +52,7 @@ def mock_security_manager():
|
|
48
52
|
status=AuthStatus.SUCCESS,
|
49
53
|
username="user",
|
50
54
|
roles=["user"],
|
51
|
-
auth_method=AuthMethod.API_KEY
|
55
|
+
auth_method=AuthMethod.API_KEY,
|
52
56
|
)
|
53
57
|
elif api_key == "readonly_key_789":
|
54
58
|
return AuthResult(
|
@@ -56,7 +60,7 @@ def mock_security_manager():
|
|
56
60
|
status=AuthStatus.SUCCESS,
|
57
61
|
username="readonly",
|
58
62
|
roles=["readonly"],
|
59
|
-
auth_method=AuthMethod.API_KEY
|
63
|
+
auth_method=AuthMethod.API_KEY,
|
60
64
|
)
|
61
65
|
else:
|
62
66
|
return AuthResult(
|
@@ -66,9 +70,9 @@ def mock_security_manager():
|
|
66
70
|
roles=[],
|
67
71
|
auth_method=None,
|
68
72
|
error_code=-32002,
|
69
|
-
error_message="Invalid API key"
|
73
|
+
error_message="Invalid API key",
|
70
74
|
)
|
71
|
-
|
75
|
+
|
72
76
|
def mock_authenticate_jwt_token(token: str) -> AuthResult:
|
73
77
|
if token == "valid_jwt_token":
|
74
78
|
return AuthResult(
|
@@ -76,7 +80,7 @@ def mock_security_manager():
|
|
76
80
|
status=AuthStatus.SUCCESS,
|
77
81
|
username="jwt_user",
|
78
82
|
roles=["user"],
|
79
|
-
auth_method=AuthMethod.JWT
|
83
|
+
auth_method=AuthMethod.JWT,
|
80
84
|
)
|
81
85
|
else:
|
82
86
|
return AuthResult(
|
@@ -86,57 +90,50 @@ def mock_security_manager():
|
|
86
90
|
roles=[],
|
87
91
|
auth_method=None,
|
88
92
|
error_code=-32003,
|
89
|
-
error_message="Invalid JWT token"
|
93
|
+
error_message="Invalid JWT token",
|
90
94
|
)
|
91
|
-
|
92
|
-
def mock_check_permissions(
|
95
|
+
|
96
|
+
def mock_check_permissions(
|
97
|
+
user_roles: list, required_permissions: list
|
98
|
+
) -> ValidationResult:
|
93
99
|
if "admin" in user_roles:
|
94
|
-
return ValidationResult(
|
95
|
-
is_valid=True,
|
96
|
-
status=ValidationStatus.VALID
|
97
|
-
)
|
100
|
+
return ValidationResult(is_valid=True, status=ValidationStatus.VALID)
|
98
101
|
elif "user" in user_roles and "read" in required_permissions:
|
99
|
-
return ValidationResult(
|
100
|
-
is_valid=True,
|
101
|
-
status=ValidationStatus.VALID
|
102
|
-
)
|
102
|
+
return ValidationResult(is_valid=True, status=ValidationStatus.VALID)
|
103
103
|
elif "readonly" in user_roles and "read" in required_permissions:
|
104
|
-
return ValidationResult(
|
105
|
-
is_valid=True,
|
106
|
-
status=ValidationStatus.VALID
|
107
|
-
)
|
104
|
+
return ValidationResult(is_valid=True, status=ValidationStatus.VALID)
|
108
105
|
else:
|
109
106
|
return ValidationResult(
|
110
107
|
is_valid=False,
|
111
108
|
status=ValidationStatus.INVALID,
|
112
109
|
error_code=-32007,
|
113
|
-
error_message="Insufficient permissions"
|
110
|
+
error_message="Insufficient permissions",
|
114
111
|
)
|
115
|
-
|
112
|
+
|
116
113
|
def mock_check_rate_limit(identifier: str) -> bool:
|
117
114
|
# Simple rate limiting logic for testing
|
118
|
-
if not hasattr(mock_manager,
|
115
|
+
if not hasattr(mock_manager, "_request_counts"):
|
119
116
|
mock_manager._request_counts = {}
|
120
|
-
|
117
|
+
|
121
118
|
if identifier not in mock_manager._request_counts:
|
122
119
|
mock_manager._request_counts[identifier] = 0
|
123
|
-
|
120
|
+
|
124
121
|
mock_manager._request_counts[identifier] += 1
|
125
|
-
|
122
|
+
|
126
123
|
# Allow up to 100 requests per identifier
|
127
124
|
return mock_manager._request_counts[identifier] <= 100
|
128
|
-
|
125
|
+
|
129
126
|
# Assign mock methods
|
130
127
|
mock_manager.authenticate_api_key = mock_authenticate_api_key
|
131
128
|
mock_manager.authenticate_jwt_token = mock_authenticate_jwt_token
|
132
129
|
mock_manager.check_permissions = mock_check_permissions
|
133
130
|
mock_manager.check_rate_limit = mock_check_rate_limit
|
134
|
-
|
131
|
+
|
135
132
|
# Mock other properties and methods
|
136
133
|
mock_manager.is_authenticated = True
|
137
134
|
mock_manager.user_roles = ["user"]
|
138
135
|
mock_manager.effective_permissions = {"read", "write"}
|
139
|
-
|
136
|
+
|
140
137
|
return mock_manager
|
141
138
|
|
142
139
|
|
@@ -144,10 +141,10 @@ def mock_security_manager():
|
|
144
141
|
def mock_security_manager_class():
|
145
142
|
"""
|
146
143
|
Create a mock SecurityManager class for testing.
|
147
|
-
|
144
|
+
|
148
145
|
This fixture provides a mock class that can be used to patch
|
149
146
|
SecurityManager imports in tests.
|
150
|
-
|
147
|
+
|
151
148
|
Returns:
|
152
149
|
Mock: Mock SecurityManager class
|
153
150
|
"""
|
@@ -160,12 +157,12 @@ def mock_security_manager_class():
|
|
160
157
|
def mock_certificate_manager():
|
161
158
|
"""
|
162
159
|
Create a mock CertificateManager instance for testing.
|
163
|
-
|
160
|
+
|
164
161
|
Returns:
|
165
162
|
Mock: Mock CertificateManager instance
|
166
163
|
"""
|
167
164
|
mock_manager = Mock()
|
168
|
-
|
165
|
+
|
169
166
|
# Mock certificate validation
|
170
167
|
mock_manager.validate_certificate_chain.return_value = True
|
171
168
|
mock_manager.get_certificate_info.return_value = Mock(
|
@@ -176,10 +173,10 @@ def mock_certificate_manager():
|
|
176
173
|
not_after="2024-01-01",
|
177
174
|
key_size=2048,
|
178
175
|
certificate_type="SERVER",
|
179
|
-
subject_alt_names=["test.example.com"]
|
176
|
+
subject_alt_names=["test.example.com"],
|
180
177
|
)
|
181
178
|
mock_manager.revoke_certificate.return_value = True
|
182
|
-
|
179
|
+
|
183
180
|
return mock_manager
|
184
181
|
|
185
182
|
|
@@ -187,7 +184,7 @@ def mock_certificate_manager():
|
|
187
184
|
def mock_ssl_manager():
|
188
185
|
"""
|
189
186
|
Create a mock SSLManager instance for testing.
|
190
|
-
|
187
|
+
|
191
188
|
Returns:
|
192
189
|
Mock: Mock SSLManager instance
|
193
190
|
"""
|
@@ -202,7 +199,7 @@ def mock_ssl_manager():
|
|
202
199
|
def test_config():
|
203
200
|
"""
|
204
201
|
Create a test configuration for testing.
|
205
|
-
|
202
|
+
|
206
203
|
Returns:
|
207
204
|
Dict[str, Any]: Test configuration
|
208
205
|
"""
|
@@ -214,24 +211,24 @@ def test_config():
|
|
214
211
|
"admin_key_123": {
|
215
212
|
"username": "admin",
|
216
213
|
"roles": ["admin"],
|
217
|
-
"permissions": ["read", "write", "delete", "admin"]
|
214
|
+
"permissions": ["read", "write", "delete", "admin"],
|
218
215
|
},
|
219
216
|
"user_key_456": {
|
220
217
|
"username": "user",
|
221
218
|
"roles": ["user"],
|
222
|
-
"permissions": ["read", "write"]
|
219
|
+
"permissions": ["read", "write"],
|
223
220
|
},
|
224
221
|
"readonly_key_789": {
|
225
222
|
"username": "readonly",
|
226
223
|
"roles": ["readonly"],
|
227
|
-
"permissions": ["read"]
|
228
|
-
}
|
224
|
+
"permissions": ["read"],
|
225
|
+
},
|
229
226
|
},
|
230
227
|
"jwt": {
|
231
228
|
"secret": "test_secret_key",
|
232
229
|
"algorithm": "HS256",
|
233
|
-
"expiry_hours": 24
|
234
|
-
}
|
230
|
+
"expiry_hours": 24,
|
231
|
+
},
|
235
232
|
},
|
236
233
|
"ssl": {
|
237
234
|
"enabled": False,
|
@@ -239,17 +236,17 @@ def test_config():
|
|
239
236
|
"key_file": None,
|
240
237
|
"ca_cert_file": None,
|
241
238
|
"verify_mode": "CERT_NONE",
|
242
|
-
"min_version": "TLSv1.2"
|
239
|
+
"min_version": "TLSv1.2",
|
243
240
|
},
|
244
241
|
"rate_limiting": {
|
245
242
|
"enabled": True,
|
246
243
|
"requests_per_minute": 100,
|
247
|
-
"window_seconds": 60
|
244
|
+
"window_seconds": 60,
|
248
245
|
},
|
249
246
|
"logging": {
|
250
247
|
"level": "INFO",
|
251
|
-
"format": "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
|
252
|
-
}
|
248
|
+
"format": "%(asctime)s - %(name)s - %(levelname)s - %(message)s",
|
249
|
+
},
|
253
250
|
}
|
254
251
|
|
255
252
|
|
@@ -257,21 +254,21 @@ def test_config():
|
|
257
254
|
def temp_config_file(tmp_path, test_config):
|
258
255
|
"""
|
259
256
|
Create a temporary configuration file for testing.
|
260
|
-
|
257
|
+
|
261
258
|
Args:
|
262
259
|
tmp_path: Pytest temporary directory fixture
|
263
260
|
test_config: Test configuration fixture
|
264
|
-
|
261
|
+
|
265
262
|
Returns:
|
266
263
|
str: Path to temporary configuration file
|
267
264
|
"""
|
268
265
|
import json
|
269
266
|
import os
|
270
|
-
|
267
|
+
|
271
268
|
config_file = tmp_path / "test_config.json"
|
272
|
-
with open(config_file,
|
269
|
+
with open(config_file, "w") as f:
|
273
270
|
json.dump(test_config, f)
|
274
|
-
|
271
|
+
|
275
272
|
return str(config_file)
|
276
273
|
|
277
274
|
|
@@ -279,28 +276,28 @@ def temp_config_file(tmp_path, test_config):
|
|
279
276
|
def patch_security_manager():
|
280
277
|
"""
|
281
278
|
Create a patch decorator for SecurityManager.
|
282
|
-
|
279
|
+
|
283
280
|
Returns:
|
284
281
|
function: Patch decorator
|
285
282
|
"""
|
286
|
-
return patch(
|
283
|
+
return patch("mcp_security_framework.core.security_manager.SecurityManager")
|
287
284
|
|
288
285
|
|
289
286
|
def patch_certificate_manager():
|
290
287
|
"""
|
291
288
|
Create a patch decorator for CertificateManager.
|
292
|
-
|
289
|
+
|
293
290
|
Returns:
|
294
291
|
function: Patch decorator
|
295
292
|
"""
|
296
|
-
return patch(
|
293
|
+
return patch("mcp_security_framework.core.cert_manager.CertificateManager")
|
297
294
|
|
298
295
|
|
299
296
|
def patch_ssl_manager():
|
300
297
|
"""
|
301
298
|
Create a patch decorator for SSLManager.
|
302
|
-
|
299
|
+
|
303
300
|
Returns:
|
304
301
|
function: Patch decorator
|
305
302
|
"""
|
306
|
-
return patch(
|
303
|
+
return patch("mcp_security_framework.core.ssl_manager.SSLManager")
|