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
@@ -27,165 +27,167 @@ Last Modified: 2024-01-20
27
27
 
28
28
  import json
29
29
  import logging
30
- from typing import Any, Dict, Optional
30
+ from typing import Any, Dict, List, Optional
31
31
 
32
- from flask import Request, Response, jsonify, current_app
32
+ from flask import Request, Response, current_app, jsonify
33
33
  from werkzeug.exceptions import Unauthorized
34
34
 
35
35
  from mcp_security_framework.middleware.auth_middleware import AuthMiddleware
36
- from mcp_security_framework.schemas.models import AuthResult, AuthStatus, AuthMethod
37
36
  from mcp_security_framework.schemas.config import SecurityConfig
37
+ from mcp_security_framework.schemas.models import AuthMethod, AuthResult, AuthStatus
38
38
 
39
39
 
40
40
  class FlaskAuthMiddleware(AuthMiddleware):
41
41
  """
42
42
  Flask Authentication Middleware Implementation
43
-
43
+
44
44
  This class provides Flask-specific authentication middleware that
45
45
  handles authentication-only processing for Flask applications.
46
-
46
+
47
47
  The middleware implements:
48
48
  - Flask request/response handling
49
49
  - Authentication caching and optimization
50
50
  - Framework-specific error responses
51
51
  - Security event logging
52
-
52
+
53
53
  Attributes:
54
54
  config (SecurityConfig): Security configuration settings
55
55
  security_manager: Security manager instance
56
56
  logger (Logger): Logger instance for authentication operations
57
57
  _auth_cache (Dict): Authentication result cache
58
-
58
+
59
59
  Example:
60
60
  >>> from mcp_security_framework.middleware import create_auth_middleware
61
61
  >>> middleware = create_auth_middleware(config, framework="flask")
62
62
  >>> app.wsgi_app = middleware(app.wsgi_app)
63
-
63
+
64
64
  Raises:
65
65
  AuthMiddlewareError: When authentication processing fails
66
66
  """
67
-
68
- def __init__(self, config: SecurityConfig, security_manager: Any):
67
+
68
+ def __init__(self, security_manager: Any):
69
69
  """
70
70
  Initialize Flask Authentication Middleware.
71
-
71
+
72
72
  Args:
73
- config (SecurityConfig): Security configuration settings
74
- security_manager: Security manager instance
73
+ security_manager: Security manager instance containing
74
+ all security components and configuration.
75
75
  """
76
- super().__init__(config, security_manager)
76
+ super().__init__(security_manager)
77
77
  self.logger = logging.getLogger(__name__)
78
-
78
+
79
79
  def __call__(self, environ: Dict[str, Any], start_response: Any) -> Any:
80
80
  """
81
81
  Process Flask request through authentication middleware.
82
-
82
+
83
83
  This method implements the authentication-only processing
84
84
  pipeline for Flask requests, focusing solely on user
85
85
  authentication without authorization checks.
86
-
86
+
87
87
  Args:
88
88
  environ (Dict[str, Any]): WSGI environment
89
89
  start_response: WSGI start_response function
90
-
90
+
91
91
  Returns:
92
92
  Any: WSGI response
93
-
93
+
94
94
  Raises:
95
95
  AuthMiddlewareError: If authentication processing fails
96
96
  """
97
97
  try:
98
98
  # Create Flask request object
99
99
  request = Request(environ)
100
-
100
+
101
101
  # Check if path is public (bypasses authentication)
102
102
  if self._is_public_path(request):
103
103
  return self._call_next(environ, start_response)
104
-
104
+
105
105
  # Perform authentication
106
106
  auth_result = self._authenticate_only(request)
107
-
107
+
108
108
  if not auth_result.is_valid:
109
109
  return self._auth_error_response(auth_result, start_response)
110
-
110
+
111
111
  # Add authentication info to request context
112
112
  request.auth_result = auth_result
113
113
  request.username = auth_result.username
114
114
  request.user_roles = auth_result.roles
115
115
  request.auth_method = auth_result.auth_method
116
-
116
+
117
117
  # Process request
118
118
  response = self._call_next(environ, start_response)
119
-
119
+
120
120
  # Log successful authentication
