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
@@ -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 pytest
21
+ import os
23
22
  import tempfile
24
- from unittest.mock import Mock, patch, MagicMock
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.models import AuthResult, AuthStatus, AuthMethod, ValidationResult, ValidationStatus
29
- from mcp_security_framework.schemas.config import SecurityConfig, AuthConfig, SSLConfig, PermissionConfig, RateLimitConfig
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, 'w') as f:
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('mcp_security_framework.core.security_manager.SecurityManager.authenticate_user')
120
- @patch('mcp_security_framework.core.security_manager.SecurityManager.check_permissions')
121
- def test_fastapi_example_protected_endpoint_with_api_key(self, mock_check_permissions, mock_authenticate):
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('mcp_security_framework.core.security_manager.SecurityManager.authenticate_user')
169
- @patch('mcp_security_framework.core.security_manager.SecurityManager.check_permissions')
170
- @patch('mcp_security_framework.core.rate_limiter.RateLimiter.check_rate_limit')
171
- def test_fastapi_example_rate_limiting(self, mock_check_rate_limit, mock_check_permissions, mock_authenticate):
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
- "/api/v1/users/me",
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
- "enabled": False
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, 'w') as f:
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('mcp_security_framework.core.security_manager.SecurityManager.authenticate_user')
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
- "/api/v1/users/me",
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, 'run')
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('mcp_security_framework.core.security_manager.SecurityManager.authenticate_user')
309
- @patch('mcp_security_framework.core.security_manager.SecurityManager.check_permissions')
310
- def test_fastapi_example_jwt_authentication(self, mock_check_permissions, mock_authenticate_jwt):
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)