mcp-proxy-adapter 4.1.0__py3-none-any.whl → 6.0.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.
Files changed (101) hide show
  1. mcp_proxy_adapter/__main__.py +12 -0
  2. mcp_proxy_adapter/api/app.py +138 -11
  3. mcp_proxy_adapter/api/handlers.py +16 -1
  4. mcp_proxy_adapter/api/middleware/__init__.py +30 -29
  5. mcp_proxy_adapter/api/middleware/auth_adapter.py +235 -0
  6. mcp_proxy_adapter/api/middleware/error_handling.py +9 -0
  7. mcp_proxy_adapter/api/middleware/factory.py +219 -0
  8. mcp_proxy_adapter/api/middleware/logging.py +32 -6
  9. mcp_proxy_adapter/api/middleware/mtls_adapter.py +305 -0
  10. mcp_proxy_adapter/api/middleware/mtls_middleware.py +296 -0
  11. mcp_proxy_adapter/api/middleware/protocol_middleware.py +135 -0
  12. mcp_proxy_adapter/api/middleware/rate_limit_adapter.py +241 -0
  13. mcp_proxy_adapter/api/middleware/roles_adapter.py +365 -0
  14. mcp_proxy_adapter/api/middleware/roles_middleware.py +381 -0
  15. mcp_proxy_adapter/api/middleware/security.py +376 -0
  16. mcp_proxy_adapter/api/middleware/token_auth_middleware.py +261 -0
  17. mcp_proxy_adapter/api/middleware/transport_middleware.py +122 -0
  18. mcp_proxy_adapter/commands/__init__.py +13 -4
  19. mcp_proxy_adapter/commands/auth_validation_command.py +408 -0
  20. mcp_proxy_adapter/commands/base.py +61 -30
  21. mcp_proxy_adapter/commands/builtin_commands.py +89 -0
  22. mcp_proxy_adapter/commands/catalog_manager.py +838 -0
  23. mcp_proxy_adapter/commands/cert_monitor_command.py +620 -0
  24. mcp_proxy_adapter/commands/certificate_management_command.py +608 -0
  25. mcp_proxy_adapter/commands/command_registry.py +705 -345
  26. mcp_proxy_adapter/commands/dependency_manager.py +245 -0
  27. mcp_proxy_adapter/commands/health_command.py +7 -0
  28. mcp_proxy_adapter/commands/hooks.py +200 -167
  29. mcp_proxy_adapter/commands/key_management_command.py +506 -0
  30. mcp_proxy_adapter/commands/load_command.py +176 -0
  31. mcp_proxy_adapter/commands/plugins_command.py +235 -0
  32. mcp_proxy_adapter/commands/protocol_management_command.py +232 -0
  33. mcp_proxy_adapter/commands/proxy_registration_command.py +268 -0
  34. mcp_proxy_adapter/commands/reload_command.py +48 -50
  35. mcp_proxy_adapter/commands/result.py +1 -0
  36. mcp_proxy_adapter/commands/roles_management_command.py +697 -0
  37. mcp_proxy_adapter/commands/ssl_setup_command.py +483 -0
  38. mcp_proxy_adapter/commands/token_management_command.py +529 -0
  39. mcp_proxy_adapter/commands/transport_management_command.py +144 -0
  40. mcp_proxy_adapter/commands/unload_command.py +158 -0
  41. mcp_proxy_adapter/config.py +99 -2
  42. mcp_proxy_adapter/core/auth_validator.py +606 -0
  43. mcp_proxy_adapter/core/certificate_utils.py +827 -0
  44. mcp_proxy_adapter/core/config_converter.py +405 -0
  45. mcp_proxy_adapter/core/config_validator.py +218 -0
  46. mcp_proxy_adapter/core/logging.py +11 -0
  47. mcp_proxy_adapter/core/protocol_manager.py +226 -0
  48. mcp_proxy_adapter/core/proxy_registration.py +270 -0
  49. mcp_proxy_adapter/core/role_utils.py +426 -0
  50. mcp_proxy_adapter/core/security_adapter.py +373 -0
  51. mcp_proxy_adapter/core/security_factory.py +239 -0
  52. mcp_proxy_adapter/core/settings.py +1 -0
  53. mcp_proxy_adapter/core/ssl_utils.py +233 -0
  54. mcp_proxy_adapter/core/transport_manager.py +292 -0
  55. mcp_proxy_adapter/custom_openapi.py +22 -11
  56. mcp_proxy_adapter/examples/basic_server/config.json +58 -23
  57. mcp_proxy_adapter/examples/basic_server/config_all_protocols.json +54 -0
  58. mcp_proxy_adapter/examples/basic_server/config_http.json +70 -0
  59. mcp_proxy_adapter/examples/basic_server/config_http_only.json +52 -0
  60. mcp_proxy_adapter/examples/basic_server/config_https.json +58 -0
  61. mcp_proxy_adapter/examples/basic_server/config_mtls.json +58 -0
  62. mcp_proxy_adapter/examples/basic_server/config_ssl.json +46 -0
  63. mcp_proxy_adapter/examples/basic_server/server.py +17 -1
  64. mcp_proxy_adapter/examples/custom_commands/__init__.py +1 -1
  65. mcp_proxy_adapter/examples/custom_commands/advanced_hooks.py +339 -23
  66. mcp_proxy_adapter/examples/custom_commands/auto_commands/test_command.py +105 -0
  67. mcp_proxy_adapter/examples/custom_commands/catalog/commands/test_command.py +129 -0
  68. mcp_proxy_adapter/examples/custom_commands/config.json +97 -41
  69. mcp_proxy_adapter/examples/custom_commands/config_all_protocols.json +46 -0
  70. mcp_proxy_adapter/examples/custom_commands/config_https_only.json +46 -0
  71. mcp_proxy_adapter/examples/custom_commands/config_https_transport.json +33 -0
  72. mcp_proxy_adapter/examples/custom_commands/config_mtls_only.json +46 -0
  73. mcp_proxy_adapter/examples/custom_commands/config_mtls_transport.json +33 -0
  74. mcp_proxy_adapter/examples/custom_commands/config_single_transport.json +33 -0
  75. mcp_proxy_adapter/examples/custom_commands/full_help_response.json +1 -0
  76. mcp_proxy_adapter/examples/custom_commands/generated_openapi.json +629 -0
  77. mcp_proxy_adapter/examples/custom_commands/get_openapi.py +103 -0
  78. mcp_proxy_adapter/examples/custom_commands/loadable_commands/test_ignored.py +129 -0
  79. mcp_proxy_adapter/examples/custom_commands/proxy_connection_manager.py +278 -0
  80. mcp_proxy_adapter/examples/custom_commands/server.py +92 -63
  81. mcp_proxy_adapter/examples/custom_commands/simple_openapi_server.py +75 -0
  82. mcp_proxy_adapter/examples/custom_commands/start_server_with_proxy_manager.py +299 -0
  83. mcp_proxy_adapter/examples/custom_commands/start_server_with_registration.py +278 -0
  84. mcp_proxy_adapter/examples/custom_commands/test_openapi.py +27 -0
  85. mcp_proxy_adapter/examples/custom_commands/test_registry.py +23 -0
  86. mcp_proxy_adapter/examples/custom_commands/test_simple.py +19 -0
  87. mcp_proxy_adapter/examples/custom_project_example/README.md +103 -0
  88. mcp_proxy_adapter/examples/custom_project_example/README_EN.md +103 -0
  89. mcp_proxy_adapter/examples/simple_custom_commands/README.md +149 -0
  90. mcp_proxy_adapter/examples/simple_custom_commands/README_EN.md +149 -0
  91. mcp_proxy_adapter/main.py +175 -0
  92. mcp_proxy_adapter/schemas/roles_schema.json +162 -0
  93. mcp_proxy_adapter/tests/unit/test_config.py +53 -0
  94. mcp_proxy_adapter/version.py +1 -1
  95. {mcp_proxy_adapter-4.1.0.dist-info → mcp_proxy_adapter-6.0.0.dist-info}/METADATA +2 -1
  96. mcp_proxy_adapter-6.0.0.dist-info/RECORD +179 -0
  97. mcp_proxy_adapter/commands/reload_settings_command.py +0 -125
  98. mcp_proxy_adapter-4.1.0.dist-info/RECORD +0 -110
  99. {mcp_proxy_adapter-4.1.0.dist-info → mcp_proxy_adapter-6.0.0.dist-info}/WHEEL +0 -0
  100. {mcp_proxy_adapter-4.1.0.dist-info → mcp_proxy_adapter-6.0.0.dist-info}/licenses/LICENSE +0 -0
  101. {mcp_proxy_adapter-4.1.0.dist-info → mcp_proxy_adapter-6.0.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,405 @@
1
+ """
2
+ Configuration Converter for security framework integration.
3
+
4
+ This module provides utilities to convert between mcp_proxy_adapter configuration
5
+ format and mcp_security_framework configuration format, ensuring backward compatibility.
6
+ """
7
+
8
+ import json
9
+ import logging
10
+ from typing import Dict, Any, Optional
11
+ from pathlib import Path
12
+
13
+ from mcp_proxy_adapter.core.logging import logger
14
+
15
+
16
+ class ConfigConverter:
17
+ """
18
+ Converter for configuration formats.
19
+
20
+ Provides methods to convert between mcp_proxy_adapter configuration
21
+ and mcp_security_framework configuration formats.
22
+ """
23
+
24
+ @staticmethod
25
+ def to_security_framework_config(mcp_config: Dict[str, Any]) -> Dict[str, Any]:
26
+ """
27
+ Convert mcp_proxy_adapter configuration to SecurityConfig format.
28
+
29
+ Args:
30
+ mcp_config: mcp_proxy_adapter configuration dictionary
31
+
32
+ Returns:
33
+ SecurityConfig compatible dictionary
34
+ """
35
+ try:
36
+ # Start with default security framework config
37
+ security_config = {
38
+ "auth": {
39
+ "enabled": True,
40
+ "methods": ["api_key"],
41
+ "api_keys": {},
42
+ "jwt_secret": "",
43
+ "jwt_algorithm": "HS256"
44
+ },
45
+ "ssl": {
46
+ "enabled": False,
47
+ "cert_file": None,
48
+ "key_file": None,
49
+ "ca_cert": None,
50
+ "min_tls_version": "TLSv1.2",
51
+ "verify_client": False,
52
+ "client_cert_required": False
53
+ },
54
+ "permissions": {
55
+ "enabled": True,
56
+ "roles_file": "roles.json",
57
+ "default_role": "user",
58
+ "deny_by_default": True
59
+ },
60
+ "rate_limit": {
61
+ "enabled": True,
62
+ "requests_per_minute": 60,
63
+ "requests_per_hour": 1000,
64
+ "burst_limit": 10,
65
+ "by_ip": True,
66
+ "by_user": True
67
+ }
68
+ }
69
+
70
+ # Convert from security section if exists
71
+ if "security" in mcp_config:
72
+ security_section = mcp_config["security"]
73
+
74
+ # Convert auth config
75
+ if "auth" in security_section:
76
+ auth_config = security_section["auth"]
77
+ security_config["auth"].update({
78
+ "enabled": auth_config.get("enabled", True),
79
+ "methods": auth_config.get("methods", ["api_key"]),
80
+ "api_keys": auth_config.get("api_keys", {}),
81
+ "jwt_secret": auth_config.get("jwt_secret", ""),
82
+ "jwt_algorithm": auth_config.get("jwt_algorithm", "HS256")
83
+ })
84
+
85
+ # Convert SSL config
86
+ if "ssl" in security_section:
87
+ ssl_config = security_section["ssl"]
88
+ security_config["ssl"].update({
89
+ "enabled": ssl_config.get("enabled", False),
90
+ "cert_file": ssl_config.get("cert_file"),
91
+ "key_file": ssl_config.get("key_file"),
92
+ "ca_cert": ssl_config.get("ca_cert"),
93
+ "min_tls_version": ssl_config.get("min_tls_version", "TLSv1.2"),
94
+ "verify_client": ssl_config.get("verify_client", False),
95
+ "client_cert_required": ssl_config.get("client_cert_required", False)
96
+ })
97
+
98
+ # Convert permissions config
99
+ if "permissions" in security_section:
100
+ permissions_config = security_section["permissions"]
101
+ security_config["permissions"].update({
102
+ "enabled": permissions_config.get("enabled", True),
103
+ "roles_file": permissions_config.get("roles_file", "roles.json"),
104
+ "default_role": permissions_config.get("default_role", "user"),
105
+ "deny_by_default": permissions_config.get("deny_by_default", True)
106
+ })
107
+
108
+ # Convert rate limit config
109
+ if "rate_limit" in security_section:
110
+ rate_limit_config = security_section["rate_limit"]
111
+ security_config["rate_limit"].update({
112
+ "enabled": rate_limit_config.get("enabled", True),
113
+ "requests_per_minute": rate_limit_config.get("requests_per_minute", 60),
114
+ "requests_per_hour": rate_limit_config.get("requests_per_hour", 1000),
115
+ "burst_limit": rate_limit_config.get("burst_limit", 10),
116
+ "by_ip": rate_limit_config.get("by_ip", True),
117
+ "by_user": rate_limit_config.get("by_user", True)
118
+ })
119
+
120
+ # Convert from legacy SSL config if security section doesn't exist
121
+ elif "ssl" in mcp_config:
122
+ ssl_config = mcp_config["ssl"]
123
+ security_config["ssl"].update({
124
+ "enabled": ssl_config.get("enabled", False),
125
+ "cert_file": ssl_config.get("cert_file"),
126
+ "key_file": ssl_config.get("key_file"),
127
+ "ca_cert": ssl_config.get("ca_cert"),
128
+ "min_tls_version": ssl_config.get("min_tls_version", "TLSv1.2"),
129
+ "verify_client": ssl_config.get("verify_client", False),
130
+ "client_cert_required": ssl_config.get("client_cert_required", False)
131
+ })
132
+
133
+ # Extract API keys from legacy SSL config
134
+ if "api_keys" in ssl_config:
135
+ security_config["auth"]["api_keys"] = ssl_config["api_keys"]
136
+
137
+ # Convert from legacy roles config
138
+ if "roles" in mcp_config:
139
+ roles_config = mcp_config["roles"]
140
+ security_config["permissions"].update({
141
+ "enabled": roles_config.get("enabled", True),
142
+ "roles_file": roles_config.get("config_file", "roles.json"),
143
+ "default_role": "user",
144
+ "deny_by_default": roles_config.get("default_policy", {}).get("deny_by_default", True)
145
+ })
146
+
147
+ logger.info("Configuration converted to security framework format successfully")
148
+ return security_config
149
+
150
+ except Exception as e:
151
+ logger.error(f"Failed to convert configuration to security framework format: {e}")
152
+ return ConfigConverter._get_default_security_config()
153
+
154
+ @staticmethod
155
+ def from_security_framework_config(security_config: Dict[str, Any]) -> Dict[str, Any]:
156
+ """
157
+ Convert SecurityConfig format to mcp_proxy_adapter configuration.
158
+
159
+ Args:
160
+ security_config: SecurityConfig compatible dictionary
161
+
162
+ Returns:
163
+ mcp_proxy_adapter configuration dictionary
164
+ """
165
+ try:
166
+ mcp_config = {
167
+ "security": {
168
+ "framework": "mcp_security_framework",
169
+ "enabled": True
170
+ }
171
+ }
172
+
173
+ # Convert auth config
174
+ if "auth" in security_config:
175
+ auth_config = security_config["auth"]
176
+ mcp_config["security"]["auth"] = {
177
+ "enabled": auth_config.get("enabled", True),
178
+ "methods": auth_config.get("methods", ["api_key"]),
179
+ "api_keys": auth_config.get("api_keys", {}),
180
+ "jwt_secret": auth_config.get("jwt_secret", ""),
181
+ "jwt_algorithm": auth_config.get("jwt_algorithm", "HS256")
182
+ }
183
+
184
+ # Convert SSL config
185
+ if "ssl" in security_config:
186
+ ssl_config = security_config["ssl"]
187
+ mcp_config["security"]["ssl"] = {
188
+ "enabled": ssl_config.get("enabled", False),
189
+ "cert_file": ssl_config.get("cert_file"),
190
+ "key_file": ssl_config.get("key_file"),
191
+ "ca_cert": ssl_config.get("ca_cert"),
192
+ "min_tls_version": ssl_config.get("min_tls_version", "TLSv1.2"),
193
+ "verify_client": ssl_config.get("verify_client", False),
194
+ "client_cert_required": ssl_config.get("client_cert_required", False)
195
+ }
196
+
197
+ # Convert permissions config
198
+ if "permissions" in security_config:
199
+ permissions_config = security_config["permissions"]
200
+ mcp_config["security"]["permissions"] = {
201
+ "enabled": permissions_config.get("enabled", True),
202
+ "roles_file": permissions_config.get("roles_file", "roles.json"),
203
+ "default_role": permissions_config.get("default_role", "user"),
204
+ "deny_by_default": permissions_config.get("deny_by_default", True)
205
+ }
206
+
207
+ # Convert rate limit config
208
+ if "rate_limit" in security_config:
209
+ rate_limit_config = security_config["rate_limit"]
210
+ mcp_config["security"]["rate_limit"] = {
211
+ "enabled": rate_limit_config.get("enabled", True),
212
+ "requests_per_minute": rate_limit_config.get("requests_per_minute", 60),
213
+ "requests_per_hour": rate_limit_config.get("requests_per_hour", 1000),
214
+ "burst_limit": rate_limit_config.get("burst_limit", 10),
215
+ "by_ip": rate_limit_config.get("by_ip", True),
216
+ "by_user": rate_limit_config.get("by_user", True)
217
+ }
218
+
219
+ logger.info("Configuration converted from security framework format successfully")
220
+ return mcp_config
221
+
222
+ except Exception as e:
223
+ logger.error(f"Failed to convert configuration from security framework format: {e}")
224
+ return ConfigConverter._get_default_mcp_config()
225
+
226
+ @staticmethod
227
+ def migrate_legacy_config(config_path: str, output_path: Optional[str] = None) -> bool:
228
+ """
229
+ Migrate legacy configuration to new security framework format.
230
+
231
+ Args:
232
+ config_path: Path to legacy configuration file
233
+ output_path: Path to output migrated configuration file (optional)
234
+
235
+ Returns:
236
+ True if migration successful, False otherwise
237
+ """
238
+ try:
239
+ # Read legacy configuration
240
+ with open(config_path, 'r', encoding='utf-8') as f:
241
+ legacy_config = json.load(f)
242
+
243
+ # Convert to new format
244
+ new_config = ConfigConverter.to_security_framework_config(legacy_config)
245
+
246
+ # Add security section to legacy config
247
+ legacy_config["security"] = new_config
248
+
249
+ # Determine output path
250
+ if output_path is None:
251
+ output_path = config_path.replace('.json', '_migrated.json')
252
+
253
+ # Write migrated configuration
254
+ with open(output_path, 'w', encoding='utf-8') as f:
255
+ json.dump(legacy_config, f, indent=2, ensure_ascii=False)
256
+
257
+ logger.info(f"Configuration migrated successfully to {output_path}")
258
+ return True
259
+
260
+ except Exception as e:
261
+ logger.error(f"Failed to migrate configuration: {e}")
262
+ return False
263
+
264
+ @staticmethod
265
+ def validate_security_config(config: Dict[str, Any]) -> bool:
266
+ """
267
+ Validate security configuration format.
268
+
269
+ Args:
270
+ config: Configuration dictionary to validate
271
+
272
+ Returns:
273
+ True if configuration is valid, False otherwise
274
+ """
275
+ try:
276
+ # Check if security section exists
277
+ if "security" not in config:
278
+ logger.error("Security section not found in configuration")
279
+ return False
280
+
281
+ security_section = config["security"]
282
+
283
+ # Validate required fields
284
+ required_sections = ["auth", "ssl", "permissions", "rate_limit"]
285
+ for section in required_sections:
286
+ if section not in security_section:
287
+ logger.error(f"Required section '{section}' not found in security configuration")
288
+ return False
289
+
290
+ if not isinstance(security_section[section], dict):
291
+ logger.error(f"Section '{section}' must be a dictionary")
292
+ return False
293
+
294
+ # Validate auth configuration
295
+ auth_config = security_section["auth"]
296
+ if not isinstance(auth_config.get("methods", []), list):
297
+ logger.error("Auth methods must be a list")
298
+ return False
299
+
300
+ if not isinstance(auth_config.get("api_keys", {}), dict):
301
+ logger.error("API keys must be a dictionary")
302
+ return False
303
+
304
+ # Validate SSL configuration
305
+ ssl_config = security_section["ssl"]
306
+ if not isinstance(ssl_config.get("enabled", False), bool):
307
+ logger.error("SSL enabled must be a boolean")
308
+ return False
309
+
310
+ # Validate permissions configuration
311
+ permissions_config = security_section["permissions"]
312
+ if not isinstance(permissions_config.get("enabled", True), bool):
313
+ logger.error("Permissions enabled must be a boolean")
314
+ return False
315
+
316
+ # Validate rate limit configuration
317
+ rate_limit_config = security_section["rate_limit"]
318
+ if not isinstance(rate_limit_config.get("enabled", True), bool):
319
+ logger.error("Rate limit enabled must be a boolean")
320
+ return False
321
+
322
+ if not isinstance(rate_limit_config.get("requests_per_minute", 60), int):
323
+ logger.error("Requests per minute must be an integer")
324
+ return False
325
+
326
+ logger.info("Security configuration validation passed")
327
+ return True
328
+
329
+ except Exception as e:
330
+ logger.error(f"Configuration validation failed: {e}")
331
+ return False
332
+
333
+ @staticmethod
334
+ def _get_default_security_config() -> Dict[str, Any]:
335
+ """
336
+ Get default security framework configuration.
337
+
338
+ Returns:
339
+ Default security framework configuration
340
+ """
341
+ return {
342
+ "auth": {
343
+ "enabled": True,
344
+ "methods": ["api_key"],
345
+ "api_keys": {},
346
+ "jwt_secret": "",
347
+ "jwt_algorithm": "HS256"
348
+ },
349
+ "ssl": {
350
+ "enabled": False,
351
+ "cert_file": None,
352
+ "key_file": None,
353
+ "ca_cert": None,
354
+ "min_tls_version": "TLSv1.2",
355
+ "verify_client": False,
356
+ "client_cert_required": False
357
+ },
358
+ "permissions": {
359
+ "enabled": True,
360
+ "roles_file": "roles.json",
361
+ "default_role": "user",
362
+ "deny_by_default": True
363
+ },
364
+ "rate_limit": {
365
+ "enabled": True,
366
+ "requests_per_minute": 60,
367
+ "requests_per_hour": 1000,
368
+ "burst_limit": 10,
369
+ "by_ip": True,
370
+ "by_user": True
371
+ }
372
+ }
373
+
374
+ @staticmethod
375
+ def _get_default_mcp_config() -> Dict[str, Any]:
376
+ """
377
+ Get default mcp_proxy_adapter configuration.
378
+
379
+ Returns:
380
+ Default mcp_proxy_adapter configuration
381
+ """
382
+ return {
383
+ "security": {
384
+ "framework": "mcp_security_framework",
385
+ "enabled": True,
386
+ "auth": {
387
+ "enabled": True,
388
+ "methods": ["api_key"],
389
+ "api_keys": {}
390
+ },
391
+ "ssl": {
392
+ "enabled": False,
393
+ "cert_file": None,
394
+ "key_file": None
395
+ },
396
+ "permissions": {
397
+ "enabled": True,
398
+ "roles_file": "roles.json"
399
+ },
400
+ "rate_limit": {
401
+ "enabled": True,
402
+ "requests_per_minute": 60
403
+ }
404
+ }
405
+ }
@@ -0,0 +1,218 @@
1
+ """
2
+ Configuration validator for strict validation before startup.
3
+
4
+ This module provides strict validation of configuration to prevent startup
5
+ with invalid or insecure configurations.
6
+ """
7
+
8
+ import os
9
+ import json
10
+ from typing import Dict, Any, List, Optional
11
+ from pathlib import Path
12
+
13
+ from mcp_proxy_adapter.core.logging import logger
14
+
15
+
16
+ class ConfigValidator:
17
+ """
18
+ Strict configuration validator.
19
+
20
+ Validates configuration before startup and prevents startup
21
+ with invalid or insecure configurations.
22
+ """
23
+
24
+ def __init__(self, config: Dict[str, Any]):
25
+ """
26
+ Initialize configuration validator.
27
+
28
+ Args:
29
+ config: Configuration dictionary to validate
30
+ """
31
+ self.config = config
32
+ self.errors: List[str] = []
33
+ self.warnings: List[str] = []
34
+
35
+ def validate_all(self) -> bool:
36
+ """
37
+ Validate all configuration sections.
38
+
39
+ Returns:
40
+ True if configuration is valid, False otherwise
41
+ """
42
+ self.errors.clear()
43
+ self.warnings.clear()
44
+
45
+ # Validate basic structure
46
+ if not self._validate_basic_structure():
47
+ return False
48
+
49
+ # Validate server configuration
50
+ if not self._validate_server_config():
51
+ return False
52
+
53
+ # Validate security configuration
54
+ if not self._validate_security_config():
55
+ return False
56
+
57
+ # Validate commands configuration
58
+ if not self._validate_commands_config():
59
+ return False
60
+
61
+ # Validate SSL configuration
62
+ if not self._validate_ssl_config():
63
+ return False
64
+
65
+ # Validate roles configuration
66
+ if not self._validate_roles_config():
67
+ return False
68
+
69
+ # Log warnings if any
70
+ if self.warnings:
71
+ for warning in self.warnings:
72
+ logger.warning(f"Configuration warning: {warning}")
73
+
74
+ # Return success only if no errors
75
+ return len(self.errors) == 0
76
+
77
+ def _validate_basic_structure(self) -> bool:
78
+ """Validate basic configuration structure."""
79
+ required_sections = ["server", "logging", "commands"]
80
+
81
+ for section in required_sections:
82
+ if section not in self.config:
83
+ self.errors.append(f"Missing required configuration section: {section}")
84
+
85
+ return len(self.errors) == 0
86
+
87
+ def _validate_server_config(self) -> bool:
88
+ """Validate server configuration."""
89
+ server_config = self.config.get("server", {})
90
+
91
+ # Validate host
92
+ host = server_config.get("host")
93
+ if not host:
94
+ self.errors.append("Server host is required")
95
+
96
+ # Validate port
97
+ port = server_config.get("port")
98
+ if not isinstance(port, int) or port < 1 or port > 65535:
99
+ self.errors.append("Server port must be an integer between 1 and 65535")
100
+
101
+ return len(self.errors) == 0
102
+
103
+ def _validate_security_config(self) -> bool:
104
+ """Validate security configuration."""
105
+ security_config = self.config.get("security", {})
106
+
107
+ # Check if security is enabled
108
+ security_enabled = security_config.get("enabled", True)
109
+ auth_enabled = self.config.get("auth_enabled", False)
110
+
111
+ if security_enabled and auth_enabled:
112
+ # Validate auth configuration
113
+ auth_config = security_config.get("auth", {})
114
+ if not auth_config.get("enabled", False):
115
+ self.errors.append("Security is enabled but auth is disabled")
116
+ return False
117
+
118
+ # Validate API keys if auth is enabled
119
+ if auth_config.get("enabled", False):
120
+ api_keys = auth_config.get("api_keys", {})
121
+ if not api_keys:
122
+ self.errors.append("API keys are required when authentication is enabled")
123
+ return False
124
+
125
+ # Validate API key format
126
+ for key, value in api_keys.items():
127
+ if not key or not value:
128
+ self.errors.append("API keys must have non-empty key and value")
129
+ return False
130
+
131
+ return len(self.errors) == 0
132
+
133
+ def _validate_commands_config(self) -> bool:
134
+ """Validate commands configuration."""
135
+ commands_config = self.config.get("commands", {})
136
+
137
+ # Validate commands directory if auto_discovery is enabled
138
+ if commands_config.get("auto_discovery", True):
139
+ commands_dir = commands_config.get("commands_directory", "./commands")
140
+ if not os.path.exists(commands_dir):
141
+ self.warnings.append(f"Commands directory does not exist: {commands_dir}")
142
+
143
+ return True
144
+
145
+ def _validate_ssl_config(self) -> bool:
146
+ """Validate SSL configuration."""
147
+ ssl_config = self.config.get("ssl", {})
148
+ ssl_enabled = ssl_config.get("enabled", False)
149
+
150
+ if ssl_enabled:
151
+ # Validate certificate files
152
+ cert_file = ssl_config.get("cert_file")
153
+ key_file = ssl_config.get("key_file")
154
+
155
+ if not cert_file or not key_file:
156
+ self.errors.append("SSL certificate and key files are required when SSL is enabled")
157
+ return False
158
+
159
+ if not os.path.exists(cert_file):
160
+ self.errors.append(f"SSL certificate file not found: {cert_file}")
161
+
162
+ if not os.path.exists(key_file):
163
+ self.errors.append(f"SSL private key file not found: {key_file}")
164
+
165
+ return len(self.errors) == 0
166
+
167
+ def _validate_roles_config(self) -> bool:
168
+ """Validate roles configuration."""
169
+ roles_config = self.config.get("roles", {})
170
+ roles_enabled = roles_config.get("enabled", False)
171
+
172
+ if roles_enabled:
173
+ config_file = roles_config.get("config_file")
174
+ if not config_file:
175
+ self.errors.append("Roles config file is required when roles are enabled")
176
+ return False
177
+
178
+ if not os.path.exists(config_file):
179
+ self.errors.append(f"Roles config file not found: {config_file}")
180
+ return False
181
+
182
+ # Validate roles schema file
183
+ try:
184
+ with open(config_file, 'r') as f:
185
+ roles_schema = json.load(f)
186
+
187
+ if "roles" not in roles_schema:
188
+ self.errors.append("Roles config file must contain 'roles' section")
189
+ return False
190
+
191
+ except (json.JSONDecodeError, IOError) as e:
192
+ self.errors.append(f"Failed to read roles config file: {e}")
193
+ return False
194
+
195
+ return len(self.errors) == 0
196
+
197
+ def get_errors(self) -> List[str]:
198
+ """Get validation errors."""
199
+ return self.errors.copy()
200
+
201
+ def get_warnings(self) -> List[str]:
202
+ """Get validation warnings."""
203
+ return self.warnings.copy()
204
+
205
+ def print_validation_report(self):
206
+ """Print validation report."""
207
+ if self.errors:
208
+ logger.error("Configuration validation failed:")
209
+ for error in self.errors:
210
+ logger.error(f" - {error}")
211
+
212
+ if self.warnings:
213
+ logger.warning("Configuration warnings:")
214
+ for warning in self.warnings:
215
+ logger.warning(f" - {warning}")
216
+
217
+ if not self.errors and not self.warnings:
218
+ logger.info("Configuration validation passed")
@@ -121,6 +121,17 @@ def setup_logging(
121
121
  """
122
122
  # Get parameters from configuration if not explicitly specified
123
123
  level = level or config.get("logging.level", "INFO")
124
+
125
+ # Check debug level from config if debug is enabled
126
+ if config.get("debug.enabled", False):
127
+ debug_level = config.get("debug.level", "INFO")
128
+ # Use debug level if it's more verbose than the logging level
129
+ debug_level_num = getattr(logging, debug_level.upper(), logging.INFO)
130
+ level_num = getattr(logging, level.upper(), logging.INFO)
131
+ if debug_level_num < level_num:
132
+ level = debug_level
133
+ logger.debug(f"Using debug level from config: {debug_level}")
134
+
124
135
  log_file = log_file or config.get("logging.file")
125
136
  rotation_type = rotation_type or "size" # Default to size-based rotation
126
137