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