mcp-proxy-adapter 6.0.0__py3-none-any.whl → 6.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.
- mcp_proxy_adapter/api/app.py +174 -80
- mcp_proxy_adapter/api/handlers.py +16 -5
- mcp_proxy_adapter/api/middleware/__init__.py +9 -4
- mcp_proxy_adapter/api/middleware/command_permission_middleware.py +148 -0
- mcp_proxy_adapter/api/middleware/factory.py +36 -12
- mcp_proxy_adapter/api/middleware/protocol_middleware.py +32 -13
- mcp_proxy_adapter/api/middleware/unified_security.py +160 -0
- mcp_proxy_adapter/api/middleware/user_info_middleware.py +83 -0
- mcp_proxy_adapter/commands/__init__.py +7 -1
- mcp_proxy_adapter/commands/base.py +7 -4
- mcp_proxy_adapter/commands/builtin_commands.py +8 -2
- mcp_proxy_adapter/commands/command_registry.py +8 -0
- mcp_proxy_adapter/commands/echo_command.py +81 -0
- mcp_proxy_adapter/commands/help_command.py +21 -14
- mcp_proxy_adapter/commands/proxy_registration_command.py +326 -185
- mcp_proxy_adapter/commands/role_test_command.py +141 -0
- mcp_proxy_adapter/commands/security_command.py +488 -0
- mcp_proxy_adapter/commands/ssl_setup_command.py +2 -2
- mcp_proxy_adapter/commands/token_management_command.py +1 -1
- mcp_proxy_adapter/config.py +81 -21
- mcp_proxy_adapter/core/app_factory.py +326 -0
- mcp_proxy_adapter/core/client_security.py +384 -0
- mcp_proxy_adapter/core/logging.py +8 -3
- mcp_proxy_adapter/core/mtls_asgi.py +156 -0
- mcp_proxy_adapter/core/mtls_asgi_app.py +187 -0
- mcp_proxy_adapter/core/protocol_manager.py +139 -8
- mcp_proxy_adapter/core/proxy_client.py +602 -0
- mcp_proxy_adapter/core/proxy_registration.py +299 -47
- mcp_proxy_adapter/core/security_adapter.py +12 -15
- mcp_proxy_adapter/core/security_integration.py +285 -0
- mcp_proxy_adapter/core/server_adapter.py +345 -0
- mcp_proxy_adapter/core/server_engine.py +364 -0
- mcp_proxy_adapter/core/unified_config_adapter.py +579 -0
- mcp_proxy_adapter/docs/EN/TROUBLESHOOTING.md +285 -0
- mcp_proxy_adapter/docs/RU/TROUBLESHOOTING.md +285 -0
- mcp_proxy_adapter/examples/README.md +230 -97
- mcp_proxy_adapter/examples/README_EN.md +258 -0
- mcp_proxy_adapter/examples/SECURITY_TESTING.md +455 -0
- mcp_proxy_adapter/examples/basic_framework/configs/http_auth.json +37 -0
- mcp_proxy_adapter/examples/basic_framework/configs/http_simple.json +23 -0
- mcp_proxy_adapter/examples/basic_framework/configs/https_auth.json +43 -0
- mcp_proxy_adapter/examples/basic_framework/configs/https_no_protocol_middleware.json +36 -0
- mcp_proxy_adapter/examples/basic_framework/configs/https_simple.json +29 -0
- mcp_proxy_adapter/examples/basic_framework/configs/mtls_no_protocol_middleware.json +34 -0
- mcp_proxy_adapter/examples/basic_framework/configs/mtls_no_roles.json +39 -0
- mcp_proxy_adapter/examples/basic_framework/configs/mtls_simple.json +35 -0
- mcp_proxy_adapter/examples/basic_framework/configs/mtls_with_roles.json +45 -0
- mcp_proxy_adapter/examples/basic_framework/main.py +63 -0
- mcp_proxy_adapter/examples/basic_framework/roles.json +21 -0
- mcp_proxy_adapter/examples/cert_config.json +9 -0
- mcp_proxy_adapter/examples/certs/admin.crt +32 -0
- mcp_proxy_adapter/examples/certs/admin.key +52 -0
- mcp_proxy_adapter/examples/certs/admin_cert.pem +21 -0
- mcp_proxy_adapter/examples/certs/admin_key.pem +28 -0
- mcp_proxy_adapter/examples/certs/ca_cert.pem +23 -0
- mcp_proxy_adapter/examples/certs/ca_cert.srl +1 -0
- mcp_proxy_adapter/examples/certs/ca_key.pem +28 -0
- mcp_proxy_adapter/examples/certs/cert_config.json +9 -0
- mcp_proxy_adapter/examples/certs/client.crt +32 -0
- mcp_proxy_adapter/examples/certs/client.key +52 -0
- mcp_proxy_adapter/examples/certs/client_admin.crt +32 -0
- mcp_proxy_adapter/examples/certs/client_admin.key +52 -0
- mcp_proxy_adapter/examples/certs/client_user.crt +32 -0
- mcp_proxy_adapter/examples/certs/client_user.key +52 -0
- mcp_proxy_adapter/examples/certs/guest_cert.pem +21 -0
- mcp_proxy_adapter/examples/certs/guest_key.pem +28 -0
- mcp_proxy_adapter/examples/certs/mcp_proxy_adapter_ca_ca.crt +23 -0
- mcp_proxy_adapter/examples/certs/proxy_cert.pem +21 -0
- mcp_proxy_adapter/examples/certs/proxy_key.pem +28 -0
- mcp_proxy_adapter/examples/certs/readonly.crt +32 -0
- mcp_proxy_adapter/examples/certs/readonly.key +52 -0
- mcp_proxy_adapter/examples/certs/readonly_cert.pem +21 -0
- mcp_proxy_adapter/examples/certs/readonly_key.pem +28 -0
- mcp_proxy_adapter/examples/certs/server.crt +32 -0
- mcp_proxy_adapter/examples/certs/server.key +52 -0
- mcp_proxy_adapter/examples/certs/server_cert.pem +32 -0
- mcp_proxy_adapter/examples/certs/server_key.pem +52 -0
- mcp_proxy_adapter/examples/certs/test_ca_ca.crt +20 -0
- mcp_proxy_adapter/examples/certs/user.crt +32 -0
- mcp_proxy_adapter/examples/certs/user.key +52 -0
- mcp_proxy_adapter/examples/certs/user_cert.pem +21 -0
- mcp_proxy_adapter/examples/certs/user_key.pem +28 -0
- mcp_proxy_adapter/examples/client_configs/api_key_client.json +13 -0
- mcp_proxy_adapter/examples/client_configs/basic_auth_client.json +13 -0
- mcp_proxy_adapter/examples/client_configs/certificate_client.json +22 -0
- mcp_proxy_adapter/examples/client_configs/jwt_client.json +15 -0
- mcp_proxy_adapter/examples/client_configs/no_auth_client.json +9 -0
- mcp_proxy_adapter/examples/commands/__init__.py +1 -0
- mcp_proxy_adapter/examples/create_certificates_simple.py +307 -0
- mcp_proxy_adapter/examples/debug_request_state.py +144 -0
- mcp_proxy_adapter/examples/debug_role_chain.py +205 -0
- mcp_proxy_adapter/examples/demo_client.py +341 -0
- mcp_proxy_adapter/examples/full_application/commands/custom_echo_command.py +99 -0
- mcp_proxy_adapter/examples/full_application/commands/dynamic_calculator_command.py +106 -0
- mcp_proxy_adapter/examples/full_application/configs/http_auth.json +37 -0
- mcp_proxy_adapter/examples/full_application/configs/http_simple.json +23 -0
- mcp_proxy_adapter/examples/full_application/configs/https_auth.json +39 -0
- mcp_proxy_adapter/examples/full_application/configs/https_simple.json +25 -0
- mcp_proxy_adapter/examples/full_application/configs/mtls_no_roles.json +39 -0
- mcp_proxy_adapter/examples/full_application/configs/mtls_with_roles.json +45 -0
- mcp_proxy_adapter/examples/full_application/hooks/application_hooks.py +97 -0
- mcp_proxy_adapter/examples/full_application/hooks/builtin_command_hooks.py +95 -0
- mcp_proxy_adapter/examples/full_application/main.py +138 -0
- mcp_proxy_adapter/examples/full_application/roles.json +21 -0
- mcp_proxy_adapter/examples/generate_all_certificates.py +429 -0
- mcp_proxy_adapter/examples/generate_certificates.py +121 -0
- mcp_proxy_adapter/examples/keys/ca_key.pem +28 -0
- mcp_proxy_adapter/examples/keys/mcp_proxy_adapter_ca_ca.key +28 -0
- mcp_proxy_adapter/examples/keys/test_ca_ca.key +28 -0
- mcp_proxy_adapter/examples/logs/mcp_proxy_adapter.log +220 -0
- mcp_proxy_adapter/examples/logs/mcp_proxy_adapter.log.1 +1 -0
- mcp_proxy_adapter/examples/logs/mcp_proxy_adapter.log.2 +1 -0
- mcp_proxy_adapter/examples/logs/mcp_proxy_adapter.log.3 +1 -0
- mcp_proxy_adapter/examples/logs/mcp_proxy_adapter.log.4 +1 -0
- mcp_proxy_adapter/examples/logs/mcp_proxy_adapter.log.5 +1 -0
- mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_access.log +220 -0
- mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_access.log.1 +1 -0
- mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_access.log.2 +1 -0
- mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_access.log.3 +1 -0
- mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_access.log.4 +1 -0
- mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_access.log.5 +1 -0
- mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_error.log +2 -0
- mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_error.log.1 +1 -0
- mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_error.log.2 +1 -0
- mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_error.log.3 +1 -0
- mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_error.log.4 +1 -0
- mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_error.log.5 +1 -0
- mcp_proxy_adapter/examples/proxy_registration_example.py +401 -0
- mcp_proxy_adapter/examples/roles.json +38 -0
- mcp_proxy_adapter/examples/run_example.py +81 -0
- mcp_proxy_adapter/examples/run_security_tests.py +326 -0
- mcp_proxy_adapter/examples/run_security_tests_fixed.py +300 -0
- mcp_proxy_adapter/examples/security_test_client.py +743 -0
- mcp_proxy_adapter/examples/server_configs/config_basic_http.json +204 -0
- mcp_proxy_adapter/examples/server_configs/config_http_token.json +238 -0
- mcp_proxy_adapter/examples/server_configs/config_https.json +215 -0
- mcp_proxy_adapter/examples/server_configs/config_https_token.json +231 -0
- mcp_proxy_adapter/examples/server_configs/config_mtls.json +215 -0
- mcp_proxy_adapter/examples/server_configs/config_proxy_registration.json +250 -0
- mcp_proxy_adapter/examples/server_configs/config_simple.json +46 -0
- mcp_proxy_adapter/examples/server_configs/roles.json +38 -0
- mcp_proxy_adapter/examples/test_config_generator.py +110 -0
- mcp_proxy_adapter/examples/test_examples.py +344 -0
- mcp_proxy_adapter/examples/universal_client.py +628 -0
- mcp_proxy_adapter/main.py +21 -10
- mcp_proxy_adapter/utils/config_generator.py +727 -0
- mcp_proxy_adapter/version.py +5 -2
- mcp_proxy_adapter-6.1.1.dist-info/METADATA +205 -0
- mcp_proxy_adapter-6.1.1.dist-info/RECORD +197 -0
- mcp_proxy_adapter-6.1.1.dist-info/entry_points.txt +2 -0
- {mcp_proxy_adapter-6.0.0.dist-info → mcp_proxy_adapter-6.1.1.dist-info}/licenses/LICENSE +2 -2
- mcp_proxy_adapter/api/middleware/auth.py +0 -146
- mcp_proxy_adapter/api/middleware/auth_adapter.py +0 -235
- mcp_proxy_adapter/api/middleware/mtls_adapter.py +0 -305
- mcp_proxy_adapter/api/middleware/mtls_middleware.py +0 -296
- mcp_proxy_adapter/api/middleware/rate_limit.py +0 -152
- mcp_proxy_adapter/api/middleware/rate_limit_adapter.py +0 -241
- mcp_proxy_adapter/api/middleware/roles_adapter.py +0 -365
- mcp_proxy_adapter/api/middleware/roles_middleware.py +0 -381
- mcp_proxy_adapter/api/middleware/security.py +0 -376
- mcp_proxy_adapter/api/middleware/token_auth_middleware.py +0 -261
- mcp_proxy_adapter/examples/__init__.py +0 -7
- mcp_proxy_adapter/examples/basic_server/README.md +0 -60
- mcp_proxy_adapter/examples/basic_server/__init__.py +0 -7
- mcp_proxy_adapter/examples/basic_server/basic_custom_settings.json +0 -39
- mcp_proxy_adapter/examples/basic_server/config.json +0 -70
- mcp_proxy_adapter/examples/basic_server/config_all_protocols.json +0 -54
- mcp_proxy_adapter/examples/basic_server/config_http.json +0 -70
- mcp_proxy_adapter/examples/basic_server/config_http_only.json +0 -52
- mcp_proxy_adapter/examples/basic_server/config_https.json +0 -58
- mcp_proxy_adapter/examples/basic_server/config_mtls.json +0 -58
- mcp_proxy_adapter/examples/basic_server/config_ssl.json +0 -46
- mcp_proxy_adapter/examples/basic_server/custom_settings_example.py +0 -238
- mcp_proxy_adapter/examples/basic_server/server.py +0 -114
- mcp_proxy_adapter/examples/custom_commands/README.md +0 -127
- mcp_proxy_adapter/examples/custom_commands/__init__.py +0 -27
- mcp_proxy_adapter/examples/custom_commands/advanced_hooks.py +0 -566
- mcp_proxy_adapter/examples/custom_commands/auto_commands/__init__.py +0 -6
- mcp_proxy_adapter/examples/custom_commands/auto_commands/auto_echo_command.py +0 -103
- mcp_proxy_adapter/examples/custom_commands/auto_commands/auto_info_command.py +0 -111
- mcp_proxy_adapter/examples/custom_commands/auto_commands/test_command.py +0 -105
- mcp_proxy_adapter/examples/custom_commands/catalog/commands/test_command.py +0 -129
- mcp_proxy_adapter/examples/custom_commands/config.json +0 -118
- mcp_proxy_adapter/examples/custom_commands/config_all_protocols.json +0 -46
- mcp_proxy_adapter/examples/custom_commands/config_https_only.json +0 -46
- mcp_proxy_adapter/examples/custom_commands/config_https_transport.json +0 -33
- mcp_proxy_adapter/examples/custom_commands/config_mtls_only.json +0 -46
- mcp_proxy_adapter/examples/custom_commands/config_mtls_transport.json +0 -33
- mcp_proxy_adapter/examples/custom_commands/config_single_transport.json +0 -33
- mcp_proxy_adapter/examples/custom_commands/custom_health_command.py +0 -169
- mcp_proxy_adapter/examples/custom_commands/custom_help_command.py +0 -215
- mcp_proxy_adapter/examples/custom_commands/custom_openapi_generator.py +0 -76
- mcp_proxy_adapter/examples/custom_commands/custom_settings.json +0 -96
- mcp_proxy_adapter/examples/custom_commands/custom_settings_manager.py +0 -241
- mcp_proxy_adapter/examples/custom_commands/data_transform_command.py +0 -135
- mcp_proxy_adapter/examples/custom_commands/echo_command.py +0 -122
- mcp_proxy_adapter/examples/custom_commands/full_help_response.json +0 -1
- mcp_proxy_adapter/examples/custom_commands/generated_openapi.json +0 -629
- mcp_proxy_adapter/examples/custom_commands/get_openapi.py +0 -103
- mcp_proxy_adapter/examples/custom_commands/hooks.py +0 -230
- mcp_proxy_adapter/examples/custom_commands/intercept_command.py +0 -123
- mcp_proxy_adapter/examples/custom_commands/loadable_commands/test_ignored.py +0 -129
- mcp_proxy_adapter/examples/custom_commands/manual_echo_command.py +0 -103
- mcp_proxy_adapter/examples/custom_commands/proxy_connection_manager.py +0 -278
- mcp_proxy_adapter/examples/custom_commands/server.py +0 -252
- mcp_proxy_adapter/examples/custom_commands/simple_openapi_server.py +0 -75
- mcp_proxy_adapter/examples/custom_commands/start_server_with_proxy_manager.py +0 -299
- mcp_proxy_adapter/examples/custom_commands/start_server_with_registration.py +0 -278
- mcp_proxy_adapter/examples/custom_commands/test_hooks.py +0 -176
- mcp_proxy_adapter/examples/custom_commands/test_openapi.py +0 -27
- mcp_proxy_adapter/examples/custom_commands/test_registry.py +0 -23
- mcp_proxy_adapter/examples/custom_commands/test_simple.py +0 -19
- mcp_proxy_adapter/examples/custom_project_example/README.md +0 -103
- mcp_proxy_adapter/examples/custom_project_example/README_EN.md +0 -103
- mcp_proxy_adapter/examples/deployment/README.md +0 -49
- mcp_proxy_adapter/examples/deployment/__init__.py +0 -7
- mcp_proxy_adapter/examples/deployment/config.development.json +0 -8
- mcp_proxy_adapter/examples/deployment/config.json +0 -29
- mcp_proxy_adapter/examples/deployment/config.production.json +0 -12
- mcp_proxy_adapter/examples/deployment/config.staging.json +0 -11
- mcp_proxy_adapter/examples/deployment/docker-compose.yml +0 -31
- mcp_proxy_adapter/examples/deployment/run.sh +0 -43
- mcp_proxy_adapter/examples/deployment/run_docker.sh +0 -84
- mcp_proxy_adapter/examples/simple_custom_commands/README.md +0 -149
- mcp_proxy_adapter/examples/simple_custom_commands/README_EN.md +0 -149
- mcp_proxy_adapter/schemas/base_schema.json +0 -114
- mcp_proxy_adapter/schemas/openapi_schema.json +0 -314
- mcp_proxy_adapter/schemas/roles_schema.json +0 -162
- mcp_proxy_adapter/tests/__init__.py +0 -0
- mcp_proxy_adapter/tests/api/__init__.py +0 -3
- mcp_proxy_adapter/tests/api/test_cmd_endpoint.py +0 -115
- mcp_proxy_adapter/tests/api/test_custom_openapi.py +0 -617
- mcp_proxy_adapter/tests/api/test_handlers.py +0 -522
- mcp_proxy_adapter/tests/api/test_middleware.py +0 -340
- mcp_proxy_adapter/tests/api/test_schemas.py +0 -546
- mcp_proxy_adapter/tests/api/test_tool_integration.py +0 -531
- mcp_proxy_adapter/tests/commands/__init__.py +0 -3
- mcp_proxy_adapter/tests/commands/test_config_command.py +0 -211
- mcp_proxy_adapter/tests/commands/test_echo_command.py +0 -127
- mcp_proxy_adapter/tests/commands/test_help_command.py +0 -136
- mcp_proxy_adapter/tests/conftest.py +0 -131
- mcp_proxy_adapter/tests/functional/__init__.py +0 -3
- mcp_proxy_adapter/tests/functional/test_api.py +0 -253
- mcp_proxy_adapter/tests/integration/__init__.py +0 -3
- mcp_proxy_adapter/tests/integration/test_cmd_integration.py +0 -129
- mcp_proxy_adapter/tests/integration/test_integration.py +0 -255
- mcp_proxy_adapter/tests/performance/__init__.py +0 -3
- mcp_proxy_adapter/tests/performance/test_performance.py +0 -189
- mcp_proxy_adapter/tests/stubs/__init__.py +0 -10
- mcp_proxy_adapter/tests/stubs/echo_command.py +0 -104
- mcp_proxy_adapter/tests/test_api_endpoints.py +0 -271
- mcp_proxy_adapter/tests/test_api_handlers.py +0 -289
- mcp_proxy_adapter/tests/test_base_command.py +0 -123
- mcp_proxy_adapter/tests/test_batch_requests.py +0 -117
- mcp_proxy_adapter/tests/test_command_registry.py +0 -281
- mcp_proxy_adapter/tests/test_config.py +0 -127
- mcp_proxy_adapter/tests/test_utils.py +0 -65
- mcp_proxy_adapter/tests/unit/__init__.py +0 -3
- mcp_proxy_adapter/tests/unit/test_base_command.py +0 -436
- mcp_proxy_adapter/tests/unit/test_config.py +0 -270
- mcp_proxy_adapter-6.0.0.dist-info/METADATA +0 -201
- mcp_proxy_adapter-6.0.0.dist-info/RECORD +0 -179
- {mcp_proxy_adapter-6.0.0.dist-info → mcp_proxy_adapter-6.1.1.dist-info}/WHEEL +0 -0
- {mcp_proxy_adapter-6.0.0.dist-info → mcp_proxy_adapter-6.1.1.dist-info}/top_level.txt +0 -0
@@ -1,296 +0,0 @@
|
|
1
|
-
"""
|
2
|
-
mTLS Middleware
|
3
|
-
|
4
|
-
This module provides middleware for mutual TLS (mTLS) authentication.
|
5
|
-
Extracts and validates client certificates, extracts roles, and validates access.
|
6
|
-
|
7
|
-
Author: MCP Proxy Adapter Team
|
8
|
-
Version: 1.0.0
|
9
|
-
"""
|
10
|
-
|
11
|
-
import logging
|
12
|
-
from typing import Dict, List, Optional, Any
|
13
|
-
from cryptography import x509
|
14
|
-
from cryptography.hazmat.primitives import serialization
|
15
|
-
|
16
|
-
from fastapi import Request, Response
|
17
|
-
from starlette.middleware.base import BaseHTTPMiddleware
|
18
|
-
|
19
|
-
from ...core.auth_validator import AuthValidator
|
20
|
-
from ...core.role_utils import RoleUtils
|
21
|
-
from ...core.certificate_utils import CertificateUtils
|
22
|
-
from .base import BaseMiddleware
|
23
|
-
|
24
|
-
logger = logging.getLogger(__name__)
|
25
|
-
|
26
|
-
|
27
|
-
class MTLSMiddleware(BaseMiddleware):
|
28
|
-
"""
|
29
|
-
Middleware for mTLS authentication.
|
30
|
-
|
31
|
-
Extracts client certificates from requests, validates them against CA,
|
32
|
-
extracts roles, and validates access based on configuration.
|
33
|
-
"""
|
34
|
-
|
35
|
-
def __init__(self, app, mtls_config: Dict[str, Any]):
|
36
|
-
"""
|
37
|
-
Initialize mTLS middleware.
|
38
|
-
|
39
|
-
Args:
|
40
|
-
app: FastAPI application
|
41
|
-
mtls_config: mTLS configuration dictionary
|
42
|
-
"""
|
43
|
-
super().__init__(app)
|
44
|
-
self.mtls_config = mtls_config
|
45
|
-
self.auth_validator = AuthValidator()
|
46
|
-
self.role_utils = RoleUtils()
|
47
|
-
self.certificate_utils = CertificateUtils()
|
48
|
-
|
49
|
-
# Extract configuration
|
50
|
-
self.enabled = mtls_config.get("enabled", False)
|
51
|
-
self.ca_cert_path = mtls_config.get("ca_cert")
|
52
|
-
self.verify_client = mtls_config.get("verify_client", True)
|
53
|
-
self.client_cert_required = mtls_config.get("client_cert_required", True)
|
54
|
-
self.allowed_roles = mtls_config.get("allowed_roles", [])
|
55
|
-
self.require_roles = mtls_config.get("require_roles", False)
|
56
|
-
|
57
|
-
logger.info(f"mTLS middleware initialized: enabled={self.enabled}, "
|
58
|
-
f"verify_client={self.verify_client}, "
|
59
|
-
f"client_cert_required={self.client_cert_required}")
|
60
|
-
|
61
|
-
async def before_request(self, request: Request) -> None:
|
62
|
-
"""
|
63
|
-
Process request before calling the main handler.
|
64
|
-
|
65
|
-
Args:
|
66
|
-
request: FastAPI request object
|
67
|
-
"""
|
68
|
-
if not self.enabled:
|
69
|
-
return
|
70
|
-
|
71
|
-
try:
|
72
|
-
# Extract client certificate
|
73
|
-
client_cert = self._extract_client_certificate(request)
|
74
|
-
|
75
|
-
if client_cert is None:
|
76
|
-
if self.client_cert_required:
|
77
|
-
raise ValueError("Client certificate is required but not provided")
|
78
|
-
return
|
79
|
-
|
80
|
-
# Validate client certificate
|
81
|
-
if not self._validate_client_certificate(client_cert):
|
82
|
-
raise ValueError("Client certificate validation failed")
|
83
|
-
|
84
|
-
# Extract roles from certificate
|
85
|
-
roles = self._extract_roles_from_certificate(client_cert)
|
86
|
-
|
87
|
-
# Validate access based on roles
|
88
|
-
if self.require_roles and not self._validate_access(roles):
|
89
|
-
raise ValueError("Access denied: insufficient roles")
|
90
|
-
|
91
|
-
# Store certificate and roles in request state
|
92
|
-
request.state.client_certificate = client_cert
|
93
|
-
request.state.client_roles = roles
|
94
|
-
request.state.client_common_name = self._get_common_name(client_cert)
|
95
|
-
|
96
|
-
logger.debug(f"mTLS authentication successful for {request.state.client_common_name} "
|
97
|
-
f"with roles: {roles}")
|
98
|
-
|
99
|
-
except Exception as e:
|
100
|
-
logger.error(f"mTLS authentication failed: {e}")
|
101
|
-
raise
|
102
|
-
|
103
|
-
def _extract_client_certificate(self, request: Request) -> Optional[x509.Certificate]:
|
104
|
-
"""
|
105
|
-
Extract client certificate from request.
|
106
|
-
|
107
|
-
Args:
|
108
|
-
request: FastAPI request object
|
109
|
-
|
110
|
-
Returns:
|
111
|
-
Client certificate object or None if not found
|
112
|
-
"""
|
113
|
-
try:
|
114
|
-
# Check if client certificate is available in SSL context
|
115
|
-
if hasattr(request, 'scope') and 'ssl' in request.scope:
|
116
|
-
ssl_context = request.scope['ssl']
|
117
|
-
if hasattr(ssl_context, 'getpeercert'):
|
118
|
-
cert_data = ssl_context.getpeercert(binary_form=True)
|
119
|
-
if cert_data:
|
120
|
-
return x509.load_der_x509_certificate(cert_data)
|
121
|
-
|
122
|
-
# Check for certificate in headers (for proxy scenarios)
|
123
|
-
cert_header = request.headers.get('ssl-client-cert')
|
124
|
-
if cert_header:
|
125
|
-
# Remove header prefix if present
|
126
|
-
if cert_header.startswith('-----BEGIN CERTIFICATE-----'):
|
127
|
-
cert_data = cert_header.encode('utf-8')
|
128
|
-
else:
|
129
|
-
# Assume it's base64 encoded
|
130
|
-
import base64
|
131
|
-
cert_data = base64.b64decode(cert_header)
|
132
|
-
|
133
|
-
return x509.load_pem_x509_certificate(cert_data)
|
134
|
-
|
135
|
-
return None
|
136
|
-
|
137
|
-
except Exception as e:
|
138
|
-
logger.error(f"Failed to extract client certificate: {e}")
|
139
|
-
return None
|
140
|
-
|
141
|
-
def _validate_client_certificate(self, cert: x509.Certificate) -> bool:
|
142
|
-
"""
|
143
|
-
Validate client certificate.
|
144
|
-
|
145
|
-
Args:
|
146
|
-
cert: Client certificate object
|
147
|
-
|
148
|
-
Returns:
|
149
|
-
True if certificate is valid, False otherwise
|
150
|
-
"""
|
151
|
-
try:
|
152
|
-
if not self.verify_client:
|
153
|
-
return True
|
154
|
-
|
155
|
-
# Convert certificate to PEM format for validation
|
156
|
-
cert_pem = cert.public_bytes(serialization.Encoding.PEM)
|
157
|
-
|
158
|
-
# Use AuthValidator to validate certificate
|
159
|
-
result = self.auth_validator.validate_certificate_data(cert_pem)
|
160
|
-
if not result.is_valid:
|
161
|
-
logger.warning(f"Certificate validation failed: {result.error_message}")
|
162
|
-
return False
|
163
|
-
|
164
|
-
# Validate certificate chain if CA is provided
|
165
|
-
if self.ca_cert_path and self.ca_cert_path != "None":
|
166
|
-
# Create temporary file for certificate
|
167
|
-
import tempfile
|
168
|
-
import os
|
169
|
-
|
170
|
-
with tempfile.NamedTemporaryFile(mode='wb', suffix='.crt', delete=False) as f:
|
171
|
-
f.write(cert_pem)
|
172
|
-
temp_cert_path = f.name
|
173
|
-
|
174
|
-
try:
|
175
|
-
chain_valid = self.certificate_utils.validate_certificate_chain(
|
176
|
-
temp_cert_path, self.ca_cert_path
|
177
|
-
)
|
178
|
-
if not chain_valid:
|
179
|
-
logger.warning("Certificate chain validation failed")
|
180
|
-
return False
|
181
|
-
finally:
|
182
|
-
os.unlink(temp_cert_path)
|
183
|
-
|
184
|
-
return True
|
185
|
-
|
186
|
-
except Exception as e:
|
187
|
-
logger.error(f"Failed to validate client certificate: {e}")
|
188
|
-
return False
|
189
|
-
|
190
|
-
def _extract_roles_from_certificate(self, cert: x509.Certificate) -> List[str]:
|
191
|
-
"""
|
192
|
-
Extract roles from client certificate.
|
193
|
-
|
194
|
-
Args:
|
195
|
-
cert: Client certificate object
|
196
|
-
|
197
|
-
Returns:
|
198
|
-
List of roles extracted from certificate
|
199
|
-
"""
|
200
|
-
try:
|
201
|
-
return self.certificate_utils.extract_roles_from_certificate_object(cert)
|
202
|
-
except Exception as e:
|
203
|
-
logger.error(f"Failed to extract roles from certificate: {e}")
|
204
|
-
return []
|
205
|
-
|
206
|
-
def _validate_access(self, roles: List[str]) -> bool:
|
207
|
-
"""
|
208
|
-
Validate access based on roles.
|
209
|
-
|
210
|
-
Args:
|
211
|
-
roles: List of roles from client certificate
|
212
|
-
|
213
|
-
Returns:
|
214
|
-
True if access is allowed, False otherwise
|
215
|
-
"""
|
216
|
-
try:
|
217
|
-
if not self.allowed_roles:
|
218
|
-
return True
|
219
|
-
|
220
|
-
if not roles:
|
221
|
-
return False
|
222
|
-
|
223
|
-
# Check if any of the client roles match allowed roles
|
224
|
-
for client_role in roles:
|
225
|
-
for allowed_role in self.allowed_roles:
|
226
|
-
if self.role_utils.compare_roles(client_role, allowed_role):
|
227
|
-
return True
|
228
|
-
|
229
|
-
return False
|
230
|
-
|
231
|
-
except Exception as e:
|
232
|
-
logger.error(f"Failed to validate access: {e}")
|
233
|
-
return False
|
234
|
-
|
235
|
-
def _get_common_name(self, cert: x509.Certificate) -> str:
|
236
|
-
"""
|
237
|
-
Get common name from certificate.
|
238
|
-
|
239
|
-
Args:
|
240
|
-
cert: Certificate object
|
241
|
-
|
242
|
-
Returns:
|
243
|
-
Common name or empty string if not found
|
244
|
-
"""
|
245
|
-
try:
|
246
|
-
for name_attribute in cert.subject:
|
247
|
-
if name_attribute.oid == x509.NameOID.COMMON_NAME:
|
248
|
-
return str(name_attribute.value)
|
249
|
-
return ""
|
250
|
-
except Exception as e:
|
251
|
-
logger.error(f"Failed to get common name: {e}")
|
252
|
-
return ""
|
253
|
-
|
254
|
-
async def handle_error(self, request: Request, exception: Exception) -> Response:
|
255
|
-
"""
|
256
|
-
Handle mTLS authentication errors.
|
257
|
-
|
258
|
-
Args:
|
259
|
-
request: FastAPI request object
|
260
|
-
exception: Exception that occurred
|
261
|
-
|
262
|
-
Returns:
|
263
|
-
Error response
|
264
|
-
"""
|
265
|
-
from fastapi.responses import JSONResponse
|
266
|
-
|
267
|
-
error_message = str(exception)
|
268
|
-
|
269
|
-
if "certificate is required" in error_message.lower():
|
270
|
-
status_code = 401
|
271
|
-
error_code = -32009 # Certificate not found
|
272
|
-
elif "validation failed" in error_message.lower():
|
273
|
-
status_code = 401
|
274
|
-
error_code = -32003 # Certificate validation failed
|
275
|
-
elif "access denied" in error_message.lower():
|
276
|
-
status_code = 403
|
277
|
-
error_code = -32007 # Role validation failed
|
278
|
-
else:
|
279
|
-
status_code = 500
|
280
|
-
error_code = -32603 # Internal error
|
281
|
-
|
282
|
-
return JSONResponse(
|
283
|
-
status_code=status_code,
|
284
|
-
content={
|
285
|
-
"jsonrpc": "2.0",
|
286
|
-
"error": {
|
287
|
-
"code": error_code,
|
288
|
-
"message": error_message,
|
289
|
-
"data": {
|
290
|
-
"validation_type": "mtls",
|
291
|
-
"request_id": getattr(request.state, 'request_id', None)
|
292
|
-
}
|
293
|
-
},
|
294
|
-
"id": None
|
295
|
-
}
|
296
|
-
)
|
@@ -1,152 +0,0 @@
|
|
1
|
-
"""
|
2
|
-
Middleware for rate limiting.
|
3
|
-
"""
|
4
|
-
|
5
|
-
import time
|
6
|
-
from typing import Dict, List, Callable, Awaitable
|
7
|
-
from collections import defaultdict
|
8
|
-
|
9
|
-
from fastapi import Request, Response
|
10
|
-
from starlette.responses import JSONResponse
|
11
|
-
|
12
|
-
from mcp_proxy_adapter.core.logging import logger
|
13
|
-
from .base import BaseMiddleware
|
14
|
-
|
15
|
-
class RateLimitMiddleware(BaseMiddleware):
|
16
|
-
"""
|
17
|
-
Middleware for limiting request rate.
|
18
|
-
"""
|
19
|
-
|
20
|
-
def __init__(self, app, rate_limit: int = 100, time_window: int = 60,
|
21
|
-
by_ip: bool = True, by_user: bool = True,
|
22
|
-
public_paths: List[str] = None):
|
23
|
-
"""
|
24
|
-
Initializes middleware for rate limiting.
|
25
|
-
|
26
|
-
Args:
|
27
|
-
app: FastAPI application
|
28
|
-
rate_limit: Maximum number of requests in the specified time period
|
29
|
-
time_window: Time period in seconds
|
30
|
-
by_ip: Limit requests by IP address
|
31
|
-
by_user: Limit requests by user
|
32
|
-
public_paths: List of paths for which rate limiting is not applied
|
33
|
-
"""
|
34
|
-
super().__init__(app)
|
35
|
-
self.rate_limit = rate_limit
|
36
|
-
self.time_window = time_window
|
37
|
-
self.by_ip = by_ip
|
38
|
-
self.by_user = by_user
|
39
|
-
self.public_paths = public_paths or [
|
40
|
-
"/docs",
|
41
|
-
"/redoc",
|
42
|
-
"/openapi.json",
|
43
|
-
"/health"
|
44
|
-
]
|
45
|
-
|
46
|
-
# Storage for requests by IP
|
47
|
-
self.ip_requests = defaultdict(list)
|
48
|
-
|
49
|
-
# Storage for requests by user
|
50
|
-
self.user_requests = defaultdict(list)
|
51
|
-
|
52
|
-
async def dispatch(self, request: Request, call_next: Callable[[Request], Awaitable[Response]]) -> Response:
|
53
|
-
"""
|
54
|
-
Processes request and checks rate limit.
|
55
|
-
|
56
|
-
Args:
|
57
|
-
request: Request.
|
58
|
-
call_next: Next handler.
|
59
|
-
|
60
|
-
Returns:
|
61
|
-
Response.
|
62
|
-
"""
|
63
|
-
# Check if path is public
|
64
|
-
path = request.url.path
|
65
|
-
if self._is_public_path(path):
|
66
|
-
# If path is public, skip rate limiting
|
67
|
-
return await call_next(request)
|
68
|
-
|
69
|
-
# Current time
|
70
|
-
current_time = time.time()
|
71
|
-
|
72
|
-
# Get client IP address
|
73
|
-
client_ip = request.client.host if request.client else "unknown"
|
74
|
-
|
75
|
-
# Get user from request state (if any)
|
76
|
-
username = getattr(request.state, "username", None)
|
77
|
-
|
78
|
-
# Check limit by IP
|
79
|
-
if self.by_ip and client_ip != "unknown":
|
80
|
-
# Clean old requests
|
81
|
-
self._clean_old_requests(self.ip_requests[client_ip], current_time)
|
82
|
-
|
83
|
-
# Check number of requests
|
84
|
-
if len(self.ip_requests[client_ip]) >= self.rate_limit:
|
85
|
-
logger.warning(f"Rate limit exceeded for IP: {client_ip} | Path: {path}")
|
86
|
-
return self._create_error_response("Rate limit exceeded", 429)
|
87
|
-
|
88
|
-
# Add current request
|
89
|
-
self.ip_requests[client_ip].append(current_time)
|
90
|
-
|
91
|
-
# Check limit by user
|
92
|
-
if self.by_user and username:
|
93
|
-
# Clean old requests
|
94
|
-
self._clean_old_requests(self.user_requests[username], current_time)
|
95
|
-
|
96
|
-
# Check number of requests
|
97
|
-
if len(self.user_requests[username]) >= self.rate_limit:
|
98
|
-
logger.warning(f"Rate limit exceeded for user: {username} | Path: {path}")
|
99
|
-
return self._create_error_response("Rate limit exceeded", 429)
|
100
|
-
|
101
|
-
# Add current request
|
102
|
-
self.user_requests[username].append(current_time)
|
103
|
-
|
104
|
-
# Call the next middleware or main handler
|
105
|
-
return await call_next(request)
|
106
|
-
|
107
|
-
def _clean_old_requests(self, requests: List[float], current_time: float) -> None:
|
108
|
-
"""
|
109
|
-
Cleans old requests that are outside the time window.
|
110
|
-
|
111
|
-
Args:
|
112
|
-
requests: List of request timestamps.
|
113
|
-
current_time: Current time.
|
114
|
-
"""
|
115
|
-
min_time = current_time - self.time_window
|
116
|
-
while requests and requests[0] < min_time:
|
117
|
-
requests.pop(0)
|
118
|
-
|
119
|
-
def _is_public_path(self, path: str) -> bool:
|
120
|
-
"""
|
121
|
-
Checks if the path is public.
|
122
|
-
|
123
|
-
Args:
|
124
|
-
path: Path to check.
|
125
|
-
|
126
|
-
Returns:
|
127
|
-
True if path is public, False otherwise.
|
128
|
-
"""
|
129
|
-
return any(path.startswith(public_path) for public_path in self.public_paths)
|
130
|
-
|
131
|
-
def _create_error_response(self, message: str, status_code: int) -> Response:
|
132
|
-
"""
|
133
|
-
Creates error response in JSON-RPC format.
|
134
|
-
|
135
|
-
Args:
|
136
|
-
message: Error message.
|
137
|
-
status_code: HTTP status code.
|
138
|
-
|
139
|
-
Returns:
|
140
|
-
JSON response with error.
|
141
|
-
"""
|
142
|
-
return JSONResponse(
|
143
|
-
status_code=status_code,
|
144
|
-
content={
|
145
|
-
"jsonrpc": "2.0",
|
146
|
-
"error": {
|
147
|
-
"code": -32000,
|
148
|
-
"message": message
|
149
|
-
},
|
150
|
-
"id": None
|
151
|
-
}
|
152
|
-
)
|
@@ -1,241 +0,0 @@
|
|
1
|
-
"""
|
2
|
-
Rate Limit Middleware Adapter for backward compatibility.
|
3
|
-
|
4
|
-
This module provides an adapter that maintains the same interface as RateLimitMiddleware
|
5
|
-
while using the new SecurityMiddleware internally.
|
6
|
-
"""
|
7
|
-
|
8
|
-
import time
|
9
|
-
from typing import Dict, List, Callable, Awaitable, Any
|
10
|
-
from collections import defaultdict
|
11
|
-
|
12
|
-
from fastapi import Request, Response
|
13
|
-
from starlette.responses import JSONResponse
|
14
|
-
|
15
|
-
from mcp_proxy_adapter.core.logging import logger
|
16
|
-
from .base import BaseMiddleware
|
17
|
-
from .security import SecurityMiddleware
|
18
|
-
|
19
|
-
|
20
|
-
class RateLimitMiddlewareAdapter(BaseMiddleware):
|
21
|
-
"""
|
22
|
-
Adapter for RateLimitMiddleware that uses SecurityMiddleware internally.
|
23
|
-
|
24
|
-
Maintains the same interface as the original RateLimitMiddleware for backward compatibility.
|
25
|
-
"""
|
26
|
-
|
27
|
-
def __init__(self, app, rate_limit: int = 100, time_window: int = 60,
|
28
|
-
by_ip: bool = True, by_user: bool = True,
|
29
|
-
public_paths: List[str] = None):
|
30
|
-
"""
|
31
|
-
Initialize rate limit middleware adapter.
|
32
|
-
|
33
|
-
Args:
|
34
|
-
app: FastAPI application
|
35
|
-
rate_limit: Maximum number of requests in the specified time period
|
36
|
-
time_window: Time period in seconds
|
37
|
-
by_ip: Limit requests by IP address
|
38
|
-
by_user: Limit requests by user
|
39
|
-
public_paths: List of paths for which rate limiting is not applied
|
40
|
-
"""
|
41
|
-
super().__init__(app)
|
42
|
-
|
43
|
-
# Store original parameters for backward compatibility
|
44
|
-
self.rate_limit = rate_limit
|
45
|
-
self.time_window = time_window
|
46
|
-
self.by_ip = by_ip
|
47
|
-
self.by_user = by_user
|
48
|
-
self.public_paths = public_paths or [
|
49
|
-
"/docs",
|
50
|
-
"/redoc",
|
51
|
-
"/openapi.json",
|
52
|
-
"/health"
|
53
|
-
]
|
54
|
-
|
55
|
-
# Legacy storage for backward compatibility
|
56
|
-
self.ip_requests = defaultdict(list)
|
57
|
-
self.user_requests = defaultdict(list)
|
58
|
-
|
59
|
-
# Create internal security middleware
|
60
|
-
self.security_middleware = self._create_security_middleware()
|
61
|
-
|
62
|
-
logger.info(f"RateLimitMiddlewareAdapter initialized: rate_limit={rate_limit}, "
|
63
|
-
f"time_window={time_window}, by_ip={by_ip}, by_user={by_user}")
|
64
|
-
|
65
|
-
def _create_security_middleware(self) -> SecurityMiddleware:
|
66
|
-
"""
|
67
|
-
Create internal SecurityMiddleware with RateLimitMiddleware configuration.
|
68
|
-
|
69
|
-
Returns:
|
70
|
-
SecurityMiddleware instance
|
71
|
-
"""
|
72
|
-
# Convert RateLimitMiddleware config to SecurityMiddleware config
|
73
|
-
security_config = {
|
74
|
-
"security": {
|
75
|
-
"enabled": True,
|
76
|
-
"auth": {
|
77
|
-
"enabled": False
|
78
|
-
},
|
79
|
-
"ssl": {
|
80
|
-
"enabled": False
|
81
|
-
},
|
82
|
-
"permissions": {
|
83
|
-
"enabled": False
|
84
|
-
},
|
85
|
-
"rate_limit": {
|
86
|
-
"enabled": True,
|
87
|
-
"requests_per_minute": self.rate_limit,
|
88
|
-
"requests_per_hour": self.rate_limit * 60,
|
89
|
-
"burst_limit": self.rate_limit // 10,
|
90
|
-
"by_ip": self.by_ip,
|
91
|
-
"by_user": self.by_user
|
92
|
-
},
|
93
|
-
"public_paths": self.public_paths
|
94
|
-
}
|
95
|
-
}
|
96
|
-
|
97
|
-
return SecurityMiddleware(self.app, security_config)
|
98
|
-
|
99
|
-
async def dispatch(self, request: Request, call_next: Callable[[Request], Awaitable[Response]]) -> Response:
|
100
|
-
"""
|
101
|
-
Process request using internal SecurityMiddleware with legacy fallback.
|
102
|
-
|
103
|
-
Args:
|
104
|
-
request: Request object
|
105
|
-
call_next: Next handler
|
106
|
-
|
107
|
-
Returns:
|
108
|
-
Response object
|
109
|
-
"""
|
110
|
-
# Check if path is public
|
111
|
-
path = request.url.path
|
112
|
-
if self._is_public_path(path):
|
113
|
-
return await call_next(request)
|
114
|
-
|
115
|
-
# Try to use SecurityMiddleware first
|
116
|
-
try:
|
117
|
-
await self.security_middleware.before_request(request)
|
118
|
-
return await call_next(request)
|
119
|
-
|
120
|
-
except Exception as e:
|
121
|
-
# Fallback to legacy rate limiting if SecurityMiddleware fails
|
122
|
-
logger.warning(f"SecurityMiddleware rate limiting failed, using legacy fallback: {e}")
|
123
|
-
return await self._legacy_rate_limit_check(request, call_next)
|
124
|
-
|
125
|
-
async def _legacy_rate_limit_check(self, request: Request, call_next: Callable[[Request], Awaitable[Response]]) -> Response:
|
126
|
-
"""
|
127
|
-
Legacy rate limiting implementation as fallback.
|
128
|
-
|
129
|
-
Args:
|
130
|
-
request: Request object
|
131
|
-
call_next: Next handler
|
132
|
-
|
133
|
-
Returns:
|
134
|
-
Response object
|
135
|
-
"""
|
136
|
-
current_time = time.time()
|
137
|
-
client_ip = request.client.host if request.client else "unknown"
|
138
|
-
username = getattr(request.state, "username", None)
|
139
|
-
|
140
|
-
# Check limit by IP
|
141
|
-
if self.by_ip and client_ip != "unknown":
|
142
|
-
self._clean_old_requests(self.ip_requests[client_ip], current_time)
|
143
|
-
|
144
|
-
if len(self.ip_requests[client_ip]) >= self.rate_limit:
|
145
|
-
logger.warning(f"Rate limit exceeded for IP: {client_ip}")
|
146
|
-
return self._create_error_response("Rate limit exceeded", 429)
|
147
|
-
|
148
|
-
self.ip_requests[client_ip].append(current_time)
|
149
|
-
|
150
|
-
# Check limit by user
|
151
|
-
if self.by_user and username:
|
152
|
-
self._clean_old_requests(self.user_requests[username], current_time)
|
153
|
-
|
154
|
-
if len(self.user_requests[username]) >= self.rate_limit:
|
155
|
-
logger.warning(f"Rate limit exceeded for user: {username}")
|
156
|
-
return self._create_error_response("Rate limit exceeded", 429)
|
157
|
-
|
158
|
-
self.user_requests[username].append(current_time)
|
159
|
-
|
160
|
-
return await call_next(request)
|
161
|
-
|
162
|
-
def _clean_old_requests(self, requests_list: List[float], current_time: float) -> None:
|
163
|
-
"""
|
164
|
-
Remove old requests from the list.
|
165
|
-
|
166
|
-
Args:
|
167
|
-
requests_list: List of request timestamps
|
168
|
-
current_time: Current time
|
169
|
-
"""
|
170
|
-
cutoff_time = current_time - self.time_window
|
171
|
-
requests_list[:] = [req_time for req_time in requests_list if req_time > cutoff_time]
|
172
|
-
|
173
|
-
def _is_public_path(self, path: str) -> bool:
|
174
|
-
"""
|
175
|
-
Check if the path is public (doesn't require rate limiting).
|
176
|
-
|
177
|
-
Args:
|
178
|
-
path: Request path
|
179
|
-
|
180
|
-
Returns:
|
181
|
-
True if path is public, False otherwise
|
182
|
-
"""
|
183
|
-
return any(path.startswith(public_path) for public_path in self.public_paths)
|
184
|
-
|
185
|
-
def _create_error_response(self, message: str, status_code: int) -> JSONResponse:
|
186
|
-
"""
|
187
|
-
Create error response in RateLimitMiddleware format.
|
188
|
-
|
189
|
-
Args:
|
190
|
-
message: Error message
|
191
|
-
status_code: HTTP status code
|
192
|
-
|
193
|
-
Returns:
|
194
|
-
JSONResponse with error
|
195
|
-
"""
|
196
|
-
return JSONResponse(
|
197
|
-
status_code=status_code,
|
198
|
-
content={
|
199
|
-
"jsonrpc": "2.0",
|
200
|
-
"error": {
|
201
|
-
"code": -32008 if status_code == 429 else -32603,
|
202
|
-
"message": message,
|
203
|
-
"data": {
|
204
|
-
"rate_limit": self.rate_limit,
|
205
|
-
"time_window": self.time_window,
|
206
|
-
"status_code": status_code
|
207
|
-
}
|
208
|
-
},
|
209
|
-
"id": None
|
210
|
-
}
|
211
|
-
)
|
212
|
-
|
213
|
-
def get_rate_limit_info(self, request: Request) -> Dict[str, Any]:
|
214
|
-
"""
|
215
|
-
Get rate limit information for the request (backward compatibility).
|
216
|
-
|
217
|
-
Args:
|
218
|
-
request: Request object
|
219
|
-
|
220
|
-
Returns:
|
221
|
-
Dictionary with rate limit information
|
222
|
-
"""
|
223
|
-
client_ip = request.client.host if request.client else "unknown"
|
224
|
-
username = getattr(request.state, "username", None)
|
225
|
-
|
226
|
-
info = {
|
227
|
-
"rate_limit": self.rate_limit,
|
228
|
-
"time_window": self.time_window,
|
229
|
-
"by_ip": self.by_ip,
|
230
|
-
"by_user": self.by_user
|
231
|
-
}
|
232
|
-
|
233
|
-
if self.by_ip and client_ip != "unknown":
|
234
|
-
info["ip_requests"] = len(self.ip_requests[client_ip])
|
235
|
-
info["ip_remaining"] = max(0, self.rate_limit - len(self.ip_requests[client_ip]))
|
236
|
-
|
237
|
-
if self.by_user and username:
|
238
|
-
info["user_requests"] = len(self.user_requests[username])
|
239
|
-
info["user_remaining"] = max(0, self.rate_limit - len(self.user_requests[username]))
|
240
|
-
|
241
|
-
return info
|