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.
- mcp_security_framework/__init__.py +96 -0
- mcp_security_framework/cli/__init__.py +18 -0
- mcp_security_framework/cli/cert_cli.py +511 -0
- mcp_security_framework/cli/security_cli.py +791 -0
- mcp_security_framework/constants.py +209 -0
- mcp_security_framework/core/__init__.py +61 -0
- mcp_security_framework/core/auth_manager.py +1011 -0
- mcp_security_framework/core/cert_manager.py +1663 -0
- mcp_security_framework/core/permission_manager.py +735 -0
- mcp_security_framework/core/rate_limiter.py +602 -0
- mcp_security_framework/core/security_manager.py +943 -0
- mcp_security_framework/core/ssl_manager.py +735 -0
- mcp_security_framework/examples/__init__.py +75 -0
- mcp_security_framework/examples/django_example.py +615 -0
- mcp_security_framework/examples/fastapi_example.py +472 -0
- mcp_security_framework/examples/flask_example.py +506 -0
- mcp_security_framework/examples/gateway_example.py +803 -0
- mcp_security_framework/examples/microservice_example.py +690 -0
- mcp_security_framework/examples/standalone_example.py +576 -0
- mcp_security_framework/middleware/__init__.py +250 -0
- mcp_security_framework/middleware/auth_middleware.py +292 -0
- mcp_security_framework/middleware/fastapi_auth_middleware.py +447 -0
- mcp_security_framework/middleware/fastapi_middleware.py +757 -0
- mcp_security_framework/middleware/flask_auth_middleware.py +465 -0
- mcp_security_framework/middleware/flask_middleware.py +591 -0
- mcp_security_framework/middleware/mtls_middleware.py +439 -0
- mcp_security_framework/middleware/rate_limit_middleware.py +403 -0
- mcp_security_framework/middleware/security_middleware.py +507 -0
- mcp_security_framework/schemas/__init__.py +109 -0
- mcp_security_framework/schemas/config.py +694 -0
- mcp_security_framework/schemas/models.py +709 -0
- mcp_security_framework/schemas/responses.py +686 -0
- mcp_security_framework/tests/__init__.py +0 -0
- mcp_security_framework/utils/__init__.py +121 -0
- mcp_security_framework/utils/cert_utils.py +525 -0
- mcp_security_framework/utils/crypto_utils.py +475 -0
- mcp_security_framework/utils/validation_utils.py +571 -0
- mcp_security_framework-0.1.0.dist-info/METADATA +411 -0
- mcp_security_framework-0.1.0.dist-info/RECORD +76 -0
- mcp_security_framework-0.1.0.dist-info/WHEEL +5 -0
- mcp_security_framework-0.1.0.dist-info/entry_points.txt +3 -0
- mcp_security_framework-0.1.0.dist-info/top_level.txt +2 -0
- tests/__init__.py +0 -0
- tests/test_cli/__init__.py +0 -0
- tests/test_cli/test_cert_cli.py +379 -0
- tests/test_cli/test_security_cli.py +657 -0
- tests/test_core/__init__.py +0 -0
- tests/test_core/test_auth_manager.py +582 -0
- tests/test_core/test_cert_manager.py +795 -0
- tests/test_core/test_permission_manager.py +395 -0
- tests/test_core/test_rate_limiter.py +626 -0
- tests/test_core/test_security_manager.py +841 -0
- tests/test_core/test_ssl_manager.py +532 -0
- tests/test_examples/__init__.py +8 -0
- tests/test_examples/test_fastapi_example.py +264 -0
- tests/test_examples/test_flask_example.py +238 -0
- tests/test_examples/test_standalone_example.py +292 -0
- tests/test_integration/__init__.py +0 -0
- tests/test_integration/test_auth_flow.py +502 -0
- tests/test_integration/test_certificate_flow.py +527 -0
- tests/test_integration/test_fastapi_integration.py +341 -0
- tests/test_integration/test_flask_integration.py +398 -0
- tests/test_integration/test_standalone_integration.py +493 -0
- tests/test_middleware/__init__.py +0 -0
- tests/test_middleware/test_fastapi_middleware.py +523 -0
- tests/test_middleware/test_flask_middleware.py +582 -0
- tests/test_middleware/test_security_middleware.py +493 -0
- tests/test_schemas/__init__.py +0 -0
- tests/test_schemas/test_config.py +811 -0
- tests/test_schemas/test_models.py +879 -0
- tests/test_schemas/test_responses.py +1054 -0
- tests/test_schemas/test_serialization.py +493 -0
- tests/test_utils/__init__.py +0 -0
- tests/test_utils/test_cert_utils.py +510 -0
- tests/test_utils/test_crypto_utils.py +603 -0
- tests/test_utils/test_validation_utils.py +477 -0
@@ -0,0 +1,75 @@
|
|
1
|
+
"""
|
2
|
+
Examples Module
|
3
|
+
|
4
|
+
This module contains comprehensive examples of how to implement
|
5
|
+
the MCP Security Framework with real server configurations.
|
6
|
+
|
7
|
+
The examples demonstrate:
|
8
|
+
- Complete implementation of abstract methods
|
9
|
+
- Real-world server configurations
|
10
|
+
- Production-ready security setups
|
11
|
+
- Integration with popular web frameworks
|
12
|
+
|
13
|
+
Author: MCP Security Team
|
14
|
+
Version: 1.0.0
|
15
|
+
License: MIT
|
16
|
+
"""
|
17
|
+
|
18
|
+
# Import examples conditionally to avoid import errors when dependencies are missing
|
19
|
+
try:
|
20
|
+
from .fastapi_example import FastAPIExample
|
21
|
+
_FASTAPI_AVAILABLE = True
|
22
|
+
except ImportError:
|
23
|
+
_FASTAPI_AVAILABLE = False
|
24
|
+
FastAPIExample = None
|
25
|
+
|
26
|
+
try:
|
27
|
+
from .flask_example import FlaskExample
|
28
|
+
_FLASK_AVAILABLE = True
|
29
|
+
except ImportError:
|
30
|
+
_FLASK_AVAILABLE = False
|
31
|
+
FlaskExample = None
|
32
|
+
|
33
|
+
try:
|
34
|
+
from .django_example import DjangoExample
|
35
|
+
_DJANGO_AVAILABLE = True
|
36
|
+
except ImportError:
|
37
|
+
_DJANGO_AVAILABLE = False
|
38
|
+
DjangoExample = None
|
39
|
+
|
40
|
+
try:
|
41
|
+
from .standalone_example import StandaloneExample
|
42
|
+
_STANDALONE_AVAILABLE = True
|
43
|
+
except ImportError:
|
44
|
+
_STANDALONE_AVAILABLE = False
|
45
|
+
StandaloneExample = None
|
46
|
+
|
47
|
+
try:
|
48
|
+
from .microservice_example import MicroserviceExample
|
49
|
+
_MICROSERVICE_AVAILABLE = True
|
50
|
+
except ImportError:
|
51
|
+
_MICROSERVICE_AVAILABLE = False
|
52
|
+
MicroserviceExample = None
|
53
|
+
|
54
|
+
try:
|
55
|
+
from .gateway_example import APIGatewayExample
|
56
|
+
_GATEWAY_AVAILABLE = True
|
57
|
+
except ImportError:
|
58
|
+
_GATEWAY_AVAILABLE = False
|
59
|
+
APIGatewayExample = None
|
60
|
+
|
61
|
+
# Build __all__ list with only available examples
|
62
|
+
__all__ = []
|
63
|
+
|
64
|
+
if _FASTAPI_AVAILABLE:
|
65
|
+
__all__.append("FastAPIExample")
|
66
|
+
if _FLASK_AVAILABLE:
|
67
|
+
__all__.append("FlaskExample")
|
68
|
+
if _DJANGO_AVAILABLE:
|
69
|
+
__all__.append("DjangoExample")
|
70
|
+
if _STANDALONE_AVAILABLE:
|
71
|
+
__all__.append("StandaloneExample")
|
72
|
+
if _MICROSERVICE_AVAILABLE:
|
73
|
+
__all__.append("MicroserviceExample")
|
74
|
+
if _GATEWAY_AVAILABLE:
|
75
|
+
__all__.append("APIGatewayExample")
|
@@ -0,0 +1,615 @@
|
|
1
|
+
"""
|
2
|
+
Django Example Implementation
|
3
|
+
|
4
|
+
This module provides a complete example of how to implement the MCP Security Framework
|
5
|
+
with Django, including all abstract method implementations for real server usage.
|
6
|
+
|
7
|
+
The example demonstrates:
|
8
|
+
- Complete Django application with security middleware
|
9
|
+
- Real-world authentication and authorization
|
10
|
+
- Rate limiting implementation
|
11
|
+
- Certificate-based authentication
|
12
|
+
- Production-ready security headers
|
13
|
+
- Comprehensive error handling
|
14
|
+
|
15
|
+
Author: MCP Security Team
|
16
|
+
Version: 1.0.0
|
17
|
+
License: MIT
|
18
|
+
"""
|
19
|
+
|
20
|
+
import os
|
21
|
+
import json
|
22
|
+
import logging
|
23
|
+
from typing import Dict, List, Any, Optional
|
24
|
+
from datetime import datetime, timedelta
|
25
|
+
|
26
|
+
from django.http import HttpRequest, HttpResponse, JsonResponse
|
27
|
+
from django.views.decorators.csrf import csrf_exempt
|
28
|
+
from django.views.decorators.http import require_http_methods
|
29
|
+
from django.middleware.base import BaseMiddleware
|
30
|
+
from django.conf import settings
|
31
|
+
from django.urls import path, include
|
32
|
+
from django.contrib.auth.models import User
|
33
|
+
from django.contrib.auth.decorators import login_required, permission_required
|
34
|
+
from django.core.exceptions import PermissionDenied
|
35
|
+
from django.utils.decorators import method_decorator
|
36
|
+
from django.views import View
|
37
|
+
|
38
|
+
from mcp_security_framework.core.security_manager import SecurityManager
|
39
|
+
from mcp_security_framework.core.auth_manager import AuthManager
|
40
|
+
from mcp_security_framework.core.ssl_manager import SSLManager
|
41
|
+
from mcp_security_framework.core.permission_manager import PermissionManager
|
42
|
+
from mcp_security_framework.core.rate_limiter import RateLimiter
|
43
|
+
from mcp_security_framework.schemas.config import SecurityConfig, AuthConfig, SSLConfig
|
44
|
+
from mcp_security_framework.schemas.models import AuthResult, AuthStatus, AuthMethod
|
45
|
+
from mcp_security_framework.constants import (
|
46
|
+
DEFAULT_CLIENT_IP, DEFAULT_SECURITY_HEADERS, AUTH_METHODS,
|
47
|
+
ErrorCodes, HTTP_UNAUTHORIZED, HTTP_FORBIDDEN, HTTP_TOO_MANY_REQUESTS
|
48
|
+
)
|
49
|
+
|
50
|
+
|
51
|
+
class DjangoSecurityMiddleware(BaseMiddleware):
|
52
|
+
"""
|
53
|
+
Django Security Middleware Implementation
|
54
|
+
|
55
|
+
This middleware provides comprehensive security features for Django applications
|
56
|
+
including authentication, authorization, rate limiting, and security headers.
|
57
|
+
"""
|
58
|
+
|
59
|
+
def __init__(self, get_response):
|
60
|
+
"""Initialize middleware with security configuration."""
|
61
|
+
super().__init__(get_response)
|
62
|
+
self.config = self._load_config()
|
63
|
+
self.security_manager = SecurityManager(self.config)
|
64
|
+
self.logger = logging.getLogger(__name__)
|
65
|
+
|
66
|
+
def _load_config(self) -> SecurityConfig:
|
67
|
+
"""Load security configuration."""
|
68
|
+
config_path = getattr(settings, 'SECURITY_CONFIG_PATH', None)
|
69
|
+
|
70
|
+
if config_path and os.path.exists(config_path):
|
71
|
+
with open(config_path, 'r') as f:
|
72
|
+
config_data = json.load(f)
|
73
|
+
return SecurityConfig(**config_data)
|
74
|
+
|
75
|
+
# Create production-ready default configuration
|
76
|
+
return SecurityConfig(
|
77
|
+
auth=AuthConfig(
|
78
|
+
enabled=True,
|
79
|
+
methods=[AUTH_METHODS["API_KEY"], AUTH_METHODS["JWT"], AUTH_METHODS["CERTIFICATE"]],
|
80
|
+
api_keys={
|
81
|
+
"admin_key_123": {"username": "admin", "roles": ["admin", "user"]},
|
82
|
+
"user_key_456": {"username": "user", "roles": ["user"]},
|
83
|
+
"readonly_key_789": {"username": "readonly", "roles": ["readonly"]}
|
84
|
+
},
|
85
|
+
jwt_secret="your-super-secret-jwt-key-change-in-production",
|
86
|
+
jwt_algorithm="HS256",
|
87
|
+
jwt_expiry_hours=24,
|
88
|
+
public_paths=["/health/", "/metrics/", "/admin/"],
|
89
|
+
security_headers=DEFAULT_SECURITY_HEADERS
|
90
|
+
),
|
91
|
+
ssl=SSLConfig(
|
92
|
+
enabled=True,
|
93
|
+
cert_file="certs/server.crt",
|
94
|
+
key_file="certs/server.key",
|
95
|
+
ca_cert_file="certs/ca.crt",
|
96
|
+
verify_mode="CERT_REQUIRED",
|
97
|
+
min_version="TLSv1.2"
|
98
|
+
),
|
99
|
+
rate_limit={
|
100
|
+
"enabled": True,
|
101
|
+
"default_requests_per_minute": 60,
|
102
|
+
"default_requests_per_hour": 1000,
|
103
|
+
"burst_limit": 2,
|
104
|
+
"window_size_seconds": 60,
|
105
|
+
"storage_backend": "redis",
|
106
|
+
"redis_config": {
|
107
|
+
"host": "localhost",
|
108
|
+
"port": 6379,
|
109
|
+
"db": 0,
|
110
|
+
"password": None
|
111
|
+
},
|
112
|
+
"exempt_paths": ["/health/", "/metrics/", "/admin/"],
|
113
|
+
"exempt_roles": ["admin"]
|
114
|
+
},
|
115
|
+
permissions={
|
116
|
+
"enabled": True,
|
117
|
+
"roles_file": "config/roles.json",
|
118
|
+
"default_role": "user",
|
119
|
+
"hierarchy_enabled": True
|
120
|
+
},
|
121
|
+
logging={
|
122
|
+
"enabled": True,
|
123
|
+
"level": "INFO",
|
124
|
+
"format": "%(asctime)s - %(name)s - %(levelname)s - %(message)s",
|
125
|
+
"file_path": "logs/security.log",
|
126
|
+
"max_file_size": 10,
|
127
|
+
"backup_count": 5,
|
128
|
+
"console_output": True,
|
129
|
+
"json_format": False
|
130
|
+
}
|
131
|
+
)
|
132
|
+
|
133
|
+
def __call__(self, request):
|
134
|
+
"""Process request through security middleware."""
|
135
|
+
# Check if path is public
|
136
|
+
if self._is_public_path(request.path):
|
137
|
+
return self.get_response(request)
|
138
|
+
|
139
|
+
# Rate limiting check
|
140
|
+
if not self._check_rate_limit(request):
|
141
|
+
return self._rate_limit_response()
|
142
|
+
|
143
|
+
# Authentication check
|
144
|
+
auth_result = self._authenticate_request(request)
|
145
|
+
if not auth_result.is_valid:
|
146
|
+
return self._auth_error_response(auth_result)
|
147
|
+
|
148
|
+
# Authorization check
|
149
|
+
if not self._check_permissions(request, auth_result):
|
150
|
+
return self._permission_error_response()
|
151
|
+
|
152
|
+
# Add user info to request
|
153
|
+
request.user_info = {
|
154
|
+
"username": auth_result.username,
|
155
|
+
"roles": auth_result.roles,
|
156
|
+
"permissions": auth_result.permissions,
|
157
|
+
"auth_method": auth_result.auth_method
|
158
|
+
}
|
159
|
+
|
160
|
+
# Process request
|
161
|
+
response = self.get_response(request)
|
162
|
+
|
163
|
+
# Add security headers
|
164
|
+
self._add_security_headers(response)
|
165
|
+
|
166
|
+
return response
|
167
|
+
|
168
|
+
def _is_public_path(self, path: str) -> bool:
|
169
|
+
"""Check if path is public (bypasses authentication)."""
|
170
|
+
return any(path.startswith(public_path) for public_path in self.config.auth.public_paths)
|
171
|
+
|
172
|
+
def _check_rate_limit(self, request: HttpRequest) -> bool:
|
173
|
+
"""Check if request is within rate limits."""
|
174
|
+
if not self.config.rate_limit.enabled:
|
175
|
+
return True
|
176
|
+
|
177
|
+
identifier = self._get_rate_limit_identifier(request)
|
178
|
+
return self.security_manager.rate_limiter.check_rate_limit(identifier)
|
179
|
+
|
180
|
+
def _get_rate_limit_identifier(self, request: HttpRequest) -> str:
|
181
|
+
"""Get rate limit identifier from request."""
|
182
|
+
# Try to get IP from headers
|
183
|
+
forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR')
|
184
|
+
if forwarded_for:
|
185
|
+
return forwarded_for.split(',')[0].strip()
|
186
|
+
|
187
|
+
real_ip = request.META.get('HTTP_X_REAL_IP')
|
188
|
+
if real_ip:
|
189
|
+
return real_ip
|
190
|
+
|
191
|
+
# Fall back to remote address
|
192
|
+
return request.META.get('REMOTE_ADDR', DEFAULT_CLIENT_IP)
|
193
|
+
|
194
|
+
def _authenticate_request(self, request: HttpRequest) -> AuthResult:
|
195
|
+
"""Authenticate the request using configured methods."""
|
196
|
+
if not self.config.auth.enabled:
|
197
|
+
return AuthResult(
|
198
|
+
is_valid=True,
|
199
|
+
status=AuthStatus.SUCCESS,
|
200
|
+
username="anonymous",
|
201
|
+
roles=[],
|
202
|
+
auth_method=None
|
203
|
+
)
|
204
|
+
|
205
|
+
# Try each authentication method in order
|
206
|
+
for method in self.config.auth.methods:
|
207
|
+
auth_result = self._try_auth_method(request, method)
|
208
|
+
if auth_result.is_valid:
|
209
|
+
return auth_result
|
210
|
+
|
211
|
+
# All authentication methods failed
|
212
|
+
return AuthResult(
|
213
|
+
is_valid=False,
|
214
|
+
status=AuthStatus.FAILED,
|
215
|
+
username=None,
|
216
|
+
roles=[],
|
217
|
+
auth_method=None,
|
218
|
+
error_code=ErrorCodes.AUTHENTICATION_ERROR,
|
219
|
+
error_message="All authentication methods failed"
|
220
|
+
)
|
221
|
+
|
222
|
+
def _try_auth_method(self, request: HttpRequest, method: str) -> AuthResult:
|
223
|
+
"""Try authentication using specific method."""
|
224
|
+
try:
|
225
|
+
if method == AUTH_METHODS["API_KEY"]:
|
226
|
+
return self._try_api_key_auth(request)
|
227
|
+
elif method == AUTH_METHODS["JWT"]:
|
228
|
+
return self._try_jwt_auth(request)
|
229
|
+
elif method == AUTH_METHODS["CERTIFICATE"]:
|
230
|
+
return self._try_certificate_auth(request)
|
231
|
+
else:
|
232
|
+
return AuthResult(
|
233
|
+
is_valid=False,
|
234
|
+
status=AuthStatus.FAILED,
|
235
|
+
username=None,
|
236
|
+
roles=[],
|
237
|
+
auth_method=None,
|
238
|
+
error_code=ErrorCodes.AUTH_METHOD_NOT_SUPPORTED,
|
239
|
+
error_message=f"Unsupported authentication method: {method}"
|
240
|
+
)
|
241
|
+
except Exception as e:
|
242
|
+
self.logger.error(f"Authentication method {method} failed: {str(e)}")
|
243
|
+
return AuthResult(
|
244
|
+
is_valid=False,
|
245
|
+
status=AuthStatus.FAILED,
|
246
|
+
username=None,
|
247
|
+
roles=[],
|
248
|
+
auth_method=None,
|
249
|
+
error_code=ErrorCodes.AUTHENTICATION_ERROR,
|
250
|
+
error_message=str(e)
|
251
|
+
)
|
252
|
+
|
253
|
+
def _try_api_key_auth(self, request: HttpRequest) -> AuthResult:
|
254
|
+
"""Try API key authentication."""
|
255
|
+
# Try to get API key from headers
|
256
|
+
api_key = request.META.get('HTTP_X_API_KEY')
|
257
|
+
if not api_key:
|
258
|
+
# Try Authorization header
|
259
|
+
auth_header = request.META.get('HTTP_AUTHORIZATION', '')
|
260
|
+
if auth_header.startswith('Bearer '):
|
261
|
+
api_key = auth_header[7:] # Remove "Bearer " prefix
|
262
|
+
|
263
|
+
if not api_key:
|
264
|
+
return AuthResult(
|
265
|
+
is_valid=False,
|
266
|
+
status=AuthStatus.FAILED,
|
267
|
+
username=None,
|
268
|
+
roles=[],
|
269
|
+
auth_method=AuthMethod.API_KEY,
|
270
|
+
error_code=ErrorCodes.API_KEY_NOT_FOUND,
|
271
|
+
error_message="API key not found in request"
|
272
|
+
)
|
273
|
+
|
274
|
+
return self.security_manager.auth_manager.authenticate_api_key(api_key)
|
275
|
+
|
276
|
+
def _try_jwt_auth(self, request: HttpRequest) -> AuthResult:
|
277
|
+
"""Try JWT authentication."""
|
278
|
+
# Try to get JWT token from Authorization header
|
279
|
+
auth_header = request.META.get('HTTP_AUTHORIZATION', '')
|
280
|
+
if not auth_header.startswith('Bearer '):
|
281
|
+
return AuthResult(
|
282
|
+
is_valid=False,
|
283
|
+
status=AuthStatus.FAILED,
|
284
|
+
username=None,
|
285
|
+
roles=[],
|
286
|
+
auth_method=AuthMethod.JWT,
|
287
|
+
error_code=ErrorCodes.JWT_VALIDATION_ERROR,
|
288
|
+
error_message="JWT token not found in Authorization header"
|
289
|
+
)
|
290
|
+
|
291
|
+
token = auth_header[7:] # Remove "Bearer " prefix
|
292
|
+
return self.security_manager.auth_manager.authenticate_jwt_token(token)
|
293
|
+
|
294
|
+
def _try_certificate_auth(self, request: HttpRequest) -> AuthResult:
|
295
|
+
"""Try certificate authentication."""
|
296
|
+
# In Django, certificate authentication would typically be handled
|
297
|
+
# at the web server level (nginx, Apache) and passed via headers
|
298
|
+
client_cert = request.META.get('SSL_CLIENT_CERT')
|
299
|
+
if not client_cert:
|
300
|
+
return AuthResult(
|
301
|
+
is_valid=False,
|
302
|
+
status=AuthStatus.FAILED,
|
303
|
+
username=None,
|
304
|
+
roles=[],
|
305
|
+
auth_method=AuthMethod.CERTIFICATE,
|
306
|
+
error_code=ErrorCodes.CERTIFICATE_AUTH_ERROR,
|
307
|
+
error_message="Client certificate not found"
|
308
|
+
)
|
309
|
+
|
310
|
+
return self.security_manager.auth_manager.authenticate_certificate(client_cert)
|
311
|
+
|
312
|
+
def _check_permissions(self, request: HttpRequest, auth_result: AuthResult) -> bool:
|
313
|
+
"""Check if user has required permissions for the request."""
|
314
|
+
if not self.config.permissions.enabled:
|
315
|
+
return True
|
316
|
+
|
317
|
+
# Get required permissions based on request
|
318
|
+
required_permissions = self._get_required_permissions(request)
|
319
|
+
if not required_permissions:
|
320
|
+
return True # No specific permissions required
|
321
|
+
|
322
|
+
return self.security_manager.permission_manager.validate_access(
|
323
|
+
auth_result.roles, required_permissions
|
324
|
+
)
|
325
|
+
|
326
|
+
def _get_required_permissions(self, request: HttpRequest) -> List[str]:
|
327
|
+
"""Get required permissions for the request."""
|
328
|
+
# This would be implemented based on your permission system
|
329
|
+
# For now, return basic permissions based on HTTP method
|
330
|
+
method_permissions = {
|
331
|
+
'GET': ['read'],
|
332
|
+
'POST': ['write'],
|
333
|
+
'PUT': ['write'],
|
334
|
+
'PATCH': ['write'],
|
335
|
+
'DELETE': ['delete']
|
336
|
+
}
|
337
|
+
|
338
|
+
return method_permissions.get(request.method, [])
|
339
|
+
|
340
|
+
def _add_security_headers(self, response: HttpResponse):
|
341
|
+
"""Add security headers to response."""
|
342
|
+
for header_name, header_value in self.config.auth.security_headers.items():
|
343
|
+
response[header_name] = header_value
|
344
|
+
|
345
|
+
def _rate_limit_response(self) -> HttpResponse:
|
346
|
+
"""Create rate limit exceeded response."""
|
347
|
+
return JsonResponse({
|
348
|
+
"error": "Rate limit exceeded",
|
349
|
+
"message": "Too many requests, please try again later",
|
350
|
+
"error_code": ErrorCodes.RATE_LIMIT_EXCEEDED_ERROR
|
351
|
+
}, status=HTTP_TOO_MANY_REQUESTS)
|
352
|
+
|
353
|
+
def _auth_error_response(self, auth_result: AuthResult) -> HttpResponse:
|
354
|
+
"""Create authentication error response."""
|
355
|
+
return JsonResponse({
|
356
|
+
"error": "Authentication failed",
|
357
|
+
"message": auth_result.error_message or "Invalid credentials",
|
358
|
+
"error_code": auth_result.error_code,
|
359
|
+
"auth_method": auth_result.auth_method
|
360
|
+
}, status=HTTP_UNAUTHORIZED)
|
361
|
+
|
362
|
+
def _permission_error_response(self) -> HttpResponse:
|
363
|
+
"""Create permission denied response."""
|
364
|
+
return JsonResponse({
|
365
|
+
"error": "Permission denied",
|
366
|
+
"message": "Insufficient permissions to access this resource",
|
367
|
+
"error_code": ErrorCodes.PERMISSION_DENIED_ERROR
|
368
|
+
}, status=HTTP_FORBIDDEN)
|
369
|
+
|
370
|
+
|
371
|
+
# Django Views
|
372
|
+
class HealthCheckView(View):
|
373
|
+
"""Health check endpoint."""
|
374
|
+
|
375
|
+
def get(self, request):
|
376
|
+
"""Handle GET request."""
|
377
|
+
return JsonResponse({
|
378
|
+
"status": "healthy",
|
379
|
+
"timestamp": datetime.utcnow().isoformat(),
|
380
|
+
"version": "1.0.0"
|
381
|
+
})
|
382
|
+
|
383
|
+
|
384
|
+
class MetricsView(View):
|
385
|
+
"""Metrics endpoint."""
|
386
|
+
|
387
|
+
def get(self, request):
|
388
|
+
"""Handle GET request."""
|
389
|
+
return JsonResponse({
|
390
|
+
"requests_total": 1000,
|
391
|
+
"requests_per_minute": 60,
|
392
|
+
"active_connections": 25,
|
393
|
+
"uptime_seconds": 3600
|
394
|
+
})
|
395
|
+
|
396
|
+
|
397
|
+
class UserProfileView(View):
|
398
|
+
"""User profile endpoint."""
|
399
|
+
|
400
|
+
def get(self, request):
|
401
|
+
"""Handle GET request."""
|
402
|
+
user_info = getattr(request, 'user_info', None)
|
403
|
+
if not user_info:
|
404
|
+
return JsonResponse({"error": "User not authenticated"}, status=401)
|
405
|
+
|
406
|
+
return JsonResponse({
|
407
|
+
"username": user_info.get("username"),
|
408
|
+
"roles": user_info.get("roles", []),
|
409
|
+
"permissions": user_info.get("permissions", []),
|
410
|
+
"last_login": datetime.utcnow().isoformat()
|
411
|
+
})
|
412
|
+
|
413
|
+
|
414
|
+
class AdminUsersView(View):
|
415
|
+
"""Admin users endpoint."""
|
416
|
+
|
417
|
+
def get(self, request):
|
418
|
+
"""Handle GET request."""
|
419
|
+
user_info = getattr(request, 'user_info', None)
|
420
|
+
if not user_info or "admin" not in user_info.get("roles", []):
|
421
|
+
return JsonResponse({"error": "Admin access required"}, status=403)
|
422
|
+
|
423
|
+
return JsonResponse({
|
424
|
+
"users": [
|
425
|
+
{"username": "admin", "roles": ["admin"], "status": "active"},
|
426
|
+
{"username": "user", "roles": ["user"], "status": "active"},
|
427
|
+
{"username": "readonly", "roles": ["readonly"], "status": "active"}
|
428
|
+
]
|
429
|
+
})
|
430
|
+
|
431
|
+
|
432
|
+
class DataView(View):
|
433
|
+
"""Data endpoint."""
|
434
|
+
|
435
|
+
def get(self, request, data_id):
|
436
|
+
"""Handle GET request."""
|
437
|
+
user_info = getattr(request, 'user_info', None)
|
438
|
+
if not user_info:
|
439
|
+
return JsonResponse({"error": "Authentication required"}, status=401)
|
440
|
+
|
441
|
+
return JsonResponse({
|
442
|
+
"id": data_id,
|
443
|
+
"data": {"example": "data"},
|
444
|
+
"created_by": "user",
|
445
|
+
"created_at": "2024-01-01T00:00:00Z"
|
446
|
+
})
|
447
|
+
|
448
|
+
def post(self, request):
|
449
|
+
"""Handle POST request."""
|
450
|
+
user_info = getattr(request, 'user_info', None)
|
451
|
+
if not user_info:
|
452
|
+
return JsonResponse({"error": "Authentication required"}, status=401)
|
453
|
+
|
454
|
+
if "readonly" in user_info.get("roles", []):
|
455
|
+
return JsonResponse({"error": "Write permission required"}, status=403)
|
456
|
+
|
457
|
+
# Process request data
|
458
|
+
data = json.loads(request.body) if request.body else {}
|
459
|
+
|
460
|
+
return JsonResponse({
|
461
|
+
"id": "data_123",
|
462
|
+
"created_by": user_info.get("username"),
|
463
|
+
"data": data,
|
464
|
+
"created_at": datetime.utcnow().isoformat()
|
465
|
+
})
|
466
|
+
|
467
|
+
|
468
|
+
# URL patterns
|
469
|
+
urlpatterns = [
|
470
|
+
path('health/', HealthCheckView.as_view(), name='health'),
|
471
|
+
path('metrics/', MetricsView.as_view(), name='metrics'),
|
472
|
+
path('api/v1/users/me/', UserProfileView.as_view(), name='user_profile'),
|
473
|
+
path('api/v1/admin/users/', AdminUsersView.as_view(), name='admin_users'),
|
474
|
+
path('api/v1/data/', DataView.as_view(), name='data'),
|
475
|
+
path('api/v1/data/<str:data_id>/', DataView.as_view(), name='data_detail'),
|
476
|
+
]
|
477
|
+
|
478
|
+
|
479
|
+
# Django Example Application
|
480
|
+
class DjangoExample:
|
481
|
+
"""
|
482
|
+
Complete Django Example with Security Framework Implementation
|
483
|
+
|
484
|
+
This class demonstrates a production-ready Django application
|
485
|
+
with comprehensive security features.
|
486
|
+
"""
|
487
|
+
|
488
|
+
def __init__(self, config_path: Optional[str] = None):
|
489
|
+
"""
|
490
|
+
Initialize Django example with security configuration.
|
491
|
+
|
492
|
+
Args:
|
493
|
+
config_path: Path to security configuration file
|
494
|
+
"""
|
495
|
+
self.config_path = config_path
|
496
|
+
self.logger = logging.getLogger(__name__)
|
497
|
+
|
498
|
+
def setup_django_settings(self):
|
499
|
+
"""Setup Django settings with security configuration."""
|
500
|
+
# This would be called in your Django settings.py
|
501
|
+
settings.SECURITY_CONFIG_PATH = self.config_path
|
502
|
+
|
503
|
+
# Add security middleware
|
504
|
+
if 'mcp_security_framework.examples.django_example.DjangoSecurityMiddleware' not in settings.MIDDLEWARE:
|
505
|
+
settings.MIDDLEWARE.insert(0, 'mcp_security_framework.examples.django_example.DjangoSecurityMiddleware')
|
506
|
+
|
507
|
+
# Security settings
|
508
|
+
settings.SECURE_SSL_REDIRECT = True
|
509
|
+
settings.SECURE_HSTS_SECONDS = 31536000
|
510
|
+
settings.SECURE_HSTS_INCLUDE_SUBDOMAINS = True
|
511
|
+
settings.SECURE_HSTS_PRELOAD = True
|
512
|
+
settings.SECURE_CONTENT_TYPE_NOSNIFF = True
|
513
|
+
settings.SECURE_BROWSER_XSS_FILTER = True
|
514
|
+
settings.X_FRAME_OPTIONS = 'DENY'
|
515
|
+
settings.SESSION_COOKIE_SECURE = True
|
516
|
+
settings.CSRF_COOKIE_SECURE = True
|
517
|
+
|
518
|
+
def create_superuser(self, username: str, email: str, password: str):
|
519
|
+
"""Create Django superuser."""
|
520
|
+
try:
|
521
|
+
if not User.objects.filter(username=username).exists():
|
522
|
+
User.objects.create_superuser(username, email, password)
|
523
|
+
self.logger.info(f"Created superuser: {username}")
|
524
|
+
else:
|
525
|
+
self.logger.info(f"Superuser {username} already exists")
|
526
|
+
except Exception as e:
|
527
|
+
self.logger.error(f"Failed to create superuser: {str(e)}")
|
528
|
+
|
529
|
+
def get_security_status(self) -> Dict[str, Any]:
|
530
|
+
"""Get security framework status."""
|
531
|
+
return {
|
532
|
+
"framework": "Django",
|
533
|
+
"middleware_enabled": True,
|
534
|
+
"ssl_enabled": True,
|
535
|
+
"auth_enabled": True,
|
536
|
+
"rate_limiting_enabled": True,
|
537
|
+
"permissions_enabled": True,
|
538
|
+
"timestamp": datetime.utcnow().isoformat()
|
539
|
+
}
|
540
|
+
|
541
|
+
|
542
|
+
# Example usage and testing
|
543
|
+
class DjangoExampleTest:
|
544
|
+
"""Test class for Django example functionality."""
|
545
|
+
|
546
|
+
@staticmethod
|
547
|
+
def test_middleware_creation():
|
548
|
+
"""Test middleware creation."""
|
549
|
+
middleware = DjangoSecurityMiddleware(lambda request: None)
|
550
|
+
assert middleware.config is not None
|
551
|
+
assert middleware.security_manager is not None
|
552
|
+
|
553
|
+
print("✅ Middleware creation test passed")
|
554
|
+
|
555
|
+
@staticmethod
|
556
|
+
def test_public_path_check():
|
557
|
+
"""Test public path checking."""
|
558
|
+
middleware = DjangoSecurityMiddleware(lambda request: None)
|
559
|
+
|
560
|
+
# Test public paths
|
561
|
+
assert middleware._is_public_path("/health/")
|
562
|
+
assert middleware._is_public_path("/metrics/")
|
563
|
+
assert middleware._is_public_path("/admin/")
|
564
|
+
|
565
|
+
# Test private paths
|
566
|
+
assert not middleware._is_public_path("/api/v1/users/")
|
567
|
+
assert not middleware._is_public_path("/private/")
|
568
|
+
|
569
|
+
print("✅ Public path check test passed")
|
570
|
+
|
571
|
+
@staticmethod
|
572
|
+
def test_rate_limit_identifier():
|
573
|
+
"""Test rate limit identifier extraction."""
|
574
|
+
middleware = DjangoSecurityMiddleware(lambda request: None)
|
575
|
+
|
576
|
+
# Mock request
|
577
|
+
class MockRequest:
|
578
|
+
def __init__(self):
|
579
|
+
self.META = {}
|
580
|
+
|
581
|
+
request = MockRequest()
|
582
|
+
|
583
|
+
# Test X-Forwarded-For
|
584
|
+
request.META['HTTP_X_FORWARDED_FOR'] = '192.168.1.1, 10.0.0.1'
|
585
|
+
assert middleware._get_rate_limit_identifier(request) == '192.168.1.1'
|
586
|
+
|
587
|
+
# Test X-Real-IP
|
588
|
+
request.META = {'HTTP_X_REAL_IP': '192.168.1.100'}
|
589
|
+
assert middleware._get_rate_limit_identifier(request) == '192.168.1.100'
|
590
|
+
|
591
|
+
# Test REMOTE_ADDR
|
592
|
+
request.META = {'REMOTE_ADDR': '127.0.0.1'}
|
593
|
+
assert middleware._get_rate_limit_identifier(request) == '127.0.0.1'
|
594
|
+
|
595
|
+
print("✅ Rate limit identifier test passed")
|
596
|
+
|
597
|
+
|
598
|
+
if __name__ == "__main__":
|
599
|
+
# Run tests
|
600
|
+
print("Running Django Example Tests...")
|
601
|
+
DjangoExampleTest.test_middleware_creation()
|
602
|
+
DjangoExampleTest.test_public_path_check()
|
603
|
+
DjangoExampleTest.test_rate_limit_identifier()
|
604
|
+
|
605
|
+
# Example usage
|
606
|
+
print("\nExample Usage:")
|
607
|
+
example = DjangoExample()
|
608
|
+
example.setup_django_settings()
|
609
|
+
|
610
|
+
# Create superuser
|
611
|
+
example.create_superuser("admin", "admin@example.com", "secure_password")
|
612
|
+
|
613
|
+
# Get security status
|
614
|
+
status = example.get_security_status()
|
615
|
+
print(f"Security status: {status}")
|