121
- self._log_auth_event("authentication_successful", {
122
- "ip_address": self._get_client_ip(request),
123
- "username": auth_result.username,
124
- "path": request.path,
125
- "method": request.method,
126
- "auth_method": auth_result.auth_method
127
- })
128
-
121
+ self._log_auth_event(
122
+ "authentication_successful",
123
+ {
124
+ "ip_address": self._get_client_ip(request),
125
+ "username": auth_result.username,
126
+ "path": request.path,
127
+ "method": request.method,
128
+ "auth_method": auth_result.auth_method,
129
+ },
130
+ )
131
+
129
132
  return response
130
-
133
+
131
134
  except Exception as e:
132
135
  self.logger.error(
133
136
  "Flask authentication middleware processing failed",
134
137
  extra={"error": str(e)},
135
- exc_info=True
138
+ exc_info=True,
136
139
  )
137
140
  raise AuthMiddlewareError(
138
- f"Authentication processing failed: {str(e)}",
139
- error_code=-32035
141
+ f"Authentication processing failed: {str(e)}", error_code=-32035
140
142
  )
141
-
143
+
142
144
  def _call_next(self, environ: Dict[str, Any], start_response: Any) -> Any:
143
145
  """
144
146
  Call the next WSGI application in the chain.
145
-
147
+
146
148
  Args:
147
149
  environ (Dict[str, Any]): WSGI environment
148
150
  start_response: WSGI start_response function
149
-
151
+
150
152
  Returns:
151
153
  Any: WSGI response
152
154
  """
153
155
  # This would typically call the Flask app
154
156
  # For now, return a simple response
155
- status = '200 OK'
156
- headers = [('Content-Type', 'application/json')]
157
+ status = "200 OK"
158
+ headers = [("Content-Type", "application/json")]
157
159
  start_response(status, headers)
158
160
  return [json.dumps({"message": "OK"}).encode()]
159
-
161
+
160
162
  def _is_public_path(self, request: Request) -> bool:
161
163
  """
162
164
  Check if the request path is public (bypasses authentication).
163
-
165
+
164
166
  Args:
165
167
  request (Request): Flask request object
166
-
168
+
167
169
  Returns:
168
170
  bool: True if path is public, False otherwise
169
171
  """
170
172
  path = request.path
171
-
173
+
172
174
  # Check configured public paths
173
- if hasattr(self.config.auth, 'public_paths'):
175
+ if hasattr(self.config.auth, "public_paths"):
174
176
  for public_path in self.config.auth.public_paths:
175
177
  if path.startswith(public_path):
176
178
  return True
177
-
179
+
178
180
  # Check common public paths
179
181
  public_paths = ["/health", "/status", "/metrics", "/docs", "/openapi.json"]
180
182
  return any(path.startswith(public_path) for public_path in public_paths)
181
-
183
+
182
184
  def _get_client_ip(self, request: Request) -> str:
183
185
  """
184
186
  Get client IP address from Flask request.
185
-
187
+
186
188
  Args:
187
189
  request (Request): Flask request object
188
-
190
+
189
191
  Returns:
190
192
  str: Client IP address
191
193
  """
@@ -193,33 +195,35 @@ class FlaskAuthMiddleware(AuthMiddleware):
193
195
  forwarded_for = request.headers.get("X-Forwarded-For")
194
196
  if forwarded_for:
195
197
  return forwarded_for.split(",")[0].strip()
196
-
198
+
197
199
  # Try X-Real-IP header
198
200
  real_ip = request.headers.get("X-Real-IP")
199
201
  if real_ip:
200
202
  return real_ip
201
-
203
+
202
204
  # Use remote address
203
205
  if request.remote_addr:
204
206
  return request.remote_addr
205
-
207
+
206
208
  # Fallback to default IP from config or environment
207
- default_ip = getattr(self.config, 'default_client_ip', None)
209
+ default_ip = getattr(self.config, "default_client_ip", None)
208
210
  if default_ip:
209
211
  return default_ip
210
-
212
+
211
213
  # Use environment variable or default
212
214
  import os
215
+
213
216
  from ..constants import DEFAULT_CLIENT_IP
214
- return os.environ.get('DEFAULT_CLIENT_IP', DEFAULT_CLIENT_IP)
215
-
217
+
218
+ return os.environ.get("DEFAULT_CLIENT_IP", DEFAULT_CLIENT_IP)
219
+
216
220
  def _get_cache_key(self, request: Request) -> str:
217
221
  """
218
222
  Generate cache key for authentication result.
219
-
223
+
220
224
  Args:
221
225
  request (Request): Flask request object
222
-
226
+
223
227
  Returns:
224
228
  str: Cache key
225
229
  """
