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