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