diff --git a/Packs/Base/ReleaseNotes/1_41_27.md b/Packs/Base/ReleaseNotes/1_41_27.md new file mode 100644 index 000000000000..a6f77313ab2e --- /dev/null +++ b/Packs/Base/ReleaseNotes/1_41_27.md @@ -0,0 +1,6 @@ + +#### Scripts + +##### CommonServerPython + +- Fixed an issue with the timestamp parsing logic to prevent errors when microsecond components are missing. diff --git a/Packs/Base/Scripts/CommonServerPython/CommonServerPython.py b/Packs/Base/Scripts/CommonServerPython/CommonServerPython.py index 7feb10270bc4..d131456bff71 100644 --- a/Packs/Base/Scripts/CommonServerPython/CommonServerPython.py +++ b/Packs/Base/Scripts/CommonServerPython/CommonServerPython.py @@ -8515,6 +8515,29 @@ def response_to_context(reponse_obj, user_predefiend_keys=None): return reponse_obj +def safe_strptime(date_str, datetime_format, strptime=datetime.strptime): + """ + Parses a date string to a datetime object, handling cases where the microsecond component is missing. + + :type date_str: ``str`` + :param date_str: The date string to parse (required) + + :type datetime_format: ``str`` + :param datetime_format: The format of the date string (required) + + :type strptime: ``Callable`` + :param strptime: The function to use for parsing the date string (optional) + + :return: The parsed datetime object + :rtype: ``datetime.datetime`` + """ + try: + return strptime(date_str, datetime_format) + except ValueError as e: + demisto.debug('Failed to parse date {} with format {}: {}'.format(date_str, datetime_format, str(e))) + return strptime(date_str, datetime_format.replace('.%f', '')) + + def parse_date_range(date_range, date_format=None, to_timestamp=False, timezone=0, utc=True): """ THIS FUNCTTION IS DEPRECATED - USE dateparser.parse instead @@ -8630,7 +8653,7 @@ def date_to_timestamp(date_str_or_dt, date_format='%Y-%m-%dT%H:%M:%S'): :rtype: ``int`` """ if isinstance(date_str_or_dt, STRING_OBJ_TYPES): - return int(time.mktime(time.strptime(date_str_or_dt, date_format)) * 1000) + return int(time.mktime(safe_strptime(date_str_or_dt, date_format, time.strptime)) * 1000) # otherwise datetime.datetime return int(time.mktime(date_str_or_dt.timetuple()) * 1000) @@ -9119,7 +9142,7 @@ def parse_date_string(date_string, date_format='%Y-%m-%dT%H:%M:%S'): :rtype: ``(datetime.datetime, datetime.datetime)`` """ try: - return datetime.strptime(date_string, date_format) + return safe_strptime(date_string, date_format) except ValueError as e: error_message = str(e) @@ -9166,7 +9189,7 @@ def parse_date_string(date_string, date_format='%Y-%m-%dT%H:%M:%S'): # '2022-01-23T12:34:56.123456789' to '2022-01-23T12:34:56.123456' date_string = re.sub(r'([0-9]+\.[0-9]{6})[0-9]*([Zz]|[+-]\S+?)?', '\\1\\2', date_string) - return datetime.strptime(date_string, date_format) + return safe_strptime(date_string, date_format) def build_dbot_entry(indicator, indicator_type, vendor, score, description=None, build_malicious=True): @@ -11666,10 +11689,10 @@ def get_latest_incident_created_time(incidents, created_time_field, date_format= :rtype: ``str`` """ demisto.debug('lb: Getting latest incident created time') - latest_incident_time = datetime.strptime(incidents[0][created_time_field], date_format) + latest_incident_time = safe_strptime(incidents[0][created_time_field], date_format) for incident in incidents: - incident_time = datetime.strptime(incident[created_time_field], date_format) + incident_time = safe_strptime(incident[created_time_field], date_format) if incident_time > latest_incident_time: latest_incident_time = incident_time diff --git a/Packs/Base/Scripts/CommonServerPython/CommonServerPython_test.py b/Packs/Base/Scripts/CommonServerPython/CommonServerPython_test.py index fe3c873099ad..3d9d7183b149 100644 --- a/Packs/Base/Scripts/CommonServerPython/CommonServerPython_test.py +++ b/Packs/Base/Scripts/CommonServerPython/CommonServerPython_test.py @@ -3570,6 +3570,22 @@ def test_http_client_debug_int_logger_sensitive_query_params(mocker): assert 'apikey=' in arg[0][0] +def test_safe_strptime(): + """ + Given: A string and a format that is missing the milliseconds + When: Calling safe_strptime + Then: Verify the result is as expected + """ + from CommonServerPython import safe_strptime + + assert safe_strptime("2024-01-15 17:00:00Z", "%Y-%m-%d %H:%M:%S.%fZ") == datetime(2024, 1, 15, 17, 0, 0) + assert safe_strptime("2024-01-15 17:00:00", "%Y-%m-%d %H:%M:%S") == datetime(2024, 1, 15, 17, 0, 0) + assert safe_strptime("2024-01-15 17:00:00", "%Y-%m-%d %H:%M:%S", strptime=time.strptime) == time.strptime("2024-01-15 17:00:00", "%Y-%m-%d %H:%M:%S") + + with pytest.raises(ValueError): + safe_strptime("2024-01-15 17:00:00", "%Y-%m-%dT%H:%M:%S") + + class TestParseDateRange: @staticmethod @freeze_time("2024-01-15 17:00:00 UTC") diff --git a/Packs/Base/pack_metadata.json b/Packs/Base/pack_metadata.json index cb02dfb8fce0..935b517820ab 100644 --- a/Packs/Base/pack_metadata.json +++ b/Packs/Base/pack_metadata.json @@ -2,7 +2,7 @@ "name": "Base", "description": "The base pack for Cortex XSOAR.", "support": "xsoar", - "currentVersion": "1.41.26", + "currentVersion": "1.41.27", "author": "Cortex XSOAR", "serverMinVersion": "6.0.0", "url": "https://www.paloaltonetworks.com/cortex", diff --git a/Packs/CrowdStrikeFalcon/Integrations/CrowdStrikeFalcon/CrowdStrikeFalcon.py b/Packs/CrowdStrikeFalcon/Integrations/CrowdStrikeFalcon/CrowdStrikeFalcon.py index 316912ba4f80..9d02472d9eb2 100644 --- a/Packs/CrowdStrikeFalcon/Integrations/CrowdStrikeFalcon/CrowdStrikeFalcon.py +++ b/Packs/CrowdStrikeFalcon/Integrations/CrowdStrikeFalcon/CrowdStrikeFalcon.py @@ -3743,7 +3743,7 @@ def parse_ioa_iom_incidents( incidents: list[dict[str, Any]] = [] fetched_ids: list[str] = [] # Hold the date_time_since of all fetched incidents, to acquire the largest date - fetched_dates: list[datetime] = [datetime.strptime(last_date, date_format)] + fetched_dates: list[datetime] = [safe_strptime(last_date, date_format)] for data in fetched_data: data_id = data.get(id_key, "") if data_id not in last_fetched_ids: @@ -3752,7 +3752,7 @@ def parse_ioa_iom_incidents( incident_context = to_incident_context(data, incident_type) incidents.append(incident_context) event_created = reformat_timestamp(data.get(date_key, ""), date_format) - fetched_dates.append(datetime.strptime(event_created, date_format)) + fetched_dates.append(safe_strptime(event_created, date_format)) else: demisto.debug(f"Ignoring CSPM incident with {data_id=} - was already fetched in the previous run") new_last_date = max(fetched_dates).strftime(date_format) @@ -3860,7 +3860,7 @@ def add_seconds_to_date(date: str, seconds_to_add: int, date_format: str) -> str Returns: str: The date with an increase in seconds. """ - added_datetime = datetime.strptime(date, date_format) + timedelta(seconds=seconds_to_add) + added_datetime = safe_strptime(date, date_format) + timedelta(seconds=seconds_to_add) return added_datetime.strftime(date_format) diff --git a/Packs/CrowdStrikeFalcon/ReleaseNotes/2_4_2.md b/Packs/CrowdStrikeFalcon/ReleaseNotes/2_4_2.md new file mode 100644 index 000000000000..082185779341 --- /dev/null +++ b/Packs/CrowdStrikeFalcon/ReleaseNotes/2_4_2.md @@ -0,0 +1,6 @@ + +#### Integrations + +##### CrowdStrike Falcon + +- Fixed an issue with the timestamp parsing logic to prevent errors when microsecond components are missing. diff --git a/Packs/CrowdStrikeFalcon/TestPlaybooks/playbook-CrowdStrikeFalcon-Test.yml b/Packs/CrowdStrikeFalcon/TestPlaybooks/playbook-CrowdStrikeFalcon-Test.yml index c87d4dbed06c..22f106050d92 100644 --- a/Packs/CrowdStrikeFalcon/TestPlaybooks/playbook-CrowdStrikeFalcon-Test.yml +++ b/Packs/CrowdStrikeFalcon/TestPlaybooks/playbook-CrowdStrikeFalcon-Test.yml @@ -2919,6 +2919,8 @@ tasks: scriptarguments: comment: simple: demo comment + value: + simple: test_${RandomString} id: complex: root: CrowdStrike.MLExclusion diff --git a/Packs/CrowdStrikeFalcon/pack_metadata.json b/Packs/CrowdStrikeFalcon/pack_metadata.json index 37694e417c64..c417d9dbb391 100644 --- a/Packs/CrowdStrikeFalcon/pack_metadata.json +++ b/Packs/CrowdStrikeFalcon/pack_metadata.json @@ -2,7 +2,7 @@ "name": "CrowdStrike Falcon", "description": "The CrowdStrike Falcon OAuth 2 API (formerly the Falcon Firehose API), enables fetching and resolving detections, searching devices, getting behaviors by ID, containing hosts, and lifting host containment.", "support": "xsoar", - "currentVersion": "2.4.1", + "currentVersion": "2.4.2", "author": "Cortex XSOAR", "url": "https://www.paloaltonetworks.com/cortex", "email": "",