mcp-proxy-adapter 6.2.36__py3-none-any.whl → 6.3.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.
@@ -14,7 +14,6 @@ import os
14
14
  import tempfile
15
15
  from pathlib import Path
16
16
  from typing import Optional, Union, Dict, Any
17
- from urllib.parse import urlparse
18
17
  import requests
19
18
  from requests.adapters import HTTPAdapter
20
19
  from urllib3.util.retry import Retry
@@ -22,12 +21,12 @@ from urllib3.util.retry import Retry
22
21
  # Import mcp_security_framework CRL utilities
23
22
  try:
24
23
  from mcp_security_framework.utils.cert_utils import (
25
- parse_crl,
26
24
  is_certificate_revoked,
27
25
  validate_certificate_against_crl,
28
26
  is_crl_valid,
29
- get_crl_info
27
+ get_crl_info,
30
28
  )
29
+
31
30
  SECURITY_FRAMEWORK_AVAILABLE = True
32
31
  except ImportError:
33
32
  SECURITY_FRAMEWORK_AVAILABLE = False
@@ -38,57 +37,68 @@ logger = logging.getLogger(__name__)
38
37
  class CRLManager:
39
38
  """
40
39
  Manager for Certificate Revocation Lists (CRL).
41
-
40
+
42
41
  Supports both file-based and URL-based CRL sources.
43
42
  Automatically downloads CRL from URLs and caches them locally.
44
43
  """
45
-
44
+
46
45
  def __init__(self, config: Dict[str, Any]):
47
46
  """
48
47
  Initialize CRL manager.
49
-
48
+
50
49
  Args:
51
50
  config: Configuration dictionary containing CRL settings
52
51
  """
53
52
  self.config = config
54
53
  self.crl_enabled = config.get("crl_enabled", False)
55
- self.crl_path = config.get("crl_path")
56
- self.crl_url = config.get("crl_url")
57
- self.crl_validity_days = config.get("crl_validity_days", 30)
58
-
54
+
55
+ # Only analyze CRL paths if certificates are enabled
56
+ certificates_enabled = config.get("certificates_enabled", True)
57
+ if certificates_enabled and self.crl_enabled:
58
+ self.crl_path = config.get("crl_path")
59
+ self.crl_url = config.get("crl_url")
60
+ self.crl_validity_days = config.get("crl_validity_days", 30)
61
+ else:
62
+ # Don't analyze CRL paths if certificates are disabled
63
+ self.crl_path = None
64
+ self.crl_url = None
65
+ self.crl_validity_days = 30
66
+
59
67
  # Cache for downloaded CRL files
60
68
  self._crl_cache: Dict[str, str] = {}
61
-
69
+
62
70
  # Setup HTTP session with retry strategy
63
71
  self._setup_http_session()
64
-
65
- logger.info(f"CRL Manager initialized - enabled: {self.crl_enabled}")
66
-
72
+
73
+ logger.info(
74
+ f"CRL Manager initialized - enabled: {self.crl_enabled}, certificates_enabled: {certificates_enabled}"
75
+ )
76
+
67
77
  def _setup_http_session(self):
68
78
  """Setup HTTP session with retry strategy for CRL downloads."""
69
79
  self.session = requests.Session()
70
-
80
+
71
81
  # Configure retry strategy
72
82
  retry_strategy = Retry(
73
83
  total=3,
74
84
  backoff_factor=1,
75
85
  status_forcelist=[429, 500, 502, 503, 504],
76
86
  )
77
-
87
+
78
88
  adapter = HTTPAdapter(max_retries=retry_strategy)
79
89
  self.session.mount("http://", adapter)
80
90
  self.session.mount("https://", adapter)
81
-
91
+
82
92
  # Set timeout
83
93
  self.session.timeout = 30
84
-
94
+
85
95
  def get_crl_data(self) -> Optional[Union[str, bytes, Path]]:
86
96
  """
87
97
  Get CRL data from configured source.
88
-
98
+
89
99
  Returns:
90
100
  CRL data as string, bytes, or Path, or None if not available
91
-
101
+
92
102
  Raises:
93
103
  ValueError: If CRL is enabled but no source is configured
94
104
  FileNotFoundError: If CRL file is not found
@@ -97,49 +107,52 @@ class CRLManager:
97
107
  if not self.crl_enabled:
98
108
  logger.debug("CRL is disabled, skipping CRL check")
99
109
  return None
100
-
110
+
101
111
  # Check if CRL URL is configured
102
112
  if self.crl_url:
103
113
  return self._get_crl_from_url()
104
-
114
+
105
115
  # Check if CRL file path is configured
106
116
  if self.crl_path:
107
117
  return self._get_crl_from_file()
108
-
118
+
109
119
  # If CRL is enabled but no source is configured, this is an error
110
120
  if self.crl_enabled:
111
121
  raise ValueError("CRL is enabled but neither crl_path nor crl_url is configured")
112
-
122
+
113
123
  return None
114
-
124
+
115
125
  def _get_crl_from_url(self) -> str:
116
126
  """
