mcp-proxy-adapter 6.9.27__py3-none-any.whl → 6.9.29__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.

Files changed (212) hide show
  1. mcp_proxy_adapter/__init__.py +10 -0
  2. mcp_proxy_adapter/__main__.py +8 -21
  3. mcp_proxy_adapter/api/app.py +10 -913
  4. mcp_proxy_adapter/api/core/__init__.py +18 -0
  5. mcp_proxy_adapter/api/core/app_factory.py +243 -0
  6. mcp_proxy_adapter/api/core/lifespan_manager.py +55 -0
  7. mcp_proxy_adapter/api/core/registration_manager.py +166 -0
  8. mcp_proxy_adapter/api/core/ssl_context_factory.py +88 -0
  9. mcp_proxy_adapter/api/handlers.py +78 -199
  10. mcp_proxy_adapter/api/middleware/__init__.py +1 -44
  11. mcp_proxy_adapter/api/middleware/base.py +0 -42
  12. mcp_proxy_adapter/api/middleware/command_permission_middleware.py +0 -85
  13. mcp_proxy_adapter/api/middleware/error_handling.py +1 -127
  14. mcp_proxy_adapter/api/middleware/factory.py +0 -94
  15. mcp_proxy_adapter/api/middleware/logging.py +0 -112
  16. mcp_proxy_adapter/api/middleware/performance.py +0 -35
  17. mcp_proxy_adapter/api/middleware/protocol_middleware.py +2 -98
  18. mcp_proxy_adapter/api/middleware/transport_middleware.py +0 -37
  19. mcp_proxy_adapter/api/middleware/unified_security.py +10 -10
  20. mcp_proxy_adapter/api/middleware/user_info_middleware.py +0 -118
  21. mcp_proxy_adapter/api/openapi/__init__.py +21 -0
  22. mcp_proxy_adapter/api/openapi/command_integration.py +105 -0
  23. mcp_proxy_adapter/api/openapi/openapi_generator.py +40 -0
  24. mcp_proxy_adapter/api/openapi/openapi_registry.py +62 -0
  25. mcp_proxy_adapter/api/openapi/schema_loader.py +116 -0
  26. mcp_proxy_adapter/api/schemas.py +0 -61
  27. mcp_proxy_adapter/api/tool_integration.py +0 -117
  28. mcp_proxy_adapter/api/tools.py +0 -46
  29. mcp_proxy_adapter/cli/__init__.py +12 -0
  30. mcp_proxy_adapter/cli/commands/__init__.py +15 -0
  31. mcp_proxy_adapter/cli/commands/client.py +100 -0
  32. mcp_proxy_adapter/cli/commands/config_generate.py +21 -0
  33. mcp_proxy_adapter/cli/commands/config_validate.py +36 -0
  34. mcp_proxy_adapter/cli/commands/generate.py +259 -0
  35. mcp_proxy_adapter/cli/commands/server.py +174 -0
  36. mcp_proxy_adapter/cli/commands/sets.py +128 -0
  37. mcp_proxy_adapter/cli/commands/testconfig.py +177 -0
  38. mcp_proxy_adapter/cli/examples/__init__.py +8 -0
  39. mcp_proxy_adapter/cli/examples/http_basic.py +82 -0
  40. mcp_proxy_adapter/cli/examples/https_token.py +96 -0
  41. mcp_proxy_adapter/cli/examples/mtls_roles.py +103 -0
  42. mcp_proxy_adapter/cli/main.py +63 -0
  43. mcp_proxy_adapter/cli/parser.py +324 -0
  44. mcp_proxy_adapter/cli/validators.py +231 -0
  45. mcp_proxy_adapter/client/jsonrpc_client.py +406 -0
  46. mcp_proxy_adapter/client/proxy.py +45 -0
  47. mcp_proxy_adapter/commands/__init__.py +44 -28
  48. mcp_proxy_adapter/commands/auth_validation_command.py +7 -344
  49. mcp_proxy_adapter/commands/base.py +19 -43
  50. mcp_proxy_adapter/commands/builtin_commands.py +0 -75
  51. mcp_proxy_adapter/commands/catalog/__init__.py +20 -0
  52. mcp_proxy_adapter/commands/catalog/catalog_loader.py +34 -0
  53. mcp_proxy_adapter/commands/catalog/catalog_manager.py +122 -0
  54. mcp_proxy_adapter/commands/catalog/catalog_syncer.py +149 -0
  55. mcp_proxy_adapter/commands/catalog/command_catalog.py +43 -0
  56. mcp_proxy_adapter/commands/catalog/dependency_manager.py +37 -0
  57. mcp_proxy_adapter/commands/catalog_manager.py +58 -928
  58. mcp_proxy_adapter/commands/cert_monitor_command.py +0 -88
  59. mcp_proxy_adapter/commands/certificate_management_command.py +0 -45
  60. mcp_proxy_adapter/commands/command_registry.py +172 -904
  61. mcp_proxy_adapter/commands/config_command.py +0 -28
  62. mcp_proxy_adapter/commands/dependency_container.py +1 -70
  63. mcp_proxy_adapter/commands/dependency_manager.py +0 -128
  64. mcp_proxy_adapter/commands/echo_command.py +0 -34
  65. mcp_proxy_adapter/commands/health_command.py +0 -3
  66. mcp_proxy_adapter/commands/help_command.py +0 -159
  67. mcp_proxy_adapter/commands/hooks.py +0 -137
  68. mcp_proxy_adapter/commands/key_management_command.py +0 -25
  69. mcp_proxy_adapter/commands/load_command.py +7 -78
  70. mcp_proxy_adapter/commands/plugins_command.py +0 -16
  71. mcp_proxy_adapter/commands/protocol_management_command.py +0 -28
  72. mcp_proxy_adapter/commands/proxy_registration_command.py +0 -88
  73. mcp_proxy_adapter/commands/queue_commands.py +750 -0
  74. mcp_proxy_adapter/commands/registration_status_command.py +0 -43
  75. mcp_proxy_adapter/commands/registry/__init__.py +18 -0
  76. mcp_proxy_adapter/commands/registry/command_info.py +103 -0
  77. mcp_proxy_adapter/commands/registry/command_loader.py +207 -0
  78. mcp_proxy_adapter/commands/registry/command_manager.py +119 -0
  79. mcp_proxy_adapter/commands/registry/command_registry.py +217 -0
  80. mcp_proxy_adapter/commands/reload_command.py +0 -80
  81. mcp_proxy_adapter/commands/result.py +25 -77
  82. mcp_proxy_adapter/commands/role_test_command.py +0 -44
  83. mcp_proxy_adapter/commands/roles_management_command.py +0 -199
  84. mcp_proxy_adapter/commands/security_command.py +0 -30
  85. mcp_proxy_adapter/commands/settings_command.py +0 -68
  86. mcp_proxy_adapter/commands/ssl_setup_command.py +0 -42
  87. mcp_proxy_adapter/commands/token_management_command.py +0 -1
  88. mcp_proxy_adapter/commands/transport_management_command.py +0 -20
  89. mcp_proxy_adapter/commands/unload_command.py +0 -71
  90. mcp_proxy_adapter/config.py +15 -626
  91. mcp_proxy_adapter/core/__init__.py +5 -39
  92. mcp_proxy_adapter/core/app_factory.py +14 -36
  93. mcp_proxy_adapter/core/app_runner.py +0 -27
  94. mcp_proxy_adapter/core/auth_validator.py +1 -93
  95. mcp_proxy_adapter/core/certificate/__init__.py +20 -0
  96. mcp_proxy_adapter/core/certificate/certificate_creator.py +371 -0
  97. mcp_proxy_adapter/core/certificate/certificate_extractor.py +183 -0
  98. mcp_proxy_adapter/core/certificate/certificate_utils.py +249 -0
  99. mcp_proxy_adapter/core/certificate/certificate_validator.py +110 -0
  100. mcp_proxy_adapter/core/certificate/ssl_context_manager.py +70 -0
  101. mcp_proxy_adapter/core/certificate_utils.py +64 -903
  102. mcp_proxy_adapter/core/client.py +0 -6
  103. mcp_proxy_adapter/core/client_manager.py +0 -19
  104. mcp_proxy_adapter/core/client_security.py +0 -2
  105. mcp_proxy_adapter/core/config/__init__.py +18 -0
  106. mcp_proxy_adapter/core/config/config.py +195 -0
  107. mcp_proxy_adapter/core/config/config_factory.py +22 -0
  108. mcp_proxy_adapter/core/config/config_loader.py +66 -0
  109. mcp_proxy_adapter/core/config/feature_manager.py +31 -0
  110. mcp_proxy_adapter/core/config/simple_config.py +112 -0
  111. mcp_proxy_adapter/core/config/simple_config_generator.py +50 -0
  112. mcp_proxy_adapter/core/config/simple_config_validator.py +96 -0
  113. mcp_proxy_adapter/core/config_converter.py +0 -186
  114. mcp_proxy_adapter/core/config_validator.py +96 -1238
  115. mcp_proxy_adapter/core/errors.py +7 -42
  116. mcp_proxy_adapter/core/job_manager.py +54 -0
  117. mcp_proxy_adapter/core/logging.py +2 -22
  118. mcp_proxy_adapter/core/mtls_asgi.py +0 -20
  119. mcp_proxy_adapter/core/mtls_asgi_app.py +0 -12
  120. mcp_proxy_adapter/core/mtls_proxy.py +0 -80
  121. mcp_proxy_adapter/core/mtls_server.py +3 -173
  122. mcp_proxy_adapter/core/protocol_manager.py +1 -191
  123. mcp_proxy_adapter/core/proxy/__init__.py +22 -0
  124. mcp_proxy_adapter/core/proxy/auth_manager.py +27 -0
  125. mcp_proxy_adapter/core/proxy/proxy_registration_manager.py +137 -0
  126. mcp_proxy_adapter/core/proxy/registration_client.py +60 -0
  127. mcp_proxy_adapter/core/proxy/ssl_manager.py +101 -0
  128. mcp_proxy_adapter/core/proxy_client.py +0 -1
  129. mcp_proxy_adapter/core/proxy_registration.py +36 -912
  130. mcp_proxy_adapter/core/role_utils.py +0 -308
  131. mcp_proxy_adapter/core/security_adapter.py +1 -36
  132. mcp_proxy_adapter/core/security_factory.py +1 -150
  133. mcp_proxy_adapter/core/security_integration.py +0 -33
  134. mcp_proxy_adapter/core/server_adapter.py +1 -40
  135. mcp_proxy_adapter/core/server_engine.py +2 -173
  136. mcp_proxy_adapter/core/settings.py +0 -127
  137. mcp_proxy_adapter/core/signal_handler.py +0 -65
  138. mcp_proxy_adapter/core/ssl_utils.py +19 -137
  139. mcp_proxy_adapter/core/transport_manager.py +0 -151
  140. mcp_proxy_adapter/core/unified_config_adapter.py +1 -193
  141. mcp_proxy_adapter/core/utils.py +1 -182
  142. mcp_proxy_adapter/core/validation/__init__.py +21 -0
  143. mcp_proxy_adapter/core/validation/config_validator.py +211 -0
  144. mcp_proxy_adapter/core/validation/file_validator.py +73 -0
  145. mcp_proxy_adapter/core/validation/protocol_validator.py +191 -0
  146. mcp_proxy_adapter/core/validation/security_validator.py +58 -0
  147. mcp_proxy_adapter/core/validation/validation_result.py +27 -0
  148. mcp_proxy_adapter/custom_openapi.py +33 -652
  149. mcp_proxy_adapter/examples/bugfix_certificate_config.py +0 -23
  150. mcp_proxy_adapter/examples/check_config.py +0 -2
  151. mcp_proxy_adapter/examples/client_usage_example.py +164 -0
  152. mcp_proxy_adapter/examples/config_builder.py +13 -2
  153. mcp_proxy_adapter/examples/config_cli.py +0 -1
  154. mcp_proxy_adapter/examples/create_test_configs.py +0 -46
  155. mcp_proxy_adapter/examples/debug_request_state.py +0 -1
  156. mcp_proxy_adapter/examples/full_application/commands/custom_echo_command.py +0 -47
  157. mcp_proxy_adapter/examples/full_application/commands/dynamic_calculator_command.py +0 -45
  158. mcp_proxy_adapter/examples/full_application/commands/echo_command.py +0 -12
  159. mcp_proxy_adapter/examples/full_application/commands/help_command.py +0 -12
  160. mcp_proxy_adapter/examples/full_application/commands/list_command.py +0 -7
  161. mcp_proxy_adapter/examples/full_application/hooks/__init__.py +0 -2
  162. mcp_proxy_adapter/examples/full_application/hooks/application_hooks.py +0 -59
  163. mcp_proxy_adapter/examples/full_application/hooks/builtin_command_hooks.py +0 -54
  164. mcp_proxy_adapter/examples/full_application/main.py +186 -150
  165. mcp_proxy_adapter/examples/full_application/proxy_endpoints.py +0 -107
  166. mcp_proxy_adapter/examples/full_application/test_minimal_server.py +0 -24
  167. mcp_proxy_adapter/examples/full_application/test_server.py +0 -58
  168. mcp_proxy_adapter/examples/generate_config.py +65 -11
  169. mcp_proxy_adapter/examples/queue_demo_simple.py +632 -0
  170. mcp_proxy_adapter/examples/queue_integration_example.py +578 -0
  171. mcp_proxy_adapter/examples/queue_server_demo.py +82 -0
  172. mcp_proxy_adapter/examples/queue_server_example.py +85 -0
  173. mcp_proxy_adapter/examples/queue_server_simple.py +173 -0
  174. mcp_proxy_adapter/examples/required_certificates.py +0 -2
  175. mcp_proxy_adapter/examples/run_full_test_suite.py +0 -29
  176. mcp_proxy_adapter/examples/run_proxy_server.py +31 -71
  177. mcp_proxy_adapter/examples/run_security_tests_fixed.py +0 -27
  178. mcp_proxy_adapter/examples/security_test/__init__.py +18 -0
  179. mcp_proxy_adapter/examples/security_test/auth_manager.py +14 -0
  180. mcp_proxy_adapter/examples/security_test/ssl_context_manager.py +28 -0
  181. mcp_proxy_adapter/examples/security_test/test_client.py +159 -0
  182. mcp_proxy_adapter/examples/security_test/test_result.py +22 -0
  183. mcp_proxy_adapter/examples/security_test_client.py +24 -1075
  184. mcp_proxy_adapter/examples/setup/__init__.py +24 -0
  185. mcp_proxy_adapter/examples/setup/certificate_manager.py +215 -0
  186. mcp_proxy_adapter/examples/setup/config_generator.py +12 -0
  187. mcp_proxy_adapter/examples/setup/config_validator.py +118 -0
  188. mcp_proxy_adapter/examples/setup/environment_setup.py +62 -0
  189. mcp_proxy_adapter/examples/setup/test_files_generator.py +10 -0
  190. mcp_proxy_adapter/examples/setup/test_runner.py +89 -0
  191. mcp_proxy_adapter/examples/setup_test_environment.py +133 -1425
  192. mcp_proxy_adapter/examples/test_config.py +0 -3
  193. mcp_proxy_adapter/examples/test_config_builder.py +25 -405
  194. mcp_proxy_adapter/examples/test_examples.py +0 -1
  195. mcp_proxy_adapter/examples/test_framework_complete.py +0 -2
  196. mcp_proxy_adapter/examples/test_mcp_server.py +0 -1
  197. mcp_proxy_adapter/examples/test_protocol_examples.py +0 -1
  198. mcp_proxy_adapter/examples/universal_client.py +0 -6
  199. mcp_proxy_adapter/examples/update_config_certificates.py +0 -1
  200. mcp_proxy_adapter/examples/validate_generator_compatibility.py +0 -1
  201. mcp_proxy_adapter/examples/validate_generator_compatibility_simple.py +0 -187
  202. mcp_proxy_adapter/integrations/__init__.py +25 -0
  203. mcp_proxy_adapter/integrations/queuemgr_integration.py +462 -0
  204. mcp_proxy_adapter/main.py +70 -62
  205. mcp_proxy_adapter/openapi.py +0 -22
  206. mcp_proxy_adapter/version.py +1 -1
  207. {mcp_proxy_adapter-6.9.27.dist-info → mcp_proxy_adapter-6.9.29.dist-info}/METADATA +2 -1
  208. mcp_proxy_adapter-6.9.29.dist-info/RECORD +235 -0
  209. {mcp_proxy_adapter-6.9.27.dist-info → mcp_proxy_adapter-6.9.29.dist-info}/entry_points.txt +1 -1
  210. mcp_proxy_adapter-6.9.27.dist-info/RECORD +0 -149
  211. {mcp_proxy_adapter-6.9.27.dist-info → mcp_proxy_adapter-6.9.29.dist-info}/WHEEL +0 -0
  212. {mcp_proxy_adapter-6.9.27.dist-info → mcp_proxy_adapter-6.9.29.dist-info}/top_level.txt +0 -0
