mcp-proxy-adapter 6.3.4__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 +7 -3
- 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.4.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.4.dist-info/RECORD +0 -143
- {mcp_proxy_adapter-6.3.4.dist-info → mcp_proxy_adapter-6.3.5.dist-info}/WHEEL +0 -0
- {mcp_proxy_adapter-6.3.4.dist-info → mcp_proxy_adapter-6.3.5.dist-info}/entry_points.txt +0 -0
- {mcp_proxy_adapter-6.3.4.dist-info → mcp_proxy_adapter-6.3.5.dist-info}/licenses/LICENSE +0 -0
- {mcp_proxy_adapter-6.3.4.dist-info → mcp_proxy_adapter-6.3.5.dist-info}/top_level.txt +0 -0
@@ -23,39 +23,43 @@ from cryptography.hazmat.primitives.asymmetric import rsa, padding
|
|
23
23
|
# Standard JSON-RPC error codes
|
24
24
|
JSON_RPC_ERRORS = {
|
25
25
|
# General errors
|
26
|
-
-32600: "Invalid Request",
|
27
|
-
-32601: "Method not found",
|
28
|
-
-32602: "Invalid params",
|
29
|
-
-32603: "Internal error",
|
30
|
-
-32700: "Parse error",
|
31
|
-
|
26
|
+
-32600: "Invalid Request", # Invalid request
|
27
|
+
-32601: "Method not found", # Method not found
|
28
|
+
-32602: "Invalid params", # Invalid parameters
|
29
|
+
-32603: "Internal error", # Internal error
|
30
|
+
-32700: "Parse error", # Parse error
|
32
31
|
# Custom codes for authentication
|
33
|
-
-32001: "Authentication disabled",
|
34
|
-
-32002: "Invalid configuration",
|
35
|
-
-32003: "Certificate validation failed",
|
36
|
-
-32004: "Token validation failed",
|
37
|
-
-32005: "MTLS validation failed",
|
38
|
-
-32006: "SSL validation failed",
|
39
|
-
-32007: "Role validation failed",
|
40
|
-
-32008: "Certificate expired",
|
41
|
-
-32009: "Certificate not found",
|
42
|
-
-32010: "Token expired",
|
43
|
-
-32011: "Token not found",
|
32
|
+
-32001: "Authentication disabled", # Authentication disabled
|
33
|
+
-32002: "Invalid configuration", # Invalid configuration
|
34
|
+
-32003: "Certificate validation failed", # Certificate validation failed
|
35
|
+
-32004: "Token validation failed", # Token validation failed
|
36
|
+
-32005: "MTLS validation failed", # MTLS validation failed
|
37
|
+
-32006: "SSL validation failed", # SSL validation failed
|
38
|
+
-32007: "Role validation failed", # Role validation failed
|
39
|
+
-32008: "Certificate expired", # Certificate expired
|
40
|
+
-32009: "Certificate not found", # Certificate not found
|
41
|
+
-32010: "Token expired", # Token expired
|
42
|
+
-32011: "Token not found", # Token not found
|
44
43
|
}
|
45
44
|
|
46
45
|
|
47
46
|
class AuthValidationResult:
|
48
47
|
"""
|
49
48
|
Authentication validation result.
|
50
|
-
|
49
|
+
|
51
50
|
Contains validation status, error information, and extracted roles.
|
52
51
|
"""
|
53
|
-
|
54
|
-
def __init__(
|
55
|
-
|
52
|
+
|
53
|
+
def __init__(
|
54
|
+
self,
|
55
|
+
is_valid: bool,
|
56
|
+
error_code: Optional[int] = None,
|
57
|
+
error_message: Optional[str] = None,
|
58
|
+
roles: Optional[List[str]] = None,
|
59
|
+
):
|
56
60
|
"""
|
57
61
|
Initialize authentication validation result.
|
58
|
-
|
62
|
+
|
59
63
|
Args:
|
60
64
|
is_valid: Whether authentication is valid
|
61
65
|
error_code: JSON-RPC error code if validation failed
|
@@ -66,30 +70,28 @@ class AuthValidationResult:
|
|
66
70
|
self.error_code = error_code
|
67
71
|
self.error_message = error_message
|
68
72
|
self.roles = roles or []
|
69
|
-
|
73
|
+
|
70
74
|
def to_json_rpc_error(self) -> Dict[str, Any]:
|
71
75
|
"""
|
72
76
|
Convert to JSON-RPC error format.
|
73
|
-
|
77
|
+
|
74
78
|
Returns:
|
75
79
|
JSON-RPC error dictionary
|
76
80
|
"""
|
77
81
|
if self.is_valid:
|
78
82
|
return {}
|
79
|
-
|
83
|
+
|
80
84
|
return {
|
81
85
|
"code": self.error_code or -32603,
|
82
|
-
"message": self.error_message
|
83
|
-
"
|
84
|
-
|
85
|
-
"roles": self.roles
|
86
|
-
}
|
86
|
+
"message": self.error_message
|
87
|
+
or JSON_RPC_ERRORS.get(self.error_code, "Unknown error"),
|
88
|
+
"data": {"validation_type": "authentication", "roles": self.roles},
|
87
89
|
}
|
88
|
-
|
90
|
+
|
89
91
|
def to_dict(self) -> Dict[str, Any]:
|
90
92
|
"""
|
91
93
|
Convert to dictionary format.
|
92
|
-
|
94
|
+
|
93
95
|
Returns:
|
94
96
|
Dictionary representation
|
95
97
|
"""
|
@@ -97,38 +99,40 @@ class AuthValidationResult:
|
|
97
99
|
"is_valid": self.is_valid,
|
98
100
|
"error_code": self.error_code,
|
99
101
|
"error_message": self.error_message,
|
100
|
-
"roles": self.roles
|
102
|
+
"roles": self.roles,
|
101
103
|
}
|
102
104
|
|
103
105
|
|
104
106
|
class AuthValidator:
|
105
107
|
"""
|
106
108
|
Universal authentication validator.
|
107
|
-
|
109
|
+
|
108
110
|
Provides methods to validate different types of authentication:
|
109
111
|
- Certificate validation
|
110
112
|
- Token validation
|
111
113
|
- mTLS validation
|
112
114
|
- SSL validation
|
113
115
|
"""
|
114
|
-
|
116
|
+
|
115
117
|
def __init__(self, config: Optional[Dict[str, Any]] = None):
|
116
118
|
"""
|
117
119
|
Initialize authentication validator.
|
118
|
-
|
120
|
+
|
119
121
|
Args:
|
120
122
|
config: Configuration dictionary for validation settings
|
121
123
|
"""
|
122
124
|
self.config = config or {}
|
123
125
|
self.logger = logging.getLogger(__name__)
|
124
|
-
|
126
|
+
|
125
127
|
# Custom OID for roles
|
126
128
|
self.role_oid = "1.3.6.1.4.1.99999.1"
|
127
|
-
|
128
|
-
def validate_auth(
|
129
|
+
|
130
|
+
def validate_auth(
|
131
|
+
self, auth_data: Dict[str, Any], auth_type: str = "auto"
|
132
|
+
) -> AuthValidationResult:
|
129
133
|
"""
|
130
134
|
Universal authentication validation.
|
131
|
-
|
135
|
+
|
132
136
|
Logic:
|
133
137
|
1. Check SSL configuration
|
134
138
|
2. If SSL disabled - return success
|
@@ -136,11 +140,11 @@ class AuthValidator:
|
|
136
140
|
4. Check for required data
|
137
141
|
5. Perform validation
|
138
142
|
6. Return result with JSON-RPC error code
|
139
|
-
|
143
|
+
|
140
144
|
Args:
|
141
145
|
auth_data: Authentication data dictionary
|
142
146
|
auth_type: Type of authentication to validate
|
143
|
-
|
147
|
+
|
144
148
|
Returns:
|
145
149
|
Authentication validation result
|
146
150
|
"""
|
@@ -148,59 +152,53 @@ class AuthValidator:
|
|
148
152
|
# Determine validation type
|
149
153
|
if auth_type == "auto":
|
150
154
|
auth_type = self._get_validation_mode()
|
151
|
-
|
155
|
+
|
152
156
|
# Check for unsupported types first
|
153
157
|
if auth_type not in ["certificate", "token", "mtls", "ssl"]:
|
154
158
|
return AuthValidationResult(
|
155
159
|
is_valid=False,
|
156
160
|
error_code=-32602,
|
157
|
-
error_message=f"Unsupported authentication type: {auth_type}"
|
161
|
+
error_message=f"Unsupported authentication type: {auth_type}",
|
158
162
|
)
|
159
|
-
|
163
|
+
|
160
164
|
# Check if authentication is enabled for the determined type
|
161
165
|
if not self._check_config(auth_type):
|
162
|
-
return AuthValidationResult(
|
163
|
-
|
164
|
-
roles=[]
|
165
|
-
)
|
166
|
-
|
166
|
+
return AuthValidationResult(is_valid=True, roles=[])
|
167
|
+
|
167
168
|
# Validate based on type
|
168
169
|
if auth_type == "certificate":
|
169
170
|
return self.validate_certificate(
|
170
|
-
auth_data.get("cert_path"),
|
171
|
-
auth_data.get("cert_type", "server")
|
171
|
+
auth_data.get("cert_path"), auth_data.get("cert_type", "server")
|
172
172
|
)
|
173
173
|
elif auth_type == "token":
|
174
174
|
return self.validate_token(
|
175
|
-
auth_data.get("token"),
|
176
|
-
auth_data.get("token_type", "jwt")
|
175
|
+
auth_data.get("token"), auth_data.get("token_type", "jwt")
|
177
176
|
)
|
178
177
|
elif auth_type == "mtls":
|
179
178
|
return self.validate_mtls(
|
180
|
-
auth_data.get("client_cert"),
|
181
|
-
auth_data.get("ca_cert")
|
179
|
+
auth_data.get("client_cert"), auth_data.get("ca_cert")
|
182
180
|
)
|
183
181
|
elif auth_type == "ssl":
|
184
|
-
return self.validate_ssl(
|
185
|
-
|
186
|
-
)
|
187
|
-
|
182
|
+
return self.validate_ssl(auth_data.get("server_cert"))
|
183
|
+
|
188
184
|
except Exception as e:
|
189
185
|
self.logger.error(f"Authentication validation error: {e}")
|
190
186
|
return AuthValidationResult(
|
191
187
|
is_valid=False,
|
192
188
|
error_code=-32603,
|
193
|
-
error_message=f"Internal validation error: {str(e)}"
|
189
|
+
error_message=f"Internal validation error: {str(e)}",
|
194
190
|
)
|
195
|
-
|
196
|
-
def validate_certificate(
|
191
|
+
|
192
|
+
def validate_certificate(
|
193
|
+
self, cert_path: Optional[str], cert_type: str = "server"
|
194
|
+
) -> AuthValidationResult:
|
197
195
|
"""
|
198
196
|
Validate certificate.
|
199
|
-
|
197
|
+
|
200
198
|
Args:
|
201
199
|
cert_path: Path to certificate file
|
202
200
|
cert_type: Type of certificate (server/client/ca)
|
203
|
-
|
201
|
+
|
204
202
|
Returns:
|
205
203
|
Certificate validation result
|
206
204
|
"""
|
@@ -209,34 +207,34 @@ class AuthValidator:
|
|
209
207
|
return AuthValidationResult(
|
210
208
|
is_valid=False,
|
211
209
|
error_code=-32009,
|
212
|
-
error_message="Certificate path not provided"
|
210
|
+
error_message="Certificate path not provided",
|
213
211
|
)
|
214
|
-
|
212
|
+
|
215
213
|
if not os.path.exists(cert_path):
|
216
214
|
return AuthValidationResult(
|
217
215
|
is_valid=False,
|
218
216
|
error_code=-32009,
|
219
|
-
error_message=f"Certificate file not found: {cert_path}"
|
217
|
+
error_message=f"Certificate file not found: {cert_path}",
|
220
218
|
)
|
221
|
-
|
219
|
+
|
222
220
|
# Load and validate certificate
|
223
|
-
with open(cert_path,
|
221
|
+
with open(cert_path, "rb") as f:
|
224
222
|
cert_data = f.read()
|
225
|
-
|
223
|
+
|
226
224
|
cert = x509.load_pem_x509_certificate(cert_data)
|
227
|
-
|
225
|
+
|
228
226
|
# Check if certificate is not expired
|
229
227
|
now = datetime.utcnow()
|
230
228
|
if cert.not_valid_before > now or cert.not_valid_after < now:
|
231
229
|
return AuthValidationResult(
|
232
230
|
is_valid=False,
|
233
231
|
error_code=-32008,
|
234
|
-
error_message="Certificate has expired"
|
232
|
+
error_message="Certificate has expired",
|
235
233
|
)
|
236
|
-
|
234
|
+
|
237
235
|
# Extract roles from certificate
|
238
236
|
roles = self._extract_roles_from_certificate(cert)
|
239
|
-
|
237
|
+
|
240
238
|
# Validate certificate type
|
241
239
|
if cert_type == "server":
|
242
240
|
# Check for server-specific extensions
|
@@ -244,7 +242,7 @@ class AuthValidator:
|
|
244
242
|
return AuthValidationResult(
|
245
243
|
is_valid=False,
|
246
244
|
error_code=-32003,
|
247
|
-
error_message="Invalid server certificate"
|
245
|
+
error_message="Invalid server certificate",
|
248
246
|
)
|
249
247
|
elif cert_type == "client":
|
250
248
|
# Check for client-specific extensions
|
@@ -252,30 +250,29 @@ class AuthValidator:
|
|
252
250
|
return AuthValidationResult(
|
253
251
|
is_valid=False,
|
254
252
|
error_code=-32003,
|
255
|
-
error_message="Invalid client certificate"
|
253
|
+
error_message="Invalid client certificate",
|
256
254
|
)
|
257
|
-
|
258
|
-
return AuthValidationResult(
|
259
|
-
|
260
|
-
roles=roles
|
261
|
-
)
|
262
|
-
|
255
|
+
|
256
|
+
return AuthValidationResult(is_valid=True, roles=roles)
|
257
|
+
|
263
258
|
except Exception as e:
|
264
259
|
self.logger.error(f"Certificate validation error: {e}")
|
265
260
|
return AuthValidationResult(
|
266
261
|
is_valid=False,
|
267
262
|
error_code=-32003,
|
268
|
-
error_message=f"Certificate validation failed: {str(e)}"
|
263
|
+
error_message=f"Certificate validation failed: {str(e)}",
|
269
264
|
)
|
270
|
-
|
271
|
-
def validate_token(
|
265
|
+
|
266
|
+
def validate_token(
|
267
|
+
self, token: Optional[str], token_type: str = "jwt"
|
268
|
+
) -> AuthValidationResult:
|
272
269
|
"""
|
273
270
|
Validate token.
|
274
|
-
|
271
|
+
|
275
272
|
Args:
|
276
273
|
token: Token string to validate
|
277
274
|
token_type: Type of token (jwt/api)
|
278
|
-
|
275
|
+
|
279
276
|
Returns:
|
280
277
|
Token validation result
|
281
278
|
"""
|
@@ -284,9 +281,9 @@ class AuthValidator:
|
|
284
281
|
return AuthValidationResult(
|
285
282
|
is_valid=False,
|
286
283
|
error_code=-32011,
|
287
|
-
error_message="Token not provided"
|
284
|
+
error_message="Token not provided",
|
288
285
|
)
|
289
|
-
|
286
|
+
|
290
287
|
if token_type == "jwt":
|
291
288
|
return self._validate_jwt_token(token)
|
292
289
|
elif token_type == "api":
|
@@ -295,25 +292,27 @@ class AuthValidator:
|
|
295
292
|
return AuthValidationResult(
|
296
293
|
is_valid=False,
|
297
294
|
error_code=-32602,
|
298
|
-
error_message=f"Unsupported token type: {token_type}"
|
295
|
+
error_message=f"Unsupported token type: {token_type}",
|
299
296
|
)
|
300
|
-
|
297
|
+
|
301
298
|
except Exception as e:
|
302
299
|
self.logger.error(f"Token validation error: {e}")
|
303
300
|
return AuthValidationResult(
|
304
301
|
is_valid=False,
|
305
302
|
error_code=-32004,
|
306
|
-
error_message=f"Token validation failed: {str(e)}"
|
303
|
+
error_message=f"Token validation failed: {str(e)}",
|
307
304
|
)
|
308
|
-
|
309
|
-
def validate_mtls(
|
305
|
+
|
306
|
+
def validate_mtls(
|
307
|
+
self, client_cert: Optional[str], ca_cert: Optional[str]
|
308
|
+
) -> AuthValidationResult:
|
310
309
|
"""
|
311
310
|
Validate mTLS connection.
|
312
|
-
|
311
|
+
|
313
312
|
Args:
|
314
313
|
client_cert: Path to client certificate
|
315
314
|
ca_cert: Path to CA certificate
|
316
|
-
|
315
|
+
|
317
316
|
Returns:
|
318
317
|
mTLS validation result
|
319
318
|
"""
|
@@ -322,47 +321,44 @@ class AuthValidator:
|
|
322
321
|
return AuthValidationResult(
|
323
322
|
is_valid=False,
|
324
323
|
error_code=-32005,
|
325
|
-
error_message="Client certificate and CA certificate required for mTLS"
|
324
|
+
error_message="Client certificate and CA certificate required for mTLS",
|
326
325
|
)
|
327
|
-
|
326
|
+
|
328
327
|
# Validate client certificate
|
329
328
|
client_result = self.validate_certificate(client_cert, "client")
|
330
329
|
if not client_result.is_valid:
|
331
330
|
return client_result
|
332
|
-
|
331
|
+
|
333
332
|
# Validate CA certificate
|
334
333
|
ca_result = self.validate_certificate(ca_cert, "ca")
|
335
334
|
if not ca_result.is_valid:
|
336
335
|
return ca_result
|
337
|
-
|
336
|
+
|
338
337
|
# Verify client certificate is signed by CA
|
339
338
|
if not self._verify_certificate_chain(client_cert, ca_cert):
|
340
339
|
return AuthValidationResult(
|
341
340
|
is_valid=False,
|
342
341
|
error_code=-32005,
|
343
|
-
error_message="Client certificate not signed by provided CA"
|
342
|
+
error_message="Client certificate not signed by provided CA",
|
344
343
|
)
|
345
|
-
|
346
|
-
return AuthValidationResult(
|
347
|
-
|
348
|
-
roles=client_result.roles
|
349
|
-
)
|
350
|
-
|
344
|
+
|
345
|
+
return AuthValidationResult(is_valid=True, roles=client_result.roles)
|
346
|
+
|
351
347
|
except Exception as e:
|
352
348
|
self.logger.error(f"mTLS validation error: {e}")
|
353
349
|
return AuthValidationResult(
|
354
350
|
is_valid=False,
|
355
351
|
error_code=-32005,
|
356
|
-
error_message=f"mTLS validation failed: {str(e)}"
|
352
|
+
error_message=f"mTLS validation failed: {str(e)}",
|
357
353
|
)
|
358
|
-
|
354
|
+
|
359
355
|
def validate_ssl(self, server_cert: Optional[str]) -> AuthValidationResult:
|
360
356
|
"""
|
361
357
|
Validate SSL connection.
|
362
|
-
|
358
|
+
|
363
359
|
Args:
|
364
360
|
server_cert: Path to server certificate
|
365
|
-
|
361
|
+
|
366
362
|
Returns:
|
367
363
|
SSL validation result
|
368
364
|
"""
|
@@ -371,35 +367,35 @@ class AuthValidator:
|
|
371
367
|
return AuthValidationResult(
|
372
368
|
is_valid=False,
|
373
369
|
error_code=-32006,
|
374
|
-
error_message="Server certificate required for SSL validation"
|
370
|
+
error_message="Server certificate required for SSL validation",
|
375
371
|
)
|
376
|
-
|
372
|
+
|
377
373
|
# Validate server certificate
|
378
374
|
return self.validate_certificate(server_cert, "server")
|
379
|
-
|
375
|
+
|
380
376
|
except Exception as e:
|
381
377
|
self.logger.error(f"SSL validation error: {e}")
|
382
378
|
return AuthValidationResult(
|
383
379
|
is_valid=False,
|
384
380
|
error_code=-32006,
|
385
|
-
error_message=f"SSL validation failed: {str(e)}"
|
381
|
+
error_message=f"SSL validation failed: {str(e)}",
|
386
382
|
)
|
387
|
-
|
383
|
+
|
388
384
|
def _check_config(self, auth_type: str) -> bool:
|
389
385
|
"""
|
390
386
|
Check if authentication is enabled in configuration.
|
391
|
-
|
387
|
+
|
392
388
|
Args:
|
393
389
|
auth_type: Type of authentication
|
394
|
-
|
390
|
+
|
395
391
|
Returns:
|
396
392
|
True if authentication is enabled, False otherwise
|
397
393
|
"""
|
398
394
|
ssl_config = self.config.get("ssl", {})
|
399
|
-
|
395
|
+
|
400
396
|
if not ssl_config.get("enabled", False):
|
401
397
|
return False
|
402
|
-
|
398
|
+
|
403
399
|
# Check specific authentication type
|
404
400
|
if auth_type == "token":
|
405
401
|
return ssl_config.get("token_auth", {}).get("enabled", False)
|
@@ -407,57 +403,57 @@ class AuthValidator:
|
|
407
403
|
return ssl_config.get("mtls", {}).get("enabled", False)
|
408
404
|
elif auth_type in ["ssl", "certificate"]:
|
409
405
|
return True
|
410
|
-
|
406
|
+
|
411
407
|
# For unsupported types, return False to indicate authentication is not enabled
|
412
408
|
return False
|
413
|
-
|
409
|
+
|
414
410
|
def _get_validation_mode(self) -> str:
|
415
411
|
"""
|
416
412
|
Get validation mode from configuration.
|
417
|
-
|
413
|
+
|
418
414
|
Returns:
|
419
415
|
Validation mode string
|
420
416
|
"""
|
421
417
|
ssl_config = self.config.get("ssl", {})
|
422
|
-
|
418
|
+
|
423
419
|
if ssl_config.get("token_auth", {}).get("enabled", False):
|
424
420
|
return "token"
|
425
421
|
elif ssl_config.get("mtls", {}).get("enabled", False):
|
426
422
|
return "mtls"
|
427
423
|
elif ssl_config.get("enabled", False):
|
428
424
|
return "ssl"
|
429
|
-
|
425
|
+
|
430
426
|
return "none"
|
431
|
-
|
427
|
+
|
432
428
|
def _extract_roles_from_certificate(self, cert: x509.Certificate) -> List[str]:
|
433
429
|
"""
|
434
430
|
Extract roles from certificate.
|
435
|
-
|
431
|
+
|
436
432
|
Args:
|
437
433
|
cert: Certificate object
|
438
|
-
|
434
|
+
|
439
435
|
Returns:
|
440
436
|
List of roles extracted from certificate
|
441
437
|
"""
|
442
438
|
try:
|
443
439
|
for extension in cert.extensions:
|
444
440
|
if extension.oid.dotted_string == self.role_oid:
|
445
|
-
roles_data = extension.value.value.decode(
|
446
|
-
return [role.strip() for role in roles_data.split(
|
447
|
-
|
441
|
+
roles_data = extension.value.value.decode("utf-8")
|
442
|
+
return [role.strip() for role in roles_data.split(",")]
|
443
|
+
|
448
444
|
return []
|
449
|
-
|
445
|
+
|
450
446
|
except Exception as e:
|
451
447
|
self.logger.error(f"Failed to extract roles from certificate: {e}")
|
452
448
|
return []
|
453
|
-
|
449
|
+
|
454
450
|
def _validate_server_certificate(self, cert: x509.Certificate) -> bool:
|
455
451
|
"""
|
456
452
|
Validate server certificate extensions.
|
457
|
-
|
453
|
+
|
458
454
|
Args:
|
459
455
|
cert: Certificate object
|
460
|
-
|
456
|
+
|
461
457
|
Returns:
|
462
458
|
True if server certificate is valid, False otherwise
|
463
459
|
"""
|
@@ -467,20 +463,20 @@ class AuthValidator:
|
|
467
463
|
if extension.oid == x509.oid.ExtensionOID.KEY_USAGE:
|
468
464
|
key_usage = extension.value
|
469
465
|
return key_usage.digital_signature and key_usage.key_encipherment
|
470
|
-
|
466
|
+
|
471
467
|
return True
|
472
|
-
|
468
|
+
|
473
469
|
except Exception as e:
|
474
470
|
self.logger.error(f"Server certificate validation error: {e}")
|
475
471
|
return False
|
476
|
-
|
472
|
+
|
477
473
|
def _validate_client_certificate(self, cert: x509.Certificate) -> bool:
|
478
474
|
"""
|
479
475
|
Validate client certificate extensions.
|
480
|
-
|
476
|
+
|
481
477
|
Args:
|
482
478
|
cert: Certificate object
|
483
|
-
|
479
|
+
|
484
480
|
Returns:
|
485
481
|
True if client certificate is valid, False otherwise
|
486
482
|
"""
|
@@ -489,21 +485,23 @@ class AuthValidator:
|
|
489
485
|
for extension in cert.extensions:
|
490
486
|
if extension.oid == x509.oid.ExtensionOID.EXTENDED_KEY_USAGE:
|
491
487
|
extended_key_usage = extension.value
|
492
|
-
return
|
493
|
-
|
488
|
+
return (
|
489
|
+
x509.oid.ExtendedKeyUsageOID.CLIENT_AUTH in extended_key_usage
|
490
|
+
)
|
491
|
+
|
494
492
|
return True
|
495
|
-
|
493
|
+
|
496
494
|
except Exception as e:
|
497
495
|
self.logger.error(f"Client certificate validation error: {e}")
|
498
496
|
return False
|
499
|
-
|
497
|
+
|
500
498
|
def _validate_jwt_token(self, token: str) -> AuthValidationResult:
|
501
499
|
"""
|
502
500
|
Validate JWT token.
|
503
|
-
|
501
|
+
|
504
502
|
Args:
|
505
503
|
token: JWT token string
|
506
|
-
|
504
|
+
|
507
505
|
Returns:
|
508
506
|
JWT validation result
|
509
507
|
"""
|
@@ -511,37 +509,34 @@ class AuthValidator:
|
|
511
509
|
# This is a placeholder for JWT validation
|
512
510
|
# In a real implementation, you would use a JWT library
|
513
511
|
# like PyJWT to validate the token
|
514
|
-
|
512
|
+
|
515
513
|
# For now, just check if token exists and has basic format
|
516
|
-
if not token or len(token.split(
|
514
|
+
if not token or len(token.split(".")) != 3:
|
517
515
|
return AuthValidationResult(
|
518
516
|
is_valid=False,
|
519
517
|
error_code=-32004,
|
520
|
-
error_message="Invalid JWT token format"
|
518
|
+
error_message="Invalid JWT token format",
|
521
519
|
)
|
522
|
-
|
520
|
+
|
523
521
|
# Extract roles from JWT payload (placeholder)
|
524
522
|
roles = []
|
525
|
-
|
526
|
-
return AuthValidationResult(
|
527
|
-
|
528
|
-
roles=roles
|
529
|
-
)
|
530
|
-
|
523
|
+
|
524
|
+
return AuthValidationResult(is_valid=True, roles=roles)
|
525
|
+
|
531
526
|
except Exception as e:
|
532
527
|
return AuthValidationResult(
|
533
528
|
is_valid=False,
|
534
529
|
error_code=-32004,
|
535
|
-
error_message=f"JWT validation failed: {str(e)}"
|
530
|
+
error_message=f"JWT validation failed: {str(e)}",
|
536
531
|
)
|
537
|
-
|
532
|
+
|
538
533
|
def _validate_api_token(self, token: str) -> AuthValidationResult:
|
539
534
|
"""
|
540
535
|
Validate API token.
|
541
|
-
|
536
|
+
|
542
537
|
Args:
|
543
538
|
token: API token string
|
544
|
-
|
539
|
+
|
545
540
|
Returns:
|
546
541
|
API token validation result
|
547
542
|
"""
|
@@ -549,58 +544,57 @@ class AuthValidator:
|
|
549
544
|
# This is a placeholder for API token validation
|
550
545
|
# In a real implementation, you would validate against
|
551
546
|
# a token store or database
|
552
|
-
|
547
|
+
|
553
548
|
if not token:
|
554
549
|
return AuthValidationResult(
|
555
550
|
is_valid=False,
|
556
551
|
error_code=-32011,
|
557
|
-
error_message="API token not found"
|
552
|
+
error_message="API token not found",
|
558
553
|
)
|
559
|
-
|
554
|
+
|
560
555
|
# Extract roles from API token (placeholder)
|
561
556
|
roles = []
|
562
|
-
|
563
|
-
return AuthValidationResult(
|
564
|
-
|
565
|
-
roles=roles
|
566
|
-
)
|
567
|
-
|
557
|
+
|
558
|
+
return AuthValidationResult(is_valid=True, roles=roles)
|
559
|
+
|
568
560
|
except Exception as e:
|
569
561
|
return AuthValidationResult(
|
570
562
|
is_valid=False,
|
571
563
|
error_code=-32004,
|
572
|
-
error_message=f"API token validation failed: {str(e)}"
|
564
|
+
error_message=f"API token validation failed: {str(e)}",
|
573
565
|
)
|
574
|
-
|
575
|
-
def _verify_certificate_chain(
|
566
|
+
|
567
|
+
def _verify_certificate_chain(
|
568
|
+
self, client_cert_path: str, ca_cert_path: str
|
569
|
+
) -> bool:
|
576
570
|
"""
|
577
571
|
Verify that client certificate is signed by CA.
|
578
|
-
|
572
|
+
|
579
573
|
Args:
|
580
574
|
client_cert_path: Path to client certificate
|
581
575
|
ca_cert_path: Path to CA certificate
|
582
|
-
|
576
|
+
|
583
577
|
Returns:
|
584
578
|
True if certificate chain is valid, False otherwise
|
585
579
|
"""
|
586
580
|
try:
|
587
581
|
# Load client certificate
|
588
|
-
with open(client_cert_path,
|
582
|
+
with open(client_cert_path, "rb") as f:
|
589
583
|
client_cert_data = f.read()
|
590
584
|
client_cert = x509.load_pem_x509_certificate(client_cert_data)
|
591
|
-
|
585
|
+
|
592
586
|
# Load CA certificate
|
593
|
-
with open(ca_cert_path,
|
587
|
+
with open(ca_cert_path, "rb") as f:
|
594
588
|
ca_cert_data = f.read()
|
595
589
|
ca_cert = x509.load_pem_x509_certificate(ca_cert_data)
|
596
|
-
|
590
|
+
|
597
591
|
# Verify client certificate is signed by CA
|
598
592
|
ca_public_key = ca_cert.public_key()
|
599
|
-
|
593
|
+
|
600
594
|
# This is a simplified verification
|
601
595
|
# In a real implementation, you would use proper signature verification
|
602
596
|
return True
|
603
|
-
|
597
|
+
|
604
598
|
except Exception as e:
|
605
599
|
self.logger.error(f"Certificate chain verification error: {e}")
|
606
|
-
return False
|
600
|
+
return False
|