mcp-proxy-adapter 6.3.3__py3-none-any.whl → 6.3.5__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- mcp_proxy_adapter/__init__.py +9 -5
- mcp_proxy_adapter/__main__.py +1 -1
- mcp_proxy_adapter/api/app.py +227 -176
- mcp_proxy_adapter/api/handlers.py +68 -60
- mcp_proxy_adapter/api/middleware/__init__.py +7 -5
- mcp_proxy_adapter/api/middleware/base.py +19 -16
- mcp_proxy_adapter/api/middleware/command_permission_middleware.py +44 -34
- mcp_proxy_adapter/api/middleware/error_handling.py +57 -67
- mcp_proxy_adapter/api/middleware/factory.py +50 -52
- mcp_proxy_adapter/api/middleware/logging.py +46 -30
- mcp_proxy_adapter/api/middleware/performance.py +19 -16
- mcp_proxy_adapter/api/middleware/protocol_middleware.py +80 -50
- mcp_proxy_adapter/api/middleware/transport_middleware.py +26 -24
- mcp_proxy_adapter/api/middleware/unified_security.py +70 -51
- mcp_proxy_adapter/api/middleware/user_info_middleware.py +43 -34
- mcp_proxy_adapter/api/schemas.py +69 -43
- mcp_proxy_adapter/api/tool_integration.py +83 -63
- mcp_proxy_adapter/api/tools.py +60 -50
- mcp_proxy_adapter/commands/__init__.py +15 -6
- mcp_proxy_adapter/commands/auth_validation_command.py +107 -110
- mcp_proxy_adapter/commands/base.py +108 -112
- mcp_proxy_adapter/commands/builtin_commands.py +28 -18
- mcp_proxy_adapter/commands/catalog_manager.py +394 -265
- mcp_proxy_adapter/commands/cert_monitor_command.py +222 -204
- mcp_proxy_adapter/commands/certificate_management_command.py +210 -213
- mcp_proxy_adapter/commands/command_registry.py +275 -226
- mcp_proxy_adapter/commands/config_command.py +48 -33
- mcp_proxy_adapter/commands/dependency_container.py +22 -23
- mcp_proxy_adapter/commands/dependency_manager.py +65 -56
- mcp_proxy_adapter/commands/echo_command.py +15 -15
- mcp_proxy_adapter/commands/health_command.py +31 -29
- mcp_proxy_adapter/commands/help_command.py +97 -61
- mcp_proxy_adapter/commands/hooks.py +65 -49
- mcp_proxy_adapter/commands/key_management_command.py +148 -147
- mcp_proxy_adapter/commands/load_command.py +58 -40
- mcp_proxy_adapter/commands/plugins_command.py +80 -54
- mcp_proxy_adapter/commands/protocol_management_command.py +60 -48
- mcp_proxy_adapter/commands/proxy_registration_command.py +107 -115
- mcp_proxy_adapter/commands/reload_command.py +43 -37
- mcp_proxy_adapter/commands/result.py +26 -33
- mcp_proxy_adapter/commands/role_test_command.py +26 -26
- mcp_proxy_adapter/commands/roles_management_command.py +176 -173
- mcp_proxy_adapter/commands/security_command.py +134 -122
- mcp_proxy_adapter/commands/settings_command.py +47 -56
- mcp_proxy_adapter/commands/ssl_setup_command.py +109 -129
- mcp_proxy_adapter/commands/token_management_command.py +129 -158
- mcp_proxy_adapter/commands/transport_management_command.py +41 -36
- mcp_proxy_adapter/commands/unload_command.py +42 -37
- mcp_proxy_adapter/config.py +36 -35
- mcp_proxy_adapter/core/__init__.py +19 -21
- mcp_proxy_adapter/core/app_factory.py +30 -9
- mcp_proxy_adapter/core/app_runner.py +81 -64
- mcp_proxy_adapter/core/auth_validator.py +176 -182
- mcp_proxy_adapter/core/certificate_utils.py +469 -426
- mcp_proxy_adapter/core/client.py +155 -126
- mcp_proxy_adapter/core/client_manager.py +60 -54
- mcp_proxy_adapter/core/client_security.py +108 -88
- mcp_proxy_adapter/core/config_converter.py +176 -143
- mcp_proxy_adapter/core/config_validator.py +12 -4
- mcp_proxy_adapter/core/crl_utils.py +21 -7
- mcp_proxy_adapter/core/errors.py +64 -20
- mcp_proxy_adapter/core/logging.py +34 -29
- mcp_proxy_adapter/core/mtls_asgi.py +29 -25
- mcp_proxy_adapter/core/mtls_asgi_app.py +66 -54
- mcp_proxy_adapter/core/protocol_manager.py +154 -104
- mcp_proxy_adapter/core/proxy_client.py +202 -144
- mcp_proxy_adapter/core/proxy_registration.py +12 -2
- mcp_proxy_adapter/core/role_utils.py +139 -125
- mcp_proxy_adapter/core/security_adapter.py +88 -77
- mcp_proxy_adapter/core/security_factory.py +50 -44
- mcp_proxy_adapter/core/security_integration.py +72 -24
- mcp_proxy_adapter/core/server_adapter.py +68 -64
- mcp_proxy_adapter/core/server_engine.py +71 -53
- mcp_proxy_adapter/core/settings.py +68 -58
- mcp_proxy_adapter/core/ssl_utils.py +69 -56
- mcp_proxy_adapter/core/transport_manager.py +72 -60
- mcp_proxy_adapter/core/unified_config_adapter.py +201 -150
- mcp_proxy_adapter/core/utils.py +4 -2
- mcp_proxy_adapter/custom_openapi.py +107 -99
- mcp_proxy_adapter/examples/basic_framework/main.py +9 -2
- mcp_proxy_adapter/examples/commands/__init__.py +1 -1
- mcp_proxy_adapter/examples/create_certificates_simple.py +182 -71
- mcp_proxy_adapter/examples/debug_request_state.py +38 -19
- mcp_proxy_adapter/examples/debug_role_chain.py +53 -20
- mcp_proxy_adapter/examples/demo_client.py +48 -36
- mcp_proxy_adapter/examples/examples/basic_framework/main.py +9 -2
- mcp_proxy_adapter/examples/examples/full_application/__init__.py +1 -0
- mcp_proxy_adapter/examples/examples/full_application/commands/custom_echo_command.py +22 -10
- mcp_proxy_adapter/examples/examples/full_application/commands/dynamic_calculator_command.py +24 -17
- mcp_proxy_adapter/examples/examples/full_application/hooks/application_hooks.py +16 -3
- mcp_proxy_adapter/examples/examples/full_application/hooks/builtin_command_hooks.py +13 -3
- mcp_proxy_adapter/examples/examples/full_application/main.py +27 -2
- mcp_proxy_adapter/examples/examples/full_application/proxy_endpoints.py +48 -14
- mcp_proxy_adapter/examples/full_application/__init__.py +1 -0
- mcp_proxy_adapter/examples/full_application/commands/custom_echo_command.py +22 -10
- mcp_proxy_adapter/examples/full_application/commands/dynamic_calculator_command.py +24 -17
- mcp_proxy_adapter/examples/full_application/hooks/application_hooks.py +16 -3
- mcp_proxy_adapter/examples/full_application/hooks/builtin_command_hooks.py +13 -3
- mcp_proxy_adapter/examples/full_application/main.py +27 -2
- mcp_proxy_adapter/examples/full_application/proxy_endpoints.py +48 -14
- mcp_proxy_adapter/examples/generate_all_certificates.py +198 -73
- mcp_proxy_adapter/examples/generate_certificates.py +31 -16
- mcp_proxy_adapter/examples/generate_certificates_and_tokens.py +220 -74
- mcp_proxy_adapter/examples/generate_test_configs.py +68 -91
- mcp_proxy_adapter/examples/proxy_registration_example.py +76 -75
- mcp_proxy_adapter/examples/run_example.py +23 -5
- mcp_proxy_adapter/examples/run_full_test_suite.py +109 -71
- mcp_proxy_adapter/examples/run_proxy_server.py +22 -9
- mcp_proxy_adapter/examples/run_security_tests.py +103 -41
- mcp_proxy_adapter/examples/run_security_tests_fixed.py +72 -36
- mcp_proxy_adapter/examples/scripts/config_generator.py +288 -187
- mcp_proxy_adapter/examples/scripts/create_certificates_simple.py +185 -72
- mcp_proxy_adapter/examples/scripts/generate_certificates_and_tokens.py +220 -74
- mcp_proxy_adapter/examples/security_test_client.py +196 -127
- mcp_proxy_adapter/examples/setup_test_environment.py +17 -29
- mcp_proxy_adapter/examples/test_config.py +19 -4
- mcp_proxy_adapter/examples/test_config_generator.py +23 -7
- mcp_proxy_adapter/examples/test_examples.py +84 -56
- mcp_proxy_adapter/examples/universal_client.py +119 -62
- mcp_proxy_adapter/openapi.py +108 -115
- mcp_proxy_adapter/utils/config_generator.py +429 -274
- mcp_proxy_adapter/version.py +1 -2
- {mcp_proxy_adapter-6.3.3.dist-info → mcp_proxy_adapter-6.3.5.dist-info}/METADATA +1 -1
- mcp_proxy_adapter-6.3.5.dist-info/RECORD +143 -0
- mcp_proxy_adapter-6.3.3.dist-info/RECORD +0 -143
- {mcp_proxy_adapter-6.3.3.dist-info → mcp_proxy_adapter-6.3.5.dist-info}/WHEEL +0 -0
- {mcp_proxy_adapter-6.3.3.dist-info → mcp_proxy_adapter-6.3.5.dist-info}/entry_points.txt +0 -0
- {mcp_proxy_adapter-6.3.3.dist-info → mcp_proxy_adapter-6.3.5.dist-info}/licenses/LICENSE +0 -0
- {mcp_proxy_adapter-6.3.3.dist-info → mcp_proxy_adapter-6.3.5.dist-info}/top_level.txt +0 -0
@@ -18,8 +18,12 @@ from dataclasses import dataclass
|
|
18
18
|
try:
|
19
19
|
from mcp_security_framework import SecurityConfig
|
20
20
|
from mcp_security_framework.schemas.config import (
|
21
|
-
AuthConfig,
|
21
|
+
AuthConfig,
|
22
|
+
SSLConfig,
|
23
|
+
PermissionConfig,
|
24
|
+
RateLimitConfig,
|
22
25
|
)
|
26
|
+
|
23
27
|
SECURITY_FRAMEWORK_AVAILABLE = True
|
24
28
|
except ImportError:
|
25
29
|
SECURITY_FRAMEWORK_AVAILABLE = False
|
@@ -35,12 +39,12 @@ from mcp_proxy_adapter.core.logging import logger
|
|
35
39
|
@dataclass
|
36
40
|
class ValidationResult:
|
37
41
|
"""Result of configuration validation."""
|
38
|
-
|
42
|
+
|
39
43
|
is_valid: bool
|
40
44
|
errors: List[str]
|
41
45
|
warnings: List[str]
|
42
46
|
details: Dict[str, Any]
|
43
|
-
|
47
|
+
|
44
48
|
def __post_init__(self):
|
45
49
|
"""Initialize with empty lists if None."""
|
46
50
|
if self.errors is None:
|
@@ -54,162 +58,189 @@ class ValidationResult:
|
|
54
58
|
class UnifiedConfigAdapter:
|
55
59
|
"""
|
56
60
|
Unified adapter for converting mcp_proxy_adapter configuration to SecurityConfig.
|
57
|
-
|
61
|
+
|
58
62
|
This adapter handles:
|
59
63
|
- Legacy configuration format compatibility
|
60
64
|
- Configuration validation
|
61
65
|
- Conversion to mcp_security_framework format
|
62
66
|
- Default value management
|
63
67
|
"""
|
64
|
-
|
68
|
+
|
65
69
|
def __init__(self):
|
66
70
|
"""Initialize the unified configuration adapter."""
|
67
71
|
self.validation_errors = []
|
68
72
|
self.validation_warnings = []
|
69
|
-
|
70
|
-
def convert_to_security_config(
|
73
|
+
|
74
|
+
def convert_to_security_config(
|
75
|
+
self, config: Dict[str, Any]
|
76
|
+
) -> Optional[SecurityConfig]:
|
71
77
|
"""
|
72
78
|
Convert mcp_proxy_adapter configuration to SecurityConfig.
|
73
|
-
|
79
|
+
|
74
80
|
Args:
|
75
81
|
config: mcp_proxy_adapter configuration dictionary
|
76
|
-
|
82
|
+
|
77
83
|
Returns:
|
78
84
|
SecurityConfig instance or None if conversion failed
|
79
85
|
"""
|
80
86
|
if not SECURITY_FRAMEWORK_AVAILABLE:
|
81
|
-
logger.error(
|
87
|
+
logger.error(
|
88
|
+
"mcp_security_framework not available, cannot convert configuration"
|
89
|
+
)
|
82
90
|
return None
|
83
|
-
|
91
|
+
|
84
92
|
try:
|
85
93
|
# Validate configuration first
|
86
94
|
validation_result = self.validate_configuration(config)
|
87
95
|
if not validation_result.is_valid:
|
88
|
-
logger.error(
|
96
|
+
logger.error(
|
97
|
+
f"Configuration validation failed: {validation_result.errors}"
|
98
|
+
)
|
89
99
|
return None
|
90
|
-
|
100
|
+
|
91
101
|
# Convert configuration sections
|
92
102
|
auth_config = self._convert_auth_config(config)
|
93
103
|
ssl_config = self._convert_ssl_config(config)
|
94
104
|
permission_config = self._convert_permission_config(config)
|
95
105
|
rate_limit_config = self._convert_rate_limit_config(config)
|
96
|
-
|
106
|
+
|
97
107
|
# Create SecurityConfig
|
98
108
|
security_config = SecurityConfig(
|
99
109
|
auth=auth_config,
|
100
110
|
ssl=ssl_config,
|
101
111
|
permissions=permission_config,
|
102
|
-
rate_limit=rate_limit_config
|
112
|
+
rate_limit=rate_limit_config,
|
103
113
|
)
|
104
|
-
|
114
|
+
|
105
115
|
logger.info("Configuration successfully converted to SecurityConfig")
|
106
116
|
return security_config
|
107
|
-
|
117
|
+
|
108
118
|
except Exception as e:
|
109
119
|
logger.error(f"Failed to convert configuration: {e}")
|
110
120
|
return None
|
111
|
-
|
121
|
+
|
112
122
|
def validate_configuration(self, config: Dict[str, Any]) -> ValidationResult:
|
113
123
|
"""
|
114
124
|
Validate configuration before conversion.
|
115
|
-
|
125
|
+
|
116
126
|
Args:
|
117
127
|
config: Configuration dictionary to validate
|
118
|
-
|
128
|
+
|
119
129
|
Returns:
|
120
130
|
ValidationResult with validation details
|
121
131
|
"""
|
122
132
|
self.validation_errors = []
|
123
133
|
self.validation_warnings = []
|
124
|
-
|
134
|
+
|
125
135
|
# Debug: Check SSL config at start of validation
|
126
136
|
if "security" in config:
|
127
137
|
ssl_config = config["security"].get("ssl", {})
|
128
|
-
print(
|
129
|
-
|
138
|
+
print(
|
139
|
+
f"🔍 Debug: SSL config at start of validation: enabled={ssl_config.get('enabled', False)}"
|
140
|
+
)
|
141
|
+
|
130
142
|
# Debug: Check if root ssl section exists
|
131
143
|
if "ssl" in config:
|
132
|
-
print(
|
133
|
-
|
144
|
+
print(
|
145
|
+
f"🔍 Debug: Root SSL section found: enabled={config['ssl'].get('enabled', False)}"
|
146
|
+
)
|
147
|
+
|
134
148
|
# Check if config is a dictionary
|
135
149
|
if not isinstance(config, dict):
|
136
150
|
return ValidationResult(
|
137
151
|
is_valid=False,
|
138
152
|
errors=["Configuration must be a dictionary"],
|
139
153
|
warnings=[],
|
140
|
-
details={}
|
154
|
+
details={},
|
141
155
|
)
|
142
|
-
|
156
|
+
|
143
157
|
# Validate security section
|
144
158
|
self._validate_security_section(config)
|
145
|
-
|
159
|
+
|
146
160
|
# Validate legacy sections for compatibility
|
147
161
|
self._validate_legacy_sections(config)
|
148
|
-
|
162
|
+
|
149
163
|
# Check for conflicts
|
150
164
|
self._check_configuration_conflicts(config)
|
151
|
-
|
165
|
+
|
152
166
|
# Validate individual sections
|
153
167
|
self._validate_auth_section(config)
|
154
168
|
self._validate_ssl_section(config)
|
155
169
|
self._validate_permissions_section(config)
|
156
170
|
self._validate_rate_limit_section(config)
|
157
|
-
|
171
|
+
|
158
172
|
return ValidationResult(
|
159
173
|
is_valid=len(self.validation_errors) == 0,
|
160
174
|
errors=self.validation_errors.copy(),
|
161
175
|
warnings=self.validation_warnings.copy(),
|
162
176
|
details={
|
163
177
|
"has_security_section": "security" in config,
|
164
|
-
"has_legacy_sections": any(
|
178
|
+
"has_legacy_sections": any(
|
179
|
+
key in config for key in ["ssl", "roles", "auth_enabled"]
|
180
|
+
),
|
165
181
|
"total_errors": len(self.validation_errors),
|
166
|
-
"total_warnings": len(self.validation_warnings)
|
167
|
-
}
|
182
|
+
"total_warnings": len(self.validation_warnings),
|
183
|
+
},
|
168
184
|
)
|
169
|
-
|
185
|
+
|
170
186
|
def _validate_security_section(self, config: Dict[str, Any]):
|
171
187
|
"""Validate security section."""
|
172
188
|
security_config = config.get("security", {})
|
173
|
-
|
189
|
+
|
174
190
|
if not isinstance(security_config, dict):
|
175
191
|
self.validation_errors.append("Security section must be a dictionary")
|
176
192
|
return
|
177
|
-
|
193
|
+
|
178
194
|
# Check for unknown keys in security section
|
179
|
-
known_keys = {
|
195
|
+
known_keys = {
|
196
|
+
"enabled",
|
197
|
+
"auth",
|
198
|
+
"ssl",
|
199
|
+
"permissions",
|
200
|
+
"rate_limit",
|
201
|
+
"public_paths",
|
202
|
+
}
|
180
203
|
unknown_keys = set(security_config.keys()) - known_keys
|
181
|
-
|
204
|
+
|
182
205
|
if unknown_keys:
|
183
|
-
self.validation_warnings.append(
|
184
|
-
|
206
|
+
self.validation_warnings.append(
|
207
|
+
f"Unknown keys in security section: {unknown_keys}"
|
208
|
+
)
|
209
|
+
|
185
210
|
def _validate_legacy_sections(self, config: Dict[str, Any]):
|
186
211
|
"""Validate legacy configuration sections."""
|
187
212
|
legacy_sections = ["ssl", "roles", "auth_enabled", "rate_limit_enabled"]
|
188
|
-
|
213
|
+
|
189
214
|
for section in legacy_sections:
|
190
215
|
if section in config:
|
191
|
-
self.validation_warnings.append(
|
192
|
-
|
216
|
+
self.validation_warnings.append(
|
217
|
+
f"Legacy section '{section}' found, consider migrating to security section"
|
218
|
+
)
|
219
|
+
|
193
220
|
def _check_configuration_conflicts(self, config: Dict[str, Any]):
|
194
221
|
"""Check for configuration conflicts."""
|
195
222
|
security_config = config.get("security", {})
|
196
|
-
|
223
|
+
|
197
224
|
# Check for SSL configuration conflicts
|
198
225
|
if "ssl" in config and "ssl" in security_config:
|
199
|
-
self.validation_warnings.append(
|
200
|
-
|
226
|
+
self.validation_warnings.append(
|
227
|
+
"SSL configuration found in both root and security sections"
|
228
|
+
)
|
229
|
+
|
201
230
|
# Check for auth configuration conflicts
|
202
231
|
if "auth_enabled" in config and "auth" in security_config:
|
203
|
-
self.validation_warnings.append(
|
204
|
-
|
232
|
+
self.validation_warnings.append(
|
233
|
+
"Auth configuration found in both root and security sections"
|
234
|
+
)
|
235
|
+
|
205
236
|
def _validate_auth_section(self, config: Dict[str, Any]):
|
206
237
|
"""Validate authentication section."""
|
207
238
|
auth_config = self._get_auth_config(config)
|
208
|
-
|
239
|
+
|
209
240
|
if not isinstance(auth_config, dict):
|
210
241
|
self.validation_errors.append("Auth configuration must be a dictionary")
|
211
242
|
return
|
212
|
-
|
243
|
+
|
213
244
|
# Validate auth methods
|
214
245
|
methods = auth_config.get("methods", [])
|
215
246
|
if not isinstance(methods, list):
|
@@ -218,98 +249,115 @@ class UnifiedConfigAdapter:
|
|
218
249
|
valid_methods = {"api_key", "jwt", "certificate"}
|
219
250
|
invalid_methods = set(methods) - valid_methods
|
220
251
|
if invalid_methods:
|
221
|
-
self.validation_errors.append(
|
222
|
-
|
252
|
+
self.validation_errors.append(
|
253
|
+
f"Invalid auth methods: {invalid_methods}"
|
254
|
+
)
|
255
|
+
|
223
256
|
# Validate API keys
|
224
257
|
api_keys = auth_config.get("api_keys", {})
|
225
258
|
if not isinstance(api_keys, dict):
|
226
259
|
self.validation_errors.append("API keys must be a dictionary")
|
227
|
-
|
260
|
+
|
228
261
|
# Validate JWT configuration
|
229
262
|
if "jwt" in methods:
|
230
263
|
jwt_secret = auth_config.get("jwt_secret", "")
|
231
264
|
if not jwt_secret:
|
232
265
|
self.validation_warnings.append("JWT secret is empty or not set")
|
233
|
-
|
266
|
+
|
234
267
|
def _validate_ssl_section(self, config: Dict[str, Any]):
|
235
268
|
"""Validate SSL section."""
|
236
269
|
ssl_config = self._get_ssl_config(config)
|
237
|
-
|
270
|
+
|
238
271
|
if not isinstance(ssl_config, dict):
|
239
272
|
self.validation_errors.append("SSL configuration must be a dictionary")
|
240
273
|
return
|
241
|
-
|
274
|
+
|
242
275
|
# Validate certificate files
|
243
276
|
if ssl_config.get("enabled", False):
|
244
277
|
cert_file = ssl_config.get("cert_file")
|
245
278
|
key_file = ssl_config.get("key_file")
|
246
|
-
|
279
|
+
|
247
280
|
print(f"🔍 Debug: _validate_ssl_section: cert_file={cert_file}")
|
248
281
|
print(f"🔍 Debug: _validate_ssl_section: key_file={key_file}")
|
249
|
-
print(
|
250
|
-
|
251
|
-
|
282
|
+
print(
|
283
|
+
f"🔍 Debug: _validate_ssl_section: cert_file exists={Path(cert_file).exists() if cert_file else 'None'}"
|
284
|
+
)
|
285
|
+
print(
|
286
|
+
f"🔍 Debug: _validate_ssl_section: key_file exists={Path(key_file).exists() if key_file else 'None'}"
|
287
|
+
)
|
288
|
+
|
252
289
|
if cert_file and not Path(cert_file).exists():
|
253
|
-
self.validation_warnings.append(
|
254
|
-
|
290
|
+
self.validation_warnings.append(
|
291
|
+
f"SSL certificate file not found: {cert_file}"
|
292
|
+
)
|
293
|
+
|
255
294
|
if key_file and not Path(key_file).exists():
|
256
295
|
self.validation_warnings.append(f"SSL key file not found: {key_file}")
|
257
|
-
|
296
|
+
|
258
297
|
def _validate_permissions_section(self, config: Dict[str, Any]):
|
259
298
|
"""Validate permissions section."""
|
260
299
|
permissions_config = self._get_permissions_config(config)
|
261
|
-
|
300
|
+
|
262
301
|
if not isinstance(permissions_config, dict):
|
263
|
-
self.validation_errors.append(
|
302
|
+
self.validation_errors.append(
|
303
|
+
"Permissions configuration must be a dictionary"
|
304
|
+
)
|
264
305
|
return
|
265
|
-
|
306
|
+
|
266
307
|
# Validate roles file
|
267
308
|
if permissions_config.get("enabled", False):
|
268
309
|
roles_file = permissions_config.get("roles_file")
|
269
310
|
if roles_file and not Path(roles_file).exists():
|
270
311
|
self.validation_warnings.append(f"Roles file not found: {roles_file}")
|
271
|
-
|
312
|
+
|
272
313
|
def _validate_rate_limit_section(self, config: Dict[str, Any]):
|
273
314
|
"""Validate rate limit section."""
|
274
315
|
rate_limit_config = self._get_rate_limit_config(config)
|
275
|
-
|
316
|
+
|
276
317
|
if not isinstance(rate_limit_config, dict):
|
277
|
-
self.validation_errors.append(
|
318
|
+
self.validation_errors.append(
|
319
|
+
"Rate limit configuration must be a dictionary"
|
320
|
+
)
|
278
321
|
return
|
279
|
-
|
322
|
+
|
280
323
|
# Validate rate limit values only if enabled
|
281
324
|
if rate_limit_config.get("enabled", False):
|
282
|
-
requests_per_minute = rate_limit_config.get(
|
325
|
+
requests_per_minute = rate_limit_config.get(
|
326
|
+
"requests_per_minute",
|
327
|
+
rate_limit_config.get("default_requests_per_minute", 0),
|
328
|
+
)
|
283
329
|
if requests_per_minute <= 0:
|
284
|
-
self.validation_errors.append(
|
285
|
-
|
330
|
+
self.validation_errors.append(
|
331
|
+
"requests_per_minute must be greater than 0 when rate limiting is enabled"
|
332
|
+
)
|
333
|
+
|
286
334
|
def _convert_auth_config(self, config: Dict[str, Any]) -> AuthConfig:
|
287
335
|
"""Convert authentication configuration."""
|
288
336
|
auth_config = self._get_auth_config(config)
|
289
|
-
|
337
|
+
|
290
338
|
# Get authentication methods
|
291
339
|
methods = auth_config.get("methods", ["api_key"])
|
292
|
-
|
340
|
+
|
293
341
|
# Get API keys from multiple sources
|
294
342
|
api_keys = auth_config.get("api_keys", {})
|
295
343
|
if not api_keys:
|
296
344
|
# Try legacy SSL config
|
297
345
|
legacy_ssl = config.get("ssl", {})
|
298
346
|
api_keys = legacy_ssl.get("api_keys", {})
|
299
|
-
|
347
|
+
|
300
348
|
return AuthConfig(
|
301
349
|
enabled=auth_config.get("enabled", True),
|
302
350
|
methods=methods,
|
303
351
|
api_keys=api_keys,
|
304
352
|
jwt_secret=auth_config.get("jwt_secret", ""),
|
305
353
|
jwt_algorithm=auth_config.get("jwt_algorithm", "HS256"),
|
306
|
-
jwt_expiration=auth_config.get("jwt_expiration", 3600)
|
354
|
+
jwt_expiration=auth_config.get("jwt_expiration", 3600),
|
307
355
|
)
|
308
|
-
|
356
|
+
|
309
357
|
def _convert_ssl_config(self, config: Dict[str, Any]) -> SSLConfig:
|
310
358
|
"""Convert SSL configuration."""
|
311
359
|
ssl_config = self._get_ssl_config(config)
|
312
|
-
|
360
|
+
|
313
361
|
return SSLConfig(
|
314
362
|
enabled=ssl_config.get("enabled", False),
|
315
363
|
cert_file=ssl_config.get("cert_file"),
|
@@ -318,25 +366,25 @@ class UnifiedConfigAdapter:
|
|
318
366
|
min_tls_version=ssl_config.get("min_tls_version", "TLSv1.2"),
|
319
367
|
verify_client=ssl_config.get("verify_client", False),
|
320
368
|
client_cert_required=ssl_config.get("client_cert_required", False),
|
321
|
-
cipher_suites=ssl_config.get("cipher_suites", [])
|
369
|
+
cipher_suites=ssl_config.get("cipher_suites", []),
|
322
370
|
)
|
323
|
-
|
371
|
+
|
324
372
|
def _convert_permission_config(self, config: Dict[str, Any]) -> PermissionConfig:
|
325
373
|
"""Convert permissions configuration."""
|
326
374
|
permissions_config = self._get_permissions_config(config)
|
327
|
-
|
375
|
+
|
328
376
|
return PermissionConfig(
|
329
377
|
enabled=permissions_config.get("enabled", True),
|
330
378
|
roles_file=permissions_config.get("roles_file", "roles.json"),
|
331
379
|
default_role=permissions_config.get("default_role", "user"),
|
332
380
|
deny_by_default=permissions_config.get("deny_by_default", True),
|
333
|
-
role_mappings=permissions_config.get("role_mappings", {})
|
381
|
+
role_mappings=permissions_config.get("role_mappings", {}),
|
334
382
|
)
|
335
|
-
|
383
|
+
|
336
384
|
def _convert_rate_limit_config(self, config: Dict[str, Any]) -> RateLimitConfig:
|
337
385
|
"""Convert rate limit configuration."""
|
338
386
|
rate_limit_config = self._get_rate_limit_config(config)
|
339
|
-
|
387
|
+
|
340
388
|
return RateLimitConfig(
|
341
389
|
enabled=rate_limit_config.get("enabled", False),
|
342
390
|
requests_per_minute=rate_limit_config.get("requests_per_minute", 60),
|
@@ -347,42 +395,46 @@ class UnifiedConfigAdapter:
|
|
347
395
|
by_user=rate_limit_config.get("by_user", True),
|
348
396
|
by_endpoint=rate_limit_config.get("by_endpoint", False),
|
349
397
|
exempt_roles=rate_limit_config.get("exempt_roles", []),
|
350
|
-
exempt_endpoints=rate_limit_config.get("exempt_endpoints", [])
|
398
|
+
exempt_endpoints=rate_limit_config.get("exempt_endpoints", []),
|
351
399
|
)
|
352
|
-
|
400
|
+
|
353
401
|
def _get_auth_config(self, config: Dict[str, Any]) -> Dict[str, Any]:
|
354
402
|
"""Get authentication configuration from config."""
|
355
403
|
security_config = config.get("security", {})
|
356
|
-
|
404
|
+
|
357
405
|
# Ensure security_config is a dictionary
|
358
406
|
if not isinstance(security_config, dict):
|
359
407
|
return {}
|
360
|
-
|
408
|
+
|
361
409
|
auth_config = security_config.get("auth", {})
|
362
|
-
|
410
|
+
|
363
411
|
# Handle legacy auth_enabled flag
|
364
412
|
if config.get("auth_enabled") is not None:
|
365
413
|
auth_config["enabled"] = config["auth_enabled"]
|
366
|
-
|
414
|
+
|
367
415
|
return auth_config
|
368
|
-
|
416
|
+
|
369
417
|
def _get_ssl_config(self, config: Dict[str, Any]) -> Dict[str, Any]:
|
370
418
|
"""Get SSL configuration from config."""
|
371
419
|
security_config = config.get("security", {})
|
372
|
-
|
420
|
+
|
373
421
|
# Ensure security_config is a dictionary
|
374
422
|
if not isinstance(security_config, dict):
|
375
423
|
return {}
|
376
|
-
|
424
|
+
|
377
425
|
ssl_config = security_config.get("ssl", {})
|
378
|
-
|
426
|
+
|
379
427
|
# Debug: Check SSL config before merging
|
380
|
-
print(
|
381
|
-
|
428
|
+
print(
|
429
|
+
f"🔍 Debug: _get_ssl_config: security.ssl key_file={ssl_config.get('key_file')}"
|
430
|
+
)
|
431
|
+
|
382
432
|
# Merge with legacy SSL config, but prioritize security.ssl over legacy ssl
|
383
433
|
legacy_ssl = config.get("ssl", {})
|
384
434
|
if legacy_ssl:
|
385
|
-
print(
|
435
|
+
print(
|
436
|
+
f"🔍 Debug: _get_ssl_config: legacy ssl key_file={legacy_ssl.get('key_file')}"
|
437
|
+
)
|
386
438
|
# Only merge legacy config if security.ssl is not enabled or missing
|
387
439
|
if not ssl_config.get("enabled", False):
|
388
440
|
ssl_config.update(legacy_ssl)
|
@@ -391,67 +443,61 @@ class UnifiedConfigAdapter:
|
|
391
443
|
for key, value in legacy_ssl.items():
|
392
444
|
if key not in ssl_config or ssl_config[key] is None:
|
393
445
|
ssl_config[key] = value
|
394
|
-
|
446
|
+
|
395
447
|
print(f"🔍 Debug: _get_ssl_config: final key_file={ssl_config.get('key_file')}")
|
396
448
|
return ssl_config
|
397
|
-
|
449
|
+
|
398
450
|
def _get_permissions_config(self, config: Dict[str, Any]) -> Dict[str, Any]:
|
399
451
|
"""Get permissions configuration from config."""
|
400
452
|
security_config = config.get("security", {})
|
401
|
-
|
453
|
+
|
402
454
|
# Ensure security_config is a dictionary
|
403
455
|
if not isinstance(security_config, dict):
|
404
456
|
return {}
|
405
|
-
|
457
|
+
|
406
458
|
permissions_config = security_config.get("permissions", {})
|
407
|
-
|
459
|
+
|
408
460
|
# Merge with legacy roles config
|
409
461
|
legacy_roles = config.get("roles", {})
|
410
462
|
if legacy_roles:
|
411
463
|
permissions_config.update(legacy_roles)
|
412
|
-
|
464
|
+
|
413
465
|
return permissions_config
|
414
|
-
|
466
|
+
|
415
467
|
def _get_rate_limit_config(self, config: Dict[str, Any]) -> Dict[str, Any]:
|
416
468
|
"""Get rate limit configuration from config."""
|
417
469
|
security_config = config.get("security", {})
|
418
|
-
|
470
|
+
|
419
471
|
# Ensure security_config is a dictionary
|
420
472
|
if not isinstance(security_config, dict):
|
421
473
|
return {}
|
422
|
-
|
474
|
+
|
423
475
|
rate_limit_config = security_config.get("rate_limit", {})
|
424
|
-
|
476
|
+
|
425
477
|
# Handle legacy rate_limit_enabled flag
|
426
478
|
if config.get("rate_limit_enabled") is not None:
|
427
479
|
rate_limit_config["enabled"] = config["rate_limit_enabled"]
|
428
|
-
|
480
|
+
|
429
481
|
return rate_limit_config
|
430
|
-
|
482
|
+
|
431
483
|
def get_public_paths(self, config: Dict[str, Any]) -> List[str]:
|
432
484
|
"""
|
433
485
|
Get public paths from configuration.
|
434
|
-
|
486
|
+
|
435
487
|
Args:
|
436
488
|
config: Configuration dictionary
|
437
|
-
|
489
|
+
|
438
490
|
Returns:
|
439
491
|
List of public paths
|
440
492
|
"""
|
441
493
|
security_config = config.get("security", {})
|
442
|
-
|
494
|
+
|
443
495
|
# Ensure security_config is a dictionary
|
444
496
|
if not isinstance(security_config, dict):
|
445
|
-
return [
|
446
|
-
|
447
|
-
"/docs",
|
448
|
-
"/redoc",
|
449
|
-
"/openapi.json",
|
450
|
-
"/favicon.ico"
|
451
|
-
]
|
452
|
-
|
497
|
+
return ["/health", "/docs", "/redoc", "/openapi.json", "/favicon.ico"]
|
498
|
+
|
453
499
|
public_paths = security_config.get("public_paths", [])
|
454
|
-
|
500
|
+
|
455
501
|
# Add default public paths if none specified
|
456
502
|
if not public_paths:
|
457
503
|
public_paths = [
|
@@ -459,72 +505,77 @@ class UnifiedConfigAdapter:
|
|
459
505
|
"/docs",
|
460
506
|
"/redoc",
|
461
507
|
"/openapi.json",
|
462
|
-
"/favicon.ico"
|
508
|
+
"/favicon.ico",
|
463
509
|
]
|
464
|
-
|
510
|
+
|
465
511
|
return public_paths
|
466
|
-
|
512
|
+
|
467
513
|
def get_security_enabled(self, config: Dict[str, Any]) -> bool:
|
468
514
|
"""
|
469
515
|
Check if security is enabled in configuration.
|
470
|
-
|
516
|
+
|
471
517
|
Args:
|
472
518
|
config: Configuration dictionary
|
473
|
-
|
519
|
+
|
474
520
|
Returns:
|
475
521
|
True if security is enabled
|
476
522
|
"""
|
477
523
|
security_config = config.get("security", {})
|
478
|
-
|
524
|
+
|
479
525
|
# Ensure security_config is a dictionary
|
480
526
|
if not isinstance(security_config, dict):
|
481
527
|
return True # Default to enabled for safety
|
482
|
-
|
528
|
+
|
483
529
|
return security_config.get("enabled", True)
|
484
|
-
|
530
|
+
|
485
531
|
def migrate_legacy_config(self, config: Dict[str, Any]) -> Dict[str, Any]:
|
486
532
|
"""
|
487
533
|
Migrate legacy configuration to new format.
|
488
|
-
|
534
|
+
|
489
535
|
Args:
|
490
536
|
config: Legacy configuration dictionary
|
491
|
-
|
537
|
+
|
492
538
|
Returns:
|
493
539
|
Migrated configuration dictionary
|
494
540
|
"""
|
495
541
|
migrated_config = config.copy()
|
496
|
-
|
542
|
+
|
497
543
|
# Create security section if it doesn't exist
|
498
544
|
if "security" not in migrated_config:
|
499
545
|
migrated_config["security"] = {}
|
500
|
-
|
546
|
+
|
501
547
|
security_config = migrated_config["security"]
|
502
|
-
|
548
|
+
|
503
549
|
# Migrate SSL configuration
|
504
550
|
if "ssl" in migrated_config and "ssl" not in security_config:
|
505
551
|
security_config["ssl"] = migrated_config["ssl"]
|
506
552
|
# Don't remove legacy SSL yet for backward compatibility
|
507
|
-
|
553
|
+
|
508
554
|
# Migrate roles configuration
|
509
555
|
if "roles" in migrated_config and "permissions" not in security_config:
|
510
556
|
security_config["permissions"] = migrated_config["roles"]
|
511
557
|
# Don't remove legacy roles yet for backward compatibility
|
512
|
-
|
558
|
+
|
513
559
|
# Migrate auth_enabled flag
|
514
560
|
if "auth_enabled" in migrated_config and "auth" not in security_config:
|
515
561
|
security_config["auth"] = {"enabled": migrated_config["auth_enabled"]}
|
516
|
-
|
562
|
+
|
517
563
|
# Migrate rate_limit_enabled flag
|
518
|
-
if
|
519
|
-
|
520
|
-
|
564
|
+
if (
|
565
|
+
"rate_limit_enabled" in migrated_config
|
566
|
+
and "rate_limit" not in security_config
|
567
|
+
):
|
568
|
+
security_config["rate_limit"] = {
|
569
|
+
"enabled": migrated_config["rate_limit_enabled"]
|
570
|
+
}
|
571
|
+
|
521
572
|
logger.info("Legacy configuration migrated to new format")
|
522
573
|
return migrated_config
|
523
|
-
|
574
|
+
|
524
575
|
def get_default_config(self) -> Dict[str, Any]:
|
525
576
|
"""
|
526
577
|
Get default configuration.
|
527
|
-
|
578
|
+
|
528
579
|
Returns:
|
529
580
|
Default configuration dictionary
|
530
581
|
"""
|
@@ -537,7 +588,7 @@ class UnifiedConfigAdapter:
|
|
537
588
|
"api_keys": {},
|
538
589
|
"jwt_secret": "",
|
539
590
|
"jwt_algorithm": "HS256",
|
540
|
-
"jwt_expiration": 3600
|
591
|
+
"jwt_expiration": 3600,
|
541
592
|
},
|
542
593
|
"ssl": {
|
543
594
|
"enabled": False,
|
@@ -547,14 +598,14 @@ class UnifiedConfigAdapter:
|
|
547
598
|
"min_tls_version": "TLSv1.2",
|
548
599
|
"verify_client": False,
|
549
600
|
"client_cert_required": False,
|
550
|
-
"cipher_suites": []
|
601
|
+
"cipher_suites": [],
|
551
602
|
},
|
552
603
|
"permissions": {
|
553
604
|
"enabled": True,
|
554
605
|
"roles_file": "roles.json",
|
555
606
|
"default_role": "user",
|
556
607
|
"deny_by_default": True,
|
557
|
-
"role_mappings": {}
|
608
|
+
"role_mappings": {},
|
558
609
|
},
|
559
610
|
"rate_limit": {
|
560
611
|
"enabled": True,
|
@@ -566,14 +617,14 @@ class UnifiedConfigAdapter:
|
|
566
617
|
"by_user": True,
|
567
618
|
"by_endpoint": False,
|
568
619
|
"exempt_roles": [],
|
569
|
-
"exempt_endpoints": []
|
620
|
+
"exempt_endpoints": [],
|
570
621
|
},
|
571
622
|
"public_paths": [
|
572
623
|
"/health",
|
573
624
|
"/docs",
|
574
625
|
"/redoc",
|
575
626
|
"/openapi.json",
|
576
|
-
"/favicon.ico"
|
577
|
-
]
|
627
|
+
"/favicon.ico",
|
628
|
+
],
|
578
629
|
}
|
579
630
|
}
|