mcp-proxy-adapter 4.1.1__py3-none-any.whl → 6.0.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- mcp_proxy_adapter/__main__.py +32 -0
- mcp_proxy_adapter/api/app.py +290 -33
- mcp_proxy_adapter/api/handlers.py +32 -6
- mcp_proxy_adapter/api/middleware/__init__.py +38 -32
- 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 +201 -0
- mcp_proxy_adapter/api/middleware/transport_middleware.py +122 -0
- mcp_proxy_adapter/api/middleware/unified_security.py +197 -0
- mcp_proxy_adapter/api/middleware/user_info_middleware.py +158 -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 +8 -1
- 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 +366 -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 +394 -14
- mcp_proxy_adapter/core/app_factory.py +410 -0
- mcp_proxy_adapter/core/app_runner.py +272 -0
- mcp_proxy_adapter/core/auth_validator.py +606 -0
- mcp_proxy_adapter/core/certificate_utils.py +1045 -0
- mcp_proxy_adapter/core/client.py +574 -0
- mcp_proxy_adapter/core/client_manager.py +284 -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 +385 -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 +286 -0
- mcp_proxy_adapter/core/server_adapter.py +282 -0
- mcp_proxy_adapter/core/server_engine.py +270 -0
- mcp_proxy_adapter/core/settings.py +1 -0
- mcp_proxy_adapter/core/ssl_utils.py +234 -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/__init__.py +13 -4
- mcp_proxy_adapter/examples/basic_framework/__init__.py +9 -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 +44 -0
- mcp_proxy_adapter/examples/commands/__init__.py +5 -0
- mcp_proxy_adapter/examples/create_certificates_simple.py +550 -0
- mcp_proxy_adapter/examples/debug_request_state.py +112 -0
- mcp_proxy_adapter/examples/debug_role_chain.py +158 -0
- mcp_proxy_adapter/examples/demo_client.py +275 -0
- mcp_proxy_adapter/examples/examples/basic_framework/__init__.py +9 -0
- mcp_proxy_adapter/examples/examples/basic_framework/commands/__init__.py +4 -0
- mcp_proxy_adapter/examples/examples/basic_framework/hooks/__init__.py +4 -0
- mcp_proxy_adapter/examples/examples/basic_framework/main.py +44 -0
- mcp_proxy_adapter/examples/examples/full_application/__init__.py +12 -0
- mcp_proxy_adapter/examples/examples/full_application/commands/__init__.py +7 -0
- mcp_proxy_adapter/examples/examples/full_application/commands/custom_echo_command.py +80 -0
- mcp_proxy_adapter/examples/examples/full_application/commands/dynamic_calculator_command.py +90 -0
- mcp_proxy_adapter/examples/examples/full_application/hooks/__init__.py +7 -0
- mcp_proxy_adapter/examples/examples/full_application/hooks/application_hooks.py +75 -0
- mcp_proxy_adapter/examples/examples/full_application/hooks/builtin_command_hooks.py +71 -0
- mcp_proxy_adapter/examples/examples/full_application/main.py +173 -0
- mcp_proxy_adapter/examples/examples/full_application/proxy_endpoints.py +154 -0
- mcp_proxy_adapter/examples/full_application/__init__.py +12 -0
- mcp_proxy_adapter/examples/full_application/commands/__init__.py +7 -0
- mcp_proxy_adapter/examples/full_application/commands/custom_echo_command.py +80 -0
- mcp_proxy_adapter/examples/full_application/commands/dynamic_calculator_command.py +90 -0
- mcp_proxy_adapter/examples/full_application/hooks/__init__.py +7 -0
- mcp_proxy_adapter/examples/full_application/hooks/application_hooks.py +75 -0
- mcp_proxy_adapter/examples/full_application/hooks/builtin_command_hooks.py +71 -0
- mcp_proxy_adapter/examples/full_application/main.py +173 -0
- mcp_proxy_adapter/examples/full_application/proxy_endpoints.py +154 -0
- mcp_proxy_adapter/examples/generate_all_certificates.py +362 -0
- mcp_proxy_adapter/examples/generate_certificates.py +177 -0
- mcp_proxy_adapter/examples/generate_certificates_and_tokens.py +369 -0
- mcp_proxy_adapter/examples/generate_test_configs.py +331 -0
- mcp_proxy_adapter/examples/proxy_registration_example.py +334 -0
- mcp_proxy_adapter/examples/run_example.py +59 -0
- mcp_proxy_adapter/examples/run_full_test_suite.py +318 -0
- mcp_proxy_adapter/examples/run_proxy_server.py +146 -0
- mcp_proxy_adapter/examples/run_security_tests.py +544 -0
- mcp_proxy_adapter/examples/run_security_tests_fixed.py +247 -0
- mcp_proxy_adapter/examples/scripts/config_generator.py +740 -0
- mcp_proxy_adapter/examples/scripts/create_certificates_simple.py +560 -0
- mcp_proxy_adapter/examples/scripts/generate_certificates_and_tokens.py +369 -0
- mcp_proxy_adapter/examples/security_test_client.py +782 -0
- mcp_proxy_adapter/examples/setup_test_environment.py +328 -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 +281 -0
- mcp_proxy_adapter/examples/universal_client.py +620 -0
- mcp_proxy_adapter/main.py +93 -0
- mcp_proxy_adapter/utils/config_generator.py +1008 -0
- mcp_proxy_adapter/version.py +5 -2
- mcp_proxy_adapter-6.0.1.dist-info/METADATA +679 -0
- mcp_proxy_adapter-6.0.1.dist-info/RECORD +140 -0
- mcp_proxy_adapter-6.0.1.dist-info/entry_points.txt +2 -0
- {mcp_proxy_adapter-4.1.1.dist-info → mcp_proxy_adapter-6.0.1.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/README.md +0 -124
- 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.0.1.dist-info}/WHEEL +0 -0
- {mcp_proxy_adapter-4.1.1.dist-info → mcp_proxy_adapter-6.0.1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,122 @@
|
|
1
|
+
"""
|
2
|
+
Transport Middleware Module
|
3
|
+
|
4
|
+
This module provides middleware for transport validation in the MCP Proxy Adapter.
|
5
|
+
"""
|
6
|
+
|
7
|
+
from typing import Callable
|
8
|
+
from fastapi import Request, Response
|
9
|
+
from fastapi.responses import JSONResponse
|
10
|
+
from starlette.middleware.base import BaseHTTPMiddleware
|
11
|
+
|
12
|
+
from mcp_proxy_adapter.core.transport_manager import transport_manager
|
13
|
+
from mcp_proxy_adapter.core.logging import logger
|
14
|
+
|
15
|
+
|
16
|
+
class TransportMiddleware(BaseHTTPMiddleware):
|
17
|
+
"""Middleware for transport validation."""
|
18
|
+
|
19
|
+
def __init__(self, app, transport_manager_instance=None):
|
20
|
+
"""
|
21
|
+
Initialize transport middleware.
|
22
|
+
|
23
|
+
Args:
|
24
|
+
app: FastAPI application
|
25
|
+
transport_manager_instance: Transport manager instance (optional)
|
26
|
+
"""
|
27
|
+
super().__init__(app)
|
28
|
+
self.transport_manager = transport_manager_instance or transport_manager
|
29
|
+
|
30
|
+
async def dispatch(self, request: Request, call_next: Callable) -> Response:
|
31
|
+
"""
|
32
|
+
Process request through transport middleware.
|
33
|
+
|
34
|
+
Args:
|
35
|
+
request: Incoming request
|
36
|
+
call_next: Next middleware/endpoint
|
37
|
+
|
38
|
+
Returns:
|
39
|
+
Response from next middleware/endpoint or error response
|
40
|
+
"""
|
41
|
+
# Determine transport type from request
|
42
|
+
transport_type = self._get_request_transport_type(request)
|
43
|
+
|
44
|
+
# Check if request matches configured transport
|
45
|
+
if not self._is_transport_allowed(transport_type):
|
46
|
+
configured_type = self.transport_manager.get_transport_type()
|
47
|
+
configured_type_str = configured_type.value if configured_type else "not configured"
|
48
|
+
logger.warning(f"Transport not allowed: {transport_type} for {request.url}")
|
49
|
+
return JSONResponse(
|
50
|
+
status_code=403,
|
51
|
+
content={
|
52
|
+
"error": "Transport not allowed",
|
53
|
+
"message": f"Transport '{transport_type}' is not allowed. Configured transport: {configured_type_str}",
|
54
|
+
"configured_transport": configured_type_str,
|
55
|
+
"request_url": str(request.url)
|
56
|
+
}
|
57
|
+
)
|
58
|
+
|
59
|
+
# Add transport info to request state
|
60
|
+
request.state.transport_type = transport_type
|
61
|
+
request.state.transport_allowed = True
|
62
|
+
|
63
|
+
response = await call_next(request)
|
64
|
+
return response
|
65
|
+
|
66
|
+
def _get_request_transport_type(self, request: Request) -> str:
|
67
|
+
"""
|
68
|
+
Determine transport type from request.
|
69
|
+
|
70
|
+
Args:
|
71
|
+
request: Incoming request
|
72
|
+
|
73
|
+
Returns:
|
74
|
+
Transport type string
|
75
|
+
"""
|
76
|
+
if request.url.scheme == "https":
|
77
|
+
# Check for client certificate for MTLS
|
78
|
+
if self._has_client_certificate(request):
|
79
|
+
return "mtls"
|
80
|
+
return "https"
|
81
|
+
return "http"
|
82
|
+
|
83
|
+
def _has_client_certificate(self, request: Request) -> bool:
|
84
|
+
"""
|
85
|
+
Check if request has client certificate.
|
86
|
+
|
87
|
+
Args:
|
88
|
+
request: Incoming request
|
89
|
+
|
90
|
+
Returns:
|
91
|
+
True if client certificate is present, False otherwise
|
92
|
+
"""
|
93
|
+
# Check for client certificate in request headers or SSL context
|
94
|
+
# This is a simplified check - in production, you might need more sophisticated detection
|
95
|
+
client_cert_header = request.headers.get("ssl-client-cert")
|
96
|
+
if client_cert_header:
|
97
|
+
return True
|
98
|
+
|
99
|
+
# Check if request has SSL client certificate context
|
100
|
+
if hasattr(request, "client") and request.client:
|
101
|
+
# In a real implementation, you would check the SSL context
|
102
|
+
# For now, we'll assume HTTPS with client cert is MTLS
|
103
|
+
return self.transport_manager.is_mtls()
|
104
|
+
|
105
|
+
return False
|
106
|
+
|
107
|
+
def _is_transport_allowed(self, transport_type: str) -> bool:
|
108
|
+
"""
|
109
|
+
Check if transport type is allowed.
|
110
|
+
|
111
|
+
Args:
|
112
|
+
transport_type: Transport type to check
|
113
|
+
|
114
|
+
Returns:
|
115
|
+
True if transport is allowed, False otherwise
|
116
|
+
"""
|
117
|
+
configured_type = self.transport_manager.get_transport_type()
|
118
|
+
if not configured_type:
|
119
|
+
logger.error("Transport not configured")
|
120
|
+
return False
|
121
|
+
|
122
|
+
return transport_type == configured_type.value
|
@@ -0,0 +1,197 @@
|
|
1
|
+
"""
|
2
|
+
Unified Security Middleware - Direct Framework Integration
|
3
|
+
|
4
|
+
This middleware now directly uses mcp_security_framework components
|
5
|
+
instead of custom implementations.
|
6
|
+
|
7
|
+
Author: Vasiliy Zdanovskiy
|
8
|
+
email: vasilyvz@gmail.com
|
9
|
+
"""
|
10
|
+
|
11
|
+
import time
|
12
|
+
import logging
|
13
|
+
from typing import Dict, Any, Optional, Callable, Awaitable
|
14
|
+
from fastapi import Request, Response
|
15
|
+
from starlette.middleware.base import BaseHTTPMiddleware
|
16
|
+
|
17
|
+
# Direct import from framework
|
18
|
+
try:
|
19
|
+
from mcp_security_framework.middleware.fastapi_middleware import FastAPISecurityMiddleware
|
20
|
+
from mcp_security_framework import SecurityManager
|
21
|
+
from mcp_security_framework.schemas.config import SecurityConfig
|
22
|
+
SECURITY_FRAMEWORK_AVAILABLE = True
|
23
|
+
except ImportError:
|
24
|
+
SECURITY_FRAMEWORK_AVAILABLE = False
|
25
|
+
FastAPISecurityMiddleware = None
|
26
|
+
SecurityManager = None
|
27
|
+
SecurityConfig = None
|
28
|
+
|
29
|
+
from mcp_proxy_adapter.core.logging import logger
|
30
|
+
from mcp_proxy_adapter.core.security_integration import create_security_integration
|
31
|
+
|
32
|
+
|
33
|
+
class SecurityValidationError(Exception):
|
34
|
+
"""Security validation error."""
|
35
|
+
|
36
|
+
def __init__(self, message: str, error_code: int):
|
37
|
+
self.message = message
|
38
|
+
self.error_code = error_code
|
39
|
+
super().__init__(self.message)
|
40
|
+
|
41
|
+
|
42
|
+
class UnifiedSecurityMiddleware(BaseHTTPMiddleware):
|
43
|
+
"""
|
44
|
+
Unified security middleware using mcp_security_framework.
|
45
|
+
|
46
|
+
This middleware now directly uses the security framework's FastAPI middleware
|
47
|
+
and components instead of custom implementations.
|
48
|
+
"""
|
49
|
+
|
50
|
+
def __init__(self, app, config: Dict[str, Any]):
|
51
|
+
"""
|
52
|
+
Initialize unified security middleware.
|
53
|
+
|
54
|
+
Args:
|
55
|
+
app: FastAPI application
|
56
|
+
config: mcp_proxy_adapter configuration dictionary
|
57
|
+
"""
|
58
|
+
super().__init__(app)
|
59
|
+
self.config = config
|
60
|
+
|
61
|
+
# Create security integration
|
62
|
+
try:
|
63
|
+
security_config = config.get("security", {})
|
64
|
+
self.security_integration = create_security_integration(security_config)
|
65
|
+
# Use framework's FastAPI middleware
|
66
|
+
self.framework_middleware = self.security_integration.security_manager.create_fastapi_middleware()
|
67
|
+
logger.info("Using mcp_security_framework FastAPI middleware")
|
68
|
+
# IMPORTANT: Don't replace self.app! This breaks the middleware chain.
|
69
|
+
# Instead, store the framework middleware for use in dispatch method.
|
70
|
+
logger.info("Framework middleware will be used in dispatch method")
|
71
|
+
except Exception as e:
|
72
|
+
logger.error(f"Security framework integration failed: {e}")
|
73
|
+
# Instead of raising error, log warning and continue without security
|
74
|
+
logger.warning("Continuing without security framework - some security features will be disabled")
|
75
|
+
self.security_integration = None
|
76
|
+
self.framework_middleware = None
|
77
|
+
# Keep original app in place when framework middleware is unavailable
|
78
|
+
# BaseHTTPMiddleware initialized it via super().__init__(app)
|
79
|
+
|
80
|
+
logger.info("Unified security middleware initialized")
|
81
|
+
|
82
|
+
async def dispatch(self, request: Request, call_next: Callable[[Request], Awaitable[Response]]) -> Response:
|
83
|
+
"""
|
84
|
+
Process request using framework middleware.
|
85
|
+
|
86
|
+
Args:
|
87
|
+
request: Request object
|
88
|
+
call_next: Next handler
|
89
|
+
|
90
|
+
Returns:
|
91
|
+
Response object
|
92
|
+
"""
|
93
|
+
try:
|
94
|
+
# Simple built-in API key enforcement if configured
|
95
|
+
security_cfg = self.config.get("security", {}) if isinstance(self.config, dict) else {}
|
96
|
+
auth_cfg = security_cfg.get("auth", {})
|
97
|
+
permissions_cfg = security_cfg.get("permissions", {})
|
98
|
+
public_paths = set(auth_cfg.get("public_paths", ["/health", "/docs", "/openapi.json"]))
|
99
|
+
# JSON-RPC endpoint must not be public when API key is required
|
100
|
+
public_paths.discard("/api/jsonrpc")
|
101
|
+
path = request.url.path
|
102
|
+
methods = set(auth_cfg.get("methods", []))
|
103
|
+
api_keys: Dict[str, str] = auth_cfg.get("api_keys", {}) or {}
|
104
|
+
|
105
|
+
# Enforce only for non-public paths when api_key method configured
|
106
|
+
if security_cfg.get("enabled", False) and ("api_key" in methods) and (path not in public_paths):
|
107
|
+
# Accept either X-API-Key or Authorization: Bearer
|
108
|
+
token = request.headers.get("X-API-Key")
|
109
|
+
if not token:
|
110
|
+
authz = request.headers.get("Authorization", "")
|
111
|
+
if authz.startswith("Bearer "):
|
112
|
+
token = authz[7:]
|
113
|
+
if not token or (api_keys and token not in api_keys):
|
114
|
+
from fastapi.responses import JSONResponse
|
115
|
+
return JSONResponse(status_code=401, content={
|
116
|
+
"error": {
|
117
|
+
"code": 401,
|
118
|
+
"message": "Unauthorized: invalid or missing API key",
|
119
|
+
"type": "authentication_error"
|
120
|
+
}
|
121
|
+
})
|
122
|
+
|
123
|
+
# Continue with framework middleware or regular flow
|
124
|
+
if self.framework_middleware:
|
125
|
+
# If framework middleware exists, we need to call it manually
|
126
|
+
# This is a workaround since we can't chain ASGI apps in BaseHTTPMiddleware
|
127
|
+
logger.debug("Framework middleware exists, continuing with regular call_next")
|
128
|
+
return await call_next(request)
|
129
|
+
else:
|
130
|
+
# No framework middleware, continue normally
|
131
|
+
return await call_next(request)
|
132
|
+
|
133
|
+
except SecurityValidationError as e:
|
134
|
+
# Handle security validation errors
|
135
|
+
return await self._handle_security_error(request, e)
|
136
|
+
except Exception as e:
|
137
|
+
# Handle other errors
|
138
|
+
logger.error(f"Unexpected error in unified security middleware: {e}")
|
139
|
+
return await self._handle_general_error(request, e)
|
140
|
+
|
141
|
+
|
142
|
+
|
143
|
+
async def _handle_security_error(self, request: Request, error: SecurityValidationError) -> Response:
|
144
|
+
"""
|
145
|
+
Handle security validation errors.
|
146
|
+
|
147
|
+
Args:
|
148
|
+
request: Request object
|
149
|
+
error: Security validation error
|
150
|
+
|
151
|
+
Returns:
|
152
|
+
Error response
|
153
|
+
"""
|
154
|
+
from fastapi.responses import JSONResponse
|
155
|
+
|
156
|
+
error_response = {
|
157
|
+
"error": {
|
158
|
+
"code": error.error_code,
|
159
|
+
"message": error.message,
|
160
|
+
"type": "security_validation_error"
|
161
|
+
}
|
162
|
+
}
|
163
|
+
|
164
|
+
logger.warning(f"Security validation failed: {error.message}")
|
165
|
+
|
166
|
+
return JSONResponse(
|
167
|
+
status_code=error.error_code,
|
168
|
+
content=error_response
|
169
|
+
)
|
170
|
+
|
171
|
+
async def _handle_general_error(self, request: Request, error: Exception) -> Response:
|
172
|
+
"""
|
173
|
+
Handle general errors.
|
174
|
+
|
175
|
+
Args:
|
176
|
+
request: Request object
|
177
|
+
error: General error
|
178
|
+
|
179
|
+
Returns:
|
180
|
+
Error response
|
181
|
+
"""
|
182
|
+
from fastapi.responses import JSONResponse
|
183
|
+
|
184
|
+
error_response = {
|
185
|
+
"error": {
|
186
|
+
"code": 500,
|
187
|
+
"message": "Internal server error",
|
188
|
+
"type": "general_error"
|
189
|
+
}
|
190
|
+
}
|
191
|
+
|
192
|
+
logger.error(f"General error in security middleware: {error}")
|
193
|
+
|
194
|
+
return JSONResponse(
|
195
|
+
status_code=500,
|
196
|
+
content=error_response
|
197
|
+
)
|
@@ -0,0 +1,158 @@
|
|
1
|
+
"""
|
2
|
+
User Info Middleware
|
3
|
+
|
4
|
+
This middleware extracts user information from authentication headers
|
5
|
+
and sets it in request.state for use by commands.
|
6
|
+
|
7
|
+
Author: Vasiliy Zdanovskiy
|
8
|
+
email: vasilyvz@gmail.com
|
9
|
+
"""
|
10
|
+
|
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
|
+
# Import mcp_security_framework components
|
19
|
+
try:
|
20
|
+
from mcp_security_framework import AuthManager
|
21
|
+
from mcp_security_framework.schemas.config import AuthConfig
|
22
|
+
_MCP_SECURITY_AVAILABLE = True
|
23
|
+
print("✅ mcp_security_framework available in middleware")
|
24
|
+
except ImportError:
|
25
|
+
_MCP_SECURITY_AVAILABLE = False
|
26
|
+
print("⚠️ mcp_security_framework not available in middleware, using basic auth")
|
27
|
+
|
28
|
+
|
29
|
+
class UserInfoMiddleware(BaseHTTPMiddleware):
|
30
|
+
"""
|
31
|
+
Middleware for setting user information in request.state.
|
32
|
+
|
33
|
+
This middleware extracts user information from authentication headers
|
34
|
+
and sets it in request.state for use by commands.
|
35
|
+
"""
|
36
|
+
|
37
|
+
def __init__(self, app, config: Dict[str, Any]):
|
38
|
+
"""
|
39
|
+
Initialize user info middleware.
|
40
|
+
|
41
|
+
Args:
|
42
|
+
app: FastAPI application
|
43
|
+
config: Configuration dictionary
|
44
|
+
"""
|
45
|
+
super().__init__(app)
|
46
|
+
self.config = config
|
47
|
+
|
48
|
+
# Initialize AuthManager if available
|
49
|
+
self.auth_manager = None
|
50
|
+
self._security_available = _MCP_SECURITY_AVAILABLE
|
51
|
+
|
52
|
+
if self._security_available:
|
53
|
+
try:
|
54
|
+
# Get API keys configuration
|
55
|
+
security_config = config.get("security", {})
|
56
|
+
auth_config = security_config.get("auth", {})
|
57
|
+
|
58
|
+
# Create AuthConfig for mcp_security_framework
|
59
|
+
mcp_auth_config = AuthConfig(
|
60
|
+
enabled=True,
|
61
|
+
methods=["api_key"],
|
62
|
+
api_keys=auth_config.get("api_keys", {})
|
63
|
+
)
|
64
|
+
|
65
|
+
self.auth_manager = AuthManager(mcp_auth_config)
|
66
|
+
logger.info("✅ User info middleware initialized with mcp_security_framework")
|
67
|
+
except Exception as e:
|
68
|
+
logger.warning(f"⚠️ Failed to initialize AuthManager: {e}")
|
69
|
+
self._security_available = False
|
70
|
+
|
71
|
+
if not self._security_available:
|
72
|
+
# Fallback to basic API key handling
|
73
|
+
security_config = config.get("security", {})
|
74
|
+
auth_config = security_config.get("auth", {})
|
75
|
+
self.api_keys = auth_config.get("api_keys", {})
|
76
|
+
logger.info("ℹ️ User info middleware initialized with basic auth")
|
77
|
+
|
78
|
+
async def dispatch(self, request: Request, call_next: Callable[[Request], Awaitable[Response]]) -> Response:
|
79
|
+
"""
|
80
|
+
Process request and set user info in request.state.
|
81
|
+
|
82
|
+
Args:
|
83
|
+
request: Request object
|
84
|
+
call_next: Next handler
|
85
|
+
|
86
|
+
Returns:
|
87
|
+
Response object
|
88
|
+
"""
|
89
|
+
# Extract API key from headers
|
90
|
+
api_key = request.headers.get("X-API-Key")
|
91
|
+
|
92
|
+
if api_key:
|
93
|
+
if self.auth_manager and self._security_available:
|
94
|
+
try:
|
95
|
+
# Use mcp_security_framework AuthManager
|
96
|
+
auth_result = self.auth_manager.authenticate_api_key(api_key)
|
97
|
+
|
98
|
+
if auth_result.is_valid:
|
99
|
+
# Set user info from AuthManager result
|
100
|
+
request.state.user = {
|
101
|
+
"id": api_key,
|
102
|
+
"role": auth_result.roles[0] if auth_result.roles else "guest",
|
103
|
+
"roles": auth_result.roles or ["guest"],
|
104
|
+
"permissions": getattr(auth_result, 'permissions', ["read"])
|
105
|
+
}
|
106
|
+
logger.debug(f"✅ Authenticated user with mcp_security_framework: {request.state.user}")
|
107
|
+
else:
|
108
|
+
# Authentication failed
|
109
|
+
request.state.user = {
|
110
|
+
"id": None,
|
111
|
+
"role": "guest",
|
112
|
+
"roles": ["guest"],
|
113
|
+
"permissions": ["read"]
|
114
|
+
}
|
115
|
+
logger.debug(f"❌ Authentication failed for API key: {api_key[:8]}...")
|
116
|
+
except Exception as e:
|
117
|
+
logger.warning(f"⚠️ AuthManager error: {e}, falling back to basic auth")
|
118
|
+
self._security_available = False
|
119
|
+
|
120
|
+
if not self._security_available:
|
121
|
+
# Fallback to basic API key handling
|
122
|
+
if api_key in getattr(self, 'api_keys', {}):
|
123
|
+
user_role = self.api_keys[api_key]
|
124
|
+
|
125
|
+
# Get permissions for this role from roles file if available
|
126
|
+
role_permissions = ["read"] # default permissions
|
127
|
+
if hasattr(self, 'roles_config') and self.roles_config and user_role in self.roles_config:
|
128
|
+
role_permissions = self.roles_config[user_role].get("permissions", ["read"])
|
129
|
+
|
130
|
+
# Set user info in request.state
|
131
|
+
request.state.user = {
|
132
|
+
"id": api_key,
|
133
|
+
"role": user_role,
|
134
|
+
"roles": [user_role],
|
135
|
+
"permissions": role_permissions
|
136
|
+
}
|
137
|
+
|
138
|
+
logger.debug(f"✅ Authenticated user with basic auth: {request.state.user}")
|
139
|
+
else:
|
140
|
+
# API key not found
|
141
|
+
request.state.user = {
|
142
|
+
"id": None,
|
143
|
+
"role": "guest",
|
144
|
+
"roles": ["guest"],
|
145
|
+
"permissions": ["read"]
|
146
|
+
}
|
147
|
+
logger.debug(f"❌ API key not found: {api_key[:8]}...")
|
148
|
+
else:
|
149
|
+
# No API key provided - guest access
|
150
|
+
request.state.user = {
|
151
|
+
"id": None,
|
152
|
+
"role": "guest",
|
153
|
+
"roles": ["guest"],
|
154
|
+
"permissions": ["read"]
|
155
|
+
}
|
156
|
+
logger.debug("ℹ️ No API key provided, using guest access")
|
157
|
+
|
158
|
+
return await call_next(request)
|
@@ -6,9 +6,15 @@ from mcp_proxy_adapter.commands.base import Command
|
|
6
6
|
from mcp_proxy_adapter.commands.command_registry import registry, CommandRegistry
|
7
7
|
from mcp_proxy_adapter.commands.dependency_container import container, DependencyContainer
|
8
8
|
from mcp_proxy_adapter.commands.result import CommandResult, SuccessResult, ErrorResult
|
9
|
-
|
10
|
-
|
11
|
-
|
9
|
+
from mcp_proxy_adapter.commands.auth_validation_command import AuthValidationCommand
|
10
|
+
from mcp_proxy_adapter.commands.ssl_setup_command import SSLSetupCommand
|
11
|
+
from mcp_proxy_adapter.commands.certificate_management_command import CertificateManagementCommand
|
12
|
+
from mcp_proxy_adapter.commands.key_management_command import KeyManagementCommand
|
13
|
+
from mcp_proxy_adapter.commands.cert_monitor_command import CertMonitorCommand
|
14
|
+
from mcp_proxy_adapter.commands.transport_management_command import TransportManagementCommand
|
15
|
+
from mcp_proxy_adapter.commands.role_test_command import RoleTestCommand
|
16
|
+
from mcp_proxy_adapter.commands.echo_command import EchoCommand
|
17
|
+
from mcp_proxy_adapter.commands.proxy_registration_command import ProxyRegistrationCommand
|
12
18
|
|
13
19
|
__all__ = [
|
14
20
|
"Command",
|
@@ -18,5 +24,14 @@ __all__ = [
|
|
18
24
|
"registry",
|
19
25
|
"CommandRegistry",
|
20
26
|
"container",
|
21
|
-
"DependencyContainer"
|
27
|
+
"DependencyContainer",
|
28
|
+
"AuthValidationCommand",
|
29
|
+
"SSLSetupCommand",
|
30
|
+
"CertificateManagementCommand",
|
31
|
+
"KeyManagementCommand",
|
32
|
+
"CertMonitorCommand",
|
33
|
+
"TransportManagementCommand",
|
34
|
+
"RoleTestCommand",
|
35
|
+
"EchoCommand",
|
36
|
+
"ProxyRegistrationCommand"
|
22
37
|
]
|