mcp-security-framework 1.1.0__py3-none-any.whl → 1.1.2__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 +41 -22
  7. mcp_security_framework/core/cert_manager.py +210 -147
  8. mcp_security_framework/core/permission_manager.py +9 -9
  9. mcp_security_framework/core/rate_limiter.py +2 -2
  10. mcp_security_framework/core/security_manager.py +284 -229
  11. mcp_security_framework/examples/__init__.py +6 -0
  12. mcp_security_framework/examples/comprehensive_example.py +349 -279
  13. mcp_security_framework/examples/django_example.py +247 -206
  14. mcp_security_framework/examples/fastapi_example.py +315 -283
  15. mcp_security_framework/examples/flask_example.py +274 -203
  16. mcp_security_framework/examples/gateway_example.py +304 -237
  17. mcp_security_framework/examples/microservice_example.py +258 -189
  18. mcp_security_framework/examples/standalone_example.py +255 -230
  19. mcp_security_framework/examples/test_all_examples.py +151 -135
  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 +119 -118
  23. mcp_security_framework/middleware/fastapi_middleware.py +156 -148
  24. mcp_security_framework/middleware/flask_auth_middleware.py +160 -147
  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 +18 -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-1.1.0.dist-info → mcp_security_framework-1.1.2.dist-info}/METADATA +4 -3
  34. mcp_security_framework-1.1.2.dist-info/RECORD +84 -0
  35. tests/conftest.py +63 -66
  36. tests/test_cli/test_cert_cli.py +184 -146
  37. tests/test_cli/test_security_cli.py +274 -247
  38. tests/test_core/test_cert_manager.py +24 -10
  39. tests/test_core/test_security_manager.py +2 -2
  40. tests/test_examples/test_comprehensive_example.py +190 -137
  41. tests/test_examples/test_fastapi_example.py +124 -101
  42. tests/test_examples/test_flask_example.py +124 -101
  43. tests/test_examples/test_standalone_example.py +73 -80
  44. tests/test_integration/test_auth_flow.py +213 -197
  45. tests/test_integration/test_certificate_flow.py +180 -149
  46. tests/test_integration/test_fastapi_integration.py +108 -111
  47. tests/test_integration/test_flask_integration.py +141 -140
  48. tests/test_integration/test_standalone_integration.py +290 -259
  49. tests/test_middleware/test_fastapi_auth_middleware.py +195 -174
  50. tests/test_middleware/test_fastapi_middleware.py +147 -132
  51. tests/test_middleware/test_flask_auth_middleware.py +260 -202
  52. tests/test_middleware/test_flask_middleware.py +201 -179
  53. tests/test_middleware/test_security_middleware.py +145 -130
  54. tests/test_utils/test_datetime_compat.py +147 -0
  55. mcp_security_framework-1.1.0.dist-info/RECORD +0 -82
  56. {mcp_security_framework-1.1.0.dist-info → mcp_security_framework-1.1.2.dist-info}/WHEEL +0 -0
  57. {mcp_security_framework-1.1.0.dist-info → mcp_security_framework-1.1.2.dist-info}/entry_points.txt +0 -0
  58. {mcp_security_framework-1.1.0.dist-info → mcp_security_framework-1.1.2.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
- from typing import Dict, List, Any, Optional
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='django-insecure-test-key-for-examples',
33
+ SECRET_KEY="django-insecure-test-key-for-examples",
34
34
  INSTALLED_APPS=[
35
- 'django.contrib.auth',
36
- 'django.contrib.contenttypes',
37
- 'django.contrib.sessions',
35
+ "django.contrib.auth",
36
+ "django.contrib.contenttypes",
37
+ "django.contrib.sessions",
38
38
  ],
39
39
  DATABASES={
40
- 'default': {
41
- 'ENGINE': 'django.db.backends.sqlite3',
42
- 'NAME': ':memory:',
40
+ "default": {
41
+ "ENGINE": "django.db.backends.sqlite3",
42
+ "NAME": ":memory:",
43
43
  }
44
44
  },
45
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',
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
- from django.urls import path, include
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.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
+ )
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.schemas.config import SecurityConfig, AuthConfig, SSLConfig
77
- from mcp_security_framework.schemas.models import AuthResult, AuthStatus, AuthMethod
78
- from mcp_security_framework.constants import (
79
- DEFAULT_CLIENT_IP, DEFAULT_SECURITY_HEADERS, AUTH_METHODS,
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, 'SECURITY_CONFIG_PATH', None)
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, 'r') as f:
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=[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
+ ],
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(path.startswith(public_path) for public_path in self.config.auth.public_paths)
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('HTTP_X_FORWARDED_FOR')
230
+ forwarded_for = request.META.get("HTTP_X_FORWARDED_FOR")
217
231
  if forwarded_for:
