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