regscale-cli 6.20.3.1__py3-none-any.whl → 6.20.4.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.
Potentially problematic release.
This version of regscale-cli might be problematic. Click here for more details.
- regscale/__init__.py +1 -1
- regscale/integrations/commercial/__init__.py +1 -0
- regscale/integrations/commercial/jira.py +35 -16
- regscale/integrations/commercial/qualys/__init__.py +298 -28
- regscale/integrations/commercial/qualys/qualys_error_handler.py +519 -0
- regscale/integrations/commercial/qualys/scanner.py +222 -97
- regscale/integrations/commercial/synqly/assets.py +11 -1
- regscale/integrations/commercial/synqly/edr.py +4 -4
- regscale/integrations/commercial/synqly/ticketing.py +1 -1
- regscale/integrations/commercial/synqly/vulnerabilities.py +2 -2
- regscale/integrations/public/fedramp/fedramp_cis_crm.py +72 -42
- regscale/models/app_models/import_validater.py +20 -2
- regscale/models/integration_models/cisa_kev_data.json +53 -8
- regscale/models/integration_models/synqly_models/capabilities.json +1 -1
- regscale/models/regscale_models/task.py +0 -1
- {regscale_cli-6.20.3.1.dist-info → regscale_cli-6.20.4.0.dist-info}/METADATA +1 -1
- {regscale_cli-6.20.3.1.dist-info → regscale_cli-6.20.4.0.dist-info}/RECORD +21 -20
- {regscale_cli-6.20.3.1.dist-info → regscale_cli-6.20.4.0.dist-info}/LICENSE +0 -0
- {regscale_cli-6.20.3.1.dist-info → regscale_cli-6.20.4.0.dist-info}/WHEEL +0 -0
- {regscale_cli-6.20.3.1.dist-info → regscale_cli-6.20.4.0.dist-info}/entry_points.txt +0 -0
- {regscale_cli-6.20.3.1.dist-info → regscale_cli-6.20.4.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,519 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""
|
|
4
|
+
Qualys Error Handler
|
|
5
|
+
|
|
6
|
+
This module provides comprehensive error handling for Qualys API responses
|
|
7
|
+
and XML parsing. It handles various error codes and conditions that can be
|
|
8
|
+
returned by the Qualys API.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import logging
|
|
12
|
+
import xml.etree.ElementTree as ET
|
|
13
|
+
from typing import Dict, Optional, Tuple, Union, Any
|
|
14
|
+
import xmltodict
|
|
15
|
+
from requests import Response
|
|
16
|
+
|
|
17
|
+
logger = logging.getLogger("regscale")
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class QualysErrorCode:
|
|
21
|
+
"""Qualys API error codes and their meanings"""
|
|
22
|
+
|
|
23
|
+
# Authentication errors
|
|
24
|
+
INVALID_LOGIN = "999"
|
|
25
|
+
AUTHENTICATION_FAILED = "1001"
|
|
26
|
+
INVALID_USERNAME_PASSWORD = "1002"
|
|
27
|
+
ACCOUNT_DISABLED = "1003"
|
|
28
|
+
|
|
29
|
+
# Authorization errors
|
|
30
|
+
INSUFFICIENT_PRIVILEGES = "2001"
|
|
31
|
+
FEATURE_NOT_ENABLED = "2002"
|
|
32
|
+
API_ACCESS_DISABLED = "2003"
|
|
33
|
+
|
|
34
|
+
# Request errors
|
|
35
|
+
INVALID_REQUEST = "3001"
|
|
36
|
+
MISSING_REQUIRED_PARAMETER = "3002"
|
|
37
|
+
INVALID_PARAMETER_VALUE = "3003"
|
|
38
|
+
REQUEST_TOO_LARGE = "3004"
|
|
39
|
+
|
|
40
|
+
# Rate limiting
|
|
41
|
+
RATE_LIMIT_EXCEEDED = "4001"
|
|
42
|
+
CONCURRENT_LIMIT_EXCEEDED = "4002"
|
|
43
|
+
|
|
44
|
+
# Server errors
|
|
45
|
+
INTERNAL_SERVER_ERROR = "5001"
|
|
46
|
+
SERVICE_UNAVAILABLE = "5002"
|
|
47
|
+
TIMEOUT = "5003"
|
|
48
|
+
DATABASE_ERROR = "5004"
|
|
49
|
+
|
|
50
|
+
# Data errors
|
|
51
|
+
NO_DATA_FOUND = "6001"
|
|
52
|
+
DATA_PROCESSING_ERROR = "6002"
|
|
53
|
+
INVALID_DATA_FORMAT = "6003"
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class QualysErrorHandler:
|
|
57
|
+
"""Handler for Qualys API errors and XML parsing issues"""
|
|
58
|
+
|
|
59
|
+
ERROR_CODE_MESSAGES = {
|
|
60
|
+
QualysErrorCode.INVALID_LOGIN: "Invalid login credentials",
|
|
61
|
+
QualysErrorCode.AUTHENTICATION_FAILED: "Authentication failed",
|
|
62
|
+
QualysErrorCode.INVALID_USERNAME_PASSWORD: "Invalid username or password",
|
|
63
|
+
QualysErrorCode.ACCOUNT_DISABLED: "Account is disabled",
|
|
64
|
+
QualysErrorCode.INSUFFICIENT_PRIVILEGES: ("Insufficient privileges for this operation"),
|
|
65
|
+
QualysErrorCode.FEATURE_NOT_ENABLED: "Required feature is not enabled",
|
|
66
|
+
QualysErrorCode.API_ACCESS_DISABLED: "API access is disabled",
|
|
67
|
+
QualysErrorCode.INVALID_REQUEST: "Invalid request format",
|
|
68
|
+
QualysErrorCode.MISSING_REQUIRED_PARAMETER: "Missing required parameter",
|
|
69
|
+
QualysErrorCode.INVALID_PARAMETER_VALUE: "Invalid parameter value",
|
|
70
|
+
QualysErrorCode.REQUEST_TOO_LARGE: "Request is too large",
|
|
71
|
+
QualysErrorCode.RATE_LIMIT_EXCEEDED: "Rate limit exceeded",
|
|
72
|
+
QualysErrorCode.CONCURRENT_LIMIT_EXCEEDED: ("Concurrent request limit exceeded"),
|
|
73
|
+
QualysErrorCode.INTERNAL_SERVER_ERROR: "Internal server error",
|
|
74
|
+
QualysErrorCode.SERVICE_UNAVAILABLE: "Service temporarily unavailable",
|
|
75
|
+
QualysErrorCode.TIMEOUT: "Request timeout",
|
|
76
|
+
QualysErrorCode.DATABASE_ERROR: "Database error",
|
|
77
|
+
QualysErrorCode.NO_DATA_FOUND: "No data found",
|
|
78
|
+
QualysErrorCode.DATA_PROCESSING_ERROR: "Data processing error",
|
|
79
|
+
QualysErrorCode.INVALID_DATA_FORMAT: "Invalid data format",
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
RETRY_CODES = {
|
|
83
|
+
QualysErrorCode.RATE_LIMIT_EXCEEDED,
|
|
84
|
+
QualysErrorCode.CONCURRENT_LIMIT_EXCEEDED,
|
|
85
|
+
QualysErrorCode.SERVICE_UNAVAILABLE,
|
|
86
|
+
QualysErrorCode.TIMEOUT,
|
|
87
|
+
QualysErrorCode.DATABASE_ERROR,
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
FATAL_CODES = {
|
|
91
|
+
QualysErrorCode.INVALID_LOGIN,
|
|
92
|
+
QualysErrorCode.AUTHENTICATION_FAILED,
|
|
93
|
+
QualysErrorCode.INVALID_USERNAME_PASSWORD,
|
|
94
|
+
QualysErrorCode.ACCOUNT_DISABLED,
|
|
95
|
+
QualysErrorCode.INSUFFICIENT_PRIVILEGES,
|
|
96
|
+
QualysErrorCode.API_ACCESS_DISABLED,
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
@staticmethod
|
|
100
|
+
def validate_response(response: Response) -> Tuple[bool, Optional[str], Optional[Dict[str, Any]]]:
|
|
101
|
+
"""
|
|
102
|
+
Validate a Qualys API response and check for errors.
|
|
103
|
+
|
|
104
|
+
:param Response response: HTTP response from Qualys API
|
|
105
|
+
:return: Tuple of (is_valid, error_message, parsed_data)
|
|
106
|
+
:rtype: Tuple[bool, Optional[str], Optional[Dict[str, Any]]]
|
|
107
|
+
"""
|
|
108
|
+
if not response:
|
|
109
|
+
return False, "No response received from Qualys API", None
|
|
110
|
+
|
|
111
|
+
if not response.ok:
|
|
112
|
+
return False, f"HTTP {response.status_code}: {response.text}", None
|
|
113
|
+
|
|
114
|
+
if not response.text:
|
|
115
|
+
return False, "Empty response from Qualys API", None
|
|
116
|
+
|
|
117
|
+
# Try to parse the XML response
|
|
118
|
+
try:
|
|
119
|
+
parsed_data = xmltodict.parse(response.text)
|
|
120
|
+
|
|
121
|
+
# Check for Qualys-specific errors in the parsed data
|
|
122
|
+
error_info = QualysErrorHandler._check_qualys_errors(parsed_data)
|
|
123
|
+
if error_info:
|
|
124
|
+
return False, error_info, parsed_data
|
|
125
|
+
|
|
126
|
+
return True, None, parsed_data
|
|
127
|
+
|
|
128
|
+
except Exception as e:
|
|
129
|
+
logger.error(f"Failed to parse Qualys response as XML: {e}")
|
|
130
|
+
return False, f"XML parsing error: {e}", None
|
|
131
|
+
|
|
132
|
+
@staticmethod
|
|
133
|
+
def _check_qualys_errors(parsed_data: Dict[str, Any]) -> Optional[str]:
|
|
134
|
+
"""
|
|
135
|
+
Check parsed XML data for Qualys-specific error conditions.
|
|
136
|
+
|
|
137
|
+
:param Dict[str, Any] parsed_data: Parsed XML data
|
|
138
|
+
:return: Error message if found, None otherwise
|
|
139
|
+
:rtype: Optional[str]
|
|
140
|
+
"""
|
|
141
|
+
# Check for SIMPLE_RETURN error format
|
|
142
|
+
simple_return_error = QualysErrorHandler._check_simple_return_errors(parsed_data)
|
|
143
|
+
if simple_return_error:
|
|
144
|
+
return simple_return_error
|
|
145
|
+
|
|
146
|
+
# Check for HOST_LIST_VM_DETECTION_OUTPUT errors
|
|
147
|
+
detection_error = QualysErrorHandler._check_detection_output_errors(parsed_data)
|
|
148
|
+
if detection_error:
|
|
149
|
+
return detection_error
|
|
150
|
+
|
|
151
|
+
# Check for other common error patterns
|
|
152
|
+
return QualysErrorHandler._check_common_error_patterns(parsed_data)
|
|
153
|
+
|
|
154
|
+
@staticmethod
|
|
155
|
+
def _check_simple_return_errors(parsed_data: Dict[str, Any]) -> Optional[str]:
|
|
156
|
+
"""
|
|
157
|
+
Check for SIMPLE_RETURN error format.
|
|
158
|
+
|
|
159
|
+
:param Dict[str, Any] parsed_data: Parsed XML data
|
|
160
|
+
:return: Error message if found, None otherwise
|
|
161
|
+
:rtype: Optional[str]
|
|
162
|
+
"""
|
|
163
|
+
if "SIMPLE_RETURN" not in parsed_data:
|
|
164
|
+
return None
|
|
165
|
+
|
|
166
|
+
simple_return = parsed_data["SIMPLE_RETURN"]
|
|
167
|
+
response = simple_return.get("RESPONSE", {})
|
|
168
|
+
|
|
169
|
+
if "TEXT" not in response:
|
|
170
|
+
return None
|
|
171
|
+
|
|
172
|
+
error_text = response["TEXT"]
|
|
173
|
+
logger.error(f"Qualys API error: {error_text}")
|
|
174
|
+
|
|
175
|
+
if "CODE" in response:
|
|
176
|
+
error_code = response["CODE"]
|
|
177
|
+
return QualysErrorHandler._format_error_message(error_code, error_text)
|
|
178
|
+
|
|
179
|
+
return f"Qualys API error: {error_text}"
|
|
180
|
+
|
|
181
|
+
@staticmethod
|
|
182
|
+
def _check_detection_output_errors(parsed_data: Dict[str, Any]) -> Optional[str]:
|
|
183
|
+
"""
|
|
184
|
+
Check for HOST_LIST_VM_DETECTION_OUTPUT errors.
|
|
185
|
+
|
|
186
|
+
:param Dict[str, Any] parsed_data: Parsed XML data
|
|
187
|
+
:return: Error message if found, None otherwise
|
|
188
|
+
:rtype: Optional[str]
|
|
189
|
+
"""
|
|
190
|
+
if "HOST_LIST_VM_DETECTION_OUTPUT" not in parsed_data:
|
|
191
|
+
return None
|
|
192
|
+
|
|
193
|
+
output = parsed_data["HOST_LIST_VM_DETECTION_OUTPUT"]
|
|
194
|
+
response = output.get("RESPONSE", {})
|
|
195
|
+
|
|
196
|
+
# Check for error text in the response
|
|
197
|
+
if "TEXT" in response and "error" in str(response["TEXT"]).lower():
|
|
198
|
+
error_text = response["TEXT"]
|
|
199
|
+
logger.error(f"Qualys detection output error: {error_text}")
|
|
200
|
+
return f"Qualys detection error: {error_text}"
|
|
201
|
+
|
|
202
|
+
# Check for warning messages (non-fatal)
|
|
203
|
+
QualysErrorHandler._log_warnings_if_present(response)
|
|
204
|
+
return None
|
|
205
|
+
|
|
206
|
+
@staticmethod
|
|
207
|
+
def _log_warnings_if_present(response: Dict[str, Any]) -> None:
|
|
208
|
+
"""
|
|
209
|
+
Log warning messages if present in response.
|
|
210
|
+
|
|
211
|
+
:param Dict[str, Any] response: Response data
|
|
212
|
+
"""
|
|
213
|
+
if "WARNING" not in response:
|
|
214
|
+
return
|
|
215
|
+
|
|
216
|
+
warning = response["WARNING"]
|
|
217
|
+
if isinstance(warning, dict) and "TEXT" in warning:
|
|
218
|
+
warning_text = warning["TEXT"]
|
|
219
|
+
logger.warning(f"Qualys API warning: {warning_text}")
|
|
220
|
+
|
|
221
|
+
@staticmethod
|
|
222
|
+
def _check_common_error_patterns(parsed_data: Dict[str, Any]) -> Optional[str]:
|
|
223
|
+
"""
|
|
224
|
+
Check for other common error patterns.
|
|
225
|
+
|
|
226
|
+
:param Dict[str, Any] parsed_data: Parsed XML data
|
|
227
|
+
:return: Error message if found, None otherwise
|
|
228
|
+
:rtype: Optional[str]
|
|
229
|
+
"""
|
|
230
|
+
error_patterns = [
|
|
231
|
+
("ERROR", "error"),
|
|
232
|
+
("FAULT", "fault"),
|
|
233
|
+
("EXCEPTION", "exception"),
|
|
234
|
+
]
|
|
235
|
+
|
|
236
|
+
for pattern_key, pattern_name in error_patterns:
|
|
237
|
+
error_message = QualysErrorHandler._check_error_pattern(parsed_data, pattern_key, pattern_name)
|
|
238
|
+
if error_message:
|
|
239
|
+
return error_message
|
|
240
|
+
|
|
241
|
+
return None
|
|
242
|
+
|
|
243
|
+
@staticmethod
|
|
244
|
+
def _check_error_pattern(parsed_data: Dict[str, Any], pattern_key: str, pattern_name: str) -> Optional[str]:
|
|
245
|
+
"""
|
|
246
|
+
Check for a specific error pattern in parsed data.
|
|
247
|
+
|
|
248
|
+
:param Dict[str, Any] parsed_data: Parsed XML data
|
|
249
|
+
:param str pattern_key: Key to look for in the data
|
|
250
|
+
:param str pattern_name: Human-readable name for the pattern
|
|
251
|
+
:return: Error message if found, None otherwise
|
|
252
|
+
:rtype: Optional[str]
|
|
253
|
+
"""
|
|
254
|
+
if pattern_key not in parsed_data:
|
|
255
|
+
return None
|
|
256
|
+
|
|
257
|
+
error_data = parsed_data[pattern_key]
|
|
258
|
+
|
|
259
|
+
if isinstance(error_data, dict) and "TEXT" in error_data:
|
|
260
|
+
error_text = error_data["TEXT"]
|
|
261
|
+
logger.error(f"Qualys {pattern_name} error: {error_text}")
|
|
262
|
+
return f"Qualys {pattern_name} error: {error_text}"
|
|
263
|
+
|
|
264
|
+
return None
|
|
265
|
+
|
|
266
|
+
@staticmethod
|
|
267
|
+
def _format_error_message(error_code: str, error_text: str) -> str:
|
|
268
|
+
"""
|
|
269
|
+
Format an error message with code and description.
|
|
270
|
+
|
|
271
|
+
:param str error_code: Qualys error code
|
|
272
|
+
:param str error_text: Error text from API
|
|
273
|
+
:return: Formatted error message
|
|
274
|
+
:rtype: str
|
|
275
|
+
"""
|
|
276
|
+
code_description = QualysErrorHandler.ERROR_CODE_MESSAGES.get(error_code, "Unknown error code")
|
|
277
|
+
return f"Qualys Error {error_code}: {code_description} - {error_text}"
|
|
278
|
+
|
|
279
|
+
@staticmethod
|
|
280
|
+
def should_retry(error_code: str) -> bool:
|
|
281
|
+
"""
|
|
282
|
+
Determine if a request should be retried based on the error code.
|
|
283
|
+
|
|
284
|
+
:param str error_code: Qualys error code
|
|
285
|
+
:return: True if the request should be retried
|
|
286
|
+
:rtype: bool
|
|
287
|
+
"""
|
|
288
|
+
return error_code in QualysErrorHandler.RETRY_CODES
|
|
289
|
+
|
|
290
|
+
@staticmethod
|
|
291
|
+
def is_fatal_error(error_code: str) -> bool:
|
|
292
|
+
"""
|
|
293
|
+
Determine if an error is fatal and should stop processing.
|
|
294
|
+
|
|
295
|
+
:param str error_code: Qualys error code
|
|
296
|
+
:return: True if the error is fatal
|
|
297
|
+
:rtype: bool
|
|
298
|
+
"""
|
|
299
|
+
return error_code in QualysErrorHandler.FATAL_CODES
|
|
300
|
+
|
|
301
|
+
@staticmethod
|
|
302
|
+
def parse_xml_safely(xml_content: str) -> Tuple[bool, Optional[Dict[str, Any]], Optional[str]]:
|
|
303
|
+
"""
|
|
304
|
+
Safely parse XML content with comprehensive error handling.
|
|
305
|
+
|
|
306
|
+
:param str xml_content: XML content to parse
|
|
307
|
+
:return: Tuple of (success, parsed_data, error_message)
|
|
308
|
+
:rtype: Tuple[bool, Optional[Dict[str, Any]], Optional[str]]
|
|
309
|
+
"""
|
|
310
|
+
if not xml_content:
|
|
311
|
+
return False, None, "Empty XML content"
|
|
312
|
+
|
|
313
|
+
if not xml_content.strip():
|
|
314
|
+
return False, None, "XML content contains only whitespace"
|
|
315
|
+
|
|
316
|
+
try:
|
|
317
|
+
# First try with xmltodict for better structure
|
|
318
|
+
parsed_data = xmltodict.parse(xml_content)
|
|
319
|
+
|
|
320
|
+
# Check for Qualys-specific errors
|
|
321
|
+
error_info = QualysErrorHandler._check_qualys_errors(parsed_data)
|
|
322
|
+
if error_info:
|
|
323
|
+
return False, parsed_data, error_info
|
|
324
|
+
|
|
325
|
+
return True, parsed_data, None
|
|
326
|
+
|
|
327
|
+
except xmltodict.expat.ExpatError as e:
|
|
328
|
+
logger.error(f"XML parsing error (expat): {e}")
|
|
329
|
+
return False, None, f"XML parsing error: {e}"
|
|
330
|
+
|
|
331
|
+
except ET.ParseError as e:
|
|
332
|
+
logger.error(f"XML parsing error (ElementTree): {e}")
|
|
333
|
+
return False, None, f"XML parsing error: {e}"
|
|
334
|
+
|
|
335
|
+
except Exception as e:
|
|
336
|
+
logger.error(f"Unexpected error parsing XML: {e}")
|
|
337
|
+
return False, None, f"Unexpected XML parsing error: {e}"
|
|
338
|
+
|
|
339
|
+
@staticmethod
|
|
340
|
+
def extract_error_details(parsed_data: Dict[str, Any]) -> Dict[str, Any]:
|
|
341
|
+
"""
|
|
342
|
+
Extract detailed error information from parsed Qualys response.
|
|
343
|
+
|
|
344
|
+
:param Dict[str, Any] parsed_data: Parsed XML data
|
|
345
|
+
:return: Dictionary containing error details
|
|
346
|
+
:rtype: Dict[str, Any]
|
|
347
|
+
"""
|
|
348
|
+
error_details = QualysErrorHandler._create_empty_error_details()
|
|
349
|
+
|
|
350
|
+
# Check SIMPLE_RETURN format
|
|
351
|
+
QualysErrorHandler._extract_simple_return_details(parsed_data, error_details)
|
|
352
|
+
|
|
353
|
+
# Check for other error formats if no SIMPLE_RETURN error found
|
|
354
|
+
if not error_details["has_error"]:
|
|
355
|
+
QualysErrorHandler._extract_other_error_formats(parsed_data, error_details)
|
|
356
|
+
|
|
357
|
+
return error_details
|
|
358
|
+
|
|
359
|
+
@staticmethod
|
|
360
|
+
def _create_empty_error_details() -> Dict[str, Any]:
|
|
361
|
+
"""
|
|
362
|
+
Create an empty error details dictionary with default values.
|
|
363
|
+
|
|
364
|
+
:return: Dictionary with default error detail values
|
|
365
|
+
:rtype: Dict[str, Any]
|
|
366
|
+
"""
|
|
367
|
+
return {
|
|
368
|
+
"has_error": False,
|
|
369
|
+
"error_code": None,
|
|
370
|
+
"error_message": None,
|
|
371
|
+
"error_type": None,
|
|
372
|
+
"retry_after": None,
|
|
373
|
+
"additional_info": {},
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
@staticmethod
|
|
377
|
+
def _extract_simple_return_details(parsed_data: Dict[str, Any], error_details: Dict[str, Any]) -> None:
|
|
378
|
+
"""
|
|
379
|
+
Extract error details from SIMPLE_RETURN format.
|
|
380
|
+
|
|
381
|
+
:param Dict[str, Any] parsed_data: Parsed XML data
|
|
382
|
+
:param Dict[str, Any] error_details: Error details dictionary to update
|
|
383
|
+
"""
|
|
384
|
+
if "SIMPLE_RETURN" not in parsed_data:
|
|
385
|
+
return
|
|
386
|
+
|
|
387
|
+
simple_return = parsed_data["SIMPLE_RETURN"]
|
|
388
|
+
response = simple_return.get("RESPONSE", {})
|
|
389
|
+
|
|
390
|
+
if "TEXT" not in response:
|
|
391
|
+
return
|
|
392
|
+
|
|
393
|
+
error_details["has_error"] = True
|
|
394
|
+
error_details["error_message"] = response["TEXT"]
|
|
395
|
+
error_details["error_type"] = "SIMPLE_RETURN"
|
|
396
|
+
|
|
397
|
+
if "CODE" in response:
|
|
398
|
+
error_details["error_code"] = response["CODE"]
|
|
399
|
+
|
|
400
|
+
# Extract retry-after information if available
|
|
401
|
+
QualysErrorHandler._extract_retry_after_info(response, error_details)
|
|
402
|
+
|
|
403
|
+
@staticmethod
|
|
404
|
+
def _extract_retry_after_info(response: Dict[str, Any], error_details: Dict[str, Any]) -> None:
|
|
405
|
+
"""
|
|
406
|
+
Extract retry-after information from response.
|
|
407
|
+
|
|
408
|
+
:param Dict[str, Any] response: Response data
|
|
409
|
+
:param Dict[str, Any] error_details: Error details dictionary to update
|
|
410
|
+
"""
|
|
411
|
+
if "ITEM_LIST" not in response:
|
|
412
|
+
return
|
|
413
|
+
|
|
414
|
+
item_list = response["ITEM_LIST"]
|
|
415
|
+
if not isinstance(item_list, dict) or "ITEM" not in item_list:
|
|
416
|
+
return
|
|
417
|
+
|
|
418
|
+
item = item_list["ITEM"]
|
|
419
|
+
if not isinstance(item, dict) or "VALUE" not in item:
|
|
420
|
+
return
|
|
421
|
+
|
|
422
|
+
try:
|
|
423
|
+
error_details["retry_after"] = int(item["VALUE"])
|
|
424
|
+
except (ValueError, TypeError):
|
|
425
|
+
pass
|
|
426
|
+
|
|
427
|
+
@staticmethod
|
|
428
|
+
def _extract_other_error_formats(parsed_data: Dict[str, Any], error_details: Dict[str, Any]) -> None:
|
|
429
|
+
"""
|
|
430
|
+
Extract error details from other error formats.
|
|
431
|
+
|
|
432
|
+
:param Dict[str, Any] parsed_data: Parsed XML data
|
|
433
|
+
:param Dict[str, Any] error_details: Error details dictionary to update
|
|
434
|
+
"""
|
|
435
|
+
error_keys = ["ERROR", "FAULT", "EXCEPTION"]
|
|
436
|
+
|
|
437
|
+
for error_key in error_keys:
|
|
438
|
+
if QualysErrorHandler._extract_error_format_details(parsed_data, error_details, error_key):
|
|
439
|
+
break # Stop after finding the first error
|
|
440
|
+
|
|
441
|
+
@staticmethod
|
|
442
|
+
def _extract_error_format_details(
|
|
443
|
+
parsed_data: Dict[str, Any], error_details: Dict[str, Any], error_key: str
|
|
444
|
+
) -> bool:
|
|
445
|
+
"""
|
|
446
|
+
Extract error details for a specific error format.
|
|
447
|
+
|
|
448
|
+
:param Dict[str, Any] parsed_data: Parsed XML data
|
|
449
|
+
:param Dict[str, Any] error_details: Error details dictionary to update
|
|
450
|
+
:param str error_key: Error key to look for
|
|
451
|
+
:return: True if error was found and extracted, False otherwise
|
|
452
|
+
:rtype: bool
|
|
453
|
+
"""
|
|
454
|
+
if error_key not in parsed_data:
|
|
455
|
+
return False
|
|
456
|
+
|
|
457
|
+
error_info = parsed_data[error_key]
|
|
458
|
+
error_details["has_error"] = True
|
|
459
|
+
error_details["error_type"] = error_key
|
|
460
|
+
|
|
461
|
+
if not isinstance(error_info, dict):
|
|
462
|
+
return True
|
|
463
|
+
|
|
464
|
+
if "TEXT" in error_info:
|
|
465
|
+
error_details["error_message"] = error_info["TEXT"]
|
|
466
|
+
if "CODE" in error_info:
|
|
467
|
+
error_details["error_code"] = error_info["CODE"]
|
|
468
|
+
|
|
469
|
+
# Store additional error information
|
|
470
|
+
QualysErrorHandler._store_additional_error_info(error_info, error_details)
|
|
471
|
+
return True
|
|
472
|
+
|
|
473
|
+
@staticmethod
|
|
474
|
+
def _store_additional_error_info(error_info: Dict[str, Any], error_details: Dict[str, Any]) -> None:
|
|
475
|
+
"""
|
|
476
|
+
Store additional error information excluding standard fields.
|
|
477
|
+
|
|
478
|
+
:param Dict[str, Any] error_info: Error information from parsed data
|
|
479
|
+
:param Dict[str, Any] error_details: Error details dictionary to update
|
|
480
|
+
"""
|
|
481
|
+
excluded_keys = {"TEXT", "CODE"}
|
|
482
|
+
|
|
483
|
+
for key, value in error_info.items():
|
|
484
|
+
if key not in excluded_keys:
|
|
485
|
+
error_details["additional_info"][key] = value
|
|
486
|
+
|
|
487
|
+
@staticmethod
|
|
488
|
+
def log_error_details(error_details: Dict[str, Any]) -> None:
|
|
489
|
+
"""
|
|
490
|
+
Log detailed error information.
|
|
491
|
+
|
|
492
|
+
:param Dict[str, Any] error_details: Error details dictionary
|
|
493
|
+
"""
|
|
494
|
+
if not error_details.get("has_error"):
|
|
495
|
+
return
|
|
496
|
+
|
|
497
|
+
error_code = error_details.get("error_code")
|
|
498
|
+
error_message = error_details.get("error_message")
|
|
499
|
+
error_type = error_details.get("error_type")
|
|
500
|
+
retry_after = error_details.get("retry_after")
|
|
501
|
+
|
|
502
|
+
log_message = f"Qualys API Error ({error_type})"
|
|
503
|
+
|
|
504
|
+
if error_code:
|
|
505
|
+
code_description = QualysErrorHandler.ERROR_CODE_MESSAGES.get(error_code, "Unknown error code")
|
|
506
|
+
log_message += f" - Code {error_code}: {code_description}"
|
|
507
|
+
|
|
508
|
+
if error_message:
|
|
509
|
+
log_message += f" - Message: {error_message}"
|
|
510
|
+
|
|
511
|
+
if retry_after:
|
|
512
|
+
log_message += f" - Retry after: {retry_after} seconds"
|
|
513
|
+
|
|
514
|
+
logger.error(log_message)
|
|
515
|
+
|
|
516
|
+
# Log additional information if available
|
|
517
|
+
additional_info = error_details.get("additional_info", {})
|
|
518
|
+
if additional_info:
|
|
519
|
+
logger.debug(f"Additional error information: {additional_info}")
|