218
- return forwarded_for.split(',')[0].strip()
219
-
220
- 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")
221
235
  if real_ip:
222
236
  return real_ip
223
-
237
+
224
238
  # Fall back to remote address
225
- return request.META.get('REMOTE_ADDR', DEFAULT_CLIENT_IP)
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('HTTP_X_API_KEY')
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('HTTP_AUTHORIZATION', '')
293
- if auth_header.startswith('Bearer '):
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('HTTP_AUTHORIZATION', '')
313
- if not auth_header.startswith('Bearer '):
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('SSL_CLIENT_CERT')
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
- 'GET': ['read'],
365
- 'POST': ['write'],
366
- 'PUT': ['write'],
367
- 'PATCH': ['write'],
368
- 'DELETE': ['delete']
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
- "error": "Rate limit exceeded",
382
- "message": "Too many requests, please try again later",
383
- "error_code": ErrorCodes.RATE_LIMIT_EXCEEDED_ERROR
384
- }, status=HTTP_TOO_MANY_REQUESTS)
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
- "error": "Authentication failed",
390
- "message": auth_result.error_message or "Invalid credentials",
391
- "error_code": auth_result.error_code,
392
- "auth_method": auth_result.auth_method
393
- }, status=HTTP_UNAUTHORIZED)
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
- "error": "Permission denied",
399
- "message": "Insufficient permissions to access this resource",
400
- "error_code": ErrorCodes.PERMISSION_DENIED_ERROR
401
- }, 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
+ )
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
- "status": "healthy",
412
- "timestamp": datetime.now(timezone.utc).isoformat(),
413
- "version": "1.0.0"
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
- "requests_total": 1000,
424
- "requests_per_minute": 60,
425
- "active_connections": 25,
426
- "uptime_seconds": 3600
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, 'user_info', None)
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
- "username": user_info.get("username"),
441
- "roles": user_info.get("roles", []),
442
- "permissions": user_info.get("permissions", []),
443
- "last_login": datetime.now(timezone.utc).isoformat()
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, 'user_info', None)
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
- "users": [
458
- {"username": "admin", "roles": ["admin"], "status": "active"},
459
- {"username": "user", "roles": ["user"], "status": "active"},
460
- {"username": "readonly", "roles": ["readonly"], "status": "active"}
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, 'user_info', None)
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
- "id": data_id,
476
- "data": {"example": "data"},
477
- "created_by": "user",
478
- "created_at": "2024-01-01T00:00:00Z"
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, 'user_info', None)
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
- "id": "data_123",
495
- "created_by": user_info.get("username"),
496
- "data": data,
497
- "created_at": datetime.now(timezone.utc).isoformat()
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('health/', HealthCheckView.as_view(), name='health'),
504
- path('metrics/', MetricsView.as_view(), name='metrics'),
505
- path('api/v1/users/me/', UserProfileView.as_view(), name='user_profile'),
506
- path('api/v1/admin/users/', AdminUsersView.as_view(), name='admin_users'),
507
- path('api/v1/data/', DataView.as_view(), name='data'),
508
- 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"),
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 'mcp_security_framework.examples.django_example.DjangoSecurityMiddleware' not in settings.MIDDLEWARE:
538
- settings.MIDDLEWARE.insert(0, 'mcp_security_framework.examples.django_example.DjangoSecurityMiddleware')
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 = 'DENY'
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['HTTP_X_FORWARDED_FOR'] = '192.168.1.1, 10.0.0.1'
618
- assert middleware._get_rate_limit_identifier(request) == '192.168.1.1'
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 = {'HTTP_X_REAL_IP': '192.168.1.100'}
622
- assert middleware._get_rate_limit_identifier(request) == '192.168.1.100'
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 = {'REMOTE_ADDR': '127.0.0.1'}
626
- assert middleware._get_rate_limit_identifier(request) == '127.0.0.1'
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}")