mcp-proxy-adapter 4.1.0__py3-none-any.whl → 6.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (101) hide show
  1. mcp_proxy_adapter/__main__.py +12 -0
  2. mcp_proxy_adapter/api/app.py +138 -11
  3. mcp_proxy_adapter/api/handlers.py +16 -1
  4. mcp_proxy_adapter/api/middleware/__init__.py +30 -29
  5. mcp_proxy_adapter/api/middleware/auth_adapter.py +235 -0
  6. mcp_proxy_adapter/api/middleware/error_handling.py +9 -0
  7. mcp_proxy_adapter/api/middleware/factory.py +219 -0
  8. mcp_proxy_adapter/api/middleware/logging.py +32 -6
  9. mcp_proxy_adapter/api/middleware/mtls_adapter.py +305 -0
  10. mcp_proxy_adapter/api/middleware/mtls_middleware.py +296 -0
  11. mcp_proxy_adapter/api/middleware/protocol_middleware.py +135 -0
  12. mcp_proxy_adapter/api/middleware/rate_limit_adapter.py +241 -0
  13. mcp_proxy_adapter/api/middleware/roles_adapter.py +365 -0
  14. mcp_proxy_adapter/api/middleware/roles_middleware.py +381 -0
  15. mcp_proxy_adapter/api/middleware/security.py +376 -0
  16. mcp_proxy_adapter/api/middleware/token_auth_middleware.py +261 -0
  17. mcp_proxy_adapter/api/middleware/transport_middleware.py +122 -0
  18. mcp_proxy_adapter/commands/__init__.py +13 -4
  19. mcp_proxy_adapter/commands/auth_validation_command.py +408 -0
  20. mcp_proxy_adapter/commands/base.py +61 -30
  21. mcp_proxy_adapter/commands/builtin_commands.py +89 -0
  22. mcp_proxy_adapter/commands/catalog_manager.py +838 -0
  23. mcp_proxy_adapter/commands/cert_monitor_command.py +620 -0
  24. mcp_proxy_adapter/commands/certificate_management_command.py +608 -0
  25. mcp_proxy_adapter/commands/command_registry.py +705 -345
  26. mcp_proxy_adapter/commands/dependency_manager.py +245 -0
  27. mcp_proxy_adapter/commands/health_command.py +7 -0
  28. mcp_proxy_adapter/commands/hooks.py +200 -167
  29. mcp_proxy_adapter/commands/key_management_command.py +506 -0
  30. mcp_proxy_adapter/commands/load_command.py +176 -0
  31. mcp_proxy_adapter/commands/plugins_command.py +235 -0
  32. mcp_proxy_adapter/commands/protocol_management_command.py +232 -0
  33. mcp_proxy_adapter/commands/proxy_registration_command.py +268 -0
  34. mcp_proxy_adapter/commands/reload_command.py +48 -50
  35. mcp_proxy_adapter/commands/result.py +1 -0
  36. mcp_proxy_adapter/commands/roles_management_command.py +697 -0
  37. mcp_proxy_adapter/commands/ssl_setup_command.py +483 -0
  38. mcp_proxy_adapter/commands/token_management_command.py +529 -0
  39. mcp_proxy_adapter/commands/transport_management_command.py +144 -0
  40. mcp_proxy_adapter/commands/unload_command.py +158 -0
  41. mcp_proxy_adapter/config.py +99 -2
  42. mcp_proxy_adapter/core/auth_validator.py +606 -0
  43. mcp_proxy_adapter/core/certificate_utils.py +827 -0
  44. mcp_proxy_adapter/core/config_converter.py +405 -0
  45. mcp_proxy_adapter/core/config_validator.py +218 -0
  46. mcp_proxy_adapter/core/logging.py +11 -0
  47. mcp_proxy_adapter/core/protocol_manager.py +226 -0
  48. mcp_proxy_adapter/core/proxy_registration.py +270 -0
  49. mcp_proxy_adapter/core/role_utils.py +426 -0
  50. mcp_proxy_adapter/core/security_adapter.py +373 -0
  51. mcp_proxy_adapter/core/security_factory.py +239 -0
  52. mcp_proxy_adapter/core/settings.py +1 -0
  53. mcp_proxy_adapter/core/ssl_utils.py +233 -0
  54. mcp_proxy_adapter/core/transport_manager.py +292 -0
  55. mcp_proxy_adapter/custom_openapi.py +22 -11
  56. mcp_proxy_adapter/examples/basic_server/config.json +58 -23
  57. mcp_proxy_adapter/examples/basic_server/config_all_protocols.json +54 -0
  58. mcp_proxy_adapter/examples/basic_server/config_http.json +70 -0
  59. mcp_proxy_adapter/examples/basic_server/config_http_only.json +52 -0
  60. mcp_proxy_adapter/examples/basic_server/config_https.json +58 -0
  61. mcp_proxy_adapter/examples/basic_server/config_mtls.json +58 -0
  62. mcp_proxy_adapter/examples/basic_server/config_ssl.json +46 -0
  63. mcp_proxy_adapter/examples/basic_server/server.py +17 -1
  64. mcp_proxy_adapter/examples/custom_commands/__init__.py +1 -1
  65. mcp_proxy_adapter/examples/custom_commands/advanced_hooks.py +339 -23
  66. mcp_proxy_adapter/examples/custom_commands/auto_commands/test_command.py +105 -0
  67. mcp_proxy_adapter/examples/custom_commands/catalog/commands/test_command.py +129 -0
  68. mcp_proxy_adapter/examples/custom_commands/config.json +97 -41
  69. mcp_proxy_adapter/examples/custom_commands/config_all_protocols.json +46 -0
  70. mcp_proxy_adapter/examples/custom_commands/config_https_only.json +46 -0
  71. mcp_proxy_adapter/examples/custom_commands/config_https_transport.json +33 -0
  72. mcp_proxy_adapter/examples/custom_commands/config_mtls_only.json +46 -0
  73. mcp_proxy_adapter/examples/custom_commands/config_mtls_transport.json +33 -0
  74. mcp_proxy_adapter/examples/custom_commands/config_single_transport.json +33 -0
  75. mcp_proxy_adapter/examples/custom_commands/full_help_response.json +1 -0
  76. mcp_proxy_adapter/examples/custom_commands/generated_openapi.json +629 -0
  77. mcp_proxy_adapter/examples/custom_commands/get_openapi.py +103 -0
  78. mcp_proxy_adapter/examples/custom_commands/loadable_commands/test_ignored.py +129 -0
  79. mcp_proxy_adapter/examples/custom_commands/proxy_connection_manager.py +278 -0
  80. mcp_proxy_adapter/examples/custom_commands/server.py +92 -63
  81. mcp_proxy_adapter/examples/custom_commands/simple_openapi_server.py +75 -0
  82. mcp_proxy_adapter/examples/custom_commands/start_server_with_proxy_manager.py +299 -0
  83. mcp_proxy_adapter/examples/custom_commands/start_server_with_registration.py +278 -0
  84. mcp_proxy_adapter/examples/custom_commands/test_openapi.py +27 -0
  85. mcp_proxy_adapter/examples/custom_commands/test_registry.py +23 -0
  86. mcp_proxy_adapter/examples/custom_commands/test_simple.py +19 -0
  87. mcp_proxy_adapter/examples/custom_project_example/README.md +103 -0
  88. mcp_proxy_adapter/examples/custom_project_example/README_EN.md +103 -0
  89. mcp_proxy_adapter/examples/simple_custom_commands/README.md +149 -0
  90. mcp_proxy_adapter/examples/simple_custom_commands/README_EN.md +149 -0
  91. mcp_proxy_adapter/main.py +175 -0
  92. mcp_proxy_adapter/schemas/roles_schema.json +162 -0
  93. mcp_proxy_adapter/tests/unit/test_config.py +53 -0
  94. mcp_proxy_adapter/version.py +1 -1
  95. {mcp_proxy_adapter-4.1.0.dist-info → mcp_proxy_adapter-6.0.0.dist-info}/METADATA +2 -1
  96. mcp_proxy_adapter-6.0.0.dist-info/RECORD +179 -0
  97. mcp_proxy_adapter/commands/reload_settings_command.py +0 -125
  98. mcp_proxy_adapter-4.1.0.dist-info/RECORD +0 -110
  99. {mcp_proxy_adapter-4.1.0.dist-info → mcp_proxy_adapter-6.0.0.dist-info}/WHEEL +0 -0
  100. {mcp_proxy_adapter-4.1.0.dist-info → mcp_proxy_adapter-6.0.0.dist-info}/licenses/LICENSE +0 -0
  101. {mcp_proxy_adapter-4.1.0.dist-info → mcp_proxy_adapter-6.0.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,219 @@
1
+ """
2
+ Middleware Factory for creating and managing middleware components.
3
+
4
+ This module provides a factory for creating middleware components with proper
5
+ configuration and dependency management.
6
+ """
7
+
8
+ import logging
9
+ from typing import Dict, Any, List, Optional, Type
10
+
11
+ from fastapi import FastAPI
12
+
13
+ from mcp_proxy_adapter.core.logging import logger
14
+ from mcp_proxy_adapter.core.security_factory import SecurityFactory
15
+ from .base import BaseMiddleware
16
+ from .security import SecurityMiddleware
17
+ from .error_handling import ErrorHandlingMiddleware
18
+ from .logging import LoggingMiddleware
19
+
20
+
21
+ class MiddlewareFactory:
22
+ """
23
+ Factory for creating and managing middleware components.
24
+
25
+ Provides methods to create middleware components with proper configuration
26
+ and dependency management.
27
+ """
28
+
29
+ def __init__(self, app: FastAPI, config: Dict[str, Any]):
30
+ """
31
+ Initialize middleware factory.
32
+
33
+ Args:
34
+ app: FastAPI application
35
+ config: Application configuration
36
+ """
37
+ self.app = app
38
+ self.config = config
39
+ self.middleware_stack: List[BaseMiddleware] = []
40
+
41
+ logger.info("Middleware factory initialized")
42
+
43
+ def create_security_middleware(self) -> Optional[SecurityMiddleware]:
44
+ """
45
+ Create security middleware.
46
+
47
+ Returns:
48
+ SecurityMiddleware instance or None if creation failed
49
+ """
50
+ try:
51
+ security_config = self.config.get("security", {})
52
+
53
+ if not security_config.get("enabled", True):
54
+ logger.info("Security middleware disabled by configuration")
55
+ return None
56
+
57
+ middleware = SecurityMiddleware(self.app, self.config)
58
+ self.middleware_stack.append(middleware)
59
+
60
+ logger.info("Security middleware created successfully")
61
+ return middleware
62
+
63
+ except Exception as e:
64
+ logger.error(f"Failed to create security middleware: {e}")
65
+ return None
66
+
67
+ def create_error_handling_middleware(self) -> Optional[ErrorHandlingMiddleware]:
68
+ """
69
+ Create error handling middleware.
70
+
71
+ Returns:
72
+ ErrorHandlingMiddleware instance or None if creation failed
73
+ """
74
+ try:
75
+ # Import here to avoid circular imports
76
+ from .error_handling import ErrorHandlingMiddleware
77
+
78
+ middleware = ErrorHandlingMiddleware(self.app)
79
+ self.middleware_stack.append(middleware)
80
+
81
+ logger.info("Error handling middleware created successfully")
82
+ return middleware
83
+
84
+ except Exception as e:
85
+ logger.error(f"Failed to create error handling middleware: {e}")
86
+ return None
87
+
88
+ def create_logging_middleware(self) -> Optional[LoggingMiddleware]:
89
+ """
90
+ Create logging middleware.
91
+
92
+ Returns:
93
+ LoggingMiddleware instance or None if creation failed
94
+ """
95
+ try:
96
+ # Import here to avoid circular imports
97
+ from .logging import LoggingMiddleware
98
+
99
+ middleware = LoggingMiddleware(self.app, self.config)
100
+ self.middleware_stack.append(middleware)
101
+
102
+ logger.info("Logging middleware created successfully")
103
+ return middleware
104
+
105
+ except Exception as e:
106
+ logger.error(f"Failed to create logging middleware: {e}")
107
+ return None
108
+
109
+
110
+
111
+ def create_all_middleware(self) -> List[BaseMiddleware]:
112
+ """
113
+ Create all required middleware components.
114
+
115
+ Returns:
116
+ List of created middleware instances
117
+ """
118
+ middleware_list = []
119
+
120
+ # Create security middleware (unified)
121
+ security_middleware = self.create_security_middleware()
122
+ if security_middleware:
123
+ middleware_list.append(security_middleware)
124
+
125
+ # Create error handling middleware
126
+ error_middleware = self.create_error_handling_middleware()
127
+ if error_middleware:
128
+ middleware_list.append(error_middleware)
129
+
130
+ # Create logging middleware
131
+ logging_middleware = self.create_logging_middleware()
132
+ if logging_middleware:
133
+ middleware_list.append(logging_middleware)
134
+
135
+ logger.info(f"Created {len(middleware_list)} middleware components")
136
+ return middleware_list
137
+
138
+
139
+
140
+ def get_middleware_by_type(self, middleware_type: Type[BaseMiddleware]) -> Optional[BaseMiddleware]:
141
+ """
142
+ Get middleware instance by type.
143
+
144
+ Args:
145
+ middleware_type: Type of middleware to find
146
+
147
+ Returns:
148
+ Middleware instance or None if not found
149
+ """
150
+ for middleware in self.middleware_stack:
151
+ if isinstance(middleware, middleware_type):
152
+ return middleware
153
+ return None
154
+
155
+ def get_security_middleware(self) -> Optional[SecurityMiddleware]:
156
+ """
157
+ Get security middleware instance.
158
+
159
+ Returns:
160
+ SecurityMiddleware instance or None if not found
161
+ """
162
+ return self.get_middleware_by_type(SecurityMiddleware)
163
+
164
+ def validate_middleware_config(self) -> bool:
165
+ """
166
+ Validate middleware configuration.
167
+
168
+ Returns:
169
+ True if configuration is valid, False otherwise
170
+ """
171
+ try:
172
+ security_config = self.config.get("security", {})
173
+
174
+ # Validate security configuration
175
+ if not SecurityFactory.validate_config(self.config):
176
+ logger.error("Security configuration validation failed")
177
+ return False
178
+
179
+ # Validate middleware-specific configurations
180
+ if security_config.get("enabled", True):
181
+ # Check required fields for security middleware
182
+ auth_config = security_config.get("auth", {})
183
+ if not isinstance(auth_config, dict):
184
+ logger.error("Auth configuration must be a dictionary")
185
+ return False
186
+
187
+ ssl_config = security_config.get("ssl", {})
188
+ if not isinstance(ssl_config, dict):
189
+ logger.error("SSL configuration must be a dictionary")
190
+ return False
191
+
192
+ logger.info("Middleware configuration validation passed")
193
+ return True
194
+
195
+ except Exception as e:
196
+ logger.error(f"Middleware configuration validation failed: {e}")
197
+ return False
198
+
199
+ def get_middleware_info(self) -> Dict[str, Any]:
200
+ """
201
+ Get information about created middleware.
202
+
203
+ Returns:
204
+ Dictionary with middleware information
205
+ """
206
+ info = {
207
+ "total_middleware": len(self.middleware_stack),
208
+ "middleware_types": [],
209
+ "security_enabled": False
210
+ }
211
+
212
+ for middleware in self.middleware_stack:
213
+ middleware_type = type(middleware).__name__
214
+ info["middleware_types"].append(middleware_type)
215
+
216
+ if isinstance(middleware, SecurityMiddleware):
217
+ info["security_enabled"] = True
218
+
219
+ return info
@@ -5,7 +5,7 @@ Middleware for request logging.
5
5
  import time
6
6
  import json
7
7
  import uuid
8
- from typing import Callable, Awaitable
8
+ from typing import Callable, Awaitable, Dict, Any
9
9
 
10
10
  from fastapi import Request, Response
11
11
 
@@ -17,6 +17,17 @@ class LoggingMiddleware(BaseMiddleware):
17
17
  Middleware for logging requests and responses.
18
18
  """
19
19
 
20
+ def __init__(self, app, config: Dict[str, Any] = None):
21
+ """
22
+ Initialize logging middleware.
23
+
24
+ Args:
25
+ app: FastAPI application
26
+ config: Application configuration (optional)
27
+ """
28
+ super().__init__(app)
29
+ self.config = config or {}
30
+
20
31
  async def dispatch(self, request: Request, call_next: Callable[[Request], Awaitable[Response]]) -> Response:
21
32
  """
22
33
  Processes request and logs information about it.
@@ -43,7 +54,13 @@ class LoggingMiddleware(BaseMiddleware):
43
54
  url = str(request.url)
44
55
  client_host = request.client.host if request.client else "unknown"
45
56
 
46
- req_logger.info(f"Request started: {method} {url} | Client: {client_host}")
57
+ # Check if this is an OpenAPI schema request (should be logged at DEBUG level)
58
+ is_openapi_request = "/openapi.json" in url
59
+
60
+ if is_openapi_request:
61
+ req_logger.debug(f"Request started: {method} {url} | Client: {client_host}")
62
+ else:
63
+ req_logger.info(f"Request started: {method} {url} | Client: {client_host}")
47
64
 
48
65
  # Log request body if not GET or HEAD
49
66
  if method not in ["GET", "HEAD"]:
@@ -79,8 +96,12 @@ class LoggingMiddleware(BaseMiddleware):
79
96
  process_time = time.time() - start_time
80
97
  status_code = response.status_code
81
98
 
82
- req_logger.info(f"Request completed: {method} {url} | Status: {status_code} | "
83
- f"Time: {process_time:.3f}s")
99
+ if is_openapi_request:
100
+ req_logger.debug(f"Request completed: {method} {url} | Status: {status_code} | "
101
+ f"Time: {process_time:.3f}s")
102
+ else:
103
+ req_logger.info(f"Request completed: {method} {url} | Status: {status_code} | "
104
+ f"Time: {process_time:.3f}s")
84
105
 
85
106
  # Add request ID to response headers
86
107
  response.headers["X-Request-ID"] = request_id
@@ -90,7 +111,12 @@ class LoggingMiddleware(BaseMiddleware):
90
111
  except Exception as e:
91
112
  # Log error
92
113
  process_time = time.time() - start_time
93
- req_logger.error(f"Request failed: {method} {url} | Error: {str(e)} | "
94
- f"Time: {process_time:.3f}s")
114
+
115
+ if is_openapi_request:
116
+ req_logger.debug(f"Request failed: {method} {url} | Error: {str(e)} | "
117
+ f"Time: {process_time:.3f}s")
118
+ else:
119
+ req_logger.error(f"Request failed: {method} {url} | Error: {str(e)} | "
120
+ f"Time: {process_time:.3f}s")
95
121
 
96
122
  raise
@@ -0,0 +1,305 @@
1
+ """
2
+ MTLS Middleware Adapter for backward compatibility.
3
+
4
+ This module provides an adapter that maintains the same interface as MTLSMiddleware
5
+ while using the new SecurityMiddleware internally.
6
+ """
7
+
8
+ import logging
9
+ from typing import Dict, List, Optional, Any, Callable, Awaitable
10
+ from cryptography import x509
11
+ from cryptography.hazmat.primitives import serialization
12
+
13
+ from fastapi import Request, Response
14
+ from starlette.responses import JSONResponse
15
+
16
+ from mcp_proxy_adapter.core.logging import logger
17
+ from mcp_proxy_adapter.core.auth_validator import AuthValidator
18
+ from mcp_proxy_adapter.core.role_utils import RoleUtils
19
+ from mcp_proxy_adapter.core.certificate_utils import CertificateUtils
20
+ from .base import BaseMiddleware
21
+ from .security import SecurityMiddleware
22
+
23
+
24
+ class MTLSMiddlewareAdapter(BaseMiddleware):
25
+ """
26
+ Adapter for MTLSMiddleware that uses SecurityMiddleware internally.
27
+
28
+ Maintains the same interface as the original MTLSMiddleware for backward compatibility.
29
+ """
30
+
31
+ def __init__(self, app, mtls_config: Dict[str, Any]):
32
+ """
33
+ Initialize mTLS middleware adapter.
34
+
35
+ Args:
36
+ app: FastAPI application
37
+ mtls_config: mTLS configuration dictionary
38
+ """
39
+ super().__init__(app)
40
+
41
+ # Store original configuration for backward compatibility
42
+ self.mtls_config = mtls_config
43
+ self.auth_validator = AuthValidator()
44
+ self.role_utils = RoleUtils()
45
+ self.certificate_utils = CertificateUtils()
46
+
47
+ # Extract configuration
48
+ self.enabled = mtls_config.get("enabled", False)
49
+ self.ca_cert_path = mtls_config.get("ca_cert")
50
+ self.verify_client = mtls_config.get("verify_client", True)
51
+ self.client_cert_required = mtls_config.get("client_cert_required", True)
52
+ self.allowed_roles = mtls_config.get("allowed_roles", [])
53
+ self.require_roles = mtls_config.get("require_roles", False)
54
+
55
+ # Create internal security middleware
56
+ self.security_middleware = self._create_security_middleware()
57
+
58
+ logger.info(f"MTLSMiddlewareAdapter initialized: enabled={self.enabled}, "
59
+ f"verify_client={self.verify_client}, "
60
+ f"client_cert_required={self.client_cert_required}")
61
+
62
+ def _create_security_middleware(self) -> SecurityMiddleware:
63
+ """
64
+ Create internal SecurityMiddleware with MTLSMiddleware configuration.
65
+
66
+ Returns:
67
+ SecurityMiddleware instance
68
+ """
69
+ # Convert MTLSMiddleware config to SecurityMiddleware config
70
+ security_config = {
71
+ "security": {
72
+ "enabled": self.enabled,
73
+ "auth": {
74
+ "enabled": False
75
+ },
76
+ "ssl": {
77
+ "enabled": self.enabled,
78
+ "cert_file": None,
79
+ "key_file": None,
80
+ "ca_cert": self.ca_cert_path,
81
+ "min_tls_version": "TLSv1.2",
82
+ "verify_client": self.verify_client,
83
+ "client_cert_required": self.client_cert_required
84
+ },
85
+ "permissions": {
86
+ "enabled": self.require_roles,
87
+ "roles_file": None,
88
+ "default_role": "user",
89
+ "deny_by_default": True
90
+ },
91
+ "rate_limit": {
92
+ "enabled": False
93
+ }
94
+ }
95
+ }
96
+
97
+ return SecurityMiddleware(self.app, security_config)
98
+
99
+ async def before_request(self, request: Request) -> None:
100
+ """
101
+ Process request before calling the main handler.
102
+
103
+ Args:
104
+ request: FastAPI request object
105
+ """
106
+ if not self.enabled:
107
+ return
108
+
109
+ try:
110
+ # Use SecurityMiddleware for validation
111
+ await self.security_middleware.before_request(request)
112
+
113
+ # Additional MTLS-specific processing
114
+ client_cert = self._extract_client_certificate(request)
115
+ if client_cert:
116
+ # Store certificate and roles in request state for backward compatibility
117
+ request.state.client_certificate = client_cert
118
+ request.state.client_roles = self._extract_roles_from_certificate(client_cert)
119
+ request.state.client_common_name = self._get_common_name(client_cert)
120
+
121
+ logger.debug(f"mTLS authentication successful for {request.state.client_common_name} "
122
+ f"with roles: {request.state.client_roles}")
123
+
124
+ except Exception as e:
125
+ logger.error(f"mTLS authentication failed: {e}")
126
+ raise
127
+
128
+ def _extract_client_certificate(self, request: Request) -> Optional[x509.Certificate]:
129
+ """
130
+ Extract client certificate from request.
131
+
132
+ Args:
133
+ request: FastAPI request object
134
+
135
+ Returns:
136
+ Client certificate or None
137
+ """
138
+ # Check for certificate in request headers
139
+ cert_header = request.headers.get("X-Client-Cert")
140
+ if cert_header:
141
+ try:
142
+ cert_data = cert_header.encode('utf-8')
143
+ return x509.load_pem_x509_certificate(cert_data)
144
+ except Exception as e:
145
+ logger.warning(f"Failed to parse certificate from header: {e}")
146
+
147
+ # Check for certificate in request state (from SSL context)
148
+ if hasattr(request, 'client') and hasattr(request.client, 'get_extra_info'):
149
+ cert = request.client.get_extra_info('ssl_object')
150
+ if cert:
151
+ return cert
152
+
153
+ return None
154
+
155
+ def _validate_client_certificate(self, cert: x509.Certificate) -> bool:
156
+ """
157
+ Validate client certificate.
158
+
159
+ Args:
160
+ cert: Client certificate
161
+
162
+ Returns:
163
+ True if valid, False otherwise
164
+ """
165
+ try:
166
+ # Basic validation
167
+ if not self.certificate_utils.is_certificate_valid(cert):
168
+ return False
169
+
170
+ # CA validation if CA cert is provided
171
+ if self.ca_cert_path:
172
+ return self.certificate_utils.validate_certificate_chain(cert, self.ca_cert_path)
173
+
174
+ return True
175
+
176
+ except Exception as e:
177
+ logger.error(f"Certificate validation failed: {e}")
178
+ return False
179
+
180
+ def _extract_roles_from_certificate(self, cert: x509.Certificate) -> List[str]:
181
+ """
182
+ Extract roles from client certificate.
183
+
184
+ Args:
185
+ cert: Client certificate
186
+
187
+ Returns:
188
+ List of roles
189
+ """
190
+ try:
191
+ # Extract from subject alternative names
192
+ roles = []
193
+
194
+ # Check for roles in SAN
195
+ san = cert.extensions.get_extension_for_oid(x509.oid.ExtensionOID.SUBJECT_ALTERNATIVE_NAME)
196
+ if san:
197
+ for name in san.value:
198
+ if isinstance(name, x509.DNSName):
199
+ if name.value.startswith("role="):
200
+ role = name.value.split("=", 1)[1]
201
+ roles.append(role)
202
+
203
+ # Check for roles in subject
204
+ subject = cert.subject
205
+ for attr in subject:
206
+ if attr.oid.dotted_string == "2.5.4.3": # Common Name
207
+ if attr.value.startswith("role="):
208
+ role = attr.value.split("=", 1)[1]
209
+ roles.append(role)
210
+
211
+ # Check allowed roles if specified
212
+ if self.allowed_roles:
213
+ roles = [role for role in roles if role in self.allowed_roles]
214
+
215
+ return roles
216
+
217
+ except Exception as e:
218
+ logger.error(f"Failed to extract roles from certificate: {e}")
219
+ return []
220
+
221
+ def _get_common_name(self, cert: x509.Certificate) -> str:
222
+ """
223
+ Get common name from certificate.
224
+
225
+ Args:
226
+ cert: Client certificate
227
+
228
+ Returns:
229
+ Common name
230
+ """
231
+ try:
232
+ subject = cert.subject
233
+ for attr in subject:
234
+ if attr.oid.dotted_string == "2.5.4.3": # Common Name
235
+ return attr.value
236
+ return "unknown"
237
+ except Exception:
238
+ return "unknown"
239
+
240
+ def _validate_access(self, roles: List[str]) -> bool:
241
+ """
242
+ Validate access based on roles.
243
+
244
+ Args:
245
+ roles: List of client roles
246
+
247
+ Returns:
248
+ True if access is allowed, False otherwise
249
+ """
250
+ if not self.require_roles:
251
+ return True
252
+
253
+ if not roles:
254
+ return False
255
+
256
+ # Check if any role is allowed
257
+ return any(role in self.allowed_roles for role in roles)
258
+
259
+ def get_client_certificate(self, request: Request) -> Optional[x509.Certificate]:
260
+ """
261
+ Get client certificate from request state (backward compatibility).
262
+
263
+ Args:
264
+ request: Request object
265
+
266
+ Returns:
267
+ Client certificate or None
268
+ """
269
+ return getattr(request.state, 'client_certificate', None)
270
+
271
+ def get_client_roles(self, request: Request) -> List[str]:
272
+ """
273
+ Get client roles from request state (backward compatibility).
274
+
275
+ Args:
276
+ request: Request object
277
+
278
+ Returns:
279
+ List of client roles
280
+ """
281
+ return getattr(request.state, 'client_roles', [])
282
+
283
+ def get_client_common_name(self, request: Request) -> str:
284
+ """
285
+ Get client common name from request state (backward compatibility).
286
+
287
+ Args:
288
+ request: Request object
289
+
290
+ Returns:
291
+ Client common name
292
+ """
293
+ return getattr(request.state, 'client_common_name', 'unknown')
294
+
295
+ def is_mtls_authenticated(self, request: Request) -> bool:
296
+ """
297
+ Check if request is mTLS authenticated (backward compatibility).
298
+
299
+ Args:
300
+ request: Request object
301
+
302
+ Returns:
303
+ True if mTLS authenticated, False otherwise
304
+ """
305
+ return hasattr(request.state, 'client_certificate') and request.state.client_certificate is not None