mcp-proxy-adapter 4.1.1__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/__main__.py +12 -0
- mcp_proxy_adapter/api/app.py +254 -33
- mcp_proxy_adapter/api/handlers.py +32 -6
- mcp_proxy_adapter/api/middleware/__init__.py +36 -30
- mcp_proxy_adapter/api/middleware/command_permission_middleware.py +148 -0
- mcp_proxy_adapter/api/middleware/error_handling.py +9 -0
- mcp_proxy_adapter/api/middleware/factory.py +243 -0
- mcp_proxy_adapter/api/middleware/logging.py +32 -6
- mcp_proxy_adapter/api/middleware/protocol_middleware.py +135 -0
- mcp_proxy_adapter/api/middleware/transport_middleware.py +122 -0
- 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 +19 -4
- mcp_proxy_adapter/commands/auth_validation_command.py +408 -0
- mcp_proxy_adapter/commands/base.py +66 -32
- mcp_proxy_adapter/commands/builtin_commands.py +95 -0
- mcp_proxy_adapter/commands/catalog_manager.py +838 -0
- mcp_proxy_adapter/commands/cert_monitor_command.py +620 -0
- mcp_proxy_adapter/commands/certificate_management_command.py +608 -0
- mcp_proxy_adapter/commands/command_registry.py +711 -354
- mcp_proxy_adapter/commands/dependency_manager.py +245 -0
- mcp_proxy_adapter/commands/echo_command.py +81 -0
- mcp_proxy_adapter/commands/health_command.py +7 -0
- mcp_proxy_adapter/commands/help_command.py +21 -14
- mcp_proxy_adapter/commands/hooks.py +200 -167
- mcp_proxy_adapter/commands/key_management_command.py +506 -0
- mcp_proxy_adapter/commands/load_command.py +176 -0
- mcp_proxy_adapter/commands/plugins_command.py +235 -0
- mcp_proxy_adapter/commands/protocol_management_command.py +232 -0
- mcp_proxy_adapter/commands/proxy_registration_command.py +409 -0
- mcp_proxy_adapter/commands/reload_command.py +48 -50
- mcp_proxy_adapter/commands/result.py +1 -0
- mcp_proxy_adapter/commands/role_test_command.py +141 -0
- mcp_proxy_adapter/commands/roles_management_command.py +697 -0
- mcp_proxy_adapter/commands/security_command.py +488 -0
- mcp_proxy_adapter/commands/ssl_setup_command.py +483 -0
- mcp_proxy_adapter/commands/token_management_command.py +529 -0
- mcp_proxy_adapter/commands/transport_management_command.py +144 -0
- mcp_proxy_adapter/commands/unload_command.py +158 -0
- mcp_proxy_adapter/config.py +159 -2
- mcp_proxy_adapter/core/app_factory.py +326 -0
- mcp_proxy_adapter/core/auth_validator.py +606 -0
- mcp_proxy_adapter/core/certificate_utils.py +827 -0
- mcp_proxy_adapter/core/client_security.py +384 -0
- mcp_proxy_adapter/core/config_converter.py +405 -0
- mcp_proxy_adapter/core/config_validator.py +218 -0
- mcp_proxy_adapter/core/logging.py +19 -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 +235 -0
- mcp_proxy_adapter/core/proxy_client.py +602 -0
- mcp_proxy_adapter/core/proxy_registration.py +522 -0
- mcp_proxy_adapter/core/role_utils.py +426 -0
- mcp_proxy_adapter/core/security_adapter.py +370 -0
- mcp_proxy_adapter/core/security_factory.py +239 -0
- 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/settings.py +1 -0
- mcp_proxy_adapter/core/ssl_utils.py +233 -0
- mcp_proxy_adapter/core/transport_manager.py +292 -0
- mcp_proxy_adapter/core/unified_config_adapter.py +579 -0
- mcp_proxy_adapter/custom_openapi.py +22 -11
- 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 +186 -0
- 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-4.1.1.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/rate_limit.py +0 -152
- mcp_proxy_adapter/commands/reload_settings_command.py +0 -125
- 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 -35
- mcp_proxy_adapter/examples/basic_server/custom_settings_example.py +0 -238
- mcp_proxy_adapter/examples/basic_server/server.py +0 -103
- 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 -250
- 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/config.json +0 -35
- 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/hooks.py +0 -230
- mcp_proxy_adapter/examples/custom_commands/intercept_command.py +0 -123
- mcp_proxy_adapter/examples/custom_commands/manual_echo_command.py +0 -103
- mcp_proxy_adapter/examples/custom_commands/server.py +0 -228
- mcp_proxy_adapter/examples/custom_commands/test_hooks.py +0 -176
- 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/schemas/base_schema.json +0 -114
- mcp_proxy_adapter/schemas/openapi_schema.json +0 -314
- 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 -217
- mcp_proxy_adapter-4.1.1.dist-info/METADATA +0 -200
- mcp_proxy_adapter-4.1.1.dist-info/RECORD +0 -110
- {mcp_proxy_adapter-4.1.1.dist-info → mcp_proxy_adapter-6.1.0.dist-info}/WHEEL +0 -0
- {mcp_proxy_adapter-4.1.1.dist-info → mcp_proxy_adapter-6.1.0.dist-info}/top_level.txt +0 -0
@@ -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
|
@@ -17,6 +17,15 @@ class ErrorHandlingMiddleware(BaseMiddleware):
|
|
17
17
|
Middleware for handling and formatting errors.
|
18
18
|
"""
|
19
19
|
|
20
|
+
def __init__(self, app):
|
21
|
+
"""
|
22
|
+
Initialize error handling middleware.
|
23
|
+
|
24
|
+
Args:
|
25
|
+
app: FastAPI application
|
26
|
+
"""
|
27
|
+
super().__init__(app)
|
28
|
+
|
20
29
|
async def dispatch(self, request: Request, call_next: Callable[[Request], Awaitable[Response]]) -> Response:
|
21
30
|
"""
|
22
31
|
Processes request and catches errors.
|
@@ -0,0 +1,243 @@
|
|
1
|
+
"""
|
2
|
+
Middleware Factory for creating and managing middleware components.
|
3
|
+
|
4
|
+
This module provides a factory for creating middleware components with proper
|
5
|
+
configuration and dependency management.
|
6
|
+
"""
|
7
|
+
|
8
|
+
import logging
|
9
|
+
from typing import Dict, Any, List, Optional, Type
|
10
|
+
|
11
|
+
from fastapi import FastAPI
|
12
|
+
|
13
|
+
from mcp_proxy_adapter.core.logging import logger
|
14
|
+
from mcp_proxy_adapter.core.security_factory import SecurityFactory
|
15
|
+
from .base import BaseMiddleware
|
16
|
+
from .unified_security import UnifiedSecurityMiddleware
|
17
|
+
from .error_handling import ErrorHandlingMiddleware
|
18
|
+
from .logging import LoggingMiddleware
|
19
|
+
from .user_info_middleware import UserInfoMiddleware
|
20
|
+
|
21
|
+
|
22
|
+
class MiddlewareFactory:
|
23
|
+
"""
|
24
|
+
Factory for creating and managing middleware components.
|
25
|
+
|
26
|
+
Provides methods to create middleware components with proper configuration
|
27
|
+
and dependency management.
|
28
|
+
"""
|
29
|
+
|
30
|
+
def __init__(self, app: FastAPI, config: Dict[str, Any]):
|
31
|
+
"""
|
32
|
+
Initialize middleware factory.
|
33
|
+
|
34
|
+
Args:
|
35
|
+
app: FastAPI application
|
36
|
+
config: Application configuration
|
37
|
+
"""
|
38
|
+
self.app = app
|
39
|
+
self.config = config
|
40
|
+
self.middleware_stack: List[BaseMiddleware] = []
|
41
|
+
|
42
|
+
logger.info("Middleware factory initialized")
|
43
|
+
|
44
|
+
def create_security_middleware(self) -> Optional[UnifiedSecurityMiddleware]:
|
45
|
+
"""
|
46
|
+
Create unified security middleware.
|
47
|
+
|
48
|
+
Returns:
|
49
|
+
UnifiedSecurityMiddleware instance or None if creation failed
|
50
|
+
"""
|
51
|
+
try:
|
52
|
+
security_config = self.config.get("security", {})
|
53
|
+
|
54
|
+
if not security_config.get("enabled", True):
|
55
|
+
logger.info("Security middleware disabled by configuration")
|
56
|
+
return None
|
57
|
+
|
58
|
+
middleware = UnifiedSecurityMiddleware(self.app, self.config)
|
59
|
+
self.middleware_stack.append(middleware)
|
60
|
+
|
61
|
+
logger.info("Unified security middleware created successfully")
|
62
|
+
return middleware
|
63
|
+
|
64
|
+
except Exception as e:
|
65
|
+
logger.error(f"Failed to create unified security middleware: {e}")
|
66
|
+
return None
|
67
|
+
|
68
|
+
def create_error_handling_middleware(self) -> Optional[ErrorHandlingMiddleware]:
|
69
|
+
"""
|
70
|
+
Create error handling middleware.
|
71
|
+
|
72
|
+
Returns:
|
73
|
+
ErrorHandlingMiddleware instance or None if creation failed
|
74
|
+
"""
|
75
|
+
try:
|
76
|
+
# Import here to avoid circular imports
|
77
|
+
from .error_handling import ErrorHandlingMiddleware
|
78
|
+
|
79
|
+
middleware = ErrorHandlingMiddleware(self.app)
|
80
|
+
self.middleware_stack.append(middleware)
|
81
|
+
|
82
|
+
logger.info("Error handling middleware created successfully")
|
83
|
+
return middleware
|
84
|
+
|
85
|
+
except Exception as e:
|
86
|
+
logger.error(f"Failed to create error handling middleware: {e}")
|
87
|
+
return None
|
88
|
+
|
89
|
+
def create_logging_middleware(self) -> Optional[LoggingMiddleware]:
|
90
|
+
"""
|
91
|
+
Create logging middleware.
|
92
|
+
|
93
|
+
Returns:
|
94
|
+
LoggingMiddleware instance or None if creation failed
|
95
|
+
"""
|
96
|
+
try:
|
97
|
+
# Import here to avoid circular imports
|
98
|
+
from .logging import LoggingMiddleware
|
99
|
+
|
100
|
+
middleware = LoggingMiddleware(self.app, self.config)
|
101
|
+
self.middleware_stack.append(middleware)
|
102
|
+
|
103
|
+
logger.info("Logging middleware created successfully")
|
104
|
+
return middleware
|
105
|
+
|
106
|
+
except Exception as e:
|
107
|
+
logger.error(f"Failed to create logging middleware: {e}")
|
108
|
+
return None
|
109
|
+
|
110
|
+
def create_user_info_middleware(self) -> Optional[UserInfoMiddleware]:
|
111
|
+
"""
|
112
|
+
Create user info middleware.
|
113
|
+
|
114
|
+
Returns:
|
115
|
+
UserInfoMiddleware instance or None if creation failed
|
116
|
+
"""
|
117
|
+
try:
|
118
|
+
middleware = UserInfoMiddleware(self.app, self.config)
|
119
|
+
self.middleware_stack.append(middleware)
|
120
|
+
|
121
|
+
logger.info("User info middleware created successfully")
|
122
|
+
return middleware
|
123
|
+
|
124
|
+
except Exception as e:
|
125
|
+
logger.error(f"Failed to create user info middleware: {e}")
|
126
|
+
return None
|
127
|
+
|
128
|
+
|
129
|
+
|
130
|
+
def create_all_middleware(self) -> List[BaseMiddleware]:
|
131
|
+
"""
|
132
|
+
Create all required middleware components.
|
133
|
+
|
134
|
+
Returns:
|
135
|
+
List of created middleware instances
|
136
|
+
"""
|
137
|
+
middleware_list = []
|
138
|
+
|
139
|
+
# Create security middleware (unified)
|
140
|
+
security_middleware = self.create_security_middleware()
|
141
|
+
if security_middleware:
|
142
|
+
middleware_list.append(security_middleware)
|
143
|
+
|
144
|
+
# Create error handling middleware
|
145
|
+
error_middleware = self.create_error_handling_middleware()
|
146
|
+
if error_middleware:
|
147
|
+
middleware_list.append(error_middleware)
|
148
|
+
|
149
|
+
# Create logging middleware
|
150
|
+
logging_middleware = self.create_logging_middleware()
|
151
|
+
if logging_middleware:
|
152
|
+
middleware_list.append(logging_middleware)
|
153
|
+
|
154
|
+
# Create user info middleware
|
155
|
+
user_info_middleware = self.create_user_info_middleware()
|
156
|
+
if user_info_middleware:
|
157
|
+
middleware_list.append(user_info_middleware)
|
158
|
+
|
159
|
+
logger.info(f"Created {len(middleware_list)} middleware components")
|
160
|
+
return middleware_list
|
161
|
+
|
162
|
+
|
163
|
+
|
164
|
+
def get_middleware_by_type(self, middleware_type: Type[BaseMiddleware]) -> Optional[BaseMiddleware]:
|
165
|
+
"""
|
166
|
+
Get middleware instance by type.
|
167
|
+
|
168
|
+
Args:
|
169
|
+
middleware_type: Type of middleware to find
|
170
|
+
|
171
|
+
Returns:
|
172
|
+
Middleware instance or None if not found
|
173
|
+
"""
|
174
|
+
for middleware in self.middleware_stack:
|
175
|
+
if isinstance(middleware, middleware_type):
|
176
|
+
return middleware
|
177
|
+
return None
|
178
|
+
|
179
|
+
def get_security_middleware(self) -> Optional[UnifiedSecurityMiddleware]:
|
180
|
+
"""
|
181
|
+
Get unified security middleware instance.
|
182
|
+
|
183
|
+
Returns:
|
184
|
+
UnifiedSecurityMiddleware instance or None if not found
|
185
|
+
"""
|
186
|
+
return self.get_middleware_by_type(UnifiedSecurityMiddleware)
|
187
|
+
|
188
|
+
def validate_middleware_config(self) -> bool:
|
189
|
+
"""
|
190
|
+
Validate middleware configuration.
|
191
|
+
|
192
|
+
Returns:
|
193
|
+
True if configuration is valid, False otherwise
|
194
|
+
"""
|
195
|
+
try:
|
196
|
+
security_config = self.config.get("security", {})
|
197
|
+
|
198
|
+
# Validate security configuration
|
199
|
+
if not SecurityFactory.validate_config(self.config):
|
200
|
+
logger.error("Security configuration validation failed")
|
201
|
+
return False
|
202
|
+
|
203
|
+
# Validate middleware-specific configurations
|
204
|
+
if security_config.get("enabled", True):
|
205
|
+
# Check required fields for security middleware
|
206
|
+
auth_config = security_config.get("auth", {})
|
207
|
+
if not isinstance(auth_config, dict):
|
208
|
+
logger.error("Auth configuration must be a dictionary")
|
209
|
+
return False
|
210
|
+
|
211
|
+
ssl_config = security_config.get("ssl", {})
|
212
|
+
if not isinstance(ssl_config, dict):
|
213
|
+
logger.error("SSL configuration must be a dictionary")
|
214
|
+
return False
|
215
|
+
|
216
|
+
logger.info("Middleware configuration validation passed")
|
217
|
+
return True
|
218
|
+
|
219
|
+
except Exception as e:
|
220
|
+
logger.error(f"Middleware configuration validation failed: {e}")
|
221
|
+
return False
|
222
|
+
|
223
|
+
def get_middleware_info(self) -> Dict[str, Any]:
|
224
|
+
"""
|
225
|
+
Get information about created middleware.
|
226
|
+
|
227
|
+
Returns:
|
228
|
+
Dictionary with middleware information
|
229
|
+
"""
|
230
|
+
info = {
|
231
|
+
"total_middleware": len(self.middleware_stack),
|
232
|
+
"middleware_types": [],
|
233
|
+
"security_enabled": False
|
234
|
+
}
|
235
|
+
|
236
|
+
for middleware in self.middleware_stack:
|
237
|
+
middleware_type = type(middleware).__name__
|
238
|
+
info["middleware_types"].append(middleware_type)
|
239
|
+
|
240
|
+
if isinstance(middleware, UnifiedSecurityMiddleware):
|
241
|
+
info["security_enabled"] = True
|
242
|
+
|
243
|
+
return info
|
@@ -5,7 +5,7 @@ Middleware for request logging.
|
|
5
5
|
import time
|
6
6
|
import json
|
7
7
|
import uuid
|
8
|
-
from typing import Callable, Awaitable
|
8
|
+
from typing import Callable, Awaitable, Dict, Any
|
9
9
|
|
10
10
|
from fastapi import Request, Response
|
11
11
|
|
@@ -17,6 +17,17 @@ class LoggingMiddleware(BaseMiddleware):
|
|
17
17
|
Middleware for logging requests and responses.
|
18
18
|
"""
|
19
19
|
|
20
|
+
def __init__(self, app, config: Dict[str, Any] = None):
|
21
|
+
"""
|
22
|
+
Initialize logging middleware.
|
23
|
+
|
24
|
+
Args:
|
25
|
+
app: FastAPI application
|
26
|
+
config: Application configuration (optional)
|
27
|
+
"""
|
28
|
+
super().__init__(app)
|
29
|
+
self.config = config or {}
|
30
|
+
|
20
31
|
async def dispatch(self, request: Request, call_next: Callable[[Request], Awaitable[Response]]) -> Response:
|
21
32
|
"""
|
22
33
|
Processes request and logs information about it.
|
@@ -43,7 +54,13 @@ class LoggingMiddleware(BaseMiddleware):
|
|
43
54
|
url = str(request.url)
|
44
55
|
client_host = request.client.host if request.client else "unknown"
|
45
56
|
|
46
|
-
|
57
|
+
# Check if this is an OpenAPI schema request (should be logged at DEBUG level)
|
58
|
+
is_openapi_request = "/openapi.json" in url
|
59
|
+
|
60
|
+
if is_openapi_request:
|
61
|
+
req_logger.debug(f"Request started: {method} {url} | Client: {client_host}")
|
62
|
+
else:
|
63
|
+
req_logger.info(f"Request started: {method} {url} | Client: {client_host}")
|
47
64
|
|
48
65
|
# Log request body if not GET or HEAD
|
49
66
|
if method not in ["GET", "HEAD"]:
|
@@ -79,8 +96,12 @@ class LoggingMiddleware(BaseMiddleware):
|
|
79
96
|
process_time = time.time() - start_time
|
80
97
|
status_code = response.status_code
|
81
98
|
|
82
|
-
|
83
|
-
|
99
|
+
if is_openapi_request:
|
100
|
+
req_logger.debug(f"Request completed: {method} {url} | Status: {status_code} | "
|
101
|
+
f"Time: {process_time:.3f}s")
|
102
|
+
else:
|
103
|
+
req_logger.info(f"Request completed: {method} {url} | Status: {status_code} | "
|
104
|
+
f"Time: {process_time:.3f}s")
|
84
105
|
|
85
106
|
# Add request ID to response headers
|
86
107
|
response.headers["X-Request-ID"] = request_id
|
@@ -90,7 +111,12 @@ class LoggingMiddleware(BaseMiddleware):
|
|
90
111
|
except Exception as e:
|
91
112
|
# Log error
|
92
113
|
process_time = time.time() - start_time
|
93
|
-
|
94
|
-
|
114
|
+
|
115
|
+
if is_openapi_request:
|
116
|
+
req_logger.debug(f"Request failed: {method} {url} | Error: {str(e)} | "
|
117
|
+
f"Time: {process_time:.3f}s")
|
118
|
+
else:
|
119
|
+
req_logger.error(f"Request failed: {method} {url} | Error: {str(e)} | "
|
120
|
+
f"Time: {process_time:.3f}s")
|
95
121
|
|
96
122
|
raise
|
@@ -0,0 +1,135 @@
|
|
1
|
+
"""
|
2
|
+
Protocol middleware module.
|
3
|
+
|
4
|
+
This module provides middleware for validating protocol access based on configuration.
|
5
|
+
"""
|
6
|
+
|
7
|
+
from typing import Callable
|
8
|
+
from fastapi import Request, Response
|
9
|
+
from starlette.middleware.base import BaseHTTPMiddleware
|
10
|
+
from starlette.responses import JSONResponse
|
11
|
+
|
12
|
+
from mcp_proxy_adapter.core.protocol_manager import protocol_manager
|
13
|
+
from mcp_proxy_adapter.core.logging import logger
|
14
|
+
|
15
|
+
|
16
|
+
class ProtocolMiddleware(BaseHTTPMiddleware):
|
17
|
+
"""
|
18
|
+
Middleware for protocol validation.
|
19
|
+
|
20
|
+
This middleware checks if the incoming request protocol is allowed
|
21
|
+
based on the protocol configuration.
|
22
|
+
"""
|
23
|
+
|
24
|
+
def __init__(self, app, protocol_manager_instance=None):
|
25
|
+
"""
|
26
|
+
Initialize protocol middleware.
|
27
|
+
|
28
|
+
Args:
|
29
|
+
app: FastAPI application
|
30
|
+
protocol_manager_instance: Protocol manager instance (optional)
|
31
|
+
"""
|
32
|
+
super().__init__(app)
|
33
|
+
self.protocol_manager = protocol_manager_instance or protocol_manager
|
34
|
+
|
35
|
+
async def dispatch(self, request: Request, call_next: Callable) -> Response:
|
36
|
+
"""
|
37
|
+
Process request through protocol middleware.
|
38
|
+
|
39
|
+
Args:
|
40
|
+
request: Incoming request
|
41
|
+
call_next: Next middleware/endpoint function
|
42
|
+
|
43
|
+
Returns:
|
44
|
+
Response object
|
45
|
+
"""
|
46
|
+
try:
|
47
|
+
# Get protocol from request
|
48
|
+
protocol = self._get_request_protocol(request)
|
49
|
+
|
50
|
+
# Check if protocol is allowed
|
51
|
+
if not self.protocol_manager.is_protocol_allowed(protocol):
|
52
|
+
logger.warning(f"Protocol '{protocol}' not allowed for request to {request.url.path}")
|
53
|
+
return JSONResponse(
|
54
|
+
status_code=403,
|
55
|
+
content={
|
56
|
+
"error": "Protocol not allowed",
|
57
|
+
"message": f"Protocol '{protocol}' is not allowed. Allowed protocols: {self.protocol_manager.get_allowed_protocols()}",
|
58
|
+
"allowed_protocols": self.protocol_manager.get_allowed_protocols()
|
59
|
+
}
|
60
|
+
)
|
61
|
+
|
62
|
+
# Continue processing
|
63
|
+
response = await call_next(request)
|
64
|
+
return response
|
65
|
+
|
66
|
+
except Exception as e:
|
67
|
+
logger.error(f"Protocol middleware error: {e}")
|
68
|
+
return JSONResponse(
|
69
|
+
status_code=500,
|
70
|
+
content={
|
71
|
+
"error": "Protocol validation error",
|
72
|
+
"message": str(e)
|
73
|
+
}
|
74
|
+
)
|
75
|
+
|
76
|
+
def _get_request_protocol(self, request: Request) -> str:
|
77
|
+
"""
|
78
|
+
Extract protocol from request.
|
79
|
+
|
80
|
+
Args:
|
81
|
+
request: FastAPI request object
|
82
|
+
|
83
|
+
Returns:
|
84
|
+
Protocol name (http, https, mtls)
|
85
|
+
"""
|
86
|
+
# Check if request is secure (HTTPS)
|
87
|
+
if request.url.scheme:
|
88
|
+
scheme = request.url.scheme.lower()
|
89
|
+
|
90
|
+
# If HTTPS, check if client certificate is provided (MTLS)
|
91
|
+
if scheme == "https":
|
92
|
+
# Check for client certificate in headers or SSL context
|
93
|
+
if hasattr(request, 'scope') and 'ssl' in request.scope:
|
94
|
+
ssl_context = request.scope.get('ssl')
|
95
|
+
if ssl_context and hasattr(ssl_context, 'getpeercert'):
|
96
|
+
try:
|
97
|
+
cert = ssl_context.getpeercert()
|
98
|
+
if cert:
|
99
|
+
return "mtls"
|
100
|
+
except:
|
101
|
+
pass
|
102
|
+
|
103
|
+
# Check for client certificate in headers
|
104
|
+
if request.headers.get("ssl-client-cert") or request.headers.get("x-client-cert"):
|
105
|
+
return "mtls"
|
106
|
+
|
107
|
+
return "https"
|
108
|
+
|
109
|
+
return scheme
|
110
|
+
|
111
|
+
# Fallback to checking headers
|
112
|
+
if request.headers.get("x-forwarded-proto"):
|
113
|
+
return request.headers.get("x-forwarded-proto").lower()
|
114
|
+
|
115
|
+
# Default to HTTP
|
116
|
+
return "http"
|
117
|
+
|
118
|
+
|
119
|
+
def setup_protocol_middleware(app, protocol_manager_instance=None):
|
120
|
+
"""
|
121
|
+
Setup protocol middleware for FastAPI application.
|
122
|
+
|
123
|
+
Args:
|
124
|
+
app: FastAPI application
|
125
|
+
protocol_manager_instance: Protocol manager instance (optional)
|
126
|
+
"""
|
127
|
+
if protocol_manager_instance is None:
|
128
|
+
protocol_manager_instance = protocol_manager
|
129
|
+
|
130
|
+
# Only add middleware if protocol management is enabled
|
131
|
+
if protocol_manager_instance.enabled:
|
132
|
+
app.add_middleware(ProtocolMiddleware, protocol_manager_instance=protocol_manager_instance)
|
133
|
+
logger.info("Protocol middleware added to application")
|
134
|
+
else:
|
135
|
+
logger.debug("Protocol management is disabled, skipping protocol middleware")
|