117
127
  Download CRL from URL.
118
-
128
+
119
129
  Returns:
120
130
  Path to downloaded CRL file
121
-
131
+
122
132
  Raises:
123
133
  requests.RequestException: If download fails
124
134
  ValueError: If downloaded data is not valid CRL
125
135
  """
126
136
  try:
127
137
  logger.info(f"Downloading CRL from URL: {self.crl_url}")
128
-
138
+
129
139
  # Download CRL
130
140
  response = self.session.get(self.crl_url)
131
141
  response.raise_for_status()
132
-
142
+
133
143
  # Validate content type
134
- content_type = response.headers.get('content-type', '').lower()
135
- if 'application/pkix-crl' not in content_type and 'application/x-pkcs7-crl' not in content_type:
144
+ content_type = response.headers.get("content-type", "").lower()
145
+ if (
146
+ "application/pkix-crl" not in content_type
147
+ and "application/x-pkcs7-crl" not in content_type
148
+ ):
136
149
  logger.warning(f"Unexpected content type for CRL: {content_type}")
137
-
150
+
138
151
  # Save to temporary file
139
- with tempfile.NamedTemporaryFile(mode='wb', suffix='.crl', delete=False) as temp_file:
152
+ with tempfile.NamedTemporaryFile(mode="wb", suffix=".crl", delete=False) as temp_file:
140
153
  temp_file.write(response.content)
141
154
  temp_file_path = temp_file.name
142
-
155
+
143
156
  # Validate CRL format
144
157
  if SECURITY_FRAMEWORK_AVAILABLE:
145
158
  try:
@@ -150,33 +163,33 @@ class CRLManager:
150
163
  raise ValueError(f"Downloaded CRL is not valid: {e}")
151
164
  else:
152
165
  logger.warning("mcp_security_framework not available, skipping CRL validation")
153
-
166
+
154
167
  # Cache the file path
155
168
  self._crl_cache[self.crl_url] = temp_file_path
156
-
169
+
157
170
  return temp_file_path
158
-
171
+
159
172
  except requests.RequestException as e:
160
173
  logger.error(f"Failed to download CRL from {self.crl_url}: {e}")
161
174
  raise
162
175
  except Exception as e:
163
176
  logger.error(f"CRL download failed: {e}")
164
177
  raise
165
-
178
+
166
179
  def _get_crl_from_file(self) -> str:
167
180
  """
168
181
  Get CRL from file path.
169
-
182
+
170
183
  Returns:
171
184
  Path to CRL file
172
-
185
+
173
186
  Raises:
174
187
  FileNotFoundError: If CRL file is not found
175
188
  ValueError: If CRL file is not valid
176
189
  """
177
190
  if not os.path.exists(self.crl_path):
178
191
  raise FileNotFoundError(f"CRL file not found: {self.crl_path}")
179
-
192
+
180
193
  # Validate CRL format
181
194
  if SECURITY_FRAMEWORK_AVAILABLE:
182
195
  try:
@@ -186,60 +199,60 @@ class CRLManager:
186
199
  raise ValueError(f"CRL file is not valid: {e}")
187
200
  else:
188
201
  logger.warning("mcp_security_framework not available, skipping CRL validation")
189
-
202
+
190
203
  return self.crl_path
191
-
204
+
192
205
  def is_certificate_revoked(self, cert_path: str) -> bool:
193
206
  """
194
207
  Check if certificate is revoked according to CRL.
195
-
208
+
196
209
  Args:
197
210
  cert_path: Path to certificate file
198
-
211
+
199
212
  Returns:
200
213
  True if certificate is revoked, False otherwise
201
-
214
+
202
215
  Raises:
203
216
  ValueError: If CRL is enabled but not available
204
217
  FileNotFoundError: If certificate file is not found
