mcp-proxy-adapter 6.0.0__py3-none-any.whl → 6.0.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 (212) hide show
  1. mcp_proxy_adapter/__main__.py +27 -7
  2. mcp_proxy_adapter/api/app.py +209 -79
  3. mcp_proxy_adapter/api/handlers.py +16 -5
  4. mcp_proxy_adapter/api/middleware/__init__.py +14 -9
  5. mcp_proxy_adapter/api/middleware/command_permission_middleware.py +148 -0
  6. mcp_proxy_adapter/api/middleware/factory.py +36 -12
  7. mcp_proxy_adapter/api/middleware/protocol_middleware.py +84 -18
  8. mcp_proxy_adapter/api/middleware/unified_security.py +197 -0
  9. mcp_proxy_adapter/api/middleware/user_info_middleware.py +158 -0
  10. mcp_proxy_adapter/commands/__init__.py +7 -1
  11. mcp_proxy_adapter/commands/base.py +7 -4
  12. mcp_proxy_adapter/commands/builtin_commands.py +8 -2
  13. mcp_proxy_adapter/commands/command_registry.py +8 -0
  14. mcp_proxy_adapter/commands/echo_command.py +81 -0
  15. mcp_proxy_adapter/commands/health_command.py +1 -1
  16. mcp_proxy_adapter/commands/help_command.py +21 -14
  17. mcp_proxy_adapter/commands/proxy_registration_command.py +326 -185
  18. mcp_proxy_adapter/commands/role_test_command.py +141 -0
  19. mcp_proxy_adapter/commands/security_command.py +488 -0
  20. mcp_proxy_adapter/commands/ssl_setup_command.py +234 -351
  21. mcp_proxy_adapter/commands/token_management_command.py +1 -1
  22. mcp_proxy_adapter/config.py +323 -40
  23. mcp_proxy_adapter/core/app_factory.py +410 -0
  24. mcp_proxy_adapter/core/app_runner.py +272 -0
  25. mcp_proxy_adapter/core/certificate_utils.py +291 -73
  26. mcp_proxy_adapter/core/client.py +574 -0
  27. mcp_proxy_adapter/core/client_manager.py +284 -0
  28. mcp_proxy_adapter/core/client_security.py +384 -0
  29. mcp_proxy_adapter/core/logging.py +8 -3
  30. mcp_proxy_adapter/core/mtls_asgi.py +156 -0
  31. mcp_proxy_adapter/core/mtls_asgi_app.py +187 -0
  32. mcp_proxy_adapter/core/protocol_manager.py +169 -10
  33. mcp_proxy_adapter/core/proxy_client.py +602 -0
  34. mcp_proxy_adapter/core/proxy_registration.py +299 -47
  35. mcp_proxy_adapter/core/security_adapter.py +12 -15
  36. mcp_proxy_adapter/core/security_integration.py +286 -0
  37. mcp_proxy_adapter/core/server_adapter.py +282 -0
  38. mcp_proxy_adapter/core/server_engine.py +270 -0
  39. mcp_proxy_adapter/core/ssl_utils.py +13 -12
  40. mcp_proxy_adapter/core/transport_manager.py +5 -5
  41. mcp_proxy_adapter/core/unified_config_adapter.py +579 -0
  42. mcp_proxy_adapter/examples/__init__.py +13 -4
  43. mcp_proxy_adapter/examples/basic_framework/__init__.py +9 -0
  44. mcp_proxy_adapter/examples/basic_framework/commands/__init__.py +4 -0
  45. mcp_proxy_adapter/examples/basic_framework/hooks/__init__.py +4 -0
  46. mcp_proxy_adapter/examples/basic_framework/main.py +44 -0
  47. mcp_proxy_adapter/examples/commands/__init__.py +5 -0
  48. mcp_proxy_adapter/examples/create_certificates_simple.py +550 -0
  49. mcp_proxy_adapter/examples/debug_request_state.py +112 -0
  50. mcp_proxy_adapter/examples/debug_role_chain.py +158 -0
  51. mcp_proxy_adapter/examples/demo_client.py +275 -0
  52. mcp_proxy_adapter/examples/examples/basic_framework/__init__.py +9 -0
  53. mcp_proxy_adapter/examples/examples/basic_framework/commands/__init__.py +4 -0
  54. mcp_proxy_adapter/examples/examples/basic_framework/hooks/__init__.py +4 -0
  55. mcp_proxy_adapter/examples/examples/basic_framework/main.py +44 -0
  56. mcp_proxy_adapter/examples/examples/full_application/__init__.py +12 -0
  57. mcp_proxy_adapter/examples/examples/full_application/commands/__init__.py +7 -0
  58. mcp_proxy_adapter/examples/examples/full_application/commands/custom_echo_command.py +80 -0
  59. mcp_proxy_adapter/examples/examples/full_application/commands/dynamic_calculator_command.py +90 -0
  60. mcp_proxy_adapter/examples/examples/full_application/hooks/__init__.py +7 -0
  61. mcp_proxy_adapter/examples/examples/full_application/hooks/application_hooks.py +75 -0
  62. mcp_proxy_adapter/examples/examples/full_application/hooks/builtin_command_hooks.py +71 -0
  63. mcp_proxy_adapter/examples/examples/full_application/main.py +173 -0
  64. mcp_proxy_adapter/examples/examples/full_application/proxy_endpoints.py +154 -0
  65. mcp_proxy_adapter/examples/full_application/__init__.py +12 -0
  66. mcp_proxy_adapter/examples/full_application/commands/__init__.py +7 -0
  67. mcp_proxy_adapter/examples/full_application/commands/custom_echo_command.py +80 -0
  68. mcp_proxy_adapter/examples/full_application/commands/dynamic_calculator_command.py +90 -0
  69. mcp_proxy_adapter/examples/full_application/hooks/__init__.py +7 -0
  70. mcp_proxy_adapter/examples/full_application/hooks/application_hooks.py +75 -0
  71. mcp_proxy_adapter/examples/full_application/hooks/builtin_command_hooks.py +71 -0
  72. mcp_proxy_adapter/examples/full_application/main.py +173 -0
  73. mcp_proxy_adapter/examples/full_application/proxy_endpoints.py +154 -0
  74. mcp_proxy_adapter/examples/generate_all_certificates.py +362 -0
  75. mcp_proxy_adapter/examples/generate_certificates.py +177 -0
  76. mcp_proxy_adapter/examples/generate_certificates_and_tokens.py +369 -0
  77. mcp_proxy_adapter/examples/generate_test_configs.py +331 -0
  78. mcp_proxy_adapter/examples/proxy_registration_example.py +334 -0
  79. mcp_proxy_adapter/examples/run_example.py +59 -0
  80. mcp_proxy_adapter/examples/run_full_test_suite.py +318 -0
  81. mcp_proxy_adapter/examples/run_proxy_server.py +146 -0
  82. mcp_proxy_adapter/examples/run_security_tests.py +544 -0
  83. mcp_proxy_adapter/examples/run_security_tests_fixed.py +247 -0
  84. mcp_proxy_adapter/examples/scripts/config_generator.py +740 -0
  85. mcp_proxy_adapter/examples/scripts/create_certificates_simple.py +560 -0
  86. mcp_proxy_adapter/examples/scripts/generate_certificates_and_tokens.py +369 -0
  87. mcp_proxy_adapter/examples/security_test_client.py +782 -0
  88. mcp_proxy_adapter/examples/setup_test_environment.py +328 -0
  89. mcp_proxy_adapter/examples/test_config.py +148 -0
  90. mcp_proxy_adapter/examples/test_config_generator.py +86 -0
  91. mcp_proxy_adapter/examples/test_examples.py +281 -0
  92. mcp_proxy_adapter/examples/universal_client.py +620 -0
  93. mcp_proxy_adapter/main.py +66 -148
  94. mcp_proxy_adapter/utils/config_generator.py +1008 -0
  95. mcp_proxy_adapter/version.py +5 -2
  96. mcp_proxy_adapter-6.0.1.dist-info/METADATA +679 -0
  97. mcp_proxy_adapter-6.0.1.dist-info/RECORD +140 -0
  98. mcp_proxy_adapter-6.0.1.dist-info/entry_points.txt +2 -0
  99. {mcp_proxy_adapter-6.0.0.dist-info → mcp_proxy_adapter-6.0.1.dist-info}/licenses/LICENSE +2 -2
  100. mcp_proxy_adapter/api/middleware/auth.py +0 -146
  101. mcp_proxy_adapter/api/middleware/auth_adapter.py +0 -235
  102. mcp_proxy_adapter/api/middleware/mtls_adapter.py +0 -305
  103. mcp_proxy_adapter/api/middleware/mtls_middleware.py +0 -296
  104. mcp_proxy_adapter/api/middleware/rate_limit.py +0 -152
  105. mcp_proxy_adapter/api/middleware/rate_limit_adapter.py +0 -241
  106. mcp_proxy_adapter/api/middleware/roles_adapter.py +0 -365
  107. mcp_proxy_adapter/api/middleware/roles_middleware.py +0 -381
  108. mcp_proxy_adapter/api/middleware/security.py +0 -376
  109. mcp_proxy_adapter/api/middleware/token_auth_middleware.py +0 -261
  110. mcp_proxy_adapter/examples/README.md +0 -124
  111. mcp_proxy_adapter/examples/basic_server/README.md +0 -60
  112. mcp_proxy_adapter/examples/basic_server/__init__.py +0 -7
  113. mcp_proxy_adapter/examples/basic_server/basic_custom_settings.json +0 -39
  114. mcp_proxy_adapter/examples/basic_server/config.json +0 -70
  115. mcp_proxy_adapter/examples/basic_server/config_all_protocols.json +0 -54
  116. mcp_proxy_adapter/examples/basic_server/config_http.json +0 -70
  117. mcp_proxy_adapter/examples/basic_server/config_http_only.json +0 -52
  118. mcp_proxy_adapter/examples/basic_server/config_https.json +0 -58
  119. mcp_proxy_adapter/examples/basic_server/config_mtls.json +0 -58
  120. mcp_proxy_adapter/examples/basic_server/config_ssl.json +0 -46
  121. mcp_proxy_adapter/examples/basic_server/custom_settings_example.py +0 -238
  122. mcp_proxy_adapter/examples/basic_server/server.py +0 -114
  123. mcp_proxy_adapter/examples/custom_commands/README.md +0 -127
  124. mcp_proxy_adapter/examples/custom_commands/__init__.py +0 -27
  125. mcp_proxy_adapter/examples/custom_commands/advanced_hooks.py +0 -566
  126. mcp_proxy_adapter/examples/custom_commands/auto_commands/__init__.py +0 -6
  127. mcp_proxy_adapter/examples/custom_commands/auto_commands/auto_echo_command.py +0 -103
  128. mcp_proxy_adapter/examples/custom_commands/auto_commands/auto_info_command.py +0 -111
  129. mcp_proxy_adapter/examples/custom_commands/auto_commands/test_command.py +0 -105
  130. mcp_proxy_adapter/examples/custom_commands/catalog/commands/test_command.py +0 -129
  131. mcp_proxy_adapter/examples/custom_commands/config.json +0 -118
  132. mcp_proxy_adapter/examples/custom_commands/config_all_protocols.json +0 -46
  133. mcp_proxy_adapter/examples/custom_commands/config_https_only.json +0 -46
  134. mcp_proxy_adapter/examples/custom_commands/config_https_transport.json +0 -33
  135. mcp_proxy_adapter/examples/custom_commands/config_mtls_only.json +0 -46
  136. mcp_proxy_adapter/examples/custom_commands/config_mtls_transport.json +0 -33
  137. mcp_proxy_adapter/examples/custom_commands/config_single_transport.json +0 -33
  138. mcp_proxy_adapter/examples/custom_commands/custom_health_command.py +0 -169
  139. mcp_proxy_adapter/examples/custom_commands/custom_help_command.py +0 -215
  140. mcp_proxy_adapter/examples/custom_commands/custom_openapi_generator.py +0 -76
  141. mcp_proxy_adapter/examples/custom_commands/custom_settings.json +0 -96
  142. mcp_proxy_adapter/examples/custom_commands/custom_settings_manager.py +0 -241
  143. mcp_proxy_adapter/examples/custom_commands/data_transform_command.py +0 -135
  144. mcp_proxy_adapter/examples/custom_commands/echo_command.py +0 -122
  145. mcp_proxy_adapter/examples/custom_commands/full_help_response.json +0 -1
  146. mcp_proxy_adapter/examples/custom_commands/generated_openapi.json +0 -629
  147. mcp_proxy_adapter/examples/custom_commands/get_openapi.py +0 -103
  148. mcp_proxy_adapter/examples/custom_commands/hooks.py +0 -230
  149. mcp_proxy_adapter/examples/custom_commands/intercept_command.py +0 -123
  150. mcp_proxy_adapter/examples/custom_commands/loadable_commands/test_ignored.py +0 -129
  151. mcp_proxy_adapter/examples/custom_commands/manual_echo_command.py +0 -103
  152. mcp_proxy_adapter/examples/custom_commands/proxy_connection_manager.py +0 -278
  153. mcp_proxy_adapter/examples/custom_commands/server.py +0 -252
  154. mcp_proxy_adapter/examples/custom_commands/simple_openapi_server.py +0 -75
  155. mcp_proxy_adapter/examples/custom_commands/start_server_with_proxy_manager.py +0 -299
  156. mcp_proxy_adapter/examples/custom_commands/start_server_with_registration.py +0 -278
  157. mcp_proxy_adapter/examples/custom_commands/test_hooks.py +0 -176
  158. mcp_proxy_adapter/examples/custom_commands/test_openapi.py +0 -27
  159. mcp_proxy_adapter/examples/custom_commands/test_registry.py +0 -23
  160. mcp_proxy_adapter/examples/custom_commands/test_simple.py +0 -19
  161. mcp_proxy_adapter/examples/custom_project_example/README.md +0 -103
  162. mcp_proxy_adapter/examples/custom_project_example/README_EN.md +0 -103
  163. mcp_proxy_adapter/examples/deployment/README.md +0 -49
  164. mcp_proxy_adapter/examples/deployment/__init__.py +0 -7
  165. mcp_proxy_adapter/examples/deployment/config.development.json +0 -8
  166. mcp_proxy_adapter/examples/deployment/config.json +0 -29
  167. mcp_proxy_adapter/examples/deployment/config.production.json +0 -12
  168. mcp_proxy_adapter/examples/deployment/config.staging.json +0 -11
  169. mcp_proxy_adapter/examples/deployment/docker-compose.yml +0 -31
  170. mcp_proxy_adapter/examples/deployment/run.sh +0 -43
  171. mcp_proxy_adapter/examples/deployment/run_docker.sh +0 -84
  172. mcp_proxy_adapter/examples/simple_custom_commands/README.md +0 -149
  173. mcp_proxy_adapter/examples/simple_custom_commands/README_EN.md +0 -149
  174. mcp_proxy_adapter/schemas/base_schema.json +0 -114
  175. mcp_proxy_adapter/schemas/openapi_schema.json +0 -314
  176. mcp_proxy_adapter/schemas/roles_schema.json +0 -162
  177. mcp_proxy_adapter/tests/__init__.py +0 -0
  178. mcp_proxy_adapter/tests/api/__init__.py +0 -3
  179. mcp_proxy_adapter/tests/api/test_cmd_endpoint.py +0 -115
  180. mcp_proxy_adapter/tests/api/test_custom_openapi.py +0 -617
  181. mcp_proxy_adapter/tests/api/test_handlers.py +0 -522
  182. mcp_proxy_adapter/tests/api/test_middleware.py +0 -340
  183. mcp_proxy_adapter/tests/api/test_schemas.py +0 -546
  184. mcp_proxy_adapter/tests/api/test_tool_integration.py +0 -531
  185. mcp_proxy_adapter/tests/commands/__init__.py +0 -3
  186. mcp_proxy_adapter/tests/commands/test_config_command.py +0 -211
  187. mcp_proxy_adapter/tests/commands/test_echo_command.py +0 -127
  188. mcp_proxy_adapter/tests/commands/test_help_command.py +0 -136
  189. mcp_proxy_adapter/tests/conftest.py +0 -131
  190. mcp_proxy_adapter/tests/functional/__init__.py +0 -3
  191. mcp_proxy_adapter/tests/functional/test_api.py +0 -253
  192. mcp_proxy_adapter/tests/integration/__init__.py +0 -3
  193. mcp_proxy_adapter/tests/integration/test_cmd_integration.py +0 -129
  194. mcp_proxy_adapter/tests/integration/test_integration.py +0 -255
  195. mcp_proxy_adapter/tests/performance/__init__.py +0 -3
  196. mcp_proxy_adapter/tests/performance/test_performance.py +0 -189
  197. mcp_proxy_adapter/tests/stubs/__init__.py +0 -10
  198. mcp_proxy_adapter/tests/stubs/echo_command.py +0 -104
  199. mcp_proxy_adapter/tests/test_api_endpoints.py +0 -271
  200. mcp_proxy_adapter/tests/test_api_handlers.py +0 -289
  201. mcp_proxy_adapter/tests/test_base_command.py +0 -123
  202. mcp_proxy_adapter/tests/test_batch_requests.py +0 -117
  203. mcp_proxy_adapter/tests/test_command_registry.py +0 -281
  204. mcp_proxy_adapter/tests/test_config.py +0 -127
  205. mcp_proxy_adapter/tests/test_utils.py +0 -65
  206. mcp_proxy_adapter/tests/unit/__init__.py +0 -3
  207. mcp_proxy_adapter/tests/unit/test_base_command.py +0 -436
  208. mcp_proxy_adapter/tests/unit/test_config.py +0 -270
  209. mcp_proxy_adapter-6.0.0.dist-info/METADATA +0 -201
  210. mcp_proxy_adapter-6.0.0.dist-info/RECORD +0 -179
  211. {mcp_proxy_adapter-6.0.0.dist-info → mcp_proxy_adapter-6.0.1.dist-info}/WHEEL +0 -0
  212. {mcp_proxy_adapter-6.0.0.dist-info → mcp_proxy_adapter-6.0.1.dist-info}/top_level.txt +0 -0
@@ -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