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
@@ -25,15 +25,15 @@ import json
25
25
  import logging
26
26
  from typing import Any, Dict, List, Optional, Union
27
27
 
28
- from flask import Request, Response, request, make_response, jsonify, current_app
28
+ from flask import Request, Response, current_app, jsonify, make_response, request
29
29
 
30
+ from ..schemas.models import AuthMethod, AuthResult, AuthStatus
30
31
  from .security_middleware import SecurityMiddleware, SecurityMiddlewareError
31
- from ..schemas.models import AuthResult, AuthStatus, AuthMethod
32
32
 
33
33
 
34
34
  class FlaskMiddlewareError(SecurityMiddlewareError):
35
35
  """Raised when Flask middleware encounters an error."""
36
-
36
+
37
37
  def __init__(self, message: str, error_code: int = -32020):
38
38
  self.message = message
39
39
  self.error_code = error_code
@@ -43,11 +43,11 @@ class FlaskMiddlewareError(SecurityMiddlewareError):
43
43
  class FlaskSecurityMiddleware(SecurityMiddleware):
44
44
  """
45
45
  Flask Security Middleware Class
46
-
46
+
47
47
  This class provides Flask-specific implementation of the security
48
48
  middleware. It integrates with Flask's WSGI system and handles
49
49
  Flask Request/Response objects.
50
-
50
+
51
51
  The FlaskSecurityMiddleware implements:
52
52
  - Flask-specific request processing
53
53
  - Flask authentication method handling
@@ -55,7 +55,7 @@ class FlaskSecurityMiddleware(SecurityMiddleware):
55
55
  - Flask-specific error handling
56
56
  - Flask header management
57
57
  - Flask rate limiting integration
58
-
58
+
59
59
  Key Responsibilities:
60
60
  - Process Flask requests through security pipeline
61
61
  - Extract authentication credentials from Flask requests
@@ -63,206 +63,224 @@ class FlaskSecurityMiddleware(SecurityMiddleware):
63
63
  - Add security headers to Flask responses
64
64
  - Handle Flask-specific request/response objects
65
65
  - Integrate with Flask WSGI system
66
-
66
+
67
67
  Attributes:
68
68
  Inherits all attributes from SecurityMiddleware
69
69
  _flask_app: Reference to Flask application (if available)
70
-
70
+
71
71
  Example:
72
72
  >>> from flask import Flask
73
73
  >>> from mcp_security_framework.middleware import FlaskSecurityMiddleware
74
- >>>
74
+ >>>
75
75
  >>> app = Flask(__name__)
76
76
  >>> security_manager = SecurityManager(config)
77
77
  >>> middleware = FlaskSecurityMiddleware(security_manager)
78
78
  >>> app.wsgi_app = middleware(app.wsgi_app)
79
-
79
+
80
80
  Note:
81
81
  This middleware should be integrated with Flask applications
82
82
  by wrapping the WSGI application.
83
83
  """
84
-
84
+
85
85
  def __init__(self, security_manager):
86
86
  """
87
87
  Initialize Flask Security Middleware.
88
-
88
+
89
89
  Args:
90
90
  security_manager: Security manager instance containing
91
91
  all security components and configuration.
92
-
92
+
93
93
  Raises:
94
94
  FlaskMiddlewareError: If initialization fails
95
95
  """
96
96
  super().__init__(security_manager)
97
97
  self.logger = logging.getLogger(f"{__name__}.{self.__class__.__name__}")
98
-
98
+
99
99
  self.logger.info("Flask Security Middleware initialized")
100
-
100
+
101
101
  def __call__(self, environ: Dict[str, Any], start_response) -> List[bytes]:
102
102
  """
103
103
  Process Flask request through security middleware.
104
-
104
+
105
105
  This method implements the security processing pipeline for
106
106
  Flask requests, including rate limiting, authentication,
107
107
  authorization, and security header management.
108
-
108
+
109
109
  Args:
110
110
  environ (Dict[str, Any]): WSGI environment dictionary
111
111
  start_response: WSGI start_response callable
112
-
112
+
113
113
  Returns:
114
114
  List[bytes]: WSGI response body
115
-
115
+
116
116
  Raises:
117
117
  FlaskMiddlewareError: For middleware processing errors
118
118
  """
