mcp-proxy-adapter 2.0.1__py3-none-any.whl → 6.9.50__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.
Potentially problematic release.
This version of mcp-proxy-adapter might be problematic. Click here for more details.
- mcp_proxy_adapter/__init__.py +47 -0
- mcp_proxy_adapter/__main__.py +13 -0
- mcp_proxy_adapter/api/__init__.py +0 -0
- mcp_proxy_adapter/api/app.py +66 -0
- mcp_proxy_adapter/api/core/__init__.py +18 -0
- mcp_proxy_adapter/api/core/app_factory.py +400 -0
- mcp_proxy_adapter/api/core/lifespan_manager.py +55 -0
- mcp_proxy_adapter/api/core/registration_context.py +356 -0
- mcp_proxy_adapter/api/core/registration_manager.py +307 -0
- mcp_proxy_adapter/api/core/registration_tasks.py +84 -0
- mcp_proxy_adapter/api/core/ssl_context_factory.py +88 -0
- mcp_proxy_adapter/api/handlers.py +181 -0
- mcp_proxy_adapter/api/middleware/__init__.py +21 -0
- mcp_proxy_adapter/api/middleware/base.py +54 -0
- mcp_proxy_adapter/api/middleware/command_permission_middleware.py +73 -0
- mcp_proxy_adapter/api/middleware/error_handling.py +76 -0
- mcp_proxy_adapter/api/middleware/factory.py +147 -0
- mcp_proxy_adapter/api/middleware/logging.py +31 -0
- mcp_proxy_adapter/api/middleware/performance.py +51 -0
- mcp_proxy_adapter/api/middleware/protocol_middleware.py +140 -0
- mcp_proxy_adapter/api/middleware/transport_middleware.py +87 -0
- mcp_proxy_adapter/api/middleware/unified_security.py +223 -0
- mcp_proxy_adapter/api/middleware/user_info_middleware.py +132 -0
- mcp_proxy_adapter/api/openapi/__init__.py +21 -0
- mcp_proxy_adapter/api/openapi/command_integration.py +105 -0
- mcp_proxy_adapter/api/openapi/openapi_generator.py +40 -0
- mcp_proxy_adapter/api/openapi/openapi_registry.py +62 -0
- mcp_proxy_adapter/api/openapi/schema_loader.py +116 -0
- mcp_proxy_adapter/api/schemas.py +270 -0
- mcp_proxy_adapter/api/tool_integration.py +131 -0
- mcp_proxy_adapter/api/tools.py +163 -0
- mcp_proxy_adapter/cli/__init__.py +12 -0
- mcp_proxy_adapter/cli/commands/__init__.py +15 -0
- mcp_proxy_adapter/cli/commands/client.py +100 -0
- mcp_proxy_adapter/cli/commands/config_generate.py +105 -0
- mcp_proxy_adapter/cli/commands/config_validate.py +94 -0
- mcp_proxy_adapter/cli/commands/generate.py +259 -0
- mcp_proxy_adapter/cli/commands/server.py +174 -0
- mcp_proxy_adapter/cli/commands/sets.py +132 -0
- mcp_proxy_adapter/cli/commands/testconfig.py +177 -0
- mcp_proxy_adapter/cli/examples/__init__.py +8 -0
- mcp_proxy_adapter/cli/examples/http_basic.py +82 -0
- mcp_proxy_adapter/cli/examples/https_token.py +96 -0
- mcp_proxy_adapter/cli/examples/mtls_roles.py +103 -0
- mcp_proxy_adapter/cli/main.py +63 -0
- mcp_proxy_adapter/cli/parser.py +338 -0
- mcp_proxy_adapter/cli/validators.py +231 -0
- mcp_proxy_adapter/client/jsonrpc_client/__init__.py +9 -0
- mcp_proxy_adapter/client/jsonrpc_client/client.py +42 -0
- mcp_proxy_adapter/client/jsonrpc_client/command_api.py +45 -0
- mcp_proxy_adapter/client/jsonrpc_client/proxy_api.py +224 -0
- mcp_proxy_adapter/client/jsonrpc_client/queue_api.py +60 -0
- mcp_proxy_adapter/client/jsonrpc_client/transport.py +108 -0
- mcp_proxy_adapter/client/proxy.py +123 -0
- mcp_proxy_adapter/commands/__init__.py +66 -0
- mcp_proxy_adapter/commands/auth_validation_command.py +69 -0
- mcp_proxy_adapter/commands/base.py +389 -0
- mcp_proxy_adapter/commands/builtin_commands.py +30 -0
- mcp_proxy_adapter/commands/catalog/__init__.py +20 -0
- mcp_proxy_adapter/commands/catalog/catalog_loader.py +34 -0
- mcp_proxy_adapter/commands/catalog/catalog_manager.py +122 -0
- mcp_proxy_adapter/commands/catalog/catalog_syncer.py +149 -0
- mcp_proxy_adapter/commands/catalog/command_catalog.py +43 -0
- mcp_proxy_adapter/commands/catalog/dependency_manager.py +37 -0
- mcp_proxy_adapter/commands/catalog_manager.py +97 -0
- mcp_proxy_adapter/commands/cert_monitor_command.py +552 -0
- mcp_proxy_adapter/commands/certificate_management_command.py +562 -0
- mcp_proxy_adapter/commands/command_registry.py +298 -0
- mcp_proxy_adapter/commands/config_command.py +102 -0
- mcp_proxy_adapter/commands/dependency_container.py +40 -0
- mcp_proxy_adapter/commands/dependency_manager.py +143 -0
- mcp_proxy_adapter/commands/echo_command.py +48 -0
- mcp_proxy_adapter/commands/health_command.py +142 -0
- mcp_proxy_adapter/commands/help_command.py +175 -0
- mcp_proxy_adapter/commands/hooks.py +172 -0
- mcp_proxy_adapter/commands/key_management_command.py +484 -0
- mcp_proxy_adapter/commands/load_command.py +123 -0
- mcp_proxy_adapter/commands/plugins_command.py +246 -0
- mcp_proxy_adapter/commands/protocol_management_command.py +216 -0
- mcp_proxy_adapter/commands/proxy_registration_command.py +319 -0
- mcp_proxy_adapter/commands/queue_commands.py +750 -0
- mcp_proxy_adapter/commands/registration_status_command.py +76 -0
- mcp_proxy_adapter/commands/registry/__init__.py +18 -0
- mcp_proxy_adapter/commands/registry/command_info.py +103 -0
- mcp_proxy_adapter/commands/registry/command_loader.py +207 -0
- mcp_proxy_adapter/commands/registry/command_manager.py +119 -0
- mcp_proxy_adapter/commands/registry/command_registry.py +217 -0
- mcp_proxy_adapter/commands/reload_command.py +136 -0
- mcp_proxy_adapter/commands/result.py +157 -0
- mcp_proxy_adapter/commands/role_test_command.py +99 -0
- mcp_proxy_adapter/commands/roles_management_command.py +502 -0
- mcp_proxy_adapter/commands/security_command.py +472 -0
- mcp_proxy_adapter/commands/settings_command.py +113 -0
- mcp_proxy_adapter/commands/ssl_setup_command.py +306 -0
- mcp_proxy_adapter/commands/token_management_command.py +500 -0
- mcp_proxy_adapter/commands/transport_management_command.py +129 -0
- mcp_proxy_adapter/commands/unload_command.py +92 -0
- mcp_proxy_adapter/config.py +32 -0
- mcp_proxy_adapter/core/__init__.py +8 -0
- mcp_proxy_adapter/core/app_factory.py +560 -0
- mcp_proxy_adapter/core/app_runner.py +318 -0
- mcp_proxy_adapter/core/auth_validator.py +508 -0
- mcp_proxy_adapter/core/certificate/__init__.py +20 -0
- mcp_proxy_adapter/core/certificate/certificate_creator.py +372 -0
- mcp_proxy_adapter/core/certificate/certificate_extractor.py +185 -0
- mcp_proxy_adapter/core/certificate/certificate_utils.py +249 -0
- mcp_proxy_adapter/core/certificate/certificate_validator.py +481 -0
- mcp_proxy_adapter/core/certificate/ssl_context_manager.py +65 -0
- mcp_proxy_adapter/core/certificate_utils.py +249 -0
- mcp_proxy_adapter/core/client.py +608 -0
- mcp_proxy_adapter/core/client_manager.py +271 -0
- mcp_proxy_adapter/core/client_security.py +411 -0
- mcp_proxy_adapter/core/config/__init__.py +18 -0
- mcp_proxy_adapter/core/config/config.py +237 -0
- mcp_proxy_adapter/core/config/config_factory.py +22 -0
- mcp_proxy_adapter/core/config/config_loader.py +66 -0
- mcp_proxy_adapter/core/config/feature_manager.py +31 -0
- mcp_proxy_adapter/core/config/simple_config.py +204 -0
- mcp_proxy_adapter/core/config/simple_config_generator.py +131 -0
- mcp_proxy_adapter/core/config/simple_config_validator.py +476 -0
- mcp_proxy_adapter/core/config_converter.py +252 -0
- mcp_proxy_adapter/core/config_validator.py +211 -0
- mcp_proxy_adapter/core/crl_utils.py +362 -0
- mcp_proxy_adapter/core/errors.py +276 -0
- mcp_proxy_adapter/core/job_manager.py +54 -0
- mcp_proxy_adapter/core/logging.py +250 -0
- mcp_proxy_adapter/core/mtls_asgi.py +140 -0
- mcp_proxy_adapter/core/mtls_asgi_app.py +187 -0
- mcp_proxy_adapter/core/mtls_proxy.py +229 -0
- mcp_proxy_adapter/core/mtls_server.py +154 -0
- mcp_proxy_adapter/core/protocol_manager.py +232 -0
- mcp_proxy_adapter/core/proxy/__init__.py +19 -0
- mcp_proxy_adapter/core/proxy/auth_manager.py +26 -0
- mcp_proxy_adapter/core/proxy/proxy_registration_manager.py +160 -0
- mcp_proxy_adapter/core/proxy/registration_client.py +186 -0
- mcp_proxy_adapter/core/proxy/ssl_manager.py +101 -0
- mcp_proxy_adapter/core/proxy_client.py +184 -0
- mcp_proxy_adapter/core/proxy_registration.py +80 -0
- mcp_proxy_adapter/core/role_utils.py +103 -0
- mcp_proxy_adapter/core/security_adapter.py +343 -0
- mcp_proxy_adapter/core/security_factory.py +96 -0
- mcp_proxy_adapter/core/security_integration.py +342 -0
- mcp_proxy_adapter/core/server_adapter.py +251 -0
- mcp_proxy_adapter/core/server_engine.py +217 -0
- mcp_proxy_adapter/core/settings.py +260 -0
- mcp_proxy_adapter/core/signal_handler.py +107 -0
- mcp_proxy_adapter/core/ssl_utils.py +161 -0
- mcp_proxy_adapter/core/transport_manager.py +153 -0
- mcp_proxy_adapter/core/unified_config_adapter.py +471 -0
- mcp_proxy_adapter/core/utils.py +101 -0
- mcp_proxy_adapter/core/validation/__init__.py +21 -0
- mcp_proxy_adapter/core/validation/config_validator.py +219 -0
- mcp_proxy_adapter/core/validation/file_validator.py +131 -0
- mcp_proxy_adapter/core/validation/protocol_validator.py +205 -0
- mcp_proxy_adapter/core/validation/security_validator.py +140 -0
- mcp_proxy_adapter/core/validation/validation_result.py +27 -0
- mcp_proxy_adapter/custom_openapi.py +58 -0
- mcp_proxy_adapter/examples/__init__.py +16 -0
- 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 +52 -0
- mcp_proxy_adapter/examples/bugfix_certificate_config.py +261 -0
- mcp_proxy_adapter/examples/cert_manager_bugfix.py +203 -0
- mcp_proxy_adapter/examples/check_config.py +413 -0
- mcp_proxy_adapter/examples/client_usage_example.py +164 -0
- mcp_proxy_adapter/examples/commands/__init__.py +5 -0
- mcp_proxy_adapter/examples/config_builder.py +234 -0
- mcp_proxy_adapter/examples/config_cli.py +282 -0
- mcp_proxy_adapter/examples/create_test_configs.py +174 -0
- mcp_proxy_adapter/examples/debug_request_state.py +130 -0
- mcp_proxy_adapter/examples/debug_role_chain.py +191 -0
- mcp_proxy_adapter/examples/demo_client.py +287 -0
- mcp_proxy_adapter/examples/full_application/__init__.py +12 -0
- mcp_proxy_adapter/examples/full_application/commands/__init__.py +8 -0
- mcp_proxy_adapter/examples/full_application/commands/custom_echo_command.py +45 -0
- mcp_proxy_adapter/examples/full_application/commands/dynamic_calculator_command.py +52 -0
- mcp_proxy_adapter/examples/full_application/commands/echo_command.py +32 -0
- mcp_proxy_adapter/examples/full_application/commands/help_command.py +54 -0
- mcp_proxy_adapter/examples/full_application/commands/list_command.py +57 -0
- mcp_proxy_adapter/examples/full_application/hooks/__init__.py +5 -0
- mcp_proxy_adapter/examples/full_application/hooks/application_hooks.py +29 -0
- mcp_proxy_adapter/examples/full_application/hooks/builtin_command_hooks.py +27 -0
- mcp_proxy_adapter/examples/full_application/main.py +311 -0
- mcp_proxy_adapter/examples/full_application/proxy_endpoints.py +161 -0
- mcp_proxy_adapter/examples/full_application/run_mtls.py +252 -0
- mcp_proxy_adapter/examples/full_application/run_simple.py +152 -0
- mcp_proxy_adapter/examples/full_application/test_minimal_server.py +45 -0
- mcp_proxy_adapter/examples/full_application/test_server.py +163 -0
- mcp_proxy_adapter/examples/full_application/test_simple_server.py +62 -0
- mcp_proxy_adapter/examples/generate_config.py +502 -0
- mcp_proxy_adapter/examples/proxy_registration_example.py +335 -0
- mcp_proxy_adapter/examples/queue_demo_simple.py +632 -0
- mcp_proxy_adapter/examples/queue_integration_example.py +578 -0
- mcp_proxy_adapter/examples/queue_server_demo.py +82 -0
- mcp_proxy_adapter/examples/queue_server_example.py +85 -0
- mcp_proxy_adapter/examples/queue_server_simple.py +173 -0
- mcp_proxy_adapter/examples/required_certificates.py +208 -0
- mcp_proxy_adapter/examples/run_example.py +77 -0
- mcp_proxy_adapter/examples/run_full_test_suite.py +619 -0
- mcp_proxy_adapter/examples/run_proxy_server.py +153 -0
- mcp_proxy_adapter/examples/run_security_tests_fixed.py +435 -0
- mcp_proxy_adapter/examples/security_test/__init__.py +18 -0
- mcp_proxy_adapter/examples/security_test/auth_manager.py +14 -0
- mcp_proxy_adapter/examples/security_test/ssl_context_manager.py +28 -0
- mcp_proxy_adapter/examples/security_test/test_client.py +159 -0
- mcp_proxy_adapter/examples/security_test/test_result.py +22 -0
- mcp_proxy_adapter/examples/security_test_client.py +72 -0
- mcp_proxy_adapter/examples/setup/__init__.py +24 -0
- mcp_proxy_adapter/examples/setup/certificate_manager.py +215 -0
- mcp_proxy_adapter/examples/setup/config_generator.py +12 -0
- mcp_proxy_adapter/examples/setup/config_validator.py +118 -0
- mcp_proxy_adapter/examples/setup/environment_setup.py +62 -0
- mcp_proxy_adapter/examples/setup/test_files_generator.py +10 -0
- mcp_proxy_adapter/examples/setup/test_runner.py +89 -0
- mcp_proxy_adapter/examples/setup_test_environment.py +235 -0
- mcp_proxy_adapter/examples/simple_protocol_test.py +125 -0
- mcp_proxy_adapter/examples/test_chk_hostname_automated.py +211 -0
- mcp_proxy_adapter/examples/test_config.py +205 -0
- mcp_proxy_adapter/examples/test_config_builder.py +110 -0
- mcp_proxy_adapter/examples/test_examples.py +308 -0
- mcp_proxy_adapter/examples/test_framework_complete.py +267 -0
- mcp_proxy_adapter/examples/test_mcp_server.py +187 -0
- mcp_proxy_adapter/examples/test_protocol_examples.py +337 -0
- mcp_proxy_adapter/examples/universal_client.py +674 -0
- mcp_proxy_adapter/examples/update_config_certificates.py +135 -0
- mcp_proxy_adapter/examples/validate_generator_compatibility.py +385 -0
- mcp_proxy_adapter/examples/validate_generator_compatibility_simple.py +61 -0
- mcp_proxy_adapter/integrations/__init__.py +25 -0
- mcp_proxy_adapter/integrations/queuemgr_integration.py +462 -0
- mcp_proxy_adapter/main.py +311 -0
- mcp_proxy_adapter/openapi.py +375 -0
- mcp_proxy_adapter/schemas/base_schema.json +114 -0
- mcp_proxy_adapter/schemas/openapi_schema.json +314 -0
- mcp_proxy_adapter/schemas/roles.json +37 -0
- mcp_proxy_adapter/schemas/roles_schema.json +162 -0
- mcp_proxy_adapter/version.py +5 -0
- mcp_proxy_adapter-6.9.50.dist-info/METADATA +1088 -0
- mcp_proxy_adapter-6.9.50.dist-info/RECORD +242 -0
- {mcp_proxy_adapter-2.0.1.dist-info → mcp_proxy_adapter-6.9.50.dist-info}/WHEEL +1 -1
- mcp_proxy_adapter-6.9.50.dist-info/entry_points.txt +14 -0
- mcp_proxy_adapter-6.9.50.dist-info/top_level.txt +1 -0
- adapters/__init__.py +0 -16
- analyzers/__init__.py +0 -14
- analyzers/docstring_analyzer.py +0 -199
- analyzers/type_analyzer.py +0 -151
- cli/__init__.py +0 -12
- cli/__main__.py +0 -79
- cli/command_runner.py +0 -233
- dispatchers/__init__.py +0 -14
- dispatchers/base_dispatcher.py +0 -85
- dispatchers/json_rpc_dispatcher.py +0 -198
- generators/__init__.py +0 -14
- generators/endpoint_generator.py +0 -172
- generators/openapi_generator.py +0 -254
- generators/rest_api_generator.py +0 -207
- mcp_proxy_adapter-2.0.1.dist-info/METADATA +0 -272
- mcp_proxy_adapter-2.0.1.dist-info/RECORD +0 -28
- mcp_proxy_adapter-2.0.1.dist-info/licenses/LICENSE +0 -21
- mcp_proxy_adapter-2.0.1.dist-info/top_level.txt +0 -7
- openapi_schema/__init__.py +0 -38
- openapi_schema/command_registry.py +0 -312
- openapi_schema/rest_schema.py +0 -510
- openapi_schema/rpc_generator.py +0 -307
- openapi_schema/rpc_schema.py +0 -416
- validators/__init__.py +0 -14
- validators/base_validator.py +0 -23
- validators/docstring_validator.py +0 -75
- validators/metadata_validator.py +0 -76
|
@@ -0,0 +1,362 @@
|
|
|
1
|
+
"""
|
|
2
|
+
CRL Utilities Module
|
|
3
|
+
|
|
4
|
+
This module provides utilities for working with Certificate Revocation Lists (CRL).
|
|
5
|
+
Supports both file-based and URL-based CRL sources.
|
|
6
|
+
|
|
7
|
+
Author: Vasiliy Zdanovskiy
|
|
8
|
+
email: vasilyvz@gmail.com
|
|
9
|
+
Version: 1.0.0
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import logging
|
|
13
|
+
import os
|
|
14
|
+
import tempfile
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
from typing import Optional, Union, Dict, Any
|
|
17
|
+
import requests
|
|
18
|
+
from requests.adapters import HTTPAdapter
|
|
19
|
+
from urllib3.util.retry import Retry
|
|
20
|
+
|
|
21
|
+
# Import mcp_security_framework CRL utilities
|
|
22
|
+
try:
|
|
23
|
+
from mcp_security_framework.utils.cert_utils import (
|
|
24
|
+
is_certificate_revoked,
|
|
25
|
+
validate_certificate_against_crl,
|
|
26
|
+
is_crl_valid,
|
|
27
|
+
get_crl_info,
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
SECURITY_FRAMEWORK_AVAILABLE = True
|
|
31
|
+
except ImportError:
|
|
32
|
+
SECURITY_FRAMEWORK_AVAILABLE = False
|
|
33
|
+
|
|
34
|
+
logger = logging.getLogger(__name__)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class CRLManager:
|
|
38
|
+
"""
|
|
39
|
+
Manager for Certificate Revocation Lists (CRL).
|
|
40
|
+
|
|
41
|
+
Supports both file-based and URL-based CRL sources.
|
|
42
|
+
Automatically downloads CRL from URLs and caches them locally.
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
def __init__(self, config: Dict[str, Any]):
|
|
46
|
+
"""
|
|
47
|
+
Initialize CRL manager.
|
|
48
|
+
|
|
49
|
+
Args:
|
|
50
|
+
config: Configuration dictionary containing CRL settings
|
|
51
|
+
"""
|
|
52
|
+
self.config = config
|
|
53
|
+
self.crl_enabled = config.get("crl_enabled", False)
|
|
54
|
+
|
|
55
|
+
# Only analyze CRL paths if certificates are enabled
|
|
56
|
+
certificates_enabled = config.get("certificates_enabled", True)
|
|
57
|
+
if certificates_enabled and self.crl_enabled:
|
|
58
|
+
self.crl_path = config.get("crl_path")
|
|
59
|
+
self.crl_url = config.get("crl_url")
|
|
60
|
+
self.crl_validity_days = config.get("crl_validity_days", 30)
|
|
61
|
+
else:
|
|
62
|
+
# Don't analyze CRL paths if certificates are disabled
|
|
63
|
+
self.crl_path = None
|
|
64
|
+
self.crl_url = None
|
|
65
|
+
self.crl_validity_days = 30
|
|
66
|
+
|
|
67
|
+
# Cache for downloaded CRL files
|
|
68
|
+
self._crl_cache: Dict[str, str] = {}
|
|
69
|
+
|
|
70
|
+
# Setup HTTP session with retry strategy
|
|
71
|
+
self._setup_http_session()
|
|
72
|
+
|
|
73
|
+
get_global_logger().info(
|
|
74
|
+
f"CRL Manager initialized - enabled: {self.crl_enabled}, certificates_enabled: {certificates_enabled}"
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
def _setup_http_session(self):
|
|
78
|
+
"""Setup HTTP session with retry strategy for CRL downloads."""
|
|
79
|
+
self.session = requests.Session()
|
|
80
|
+
|
|
81
|
+
# Configure retry strategy
|
|
82
|
+
retry_strategy = Retry(
|
|
83
|
+
total=3,
|
|
84
|
+
backoff_factor=1,
|
|
85
|
+
status_forcelist=[429, 500, 502, 503, 504],
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
adapter = HTTPAdapter(max_retries=retry_strategy)
|
|
89
|
+
self.session.mount("http://", adapter)
|
|
90
|
+
self.session.mount("https://", adapter)
|
|
91
|
+
|
|
92
|
+
# Set timeout
|
|
93
|
+
self.session.timeout = 30
|
|
94
|
+
|
|
95
|
+
def get_crl_data(self) -> Optional[Union[str, bytes, Path]]:
|
|
96
|
+
"""
|
|
97
|
+
Get CRL data from configured source.
|
|
98
|
+
|
|
99
|
+
Returns:
|
|
100
|
+
CRL data as string, bytes, or Path, or None if not available
|
|
101
|
+
|
|
102
|
+
Raises:
|
|
103
|
+
ValueError: If CRL is enabled but no source is configured
|
|
104
|
+
FileNotFoundError: If CRL file is not found
|
|
105
|
+
requests.RequestException: If CRL download fails
|
|
106
|
+
"""
|
|
107
|
+
if not self.crl_enabled:
|
|
108
|
+
get_global_logger().debug("CRL is disabled, skipping CRL check")
|
|
109
|
+
return None
|
|
110
|
+
|
|
111
|
+
# Check if CRL URL is configured
|
|
112
|
+
if self.crl_url:
|
|
113
|
+
return self._get_crl_from_url()
|
|
114
|
+
|
|
115
|
+
# Check if CRL file path is configured
|
|
116
|
+
if self.crl_path:
|
|
117
|
+
return self._get_crl_from_file()
|
|
118
|
+
|
|
119
|
+
# If CRL is enabled but no source is configured, this is an error
|
|
120
|
+
if self.crl_enabled:
|
|
121
|
+
raise ValueError(
|
|
122
|
+
"CRL is enabled but neither crl_path nor crl_url is configured"
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
return None
|
|
126
|
+
|
|
127
|
+
def _get_crl_from_url(self) -> str:
|
|
128
|
+
"""
|
|
129
|
+
Download CRL from URL.
|
|
130
|
+
|
|
131
|
+
Returns:
|
|
132
|
+
Path to downloaded CRL file
|
|
133
|
+
|
|
134
|
+
Raises:
|
|
135
|
+
requests.RequestException: If download fails
|
|
136
|
+
ValueError: If downloaded data is not valid CRL
|
|
137
|
+
"""
|
|
138
|
+
try:
|
|
139
|
+
get_global_logger().info(f"Downloading CRL from URL: {self.crl_url}")
|
|
140
|
+
|
|
141
|
+
# Download CRL
|
|
142
|
+
response = self.session.get(self.crl_url)
|
|
143
|
+
response.raise_for_status()
|
|
144
|
+
|
|
145
|
+
# Validate content type
|
|
146
|
+
content_type = response.headers.get("content-type", "").lower()
|
|
147
|
+
if (
|
|
148
|
+
"application/pkix-crl" not in content_type
|
|
149
|
+
and "application/x-pkcs7-crl" not in content_type
|
|
150
|
+
):
|
|
151
|
+
get_global_logger().warning(f"Unexpected content type for CRL: {content_type}")
|
|
152
|
+
|
|
153
|
+
# Save to temporary file
|
|
154
|
+
with tempfile.NamedTemporaryFile(
|
|
155
|
+
mode="wb", suffix=".crl", delete=False
|
|
156
|
+
) as temp_file:
|
|
157
|
+
temp_file.write(response.content)
|
|
158
|
+
temp_file_path = temp_file.name
|
|
159
|
+
|
|
160
|
+
# Validate CRL format
|
|
161
|
+
if SECURITY_FRAMEWORK_AVAILABLE:
|
|
162
|
+
try:
|
|
163
|
+
is_crl_valid(temp_file_path)
|
|
164
|
+
get_global_logger().info(
|
|
165
|
+
f"CRL downloaded and validated successfully from {self.crl_url}"
|
|
166
|
+
)
|
|
167
|
+
except Exception as e:
|
|
168
|
+
os.unlink(temp_file_path)
|
|
169
|
+
raise ValueError(f"Downloaded CRL is not valid: {e}")
|
|
170
|
+
else:
|
|
171
|
+
get_global_logger().warning(
|
|
172
|
+
"mcp_security_framework not available, skipping CRL validation"
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
# Cache the file path
|
|
176
|
+
self._crl_cache[self.crl_url] = temp_file_path
|
|
177
|
+
|
|
178
|
+
return temp_file_path
|
|
179
|
+
|
|
180
|
+
except requests.RequestException as e:
|
|
181
|
+
get_global_logger().error(f"Failed to download CRL from {self.crl_url}: {e}")
|
|
182
|
+
raise
|
|
183
|
+
except Exception as e:
|
|
184
|
+
get_global_logger().error(f"CRL download failed: {e}")
|
|
185
|
+
raise
|
|
186
|
+
|
|
187
|
+
def _get_crl_from_file(self) -> str:
|
|
188
|
+
"""
|
|
189
|
+
Get CRL from file path.
|
|
190
|
+
|
|
191
|
+
Returns:
|
|
192
|
+
Path to CRL file
|
|
193
|
+
|
|
194
|
+
Raises:
|
|
195
|
+
FileNotFoundError: If CRL file is not found
|
|
196
|
+
ValueError: If CRL file is not valid
|
|
197
|
+
"""
|
|
198
|
+
if not os.path.exists(self.crl_path):
|
|
199
|
+
raise FileNotFoundError(f"CRL file not found: {self.crl_path}")
|
|
200
|
+
|
|
201
|
+
# Validate CRL format
|
|
202
|
+
if SECURITY_FRAMEWORK_AVAILABLE:
|
|
203
|
+
try:
|
|
204
|
+
is_crl_valid(self.crl_path)
|
|
205
|
+
get_global_logger().info(f"CRL file validated successfully: {self.crl_path}")
|
|
206
|
+
except Exception as e:
|
|
207
|
+
raise ValueError(f"CRL file is not valid: {e}")
|
|
208
|
+
else:
|
|
209
|
+
get_global_logger().warning(
|
|
210
|
+
"mcp_security_framework not available, skipping CRL validation"
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
return self.crl_path
|
|
214
|
+
|
|
215
|
+
def is_certificate_revoked(self, cert_path: str) -> bool:
|
|
216
|
+
"""
|
|
217
|
+
Check if certificate is revoked according to CRL.
|
|
218
|
+
|
|
219
|
+
Args:
|
|
220
|
+
cert_path: Path to certificate file
|
|
221
|
+
|
|
222
|
+
Returns:
|
|
223
|
+
True if certificate is revoked, False otherwise
|
|
224
|
+
|
|
225
|
+
Raises:
|
|
226
|
+
ValueError: If CRL is enabled but not available
|
|
227
|
+
FileNotFoundError: If certificate file is not found
|
|
228
|
+
"""
|
|
229
|
+
if not self.crl_enabled:
|
|
230
|
+
return False
|
|
231
|
+
|
|
232
|
+
if not SECURITY_FRAMEWORK_AVAILABLE:
|
|
233
|
+
get_global_logger().warning("mcp_security_framework not available, skipping CRL check")
|
|
234
|
+
return False
|
|
235
|
+
|
|
236
|
+
try:
|
|
237
|
+
crl_data = self.get_crl_data()
|
|
238
|
+
if not crl_data:
|
|
239
|
+
get_global_logger().warning("CRL is enabled but no CRL data is available")
|
|
240
|
+
return False
|
|
241
|
+
|
|
242
|
+
is_revoked = is_certificate_revoked(cert_path, crl_data)
|
|
243
|
+
|
|
244
|
+
if is_revoked:
|
|
245
|
+
get_global_logger().warning(f"Certificate is revoked according to CRL: {cert_path}")
|
|
246
|
+
else:
|
|
247
|
+
get_global_logger().debug(
|
|
248
|
+
f"Certificate is not revoked according to CRL: {cert_path}"
|
|
249
|
+
)
|
|
250
|
+
|
|
251
|
+
return is_revoked
|
|
252
|
+
|
|
253
|
+
except Exception as e:
|
|
254
|
+
get_global_logger().error(f"CRL check failed for certificate {cert_path}: {e}")
|
|
255
|
+
# For security, consider certificate invalid if CRL check fails
|
|
256
|
+
return True
|
|
257
|
+
|
|
258
|
+
def validate_certificate_against_crl(self, cert_path: str) -> Dict[str, Any]:
|
|
259
|
+
"""
|
|
260
|
+
Validate certificate against CRL and return detailed status.
|
|
261
|
+
|
|
262
|
+
Args:
|
|
263
|
+
cert_path: Path to certificate file
|
|
264
|
+
|
|
265
|
+
Returns:
|
|
266
|
+
Dictionary containing validation results
|
|
267
|
+
|
|
268
|
+
Raises:
|
|
269
|
+
ValueError: If CRL is enabled but not available
|
|
270
|
+
FileNotFoundError: If certificate file is not found
|
|
271
|
+
"""
|
|
272
|
+
if not self.crl_enabled:
|
|
273
|
+
return {
|
|
274
|
+
"is_revoked": False,
|
|
275
|
+
"crl_checked": False,
|
|
276
|
+
"crl_source": None,
|
|
277
|
+
"message": "CRL check is disabled",
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
if not SECURITY_FRAMEWORK_AVAILABLE:
|
|
281
|
+
get_global_logger().warning(
|
|
282
|
+
"mcp_security_framework not available, skipping CRL validation"
|
|
283
|
+
)
|
|
284
|
+
return {
|
|
285
|
+
"is_revoked": False,
|
|
286
|
+
"crl_checked": False,
|
|
287
|
+
"crl_source": None,
|
|
288
|
+
"message": "mcp_security_framework not available",
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
try:
|
|
292
|
+
crl_data = self.get_crl_data()
|
|
293
|
+
if not crl_data:
|
|
294
|
+
get_global_logger().warning("CRL is enabled but no CRL data is available")
|
|
295
|
+
return {
|
|
296
|
+
"is_revoked": True, # For security, consider invalid if CRL unavailable
|
|
297
|
+
"crl_checked": False,
|
|
298
|
+
"crl_source": None,
|
|
299
|
+
"message": "CRL is enabled but not available",
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
# Get CRL source info
|
|
303
|
+
crl_source = self.crl_url if self.crl_url else self.crl_path
|
|
304
|
+
|
|
305
|
+
# Validate certificate against CRL
|
|
306
|
+
result = validate_certificate_against_crl(cert_path, crl_data)
|
|
307
|
+
|
|
308
|
+
result["crl_checked"] = True
|
|
309
|
+
result["crl_source"] = crl_source
|
|
310
|
+
|
|
311
|
+
return result
|
|
312
|
+
|
|
313
|
+
except Exception as e:
|
|
314
|
+
get_global_logger().error(f"CRL validation failed for certificate {cert_path}: {e}")
|
|
315
|
+
# For security, consider certificate invalid if CRL validation fails
|
|
316
|
+
return {
|
|
317
|
+
"is_revoked": True,
|
|
318
|
+
"crl_checked": False,
|
|
319
|
+
"crl_source": self.crl_url if self.crl_url else self.crl_path,
|
|
320
|
+
"message": f"CRL validation failed: {e}",
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
def get_crl_info(self) -> Optional[Dict[str, Any]]:
|
|
324
|
+
"""
|
|
325
|
+
Get information about the configured CRL.
|
|
326
|
+
|
|
327
|
+
Returns:
|
|
328
|
+
Dictionary containing CRL information, or None if CRL is not available
|
|
329
|
+
"""
|
|
330
|
+
if not self.crl_enabled:
|
|
331
|
+
return None
|
|
332
|
+
|
|
333
|
+
if not SECURITY_FRAMEWORK_AVAILABLE:
|
|
334
|
+
get_global_logger().warning("mcp_security_framework not available, cannot get CRL info")
|
|
335
|
+
return None
|
|
336
|
+
|
|
337
|
+
try:
|
|
338
|
+
crl_data = self.get_crl_data()
|
|
339
|
+
if not crl_data:
|
|
340
|
+
return None
|
|
341
|
+
|
|
342
|
+
return get_crl_info(crl_data)
|
|
343
|
+
|
|
344
|
+
except Exception as e:
|
|
345
|
+
get_global_logger().error(f"Failed to get CRL info: {e}")
|
|
346
|
+
return None
|
|
347
|
+
|
|
348
|
+
def cleanup_cache(self):
|
|
349
|
+
"""Clean up temporary CRL files."""
|
|
350
|
+
for url, temp_path in self._crl_cache.items():
|
|
351
|
+
try:
|
|
352
|
+
if os.path.exists(temp_path):
|
|
353
|
+
os.unlink(temp_path)
|
|
354
|
+
get_global_logger().debug(f"Cleaned up temporary CRL file: {temp_path}")
|
|
355
|
+
except Exception as e:
|
|
356
|
+
get_global_logger().warning(f"Failed to cleanup temporary CRL file {temp_path}: {e}")
|
|
357
|
+
|
|
358
|
+
self._crl_cache.clear()
|
|
359
|
+
|
|
360
|
+
def __del__(self):
|
|
361
|
+
"""Cleanup when object is destroyed."""
|
|
362
|
+
self.cleanup_cache()
|
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Module for defining errors and exceptions for the microservice.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
from typing import Any, Dict, List, Optional
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class MicroserviceError(Exception):
|
|
10
|
+
"""
|
|
11
|
+
Base class for all microservice exceptions.
|
|
12
|
+
|
|
13
|
+
Attributes:
|
|
14
|
+
message: Error message.
|
|
15
|
+
code: Error code.
|
|
16
|
+
data: Additional error data.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
def __init__(
|
|
20
|
+
self, message: str, code: int = -32000, data: Optional[Dict[str, Any]] = None
|
|
21
|
+
):
|
|
22
|
+
"""
|
|
23
|
+
Initialize the error.
|
|
24
|
+
|
|
25
|
+
Args:
|
|
26
|
+
message: Error message.
|
|
27
|
+
code: Error code according to JSON-RPC standard.
|
|
28
|
+
data: Additional error data.
|
|
29
|
+
"""
|
|
30
|
+
self.message = message
|
|
31
|
+
self.code = code
|
|
32
|
+
self.data = data or {}
|
|
33
|
+
super().__init__(message)
|
|
34
|
+
|
|
35
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
36
|
+
"""Convert error to dictionary format."""
|
|
37
|
+
result = {
|
|
38
|
+
"code": self.code,
|
|
39
|
+
"message": self.message
|
|
40
|
+
}
|
|
41
|
+
if self.data:
|
|
42
|
+
result["data"] = self.data
|
|
43
|
+
return result
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class ParseError(MicroserviceError):
|
|
48
|
+
"""
|
|
49
|
+
Error while parsing JSON request.
|
|
50
|
+
JSON-RPC Error code: -32700
|
|
51
|
+
"""
|
|
52
|
+
|
|
53
|
+
def __init__(
|
|
54
|
+
self, message: str = "Parse error", data: Optional[Dict[str, Any]] = None
|
|
55
|
+
):
|
|
56
|
+
super().__init__(message, code=-32700, data=data)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class InvalidRequestError(MicroserviceError):
|
|
60
|
+
"""
|
|
61
|
+
Invalid JSON-RPC request format.
|
|
62
|
+
JSON-RPC Error code: -32600
|
|
63
|
+
"""
|
|
64
|
+
|
|
65
|
+
def __init__(
|
|
66
|
+
self, message: str = "Invalid Request", data: Optional[Dict[str, Any]] = None
|
|
67
|
+
):
|
|
68
|
+
super().__init__(message, code=-32600, data=data)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class MethodNotFoundError(MicroserviceError):
|
|
72
|
+
"""
|
|
73
|
+
Method not found error.
|
|
74
|
+
JSON-RPC Error code: -32601
|
|
75
|
+
"""
|
|
76
|
+
|
|
77
|
+
def __init__(
|
|
78
|
+
self, message: str = "Method not found", data: Optional[Dict[str, Any]] = None
|
|
79
|
+
):
|
|
80
|
+
super().__init__(message, code=-32601, data=data)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
class InvalidParamsError(MicroserviceError):
|
|
84
|
+
"""
|
|
85
|
+
Invalid method parameters.
|
|
86
|
+
JSON-RPC Error code: -32602
|
|
87
|
+
"""
|
|
88
|
+
|
|
89
|
+
def __init__(
|
|
90
|
+
self, message: str = "Invalid params", data: Optional[Dict[str, Any]] = None
|
|
91
|
+
):
|
|
92
|
+
super().__init__(message, code=-32602, data=data)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
class InternalError(MicroserviceError):
|
|
96
|
+
"""
|
|
97
|
+
Internal server error.
|
|
98
|
+
JSON-RPC Error code: -32603
|
|
99
|
+
"""
|
|
100
|
+
|
|
101
|
+
def __init__(
|
|
102
|
+
self, message: str = "Internal error", data: Optional[Dict[str, Any]] = None
|
|
103
|
+
):
|
|
104
|
+
super().__init__(message, code=-32603, data=data)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
class ValidationError(MicroserviceError):
|
|
108
|
+
"""
|
|
109
|
+
Input data validation error.
|
|
110
|
+
JSON-RPC Error code: -32602 (using Invalid params code)
|
|
111
|
+
"""
|
|
112
|
+
|
|
113
|
+
def __init__(
|
|
114
|
+
self, message: str = "Validation error", data: Optional[Dict[str, Any]] = None
|
|
115
|
+
):
|
|
116
|
+
super().__init__(message, code=-32602, data=data)
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
class CommandError(MicroserviceError):
|
|
120
|
+
"""
|
|
121
|
+
Command execution error.
|
|
122
|
+
JSON-RPC Error code: -32000 (server error)
|
|
123
|
+
"""
|
|
124
|
+
|
|
125
|
+
def __init__(
|
|
126
|
+
self,
|
|
127
|
+
message: str = "Command execution error",
|
|
128
|
+
data: Optional[Dict[str, Any]] = None,
|
|
129
|
+
):
|
|
130
|
+
super().__init__(message, code=-32000, data=data)
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
class NotFoundError(MicroserviceError):
|
|
134
|
+
"""
|
|
135
|
+
"Not found" error.
|
|
136
|
+
JSON-RPC Error code: -32601 (using Method not found code)
|
|
137
|
+
"""
|
|
138
|
+
|
|
139
|
+
def __init__(
|
|
140
|
+
self, message: str = "Resource not found", data: Optional[Dict[str, Any]] = None
|
|
141
|
+
):
|
|
142
|
+
super().__init__(message, code=-32601, data=data)
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
class ConfigurationError(MicroserviceError):
|
|
146
|
+
"""
|
|
147
|
+
Configuration error.
|
|
148
|
+
JSON-RPC Error code: -32603 (using Internal error code)
|
|
149
|
+
"""
|
|
150
|
+
|
|
151
|
+
def __init__(
|
|
152
|
+
self,
|
|
153
|
+
message: str = "Configuration error",
|
|
154
|
+
data: Optional[Dict[str, Any]] = None,
|
|
155
|
+
):
|
|
156
|
+
super().__init__(message, code=-32603, data=data)
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
class AuthenticationError(MicroserviceError):
|
|
160
|
+
"""
|
|
161
|
+
Authentication error.
|
|
162
|
+
JSON-RPC Error code: -32001 (server error)
|
|
163
|
+
"""
|
|
164
|
+
|
|
165
|
+
def __init__(
|
|
166
|
+
self,
|
|
167
|
+
message: str = "Authentication error",
|
|
168
|
+
data: Optional[Dict[str, Any]] = None,
|
|
169
|
+
):
|
|
170
|
+
super().__init__(message, code=-32001, data=data)
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
class AuthorizationError(MicroserviceError):
|
|
174
|
+
"""
|
|
175
|
+
Authorization error.
|
|
176
|
+
JSON-RPC Error code: -32002 (server error)
|
|
177
|
+
"""
|
|
178
|
+
|
|
179
|
+
def __init__(
|
|
180
|
+
self,
|
|
181
|
+
message: str = "Authorization error",
|
|
182
|
+
data: Optional[Dict[str, Any]] = None,
|
|
183
|
+
):
|
|
184
|
+
super().__init__(message, code=-32002, data=data)
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
class TimeoutError(MicroserviceError):
|
|
188
|
+
"""
|
|
189
|
+
Timeout error.
|
|
190
|
+
JSON-RPC Error code: -32003 (server error)
|
|
191
|
+
"""
|
|
192
|
+
|
|
193
|
+
def __init__(
|
|
194
|
+
self, message: str = "Timeout error", data: Optional[Dict[str, Any]] = None
|
|
195
|
+
):
|
|
196
|
+
super().__init__(message, code=-32003, data=data)
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
@dataclass
|
|
202
|
+
class ValidationResult:
|
|
203
|
+
"""Result of configuration validation."""
|
|
204
|
+
level: str # "error", "warning", "info"
|
|
205
|
+
message: str
|
|
206
|
+
section: Optional[str] = None
|
|
207
|
+
key: Optional[str] = None
|
|
208
|
+
suggestion: Optional[str] = None
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
class ConfigError(MicroserviceError):
|
|
212
|
+
"""Configuration validation error."""
|
|
213
|
+
|
|
214
|
+
def __init__(self, message: str, validation_results: Optional[List[ValidationResult]] = None):
|
|
215
|
+
"""
|
|
216
|
+
Initialize configuration error.
|
|
217
|
+
|
|
218
|
+
Args:
|
|
219
|
+
message: Error message
|
|
220
|
+
validation_results: List of validation results that caused the error
|
|
221
|
+
"""
|
|
222
|
+
super().__init__(message, code=-32001, data={"type": "configuration_error"})
|
|
223
|
+
self.validation_results = validation_results or []
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
class MissingConfigKeyError(ConfigError):
|
|
228
|
+
"""Missing required configuration key."""
|
|
229
|
+
|
|
230
|
+
def __init__(self, key: str, section: str = None):
|
|
231
|
+
location = f"{section}.{key}" if section else key
|
|
232
|
+
message = f"Required configuration key '{location}' is missing"
|
|
233
|
+
super().__init__(message)
|
|
234
|
+
self.key = key
|
|
235
|
+
self.section = section
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
class InvalidConfigValueError(ConfigError):
|
|
239
|
+
"""Invalid configuration value."""
|
|
240
|
+
|
|
241
|
+
def __init__(self, key: str, value: Any, expected_type: str, section: str = None):
|
|
242
|
+
location = f"{section}.{key}" if section else key
|
|
243
|
+
message = f"Invalid value for '{location}': got {type(value).__name__}, expected {expected_type}"
|
|
244
|
+
super().__init__(message)
|
|
245
|
+
self.key = key
|
|
246
|
+
self.section = section
|
|
247
|
+
self.value = value
|
|
248
|
+
self.expected_type = expected_type
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
class MissingConfigSectionError(ConfigError):
|
|
252
|
+
"""Missing required configuration section."""
|
|
253
|
+
|
|
254
|
+
def __init__(self, section: str):
|
|
255
|
+
message = f"Required configuration section '{section}' is missing"
|
|
256
|
+
super().__init__(message)
|
|
257
|
+
self.section = section
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
class MissingConfigFileError(ConfigError):
|
|
261
|
+
"""Missing configuration file."""
|
|
262
|
+
|
|
263
|
+
def __init__(self, file_path: str):
|
|
264
|
+
message = f"Configuration file '{file_path}' does not exist"
|
|
265
|
+
super().__init__(message)
|
|
266
|
+
self.file_path = file_path
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
class InvalidConfigFileError(ConfigError):
|
|
270
|
+
"""Invalid configuration file format."""
|
|
271
|
+
|
|
272
|
+
def __init__(self, file_path: str, reason: str):
|
|
273
|
+
message = f"Invalid configuration file '{file_path}': {reason}"
|
|
274
|
+
super().__init__(message)
|
|
275
|
+
self.file_path = file_path
|
|
276
|
+
self.reason = reason
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Author: Vasiliy Zdanovskiy
|
|
3
|
+
email: vasilyvz@gmail.com
|
|
4
|
+
|
|
5
|
+
Simple in-memory job manager for long-running demo commands.
|
|
6
|
+
Not for production use.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import asyncio
|
|
10
|
+
import uuid
|
|
11
|
+
from typing import Any, Dict, Optional, Awaitable
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class JobRecord:
|
|
15
|
+
def __init__(self, task: asyncio.Task):
|
|
16
|
+
self.task = task
|
|
17
|
+
self.status = "running"
|
|
18
|
+
self.result: Optional[Any] = None
|
|
19
|
+
self.error: Optional[str] = None
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
_jobs: Dict[str, JobRecord] = {}
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def enqueue_coroutine(coro: Awaitable[Any]) -> str:
|
|
26
|
+
job_id = str(uuid.uuid4())
|
|
27
|
+
task = asyncio.create_task(_run_job(job_id, coro))
|
|
28
|
+
_jobs[job_id] = JobRecord(task)
|
|
29
|
+
return job_id
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
async def _run_job(job_id: str, coro):
|
|
33
|
+
rec = _jobs[job_id]
|
|
34
|
+
try:
|
|
35
|
+
rec.result = await coro
|
|
36
|
+
rec.status = "completed"
|
|
37
|
+
except Exception as exc: # noqa: BLE001
|
|
38
|
+
rec.error = str(exc)
|
|
39
|
+
rec.status = "failed"
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def get_job_status(job_id: str) -> Dict[str, Any]:
|
|
43
|
+
rec = _jobs.get(job_id)
|
|
44
|
+
if not rec:
|
|
45
|
+
return {"exists": False}
|
|
46
|
+
return {
|
|
47
|
+
"exists": True,
|
|
48
|
+
"status": rec.status,
|
|
49
|
+
"done": rec.task.done(),
|
|
50
|
+
"result": rec.result,
|
|
51
|
+
"error": rec.error,
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
|