mcp-security-framework 1.1.0__py3-none-any.whl → 1.1.2__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.2.dist-info}/METADATA +4 -3
- mcp_security_framework-1.1.2.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.2.dist-info}/WHEEL +0 -0
- {mcp_security_framework-1.1.0.dist-info → mcp_security_framework-1.1.2.dist-info}/entry_points.txt +0 -0
- {mcp_security_framework-1.1.0.dist-info → mcp_security_framework-1.1.2.dist-info}/top_level.txt +0 -0
@@ -17,25 +17,38 @@ Version: 1.0.0
|
|
17
17
|
License: MIT
|
18
18
|
"""
|
19
19
|
|
20
|
-
import os
|
21
20
|
import json
|
22
|
-
import
|
21
|
+
import os
|
23
22
|
import tempfile
|
24
|
-
from unittest.mock import Mock, patch
|
23
|
+
from unittest.mock import MagicMock, Mock, patch
|
24
|
+
|
25
|
+
import pytest
|
25
26
|
from fastapi.testclient import TestClient
|
26
27
|
|
27
28
|
from mcp_security_framework.examples.fastapi_example import FastAPISecurityExample
|
28
|
-
from mcp_security_framework.schemas.
|
29
|
-
|
29
|
+
from mcp_security_framework.schemas.config import (
|
30
|
+
AuthConfig,
|
31
|
+
PermissionConfig,
|
32
|
+
RateLimitConfig,
|
33
|
+
SecurityConfig,
|
34
|
+
SSLConfig,
|
35
|
+
)
|
36
|
+
from mcp_security_framework.schemas.models import (
|
37
|
+
AuthMethod,
|
38
|
+
AuthResult,
|
39
|
+
AuthStatus,
|
40
|
+
ValidationResult,
|
41
|
+
ValidationStatus,
|
42
|
+
)
|
30
43
|
|
31
44
|
|
32
45
|
class TestFastAPISecurityExample:
|
33
46
|
"""Test suite for FastAPI example implementation."""
|
34
|
-
|
47
|
+
|
35
48
|
def setup_method(self):
|
36
49
|
"""Set up test fixtures before each test method."""
|
37
50
|
self.temp_dir = tempfile.mkdtemp()
|
38
|
-
|
51
|
+
|
39
52
|
# Create test configuration
|
40
53
|
self.test_config = {
|
41
54
|
"environment": "test",
|
@@ -46,17 +59,17 @@ class TestFastAPISecurityExample:
|
|
46
59
|
"methods": ["api_key", "jwt", "certificate"],
|
47
60
|
"api_keys": {
|
48
61
|
"admin_key_123": {"username": "admin", "roles": ["admin"]},
|
49
|
-
"user_key_456": {"username": "user", "roles": ["user"]}
|
62
|
+
"user_key_456": {"username": "user", "roles": ["user"]},
|
50
63
|
},
|
51
64
|
"jwt_secret": "test-super-secret-jwt-key-for-testing-purposes-only",
|
52
65
|
"jwt_algorithm": "HS256",
|
53
66
|
"jwt_expiry_hours": 24,
|
54
|
-
"public_paths": ["/health", "/metrics"]
|
67
|
+
"public_paths": ["/health", "/metrics"],
|
55
68
|
},
|
56
69
|
"permissions": {
|
57
70
|
"enabled": True,
|
58
71
|
"roles_file": "test_roles.json",
|
59
|
-
"default_role": "user"
|
72
|
+
"default_role": "user",
|
60
73
|
},
|
61
74
|
"ssl": {
|
62
75
|
"enabled": False,
|
@@ -64,61 +77,68 @@ class TestFastAPISecurityExample:
|
|
64
77
|
"key_file": None,
|
65
78
|
"ca_cert_file": None,
|
66
79
|
"verify_mode": "CERT_NONE",
|
67
|
-
"min_version": "TLSv1.2"
|
80
|
+
"min_version": "TLSv1.2",
|
68
81
|
},
|
69
82
|
"certificates": {
|
70
83
|
"enabled": False,
|
71
84
|
"ca_cert_path": None,
|
72
85
|
"ca_key_path": None,
|
73
|
-
"cert_output_dir": None
|
86
|
+
"cert_output_dir": None,
|
74
87
|
},
|
75
88
|
"rate_limit": {
|
76
89
|
"enabled": True,
|
77
90
|
"requests_per_minute": 100,
|
78
91
|
"burst_limit": 10,
|
79
|
-
"window_seconds": 60
|
80
|
-
}
|
92
|
+
"window_seconds": 60,
|
93
|
+
},
|
81
94
|
}
|
82
|
-
|
95
|
+
|
83
96
|
def teardown_method(self):
|
84
97
|
"""Clean up after each test method."""
|
85
98
|
import shutil
|
99
|
+
|
86
100
|
shutil.rmtree(self.temp_dir, ignore_errors=True)
|
87
|
-
|
101
|
+
|
88
102
|
def _create_config_file(self) -> str:
|
89
103
|
"""Create temporary configuration file for testing."""
|
90
104
|
config_file = os.path.join(self.temp_dir, "test_config.json")
|
91
|
-
with open(config_file,
|
105
|
+
with open(config_file, "w") as f:
|
92
106
|
json.dump(self.test_config, f)
|
93
107
|
return config_file
|
94
|
-
|
108
|
+
|
95
109
|
def test_fastapi_example_initialization(self):
|
96
110
|
"""Test FastAPI example initialization."""
|
97
111
|
config_file = self._create_config_file()
|
98
112
|
example = FastAPISecurityExample(config_path=config_file)
|
99
|
-
|
113
|
+
|
100
114
|
# Assertions
|
101
115
|
assert example is not None
|
102
116
|
assert example.app is not None
|
103
117
|
assert example.config is not None
|
104
118
|
assert example.security_manager is not None
|
105
|
-
|
119
|
+
|
106
120
|
def test_fastapi_example_health_endpoint(self):
|
107
121
|
"""Test health check endpoint."""
|
108
122
|
config_file = self._create_config_file()
|
109
123
|
example = FastAPISecurityExample(config_path=config_file)
|
110
124
|
client = TestClient(example.app)
|
111
|
-
|
125
|
+
|
112
126
|
# Test health endpoint
|
113
127
|
response = client.get("/health")
|
114
|
-
|
128
|
+
|
115
129
|
# Assertions
|
116
130
|
assert response.status_code == 200
|
117
131
|
assert response.json()["status"] == "healthy"
|
118
|
-
|
119
|
-
@patch(
|
120
|
-
|
121
|
-
|
132
|
+
|
133
|
+
@patch(
|
134
|
+
"mcp_security_framework.core.security_manager.SecurityManager.authenticate_user"
|
135
|
+
)
|
136
|
+
@patch(
|
137
|
+
"mcp_security_framework.core.security_manager.SecurityManager.check_permissions"
|
138
|
+
)
|
139
|
+
def test_fastapi_example_protected_endpoint_with_api_key(
|
140
|
+
self, mock_check_permissions, mock_authenticate
|
141
|
+
):
|
122
142
|
"""Test protected endpoint with API key authentication."""
|
123
143
|
# Mock authentication
|
124
144
|
mock_authenticate.return_value = AuthResult(
|
@@ -126,49 +146,53 @@ class TestFastAPISecurityExample:
|
|
126
146
|
status=AuthStatus.SUCCESS,
|
127
147
|
username="admin",
|
128
148
|
roles=["admin"],
|
129
|
-
auth_method=AuthMethod.API_KEY
|
149
|
+
auth_method=AuthMethod.API_KEY,
|
130
150
|
)
|
131
|
-
|
151
|
+
|
132
152
|
# Mock permission check
|
133
153
|
mock_check_permissions.return_value = ValidationResult(
|
134
|
-
is_valid=True,
|
135
|
-
status=ValidationStatus.VALID
|
154
|
+
is_valid=True, status=ValidationStatus.VALID
|
136
155
|
)
|
137
|
-
|
156
|
+
|
138
157
|
# Create example
|
139
158
|
config_file = self._create_config_file()
|
140
159
|
example = FastAPISecurityExample(config_path=config_file)
|
141
160
|
client = TestClient(example.app)
|
142
|
-
|
161
|
+
|
143
162
|
# Test protected endpoint
|
144
163
|
response = client.get(
|
145
|
-
"/api/v1/users/me",
|
146
|
-
headers={"X-API-Key": "admin_key_123"}
|
164
|
+
"/api/v1/users/me", headers={"X-API-Key": "admin_key_123"}
|
147
165
|
)
|
148
|
-
|
166
|
+
|
149
167
|
# Assertions
|
150
168
|
assert response.status_code == 200
|
151
169
|
response_data = response.json()
|
152
170
|
assert "user" in response_data
|
153
171
|
assert "username" in response_data["user"]
|
154
|
-
|
172
|
+
|
155
173
|
def test_fastapi_example_protected_endpoint_unauthorized(self):
|
156
174
|
"""Test protected endpoint without authentication."""
|
157
175
|
# Create example
|
158
176
|
config_file = self._create_config_file()
|
159
177
|
example = FastAPISecurityExample(config_path=config_file)
|
160
178
|
client = TestClient(example.app)
|
161
|
-
|
179
|
+
|
162
180
|
# Test protected endpoint without auth
|
163
181
|
response = client.get("/api/v1/users/me")
|
164
|
-
|
182
|
+
|
165
183
|
# Assertions
|
166
184
|
assert response.status_code == 401
|
167
|
-
|
168
|
-
@patch(
|
169
|
-
|
170
|
-
|
171
|
-
|
185
|
+
|
186
|
+
@patch(
|
187
|
+
"mcp_security_framework.core.security_manager.SecurityManager.authenticate_user"
|
188
|
+
)
|
189
|
+
@patch(
|
190
|
+
"mcp_security_framework.core.security_manager.SecurityManager.check_permissions"
|
191
|
+
)
|
192
|
+
@patch("mcp_security_framework.core.rate_limiter.RateLimiter.check_rate_limit")
|
193
|
+
def test_fastapi_example_rate_limiting(
|
194
|
+
self, mock_check_rate_limit, mock_check_permissions, mock_authenticate
|
195
|
+
):
|
172
196
|
"""Test rate limiting functionality."""
|
173
197
|
# Mock authentication
|
174
198
|
mock_authenticate.return_value = AuthResult(
|
@@ -176,57 +200,54 @@ class TestFastAPISecurityExample:
|
|
176
200
|
status=AuthStatus.SUCCESS,
|
177
201
|
username="user",
|
178
202
|
roles=["user"],
|
179
|
-
auth_method=AuthMethod.API_KEY
|
203
|
+
auth_method=AuthMethod.API_KEY,
|
180
204
|
)
|
181
|
-
|
205
|
+
|
182
206
|
# Mock permission check
|
183
207
|
mock_check_permissions.return_value = ValidationResult(
|
184
|
-
is_valid=True,
|
185
|
-
status=ValidationStatus.VALID
|
208
|
+
is_valid=True, status=ValidationStatus.VALID
|
186
209
|
)
|
187
|
-
|
210
|
+
|
188
211
|
# Mock rate limiting - first 100 requests allowed, then blocked
|
189
212
|
request_count = 0
|
213
|
+
|
190
214
|
def mock_rate_limit(identifier):
|
191
215
|
nonlocal request_count
|
192
216
|
request_count += 1
|
193
217
|
return request_count <= 100
|
194
|
-
|
218
|
+
|
195
219
|
mock_check_rate_limit.side_effect = mock_rate_limit
|
196
|
-
|
220
|
+
|
197
221
|
# Create example
|
198
222
|
config_file = self._create_config_file()
|
199
223
|
example = FastAPISecurityExample(config_path=config_file)
|
200
224
|
client = TestClient(example.app)
|
201
|
-
|
225
|
+
|
202
226
|
# Test rate limiting
|
203
|
-
response = client.get(
|
204
|
-
|
205
|
-
headers={"X-API-Key": "user_key_456"}
|
206
|
-
)
|
207
|
-
|
227
|
+
response = client.get("/api/v1/users/me", headers={"X-API-Key": "user_key_456"})
|
228
|
+
|
208
229
|
# Assertions
|
209
230
|
assert response.status_code == 200
|
210
|
-
|
231
|
+
|
211
232
|
def test_fastapi_example_ssl_configuration(self):
|
212
233
|
"""Test SSL configuration."""
|
213
234
|
# SSL configuration
|
214
235
|
ssl_config = self.test_config.copy()
|
215
|
-
ssl_config["ssl"] = {
|
216
|
-
|
217
|
-
}
|
218
|
-
|
236
|
+
ssl_config["ssl"] = {"enabled": False}
|
237
|
+
|
219
238
|
# Create example
|
220
239
|
config_file = os.path.join(self.temp_dir, "ssl_config.json")
|
221
|
-
with open(config_file,
|
240
|
+
with open(config_file, "w") as f:
|
222
241
|
json.dump(ssl_config, f)
|
223
|
-
|
242
|
+
|
224
243
|
example = FastAPISecurityExample(config_path=config_file)
|
225
|
-
|
244
|
+
|
226
245
|
# Assertions
|
227
246
|
assert example.app is not None
|
228
|
-
|
229
|
-
@patch(
|
247
|
+
|
248
|
+
@patch(
|
249
|
+
"mcp_security_framework.core.security_manager.SecurityManager.authenticate_user"
|
250
|
+
)
|
230
251
|
def test_fastapi_example_error_handling(self, mock_authenticate):
|
231
252
|
"""Test error handling."""
|
232
253
|
# Mock authentication failure
|
@@ -237,77 +258,80 @@ class TestFastAPISecurityExample:
|
|
237
258
|
roles=[],
|
238
259
|
auth_method=None,
|
239
260
|
error_code=-32002,
|
240
|
-
error_message="Authentication failed"
|
261
|
+
error_message="Authentication failed",
|
241
262
|
)
|
242
|
-
|
263
|
+
|
243
264
|
# Create example
|
244
265
|
config_file = self._create_config_file()
|
245
266
|
example = FastAPISecurityExample(config_path=config_file)
|
246
267
|
client = TestClient(example.app)
|
247
|
-
|
268
|
+
|
248
269
|
# Test error handling
|
249
|
-
response = client.get(
|
250
|
-
|
251
|
-
headers={"X-API-Key": "invalid_key"}
|
252
|
-
)
|
253
|
-
|
270
|
+
response = client.get("/api/v1/users/me", headers={"X-API-Key": "invalid_key"})
|
271
|
+
|
254
272
|
# Assertions
|
255
273
|
assert response.status_code == 401
|
256
|
-
|
274
|
+
|
257
275
|
def test_fastapi_example_metrics_endpoint(self):
|
258
276
|
"""Test metrics endpoint."""
|
259
277
|
# Create example
|
260
278
|
config_file = self._create_config_file()
|
261
279
|
example = FastAPISecurityExample(config_path=config_file)
|
262
280
|
client = TestClient(example.app)
|
263
|
-
|
281
|
+
|
264
282
|
# Test metrics endpoint
|
265
283
|
response = client.get("/metrics")
|
266
|
-
|
284
|
+
|
267
285
|
# Assertions
|
268
286
|
assert response.status_code == 200
|
269
287
|
response_data = response.json()
|
270
288
|
assert "metrics" in response_data
|
271
289
|
assert "authentication_attempts" in response_data["metrics"]
|
272
|
-
|
290
|
+
|
273
291
|
def test_fastapi_example_run_method(self):
|
274
292
|
"""Test FastAPI example run method."""
|
275
293
|
# Create example
|
276
294
|
config_file = self._create_config_file()
|
277
295
|
example = FastAPISecurityExample(config_path=config_file)
|
278
|
-
|
296
|
+
|
279
297
|
# Test run method (should not raise exception)
|
280
298
|
try:
|
281
299
|
# This would normally start a server, but we're just testing the method exists
|
282
|
-
assert hasattr(example,
|
300
|
+
assert hasattr(example, "run")
|
283
301
|
except Exception as e:
|
284
302
|
# Expected behavior - server can't start in test environment
|
285
303
|
pass
|
286
|
-
|
304
|
+
|
287
305
|
def test_fastapi_example_config_loading(self):
|
288
306
|
"""Test configuration loading from file."""
|
289
307
|
config_file = self._create_config_file()
|
290
308
|
example = FastAPISecurityExample(config_path=config_file)
|
291
|
-
|
309
|
+
|
292
310
|
# Assertions
|
293
311
|
assert example.config.environment == "test"
|
294
312
|
assert example.config.auth.enabled is True
|
295
313
|
assert example.config.ssl.enabled is False
|
296
|
-
|
314
|
+
|
297
315
|
def test_fastapi_example_default_config(self):
|
298
316
|
"""Test FastAPI example with default configuration."""
|
299
317
|
# Use configuration with SSL disabled to avoid certificate file issues
|
300
318
|
config_file = self._create_config_file()
|
301
319
|
example = FastAPISecurityExample(config_path=config_file)
|
302
|
-
|
320
|
+
|
303
321
|
# Assertions
|
304
322
|
assert example is not None
|
305
323
|
assert example.app is not None
|
306
324
|
assert example.config is not None
|
307
|
-
|
308
|
-
@patch(
|
309
|
-
|
310
|
-
|
325
|
+
|
326
|
+
@patch(
|
327
|
+
"mcp_security_framework.core.security_manager.SecurityManager.authenticate_user"
|
328
|
+
)
|
329
|
+
@patch(
|
330
|
+
"mcp_security_framework.core.security_manager.SecurityManager.check_permissions"
|
331
|
+
)
|
332
|
+
def test_fastapi_example_jwt_authentication(
|
333
|
+
self, mock_check_permissions, mock_authenticate_jwt
|
334
|
+
):
|
311
335
|
"""Test JWT token authentication."""
|
312
336
|
# Mock JWT authentication
|
313
337
|
mock_authenticate_jwt.return_value = AuthResult(
|
@@ -315,47 +339,46 @@ class TestFastAPISecurityExample:
|
|
315
339
|
status=AuthStatus.SUCCESS,
|
316
340
|
username="user",
|
317
341
|
roles=["user"],
|
318
|
-
auth_method=AuthMethod.JWT
|
342
|
+
auth_method=AuthMethod.JWT,
|
319
343
|
)
|
320
|
-
|
344
|
+
|
321
345
|
# Mock permission check
|
322
346
|
mock_check_permissions.return_value = ValidationResult(
|
323
|
-
is_valid=True,
|
324
|
-
status=ValidationStatus.VALID
|
347
|
+
is_valid=True, status=ValidationStatus.VALID
|
325
348
|
)
|
326
|
-
|
349
|
+
|
327
350
|
# Create example
|
328
351
|
config_file = self._create_config_file()
|
329
352
|
example = FastAPISecurityExample(config_path=config_file)
|
330
353
|
client = TestClient(example.app)
|
331
|
-
|
354
|
+
|
332
355
|
# Test JWT authentication - use a public endpoint that doesn't require auth
|
333
356
|
response = client.get("/health")
|
334
|
-
|
357
|
+
|
335
358
|
# Assertions
|
336
359
|
assert response.status_code == 200
|
337
|
-
|
360
|
+
|
338
361
|
def test_fastapi_example_cors_configuration(self):
|
339
362
|
"""Test CORS configuration."""
|
340
363
|
config_file = self._create_config_file()
|
341
364
|
example = FastAPISecurityExample(config_path=config_file)
|
342
365
|
client = TestClient(example.app)
|
343
|
-
|
366
|
+
|
344
367
|
# Test CORS headers - use GET request instead of OPTIONS
|
345
368
|
response = client.get("/health")
|
346
|
-
|
369
|
+
|
347
370
|
# Assertions
|
348
371
|
assert response.status_code == 200
|
349
|
-
|
372
|
+
|
350
373
|
def test_fastapi_example_security_headers(self):
|
351
374
|
"""Test security headers configuration."""
|
352
375
|
config_file = self._create_config_file()
|
353
376
|
example = FastAPISecurityExample(config_path=config_file)
|
354
377
|
client = TestClient(example.app)
|
355
|
-
|
378
|
+
|
356
379
|
# Test security headers
|
357
380
|
response = client.get("/health")
|
358
|
-
|
381
|
+
|
359
382
|
# Assertions
|
360
383
|
assert response.status_code == 200
|
361
384
|
# Check that response has headers (basic check)
|