119
119
  try:
120
120
  # Create Flask request object from WSGI environ
121
121
  flask_request = Request(environ)
122
-
122
+
123
123
  # Check rate limit
124
124
  if not self._check_rate_limit(flask_request):
125
125
  return self._rate_limit_response(start_response)
126
-
126
+
127
127
  # Check if public path
128
128
  if self._is_public_path(flask_request):
129
129
  # Process request normally
130
130
  return self._process_request(environ, start_response, flask_request)
131
-
131
+
132
132
  # Authenticate request
133
133
  auth_result = self._authenticate_request(flask_request)
134
134
  if not auth_result.is_valid:
135
135
  return self._auth_error_response(auth_result, start_response)
136
-
136
+
137
137
  # Validate permissions
138
138
  if not self._validate_permissions(flask_request, auth_result):
139
139
  return self._permission_error_response(start_response)
140
-
140
+
141
141
  # Process request
142
- return self._process_request(environ, start_response, flask_request, auth_result)
143
-
142
+ return self._process_request(
143
+ environ, start_response, flask_request, auth_result
144
+ )
145
+
144
146
  except Exception as e:
145
147
  self.logger.error(
146
148
  "Flask middleware processing failed",
147
149
  extra={"error": str(e)},
148
- exc_info=True
150
+ exc_info=True,
149
151
  )
150
152
  raise FlaskMiddlewareError(
151
- f"Middleware processing failed: {str(e)}",
152
- error_code=-32021
153
+ f"Middleware processing failed: {str(e)}", error_code=-32021
153
154
  )
154
-
155
- def _process_request(self, environ: Dict[str, Any], start_response,
156
- flask_request: Request, auth_result: AuthResult = None) -> List[bytes]:
155
+
156
+ def _process_request(
157
+ self,
158
+ environ: Dict[str, Any],
159
+ start_response,
160
+ flask_request: Request,
161
+ auth_result: AuthResult = None,
162
+ ) -> List[bytes]:
157
163
  """
158
164
  Process the actual request through the WSGI application.
159
-
165
+
160
166
  Args:
161
167
  environ (Dict[str, Any]): WSGI environment
162
168
  start_response: WSGI start_response callable
163
169
  flask_request (Request): Flask request object
164
170
  auth_result (AuthResult): Authentication result (optional)
165
-
171
+
166
172
  Returns:
167
173
  List[bytes]: WSGI response body
168
174
  """
169
175
  # Store auth result in request context for later use
170
176
  if auth_result:
171
177
  flask_request.auth_result = auth_result
172
-
178
+
173
179
  # Call the original WSGI application
174
180
  def custom_start_response(status, headers, exc_info=None):
175
181
  # Add security headers
176
182
  security_headers = self._get_security_headers()
177
183
  headers.extend(security_headers)
178
-
184
+
179
185
  # Log successful request
180
186
  if auth_result:
181
- self._log_security_event("request_processed", {
182
- "ip_address": self._get_client_ip(flask_request),
183
- "username": auth_result.username,
184
- "path": flask_request.path,
185
- "method": flask_request.method,
186
- "status_code": int(status.split()[0])
187
- })
188
-
187
+ self._log_security_event(
188
+ "request_processed",
189
+ {
190
+ "ip_address": self._get_client_ip(flask_request),
191
+ "username": auth_result.username,
192
+ "path": flask_request.path,
193
+ "method": flask_request.method,
194
+ "status_code": int(status.split()[0]),
195
+ },
196
+ )
197
+
189
198
  return start_response(status, headers, exc_info)
190
-
199
+
191
200
  # Get the original WSGI app from the middleware chain
192
201
  app = current_app._get_current_object()
193
202
  return app(environ, custom_start_response)
194
-
203
+
195
204
  def _get_rate_limit_identifier(self, request: Request) -> str:
196
205
  """
197
206
  Get rate limit identifier from Flask request.
198
-
207
+
199
208
  This method extracts the rate limit identifier from the Flask
200
209
  request, typically using the client IP address.
201
-
210
+
202
211
  Args:
203
212
  request (Request): Flask request object
204
-
213
+
205
214
  Returns:
206
215
  str: Rate limit identifier (IP address)
207
216
  """
