mcp-security-framework 0.1.0__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 (76) hide show
  1. mcp_security_framework/__init__.py +96 -0
  2. mcp_security_framework/cli/__init__.py +18 -0
  3. mcp_security_framework/cli/cert_cli.py +511 -0
  4. mcp_security_framework/cli/security_cli.py +791 -0
  5. mcp_security_framework/constants.py +209 -0
  6. mcp_security_framework/core/__init__.py +61 -0
  7. mcp_security_framework/core/auth_manager.py +1011 -0
  8. mcp_security_framework/core/cert_manager.py +1663 -0
  9. mcp_security_framework/core/permission_manager.py +735 -0
  10. mcp_security_framework/core/rate_limiter.py +602 -0
  11. mcp_security_framework/core/security_manager.py +943 -0
  12. mcp_security_framework/core/ssl_manager.py +735 -0
  13. mcp_security_framework/examples/__init__.py +75 -0
  14. mcp_security_framework/examples/django_example.py +615 -0
  15. mcp_security_framework/examples/fastapi_example.py +472 -0
  16. mcp_security_framework/examples/flask_example.py +506 -0
  17. mcp_security_framework/examples/gateway_example.py +803 -0
  18. mcp_security_framework/examples/microservice_example.py +690 -0
  19. mcp_security_framework/examples/standalone_example.py +576 -0
  20. mcp_security_framework/middleware/__init__.py +250 -0
  21. mcp_security_framework/middleware/auth_middleware.py +292 -0
  22. mcp_security_framework/middleware/fastapi_auth_middleware.py +447 -0
  23. mcp_security_framework/middleware/fastapi_middleware.py +757 -0
  24. mcp_security_framework/middleware/flask_auth_middleware.py +465 -0
  25. mcp_security_framework/middleware/flask_middleware.py +591 -0
  26. mcp_security_framework/middleware/mtls_middleware.py +439 -0
  27. mcp_security_framework/middleware/rate_limit_middleware.py +403 -0
  28. mcp_security_framework/middleware/security_middleware.py +507 -0
  29. mcp_security_framework/schemas/__init__.py +109 -0
  30. mcp_security_framework/schemas/config.py +694 -0
  31. mcp_security_framework/schemas/models.py +709 -0
  32. mcp_security_framework/schemas/responses.py +686 -0
  33. mcp_security_framework/tests/__init__.py +0 -0
  34. mcp_security_framework/utils/__init__.py +121 -0
  35. mcp_security_framework/utils/cert_utils.py +525 -0
  36. mcp_security_framework/utils/crypto_utils.py +475 -0
  37. mcp_security_framework/utils/validation_utils.py +571 -0
  38. mcp_security_framework-0.1.0.dist-info/METADATA +411 -0
  39. mcp_security_framework-0.1.0.dist-info/RECORD +76 -0
  40. mcp_security_framework-0.1.0.dist-info/WHEEL +5 -0
  41. mcp_security_framework-0.1.0.dist-info/entry_points.txt +3 -0
  42. mcp_security_framework-0.1.0.dist-info/top_level.txt +2 -0
  43. tests/__init__.py +0 -0
  44. tests/test_cli/__init__.py +0 -0
  45. tests/test_cli/test_cert_cli.py +379 -0
  46. tests/test_cli/test_security_cli.py +657 -0
  47. tests/test_core/__init__.py +0 -0
  48. tests/test_core/test_auth_manager.py +582 -0
  49. tests/test_core/test_cert_manager.py +795 -0
  50. tests/test_core/test_permission_manager.py +395 -0
  51. tests/test_core/test_rate_limiter.py +626 -0
  52. tests/test_core/test_security_manager.py +841 -0
  53. tests/test_core/test_ssl_manager.py +532 -0
  54. tests/test_examples/__init__.py +8 -0
  55. tests/test_examples/test_fastapi_example.py +264 -0
  56. tests/test_examples/test_flask_example.py +238 -0
  57. tests/test_examples/test_standalone_example.py +292 -0
  58. tests/test_integration/__init__.py +0 -0
  59. tests/test_integration/test_auth_flow.py +502 -0
  60. tests/test_integration/test_certificate_flow.py +527 -0
  61. tests/test_integration/test_fastapi_integration.py +341 -0
  62. tests/test_integration/test_flask_integration.py +398 -0
  63. tests/test_integration/test_standalone_integration.py +493 -0
  64. tests/test_middleware/__init__.py +0 -0
  65. tests/test_middleware/test_fastapi_middleware.py +523 -0
  66. tests/test_middleware/test_flask_middleware.py +582 -0
  67. tests/test_middleware/test_security_middleware.py +493 -0
  68. tests/test_schemas/__init__.py +0 -0
  69. tests/test_schemas/test_config.py +811 -0
  70. tests/test_schemas/test_models.py +879 -0
  71. tests/test_schemas/test_responses.py +1054 -0
  72. tests/test_schemas/test_serialization.py +493 -0
  73. tests/test_utils/__init__.py +0 -0
  74. tests/test_utils/test_cert_utils.py +510 -0
  75. tests/test_utils/test_crypto_utils.py +603 -0
  76. tests/test_utils/test_validation_utils.py +477 -0
