mcp-proxy-adapter 6.1.0__py3-none-any.whl → 6.2.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_proxy_adapter/__main__.py +27 -7
- mcp_proxy_adapter/api/app.py +18 -7
- mcp_proxy_adapter/api/middleware/__init__.py +2 -2
- mcp_proxy_adapter/api/middleware/protocol_middleware.py +32 -13
- mcp_proxy_adapter/api/middleware/unified_security.py +12 -4
- mcp_proxy_adapter/commands/ssl_setup_command.py +234 -351
- mcp_proxy_adapter/core/app_factory.py +87 -3
- mcp_proxy_adapter/core/app_runner.py +272 -0
- mcp_proxy_adapter/core/certificate_utils.py +291 -73
- mcp_proxy_adapter/core/client.py +574 -0
- mcp_proxy_adapter/core/client_manager.py +284 -0
- mcp_proxy_adapter/core/protocol_manager.py +132 -10
- mcp_proxy_adapter/core/security_integration.py +19 -11
- mcp_proxy_adapter/core/server_adapter.py +17 -80
- mcp_proxy_adapter/core/server_engine.py +5 -99
- mcp_proxy_adapter/core/ssl_utils.py +13 -12
- mcp_proxy_adapter/core/transport_manager.py +5 -5
- mcp_proxy_adapter/examples/__init__.py +16 -0
- mcp_proxy_adapter/examples/basic_framework/__init__.py +7 -0
- mcp_proxy_adapter/examples/basic_framework/commands/__init__.py +4 -0
- mcp_proxy_adapter/examples/basic_framework/hooks/__init__.py +4 -0
- mcp_proxy_adapter/examples/basic_framework/main.py +21 -40
- mcp_proxy_adapter/examples/commands/__init__.py +5 -1
- mcp_proxy_adapter/examples/create_certificates_simple.py +260 -75
- mcp_proxy_adapter/examples/debug_request_state.py +4 -36
- mcp_proxy_adapter/examples/debug_role_chain.py +2 -49
- mcp_proxy_adapter/examples/demo_client.py +0 -66
- mcp_proxy_adapter/examples/full_application/__init__.py +11 -0
- mcp_proxy_adapter/examples/full_application/commands/__init__.py +7 -0
- mcp_proxy_adapter/examples/full_application/commands/custom_echo_command.py +0 -19
- mcp_proxy_adapter/examples/full_application/commands/dynamic_calculator_command.py +0 -16
- mcp_proxy_adapter/examples/full_application/hooks/__init__.py +7 -0
- mcp_proxy_adapter/examples/full_application/hooks/application_hooks.py +0 -22
- mcp_proxy_adapter/examples/full_application/hooks/builtin_command_hooks.py +0 -24
- mcp_proxy_adapter/examples/full_application/main.py +65 -44
- mcp_proxy_adapter/examples/full_application/proxy_endpoints.py +154 -0
- mcp_proxy_adapter/examples/generate_all_certificates.py +0 -67
- mcp_proxy_adapter/examples/generate_certificates.py +0 -15
- mcp_proxy_adapter/examples/generate_certificates_and_tokens.py +369 -0
- mcp_proxy_adapter/examples/generate_test_configs.py +204 -0
- mcp_proxy_adapter/examples/proxy_registration_example.py +3 -70
- mcp_proxy_adapter/examples/run_example.py +1 -23
- mcp_proxy_adapter/examples/run_security_tests.py +2 -60
- mcp_proxy_adapter/examples/run_security_tests_fixed.py +0 -53
- mcp_proxy_adapter/examples/security_test_client.py +18 -123
- mcp_proxy_adapter/examples/setup_test_environment.py +179 -0
- mcp_proxy_adapter/examples/test_config.py +148 -0
- mcp_proxy_adapter/examples/test_config_generator.py +86 -0
- mcp_proxy_adapter/examples/test_examples.py +4 -67
- mcp_proxy_adapter/examples/universal_client.py +154 -162
- mcp_proxy_adapter/main.py +51 -161
- mcp_proxy_adapter/utils/config_generator.py +90 -2
- mcp_proxy_adapter/version.py +4 -2
- mcp_proxy_adapter-6.2.0.dist-info/METADATA +687 -0
- mcp_proxy_adapter-6.2.0.dist-info/RECORD +122 -0
- mcp_proxy_adapter/examples/README.md +0 -257
- mcp_proxy_adapter/examples/README_EN.md +0 -258
- mcp_proxy_adapter/examples/SECURITY_TESTING.md +0 -455
- mcp_proxy_adapter/examples/__pycache__/security_configurations.cpython-312.pyc +0 -0
- mcp_proxy_adapter/examples/__pycache__/security_test_client.cpython-312.pyc +0 -0
- mcp_proxy_adapter/examples/basic_framework/configs/http_auth.json +0 -37
- mcp_proxy_adapter/examples/basic_framework/configs/http_simple.json +0 -23
- mcp_proxy_adapter/examples/basic_framework/configs/https_auth.json +0 -39
- mcp_proxy_adapter/examples/basic_framework/configs/https_simple.json +0 -25
- mcp_proxy_adapter/examples/basic_framework/configs/mtls_no_roles.json +0 -39
- mcp_proxy_adapter/examples/basic_framework/configs/mtls_with_roles.json +0 -45
- mcp_proxy_adapter/examples/basic_framework/roles.json +0 -21
- mcp_proxy_adapter/examples/cert_config.json +0 -9
- mcp_proxy_adapter/examples/certs/admin.crt +0 -32
- mcp_proxy_adapter/examples/certs/admin.key +0 -52
- mcp_proxy_adapter/examples/certs/admin_cert.pem +0 -21
- mcp_proxy_adapter/examples/certs/admin_key.pem +0 -28
- mcp_proxy_adapter/examples/certs/ca_cert.pem +0 -23
- mcp_proxy_adapter/examples/certs/ca_cert.srl +0 -1
- mcp_proxy_adapter/examples/certs/ca_key.pem +0 -28
- mcp_proxy_adapter/examples/certs/cert_config.json +0 -9
- mcp_proxy_adapter/examples/certs/client.crt +0 -32
- mcp_proxy_adapter/examples/certs/client.key +0 -52
- mcp_proxy_adapter/examples/certs/client_admin.crt +0 -32
- mcp_proxy_adapter/examples/certs/client_admin.key +0 -52
- mcp_proxy_adapter/examples/certs/client_user.crt +0 -32
- mcp_proxy_adapter/examples/certs/client_user.key +0 -52
- mcp_proxy_adapter/examples/certs/guest_cert.pem +0 -21
- mcp_proxy_adapter/examples/certs/guest_key.pem +0 -28
- mcp_proxy_adapter/examples/certs/mcp_proxy_adapter_ca_ca.crt +0 -23
- mcp_proxy_adapter/examples/certs/proxy_cert.pem +0 -21
- mcp_proxy_adapter/examples/certs/proxy_key.pem +0 -28
- mcp_proxy_adapter/examples/certs/readonly.crt +0 -32
- mcp_proxy_adapter/examples/certs/readonly.key +0 -52
- mcp_proxy_adapter/examples/certs/readonly_cert.pem +0 -21
- mcp_proxy_adapter/examples/certs/readonly_key.pem +0 -28
- mcp_proxy_adapter/examples/certs/server.crt +0 -32
- mcp_proxy_adapter/examples/certs/server.key +0 -52
- mcp_proxy_adapter/examples/certs/server_cert.pem +0 -32
- mcp_proxy_adapter/examples/certs/server_key.pem +0 -52
- mcp_proxy_adapter/examples/certs/test_ca_ca.crt +0 -20
- mcp_proxy_adapter/examples/certs/user.crt +0 -32
- mcp_proxy_adapter/examples/certs/user.key +0 -52
- mcp_proxy_adapter/examples/certs/user_cert.pem +0 -21
- mcp_proxy_adapter/examples/certs/user_key.pem +0 -28
- mcp_proxy_adapter/examples/client_configs/api_key_client.json +0 -13
- mcp_proxy_adapter/examples/client_configs/basic_auth_client.json +0 -13
- mcp_proxy_adapter/examples/client_configs/certificate_client.json +0 -22
- mcp_proxy_adapter/examples/client_configs/jwt_client.json +0 -15
- mcp_proxy_adapter/examples/client_configs/no_auth_client.json +0 -9
- mcp_proxy_adapter/examples/full_application/configs/http_auth.json +0 -37
- mcp_proxy_adapter/examples/full_application/configs/http_simple.json +0 -23
- mcp_proxy_adapter/examples/full_application/configs/https_auth.json +0 -39
- mcp_proxy_adapter/examples/full_application/configs/https_simple.json +0 -25
- mcp_proxy_adapter/examples/full_application/configs/mtls_no_roles.json +0 -39
- mcp_proxy_adapter/examples/full_application/configs/mtls_with_roles.json +0 -45
- mcp_proxy_adapter/examples/full_application/roles.json +0 -21
- mcp_proxy_adapter/examples/keys/ca_key.pem +0 -28
- mcp_proxy_adapter/examples/keys/mcp_proxy_adapter_ca_ca.key +0 -28
- mcp_proxy_adapter/examples/keys/test_ca_ca.key +0 -28
- mcp_proxy_adapter/examples/logs/mcp_proxy_adapter.log +0 -220
- mcp_proxy_adapter/examples/logs/mcp_proxy_adapter.log.1 +0 -1
- mcp_proxy_adapter/examples/logs/mcp_proxy_adapter.log.2 +0 -1
- mcp_proxy_adapter/examples/logs/mcp_proxy_adapter.log.3 +0 -1
- mcp_proxy_adapter/examples/logs/mcp_proxy_adapter.log.4 +0 -1
- mcp_proxy_adapter/examples/logs/mcp_proxy_adapter.log.5 +0 -1
- mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_access.log +0 -220
- mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_access.log.1 +0 -1
- mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_access.log.2 +0 -1
- mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_access.log.3 +0 -1
- mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_access.log.4 +0 -1
- mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_access.log.5 +0 -1
- mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_error.log +0 -2
- mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_error.log.1 +0 -1
- mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_error.log.2 +0 -1
- mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_error.log.3 +0 -1
- mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_error.log.4 +0 -1
- mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_error.log.5 +0 -1
- mcp_proxy_adapter/examples/roles.json +0 -38
- mcp_proxy_adapter/examples/server_configs/config_basic_http.json +0 -204
- mcp_proxy_adapter/examples/server_configs/config_http_token.json +0 -238
- mcp_proxy_adapter/examples/server_configs/config_https.json +0 -215
- mcp_proxy_adapter/examples/server_configs/config_https_token.json +0 -231
- mcp_proxy_adapter/examples/server_configs/config_mtls.json +0 -215
- mcp_proxy_adapter/examples/server_configs/config_proxy_registration.json +0 -250
- mcp_proxy_adapter/examples/server_configs/config_simple.json +0 -46
- mcp_proxy_adapter/examples/server_configs/roles.json +0 -38
- mcp_proxy_adapter-6.1.0.dist-info/METADATA +0 -205
- mcp_proxy_adapter-6.1.0.dist-info/RECORD +0 -193
- {mcp_proxy_adapter-6.1.0.dist-info → mcp_proxy_adapter-6.2.0.dist-info}/WHEEL +0 -0
- {mcp_proxy_adapter-6.1.0.dist-info → mcp_proxy_adapter-6.2.0.dist-info}/entry_points.txt +0 -0
- {mcp_proxy_adapter-6.1.0.dist-info → mcp_proxy_adapter-6.2.0.dist-info}/licenses/LICENSE +0 -0
- {mcp_proxy_adapter-6.1.0.dist-info → mcp_proxy_adapter-6.2.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,284 @@
|
|
1
|
+
"""
|
2
|
+
Client Manager for MCP Proxy Adapter Framework
|
3
|
+
|
4
|
+
This module provides client management functionality for the MCP Proxy Adapter framework.
|
5
|
+
It handles client creation, connection management, and proxy registration.
|
6
|
+
|
7
|
+
Author: Vasiliy Zdanovskiy
|
8
|
+
email: vasilyvz@gmail.com
|
9
|
+
"""
|
10
|
+
|
11
|
+
import asyncio
|
12
|
+
import json
|
13
|
+
import logging
|
14
|
+
from typing import Dict, Any, Optional, List
|
15
|
+
from pathlib import Path
|
16
|
+
|
17
|
+
from .client import UniversalClient, create_client_from_config
|
18
|
+
|
19
|
+
|
20
|
+
class ClientManager:
|
21
|
+
"""
|
22
|
+
Manages client connections and proxy registrations.
|
23
|
+
|
24
|
+
This class provides functionality for:
|
25
|
+
- Creating and managing client connections
|
26
|
+
- Proxy registration
|
27
|
+
- Connection pooling
|
28
|
+
- Authentication management
|
29
|
+
"""
|
30
|
+
|
31
|
+
def __init__(self, config: Dict[str, Any]):
|
32
|
+
"""
|
33
|
+
Initialize client manager.
|
34
|
+
|
35
|
+
Args:
|
36
|
+
config: Client manager configuration
|
37
|
+
"""
|
38
|
+
self.config = config
|
39
|
+
self.logger = logging.getLogger(__name__)
|
40
|
+
self.clients: Dict[str, UniversalClient] = {}
|
41
|
+
self.connection_pool: Dict[str, UniversalClient] = {}
|
42
|
+
|
43
|
+
# Client manager settings
|
44
|
+
self.max_connections = config.get("max_connections", 10)
|
45
|
+
self.connection_timeout = config.get("connection_timeout", 30)
|
46
|
+
self.retry_attempts = config.get("retry_attempts", 3)
|
47
|
+
self.retry_delay = config.get("retry_delay", 1)
|
48
|
+
|
49
|
+
self.logger.info("Client manager initialized")
|
50
|
+
|
51
|
+
async def create_client(self, client_id: str, config_file: str) -> UniversalClient:
|
52
|
+
"""
|
53
|
+
Create a new client instance.
|
54
|
+
|
55
|
+
Args:
|
56
|
+
client_id: Unique identifier for the client
|
57
|
+
config_file: Path to client configuration file
|
58
|
+
|
59
|
+
Returns:
|
60
|
+
UniversalClient instance
|
61
|
+
"""
|
62
|
+
try:
|
63
|
+
if client_id in self.clients:
|
64
|
+
self.logger.warning(f"Client {client_id} already exists, reusing existing connection")
|
65
|
+
return self.clients[client_id]
|
66
|
+
|
67
|
+
client = create_client_from_config(config_file)
|
68
|
+
self.clients[client_id] = client
|
69
|
+
|
70
|
+
self.logger.info(f"Client {client_id} created successfully")
|
71
|
+
return client
|
72
|
+
|
73
|
+
except Exception as e:
|
74
|
+
self.logger.error(f"Failed to create client {client_id}: {e}")
|
75
|
+
raise
|
76
|
+
|
77
|
+
async def get_client(self, client_id: str) -> Optional[UniversalClient]:
|
78
|
+
"""
|
79
|
+
Get an existing client instance.
|
80
|
+
|
81
|
+
Args:
|
82
|
+
client_id: Client identifier
|
83
|
+
|
84
|
+
Returns:
|
85
|
+
UniversalClient instance or None if not found
|
86
|
+
"""
|
87
|
+
return self.clients.get(client_id)
|
88
|
+
|
89
|
+
async def remove_client(self, client_id: str) -> bool:
|
90
|
+
"""
|
91
|
+
Remove a client instance.
|
92
|
+
|
93
|
+
Args:
|
94
|
+
client_id: Client identifier
|
95
|
+
|
96
|
+
Returns:
|
97
|
+
True if client was removed, False otherwise
|
98
|
+
"""
|
99
|
+
if client_id in self.clients:
|
100
|
+
client = self.clients[client_id]
|
101
|
+
await client.disconnect()
|
102
|
+
del self.clients[client_id]
|
103
|
+
self.logger.info(f"Client {client_id} removed")
|
104
|
+
return True
|
105
|
+
return False
|
106
|
+
|
107
|
+
async def test_client_connection(self, client_id: str) -> bool:
|
108
|
+
"""
|
109
|
+
Test connection for a specific client.
|
110
|
+
|
111
|
+
Args:
|
112
|
+
client_id: Client identifier
|
113
|
+
|
114
|
+
Returns:
|
115
|
+
True if connection is successful, False otherwise
|
116
|
+
"""
|
117
|
+
client = await self.get_client(client_id)
|
118
|
+
if not client:
|
119
|
+
self.logger.error(f"Client {client_id} not found")
|
120
|
+
return False
|
121
|
+
|
122
|
+
try:
|
123
|
+
return await client.test_connection()
|
124
|
+
except Exception as e:
|
125
|
+
self.logger.error(f"Connection test failed for client {client_id}: {e}")
|
126
|
+
return False
|
127
|
+
|
128
|
+
async def register_proxy(self, client_id: str, proxy_config: Dict[str, Any]) -> Dict[str, Any]:
|
129
|
+
"""
|
130
|
+
Register with proxy server using a specific client.
|
131
|
+
|
132
|
+
Args:
|
133
|
+
client_id: Client identifier
|
134
|
+
proxy_config: Proxy registration configuration
|
135
|
+
|
136
|
+
Returns:
|
137
|
+
Registration result
|
138
|
+
"""
|
139
|
+
client = await self.get_client(client_id)
|
140
|
+
if not client:
|
141
|
+
return {"error": f"Client {client_id} not found"}
|
142
|
+
|
143
|
+
try:
|
144
|
+
result = await client.register_proxy(proxy_config)
|
145
|
+
self.logger.info(f"Proxy registration completed for client {client_id}")
|
146
|
+
return result
|
147
|
+
except Exception as e:
|
148
|
+
self.logger.error(f"Proxy registration failed for client {client_id}: {e}")
|
149
|
+
return {"error": str(e)}
|
150
|
+
|
151
|
+
async def execute_command(self, client_id: str, command: str, params: Dict[str, Any] = None) -> Dict[str, Any]:
|
152
|
+
"""
|
153
|
+
Execute a command using a specific client.
|
154
|
+
|
155
|
+
Args:
|
156
|
+
client_id: Client identifier
|
157
|
+
command: Command name
|
158
|
+
params: Command parameters
|
159
|
+
|
160
|
+
Returns:
|
161
|
+
Command result
|
162
|
+
"""
|
163
|
+
client = await self.get_client(client_id)
|
164
|
+
if not client:
|
165
|
+
return {"error": f"Client {client_id} not found"}
|
166
|
+
|
167
|
+
try:
|
168
|
+
result = await client.execute_command(command, params or {})
|
169
|
+
self.logger.info(f"Command {command} executed for client {client_id}")
|
170
|
+
return result
|
171
|
+
except Exception as e:
|
172
|
+
self.logger.error(f"Command execution failed for client {client_id}: {e}")
|
173
|
+
return {"error": str(e)}
|
174
|
+
|
175
|
+
async def get_client_status(self, client_id: str) -> Dict[str, Any]:
|
176
|
+
"""
|
177
|
+
Get status information for a specific client.
|
178
|
+
|
179
|
+
Args:
|
180
|
+
client_id: Client identifier
|
181
|
+
|
182
|
+
Returns:
|
183
|
+
Client status information
|
184
|
+
"""
|
185
|
+
client = await self.get_client(client_id)
|
186
|
+
if not client:
|
187
|
+
return {"error": f"Client {client_id} not found"}
|
188
|
+
|
189
|
+
try:
|
190
|
+
# Test connection
|
191
|
+
connection_ok = await client.test_connection()
|
192
|
+
|
193
|
+
# Test security features
|
194
|
+
security_features = await client.test_security_features()
|
195
|
+
|
196
|
+
status = {
|
197
|
+
"client_id": client_id,
|
198
|
+
"base_url": client.base_url,
|
199
|
+
"auth_method": client.auth_method,
|
200
|
+
"connection_ok": connection_ok,
|
201
|
+
"security_features": security_features,
|
202
|
+
"session_active": client.session is not None
|
203
|
+
}
|
204
|
+
|
205
|
+
return status
|
206
|
+
except Exception as e:
|
207
|
+
self.logger.error(f"Failed to get status for client {client_id}: {e}")
|
208
|
+
return {"error": str(e)}
|
209
|
+
|
210
|
+
async def list_clients(self) -> List[str]:
|
211
|
+
"""
|
212
|
+
Get list of all client identifiers.
|
213
|
+
|
214
|
+
Returns:
|
215
|
+
List of client identifiers
|
216
|
+
"""
|
217
|
+
return list(self.clients.keys())
|
218
|
+
|
219
|
+
async def cleanup(self):
|
220
|
+
"""Clean up all client connections."""
|
221
|
+
for client_id in list(self.clients.keys()):
|
222
|
+
await self.remove_client(client_id)
|
223
|
+
self.logger.info("All client connections cleaned up")
|
224
|
+
|
225
|
+
async def __aenter__(self):
|
226
|
+
"""Async context manager entry."""
|
227
|
+
return self
|
228
|
+
|
229
|
+
async def __aexit__(self, exc_type, exc_val, exc_tb):
|
230
|
+
"""Async context manager exit."""
|
231
|
+
await self.cleanup()
|
232
|
+
|
233
|
+
|
234
|
+
def create_client_manager(config: Dict[str, Any]) -> ClientManager:
|
235
|
+
"""
|
236
|
+
Create a ClientManager instance.
|
237
|
+
|
238
|
+
Args:
|
239
|
+
config: Client manager configuration
|
240
|
+
|
241
|
+
Returns:
|
242
|
+
ClientManager instance
|
243
|
+
"""
|
244
|
+
return ClientManager(config)
|
245
|
+
|
246
|
+
|
247
|
+
# Example usage and testing functions
|
248
|
+
async def test_client_manager():
|
249
|
+
"""Test client manager functionality."""
|
250
|
+
# Example configuration
|
251
|
+
config = {
|
252
|
+
"max_connections": 5,
|
253
|
+
"connection_timeout": 30,
|
254
|
+
"retry_attempts": 3,
|
255
|
+
"retry_delay": 1
|
256
|
+
}
|
257
|
+
|
258
|
+
async with ClientManager(config) as manager:
|
259
|
+
# Create a client
|
260
|
+
client_id = "test_client"
|
261
|
+
config_file = "configs/http_simple.json"
|
262
|
+
|
263
|
+
try:
|
264
|
+
client = await manager.create_client(client_id, config_file)
|
265
|
+
print(f"✅ Client {client_id} created successfully")
|
266
|
+
|
267
|
+
# Test connection
|
268
|
+
connection_ok = await manager.test_client_connection(client_id)
|
269
|
+
print(f"✅ Connection test: {connection_ok}")
|
270
|
+
|
271
|
+
# Get status
|
272
|
+
status = await manager.get_client_status(client_id)
|
273
|
+
print(f"✅ Client status: {json.dumps(status, indent=2)}")
|
274
|
+
|
275
|
+
# Execute command
|
276
|
+
result = await manager.execute_command(client_id, "help")
|
277
|
+
print(f"✅ Command result: {json.dumps(result, indent=2)}")
|
278
|
+
|
279
|
+
except Exception as e:
|
280
|
+
print(f"❌ Test failed: {e}")
|
281
|
+
|
282
|
+
|
283
|
+
if __name__ == "__main__":
|
284
|
+
asyncio.run(test_client_manager())
|
@@ -21,19 +21,84 @@ class ProtocolManager:
|
|
21
21
|
ensuring that only configured protocols are accessible.
|
22
22
|
"""
|
23
23
|
|
24
|
-
def __init__(self):
|
25
|
-
"""
|
24
|
+
def __init__(self, app_config: Optional[Dict] = None):
|
25
|
+
"""
|
26
|
+
Initialize the protocol manager.
|
27
|
+
|
28
|
+
Args:
|
29
|
+
app_config: Application configuration dictionary (optional)
|
30
|
+
"""
|
31
|
+
self.app_config = app_config
|
26
32
|
self._load_config()
|
27
33
|
|
28
34
|
def _load_config(self):
|
29
35
|
"""Load protocol configuration from config."""
|
30
|
-
|
36
|
+
# Use provided config or fallback to global config
|
37
|
+
current_config = self.app_config if self.app_config is not None else config.get_all()
|
38
|
+
|
39
|
+
# Get protocols configuration
|
40
|
+
self.protocols_config = current_config.get("protocols", {})
|
31
41
|
self.enabled = self.protocols_config.get("enabled", True)
|
32
|
-
|
33
|
-
|
42
|
+
|
43
|
+
# Get SSL configuration to determine allowed protocols
|
44
|
+
ssl_enabled = self._is_ssl_enabled(current_config)
|
45
|
+
|
46
|
+
# Set allowed protocols based on SSL configuration
|
47
|
+
if ssl_enabled:
|
48
|
+
# If SSL is enabled, allow both HTTP and HTTPS
|
49
|
+
self.allowed_protocols = self.protocols_config.get("allowed_protocols", ["http", "https"])
|
50
|
+
# Ensure HTTPS is in allowed protocols if SSL is enabled
|
51
|
+
if "https" not in self.allowed_protocols:
|
52
|
+
self.allowed_protocols.append("https")
|
53
|
+
else:
|
54
|
+
# If SSL is disabled, only allow HTTP
|
55
|
+
self.allowed_protocols = self.protocols_config.get("allowed_protocols", ["http"])
|
56
|
+
# Remove HTTPS from allowed protocols if SSL is disabled
|
57
|
+
if "https" in self.allowed_protocols:
|
58
|
+
self.allowed_protocols.remove("https")
|
59
|
+
|
60
|
+
logger.debug(f"Protocol manager loaded config: enabled={self.enabled}, allowed_protocols={self.allowed_protocols}, ssl_enabled={ssl_enabled}")
|
61
|
+
|
62
|
+
def _is_ssl_enabled(self, current_config: Dict) -> bool:
|
63
|
+
"""
|
64
|
+
Check if SSL is enabled in configuration.
|
65
|
+
|
66
|
+
Args:
|
67
|
+
current_config: Current configuration dictionary
|
68
|
+
|
69
|
+
Returns:
|
70
|
+
True if SSL is enabled, False otherwise
|
71
|
+
"""
|
72
|
+
# Try security framework SSL config first
|
73
|
+
security_config = current_config.get("security", {})
|
74
|
+
ssl_config = security_config.get("ssl", {})
|
75
|
+
|
76
|
+
if ssl_config.get("enabled", False):
|
77
|
+
logger.debug("SSL enabled via security.ssl configuration")
|
78
|
+
return True
|
79
|
+
|
80
|
+
# Fallback to legacy SSL config
|
81
|
+
legacy_ssl_config = current_config.get("ssl", {})
|
82
|
+
if legacy_ssl_config.get("enabled", False):
|
83
|
+
logger.debug("SSL enabled via legacy ssl configuration")
|
84
|
+
return True
|
85
|
+
|
86
|
+
logger.debug("SSL is disabled in configuration")
|
87
|
+
return False
|
88
|
+
|
89
|
+
def update_config(self, new_config: Dict):
|
90
|
+
"""
|
91
|
+
Update configuration and reload protocol settings.
|
92
|
+
|
93
|
+
Args:
|
94
|
+
new_config: New configuration dictionary
|
95
|
+
"""
|
96
|
+
self.app_config = new_config
|
97
|
+
self._load_config()
|
98
|
+
logger.info(f"Protocol manager configuration updated: allowed_protocols={self.allowed_protocols}")
|
34
99
|
|
35
100
|
def reload_config(self):
|
36
|
-
"""Reload protocol configuration."""
|
101
|
+
"""Reload protocol configuration from global config."""
|
37
102
|
self._load_config()
|
38
103
|
|
39
104
|
def is_protocol_allowed(self, protocol: str) -> bool:
|
@@ -137,7 +202,11 @@ class ProtocolManager:
|
|
137
202
|
if protocol.lower() not in ["https", "mtls"]:
|
138
203
|
return None
|
139
204
|
|
140
|
-
|
205
|
+
# Use provided config or fallback to global config
|
206
|
+
current_config = self.app_config if self.app_config is not None else config.get_all()
|
207
|
+
|
208
|
+
# Get SSL configuration
|
209
|
+
ssl_config = self._get_ssl_config(current_config)
|
141
210
|
|
142
211
|
if not ssl_config.get("enabled", False):
|
143
212
|
logger.warning(f"SSL required for protocol '{protocol}' but SSL is disabled")
|
@@ -170,6 +239,33 @@ class ProtocolManager:
|
|
170
239
|
logger.error(f"Failed to create SSL context for protocol '{protocol}': {e}")
|
171
240
|
return None
|
172
241
|
|
242
|
+
def _get_ssl_config(self, current_config: Dict) -> Dict:
|
243
|
+
"""
|
244
|
+
Get SSL configuration from config.
|
245
|
+
|
246
|
+
Args:
|
247
|
+
current_config: Current configuration dictionary
|
248
|
+
|
249
|
+
Returns:
|
250
|
+
SSL configuration dictionary
|
251
|
+
"""
|
252
|
+
# Try security framework SSL config first
|
253
|
+
security_config = current_config.get("security", {})
|
254
|
+
ssl_config = security_config.get("ssl", {})
|
255
|
+
|
256
|
+
if ssl_config.get("enabled", False):
|
257
|
+
logger.debug("Using security.ssl configuration")
|
258
|
+
return ssl_config
|
259
|
+
|
260
|
+
# Fallback to legacy SSL config
|
261
|
+
legacy_ssl_config = current_config.get("ssl", {})
|
262
|
+
if legacy_ssl_config.get("enabled", False):
|
263
|
+
logger.debug("Using legacy ssl configuration")
|
264
|
+
return legacy_ssl_config
|
265
|
+
|
266
|
+
# Return empty config if SSL is disabled
|
267
|
+
return {"enabled": False}
|
268
|
+
|
173
269
|
def get_protocol_info(self) -> Dict[str, Dict]:
|
174
270
|
"""
|
175
271
|
Get information about all configured protocols.
|
@@ -222,7 +318,10 @@ class ProtocolManager:
|
|
222
318
|
|
223
319
|
# Check SSL requirements
|
224
320
|
if protocol in ["https", "mtls"]:
|
225
|
-
|
321
|
+
# Use provided config or fallback to global config
|
322
|
+
current_config = self.app_config if self.app_config is not None else config.get_all()
|
323
|
+
ssl_config = self._get_ssl_config(current_config)
|
324
|
+
|
226
325
|
if not ssl_config.get("enabled", False):
|
227
326
|
errors.append(f"Protocol '{protocol}' requires SSL but SSL is disabled")
|
228
327
|
elif not ssl_config.get("cert_file") or not ssl_config.get("key_file"):
|
@@ -231,5 +330,28 @@ class ProtocolManager:
|
|
231
330
|
return errors
|
232
331
|
|
233
332
|
|
234
|
-
# Global protocol manager instance
|
235
|
-
protocol_manager =
|
333
|
+
# Global protocol manager instance - will be updated with config when needed
|
334
|
+
protocol_manager = None
|
335
|
+
|
336
|
+
def get_protocol_manager(app_config: Optional[Dict] = None) -> ProtocolManager:
|
337
|
+
"""
|
338
|
+
Get protocol manager instance with current configuration.
|
339
|
+
|
340
|
+
Args:
|
341
|
+
app_config: Application configuration dictionary (optional)
|
342
|
+
|
343
|
+
Returns:
|
344
|
+
ProtocolManager instance
|
345
|
+
"""
|
346
|
+
global protocol_manager
|
347
|
+
|
348
|
+
# If no app_config provided, use global config
|
349
|
+
if app_config is None:
|
350
|
+
app_config = config.get_all()
|
351
|
+
|
352
|
+
# Create new instance if none exists or config changed
|
353
|
+
if protocol_manager is None or protocol_manager.app_config != app_config:
|
354
|
+
protocol_manager = ProtocolManager(app_config)
|
355
|
+
logger.info("Protocol manager created with new configuration")
|
356
|
+
|
357
|
+
return protocol_manager
|
@@ -102,18 +102,26 @@ class SecurityIntegration:
|
|
102
102
|
public_paths=security_section.get("auth", {}).get("public_paths", [])
|
103
103
|
)
|
104
104
|
|
105
|
-
# Create permission config
|
105
|
+
# Create permission config - handle null values properly
|
106
|
+
permissions_section = security_section.get("permissions", {})
|
107
|
+
roles_file = permissions_section.get("roles_file")
|
108
|
+
|
109
|
+
# If roles_file is None or empty string, don't pass it to avoid framework errors
|
110
|
+
if roles_file is None or roles_file == "":
|
111
|
+
logger.warning("roles_file is None or empty, permissions will use default configuration")
|
112
|
+
roles_file = None
|
113
|
+
|
106
114
|
permission_config = PermissionConfig(
|
107
|
-
enabled=
|
108
|
-
roles_file=
|
109
|
-
default_role=
|
110
|
-
admin_role=
|
111
|
-
role_hierarchy=
|
112
|
-
permission_cache_enabled=
|
113
|
-
permission_cache_ttl=
|
114
|
-
wildcard_permissions=
|
115
|
-
strict_mode=
|
116
|
-
roles=
|
115
|
+
enabled=permissions_section.get("enabled", True),
|
116
|
+
roles_file=roles_file,
|
117
|
+
default_role=permissions_section.get("default_role", "guest"),
|
118
|
+
admin_role=permissions_section.get("admin_role", "admin"),
|
119
|
+
role_hierarchy=permissions_section.get("role_hierarchy", {}),
|
120
|
+
permission_cache_enabled=permissions_section.get("permission_cache_enabled", True),
|
121
|
+
permission_cache_ttl=permissions_section.get("permission_cache_ttl", 300),
|
122
|
+
wildcard_permissions=permissions_section.get("wildcard_permissions", False),
|
123
|
+
strict_mode=permissions_section.get("strict_mode", True),
|
124
|
+
roles=permissions_section.get("roles")
|
117
125
|
)
|
118
126
|
|
119
127
|
# Create rate limit config
|
@@ -35,7 +35,7 @@ class ServerConfigAdapter:
|
|
35
35
|
|
36
36
|
Args:
|
37
37
|
ssl_config: Source SSL configuration
|
38
|
-
target_engine: Target engine name (
|
38
|
+
target_engine: Target engine name (hypercorn)
|
39
39
|
|
40
40
|
Returns:
|
41
41
|
Converted SSL configuration for the target engine
|
@@ -45,35 +45,12 @@ class ServerConfigAdapter:
|
|
45
45
|
logger.error(f"Unknown server engine: {target_engine}")
|
46
46
|
return {}
|
47
47
|
|
48
|
-
if target_engine == "
|
49
|
-
return ServerConfigAdapter._convert_to_uvicorn_ssl(ssl_config)
|
50
|
-
elif target_engine == "hypercorn":
|
48
|
+
if target_engine == "hypercorn":
|
51
49
|
return ServerConfigAdapter._convert_to_hypercorn_ssl(ssl_config)
|
52
50
|
else:
|
53
51
|
logger.warning(f"No SSL conversion available for engine: {target_engine}")
|
54
52
|
return {}
|
55
53
|
|
56
|
-
@staticmethod
|
57
|
-
def _convert_to_uvicorn_ssl(ssl_config: Dict[str, Any]) -> Dict[str, Any]:
|
58
|
-
"""Convert SSL configuration to uvicorn format."""
|
59
|
-
uvicorn_ssl = {}
|
60
|
-
|
61
|
-
# Map SSL parameters
|
62
|
-
if ssl_config.get("cert_file"):
|
63
|
-
uvicorn_ssl["ssl_certfile"] = ssl_config["cert_file"]
|
64
|
-
if ssl_config.get("key_file"):
|
65
|
-
uvicorn_ssl["ssl_keyfile"] = ssl_config["key_file"]
|
66
|
-
if ssl_config.get("ca_cert"):
|
67
|
-
uvicorn_ssl["ssl_ca_certs"] = ssl_config["ca_cert"]
|
68
|
-
|
69
|
-
# Map verification mode
|
70
|
-
if ssl_config.get("verify_client", False):
|
71
|
-
import ssl
|
72
|
-
uvicorn_ssl["ssl_cert_reqs"] = ssl.CERT_REQUIRED
|
73
|
-
|
74
|
-
logger.debug(f"Converted SSL config to uvicorn: {uvicorn_ssl}")
|
75
|
-
return uvicorn_ssl
|
76
|
-
|
77
54
|
@staticmethod
|
78
55
|
def _convert_to_hypercorn_ssl(ssl_config: Dict[str, Any]) -> Dict[str, Any]:
|
79
56
|
"""Convert SSL configuration to hypercorn format."""
|
@@ -99,49 +76,16 @@ class ServerConfigAdapter:
|
|
99
76
|
"""
|
100
77
|
Determine the optimal server engine for a given configuration.
|
101
78
|
|
79
|
+
Currently only hypercorn is supported.
|
80
|
+
|
102
81
|
Args:
|
103
82
|
config: Server configuration
|
104
83
|
|
105
84
|
Returns:
|
106
|
-
|
85
|
+
Optimal engine name (currently always "hypercorn")
|
107
86
|
"""
|
108
|
-
#
|
109
|
-
|
110
|
-
if not ssl_config:
|
111
|
-
# Try to get SSL config from security section
|
112
|
-
ssl_config = config.get("security", {}).get("ssl", {})
|
113
|
-
|
114
|
-
# Prefer hypercorn for all SSL/TLS scenarios due to better mTLS support
|
115
|
-
if ssl_config.get("enabled", False):
|
116
|
-
engine = ServerEngineFactory.get_engine("hypercorn")
|
117
|
-
if engine:
|
118
|
-
logger.info("Selected hypercorn for SSL/TLS support (better mTLS capabilities)")
|
119
|
-
return engine.get_name()
|
120
|
-
else:
|
121
|
-
logger.warning("SSL enabled but hypercorn not available")
|
122
|
-
|
123
|
-
# For mTLS client verification, hypercorn is required
|
124
|
-
if ssl_config.get("verify_client", False) or ssl_config.get("client_cert_required", False):
|
125
|
-
engine = ServerEngineFactory.get_engine_with_feature("mtls_client_certs")
|
126
|
-
if engine:
|
127
|
-
logger.info(f"Selected {engine.get_name()} for mTLS support")
|
128
|
-
return engine.get_name()
|
129
|
-
else:
|
130
|
-
logger.warning("mTLS required but no suitable engine available")
|
131
|
-
return None
|
132
|
-
|
133
|
-
# Default to hypercorn for better async support, fallback to uvicorn
|
134
|
-
engine = ServerEngineFactory.get_engine("hypercorn")
|
135
|
-
if engine:
|
136
|
-
logger.info("Selected hypercorn as default engine (better async support)")
|
137
|
-
return engine.get_name()
|
138
|
-
|
139
|
-
engine = ServerEngineFactory.get_engine("uvicorn")
|
140
|
-
if engine:
|
141
|
-
logger.info("Selected uvicorn as fallback engine")
|
142
|
-
return "uvicorn"
|
143
|
-
|
144
|
-
return None
|
87
|
+
# Currently only hypercorn is supported
|
88
|
+
return "hypercorn"
|
145
89
|
|
146
90
|
@staticmethod
|
147
91
|
def validate_engine_compatibility(
|
@@ -201,19 +145,18 @@ class ServerConfigAdapter:
|
|
201
145
|
|
202
146
|
class UnifiedServerRunner:
|
203
147
|
"""
|
204
|
-
Unified server runner that
|
148
|
+
Unified server runner that uses hypercorn as the default engine.
|
205
149
|
|
206
|
-
This class provides a unified interface for running servers
|
207
|
-
|
208
|
-
for the given configuration.
|
150
|
+
This class provides a unified interface for running servers using hypercorn
|
151
|
+
as the underlying engine.
|
209
152
|
"""
|
210
153
|
|
211
|
-
def __init__(self, default_engine: str = "
|
154
|
+
def __init__(self, default_engine: str = "hypercorn"):
|
212
155
|
"""
|
213
156
|
Initialize the unified server runner.
|
214
157
|
|
215
158
|
Args:
|
216
|
-
default_engine: Default engine to use
|
159
|
+
default_engine: Default engine to use (currently only hypercorn is supported)
|
217
160
|
"""
|
218
161
|
self.default_engine = default_engine
|
219
162
|
self.available_engines = ServerEngineFactory.get_available_engines()
|
@@ -228,22 +171,16 @@ class UnifiedServerRunner:
|
|
228
171
|
engine_name: Optional[str] = None
|
229
172
|
) -> None:
|
230
173
|
"""
|
231
|
-
Run server with
|
174
|
+
Run server with hypercorn engine.
|
232
175
|
|
233
176
|
Args:
|
234
177
|
app: ASGI application
|
235
178
|
config: Server configuration
|
236
|
-
engine_name:
|
179
|
+
engine_name: Engine to use (currently only hypercorn is supported)
|
237
180
|
"""
|
238
|
-
#
|
239
|
-
|
240
|
-
|
241
|
-
logger.info(f"Using specified engine: {selected_engine}")
|
242
|
-
else:
|
243
|
-
selected_engine = ServerConfigAdapter.get_optimal_engine_for_config(config)
|
244
|
-
if not selected_engine:
|
245
|
-
selected_engine = self.default_engine
|
246
|
-
logger.info(f"Using default engine: {selected_engine}")
|
181
|
+
# Use hypercorn as the only supported engine
|
182
|
+
selected_engine = "hypercorn"
|
183
|
+
logger.info(f"Using hypercorn engine")
|
247
184
|
|
248
185
|
# Validate compatibility
|
249
186
|
if not ServerConfigAdapter.validate_engine_compatibility(config, selected_engine):
|