@@ -5,639 +5,28 @@ Author: Vasiliy Zdanovskiy
5
5
  email: vasilyvz@gmail.com
6
6
  """
7
7
 
8
- import json
9
- import logging
10
- import os
11
- from typing import Any, Dict, Optional, List
8
+ from typing import Optional
12
9
 
13
- logger = logging.getLogger(__name__)
10
+ from .core.config import Config
14
11
 
15
- # Import validation if available
16
- try:
17
- from .core.config_validator import ConfigValidator, ValidationResult, ValidationLevel
18
- VALIDATION_AVAILABLE = True
19
- except ImportError:
20
- VALIDATION_AVAILABLE = False
21
- logger.warning("Configuration validation not available. Install the package to enable validation.")
12
+ # Global configuration instance
13
+ _config: Optional[Config] = None
22
14
 
23
- # Import configuration errors
24
- from .core.errors import ConfigError, ValidationResult
25
15
 
26
-
27
- class Config:
28
- """
29
- Configuration management class for the microservice.
30
- Allows loading settings from configuration file and environment variables.
31
- Supports optional features that can be enabled/disabled.
16
+ def get_config() -> Config:
32
17
  """
18
+ Get the global configuration instance.
33
19
 
34
- def __init__(self, config_path: Optional[str] = None, validate_on_load: bool = True):
35
- """
36
- Initialize configuration.
37
-
38
- Args:
39
- config_path: Path to configuration file. If not specified,
40
- "./config.json" is used.
41
- validate_on_load: Whether to validate configuration on load (default: True)
42
-
43
- Raises:
44
- ConfigError: If configuration validation fails
45
- """
46
- self.config_path = config_path or "./config.json"
47
- self.config_data: Dict[str, Any] = {}
48
- self.validate_on_load = validate_on_load
49
- self.validation_results: List[ValidationResult] = []
50
- self.validator = None
51
-
52
- if VALIDATION_AVAILABLE:
53
- self.validator = ConfigValidator()
54
-
55
- # Don't auto-load config - let user call load_from_file explicitly
56
-
57
- def load_config(self) -> None:
58
- """
59
- Load configuration from file and environment variables.
60
- NO DEFAULT VALUES - configuration must be complete and valid.
61
- """
62
- # Load configuration from file - NO DEFAULTS
63
- if not os.path.exists(self.config_path):
64
- raise ConfigError(f"Configuration file '{self.config_path}' does not exist. Use the configuration generator to create a valid configuration.")
65
-
66
- try:
67
- with open(self.config_path, "r", encoding="utf-8") as f:
68
- self.config_data = json.load(f)
69
- except json.JSONDecodeError as e:
70
- raise ConfigError(f"Configuration file '{self.config_path}' contains invalid JSON: {e}")
71
- except Exception as e:
72
- raise ConfigError(f"Error loading configuration from '{self.config_path}': {e}")
73
-
74
- # Validate configuration BEFORE applying any logic
75
- if self.validate_on_load:
76
- if VALIDATION_AVAILABLE and self.validator:
77
- self.validator.config_data = self.config_data
78
- validation_results = self.validator.validate_config()
79
-
80
- # Check for critical errors
81
- errors = [r for r in validation_results if r.level == "error"]
82
- if errors:
83
- error_summary = "\n".join([f"[{r.section}.{r.key if r.key else 'root'}] {r.message}" for r in errors])
84
- raise ConfigError(f"Configuration validation failed with {len(errors)} error(s):\n{error_summary}", errors)
85
-
86
- # Store validation results for later access
87
- self.validation_results = validation_results
88
- else:
89
- raise ConfigError("Configuration validation is not available. Install required dependencies.")
90
-
91
- # Load configuration from environment variables (overrides file values)
92
- self._load_env_variables()
93
-
94
- # Apply hostname check logic based on SSL configuration
95
- self._validate_security_config()
96
- self._apply_hostname_check_logic()
97
-
98
- def load_from_file(self, config_path: str) -> None:
99
- """
100
- Load configuration from the specified file.
101
-
102
- Args:
103
- config_path: Path to configuration file.
104
- """
105
- self.config_path = config_path
106
- self.load_config()
107
-
108
- def _load_env_variables(self) -> None:
109
- """
110
- Load configuration from environment variables.
111
- Environment variables should be in format SERVICE_SECTION_KEY=value.
112
- For example, SERVICE_SERVER_PORT=8080.
113
- """
114
- prefix = "SERVICE_"
115
- for key, value in os.environ.items():
116
- if key.startswith(prefix):
117
- parts = key[len(prefix) :].lower().split("_", 1)
118
- if len(parts) == 2:
119
- section, param = parts
120
- if section not in self.config_data:
121
- self.config_data[section] = {}
122
- self.config_data[section][param] = self._convert_env_value(value)
123
-
124
- def _convert_env_value(self, value: str) -> Any:
125
- """
126
- Convert environment variable value to appropriate type.
127
-
128
- Args:
129
- value: Value as string
130
-
131
- Returns:
132
- Converted value
133
- """
134
- # Try to convert to appropriate type
135
- if value.lower() == "true":
136
- return True
137
- elif value.lower() == "false":
138
- return False
139
- elif value.isdigit():
140
- return int(value)
141
- else:
142
- try:
143
- return float(value)
144
- except ValueError:
145
- return value
146
-
147
- def get(self, key: str, default: Any = None) -> Any:
148
- """
149
- Get configuration value for key.
150
-
151
- Args:
152
- key: Configuration key in format "section.param"
153
- default: Default value if key not found
154
-
155
- Returns:
156
- Configuration value
157
- """
158
- parts = key.split(".")
159
-
160
- # Get value from config
161
- value = self.config_data
162
- for part in parts:
163
- if not isinstance(value, dict) or part not in value:
164
- return default
165
- value = value[part]
166
-
167
- return value
168
-
169
- def get_all(self) -> Dict[str, Any]:
170
- """
171
- Get all configuration values.
172
-
173
- Returns:
174
- Dictionary with all configuration values
175
- """
176
- return self.config_data.copy()
177
-
178
- def set(self, key: str, value: Any) -> None:
179
- """
180
- Set configuration value for key.
181
-
182
- Args:
183
- key: Configuration key in format "section.param"
184
- value: Configuration value
185
- """
186
- parts = key.split(".")
187
- if len(parts) == 1:
188
- self.config_data[key] = value
189
- else:
190
- section = parts[0]
191
- param_key = ".".join(parts[1:])
192
-
193
- if section not in self.config_data:
194
- self.config_data[section] = {}
195
-
196
- current = self.config_data[section]
197
- for part in parts[1:-1]:
198
- if part not in current:
199
- current[part] = {}
200
- current = current[part]
201
-
202
- current[parts[-1]] = value
203
-
204
- # Special handling for chk_hostname - mark it as user-set
205
- if key == "transport.ssl.chk_hostname":
206
- if "ssl" not in self.config_data.get("transport", {}):
207
- self.config_data["transport"]["ssl"] = {}
208
- self.config_data["transport"]["ssl"]["_chk_hostname_user_set"] = True
209
-
210
- def save(self, path: Optional[str] = None) -> None:
211
- """
212
- Save configuration to file.
213
-
214
- Args:
215
- path: Path to configuration file. If not specified,
216
- self.config_path is used.
217
- """
218
- save_path = path or self.config_path
219
- with open(save_path, "w", encoding="utf-8") as f:
220
- json.dump(self.config_data, f, indent=2)
221
-
222
-
223
- def enable_feature(self, feature: str) -> None:
224
- """
225
- Enable a specific feature in the configuration.
226
-
227
- Args:
228
- feature: Feature to enable (ssl, auth, roles, proxy_registration,
229
- security)
230
- """
231
- if feature == "ssl":
232
- self.set("ssl.enabled", True)
233
- self.set("security.ssl.enabled", True)
234
- elif feature == "auth":
235
- self.set("security.auth.enabled", True)
236
- elif feature == "roles":
237
- self.set("security.permissions.enabled", True)
238
- self.set("roles.enabled", True)
239
- elif feature == "proxy_registration":
240
- self.set("proxy_registration.enabled", True)
241
- elif feature == "security":
242
- self.set("security.enabled", True)
243
- elif feature == "rate_limit":
244
- self.set("security.rate_limit.enabled", True)
245
- elif feature == "certificates":
246
- self.set("security.certificates.enabled", True)
247
- else:
248
- raise ValueError(f"Unknown feature: {feature}")
249
-
250
- def disable_feature(self, feature: str) -> None:
251
- """
252
- Disable a specific feature in the configuration.
253
-
254
- Args:
255
- feature: Feature to disable (ssl, auth, roles, proxy_registration,
256
- security)
257
- """
258
- if feature == "ssl":
259
- self.set("ssl.enabled", False)
260
- self.set("security.ssl.enabled", False)
261
- elif feature == "auth":
262
- self.set("security.auth.enabled", False)
263
- elif feature == "roles":
264
- self.set("security.permissions.enabled", False)
265
- self.set("roles.enabled", False)
266
- elif feature == "proxy_registration":
267
- self.set("proxy_registration.enabled", False)
268
- elif feature == "security":
269
- self.set("security.enabled", False)
270
- elif feature == "rate_limit":
271
- self.set("security.rate_limit.enabled", False)
272
- elif feature == "certificates":
273
- self.set("security.certificates.enabled", False)
274
- else:
275
- raise ValueError(f"Unknown feature: {feature}")
276
-
277
- def is_feature_enabled(self, feature: str) -> bool:
278
- """
279
- Check if a specific feature is enabled.
280
-
281
- Args:
282
- feature: Feature to check (ssl, auth, roles, proxy_registration,
283
- security)
284
-
285
- Returns:
286
- True if feature is enabled, False otherwise
287
- """
288
- if feature == "ssl":
289
- return self.get("ssl.enabled", False) or self.get(
290
- "security.ssl.enabled", False
291
- )
292
- elif feature == "auth":
293
- return self.get("security.auth.enabled", False)
294
- elif feature == "roles":
295
- return self.get("security.permissions.enabled", False) or self.get(
296
- "roles.enabled", False
297
- )
298
- elif feature == "proxy_registration":
299
- return self.get("proxy_registration.enabled", False)
300
- elif feature == "security":
301
- return self.get("security.enabled", False)
302
- elif feature == "rate_limit":
303
- return self.get("security.rate_limit.enabled", False)
304
- elif feature == "certificates":
305
- return self.get("security.certificates.enabled", False)
306
- else:
307
- raise ValueError(f"Unknown feature: {feature}")
308
-
309
- def get_enabled_features(self) -> List[str]:
310
- """
311
- Get list of all enabled features.
312
-
313
- Returns:
314
- List of enabled feature names
315
- """
316
- features = []
317
- if self.is_feature_enabled("ssl"):
318
- features.append("ssl")
319
- if self.is_feature_enabled("auth"):
320
- features.append("auth")
321
- if self.is_feature_enabled("roles"):
322
- features.append("roles")
323
- if self.is_feature_enabled("proxy_registration"):
324
- features.append("proxy_registration")
325
- if self.is_feature_enabled("security"):
326
- features.append("security")
327
- if self.is_feature_enabled("rate_limit"):
328
- features.append("rate_limit")
329
- if self.is_feature_enabled("certificates"):
330
- features.append("certificates")
331
- return features
332
-
333
- def configure_auth_mode(self, mode: str, **kwargs) -> None:
334
- """
335
- Configure authentication mode.
336
-
337
- Args:
338
- mode: Authentication mode (api_key, jwt, certificate, basic, oauth2)
339
- **kwargs: Additional configuration parameters
340
- """
341
- if mode == "api_key":
342
- self.set("security.auth.methods", ["api_key"])
343
- if "api_keys" in kwargs:
344
- self.set("security.auth.api_keys", kwargs["api_keys"])
345
- elif mode == "jwt":
346
- self.set("security.auth.methods", ["jwt"])
347
- if "jwt_secret" in kwargs:
348
- self.set("security.auth.jwt_secret", kwargs["jwt_secret"])
349
- elif mode == "certificate":
350
- self.set("security.auth.methods", ["certificate"])
351
- self.set("security.auth.certificate_auth", True)
352
- elif mode == "basic":
353
- self.set("security.auth.methods", ["basic"])
354
- self.set("security.auth.basic_auth", True)
355
- elif mode == "oauth2":
356
- self.set("security.auth.methods", ["oauth2"])
357
- if "oauth2_config" in kwargs:
358
- self.set("security.auth.oauth2_config", kwargs["oauth2_config"])
359
- else:
360
- raise ValueError(f"Unknown authentication mode: {mode}")
361
-
362
- def configure_proxy_registration_mode(self, mode: str, **kwargs) -> None:
363
- """
364
- Configure proxy registration mode.
365
-
366
- Args:
367
- mode: Registration mode (token, certificate, api_key, none)
368
- **kwargs: Additional configuration parameters
369
- """
370
- if mode == "none":
371
- self.set("proxy_registration.enabled", False)
372
- else:
373
- self.set("proxy_registration.enabled", True)
374
-
375
- if mode == "token":
376
- self.set("proxy_registration.auth_method", "token")
377
- if "token" in kwargs:
378
- self.set("proxy_registration.token.token", kwargs["token"])
379
- elif mode == "certificate":
380
- self.set("proxy_registration.auth_method", "certificate")
381
- if "cert_file" in kwargs:
382
- self.set(
383
- "proxy_registration.certificate.cert_file", kwargs["cert_file"]
384
- )
385
- if "key_file" in kwargs:
386
- self.set(
387
- "proxy_registration.certificate.key_file", kwargs["key_file"]
388
- )
389
- elif mode == "api_key":
390
- self.set("proxy_registration.auth_method", "api_key")
391
- if "key" in kwargs:
392
- self.set("proxy_registration.api_key.key", kwargs["key"])
393
-
394
- def create_minimal_config(self) -> Dict[str, Any]:
395
- """
396
- Create minimal configuration with only essential features.
397
-
398
- Returns:
399
- Minimal configuration dictionary
400
- """
401
- minimal_config = self.config_data.copy()
402
-
403
- # Disable all optional features
404
- minimal_config["ssl"]["enabled"] = False
405
- minimal_config["security"]["enabled"] = False
406
- minimal_config["security"]["auth"]["enabled"] = False
407
- minimal_config["security"]["permissions"]["enabled"] = False
408
- minimal_config["security"]["rate_limit"]["enabled"] = False
409
- minimal_config["security"]["certificates"]["enabled"] = False
410
- minimal_config["proxy_registration"]["enabled"] = False
411
- minimal_config["roles"]["enabled"] = False
412
-
413
- return minimal_config
414
-
415
- def create_secure_config(self) -> Dict[str, Any]:
416
- """
417
- Create secure configuration with all security features enabled.
418
-
419
- Returns:
420
- Secure configuration dictionary
421
- """
422
- secure_config = self.config_data.copy()
423
-
424
- # Enable all security features
425
- secure_config["ssl"]["enabled"] = True
426
- secure_config["security"]["enabled"] = True
427
- secure_config["security"]["auth"]["enabled"] = True
428
- secure_config["security"]["permissions"]["enabled"] = True
429
- secure_config["security"]["rate_limit"]["enabled"] = True
430
- secure_config["security"]["certificates"]["enabled"] = True
431
- secure_config["proxy_registration"]["enabled"] = True
432
- secure_config["roles"]["enabled"] = True
433
-
434
- return secure_config
435
-
436
- def _validate_security_config(self) -> None:
437
- """
438
- Validate security configuration and log warnings for incomplete setup.
439
- """
440
- if not self.get("security.enabled", False):
441
- return
442
-
443
- # Check if security is enabled but no authentication methods are configured
444
- tokens = self.get("security.tokens", {})
445
- roles = self.get("security.roles", {})
446
- roles_file = self.get("security.roles_file")
447
-
448
- has_tokens = bool(tokens and any(tokens.values()))
449
- has_roles = bool(roles and any(roles.values()))
450
- has_roles_file = bool(roles_file and os.path.exists(roles_file))
451
-
452
- if not (has_tokens or has_roles or has_roles_file):
453
- logger.warning(
454
- "Security is enabled but no authentication methods are configured. "
455
- "Please configure tokens, roles, or roles_file in the security section."
456
- )
457
-
458
- def _apply_hostname_check_logic(self) -> None:
459
- """
460
- Apply hostname check logic based on protocol configuration.
461
- chk_hostname should be True for HTTPS/mTLS protocols, False for HTTP.
462
- Only set default values if chk_hostname is not explicitly configured.
463
- """
464
- protocol = self.get("server.protocol", "http")
465
- ssl_enabled = self.get("transport.ssl.enabled", False)
466
-
467
- # Check if chk_hostname is explicitly set by the user
468
- # We check if it was set by looking for a special flag
469
- transport_section = self.config_data.get("transport", {})
470
- ssl_section = transport_section.get("ssl", {})
471
- chk_hostname_explicitly_set = ssl_section.get("_chk_hostname_user_set", False)
472
-
473
- # Set chk_hostname based on protocol only if not explicitly set
474
- if not chk_hostname_explicitly_set:
475
- if protocol in ["https", "mtls"]:
476
- # For HTTPS/mTLS, enable hostname checking by default
477
- self.set("transport.ssl.chk_hostname", True)
478
- logger.debug(f"Set chk_hostname=True for protocol {protocol} (default)")
479
- else:
480
- # For HTTP, disable hostname checking
481
- self.set("transport.ssl.chk_hostname", False)
482
- logger.debug(f"Set chk_hostname=False for protocol {protocol} (default)")
483
- else:
484
- # Log the explicitly set value
485
- chk_hostname_value = self.get("transport.ssl.chk_hostname")
486
- logger.debug(f"Using explicitly set chk_hostname={chk_hostname_value} for protocol {protocol}")
20
+ Returns:
21
+ Configuration instance
22
+ """
23
+ global _config
24
+ if _config is None:
25
+ _config = Config()
26
+ return _config
487
27
 
