mcp-proxy-adapter 6.3.4__py3-none-any.whl → 6.3.6__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 (131) hide show
  1. mcp_proxy_adapter/__init__.py +9 -5
  2. mcp_proxy_adapter/__main__.py +1 -1
  3. mcp_proxy_adapter/api/app.py +227 -176
  4. mcp_proxy_adapter/api/handlers.py +68 -60
  5. mcp_proxy_adapter/api/middleware/__init__.py +7 -5
  6. mcp_proxy_adapter/api/middleware/base.py +19 -16
  7. mcp_proxy_adapter/api/middleware/command_permission_middleware.py +44 -34
  8. mcp_proxy_adapter/api/middleware/error_handling.py +57 -67
  9. mcp_proxy_adapter/api/middleware/factory.py +50 -52
  10. mcp_proxy_adapter/api/middleware/logging.py +46 -30
  11. mcp_proxy_adapter/api/middleware/performance.py +19 -16
  12. mcp_proxy_adapter/api/middleware/protocol_middleware.py +80 -50
  13. mcp_proxy_adapter/api/middleware/transport_middleware.py +26 -24
  14. mcp_proxy_adapter/api/middleware/unified_security.py +70 -51
  15. mcp_proxy_adapter/api/middleware/user_info_middleware.py +43 -34
  16. mcp_proxy_adapter/api/schemas.py +69 -43
  17. mcp_proxy_adapter/api/tool_integration.py +83 -63
  18. mcp_proxy_adapter/api/tools.py +60 -50
  19. mcp_proxy_adapter/commands/__init__.py +15 -6
  20. mcp_proxy_adapter/commands/auth_validation_command.py +107 -110
  21. mcp_proxy_adapter/commands/base.py +108 -112
  22. mcp_proxy_adapter/commands/builtin_commands.py +28 -18
  23. mcp_proxy_adapter/commands/catalog_manager.py +394 -265
  24. mcp_proxy_adapter/commands/cert_monitor_command.py +222 -204
  25. mcp_proxy_adapter/commands/certificate_management_command.py +210 -213
  26. mcp_proxy_adapter/commands/command_registry.py +275 -226
  27. mcp_proxy_adapter/commands/config_command.py +48 -33
  28. mcp_proxy_adapter/commands/dependency_container.py +22 -23
  29. mcp_proxy_adapter/commands/dependency_manager.py +65 -56
  30. mcp_proxy_adapter/commands/echo_command.py +15 -15
  31. mcp_proxy_adapter/commands/health_command.py +31 -29
  32. mcp_proxy_adapter/commands/help_command.py +97 -61
  33. mcp_proxy_adapter/commands/hooks.py +65 -49
  34. mcp_proxy_adapter/commands/key_management_command.py +148 -147
  35. mcp_proxy_adapter/commands/load_command.py +58 -40
  36. mcp_proxy_adapter/commands/plugins_command.py +80 -54
  37. mcp_proxy_adapter/commands/protocol_management_command.py +60 -48
  38. mcp_proxy_adapter/commands/proxy_registration_command.py +107 -115
  39. mcp_proxy_adapter/commands/reload_command.py +43 -37
  40. mcp_proxy_adapter/commands/result.py +26 -33
  41. mcp_proxy_adapter/commands/role_test_command.py +26 -26
  42. mcp_proxy_adapter/commands/roles_management_command.py +176 -173
  43. mcp_proxy_adapter/commands/security_command.py +134 -122
  44. mcp_proxy_adapter/commands/settings_command.py +47 -56
  45. mcp_proxy_adapter/commands/ssl_setup_command.py +109 -129
  46. mcp_proxy_adapter/commands/token_management_command.py +129 -158
  47. mcp_proxy_adapter/commands/transport_management_command.py +41 -36
  48. mcp_proxy_adapter/commands/unload_command.py +42 -37
  49. mcp_proxy_adapter/config.py +36 -35
  50. mcp_proxy_adapter/core/__init__.py +19 -21
  51. mcp_proxy_adapter/core/app_factory.py +30 -9
  52. mcp_proxy_adapter/core/app_runner.py +81 -64
  53. mcp_proxy_adapter/core/auth_validator.py +176 -182
  54. mcp_proxy_adapter/core/certificate_utils.py +469 -426
  55. mcp_proxy_adapter/core/client.py +155 -126
  56. mcp_proxy_adapter/core/client_manager.py +60 -54
  57. mcp_proxy_adapter/core/client_security.py +120 -91
  58. mcp_proxy_adapter/core/config_converter.py +176 -143
  59. mcp_proxy_adapter/core/config_validator.py +12 -4
  60. mcp_proxy_adapter/core/crl_utils.py +21 -7
  61. mcp_proxy_adapter/core/errors.py +64 -20
  62. mcp_proxy_adapter/core/logging.py +34 -29
  63. mcp_proxy_adapter/core/mtls_asgi.py +29 -25
  64. mcp_proxy_adapter/core/mtls_asgi_app.py +66 -54
  65. mcp_proxy_adapter/core/protocol_manager.py +154 -104
  66. mcp_proxy_adapter/core/proxy_client.py +202 -144
  67. mcp_proxy_adapter/core/proxy_registration.py +7 -3
  68. mcp_proxy_adapter/core/role_utils.py +139 -125
  69. mcp_proxy_adapter/core/security_adapter.py +88 -77
  70. mcp_proxy_adapter/core/security_factory.py +50 -44
  71. mcp_proxy_adapter/core/security_integration.py +72 -24
  72. mcp_proxy_adapter/core/server_adapter.py +68 -64
  73. mcp_proxy_adapter/core/server_engine.py +71 -53
  74. mcp_proxy_adapter/core/settings.py +68 -58
  75. mcp_proxy_adapter/core/ssl_utils.py +69 -56
  76. mcp_proxy_adapter/core/transport_manager.py +72 -60
  77. mcp_proxy_adapter/core/unified_config_adapter.py +201 -150
  78. mcp_proxy_adapter/core/utils.py +4 -2
  79. mcp_proxy_adapter/custom_openapi.py +107 -99
  80. mcp_proxy_adapter/examples/basic_framework/main.py +9 -2
  81. mcp_proxy_adapter/examples/commands/__init__.py +1 -1
  82. mcp_proxy_adapter/examples/create_certificates_simple.py +182 -71
  83. mcp_proxy_adapter/examples/debug_request_state.py +38 -19
  84. mcp_proxy_adapter/examples/debug_role_chain.py +53 -20
  85. mcp_proxy_adapter/examples/demo_client.py +48 -36
  86. mcp_proxy_adapter/examples/examples/basic_framework/main.py +9 -2
  87. mcp_proxy_adapter/examples/examples/full_application/__init__.py +1 -0
  88. mcp_proxy_adapter/examples/examples/full_application/commands/custom_echo_command.py +22 -10
  89. mcp_proxy_adapter/examples/examples/full_application/commands/dynamic_calculator_command.py +24 -17
  90. mcp_proxy_adapter/examples/examples/full_application/hooks/application_hooks.py +16 -3
  91. mcp_proxy_adapter/examples/examples/full_application/hooks/builtin_command_hooks.py +13 -3
  92. mcp_proxy_adapter/examples/examples/full_application/main.py +27 -2
  93. mcp_proxy_adapter/examples/examples/full_application/proxy_endpoints.py +48 -14
  94. mcp_proxy_adapter/examples/full_application/__init__.py +1 -0
  95. mcp_proxy_adapter/examples/full_application/commands/custom_echo_command.py +22 -10
  96. mcp_proxy_adapter/examples/full_application/commands/dynamic_calculator_command.py +24 -17
  97. mcp_proxy_adapter/examples/full_application/hooks/application_hooks.py +16 -3
  98. mcp_proxy_adapter/examples/full_application/hooks/builtin_command_hooks.py +13 -3
  99. mcp_proxy_adapter/examples/full_application/main.py +27 -2
  100. mcp_proxy_adapter/examples/full_application/proxy_endpoints.py +48 -14
  101. mcp_proxy_adapter/examples/generate_all_certificates.py +198 -73
  102. mcp_proxy_adapter/examples/generate_certificates.py +31 -16
  103. mcp_proxy_adapter/examples/generate_certificates_and_tokens.py +220 -74
  104. mcp_proxy_adapter/examples/generate_test_configs.py +68 -91
  105. mcp_proxy_adapter/examples/proxy_registration_example.py +76 -75
  106. mcp_proxy_adapter/examples/run_example.py +23 -5
  107. mcp_proxy_adapter/examples/run_full_test_suite.py +109 -71
  108. mcp_proxy_adapter/examples/run_proxy_server.py +22 -9
  109. mcp_proxy_adapter/examples/run_security_tests.py +103 -41
  110. mcp_proxy_adapter/examples/run_security_tests_fixed.py +72 -36
  111. mcp_proxy_adapter/examples/scripts/config_generator.py +288 -187
  112. mcp_proxy_adapter/examples/scripts/create_certificates_simple.py +185 -72
  113. mcp_proxy_adapter/examples/scripts/generate_certificates_and_tokens.py +220 -74
  114. mcp_proxy_adapter/examples/security_test_client.py +196 -127
  115. mcp_proxy_adapter/examples/setup_test_environment.py +17 -29
  116. mcp_proxy_adapter/examples/test_config.py +19 -4
  117. mcp_proxy_adapter/examples/test_config_generator.py +23 -7
  118. mcp_proxy_adapter/examples/test_examples.py +84 -56
  119. mcp_proxy_adapter/examples/universal_client.py +119 -62
  120. mcp_proxy_adapter/openapi.py +108 -115
  121. mcp_proxy_adapter/utils/config_generator.py +429 -274
  122. mcp_proxy_adapter/version.py +1 -2
  123. {mcp_proxy_adapter-6.3.4.dist-info → mcp_proxy_adapter-6.3.6.dist-info}/METADATA +1 -1
  124. mcp_proxy_adapter-6.3.6.dist-info/RECORD +144 -0
  125. mcp_proxy_adapter-6.3.6.dist-info/top_level.txt +2 -0
  126. mcp_proxy_adapter_issue_package/demonstrate_issue.py +178 -0
  127. mcp_proxy_adapter-6.3.4.dist-info/RECORD +0 -143
  128. mcp_proxy_adapter-6.3.4.dist-info/top_level.txt +0 -1
  129. {mcp_proxy_adapter-6.3.4.dist-info → mcp_proxy_adapter-6.3.6.dist-info}/WHEEL +0 -0
  130. {mcp_proxy_adapter-6.3.4.dist-info → mcp_proxy_adapter-6.3.6.dist-info}/entry_points.txt +0 -0
  131. {mcp_proxy_adapter-6.3.4.dist-info → mcp_proxy_adapter-6.3.6.dist-info}/licenses/LICENSE +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, SSLConfig, PermissionConfig, RateLimitConfig
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(self, config: Dict[str, Any]) -> Optional[SecurityConfig]:
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("mcp_security_framework not available, cannot convert configuration")
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(f"Configuration validation failed: {validation_result.errors}")
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(f"🔍 Debug: SSL config at start of validation: enabled={ssl_config.get('enabled', False)}")
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(f"🔍 Debug: Root SSL section found: enabled={config['ssl'].get('enabled', False)}")
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(key in config for key in ["ssl", "roles", "auth_enabled"]),
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 = {"enabled", "auth", "ssl", "permissions", "rate_limit", "public_paths"}
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(f"Unknown keys in security section: {unknown_keys}")
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(f"Legacy section '{section}' found, consider migrating to security section")
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("SSL configuration found in both root and security sections")
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("Auth configuration found in both root and security sections")
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(f"Invalid auth methods: {invalid_methods}")
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(f"🔍 Debug: _validate_ssl_section: cert_file exists={Path(cert_file).exists() if cert_file else 'None'}")
250
- print(f"🔍 Debug: _validate_ssl_section: key_file exists={Path(key_file).exists() if key_file else 'None'}")
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(f"SSL certificate file not found: {cert_file}")
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("Permissions configuration must be a dictionary")
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("Rate limit configuration must be a dictionary")
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("requests_per_minute", rate_limit_config.get("default_requests_per_minute", 0))
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("requests_per_minute must be greater than 0 when rate limiting is enabled")
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(f"🔍 Debug: _get_ssl_config: security.ssl key_file={ssl_config.get('key_file')}")
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(f"🔍 Debug: _get_ssl_config: legacy ssl key_file={legacy_ssl.get('key_file')}")
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
- "/health",
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 "rate_limit_enabled" in migrated_config and "rate_limit" not in security_config:
519
- security_config["rate_limit"] = {"enabled": migrated_config["rate_limit_enabled"]}
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
  }