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.
Files changed (58) hide show
  1. mcp_security_framework/__init__.py +26 -15
  2. mcp_security_framework/cli/__init__.py +1 -1
  3. mcp_security_framework/cli/cert_cli.py +233 -197
  4. mcp_security_framework/cli/security_cli.py +324 -234
  5. mcp_security_framework/constants.py +21 -27
  6. mcp_security_framework/core/auth_manager.py +41 -22
  7. mcp_security_framework/core/cert_manager.py +210 -147
  8. mcp_security_framework/core/permission_manager.py +9 -9
  9. mcp_security_framework/core/rate_limiter.py +2 -2
  10. mcp_security_framework/core/security_manager.py +284 -229
  11. mcp_security_framework/examples/__init__.py +6 -0
  12. mcp_security_framework/examples/comprehensive_example.py +349 -279
  13. mcp_security_framework/examples/django_example.py +247 -206
  14. mcp_security_framework/examples/fastapi_example.py +315 -283
  15. mcp_security_framework/examples/flask_example.py +274 -203
  16. mcp_security_framework/examples/gateway_example.py +304 -237
  17. mcp_security_framework/examples/microservice_example.py +258 -189
  18. mcp_security_framework/examples/standalone_example.py +255 -230
  19. mcp_security_framework/examples/test_all_examples.py +151 -135
  20. mcp_security_framework/middleware/__init__.py +46 -55
  21. mcp_security_framework/middleware/auth_middleware.py +62 -63
  22. mcp_security_framework/middleware/fastapi_auth_middleware.py +119 -118
  23. mcp_security_framework/middleware/fastapi_middleware.py +156 -148
  24. mcp_security_framework/middleware/flask_auth_middleware.py +160 -147
  25. mcp_security_framework/middleware/flask_middleware.py +183 -157
  26. mcp_security_framework/middleware/mtls_middleware.py +106 -117
  27. mcp_security_framework/middleware/rate_limit_middleware.py +105 -101
  28. mcp_security_framework/middleware/security_middleware.py +109 -124
  29. mcp_security_framework/schemas/config.py +2 -1
  30. mcp_security_framework/schemas/models.py +18 -6
  31. mcp_security_framework/utils/cert_utils.py +14 -8
  32. mcp_security_framework/utils/datetime_compat.py +116 -0
  33. {mcp_security_framework-1.1.0.dist-info → mcp_security_framework-1.1.1.dist-info}/METADATA +2 -1
  34. mcp_security_framework-1.1.1.dist-info/RECORD +84 -0
  35. tests/conftest.py +63 -66
  36. tests/test_cli/test_cert_cli.py +184 -146
  37. tests/test_cli/test_security_cli.py +274 -247
  38. tests/test_core/test_cert_manager.py +24 -10
  39. tests/test_core/test_security_manager.py +2 -2
  40. tests/test_examples/test_comprehensive_example.py +190 -137
  41. tests/test_examples/test_fastapi_example.py +124 -101
  42. tests/test_examples/test_flask_example.py +124 -101
  43. tests/test_examples/test_standalone_example.py +73 -80
  44. tests/test_integration/test_auth_flow.py +213 -197
  45. tests/test_integration/test_certificate_flow.py +180 -149
  46. tests/test_integration/test_fastapi_integration.py +108 -111
  47. tests/test_integration/test_flask_integration.py +141 -140
  48. tests/test_integration/test_standalone_integration.py +290 -259
  49. tests/test_middleware/test_fastapi_auth_middleware.py +195 -174
  50. tests/test_middleware/test_fastapi_middleware.py +147 -132
  51. tests/test_middleware/test_flask_auth_middleware.py +260 -202
  52. tests/test_middleware/test_flask_middleware.py +201 -179
  53. tests/test_middleware/test_security_middleware.py +145 -130
  54. tests/test_utils/test_datetime_compat.py +147 -0
  55. mcp_security_framework-1.1.0.dist-info/RECORD +0 -82
  56. {mcp_security_framework-1.1.0.dist-info → mcp_security_framework-1.1.1.dist-info}/WHEEL +0 -0
  57. {mcp_security_framework-1.1.0.dist-info → mcp_security_framework-1.1.1.dist-info}/entry_points.txt +0 -0
  58. {mcp_security_framework-1.1.0.dist-info → mcp_security_framework-1.1.1.dist-info}/top_level.txt +0 -0