208
217
  return self._get_client_ip(request)
209
-
218
+
210
219
  def _get_request_path(self, request: Request) -> str:
211
220
  """
212
221
  Get request path from Flask request.
213
-
222
+
214
223
  Args:
215
224
  request (Request): Flask request object
216
-
225
+
217
226
  Returns:
218
227
  str: Request path
219
228
  """
220
229
  return request.path
221
-
230
+
222
231
  def _get_required_permissions(self, request: Request) -> List[str]:
223
232
  """
224
233
  Get required permissions for Flask request.
225
-
234
+
226
235
  This method extracts required permissions from the Flask request,
227
236
  typically from route decorators or request context.
228
-
237
+
229
238
  Args:
230
239
  request (Request): Flask request object
231
-
240
+
232
241
  Returns:
233
242
  List[str]: List of required permissions
234
243
  """
235
244
  # Try to get permissions from request context
236
- if hasattr(request, 'required_permissions'):
245
+ if hasattr(request, "required_permissions"):
237
246
  return request.required_permissions
238
-
247
+
239
248
  # Try to get permissions from route decorators
240
- if hasattr(request, 'endpoint'):
249
+ if hasattr(request, "endpoint"):
241
250
  # Check if endpoint has permission decorators
242
251
  endpoint = request.endpoint
243
- if hasattr(endpoint, 'required_permissions') and endpoint.required_permissions is not None:
252
+ if (
253
+ hasattr(endpoint, "required_permissions")
254
+ and endpoint.required_permissions is not None
255
+ ):
244
256
  return endpoint.required_permissions
245
257
  # Check for permission decorators
246
- if hasattr(endpoint, '__permissions__') and endpoint.__permissions__ is not None:
258
+ if (
259
+ hasattr(endpoint, "__permissions__")
260
+ and endpoint.__permissions__ is not None
261
+ ):
247
262
  return endpoint.__permissions__
248
263
  # Check for role-based decorators
249
- if hasattr(endpoint, 'required_roles') and endpoint.required_roles is not None:
264
+ if (
265
+ hasattr(endpoint, "required_roles")
266
+ and endpoint.required_roles is not None
267
+ ):
250
268
  return endpoint.required_roles
251
-
269
+
252
270
  # Default: no specific permissions required
253
271
  return []
254
-
272
+
255
273
  def _try_auth_method(self, request: Request, method: str) -> AuthResult:
256
274
  """
257
275
  Try authentication using specific method with Flask request.
258
-
276
+
259
277
  This method attempts to authenticate the Flask request using
260
278
  the specified authentication method.
261
-
279
+
262
280
  Args:
263
281
  request (Request): Flask request object
264
282
  method (str): Authentication method to try
265
-
283
+
266
284
  Returns:
267
285
  AuthResult: Authentication result
268
286
  """
@@ -283,13 +301,13 @@ class FlaskSecurityMiddleware(SecurityMiddleware):
283
301
  roles=[],
284
302
  auth_method=None,
285
303
  error_code=-32022,
286
- error_message=f"Unsupported authentication method: {method}"
304
+ error_message=f"Unsupported authentication method: {method}",
287
305
  )
288
306
  except Exception as e:
289
307
  self.logger.error(
290
308
  f"Authentication method {method} failed",
291
309
  extra={"error": str(e)},
292
- exc_info=True
310
+ exc_info=True,
293
311
  )
294
312
  return AuthResult(
295
313
  is_valid=False,
@@ -298,16 +316,16 @@ class FlaskSecurityMiddleware(SecurityMiddleware):
298
316
  roles=[],
299
317
  auth_method=None,
300
318
  error_code=-32023,
301
- error_message=f"Authentication method {method} failed: {str(e)}"
319
+ error_message=f"Authentication method {method} failed: {str(e)}",
302
320
  )
303
-
321
+
304
322
  def _try_api_key_auth(self, request: Request) -> AuthResult:
305
323
  """
306
324
  Try API key authentication with Flask request.
307
-
325
+
308
326
  Args:
309
327
  request (Request): Flask request object
310
-
328
+
311
329
  Returns:
312
330
  AuthResult: Authentication result
313
331
  """