488
- def validate(self) -> List[ValidationResult]:
489
- """
490
- Validate current configuration.
491
-
492
- Returns:
493
- List of validation results
494
-
495
- Raises:
496
- ConfigError: If validation is not available or critical errors are found
497
- """
498
- if not VALIDATION_AVAILABLE:
499
- raise ConfigError("Configuration validation is not available. Please install the package properly.")
500
-
501
- if not self.validator:
502
- self.validator = ConfigValidator()
503
-
504
- self.validator.config_data = self.config_data
505
- self.validation_results = self.validator.validate_config()
506
-
507
- # Log validation results
508
- for result in self.validation_results:
509
- if result.level == ValidationLevel.ERROR:
510
- get_global_logger().error(f"Configuration error: {result.message}")
511
- elif result.level == ValidationLevel.WARNING:
512
- get_global_logger().warning(f"Configuration warning: {result.message}")
513
- else:
514
- get_global_logger().info(f"Configuration info: {result.message}")
515
-
516
- # Raise ConfigError if there are critical errors
517
- errors = [r for r in self.validation_results if r.level == ValidationLevel.ERROR]
518
- if errors:
519
- error_summary = "\n".join([f"[{r.section}.{r.key if r.key else 'root'}] {r.message}" for r in errors])
520
- raise ConfigError(f"Configuration validation failed with {len(errors)} error(s):\n{error_summary}", errors)
521
-
522
- return self.validation_results
523
-
524
- def is_valid(self) -> bool:
525
- """Check if configuration is valid."""
526
- if not VALIDATION_AVAILABLE:
527
- return True
528
-
529
- return all(result.level != ValidationLevel.ERROR for result in self.validation_results)
530
-
531
- def get_validation_errors(self) -> List[ValidationResult]:
532
- """Get all validation errors."""
533
- return [r for r in self.validation_results if r.level == ValidationLevel.ERROR]
534
-
535
- def get_validation_warnings(self) -> List[ValidationResult]:
536
- """Get all validation warnings."""
537
- return [r for r in self.validation_results if r.level == ValidationLevel.WARNING]
538
-
539
- def get_validation_summary(self) -> Dict[str, Any]:
540
- """Get validation summary."""
541
- if not VALIDATION_AVAILABLE or not self.validator:
542
- return {"total_issues": 0, "errors": 0, "warnings": 0, "info": 0, "is_valid": True}
543
-
544
- return self.validator.get_validation_summary()
545
-
546
- def print_validation_report(self) -> None:
547
- """Print detailed validation report."""
548
- if not VALIDATION_AVAILABLE or not self.validator:
549
- print("Configuration validation not available")
550
- return
551
-
552
- self.validator.print_validation_report()
553
-
554
- def check_feature_requirements(self, feature: str) -> List[ValidationResult]:
555
- """Check if all requirements for a feature are met."""
556
- if not VALIDATION_AVAILABLE:
557
- return []
558
-
559
- results = []
560
-
561
- if feature == "security":
562
- if self.get("security.enabled", False):
563
- tokens = self.get("security.tokens", {})
564
- roles = self.get("security.roles", {})
565
- roles_file = self.get("security.roles_file")
566
-
567
- has_tokens = bool(tokens and any(tokens.values()))
568
- has_roles = bool(roles and any(roles.values()))
569
- has_roles_file = bool(roles_file and os.path.exists(roles_file))
570
-
571
- if not (has_tokens or has_roles or has_roles_file):
572
- results.append(ValidationResult(
573
- level=ValidationLevel.WARNING,
574
- message="Security is enabled but no authentication methods are configured",
575
- section="security",
576
- suggestion="Configure tokens, roles, or roles_file"
577
- ))
578
-
579
- elif feature == "roles":
580
- if self.get("roles.enabled", False):
581
- config_file = self.get("roles.config_file")
582
- if not config_file:
583
- results.append(ValidationResult(
584
- level=ValidationLevel.ERROR,
585
- message="Roles are enabled but config_file is not specified",
586
- section="roles",
587
- key="config_file"
588
- ))
589
- elif not os.path.exists(config_file):
590
- results.append(ValidationResult(
591
- level=ValidationLevel.ERROR,
592
- message=f"Roles config file '{config_file}' does not exist",
593
- section="roles",
594
- key="config_file"
595
- ))
596
-
597
- elif feature == "ssl":
598
- if self.get("ssl.enabled", False):
599
- cert_file = self.get("ssl.cert_file")
600
- key_file = self.get("ssl.key_file")
601
-
602
- if not cert_file:
603
- results.append(ValidationResult(
604
- level=ValidationLevel.ERROR,
605
- message="SSL is enabled but cert_file is not specified",
606
- section="ssl",
607
- key="cert_file"
608
- ))
609
- elif not os.path.exists(cert_file):
610
- results.append(ValidationResult(
611
- level=ValidationLevel.ERROR,
612
- message=f"SSL certificate file '{cert_file}' does not exist",
613
- section="ssl",
614
- key="cert_file"
615
- ))
616
-
617
- if not key_file:
618
- results.append(ValidationResult(
619
- level=ValidationLevel.ERROR,
620
- message="SSL is enabled but key_file is not specified",
621
- section="ssl",
622
- key="key_file"
623
- ))
624
- elif not os.path.exists(key_file):
625
- results.append(ValidationResult(
626
- level=ValidationLevel.ERROR,
627
- message=f"SSL key file '{key_file}' does not exist",
628
- section="ssl",
629
- key="key_file"
630
- ))
631
-
632
- return results
633
28
 
634
29
 
635
- # Singleton instance - will be created when needed
636
- config = None
637
30
 
638
- def get_config() -> Config:
639
- """Get the global configuration instance."""
640
- global config
641
- if config is None:
642
- config = Config()
643
- return config
31
+ # For backward compatibility
32
+ config = get_config()