@@ -227,15 +231,15 @@ class FlaskAuthMiddleware(AuthMiddleware):
227
231
  ip = self._get_client_ip(request)
228
232
  user_agent = request.headers.get("User-Agent", "")
229
233
  return f"auth:{ip}:{hash(user_agent)}"
230
-
234
+
231
235
  def _try_auth_method(self, request: Request, method: str) -> AuthResult:
232
236
  """
233
237
  Try authentication using specific method with Flask request.
234
-
238
+
235
239
  Args:
236
240
  request (Request): Flask request object
237
241
  method (str): Authentication method to try
238
-
242
+
239
243
  Returns:
240
244
  AuthResult: Authentication result
241
245
  """
@@ -256,13 +260,13 @@ class FlaskAuthMiddleware(AuthMiddleware):
256
260
  roles=[],
257
261
  auth_method=None,
258
262
  error_code=-32022,
259
- error_message=f"Unsupported authentication method: {method}"
263
+ error_message=f"Unsupported authentication method: {method}",
260
264
  )
261
265
  except Exception as e:
262
266
  self.logger.error(
263
267
  f"Authentication method {method} failed",
264
268
  extra={"error": str(e)},
265
- exc_info=True
269
+ exc_info=True,
266
270
  )
267
271
  return AuthResult(
268
272
  is_valid=False,
@@ -271,16 +275,16 @@ class FlaskAuthMiddleware(AuthMiddleware):
271
275
  roles=[],
272
276
  auth_method=None,
273
277
  error_code=-32023,
274
- error_message=f"Authentication method {method} failed: {str(e)}"
278
+ error_message=f"Authentication method {method} failed: {str(e)}",
275
279
  )
276
-
280
+
277
281
  def _try_api_key_auth(self, request: Request) -> AuthResult:
278
282
  """
279
283
  Try API key authentication with Flask request.
280
-
284
+
281
285
  Args:
282
286
  request (Request): Flask request object
283
-
287
+
284
288
  Returns:
285
289
  AuthResult: Authentication result
286
290
  """
@@ -291,7 +295,7 @@ class FlaskAuthMiddleware(AuthMiddleware):
291
295
  auth_header = request.headers.get("Authorization")
292
296
  if auth_header and auth_header.startswith("Bearer "):
293
297
  api_key = auth_header[7:] # Remove "Bearer " prefix
294
-
298
+
295
299
  if not api_key:
296
300
  return AuthResult(
297
301
  is_valid=False,
@@ -300,19 +304,19 @@ class FlaskAuthMiddleware(AuthMiddleware):
300
304
  roles=[],
301
305
  auth_method=AuthMethod.API_KEY,
302
306
  error_code=-32012,
303
- error_message="API key not found in request"
307
+ error_message="API key not found in request",
304
308
  )
305
-
309
+
306
310
  # Authenticate using security manager
307
311
  return self.security_manager.auth_manager.authenticate_api_key(api_key)
308
-
312
+
309
313
  def _try_jwt_auth(self, request: Request) -> AuthResult:
310
314
  """
311
315
  Try JWT authentication with Flask request.
312
-
316
+
313
317
  Args:
314
318
  request (Request): Flask request object
315
-
319
+
316
320
  Returns:
317
321
  AuthResult: Authentication result
318
322
  """
@@ -326,21 +330,21 @@ class FlaskAuthMiddleware(AuthMiddleware):
326
330
  roles=[],
327
331
  auth_method=AuthMethod.JWT,
328
332
  error_code=-32013,
329
- error_message="JWT token not found in Authorization header"
333
+ error_message="JWT token not found in Authorization header",
330
334
  )
331
-
335
+
332
336
  token = auth_header[7:] # Remove "Bearer " prefix
333
-
337
+
334
338
  # Authenticate using security manager
335
339
  return self.security_manager.auth_manager.authenticate_jwt_token(token)
336
-
340
+
337
341
  def _try_certificate_auth(self, request: Request) -> AuthResult:
338
342
  """
339
343
  Try certificate authentication with Flask request.
340
-
344
+
341
345
  Args:
342
346
  request (Request): Flask request object
343
-
347
+
344
348
  Returns:
345
349
  AuthResult: Authentication result
346
350
  """
@@ -354,16 +358,16 @@ class FlaskAuthMiddleware(AuthMiddleware):
354
358
  roles=[],