@@ -318,7 +336,7 @@ class FlaskSecurityMiddleware(SecurityMiddleware):
318
336
  auth_header = request.headers.get("Authorization")
319
337
  if auth_header and auth_header.startswith("Bearer "):
320
338
  api_key = auth_header[7:] # Remove "Bearer " prefix
321
-
339
+
322
340
  if not api_key:
323
341
  return AuthResult(
324
342
  is_valid=False,
@@ -327,19 +345,19 @@ class FlaskSecurityMiddleware(SecurityMiddleware):
327
345
  roles=[],
328
346
  auth_method=AuthMethod.API_KEY,
329
347
  error_code=-32024,
330
- error_message="API key not found in request"
348
+ error_message="API key not found in request",
331
349
  )
332
-
350
+
333
351
  # Authenticate using security manager
334
352
  return self.security_manager.auth_manager.authenticate_api_key(api_key)
335
-
353
+
336
354
  def _try_jwt_auth(self, request: Request) -> AuthResult:
337
355
  """
338
356
  Try JWT authentication with Flask request.
339
-
357
+
340
358
  Args:
341
359
  request (Request): Flask request object
342
-
360
+
343
361
  Returns:
344
362
  AuthResult: Authentication result
345
363
  """
@@ -353,28 +371,28 @@ class FlaskSecurityMiddleware(SecurityMiddleware):
353
371
  roles=[],
354
372
  auth_method=AuthMethod.JWT,
355
373
  error_code=-32025,
356
- error_message="JWT token not found in Authorization header"
374
+ error_message="JWT token not found in Authorization header",
357
375
  )
358
-
376
+
359
377
  token = auth_header[7:] # Remove "Bearer " prefix
360
-
378
+
361
379
  # Authenticate using security manager
362
380
  return self.security_manager.auth_manager.authenticate_jwt_token(token)
363
-
381
+
364
382
  def _try_certificate_auth(self, request: Request) -> AuthResult:
365
383
  """
366
384
  Try certificate authentication with Flask request.
367
-
385
+
368
386
  Args:
369
387
  request (Request): Flask request object
370
-
388
+
371
389
  Returns:
372
390
  AuthResult: Authentication result
373
391
  """
374
392
  # For certificate authentication, we would typically need
375
393
  # to access the client certificate from the SSL context
376
394
  # This is more complex and depends on the SSL configuration
377
-
395
+
378
396
  # For now, return not implemented
379
397
  return AuthResult(
380
398
  is_valid=False,
@@ -383,16 +401,16 @@ class FlaskSecurityMiddleware(SecurityMiddleware):
383
401
  roles=[],
384
402
  auth_method=AuthMethod.CERTIFICATE,
385
403
  error_code=-32026,
386
- error_message="Certificate authentication not implemented"
404
+ error_message="Certificate authentication not implemented",
387
405
  )
388
-
406
+
389
407
  def _try_basic_auth(self, request: Request) -> AuthResult:
390
408
  """
391
409
  Try basic authentication with Flask request.
392
-
410
+
393
411
  Args:
394
412
  request (Request): Flask request object
395
-
413
+
396
414
  Returns:
397
415
  AuthResult: Authentication result
398
416
  """
@@ -406,9 +424,9 @@ class FlaskSecurityMiddleware(SecurityMiddleware):
406
424
  roles=[],
407
425
  auth_method=AuthMethod.BASIC,
408
426
  error_code=-32027,
409
- error_message="Basic authentication credentials not found"
427
+ error_message="Basic authentication credentials not found",
410
428
  )
411
-
429
+
412
430
  # Basic auth implementation would go here
413
431
  # For now, return not implemented
414
432
  return AuthResult(
@@ -418,74 +436,80 @@ class FlaskSecurityMiddleware(SecurityMiddleware):
418
436
  roles=[],
419
437
  auth_method=AuthMethod.BASIC,
420
438
  error_code=-32028,
421
- error_message="Basic authentication not implemented"
439
+ error_message="Basic authentication not implemented",
422
440
  )
