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
@@ -1,381 +0,0 @@
|
|
1
|
-
"""
|
2
|
-
Roles Middleware
|
3
|
-
|
4
|
-
This module provides middleware for role-based access control (RBAC).
|
5
|
-
Validates client roles against server roles and permissions.
|
6
|
-
|
7
|
-
Author: MCP Proxy Adapter Team
|
8
|
-
Version: 1.0.0
|
9
|
-
"""
|
10
|
-
|
11
|
-
import json
|
12
|
-
import logging
|
13
|
-
from typing import Dict, List, Optional, Any, Set
|
14
|
-
from pathlib import Path
|
15
|
-
from cryptography import x509
|
16
|
-
|
17
|
-
from fastapi import Request, Response
|
18
|
-
from starlette.middleware.base import BaseHTTPMiddleware
|
19
|
-
|
20
|
-
from ...core.auth_validator import AuthValidator
|
21
|
-
from ...core.role_utils import RoleUtils
|
22
|
-
from ...core.certificate_utils import CertificateUtils
|
23
|
-
from .base import BaseMiddleware
|
24
|
-
|
25
|
-
logger = logging.getLogger(__name__)
|
26
|
-
|
27
|
-
|
28
|
-
class RolesMiddleware(BaseMiddleware):
|
29
|
-
"""
|
30
|
-
Middleware for role-based access control.
|
31
|
-
|
32
|
-
Validates client roles against server roles and permissions,
|
33
|
-
integrates with mTLS middleware for certificate-based authentication.
|
34
|
-
"""
|
35
|
-
|
36
|
-
def __init__(self, app, roles_config_path: str):
|
37
|
-
"""
|
38
|
-
Initialize roles middleware.
|
39
|
-
|
40
|
-
Args:
|
41
|
-
app: FastAPI application
|
42
|
-
roles_config_path: Path to roles configuration file
|
43
|
-
"""
|
44
|
-
super().__init__(app)
|
45
|
-
self.roles_config_path = roles_config_path
|
46
|
-
self.auth_validator = AuthValidator()
|
47
|
-
self.role_utils = RoleUtils()
|
48
|
-
self.certificate_utils = CertificateUtils()
|
49
|
-
|
50
|
-
# Load roles configuration
|
51
|
-
self.roles_config = self._load_roles_config()
|
52
|
-
|
53
|
-
# Check if roles are enabled and config file exists
|
54
|
-
if not self.roles_config.get("enabled", True):
|
55
|
-
logger.info("Roles middleware disabled by configuration")
|
56
|
-
self.enabled = False
|
57
|
-
return
|
58
|
-
|
59
|
-
# Extract configuration
|
60
|
-
self.enabled = self.roles_config.get("enabled", True)
|
61
|
-
self.default_policy = self.roles_config.get("default_policy", {})
|
62
|
-
self.roles = self.roles_config.get("roles", {})
|
63
|
-
self.server_roles = self.roles_config.get("server_roles", {})
|
64
|
-
self.role_hierarchy = self.roles_config.get("role_hierarchy", {})
|
65
|
-
|
66
|
-
logger.info(f"Roles middleware initialized: enabled={self.enabled}, "
|
67
|
-
f"roles_count={len(self.roles)}, "
|
68
|
-
f"server_roles_count={len(self.server_roles)}")
|
69
|
-
|
70
|
-
def _load_roles_config(self) -> Dict[str, Any]:
|
71
|
-
"""
|
72
|
-
Load roles configuration from file.
|
73
|
-
|
74
|
-
Returns:
|
75
|
-
Roles configuration dictionary
|
76
|
-
"""
|
77
|
-
try:
|
78
|
-
config_path = Path(self.roles_config_path)
|
79
|
-
if not config_path.exists():
|
80
|
-
logger.error(f"Roles config file not found: {self.roles_config_path}")
|
81
|
-
logger.error("Roles middleware will be disabled. Please create the roles schema file.")
|
82
|
-
return {"enabled": False}
|
83
|
-
|
84
|
-
with open(config_path, 'r', encoding='utf-8') as f:
|
85
|
-
config = json.load(f)
|
86
|
-
|
87
|
-
logger.info(f"Roles configuration loaded from {self.roles_config_path}")
|
88
|
-
return config
|
89
|
-
|
90
|
-
except Exception as e:
|
91
|
-
logger.error(f"Failed to load roles configuration: {e}")
|
92
|
-
logger.error("Roles middleware will be disabled due to configuration error.")
|
93
|
-
return {"enabled": False}
|
94
|
-
|
95
|
-
def _get_default_config(self) -> Dict[str, Any]:
|
96
|
-
"""
|
97
|
-
Get default roles configuration.
|
98
|
-
|
99
|
-
Returns:
|
100
|
-
Default roles configuration
|
101
|
-
"""
|
102
|
-
return {
|
103
|
-
"enabled": True,
|
104
|
-
"default_policy": {
|
105
|
-
"deny_by_default": True,
|
106
|
-
"require_role_match": True,
|
107
|
-
"case_sensitive": False,
|
108
|
-
"allow_wildcard": True
|
109
|
-
},
|
110
|
-
"roles": {
|
111
|
-
"admin": {
|
112
|
-
"description": "Administrator with full access",
|
113
|
-
"allowed_servers": ["*"],
|
114
|
-
"allowed_clients": ["*"],
|
115
|
-
"permissions": ["read", "write", "delete", "admin"],
|
116
|
-
"priority": 100
|
117
|
-
}
|
118
|
-
},
|
119
|
-
"server_roles": {},
|
120
|
-
"role_hierarchy": {}
|
121
|
-
}
|
122
|
-
|
123
|
-
async def before_request(self, request: Request) -> None:
|
124
|
-
"""
|
125
|
-
Process request before calling the main handler.
|
126
|
-
|
127
|
-
Args:
|
128
|
-
request: FastAPI request object
|
129
|
-
"""
|
130
|
-
if not self.enabled:
|
131
|
-
return
|
132
|
-
|
133
|
-
# Skip role validation for OpenAPI schema endpoint
|
134
|
-
if request.url.path == "/openapi.json":
|
135
|
-
return
|
136
|
-
|
137
|
-
try:
|
138
|
-
# Extract client roles from request state (set by mTLS middleware)
|
139
|
-
client_roles = getattr(request.state, 'client_roles', [])
|
140
|
-
client_cert = getattr(request.state, 'client_certificate', None)
|
141
|
-
|
142
|
-
# If no roles from mTLS, try to extract from certificate
|
143
|
-
if not client_roles and client_cert:
|
144
|
-
client_roles = self._extract_roles_from_certificate(client_cert)
|
145
|
-
|
146
|
-
# Extract server role from request
|
147
|
-
server_role = self._extract_server_role(request)
|
148
|
-
|
149
|
-
# Validate access based on roles
|
150
|
-
if not self._validate_access(client_roles, server_role, request):
|
151
|
-
raise ValueError("Access denied: insufficient roles or permissions")
|
152
|
-
|
153
|
-
# Store roles in request state for downstream middleware
|
154
|
-
request.state.client_roles = client_roles
|
155
|
-
request.state.server_role = server_role
|
156
|
-
request.state.role_validation_passed = True
|
157
|
-
|
158
|
-
logger.debug(f"Role validation successful for client roles: {client_roles}, "
|
159
|
-
f"server role: {server_role}")
|
160
|
-
|
161
|
-
except Exception as e:
|
162
|
-
logger.error(f"Role validation failed: {e}")
|
163
|
-
request.state.role_validation_passed = False
|
164
|
-
request.state.role_validation_error = str(e)
|
165
|
-
|
166
|
-
def _extract_roles_from_certificate(self, cert: x509.Certificate) -> List[str]:
|
167
|
-
"""
|
168
|
-
Extract roles from certificate using RoleUtils.
|
169
|
-
|
170
|
-
Args:
|
171
|
-
cert: Certificate object
|
172
|
-
|
173
|
-
Returns:
|
174
|
-
List of roles extracted from certificate
|
175
|
-
"""
|
176
|
-
try:
|
177
|
-
return self.role_utils.extract_roles_from_certificate_object(cert)
|
178
|
-
except Exception as e:
|
179
|
-
logger.error(f"Failed to extract roles from certificate: {e}")
|
180
|
-
return []
|
181
|
-
|
182
|
-
def _extract_server_role(self, request: Request) -> str:
|
183
|
-
"""
|
184
|
-
Extract server role from request.
|
185
|
-
|
186
|
-
Args:
|
187
|
-
request: FastAPI request object
|
188
|
-
|
189
|
-
Returns:
|
190
|
-
Server role string
|
191
|
-
"""
|
192
|
-
# Try to extract from path
|
193
|
-
path = request.url.path
|
194
|
-
|
195
|
-
# Check if path contains server role information
|
196
|
-
if path.startswith('/api/'):
|
197
|
-
parts = path.split('/')
|
198
|
-
if len(parts) > 2:
|
199
|
-
potential_role = parts[2]
|
200
|
-
if potential_role in self.server_roles:
|
201
|
-
return potential_role
|
202
|
-
|
203
|
-
# Check from headers
|
204
|
-
server_role_header = request.headers.get('X-Server-Role')
|
205
|
-
if server_role_header and server_role_header in self.server_roles:
|
206
|
-
return server_role_header
|
207
|
-
|
208
|
-
# Default to 'basic_commands' if no specific role found
|
209
|
-
return 'basic_commands'
|
210
|
-
|
211
|
-
def _validate_access(self, client_roles: List[str], server_role: str,
|
212
|
-
request: Request) -> bool:
|
213
|
-
"""
|
214
|
-
Validate access based on client roles and server role.
|
215
|
-
|
216
|
-
Args:
|
217
|
-
client_roles: List of client roles
|
218
|
-
server_role: Server role
|
219
|
-
request: FastAPI request object
|
220
|
-
|
221
|
-
Returns:
|
222
|
-
True if access is allowed, False otherwise
|
223
|
-
"""
|
224
|
-
# Check default policy
|
225
|
-
deny_by_default = self.default_policy.get("deny_by_default", True)
|
226
|
-
require_role_match = self.default_policy.get("require_role_match", True)
|
227
|
-
|
228
|
-
# If no client roles and deny by default, deny access
|
229
|
-
if not client_roles and deny_by_default:
|
230
|
-
logger.warning("Access denied: no client roles provided and deny_by_default is True")
|
231
|
-
return False
|
232
|
-
|
233
|
-
# If no server role found, allow if not requiring role match
|
234
|
-
if not server_role and not require_role_match:
|
235
|
-
return True
|
236
|
-
|
237
|
-
# Get server role configuration
|
238
|
-
server_config = self.server_roles.get(server_role, {})
|
239
|
-
required_roles = server_config.get("required_roles", [])
|
240
|
-
|
241
|
-
# If no required roles specified, allow access
|
242
|
-
if not required_roles:
|
243
|
-
return True
|
244
|
-
|
245
|
-
# Check if client has any of the required roles
|
246
|
-
for client_role in client_roles:
|
247
|
-
if self._has_required_role(client_role, required_roles):
|
248
|
-
return True
|
249
|
-
|
250
|
-
# Check role hierarchy
|
251
|
-
for client_role in client_roles:
|
252
|
-
if self._has_role_in_hierarchy(client_role, required_roles):
|
253
|
-
return True
|
254
|
-
|
255
|
-
logger.warning(f"Access denied: client roles {client_roles} do not match "
|
256
|
-
f"required roles {required_roles} for server role {server_role}")
|
257
|
-
return False
|
258
|
-
|
259
|
-
def _has_required_role(self, client_role: str, required_roles: List[str]) -> bool:
|
260
|
-
"""
|
261
|
-
Check if client role matches any required role.
|
262
|
-
|
263
|
-
Args:
|
264
|
-
client_role: Client role to check
|
265
|
-
required_roles: List of required roles
|
266
|
-
|
267
|
-
Returns:
|
268
|
-
True if client role matches any required role
|
269
|
-
"""
|
270
|
-
# Use RoleUtils for case-insensitive comparison
|
271
|
-
for required_role in required_roles:
|
272
|
-
if self.role_utils.compare_roles(client_role, required_role):
|
273
|
-
return True
|
274
|
-
|
275
|
-
# Check for wildcard
|
276
|
-
if "*" in required_roles:
|
277
|
-
return True
|
278
|
-
|
279
|
-
return False
|
280
|
-
|
281
|
-
def _has_role_in_hierarchy(self, client_role: str, required_roles: List[str]) -> bool:
|
282
|
-
"""
|
283
|
-
Check if client role has any required role in its hierarchy.
|
284
|
-
|
285
|
-
Args:
|
286
|
-
client_role: Client role to check
|
287
|
-
required_roles: List of required roles
|
288
|
-
|
289
|
-
Returns:
|
290
|
-
True if client role has any required role in hierarchy
|
291
|
-
"""
|
292
|
-
# Get client role hierarchy
|
293
|
-
client_hierarchy = self._get_role_hierarchy(client_role)
|
294
|
-
|
295
|
-
# Check if any role in hierarchy matches required roles
|
296
|
-
for hierarchy_role in client_hierarchy:
|
297
|
-
if self._has_required_role(hierarchy_role, required_roles):
|
298
|
-
return True
|
299
|
-
|
300
|
-
return False
|
301
|
-
|
302
|
-
def _get_role_hierarchy(self, role: str) -> List[str]:
|
303
|
-
"""
|
304
|
-
Get role hierarchy for a given role.
|
305
|
-
|
306
|
-
Args:
|
307
|
-
role: Role to get hierarchy for
|
308
|
-
|
309
|
-
Returns:
|
310
|
-
List of roles in hierarchy
|
311
|
-
"""
|
312
|
-
return self.role_hierarchy.get(role, [])
|
313
|
-
|
314
|
-
def _validate_permissions(self, client_roles: List[str], required_permissions: List[str]) -> bool:
|
315
|
-
"""
|
316
|
-
Validate if client roles have required permissions.
|
317
|
-
|
318
|
-
Args:
|
319
|
-
client_roles: List of client roles
|
320
|
-
required_permissions: List of required permissions
|
321
|
-
|
322
|
-
Returns:
|
323
|
-
True if client has required permissions
|
324
|
-
"""
|
325
|
-
for client_role in client_roles:
|
326
|
-
role_config = self.roles.get(client_role, {})
|
327
|
-
role_permissions = role_config.get("permissions", [])
|
328
|
-
|
329
|
-
# Check if role has all required permissions
|
330
|
-
if all(perm in role_permissions for perm in required_permissions):
|
331
|
-
return True
|
332
|
-
|
333
|
-
return False
|
334
|
-
|
335
|
-
def get_client_roles(self, request: Request) -> List[str]:
|
336
|
-
"""
|
337
|
-
Get client roles from request state.
|
338
|
-
|
339
|
-
Args:
|
340
|
-
request: FastAPI request object
|
341
|
-
|
342
|
-
Returns:
|
343
|
-
List of client roles
|
344
|
-
"""
|
345
|
-
return getattr(request.state, 'client_roles', [])
|
346
|
-
|
347
|
-
def get_server_role(self, request: Request) -> str:
|
348
|
-
"""
|
349
|
-
Get server role from request state.
|
350
|
-
|
351
|
-
Args:
|
352
|
-
request: FastAPI request object
|
353
|
-
|
354
|
-
Returns:
|
355
|
-
Server role string
|
356
|
-
"""
|
357
|
-
return getattr(request.state, 'server_role', '')
|
358
|
-
|
359
|
-
def is_role_validation_passed(self, request: Request) -> bool:
|
360
|
-
"""
|
361
|
-
Check if role validation passed.
|
362
|
-
|
363
|
-
Args:
|
364
|
-
request: FastAPI request object
|
365
|
-
|
366
|
-
Returns:
|
367
|
-
True if role validation passed
|
368
|
-
"""
|
369
|
-
return getattr(request.state, 'role_validation_passed', False)
|
370
|
-
|
371
|
-
def get_role_validation_error(self, request: Request) -> Optional[str]:
|
372
|
-
"""
|
373
|
-
Get role validation error message.
|
374
|
-
|
375
|
-
Args:
|
376
|
-
request: FastAPI request object
|
377
|
-
|
378
|
-
Returns:
|
379
|
-
Error message if validation failed, None otherwise
|
380
|
-
"""
|
381
|
-
return getattr(request.state, 'role_validation_error', None)
|