355
359
  auth_method=AuthMethod.CERTIFICATE,
356
360
  error_code=-32014,
357
- error_message="Certificate authentication not implemented"
361
+ error_message="Certificate authentication not implemented",
358
362
  )
359
-
363
+
360
364
  def _try_basic_auth(self, request: Request) -> AuthResult:
361
365
  """
362
366
  Try basic authentication with Flask request.
363
-
367
+
364
368
  Args:
365
369
  request (Request): Flask request object
366
-
370
+
367
371
  Returns:
368
372
  AuthResult: Authentication result
369
373
  """
@@ -377,9 +381,9 @@ class FlaskAuthMiddleware(AuthMiddleware):
377
381
  roles=[],
378
382
  auth_method=AuthMethod.BASIC,
379
383
  error_code=-32015,
380
- error_message="Basic authentication credentials not found"
384
+ error_message="Basic authentication credentials not found",
381
385
  )
382
-
386
+
383
387
  # Basic auth is not implemented in this version
384
388
  return AuthResult(
385
389
  is_valid=False,
@@ -388,17 +392,17 @@ class FlaskAuthMiddleware(AuthMiddleware):
388
392
  roles=[],
389
393
  auth_method=AuthMethod.BASIC,
390
394
  error_code=-32016,
391
- error_message="Basic authentication not implemented"
395
+ error_message="Basic authentication not implemented",
392
396
  )
393
-
397
+
394
398
  def _auth_error_response(self, auth_result: AuthResult, start_response: Any) -> Any:
395
399
  """
396
400
  Create authentication error response for Flask.
397
-
401
+
398
402
  Args:
399
403
  auth_result (AuthResult): Authentication result with error
400
404
  start_response: WSGI start_response function
401
-
405
+
402
406
  Returns:
403
407
  Any: WSGI error response
404
408
  """
@@ -406,22 +410,21 @@ class FlaskAuthMiddleware(AuthMiddleware):
406
410
  "error": "Authentication failed",
407
411
  "error_code": auth_result.error_code,
408
412
  "error_message": auth_result.error_message,
409
- "auth_method": auth_result.auth_method.value if auth_result.auth_method else None
413
+ "auth_method": (
414
+ auth_result.auth_method.value if auth_result.auth_method else None
415
+ ),
410
416
  }
411
-
412
- status = '401 Unauthorized'
413
- headers = [
414
- ('Content-Type', 'application/json'),
415
- ('WWW-Authenticate', 'Bearer')
416
- ]
417
-
417
+
418
+ status = "401 Unauthorized"
419
+ headers = [("Content-Type", "application/json"), ("WWW-Authenticate", "Bearer")]
420
+
418
421
  start_response(status, headers)
419
422
  return [json.dumps(error_data).encode()]
420
-
423
+
421
424
  def _log_auth_event(self, event_type: str, details: Dict[str, Any]) -> None:
422
425
  """
423
426
  Log authentication event.
424
-
427
+
425
428
  Args:
426
429
  event_type (str): Type of authentication event
427
430
  details (Dict[str, Any]): Event details
@@ -437,28 +440,185 @@ class FlaskAuthMiddleware(AuthMiddleware):
437
440
  "path": details.get("path"),
438
441
  "method": details.get("method"),
439
442
  "auth_method": details.get("auth_method"),
440
- **details
441
- }
443
+ **details,
444
+ },
442
445
  )
443
446
  except Exception as e:
444
447
  self.logger.error(
445
448
  "Failed to log authentication event",
446
449
  extra={"error": str(e)},
447
- exc_info=True
450
+ exc_info=True,
448
451
  )
449
452
 
453
+ def _apply_security_headers(self, response: Any) -> Any:
454
+ """
455
+ Apply security headers to response.
456
+
457
+ Args:
458
+ response: WSGI response
459
+
460
+ Returns:
461
+ Any: Response with security headers
462
+ """
463
+ # This would be implemented for actual Flask responses
464
+ # For now, return the response as-is
465
+ return response
466
+
467
+ def _create_error_response(
468
+ self, error_message: str, error_code: int, start_response: Any
469
+ ) -> Any:
470
+ """
471
+ Create error response for Flask.
472
+
473
+ Args:
474
+ error_message (str): Error message
475
+ error_code (int): Error code
476
+ start_response: WSGI start_response function
477
+
478
+ Returns:
479
+ Any: WSGI error response
480
+ """
481
+ error_data = {
482
+ "error": "Security error",
483
+ "error_code": error_code,
484
+ "error_message": error_message,
485
+ }
486
+
487
+ status = "400 Bad Request"
488
+ headers = [("Content-Type", "application/json")]
489
+
490
+ start_response(status, headers)
491
+ return [json.dumps(error_data).encode()]
492
+
493
+ def _get_rate_limit_identifier(self, request: Request) -> str:
494
+ """
495
+ Get rate limit identifier from request.
496
+
497
+ Args:
498
+ request (Request): Flask request object
499
+
500
+ Returns:
501
+ str: Rate limit identifier (IP address)
502
+ """
503
+ return self._get_client_ip(request)
504
+
505
+ def _get_request_path(self, request: Request) -> str:
506
+ """
507
+ Get request path from request.
508
+
509
+ Args:
510
+ request (Request): Flask request object
511
+
512
+ Returns:
513
+ str: Request path
514
+ """
515
+ return request.path
516
+
517
+ def _get_required_permissions(self, request: Request) -> List[str]:
518
+ """
519
+ Get required permissions for request.
520
+
521
+ Args:
522
+ request (Request): Flask request object
523
+
524
+ Returns:
525
+ List[str]: Required permissions
526
+ """
527
+ # This would be implemented based on your application's authorization logic
528
+ # For now, return an empty list
529
+ return []
530
+
531
+ def _validation_error_response(
532
+ self, error_message: str, error_code: int = -32000, start_response: Any = None
533
+ ) -> Any:
534
+ """
535
+ Create validation error response for Flask.
536
+
537
+ Args:
538
+ error_message (str): Error message
539
+ error_code (int): Error code
540
+ start_response: WSGI start_response function
541
+
542
+ Returns:
543
+ Any: WSGI error response
544
+ """
545
+ if start_response is None:
546
+ start_response = Mock()
547
+ return self._create_error_response(error_message, error_code, start_response)
548
+
549
+ def _rate_limit_error_response(
550
+ self, error_message: str, error_code: int = -32000, start_response: Any = None
551
+ ) -> Any:
552
+ """
553
+ Create rate limit error response for Flask.
554
+
555
+ Args:
556
+ error_message (str): Error message
557
+ error_code (int): Error code
558
+ start_response: WSGI start_response function
559
+
560
+ Returns:
561
+ Any: WSGI error response
562
+ """
563
+ if start_response is None:
564
+ start_response = Mock()
565
+ return self._create_error_response(error_message, error_code, start_response)
566
+
567
+ def _security_header_response(
568
+ self, error_message: str, error_code: int = -32000, start_response: Any = None
569
+ ) -> Any:
570
+ """
571
+ Create security header error response for Flask.
572
+
573
+ Args:
574
+ error_message (str): Error message
575
+ error_code (int): Error code
576
+ start_response: WSGI start_response function
577
+
578
+ Returns:
579
+ Any: WSGI error response
580
+ """
581
+ if start_response is None:
582
+ start_response = Mock()
583
+ return self._create_error_response(error_message, error_code, start_response)
584
+
585
+ def _authz_error_response(
586
+ self, auth_result: AuthResult, start_response: Any
587
+ ) -> Any:
588
+ """
589
+ Create authorization error response for Flask.
590
+
591
+ Args:
592
+ auth_result (AuthResult): Authentication result
593
+ start_response: WSGI start_response function
594
+
595
+ Returns:
596
+ Any: WSGI error response
597
+ """
598
+ error_data = {
599
+ "error": "Authorization failed",
600
+ "error_code": auth_result.error_code,
601
+ "error_message": auth_result.error_message,
602
+ }
603
+
604
+ status = "403 Forbidden"
605
+ headers = [("Content-Type", "application/json")]
606
+
607
+ start_response(status, headers)
608
+ return [json.dumps(error_data).encode()]
609
+
450
610
 
451
611
  class AuthMiddlewareError(Exception):
452
612
  """
453
613
  Authentication Middleware Error
454
-
614
+
455
615
  This exception is raised when authentication middleware processing fails.
456
-
616
+
457
617
  Attributes:
458
618
  message (str): Error message
459
619
  error_code (int): Error code for programmatic handling
460
620
  """
461
-
621
+
462
622
  def __init__(self, message: str, error_code: int = -32030):
463
623
  self.message = message
464
624
  self.error_code = error_code