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.
Files changed (58) hide show
  1. mcp_security_framework/__init__.py +26 -15
  2. mcp_security_framework/cli/__init__.py +1 -1
  3. mcp_security_framework/cli/cert_cli.py +233 -197
  4. mcp_security_framework/cli/security_cli.py +324 -234
  5. mcp_security_framework/constants.py +21 -27
  6. mcp_security_framework/core/auth_manager.py +49 -20
  7. mcp_security_framework/core/cert_manager.py +398 -104
  8. mcp_security_framework/core/permission_manager.py +13 -9
  9. mcp_security_framework/core/rate_limiter.py +10 -0
  10. mcp_security_framework/core/security_manager.py +286 -229
  11. mcp_security_framework/examples/__init__.py +6 -0
  12. mcp_security_framework/examples/comprehensive_example.py +954 -0
  13. mcp_security_framework/examples/django_example.py +276 -202
  14. mcp_security_framework/examples/fastapi_example.py +897 -393
  15. mcp_security_framework/examples/flask_example.py +311 -200
  16. mcp_security_framework/examples/gateway_example.py +373 -214
  17. mcp_security_framework/examples/microservice_example.py +337 -172
  18. mcp_security_framework/examples/standalone_example.py +719 -478
  19. mcp_security_framework/examples/test_all_examples.py +572 -0
  20. mcp_security_framework/middleware/__init__.py +46 -55
  21. mcp_security_framework/middleware/auth_middleware.py +62 -63
  22. mcp_security_framework/middleware/fastapi_auth_middleware.py +179 -110
  23. mcp_security_framework/middleware/fastapi_middleware.py +156 -148
  24. mcp_security_framework/middleware/flask_auth_middleware.py +267 -107
  25. mcp_security_framework/middleware/flask_middleware.py +183 -157
  26. mcp_security_framework/middleware/mtls_middleware.py +106 -117
  27. mcp_security_framework/middleware/rate_limit_middleware.py +105 -101
  28. mcp_security_framework/middleware/security_middleware.py +109 -124
  29. mcp_security_framework/schemas/config.py +2 -1
  30. mcp_security_framework/schemas/models.py +19 -6
  31. mcp_security_framework/utils/cert_utils.py +14 -8
  32. mcp_security_framework/utils/datetime_compat.py +116 -0
  33. {mcp_security_framework-0.1.0.dist-info → mcp_security_framework-1.1.1.dist-info}/METADATA +2 -1
  34. mcp_security_framework-1.1.1.dist-info/RECORD +84 -0
  35. tests/conftest.py +303 -0
  36. tests/test_cli/test_cert_cli.py +194 -174
  37. tests/test_cli/test_security_cli.py +274 -247
  38. tests/test_core/test_cert_manager.py +33 -19
  39. tests/test_core/test_security_manager.py +2 -2
  40. tests/test_examples/test_comprehensive_example.py +613 -0
  41. tests/test_examples/test_fastapi_example.py +290 -169
  42. tests/test_examples/test_flask_example.py +304 -162
  43. tests/test_examples/test_standalone_example.py +106 -168
  44. tests/test_integration/test_auth_flow.py +214 -198
  45. tests/test_integration/test_certificate_flow.py +181 -150
  46. tests/test_integration/test_fastapi_integration.py +140 -149
  47. tests/test_integration/test_flask_integration.py +144 -141
  48. tests/test_integration/test_standalone_integration.py +331 -300
  49. tests/test_middleware/test_fastapi_auth_middleware.py +745 -0
  50. tests/test_middleware/test_fastapi_middleware.py +147 -132
  51. tests/test_middleware/test_flask_auth_middleware.py +696 -0
  52. tests/test_middleware/test_flask_middleware.py +201 -179
  53. tests/test_middleware/test_security_middleware.py +151 -130
  54. tests/test_utils/test_datetime_compat.py +147 -0
  55. mcp_security_framework-0.1.0.dist-info/RECORD +0 -76
  56. {mcp_security_framework-0.1.0.dist-info → mcp_security_framework-1.1.1.dist-info}/WHEEL +0 -0
  57. {mcp_security_framework-0.1.0.dist-info → mcp_security_framework-1.1.1.dist-info}/entry_points.txt +0 -0
  58. {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
- from typing import Dict, List, Any, Optional
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
- 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
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.core.security_manager import SecurityManager
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.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
- )
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, 'SECURITY_CONFIG_PATH', None)
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, 'r') as f:
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=[AUTH_METHODS["API_KEY"], AUTH_METHODS["JWT"], AUTH_METHODS["CERTIFICATE"]],
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=True,
93
- cert_file="certs/server.crt",
94
- key_file="certs/server.key",
95
- ca_cert_file="certs/ca.crt",
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": True,
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(path.startswith(public_path) for public_path in self.config.auth.public_paths)
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('HTTP_X_FORWARDED_FOR')
230
+ forwarded_for = request.META.get("HTTP_X_FORWARDED_FOR")
184
231
  if forwarded_for:
185
- return forwarded_for.split(',')[0].strip()
186
-
187
- real_ip = request.META.get('HTTP_X_REAL_IP')
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('REMOTE_ADDR', DEFAULT_CLIENT_IP)
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('HTTP_X_API_KEY')
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('HTTP_AUTHORIZATION', '')
260
- if auth_header.startswith('Bearer '):
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('HTTP_AUTHORIZATION', '')
280
- if not auth_header.startswith('Bearer '):
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('SSL_CLIENT_CERT')
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
- 'GET': ['read'],
332
- 'POST': ['write'],
333
- 'PUT': ['write'],
334
- 'PATCH': ['write'],
335
- 'DELETE': ['delete']
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
- "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
-
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
- "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
-
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
- "error": "Permission denied",
366
- "message": "Insufficient permissions to access this resource",
367
- "error_code": ErrorCodes.PERMISSION_DENIED_ERROR
368
- }, status=HTTP_FORBIDDEN)
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
- "status": "healthy",
379
- "timestamp": datetime.utcnow().isoformat(),
380
- "version": "1.0.0"
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
- "requests_total": 1000,
391
- "requests_per_minute": 60,
392
- "active_connections": 25,
393
- "uptime_seconds": 3600
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, 'user_info', None)
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
- "username": user_info.get("username"),
408
- "roles": user_info.get("roles", []),
409
- "permissions": user_info.get("permissions", []),
410
- "last_login": datetime.utcnow().isoformat()
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, 'user_info', None)
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
- "users": [
425
- {"username": "admin", "roles": ["admin"], "status": "active"},
426
- {"username": "user", "roles": ["user"], "status": "active"},
427
- {"username": "readonly", "roles": ["readonly"], "status": "active"}
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, 'user_info', None)
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
- "id": data_id,
443
- "data": {"example": "data"},
444
- "created_by": "user",
445
- "created_at": "2024-01-01T00:00:00Z"
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, 'user_info', None)
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
- "id": "data_123",
462
- "created_by": user_info.get("username"),
463
- "data": data,
464
- "created_at": datetime.utcnow().isoformat()
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('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'),
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 'mcp_security_framework.examples.django_example.DjangoSecurityMiddleware' not in settings.MIDDLEWARE:
505
- settings.MIDDLEWARE.insert(0, 'mcp_security_framework.examples.django_example.DjangoSecurityMiddleware')
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 = 'DENY'
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.utcnow().isoformat()
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['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
-
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 = {'HTTP_X_REAL_IP': '192.168.1.100'}
589
- assert middleware._get_rate_limit_identifier(request) == '192.168.1.100'
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 = {'REMOTE_ADDR': '127.0.0.1'}
593
- assert middleware._get_rate_limit_identifier(request) == '127.0.0.1'
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}")