@@ -11,25 +11,30 @@ License: MIT
11
11
  """
12
12
 
13
13
  import json
14
- import tempfile
15
14
  import os
16
- from unittest.mock import patch, MagicMock
17
- from typing import Dict, Any
15
+ import tempfile
16
+ from typing import Any, Dict
17
+ from unittest.mock import MagicMock, patch
18
18
 
19
19
  import pytest
20
- from fastapi.testclient import TestClient
21
20
  from cryptography import x509
22
21
  from cryptography.hazmat.primitives import hashes, serialization
23
22
  from cryptography.hazmat.primitives.asymmetric import rsa
23
+ from fastapi.testclient import TestClient
24
24
 
25
- from mcp_security_framework.examples.fastapi_example import FastAPISecurityExample
26
25
  from mcp_security_framework.core.security_manager import SecurityManager
27
- from mcp_security_framework.schemas.config import SecurityConfig, AuthConfig, RateLimitConfig, SSLConfig
26
+ from mcp_security_framework.examples.fastapi_example import FastAPISecurityExample
27
+ from mcp_security_framework.schemas.config import (
28
+ AuthConfig,
29
+ RateLimitConfig,
30
+ SecurityConfig,
31
+ SSLConfig,
32
+ )
28
33
 
29
34
 
30
35
  class TestFastAPIIntegration:
31
36
  """Integration tests for FastAPI with security framework."""
32
-
37
+
33
38
  def setup_method(self):
34
39
  """Set up test fixtures before each test method."""
35
40
  # Create temporary configuration
@@ -40,237 +45,232 @@ class TestFastAPIIntegration:
40
45
  "api_keys": {
41
46
  "admin_key_123": {"username": "admin", "roles": ["admin", "user"]},
42
47
  "user_key_456": {"username": "user", "roles": ["user"]},
43
- "readonly_key_789": {"username": "readonly", "roles": ["readonly"]}
48
+ "readonly_key_789": {"username": "readonly", "roles": ["readonly"]},
44
49
  },
45
- "public_paths": ["/health", "/metrics"]
46
- },
47
- "rate_limit": {
48
- "enabled": True,
49
- "default_requests_per_minute": 100
50
+ "public_paths": ["/health", "/metrics"],
50
51
  },
51
- "ssl": {
52
- "enabled": False
53
- },
54
- "permissions": {
55
- "enabled": True,
56
- "roles_file": "test_roles.json"
57
- },
58
- "certificates": {
59
- "enabled": False
60
- },
61
- "logging": {
62
- "level": "INFO",
63
- "format": "standard"
64
- }
52
+ "rate_limit": {"enabled": True, "default_requests_per_minute": 100},
53
+ "ssl": {"enabled": False},
54
+ "permissions": {"enabled": True, "roles_file": "test_roles.json"},
55
+ "certificates": {"enabled": False},
56
+ "logging": {"level": "INFO", "format": "standard"},
65
57
  }
66
-
58
+
67
59
  # Create temporary config file
68
- self.config_fd, self.config_path = tempfile.mkstemp(suffix='.json')
69
- with os.fdopen(self.config_fd, 'w') as f:
60
+ self.config_fd, self.config_path = tempfile.mkstemp(suffix=".json")
61
+ with os.fdopen(self.config_fd, "w") as f:
70
62
  json.dump(self.test_config, f)
71
-
63
+
72
64
  # Create temporary roles file
73
65
  self.roles_config = {
74
66
  "roles": {
75
67
  "admin": {
76
- "permissions": ["read:own", "write:own", "delete:own", "admin", "*"],
77
- "description": "Administrator role"
68
+ "permissions": [
69
+ "read:own",
70
+ "write:own",
71
+ "delete:own",
72
+ "admin",
73
+ "*",
74
+ ],
75
+ "description": "Administrator role",
78
76
  },
79
77
  "user": {
80
78
  "permissions": ["read:own", "write:own"],
81
- "description": "Regular user role"
79
+ "description": "Regular user role",
82
80
  },
83
81
  "readonly": {
84
82
  "permissions": ["read:own"],
85
- "description": "Read-only user role"
86
- }
83
+ "description": "Read-only user role",
84
+ },
87
85
  }
88
86
  }
89
-
90
- self.roles_fd, self.roles_path = tempfile.mkstemp(suffix='.json')
91
- with os.fdopen(self.roles_fd, 'w') as f:
87
+
88
+ self.roles_fd, self.roles_path = tempfile.mkstemp(suffix=".json")
89
+ with os.fdopen(self.roles_fd, "w") as f:
92
90
  json.dump(self.roles_config, f)
93
-
91
+
94
92
  # Update config to use roles file
95
93
  self.test_config["permissions"]["roles_file"] = self.roles_path
96
-
94
+
97
95
  # Recreate config file with updated roles path
98
- with open(self.config_path, 'w') as f:
96
+ with open(self.config_path, "w") as f:
99
97
  json.dump(self.test_config, f)
100
-
98
+
101
99
  def teardown_method(self):
102
100
  """Clean up after each test method."""
103
101
  # Remove temporary files
104
- if hasattr(self, 'config_path') and os.path.exists(self.config_path):
102
+ if hasattr(self, "config_path") and os.path.exists(self.config_path):
105
103
  os.unlink(self.config_path)
106
- if hasattr(self, 'roles_path') and os.path.exists(self.roles_path):
104
+ if hasattr(self, "roles_path") and os.path.exists(self.roles_path):
107
105
  os.unlink(self.roles_path)
108
-
106
+
109
107
  def test_fastapi_full_integration(self):
110
108
  """Test complete FastAPI integration with security framework."""
111
109
  # Create FastAPI example
112
110
  example = FastAPISecurityExample(config_path=self.config_path)
113
-
111
+
114
112
  # Test that the app is properly configured
115
113
  assert example.app is not None
116
- assert hasattr(example.app, 'add_middleware')
117
-
114
+ assert hasattr(example.app, "add_middleware")
115
+
118
116
  # Test that security manager is configured
119
117
  assert example.security_manager is not None
120
118
  assert isinstance(example.security_manager, SecurityManager)
121
-
119
+
122
120
  # Test that configuration is loaded
123
121
  assert example.config is not None
124
122
  assert example.config.auth.enabled is True
125
123
  assert example.config.rate_limit.enabled is True
126
-
124
+
127
125
  def test_fastapi_authentication_flow(self):
128
126
  """Test complete authentication flow in FastAPI."""
129
127
  example = FastAPISecurityExample(config_path=self.config_path)
130
128
  client = TestClient(example.app)
131
-
129
+
132
130
  # Test unauthenticated access to protected endpoint
133
131
  response = client.get("/api/v1/users/me")
134
132
  assert response.status_code == 401
135
-
133
+
136
134
  # Test authenticated access with valid API key
137
135
  headers = {"X-API-Key": "admin_key_123"}
138
136
  response = client.get("/api/v1/users/me", headers=headers)
139
137
  assert response.status_code == 200 # Should be authenticated
140
-
138
+
141
139
  # Test authenticated access with different user
142
140
  headers = {"X-API-Key": "user_key_456"}
143
141
  response = client.get("/api/v1/users/me", headers=headers)
144
142
  assert response.status_code == 200 # User should be authenticated
145
-
143
+
146
144
  def test_fastapi_authorization_flow(self):
147
145
  """Test complete authorization flow in FastAPI."""
148
146
  example = FastAPISecurityExample(config_path=self.config_path)
149
147
  client = TestClient(example.app)
150
-
148
+
151
149
  # Test admin access to admin-only endpoint
152
150
  headers = {"X-API-Key": "admin_key_123"}
153
151
  response = client.get("/admin/users", headers=headers)
154
152
  assert response.status_code == 200 # Admin should have access
155
-
153
+
156
154
  # Test regular user access to admin-only endpoint (should be denied)
157
155
  headers = {"X-API-Key": "user_key_456"}
158
156
  response = client.get("/admin/users", headers=headers)
159
157
  assert response.status_code == 403 # User should be denied admin access
160
-
161
- # Test readonly user access to data endpoint (should be allowed for read)
158
+
159
+ # Test readonly user access to data endpoint (should be allowed for read)
162
160
  headers = {"X-API-Key": "readonly_key_789"}
163
161
  response = client.get("/api/v1/data", headers=headers)
164
162
  assert response.status_code == 200 # Readonly user should have read access
165
-
163
+
166
164
  def test_fastapi_rate_limiting(self):
167
165
  """Test rate limiting in FastAPI."""
168
166
  example = FastAPISecurityExample(config_path=self.config_path)
169
167
  client = TestClient(example.app)
170
-
168
+
171
169
  headers = {"X-API-Key": "user_key_456"}
172
-
170
+
173
171
  # Make multiple requests to trigger rate limiting
174
172
  responses = []
175
173
  for i in range(105): # Exceed the 100 requests per minute limit
176
174
  response = client.get("/api/v1/users/me", headers=headers)
177
175
  responses.append(response.status_code)
178
-
176
+
179
177
  # Check that some requests were rate limited
180
178
  # Note: Rate limiting may not be triggered in test environment
181
179
  # but the requests should still be processed
182
180
  assert len(responses) == 105, "All requests should be processed"
183
- assert all(status in [200, 429] for status in responses), "Responses should be either 200 or 429"
184
-
181
+ assert all(
182
+ status in [200, 429] for status in responses
183
+ ), "Responses should be either 200 or 429"
184
+
185
185
  def test_fastapi_ssl_integration(self):
186
186
  """Test SSL/TLS integration in FastAPI."""
187
187
  # Create config with SSL enabled
188
188
  ssl_config = self.test_config.copy()
189
- ssl_config["ssl"] = {
190
- "enabled": False # Disable SSL for testing
191
- }
192
-
189
+ ssl_config["ssl"] = {"enabled": False} # Disable SSL for testing
190
+
193
191
  # Create temporary SSL config file
194
- ssl_config_fd, ssl_config_path = tempfile.mkstemp(suffix='.json')
195
- with os.fdopen(ssl_config_fd, 'w') as f:
192
+ ssl_config_fd, ssl_config_path = tempfile.mkstemp(suffix=".json")
193
+ with os.fdopen(ssl_config_fd, "w") as f:
196
194
  json.dump(ssl_config, f)
197
-
195
+
198
196
  try:
199
197
  # Mock SSL context creation to avoid file requirements
200
- with patch('mcp_security_framework.core.ssl_manager.SSLManager.create_server_context') as mock_ssl:
198
+ with patch(
199
+ "mcp_security_framework.core.ssl_manager.SSLManager.create_server_context"
200
+ ) as mock_ssl:
201
201
  mock_ssl.return_value = MagicMock()
202
-
202
+
203
203
  example = FastAPISecurityExample(config_path=ssl_config_path)
204
-
204
+
205
205
  # Test that SSL is configured
206
206
  assert example.config.ssl.enabled is False # SSL disabled for testing
207
-
207
+
208
208
  finally:
209
209
  os.unlink(ssl_config_path)
210
-
210
+
211
211
  def test_fastapi_error_handling(self):
212
212
  """Test error handling in FastAPI integration."""
213
213
  example = FastAPISecurityExample(config_path=self.config_path)
214
214
  client = TestClient(example.app)
215
-
215
+
216
216
  # Test invalid API key
217
217
  headers = {"X-API-Key": "invalid_key"}
218
218
  response = client.get("/api/v1/users/me", headers=headers)
219
219
  assert response.status_code == 401
220
-
220
+
221
221
  # Test malformed request
222
222
  headers = {"X-API-Key": "admin_key_123"}
223
223
  response = client.get("/api/v1/data", headers=headers)
224
224
  assert response.status_code == 200 # Should succeed with valid auth
225
-
225
+
226
226
  def test_fastapi_health_and_metrics(self):
227
227
  """Test health check and metrics endpoints."""
228
228
  example = FastAPISecurityExample(config_path=self.config_path)
229
229
  client = TestClient(example.app)
230
-
230
+
231
231
  # Test health check
232
232
  response = client.get("/health")
233
233
  assert response.status_code == 200
234
234
  data = response.json()
235
235
  assert "status" in data
236
236
  assert data["status"] == "healthy"
237
-
237
+
238
238
  # Test metrics
239
239
  response = client.get("/metrics")
240
240
  assert response.status_code == 200
241
241
  data = response.json()
242
242
  assert "metrics" in data # The actual response structure has "metrics" wrapper
243
243
  assert "framework" in data
244
-
244
+
245
245
  def test_fastapi_data_operations(self):
246
246
  """Test data operations with security."""
247
247
  example = FastAPISecurityExample(config_path=self.config_path)
248
248
  client = TestClient(example.app)
249
-
249
+
250
250
  headers = {"X-API-Key": "admin_key_123"}
251
-
251
+
252
252
  # Get data
253
253
  create_response = client.get("/api/v1/data", headers=headers)
254
254
 
255
255
  assert create_response.status_code == 200 # Should succeed with valid auth
256
256
  data = create_response.json()
257
257
  assert "message" in data # The actual response contains "message"
258
-
258
+
259
259
  # Retrieve data (using the general data endpoint since there's no specific ID endpoint)
260
260
  get_response = client.get("/api/v1/data", headers=headers)
261
261
  assert get_response.status_code == 200
262
262
  retrieved_data = get_response.json()
263
263
  assert "data" in retrieved_data
264
-
264
+
265
265
  def test_fastapi_middleware_integration(self):
266
266
  """Test that security middleware is properly integrated."""
267
267
  example = FastAPISecurityExample(config_path=self.config_path)
268
-
268
+
269
269
  # Check that middleware is configured
270
270
  # Note: In test environment, middleware setup is skipped
271
271
  # but we can verify the app structure
272
- assert hasattr(example.app, 'user_middleware')
273
-
272
+ assert hasattr(example.app, "user_middleware")
273
+
274
274
  # Test that routes are properly configured
275
275
  routes = [route.path for route in example.app.routes]
276
276
  expected_routes = [
@@ -278,58 +278,55 @@ class TestFastAPIIntegration:
278
278
  "/metrics",
279
279
  "/admin/users",
280
280
  "/api/v1/users/me",
281
- "/api/v1/data"
281
+ "/api/v1/data",
282
282
  ]
283
-
283
+
284
284
  for route in expected_routes:
285
285
  assert route in routes, f"Route {route} not found in app routes"
286
-
286
+
287
287
  def test_fastapi_configuration_validation(self):
288
288
  """Test configuration validation in FastAPI integration."""
289
289
  # Test with invalid configuration
290
- invalid_config = {
291
- "auth": {
292
- "enabled": True,
293
- "methods": ["invalid_method"]
294
- }
295
- }
296
-
297
- invalid_config_fd, invalid_config_path = tempfile.mkstemp(suffix='.json')
298
- with os.fdopen(invalid_config_fd, 'w') as f:
290
+ invalid_config = {"auth": {"enabled": True, "methods": ["invalid_method"]}}
291
+
292
+ invalid_config_fd, invalid_config_path = tempfile.mkstemp(suffix=".json")
293
+ with os.fdopen(invalid_config_fd, "w") as f:
299
294
  json.dump(invalid_config, f)
300
-
295
+
301
296
  try:
302
297
  # Should raise validation error
303
298
  with pytest.raises(Exception):
304
299
  FastAPISecurityExample(config_path=invalid_config_path)
305
300
  finally:
306
301
  os.unlink(invalid_config_path)
307
-
302
+
308
303
  def test_fastapi_performance_benchmark(self):
309
304
  """Test performance of FastAPI integration."""
310
305
  example = FastAPISecurityExample(config_path=self.config_path)
311
306
  client = TestClient(example.app)
312
-
307
+
313
308
  headers = {"X-API-Key": "user_key_456"}
314
-
309
+
315
310
  import time
316
-
311
+
317
312
  # Benchmark health check endpoint
318
313
  start_time = time.time()
319
314
  for _ in range(100):
320
315
  response = client.get("/health")
321
316
  assert response.status_code == 200
322
317
  end_time = time.time()
323
-
318
+
324
319
  avg_time = (end_time - start_time) / 100
325
320
  assert avg_time < 0.01, f"Health check too slow: {avg_time:.4f}s per request"
326
-
321
+
327
322
  # Benchmark authenticated endpoint
328
323
  start_time = time.time()
329
324
  for _ in range(50):
330
325
  response = client.get("/api/v1/users/me", headers=headers)
331
326
  assert response.status_code == 200 # Should be authenticated
332
327
  end_time = time.time()
333
-
328
+
334
329
  avg_time = (end_time - start_time) / 50
335
- assert avg_time < 0.02, f"Authenticated endpoint too slow: {avg_time:.4f}s per request"
330
+ assert (
331
+ avg_time < 0.02
332
+ ), f"Authenticated endpoint too slow: {avg_time:.4f}s per request"