205
218
  """
206
219
  if not self.crl_enabled:
207
220
  return False
208
-
221
+
209
222
  if not SECURITY_FRAMEWORK_AVAILABLE:
210
223
  logger.warning("mcp_security_framework not available, skipping CRL check")
211
224
  return False
212
-
225
+
213
226
  try:
214
227
  crl_data = self.get_crl_data()
215
228
  if not crl_data:
216
229
  logger.warning("CRL is enabled but no CRL data is available")
217
230
  return False
218
-
231
+
219
232
  is_revoked = is_certificate_revoked(cert_path, crl_data)
220
-
233
+
221
234
  if is_revoked:
222
235
  logger.warning(f"Certificate is revoked according to CRL: {cert_path}")
223
236
  else:
224
237
  logger.debug(f"Certificate is not revoked according to CRL: {cert_path}")
225
-
238
+
226
239
  return is_revoked
227
-
240
+
228
241
  except Exception as e:
229
242
  logger.error(f"CRL check failed for certificate {cert_path}: {e}")
230
243
  # For security, consider certificate invalid if CRL check fails
231
244
  return True
232
-
245
+
233
246
  def validate_certificate_against_crl(self, cert_path: str) -> Dict[str, Any]:
234
247
  """
235
248
  Validate certificate against CRL and return detailed status.
236
-
249
+
237
250
  Args:
238
251
  cert_path: Path to certificate file
239
-
252
+
240
253
  Returns:
241
254
  Dictionary containing validation results
242
-
255
+
243
256
  Raises:
244
257
  ValueError: If CRL is enabled but not available
245
258
  FileNotFoundError: If certificate file is not found
@@ -249,18 +262,18 @@ class CRLManager:
249
262
  "is_revoked": False,
250
263
  "crl_checked": False,
251
264
  "crl_source": None,
252
- "message": "CRL check is disabled"
265
+ "message": "CRL check is disabled",
253
266
  }
254
-
267
+
255
268
  if not SECURITY_FRAMEWORK_AVAILABLE:
256
269
  logger.warning("mcp_security_framework not available, skipping CRL validation")
257
270
  return {
258
271
  "is_revoked": False,
259
272
  "crl_checked": False,
260
273
  "crl_source": None,
261
- "message": "mcp_security_framework not available"
274
+ "message": "mcp_security_framework not available",
262
275
  }
263
-
276
+
264
277
  try:
265
278
  crl_data = self.get_crl_data()
266
279
  if not crl_data:
@@ -269,20 +282,20 @@ class CRLManager:
269
282
  "is_revoked": True, # For security, consider invalid if CRL unavailable
270
283
  "crl_checked": False,
271
284
  "crl_source": None,
272
- "message": "CRL is enabled but not available"
285
+ "message": "CRL is enabled but not available",
273
286
  }
274
-
287
+
275
288
  # Get CRL source info
276
289
  crl_source = self.crl_url if self.crl_url else self.crl_path
277
-
290
+
278
291
  # Validate certificate against CRL
279
292
  result = validate_certificate_against_crl(cert_path, crl_data)
280
-
293
+
281
294
  result["crl_checked"] = True
282
295
  result["crl_source"] = crl_source
283
-
296
+
284
297
  return result
285
-
298
+
286
299
  except Exception as e:
287
300
  logger.error(f"CRL validation failed for certificate {cert_path}: {e}")
288
301
  # For security, consider certificate invalid if CRL validation fails
@@ -290,34 +303,34 @@ class CRLManager:
290
303
  "is_revoked": True,
291
304
  "crl_checked": False,
292
305
  "crl_source": self.crl_url if self.crl_url else self.crl_path,
293
- "message": f"CRL validation failed: {e}"
306
+ "message": f"CRL validation failed: {e}",
294
307
  }
295
-
308
+
296
309
  def get_crl_info(self) -> Optional[Dict[str, Any]]:
297
310
  """
298
311
  Get information about the configured CRL.
299
-
312
+
300
313
  Returns:
301
314
  Dictionary containing CRL information, or None if CRL is not available
302
315
  """
303
316
  if not self.crl_enabled:
304
317
  return None
305
-
318
+
306
319
  if not SECURITY_FRAMEWORK_AVAILABLE:
307
320
  logger.warning("mcp_security_framework not available, cannot get CRL info")
308
321
  return None
309
-
322
+
310
323
  try:
311
324
  crl_data = self.get_crl_data()
312
325
  if not crl_data:
313
326
  return None
314
-
327
+
315
328
  return get_crl_info(crl_data)
316
-
329
+
317
330
  except Exception as e:
318
331
  logger.error(f"Failed to get CRL info: {e}")
319
332
  return None
320
-
333
+
321
334
  def cleanup_cache(self):
322
335
  """Clean up temporary CRL files."""
323
336
  for url, temp_path in self._crl_cache.items():
@@ -327,9 +340,9 @@ class CRLManager:
327
340
  logger.debug(f"Cleaned up temporary CRL file: {temp_path}")
328
341
  except Exception as e:
329
342
  logger.warning(f"Failed to cleanup temporary CRL file {temp_path}: {e}")
330
-
343
+
331
344
  self._crl_cache.clear()
332
-
345
+
333
346
  def __del__(self):
334
347
  """Cleanup when object is destroyed."""
335
348
  self.cleanup_cache()