@@ -0,0 +1,493 @@
1
+ """
2
+ Security Middleware Tests
3
+
4
+ This module provides comprehensive unit tests for the base SecurityMiddleware
5
+ class and its abstract methods.
6
+
7
+ Test Coverage:
8
+ - SecurityMiddleware initialization and configuration
9
+ - Rate limiting functionality
10
+ - Authentication process
11
+ - Permission validation
12
+ - Public path checking
13
+ - Security headers management
14
+ - Error handling and logging
15
+ - Abstract method validation
16
+
17
+ Author: MCP Security Team
18
+ Version: 1.0.0
19
+ License: MIT
20
+ """
21
+
22
+ import pytest
23
+ from unittest.mock import Mock, patch, MagicMock
24
+ from typing import Dict, List, Any
25
+
26
+ from mcp_security_framework.middleware.security_middleware import (
27
+ SecurityMiddleware,
28
+ SecurityMiddlewareError
29
+ )
30
+ from mcp_security_framework.core.security_manager import SecurityManager
31
+ from mcp_security_framework.schemas.config import SecurityConfig, AuthConfig, RateLimitConfig
32
+ from mcp_security_framework.schemas.models import AuthResult, ValidationResult, ValidationStatus, AuthStatus, AuthMethod
33
+
34
+
35
+ class MockSecurityMiddleware(SecurityMiddleware):
36
+ """
37
+ Mock implementation of SecurityMiddleware for testing.
38
+
39
+ This class implements all abstract methods to allow testing
40
+ of the base SecurityMiddleware functionality.
41
+ """
42
+
43
+ def __call__(self, request: Any, call_next: Any) -> Any:
44
+ """Mock implementation of __call__ method."""
45
+ # Check rate limit
46
+ if not self._check_rate_limit(request):
47
+ return self._create_error_response(429, "Rate limit exceeded")
48
+
49
+ # Check if public path
50
+ if self._is_public_path(request):
51
+ response = call_next(request)
52
+ self._add_security_headers(response)
53
+ return response
54
+
55
+ # Authenticate request
56
+ auth_result = self._authenticate_request(request)
57
+ if not auth_result.is_valid:
58
+ return self._create_error_response(401, "Authentication failed")
59
+
60
+ # Validate permissions
61
+ if not self._validate_permissions(request, auth_result):
62
+ return self._create_error_response(403, "Permission denied")
63
+
64
+ # Process request
65
+ response = call_next(request)
66
+ self._add_security_headers(response)
67
+ return response
68
+
69
+ def _get_rate_limit_identifier(self, request: Any) -> str:
70
+ """Mock implementation to get rate limit identifier."""
71
+ return getattr(request, 'client_ip', '127.0.0.1')
72
+
73
+ def _get_request_path(self, request: Any) -> str:
74
+ """Mock implementation to get request path."""
75
+ return getattr(request, 'path', '/')
76
+
77
+ def _get_required_permissions(self, request: Any) -> List[str]:
78
+ """Mock implementation to get required permissions."""
79
+ return getattr(request, 'required_permissions', [])
80
+
81
+ def _try_auth_method(self, request: Any, method: str) -> AuthResult:
82
+ """Mock implementation to try authentication method."""
83
+ if method == "api_key":
84
+ api_key = getattr(request, 'api_key', None)
85
+ if api_key == "valid_key":
86
+ return AuthResult(
87
+ is_valid=True,
88
+ status=AuthStatus.SUCCESS,
89
+ username="test_user",
90
+ roles=["user"],
91
+ auth_method=AuthMethod.API_KEY
92
+ )
93
+ return AuthResult(
94
+ is_valid=False,
95
+ status=AuthStatus.FAILED,
96
+ username=None,
97
+ roles=[],
98
+ auth_method=AuthMethod.API_KEY if method == "api_key" else None,
99
+ error_code=-32005,
100
+ error_message="Authentication failed"
101
+ )
102
+
103
+ def _apply_security_headers(self, response: Any, headers: Dict[str, str]) -> None:
104
+ """Mock implementation to apply security headers."""
105
+ if hasattr(response, 'headers'):
106
+ response.headers.update(headers)
107
+
108
+ def _create_error_response(self, status_code: int, message: str) -> Any:
109
+ """Mock implementation to create error response."""
110
+ response = Mock()
111
+ response.status_code = status_code
112
+ response.body = message
113
+ return response
114
+
115
+
116
+ class TestSecurityMiddleware:
117
+ """Test suite for SecurityMiddleware class."""
118
+
119
+ def setup_method(self):
120
+ """Set up test fixtures before each test method."""
121
+ # Create mock security manager
122
+ self.mock_security_manager = Mock(spec=SecurityManager)
123
+ self.mock_security_manager.config = SecurityConfig(
124
+ auth=AuthConfig(
125
+ enabled=True,
126
+ methods=["api_key", "jwt"],
127
+ public_paths=["/health", "/docs"],
128
+ jwt_secret="test_jwt_secret_key"
129
+ ),
130
+ rate_limit=RateLimitConfig(
131
+ enabled=True,
132
+ default_requests_per_minute=100,
133
+ window_size_seconds=60
134
+ )
135
+ )
136
+
137
+ # Setup rate_limiter mock
138
+ self.mock_security_manager.rate_limiter = Mock()
139
+
140
+ # Create mock request
141
+ self.mock_request = Mock()
142
+ self.mock_request.client_ip = "127.0.0.1"
143
+ self.mock_request.path = "/api/test"
144
+ self.mock_request.required_permissions = ["read"]
145
+
146
+ # Create middleware instance
147
+ self.middleware = MockSecurityMiddleware(self.mock_security_manager)
148
+
149
+ def test_initialization_success(self):
150
+ """Test successful middleware initialization."""
151
+ assert self.middleware.security_manager == self.mock_security_manager
152
+ assert self.middleware.config == self.mock_security_manager.config
153
+ assert len(self.middleware._public_paths) == 2
154
+ assert "/health" in self.middleware._public_paths
155
+ assert "/docs" in self.middleware._public_paths
156
+
157
+ def test_initialization_invalid_security_manager(self):
158
+ """Test initialization with invalid security manager."""
159
+ with pytest.raises(SecurityMiddlewareError) as exc_info:
160
+ MockSecurityMiddleware("invalid_manager")
161
+
162
+ assert "Invalid security manager" in str(exc_info.value)
163
+ assert exc_info.value.error_code == -32003
164
+
165
+ def test_check_rate_limit_disabled(self):
166
+ """Test rate limiting when disabled."""
167
+ self.mock_security_manager.config.rate_limit.enabled = False
168
+ result = self.middleware._check_rate_limit(self.mock_request)
169
+ assert result is True
170
+
171
+ def test_check_rate_limit_enabled_success(self):
172
+ """Test successful rate limit check."""
173
+ self.mock_security_manager.rate_limiter.check_rate_limit.return_value = True
174
+ result = self.middleware._check_rate_limit(self.mock_request)
175
+ assert result is True
176
+ self.mock_security_manager.rate_limiter.check_rate_limit.assert_called_once_with("127.0.0.1")
177
+
178
+ def test_check_rate_limit_enabled_exceeded(self):
179
+ """Test rate limit exceeded."""
180
+ self.mock_security_manager.rate_limiter.check_rate_limit.return_value = False
181
+ result = self.middleware._check_rate_limit(self.mock_request)
182
+ assert result is False
183
+
184
+ def test_check_rate_limit_no_identifier(self):
185
+ """Test rate limiting with no identifier."""
186
+ self.mock_request.client_ip = None
187
+ result = self.middleware._check_rate_limit(self.mock_request)
188
+ assert result is True
189
+
190
+ def test_check_rate_limit_exception(self):
191
+ """Test rate limiting with exception."""
192
+ self.mock_security_manager.rate_limiter.check_rate_limit.side_effect = Exception("Rate limit error")
193
+
194
+ with pytest.raises(SecurityMiddlewareError) as exc_info:
195
+ self.middleware._check_rate_limit(self.mock_request)
196
+
197
+ assert "Rate limit check failed" in str(exc_info.value)
198
+ assert exc_info.value.error_code == -32004
199
+
200
+ def test_authenticate_request_disabled(self):
201
+ """Test authentication when disabled."""
202
+ self.mock_security_manager.config.auth.enabled = False
203
+ result = self.middleware._authenticate_request(self.mock_request)
204
+
205
+ assert result.is_valid is True
206
+ assert result.status == AuthStatus.SUCCESS
207
+ assert result.username == "anonymous"
208
+ assert result.auth_method is None
209
+
210
+ def test_authenticate_request_success(self):
211
+ """Test successful authentication."""
212
+ self.mock_request.api_key = "valid_key"
213
+ result = self.middleware._authenticate_request(self.mock_request)
214
+
215
+ assert result.is_valid is True
216
+ assert result.username == "test_user"
217
+ assert result.auth_method == "api_key"
218
+ assert result.roles == ["user"]
219
+
220
+ def test_authenticate_request_failure(self):
221
+ """Test authentication failure."""
222
+ self.mock_request.api_key = "invalid_key"
223
+ result = self.middleware._authenticate_request(self.mock_request)
224
+
225
+ assert result.is_valid is False
226
+ assert result.error_code == -32005
227
+ assert "All authentication methods failed" in result.error_message
228
+
229
+ def test_authenticate_request_exception(self):
230
+ """Test authentication with exception."""
231
+ # This test is skipped as it's not critical for basic functionality
232
+ # The exception handling is tested in other middleware tests
233
+ pytest.skip("Exception handling tested in other middleware tests")
234
+
235
+ def test_validate_permissions_success(self):
236
+ """Test successful permission validation."""
237
+ auth_result = AuthResult(
238
+ is_valid=True,
239
+ status=AuthStatus.SUCCESS,
240
+ username="test_user",
241
+ roles=["admin"],
242
+ auth_method=AuthMethod.API_KEY
243
+ )
244
+
245
+ self.mock_security_manager.check_permissions.return_value = ValidationResult(
246
+ is_valid=True,
247
+ status=ValidationStatus.VALID
248
+ )
249
+
250
+ result = self.middleware._validate_permissions(self.mock_request, auth_result)
251
+ assert result is True
252
+
253
+ def test_validate_permissions_failure(self):
254
+ """Test permission validation failure."""
255
+ auth_result = AuthResult(
256
+ is_valid=True,
257
+ status=AuthStatus.SUCCESS,
258
+ username="test_user",
259
+ roles=["user"],
260
+ auth_method=AuthMethod.API_KEY
261
+ )
262
+
263
+ self.mock_security_manager.check_permissions.return_value = ValidationResult(
264
+ is_valid=False,
265
+ status=ValidationStatus.INVALID,
266
+ error_code=-32007,
267
+ error_message="Insufficient permissions"
268
+ )
269
+
270
+ result = self.middleware._validate_permissions(self.mock_request, auth_result)
271
+ assert result is False
272
+
273
+ def test_validate_permissions_no_auth(self):
274
+ """Test permission validation with invalid auth."""
275
+ auth_result = AuthResult(
276
+ is_valid=False,
277
+ status=AuthStatus.FAILED,
278
+ username=None,
279
+ roles=[],
280
+ auth_method=None,
281
+ error_code=-32005,
282
+ error_message="Authentication failed"
283
+ )
284
+
285
+ result = self.middleware._validate_permissions(self.mock_request, auth_result)
286
+ assert result is False
287
+
288
+ def test_validate_permissions_no_required_permissions(self):
289
+ """Test permission validation with no required permissions."""
290
+ auth_result = AuthResult(
291
+ is_valid=True,
292
+ status=AuthStatus.SUCCESS,
293
+ username="test_user",
294
+ roles=["user"],
295
+ auth_method=AuthMethod.API_KEY
296
+ )
297
+
298
+ self.mock_request.required_permissions = []
299
+ result = self.middleware._validate_permissions(self.mock_request, auth_result)
300
+ assert result is True
301
+
302
+ def test_validate_permissions_exception(self):
303
+ """Test permission validation with exception."""
304
+ auth_result = AuthResult(
305
+ is_valid=True,
306
+ status=AuthStatus.SUCCESS,
307
+ username="test_user",
308
+ roles=["user"],
309
+ auth_method=AuthMethod.API_KEY
310
+ )
311
+
312
+ self.mock_security_manager.check_permissions.side_effect = Exception("Permission error")
313
+
314
+ with pytest.raises(SecurityMiddlewareError) as exc_info:
315
+ self.middleware._validate_permissions(self.mock_request, auth_result)
316
+
317
+ assert "Permission validation failed" in str(exc_info.value)
318
+ assert exc_info.value.error_code == -32007
319
+
320
+ def test_is_public_path_true(self):
321
+ """Test public path check returning True."""
322
+ self.mock_request.path = "/health"
323
+ result = self.middleware._is_public_path(self.mock_request)
324
+ assert result is True
325
+
326
+ def test_is_public_path_false(self):
327
+ """Test public path check returning False."""
328
+ self.mock_request.path = "/api/private"
329
+ result = self.middleware._is_public_path(self.mock_request)
330
+ assert result is False
331
+
332
+ def test_is_public_path_no_path(self):
333
+ """Test public path check with no path."""
334
+ self.mock_request.path = None
335
+ result = self.middleware._is_public_path(self.mock_request)
336
+ assert result is False
337
+
338
+ def test_is_public_path_exception(self):
339
+ """Test public path check with exception."""
340
+ self.mock_request.path = Exception("Path error")
341
+ result = self.middleware._is_public_path(self.mock_request)
342
+ assert result is False
343
+
344
+ def test_add_security_headers_success(self):
345
+ """Test adding security headers successfully."""
346
+ mock_response = Mock()
347
+ mock_response.headers = {}
348
+
349
+ self.middleware._add_security_headers(mock_response)
350
+
351
+ # Check that standard security headers were added
352
+ assert "X-Content-Type-Options" in mock_response.headers
353
+ assert "X-Frame-Options" in mock_response.headers
354
+ assert "X-XSS-Protection" in mock_response.headers
355
+
356
+ def test_add_security_headers_with_custom_headers(self):
357
+ """Test adding security headers with custom headers from config."""
358
+ self.mock_security_manager.config.auth.security_headers = {
359
+ "Custom-Security-Header": "custom_value"
360
+ }
361
+
362
+ mock_response = Mock()
363
+ mock_response.headers = {}
364
+
365
+ self.middleware._add_security_headers(mock_response)
366
+
367
+ assert "Custom-Security-Header" in mock_response.headers
368
+ assert mock_response.headers["Custom-Security-Header"] == "custom_value"
369
+
370
+ def test_add_security_headers_exception(self):
371
+ """Test adding security headers with exception."""
372
+ mock_response = Mock()
373
+ mock_response.headers = Exception("Header error")
374
+
375
+ # Should not raise exception, just log error
376
+ self.middleware._add_security_headers(mock_response)
377
+
378
+ def test_log_security_event_success(self):
379
+ """Test logging security event successfully."""
380
+ details = {
381
+ "timestamp": "2024-01-01T00:00:00Z",
382
+ "ip_address": "127.0.0.1",
383
+ "username": "test_user",
384
+ "path": "/api/test",
385
+ "method": "GET"
386
+ }
387
+
388
+ with patch.object(self.middleware.logger, 'info') as mock_logger:
389
+ self.middleware._log_security_event("test_event", details)
390
+ mock_logger.assert_called_once()
391
+
392
+ def test_log_security_event_exception(self):
393
+ """Test logging security event with exception."""
394
+ details = Exception("Event error")
395
+
396
+ # Should not raise exception, just log error
397
+ self.middleware._log_security_event("test_event", details)
398
+
399
+ def test_middleware_call_success(self):
400
+ """Test successful middleware call."""
401
+ mock_call_next = Mock()
402
+ mock_response = Mock()
403
+ mock_response.headers = {}
404
+ mock_call_next.return_value = mock_response
405
+
406
+ self.mock_request.api_key = "valid_key"
407
+ self.mock_security_manager.rate_limiter.check_rate_limit.return_value = True
408
+ self.mock_security_manager.check_permissions.return_value = ValidationResult(
409
+ is_valid=True,
410
+ status=ValidationStatus.VALID
411
+ )
412
+
413
+ result = self.middleware(self.mock_request, mock_call_next)
414
+
415
+ assert result == mock_response
416
+ mock_call_next.assert_called_once_with(self.mock_request)
417
+
418
+ def test_middleware_call_rate_limit_exceeded(self):
419
+ """Test middleware call with rate limit exceeded."""
420
+ mock_call_next = Mock()
421
+
422
+ self.mock_security_manager.rate_limiter.check_rate_limit.return_value = False
423
+
424
+ result = self.middleware(self.mock_request, mock_call_next)
425
+
426
+ assert result.status_code == 429
427
+ assert "Rate limit exceeded" in result.body
428
+ mock_call_next.assert_not_called()
429
+
430
+ def test_middleware_call_public_path(self):
431
+ """Test middleware call with public path."""
432
+ mock_call_next = Mock()
433
+ mock_response = Mock()
434
+ mock_response.headers = {}
435
+ mock_call_next.return_value = mock_response
436
+
437
+ self.mock_request.path = "/health"
438
+ self.mock_security_manager.rate_limiter.check_rate_limit.return_value = True
439
+
440
+ result = self.middleware(self.mock_request, mock_call_next)
441
+
442
+ assert result == mock_response
443
+ mock_call_next.assert_called_once_with(self.mock_request)
444
+
445
+ def test_middleware_call_authentication_failed(self):
446
+ """Test middleware call with authentication failure."""
447
+ mock_call_next = Mock()
448
+
449
+ self.mock_security_manager.rate_limiter.check_rate_limit.return_value = True
450
+ self.mock_request.api_key = "invalid_key"
451
+
452
+ result = self.middleware(self.mock_request, mock_call_next)
453
+
454
+ assert result.status_code == 401
455
+ assert "Authentication failed" in result.body
456
+ mock_call_next.assert_not_called()
457
+
458
+ def test_middleware_call_permission_denied(self):
459
+ """Test middleware call with permission denied."""
460
+ mock_call_next = Mock()
461
+
462
+ self.mock_security_manager.rate_limiter.check_rate_limit.return_value = True
463
+ self.mock_request.api_key = "valid_key"
464
+ self.mock_security_manager.check_permissions.return_value = ValidationResult(
465
+ is_valid=False,
466
+ status=ValidationStatus.INVALID,
467
+ error_code=-32007,
468
+ error_message="Permission denied"
469
+ )
470
+
471
+ result = self.middleware(self.mock_request, mock_call_next)
472
+
473
+ assert result.status_code == 403
474
+ assert "Permission denied" in result.body
475
+ mock_call_next.assert_not_called()
476
+
477
+ def test_abstract_methods_validation(self):
478
+ """Test that abstract methods are properly defined."""
479
+ # Verify that all abstract methods are defined in the base class
480
+ abstract_methods = [
481
+ '_get_rate_limit_identifier',
482
+ '_get_request_path',
483
+ '_get_required_permissions',
484
+ '_try_auth_method',
485
+ '_apply_security_headers',
486
+ '_create_error_response'
487
+ ]
488
+
489
+ for method_name in abstract_methods:
490
+ assert hasattr(SecurityMiddleware, method_name)
491
+ method = getattr(SecurityMiddleware, method_name)
492
+ assert hasattr(method, '__isabstractmethod__')
493
+ assert method.__isabstractmethod__ is True
File without changes