423
-
424
- def _apply_security_headers(self, response: Response, headers: Dict[str, str]) -> None:
441
+
442
+ def _apply_security_headers(
443
+ self, response: Response, headers: Dict[str, str]
444
+ ) -> None:
425
445
  """
426
446
  Apply security headers to Flask response.
427
-
447
+
428
448
  Args:
429
449
  response (Response): Flask response object
430
450
  headers (Dict[str, str]): Headers to apply
431
451
  """
432
452
  for header_name, header_value in headers.items():
433
453
  response.headers[header_name] = header_value
434
-
454
+
435
455
  def _create_error_response(self, status_code: int, message: str) -> Response:
436
456
  """
437
457
  Create error response for security violations.
438
-
458
+
439
459
  Args:
440
460
  status_code (int): HTTP status code
441
461
  message (str): Error message
442
-
462
+
443
463
  Returns:
444
464
  Response: Flask error response
445
465
  """
446
466
  return make_response(
447
- jsonify({
448
- "error": "Security violation",
449
- "message": message,
450
- "error_code": -32029
451
- }),
452
- status_code
467
+ jsonify(
468
+ {
469
+ "error": "Security violation",
470
+ "message": message,
471
+ "error_code": -32029,
472
+ }
473
+ ),
474
+ status_code,
453
475
  )
454
-
476
+
455
477
  def _rate_limit_response(self, start_response) -> List[bytes]:
456
478
  """
457
479
  Create rate limit exceeded response.
458
-
480
+
459
481
  Args:
460
482
  start_response: WSGI start_response callable
461
-
483
+
462
484
  Returns:
463
485
  List[bytes]: WSGI response body
464
486
  """
465
487
  response_data = {
466
488
  "error": "Rate limit exceeded",
467
489
  "message": "Too many requests, please try again later",
468
- "error_code": -32030
490
+ "error_code": -32030,
469
491
  }
470
-
471
- response_body = json.dumps(response_data).encode('utf-8')
492
+
493
+ response_body = json.dumps(response_data).encode("utf-8")
472
494
  headers = [
473
- ('Content-Type', 'application/json'),
474
- ('Content-Length', str(len(response_body))),
475
- ('Retry-After', str(self.config.rate_limit.window_size_seconds))
495
+ ("Content-Type", "application/json"),
496
+ ("Content-Length", str(len(response_body))),
497
+ ("Retry-After", str(self.config.rate_limit.window_size_seconds)),
476
498
  ]
477
-
478
- start_response('429 Too Many Requests', headers)
499
+
500
+ start_response("429 Too Many Requests", headers)
479
501
  return [response_body]
480
-
481
- def _auth_error_response(self, auth_result: AuthResult, start_response) -> List[bytes]:
502
+
503
+ def _auth_error_response(
504
+ self, auth_result: AuthResult, start_response
505
+ ) -> List[bytes]:
482
506
  """
483
507
  Create authentication error response.
484
-
508
+
485
509
  Args:
486
510
  auth_result (AuthResult): Authentication result
487
511
  start_response: WSGI start_response callable
488
-
512
+
489
513
  Returns:
490
514
  List[bytes]: WSGI response body
491
515
  """
@@ -493,51 +517,51 @@ class FlaskSecurityMiddleware(SecurityMiddleware):
493
517
  "error": "Authentication failed",
494
518
  "message": auth_result.error_message or "Invalid credentials",
495
519
  "error_code": auth_result.error_code,
496
- "auth_method": auth_result.auth_method
520
+ "auth_method": auth_result.auth_method,
497
521
  }
498
-
499
- response_body = json.dumps(response_data).encode('utf-8')
522
+
523
+ response_body = json.dumps(response_data).encode("utf-8")
500
524
  headers = [
501
- ('Content-Type', 'application/json'),
502
- ('Content-Length', str(len(response_body))),
503
- ('WWW-Authenticate', 'Bearer, ApiKey')
525
+ ("Content-Type", "application/json"),
526
+ ("Content-Length", str(len(response_body))),
527
+ ("WWW-Authenticate", "Bearer, ApiKey"),
504
528
  ]
505
-
506
- start_response('401 Unauthorized', headers)
529
+
530
+ start_response("401 Unauthorized", headers)
507
531
  return [response_body]
