mcp-proxy-adapter 6.0.0__py3-none-any.whl → 6.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- mcp_proxy_adapter/api/app.py +174 -80
- mcp_proxy_adapter/api/handlers.py +16 -5
- mcp_proxy_adapter/api/middleware/__init__.py +7 -2
- 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/unified_security.py +152 -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 +9 -0
- 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 +277 -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/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/__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 +37 -0
- mcp_proxy_adapter/examples/basic_framework/configs/http_simple.json +23 -0
- mcp_proxy_adapter/examples/basic_framework/configs/https_auth.json +39 -0
- mcp_proxy_adapter/examples/basic_framework/configs/https_simple.json +25 -0
- mcp_proxy_adapter/examples/basic_framework/configs/mtls_no_roles.json +39 -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_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 +639 -0
- mcp_proxy_adapter/version.py +2 -1
- mcp_proxy_adapter-6.1.0.dist-info/METADATA +205 -0
- mcp_proxy_adapter-6.1.0.dist-info/RECORD +193 -0
- mcp_proxy_adapter-6.1.0.dist-info/entry_points.txt +2 -0
- {mcp_proxy_adapter-6.0.0.dist-info → mcp_proxy_adapter-6.1.0.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.0.dist-info}/WHEEL +0 -0
- {mcp_proxy_adapter-6.0.0.dist-info → mcp_proxy_adapter-6.1.0.dist-info}/top_level.txt +0 -0
mcp_proxy_adapter/api/app.py
CHANGED
@@ -4,6 +4,7 @@ Module for FastAPI application setup.
|
|
4
4
|
|
5
5
|
import json
|
6
6
|
import ssl
|
7
|
+
from pathlib import Path
|
7
8
|
from typing import Any, Dict, List, Optional, Union
|
8
9
|
from contextlib import asynccontextmanager
|
9
10
|
|
@@ -23,68 +24,102 @@ from mcp_proxy_adapter.commands.command_registry import registry
|
|
23
24
|
from mcp_proxy_adapter.custom_openapi import custom_openapi_with_fallback
|
24
25
|
|
25
26
|
|
26
|
-
|
27
|
-
async def lifespan(app: FastAPI):
|
27
|
+
def create_lifespan(config_path: Optional[str] = None):
|
28
28
|
"""
|
29
|
-
|
30
|
-
"""
|
31
|
-
# Startup events
|
32
|
-
from mcp_proxy_adapter.commands.command_registry import registry
|
33
|
-
from mcp_proxy_adapter.core.proxy_registration import register_with_proxy, unregister_from_proxy
|
34
|
-
|
35
|
-
# Initialize system using unified logic
|
36
|
-
# This will load config, register custom commands, and discover auto-commands
|
37
|
-
init_result = await registry.reload_system()
|
38
|
-
|
39
|
-
logger.info(f"Application started with {init_result['total_commands']} commands registered")
|
40
|
-
logger.info(f"System initialization result: {init_result}")
|
41
|
-
|
42
|
-
# Register with proxy if enabled
|
43
|
-
server_config = config.get("server", {})
|
44
|
-
server_host = server_config.get("host", "0.0.0.0")
|
45
|
-
server_port = server_config.get("port", 8000)
|
46
|
-
|
47
|
-
# Determine server URL based on SSL configuration
|
48
|
-
ssl_config = config.get("ssl", {})
|
49
|
-
if ssl_config.get("enabled", False):
|
50
|
-
protocol = "https"
|
51
|
-
else:
|
52
|
-
protocol = "http"
|
53
|
-
|
54
|
-
# Use localhost for external access if host is 0.0.0.0
|
55
|
-
if server_host == "0.0.0.0":
|
56
|
-
server_host = "localhost"
|
29
|
+
Create lifespan manager for the FastAPI application.
|
57
30
|
|
58
|
-
|
59
|
-
|
60
|
-
# Attempt proxy registration
|
61
|
-
registration_success = await register_with_proxy(server_url)
|
62
|
-
if registration_success:
|
63
|
-
logger.info("✅ Proxy registration completed successfully")
|
64
|
-
else:
|
65
|
-
logger.info("ℹ️ Proxy registration is disabled or failed")
|
66
|
-
|
67
|
-
yield # Application is running
|
31
|
+
Args:
|
32
|
+
config_path: Path to configuration file (optional)
|
68
33
|
|
69
|
-
|
70
|
-
|
34
|
+
Returns:
|
35
|
+
Lifespan context manager
|
36
|
+
"""
|
37
|
+
@asynccontextmanager
|
38
|
+
async def lifespan(app: FastAPI):
|
39
|
+
"""
|
40
|
+
Lifespan manager for the FastAPI application. Handles startup and shutdown events.
|
41
|
+
"""
|
42
|
+
# Startup events
|
43
|
+
from mcp_proxy_adapter.commands.command_registry import registry
|
44
|
+
from mcp_proxy_adapter.core.proxy_registration import register_with_proxy, unregister_from_proxy
|
45
|
+
|
46
|
+
# Initialize system using unified logic
|
47
|
+
# This will load config, register custom commands, and discover auto-commands
|
48
|
+
# Only reload config if not already loaded from the same path
|
49
|
+
if config_path:
|
50
|
+
init_result = await registry.reload_system(config_path=config_path)
|
51
|
+
else:
|
52
|
+
init_result = await registry.reload_system()
|
53
|
+
|
54
|
+
logger.info(f"Application started with {init_result['total_commands']} commands registered")
|
55
|
+
logger.info(f"System initialization result: {init_result}")
|
56
|
+
|
57
|
+
# Register with proxy if enabled
|
58
|
+
server_config = config.get("server", {})
|
59
|
+
server_host = server_config.get("host", "0.0.0.0")
|
60
|
+
server_port = server_config.get("port", 8000)
|
61
|
+
|
62
|
+
# Determine server URL based on SSL configuration
|
63
|
+
# Try security framework SSL config first
|
64
|
+
security_config = config.get("security", {})
|
65
|
+
ssl_config = security_config.get("ssl", {})
|
66
|
+
|
67
|
+
# Fallback to legacy SSL config
|
68
|
+
if not ssl_config.get("enabled", False):
|
69
|
+
ssl_config = config.get("ssl", {})
|
70
|
+
|
71
|
+
if ssl_config.get("enabled", False):
|
72
|
+
protocol = "https"
|
73
|
+
else:
|
74
|
+
protocol = "http"
|
75
|
+
|
76
|
+
# Use localhost for external access if host is 0.0.0.0
|
77
|
+
if server_host == "0.0.0.0":
|
78
|
+
server_host = "localhost"
|
79
|
+
|
80
|
+
server_url = f"{protocol}://{server_host}:{server_port}"
|
81
|
+
|
82
|
+
# Attempt proxy registration
|
83
|
+
registration_success = await register_with_proxy(server_url)
|
84
|
+
if registration_success:
|
85
|
+
logger.info("✅ Proxy registration completed successfully")
|
86
|
+
else:
|
87
|
+
logger.info("ℹ️ Proxy registration is disabled or failed")
|
88
|
+
|
89
|
+
yield # Application is running
|
90
|
+
|
91
|
+
# Shutdown events
|
92
|
+
logger.info("Application shutting down")
|
93
|
+
|
94
|
+
# Unregister from proxy if enabled
|
95
|
+
unregistration_success = await unregister_from_proxy()
|
96
|
+
if unregistration_success:
|
97
|
+
logger.info("✅ Proxy unregistration completed successfully")
|
98
|
+
else:
|
99
|
+
logger.warning("⚠️ Proxy unregistration failed or was disabled")
|
71
100
|
|
72
|
-
|
73
|
-
unregistration_success = await unregister_from_proxy()
|
74
|
-
if unregistration_success:
|
75
|
-
logger.info("✅ Proxy unregistration completed successfully")
|
76
|
-
else:
|
77
|
-
logger.warning("⚠️ Proxy unregistration failed or was disabled")
|
101
|
+
return lifespan
|
78
102
|
|
79
103
|
|
80
|
-
def create_ssl_context() -> Optional[ssl.SSLContext]:
|
104
|
+
def create_ssl_context(app_config: Optional[Dict[str, Any]] = None) -> Optional[ssl.SSLContext]:
|
81
105
|
"""
|
82
106
|
Create SSL context based on configuration.
|
83
107
|
|
108
|
+
Args:
|
109
|
+
app_config: Application configuration dictionary (optional)
|
110
|
+
|
84
111
|
Returns:
|
85
112
|
SSL context if SSL is enabled and properly configured, None otherwise
|
86
113
|
"""
|
87
|
-
|
114
|
+
current_config = app_config if app_config is not None else config.get_all()
|
115
|
+
|
116
|
+
# Try security framework SSL config first
|
117
|
+
security_config = current_config.get("security", {})
|
118
|
+
ssl_config = security_config.get("ssl", {})
|
119
|
+
|
120
|
+
# Fallback to legacy SSL config
|
121
|
+
if not ssl_config.get("enabled", False):
|
122
|
+
ssl_config = current_config.get("ssl", {})
|
88
123
|
|
89
124
|
if not ssl_config.get("enabled", False):
|
90
125
|
logger.info("SSL is disabled in configuration")
|
@@ -117,7 +152,7 @@ def create_ssl_context() -> Optional[ssl.SSLContext]:
|
|
117
152
|
return None
|
118
153
|
|
119
154
|
|
120
|
-
def create_app(title: Optional[str] = None, description: Optional[str] = None, version: Optional[str] = None) -> FastAPI:
|
155
|
+
def create_app(title: Optional[str] = None, description: Optional[str] = None, version: Optional[str] = None, app_config: Optional[Dict[str, Any]] = None, config_path: Optional[str] = None) -> FastAPI:
|
121
156
|
"""
|
122
157
|
Creates and configures FastAPI application.
|
123
158
|
|
@@ -125,6 +160,8 @@ def create_app(title: Optional[str] = None, description: Optional[str] = None, v
|
|
125
160
|
title: Application title (default: "MCP Proxy Adapter")
|
126
161
|
description: Application description (default: "JSON-RPC API for interacting with MCP Proxy")
|
127
162
|
version: Application version (default: "1.0.0")
|
163
|
+
app_config: Application configuration dictionary (optional)
|
164
|
+
config_path: Path to configuration file (optional)
|
128
165
|
|
129
166
|
Returns:
|
130
167
|
Configured FastAPI application.
|
@@ -132,47 +169,111 @@ def create_app(title: Optional[str] = None, description: Optional[str] = None, v
|
|
132
169
|
Raises:
|
133
170
|
SystemExit: If authentication is enabled but required files are missing (security issue)
|
134
171
|
"""
|
172
|
+
# Use provided configuration or fallback to global config
|
173
|
+
current_config = app_config if app_config is not None else config.get_all()
|
174
|
+
|
175
|
+
# Debug: Check what config is passed to create_app
|
176
|
+
if app_config:
|
177
|
+
print(f"🔍 Debug: create_app received app_config keys: {list(app_config.keys())}")
|
178
|
+
if "security" in app_config:
|
179
|
+
ssl_config = app_config["security"].get("ssl", {})
|
180
|
+
print(f"🔍 Debug: create_app SSL config: enabled={ssl_config.get('enabled', False)}")
|
181
|
+
print(f"🔍 Debug: create_app SSL config: cert_file={ssl_config.get('cert_file')}")
|
182
|
+
print(f"🔍 Debug: create_app SSL config: key_file={ssl_config.get('key_file')}")
|
183
|
+
else:
|
184
|
+
print("🔍 Debug: create_app received no app_config, using global config")
|
185
|
+
|
135
186
|
# Security check: Validate all authentication configurations before startup
|
136
187
|
security_errors = []
|
137
188
|
|
138
|
-
|
139
|
-
|
189
|
+
print(f"🔍 Debug: current_config keys: {list(current_config.keys())}")
|
190
|
+
if "security" in current_config:
|
191
|
+
print(f"🔍 Debug: security config: {current_config['security']}")
|
192
|
+
if "roles" in current_config:
|
193
|
+
print(f"🔍 Debug: roles config: {current_config['roles']}")
|
194
|
+
|
195
|
+
# Check security framework configuration only if enabled
|
196
|
+
security_config = current_config.get("security", {})
|
197
|
+
if security_config.get("enabled", False):
|
198
|
+
# Validate security framework configuration
|
199
|
+
from mcp_proxy_adapter.core.unified_config_adapter import UnifiedConfigAdapter
|
200
|
+
adapter = UnifiedConfigAdapter()
|
201
|
+
validation_result = adapter.validate_configuration(current_config)
|
202
|
+
|
203
|
+
if not validation_result.is_valid:
|
204
|
+
security_errors.extend(validation_result.errors)
|
205
|
+
|
206
|
+
# Check SSL configuration within security framework
|
207
|
+
ssl_config = security_config.get("ssl", {})
|
208
|
+
if ssl_config.get("enabled", False):
|
209
|
+
cert_file = ssl_config.get("cert_file")
|
210
|
+
key_file = ssl_config.get("key_file")
|
211
|
+
|
212
|
+
print(f"🔍 Debug: api/app.py security.ssl: cert_file={cert_file}, key_file={key_file}")
|
213
|
+
print(f"🔍 Debug: api/app.py security.ssl: cert_file exists={Path(cert_file).exists() if cert_file else 'None'}")
|
214
|
+
print(f"🔍 Debug: api/app.py security.ssl: key_file exists={Path(key_file).exists() if key_file else 'None'}")
|
215
|
+
|
216
|
+
if cert_file and not Path(cert_file).exists():
|
217
|
+
security_errors.append(f"SSL is enabled but certificate file not found: {cert_file}")
|
218
|
+
|
219
|
+
if key_file and not Path(key_file).exists():
|
220
|
+
security_errors.append(f"SSL is enabled but private key file not found: {key_file}")
|
221
|
+
|
222
|
+
# Check mTLS configuration
|
223
|
+
ca_cert_file = ssl_config.get("ca_cert_file")
|
224
|
+
if ca_cert_file and not Path(ca_cert_file).exists():
|
225
|
+
security_errors.append(f"mTLS is enabled but CA certificate file not found: {ca_cert_file}")
|
226
|
+
|
227
|
+
# Legacy configuration checks for backward compatibility
|
228
|
+
roles_config = current_config.get("roles", {})
|
229
|
+
print(f"🔍 Debug: roles_config = {roles_config}")
|
140
230
|
if roles_config.get("enabled", False):
|
141
231
|
roles_config_path = roles_config.get("config_file", "schemas/roles_schema.json")
|
142
|
-
|
232
|
+
print(f"🔍 Debug: Checking roles file: {roles_config_path}")
|
143
233
|
if not Path(roles_config_path).exists():
|
144
234
|
security_errors.append(f"Roles are enabled but schema file not found: {roles_config_path}")
|
145
235
|
|
146
|
-
# Check
|
147
|
-
|
148
|
-
|
236
|
+
# Check new security framework permissions configuration
|
237
|
+
security_config = current_config.get("security", {})
|
238
|
+
permissions_config = security_config.get("permissions", {})
|
239
|
+
if permissions_config.get("enabled", False):
|
240
|
+
roles_file = permissions_config.get("roles_file")
|
241
|
+
if roles_file and not Path(roles_file).exists():
|
242
|
+
security_errors.append(f"Permissions are enabled but roles file not found: {roles_file}")
|
243
|
+
|
244
|
+
legacy_ssl_config = current_config.get("ssl", {})
|
245
|
+
if legacy_ssl_config.get("enabled", False):
|
149
246
|
# Check SSL certificate files
|
150
|
-
cert_file =
|
151
|
-
key_file =
|
247
|
+
cert_file = legacy_ssl_config.get("cert_file")
|
248
|
+
key_file = legacy_ssl_config.get("key_file")
|
249
|
+
|
250
|
+
print(f"🔍 Debug: api/app.py legacy.ssl: cert_file={cert_file}, key_file={key_file}")
|
251
|
+
print(f"🔍 Debug: api/app.py legacy.ssl: cert_file exists={Path(cert_file).exists() if cert_file else 'None'}")
|
252
|
+
print(f"🔍 Debug: api/app.py legacy.ssl: key_file exists={Path(key_file).exists() if key_file else 'None'}")
|
152
253
|
|
153
254
|
if cert_file and not Path(cert_file).exists():
|
154
|
-
security_errors.append(f"SSL is enabled but certificate file not found: {cert_file}")
|
255
|
+
security_errors.append(f"Legacy SSL is enabled but certificate file not found: {cert_file}")
|
155
256
|
|
156
257
|
if key_file and not Path(key_file).exists():
|
157
|
-
security_errors.append(f"SSL is enabled but private key file not found: {key_file}")
|
258
|
+
security_errors.append(f"Legacy SSL is enabled but private key file not found: {key_file}")
|
158
259
|
|
159
260
|
# Check mTLS configuration
|
160
|
-
if
|
161
|
-
ca_cert =
|
261
|
+
if legacy_ssl_config.get("mode") == "mtls":
|
262
|
+
ca_cert = legacy_ssl_config.get("ca_cert")
|
162
263
|
if ca_cert and not Path(ca_cert).exists():
|
163
|
-
security_errors.append(f"mTLS is enabled but CA certificate file not found: {ca_cert}")
|
264
|
+
security_errors.append(f"Legacy mTLS is enabled but CA certificate file not found: {ca_cert}")
|
164
265
|
|
165
266
|
# Check token authentication configuration
|
166
|
-
token_auth_config =
|
267
|
+
token_auth_config = legacy_ssl_config.get("token_auth", {})
|
167
268
|
if token_auth_config.get("enabled", False):
|
168
269
|
tokens_file = token_auth_config.get("tokens_file", "tokens.json")
|
169
270
|
if not Path(tokens_file).exists():
|
170
271
|
security_errors.append(f"Token authentication is enabled but tokens file not found: {tokens_file}")
|
171
272
|
|
172
273
|
# Check general authentication
|
173
|
-
if
|
274
|
+
if current_config.get("auth_enabled", False):
|
174
275
|
# If auth is enabled, check if any authentication method is properly configured
|
175
|
-
ssl_enabled =
|
276
|
+
ssl_enabled = legacy_ssl_config.get("enabled", False)
|
176
277
|
roles_enabled = roles_config.get("enabled", False)
|
177
278
|
token_auth_enabled = token_auth_config.get("enabled", False)
|
178
279
|
|
@@ -200,7 +301,7 @@ def create_app(title: Optional[str] = None, description: Optional[str] = None, v
|
|
200
301
|
version=app_version,
|
201
302
|
docs_url="/docs",
|
202
303
|
redoc_url="/redoc",
|
203
|
-
lifespan=
|
304
|
+
lifespan=create_lifespan(config_path),
|
204
305
|
)
|
205
306
|
|
206
307
|
# Configure CORS
|
@@ -213,7 +314,7 @@ def create_app(title: Optional[str] = None, description: Optional[str] = None, v
|
|
213
314
|
)
|
214
315
|
|
215
316
|
# Setup middleware using the new middleware package
|
216
|
-
setup_middleware(app)
|
317
|
+
setup_middleware(app, current_config)
|
217
318
|
|
218
319
|
# Use custom OpenAPI schema
|
219
320
|
app.openapi = lambda: custom_openapi_with_fallback(app)
|
@@ -259,7 +360,7 @@ def create_app(title: Optional[str] = None, description: Optional[str] = None, v
|
|
259
360
|
return await handle_batch_json_rpc(request_data, request)
|
260
361
|
else:
|
261
362
|
# Process single request
|
262
|
-
return await handle_json_rpc(request_data, request_id)
|
363
|
+
return await handle_json_rpc(request_data, request_id, request)
|
263
364
|
|
264
365
|
# Command execution endpoint (/cmd)
|
265
366
|
@app.post("/cmd")
|
@@ -295,7 +396,7 @@ def create_app(title: Optional[str] = None, description: Optional[str] = None, v
|
|
295
396
|
# Determine request format (CommandRequest or JSON-RPC)
|
296
397
|
if "jsonrpc" in command_data and "method" in command_data:
|
297
398
|
# JSON-RPC format
|
298
|
-
return await handle_json_rpc(command_data, request_id)
|
399
|
+
return await handle_json_rpc(command_data, request_id, request)
|
299
400
|
|
300
401
|
# CommandRequest format
|
301
402
|
if "command" not in command_data:
|
@@ -330,7 +431,7 @@ def create_app(title: Optional[str] = None, description: Optional[str] = None, v
|
|
330
431
|
|
331
432
|
# Execute command
|
332
433
|
try:
|
333
|
-
result = await execute_command(command_name, params, request_id)
|
434
|
+
result = await execute_command(command_name, params, request_id, request)
|
334
435
|
return {"result": result}
|
335
436
|
except MicroserviceError as e:
|
336
437
|
# Handle command execution errors
|
@@ -397,7 +498,7 @@ def create_app(title: Optional[str] = None, description: Optional[str] = None, v
|
|
397
498
|
request_id = getattr(request.state, "request_id", None)
|
398
499
|
|
399
500
|
try:
|
400
|
-
result = await execute_command(command_name, params, request_id)
|
501
|
+
result = await execute_command(command_name, params, request_id, request)
|
401
502
|
return result
|
402
503
|
except MicroserviceError as e:
|
403
504
|
# Convert to proper HTTP status code
|
@@ -568,10 +669,3 @@ def create_app(title: Optional[str] = None, description: Optional[str] = None, v
|
|
568
669
|
)
|
569
670
|
|
570
671
|
return app
|
571
|
-
|
572
|
-
|
573
|
-
|
574
|
-
|
575
|
-
|
576
|
-
# Create global application instance
|
577
|
-
app = create_app()
|
@@ -17,7 +17,7 @@ from mcp_proxy_adapter.core.errors import (
|
|
17
17
|
from mcp_proxy_adapter.core.logging import logger, RequestLogger, get_logger
|
18
18
|
|
19
19
|
|
20
|
-
async def execute_command(command_name: str, params: Dict[str, Any], request_id: Optional[str] = None) -> Dict[str, Any]:
|
20
|
+
async def execute_command(command_name: str, params: Dict[str, Any], request_id: Optional[str] = None, request: Optional[Request] = None) -> Dict[str, Any]:
|
21
21
|
"""
|
22
22
|
Executes a command with the specified name and parameters.
|
23
23
|
|
@@ -52,7 +52,18 @@ async def execute_command(command_name: str, params: Dict[str, Any], request_id:
|
|
52
52
|
|
53
53
|
# Use Command.run that handles instances with dependencies properly
|
54
54
|
command_class = registry.get_command(command_name)
|
55
|
-
|
55
|
+
|
56
|
+
# Create context with user info from request state
|
57
|
+
context = {}
|
58
|
+
if request and hasattr(request.state, 'user_id'):
|
59
|
+
context['user'] = {
|
60
|
+
'id': getattr(request.state, 'user_id', None),
|
61
|
+
'role': getattr(request.state, 'user_role', 'guest'),
|
62
|
+
'roles': getattr(request.state, 'user_roles', ['guest']),
|
63
|
+
'permissions': getattr(request.state, 'user_permissions', ['read'])
|
64
|
+
}
|
65
|
+
|
66
|
+
result = await command_class.run(**params, context=context)
|
56
67
|
|
57
68
|
execution_time = time.time() - start_time
|
58
69
|
|
@@ -97,13 +108,13 @@ async def handle_batch_json_rpc(batch_requests: List[Dict[str, Any]], request: O
|
|
97
108
|
|
98
109
|
for request_data in batch_requests:
|
99
110
|
# Process each request in the batch
|
100
|
-
response = await handle_json_rpc(request_data, request_id)
|
111
|
+
response = await handle_json_rpc(request_data, request_id, request)
|
101
112
|
responses.append(response)
|
102
113
|
|
103
114
|
return responses
|
104
115
|
|
105
116
|
|
106
|
-
async def handle_json_rpc(request_data: Dict[str, Any], request_id: Optional[str] = None) -> Dict[str, Any]:
|
117
|
+
async def handle_json_rpc(request_data: Dict[str, Any], request_id: Optional[str] = None, request: Optional[Request] = None) -> Dict[str, Any]:
|
107
118
|
"""
|
108
119
|
Handles JSON-RPC request.
|
109
120
|
|
@@ -139,7 +150,7 @@ async def handle_json_rpc(request_data: Dict[str, Any], request_id: Optional[str
|
|
139
150
|
|
140
151
|
try:
|
141
152
|
# Execute command
|
142
|
-
result = await execute_command(method, params, request_id)
|
153
|
+
result = await execute_command(method, params, request_id, request)
|
143
154
|
|
144
155
|
# Form successful response
|
145
156
|
return {
|
@@ -3,6 +3,7 @@ Middleware package for API.
|
|
3
3
|
This package contains middleware components for request processing.
|
4
4
|
"""
|
5
5
|
|
6
|
+
from typing import Dict, Any, Optional
|
6
7
|
from fastapi import FastAPI
|
7
8
|
|
8
9
|
from mcp_proxy_adapter.core.logging import logger
|
@@ -11,15 +12,19 @@ from .base import BaseMiddleware
|
|
11
12
|
from .factory import MiddlewareFactory
|
12
13
|
from .protocol_middleware import setup_protocol_middleware
|
13
14
|
|
14
|
-
def setup_middleware(app: FastAPI) -> None:
|
15
|
+
def setup_middleware(app: FastAPI, app_config: Optional[Dict[str, Any]] = None) -> None:
|
15
16
|
"""
|
16
17
|
Sets up middleware for application using the new middleware factory.
|
17
18
|
|
18
19
|
Args:
|
19
20
|
app: FastAPI application instance.
|
21
|
+
app_config: Application configuration dictionary (optional)
|
20
22
|
"""
|
23
|
+
# Use provided configuration or fallback to global config
|
24
|
+
current_config = app_config if app_config is not None else config.get_all()
|
25
|
+
|
21
26
|
# Create middleware factory
|
22
|
-
factory = MiddlewareFactory(app,
|
27
|
+
factory = MiddlewareFactory(app, current_config)
|
23
28
|
|
24
29
|
# Validate middleware configuration
|
25
30
|
if not factory.validate_middleware_config():
|
@@ -0,0 +1,148 @@
|
|
1
|
+
"""
|
2
|
+
Command Permission Middleware
|
3
|
+
|
4
|
+
This middleware checks permissions for specific commands based on user roles.
|
5
|
+
|
6
|
+
Author: Vasiliy Zdanovskiy
|
7
|
+
email: vasilyvz@gmail.com
|
8
|
+
"""
|
9
|
+
|
10
|
+
import json
|
11
|
+
import logging
|
12
|
+
from typing import Dict, Any, Optional, Callable, Awaitable
|
13
|
+
from fastapi import Request, Response
|
14
|
+
from starlette.middleware.base import BaseHTTPMiddleware
|
15
|
+
|
16
|
+
from mcp_proxy_adapter.core.logging import logger
|
17
|
+
|
18
|
+
|
19
|
+
class CommandPermissionMiddleware(BaseHTTPMiddleware):
|
20
|
+
"""
|
21
|
+
Middleware for checking command permissions.
|
22
|
+
|
23
|
+
This middleware checks if the authenticated user has the required
|
24
|
+
permissions to execute specific commands.
|
25
|
+
"""
|
26
|
+
|
27
|
+
def __init__(self, app, config: Dict[str, Any]):
|
28
|
+
"""
|
29
|
+
Initialize command permission middleware.
|
30
|
+
|
31
|
+
Args:
|
32
|
+
app: FastAPI application
|
33
|
+
config: Configuration dictionary
|
34
|
+
"""
|
35
|
+
super().__init__(app)
|
36
|
+
self.config = config
|
37
|
+
|
38
|
+
# Define command permissions
|
39
|
+
self.command_permissions = {
|
40
|
+
"echo": ["read"],
|
41
|
+
"health": ["read"],
|
42
|
+
"role_test": ["read"],
|
43
|
+
"config": ["read"],
|
44
|
+
"help": ["read"],
|
45
|
+
# Add more commands as needed
|
46
|
+
}
|
47
|
+
|
48
|
+
logger.info("Command permission middleware initialized")
|
49
|
+
|
50
|
+
async def dispatch(self, request: Request, call_next: Callable[[Request], Awaitable[Response]]) -> Response:
|
51
|
+
"""
|
52
|
+
Process request and check command permissions.
|
53
|
+
|
54
|
+
Args:
|
55
|
+
request: Request object
|
56
|
+
call_next: Next handler
|
57
|
+
|
58
|
+
Returns:
|
59
|
+
Response object
|
60
|
+
"""
|
61
|
+
# Only check permissions for /cmd endpoint
|
62
|
+
if request.url.path != "/cmd":
|
63
|
+
return await call_next(request)
|
64
|
+
|
65
|
+
try:
|
66
|
+
# Get request body
|
67
|
+
body = await request.body()
|
68
|
+
if not body:
|
69
|
+
return await call_next(request)
|
70
|
+
|
71
|
+
# Parse JSON-RPC request
|
72
|
+
try:
|
73
|
+
data = json.loads(body)
|
74
|
+
except json.JSONDecodeError:
|
75
|
+
return await call_next(request)
|
76
|
+
|
77
|
+
# Extract method (command name)
|
78
|
+
method = data.get("method")
|
79
|
+
if not method:
|
80
|
+
return await call_next(request)
|
81
|
+
|
82
|
+
# Check if method requires permissions
|
83
|
+
if method not in self.command_permissions:
|
84
|
+
return await call_next(request)
|
85
|
+
|
86
|
+
required_permissions = self.command_permissions[method]
|
87
|
+
|
88
|
+
# Get user info from request state
|
89
|
+
user_info = getattr(request.state, "user", None)
|
90
|
+
if not user_info:
|
91
|
+
logger.warning(f"No user info found for command {method}")
|
92
|
+
return await call_next(request)
|
93
|
+
|
94
|
+
user_roles = user_info.get("roles", [])
|
95
|
+
user_permissions = user_info.get("permissions", [])
|
96
|
+
|
97
|
+
logger.debug(f"Checking permissions for {method}: user_roles={user_roles}, required={required_permissions}")
|
98
|
+
|
99
|
+
# Check if user has required permissions
|
100
|
+
has_permission = self._check_permissions(user_roles, user_permissions, required_permissions)
|
101
|
+
|
102
|
+
if not has_permission:
|
103
|
+
logger.warning(f"Permission denied for {method}: user_roles={user_roles}, required={required_permissions}")
|
104
|
+
|
105
|
+
# Return permission denied response
|
106
|
+
error_response = {
|
107
|
+
"error": {
|
108
|
+
"code": 403,
|
109
|
+
"message": f"Permission denied: {method} requires {required_permissions}",
|
110
|
+
"type": "permission_denied"
|
111
|
+
}
|
112
|
+
}
|
113
|
+
|
114
|
+
return Response(
|
115
|
+
content=json.dumps(error_response),
|
116
|
+
status_code=403,
|
117
|
+
media_type="application/json"
|
118
|
+
)
|
119
|
+
|
120
|
+
logger.debug(f"Permission granted for {method}")
|
121
|
+
return await call_next(request)
|
122
|
+
|
123
|
+
except Exception as e:
|
124
|
+
logger.error(f"Error in command permission middleware: {e}")
|
125
|
+
return await call_next(request)
|
126
|
+
|
127
|
+
def _check_permissions(self, user_roles: list, user_permissions: list, required_permissions: list) -> bool:
|
128
|
+
"""
|
129
|
+
Check if user has required permissions.
|
130
|
+
|
131
|
+
Args:
|
132
|
+
user_roles: User roles
|
133
|
+
user_permissions: User permissions
|
134
|
+
required_permissions: Required permissions
|
135
|
+
|
136
|
+
Returns:
|
137
|
+
True if user has required permissions
|
138
|
+
"""
|
139
|
+
# Admin has all permissions
|
140
|
+
if "admin" in user_roles or "*" in user_permissions:
|
141
|
+
return True
|
142
|
+
|
143
|
+
# Check if user has all required permissions
|
144
|
+
for required in required_permissions:
|
145
|
+
if required not in user_permissions:
|
146
|
+
return False
|
147
|
+
|
148
|
+
return True
|