508
-
532
+
509
533
  def _permission_error_response(self, start_response) -> List[bytes]:
510
534
  """
511
535
  Create permission denied response.
512
-
536
+
513
537
  Args:
514
538
  start_response: WSGI start_response callable
515
-
539
+
516
540
  Returns:
517
541
  List[bytes]: WSGI response body
518
542
  """
519
543
  response_data = {
520
544
  "error": "Permission denied",
521
545
  "message": "Insufficient permissions to access this resource",
522
- "error_code": -32031
546
+ "error_code": -32031,
523
547
  }
524
-
525
- response_body = json.dumps(response_data).encode('utf-8')
548
+
549
+ response_body = json.dumps(response_data).encode("utf-8")
526
550
  headers = [
527
- ('Content-Type', 'application/json'),
528
- ('Content-Length', str(len(response_body)))
551
+ ("Content-Type", "application/json"),
552
+ ("Content-Length", str(len(response_body))),
529
553
  ]
530
-
531
- start_response('403 Forbidden', headers)
554
+
555
+ start_response("403 Forbidden", headers)
532
556
  return [response_body]
533
-
557
+
534
558
  def _get_client_ip(self, request: Request) -> str:
535
559
  """
536
560
  Get client IP address from Flask request.
537
-
561
+
538
562
  Args:
539
563
  request (Request): Flask request object
540
-
564
+
541
565
  Returns:
542
566
  str: Client IP address
543
567
  """
@@ -546,46 +570,48 @@ class FlaskSecurityMiddleware(SecurityMiddleware):
546
570
  if forwarded_for:
547
571
  # Take the first IP in the chain
548
572
  return forwarded_for.split(",")[0].strip()
549
-
573
+
550
574
  # Try to get IP from X-Real-IP header
551
575
  real_ip = request.headers.get("X-Real-IP")
552
576
  if real_ip:
553
577
  return real_ip
554
-
578
+
555
579
  # Fall back to remote address
556
580
  if request.remote_addr:
557
581
  return request.remote_addr
558
-
582
+
559
583
  # Default fallback
560
584
  # Fallback to default IP from config or environment
561
- default_ip = getattr(self.config, 'default_client_ip', None)
585
+ default_ip = getattr(self.config, "default_client_ip", None)
562
586
  if default_ip:
563
587
  return default_ip
564
-
588
+
565
589
  # Use environment variable or default
566
590
  import os
591
+
567
592
  from ..constants import DEFAULT_CLIENT_IP
568
- return os.environ.get('DEFAULT_CLIENT_IP', DEFAULT_CLIENT_IP)
569
-
593
+
594
+ return os.environ.get("DEFAULT_CLIENT_IP", DEFAULT_CLIENT_IP)
595
+
570
596
  def _get_security_headers(self) -> List[tuple]:
571
597
  """
572
598
  Get security headers to add to responses.
573
-
599
+
574
600
  Returns:
575
601
  List[tuple]: List of (header_name, header_value) tuples
576
602
  """
577
603
  headers = [
578
- ('X-Content-Type-Options', 'nosniff'),
579
- ('X-Frame-Options', 'DENY'),
580
- ('X-XSS-Protection', '1; mode=block'),
581
- ('Strict-Transport-Security', 'max-age=31536000; includeSubDomains'),
582
- ('Content-Security-Policy', 'default-src \'self\''),
583
- ('Referrer-Policy', 'strict-origin-when-cross-origin')
604
+ ("X-Content-Type-Options", "nosniff"),
605
+ ("X-Frame-Options", "DENY"),
606
+ ("X-XSS-Protection", "1; mode=block"),
607
+ ("Strict-Transport-Security", "max-age=31536000; includeSubDomains"),
608
+ ("Content-Security-Policy", "default-src 'self'"),
609
+ ("Referrer-Policy", "strict-origin-when-cross-origin"),
584
610
  ]
585
-
611
+
586
612
  # Add custom security headers from config
587
613
  if self.config.auth and self.config.auth.security_headers:
588
614
  for header_name, header_value in self.config.auth.security_headers.items():
589
615
  headers.append((header_name, header_value))
590
-
616
+
591
617
  return headers