From fc3b955979f974cbb422ae72942830e61e4a7df7 Mon Sep 17 00:00:00 2001 From: barryyosi-panw <158817412+barryyosi-panw@users.noreply.github.com> Date: Tue, 9 Jul 2024 08:17:35 +0300 Subject: [PATCH] custom close reason support (#35038) --- .../CoreIRApiModule/CoreIRApiModule.py | 8 +- .../CoreIRApiModule/CoreIRApiModule.yml | 2 +- Packs/Base/ReleaseNotes/1_34_27.md | 6 + .../CommonServerPython/CommonServerPython.py | 30 +++++ .../CommonServerPython_test.py | 74 ++++++++++-- Packs/Base/pack_metadata.json | 2 +- Packs/Core/ReleaseNotes/3_0_50.md | 11 ++ Packs/Core/pack_metadata.json | 2 +- .../Integrations/CortexXDRIR/CortexXDRIR.py | 111 ++++++++++-------- .../Integrations/CortexXDRIR/CortexXDRIR.yml | 2 +- .../CortexXDRIR/CortexXDRIR_test.py | 60 +++++++--- Packs/CortexXDR/ReleaseNotes/6_1_54.md | 6 + Packs/CortexXDR/pack_metadata.json | 2 +- Packs/ctf01/ReleaseNotes/1_0_21.md | 6 + Packs/ctf01/pack_metadata.json | 2 +- 15 files changed, 237 insertions(+), 87 deletions(-) create mode 100644 Packs/Base/ReleaseNotes/1_34_27.md create mode 100644 Packs/Core/ReleaseNotes/3_0_50.md create mode 100644 Packs/CortexXDR/ReleaseNotes/6_1_54.md create mode 100644 Packs/ctf01/ReleaseNotes/1_0_21.md diff --git a/Packs/ApiModules/Scripts/CoreIRApiModule/CoreIRApiModule.py b/Packs/ApiModules/Scripts/CoreIRApiModule/CoreIRApiModule.py index da80ebf03a6e..97868007170f 100644 --- a/Packs/ApiModules/Scripts/CoreIRApiModule/CoreIRApiModule.py +++ b/Packs/ApiModules/Scripts/CoreIRApiModule/CoreIRApiModule.py @@ -2968,6 +2968,7 @@ def resolve_xdr_close_reason(xsoar_close_reason: str) -> str: """ # Initially setting the close reason according to the default mapping. xdr_close_reason = XSOAR_RESOLVED_STATUS_TO_XDR.get(xsoar_close_reason, 'resolved_other') + # Reading custom XSOAR->XDR close-reason mapping. custom_xsoar_to_xdr_close_reason_mapping = comma_separated_mapping_to_dict( demisto.params().get("custom_xsoar_to_xdr_close_reason_mapping") @@ -2975,16 +2976,15 @@ def resolve_xdr_close_reason(xsoar_close_reason: str) -> str: # Overriding default close-reason mapping if there exists a custom one. if xsoar_close_reason in custom_xsoar_to_xdr_close_reason_mapping: - xdr_close_reason_candidate = custom_xsoar_to_xdr_close_reason_mapping[xsoar_close_reason] + xdr_close_reason_candidate = custom_xsoar_to_xdr_close_reason_mapping.get(xsoar_close_reason) # Transforming resolved close-reason into snake_case format with known prefix to match XDR status format. - demisto.debug( - f"resolve_xdr_close_reason XSOAR->XDR custom close-reason exists, using {xsoar_close_reason}={xdr_close_reason}") xdr_close_reason_candidate = "resolved_" + "_".join(xdr_close_reason_candidate.lower().split(" ")) - if xdr_close_reason_candidate not in XDR_RESOLVED_STATUS_TO_XSOAR: demisto.debug("Warning: Provided XDR close-reason does not exist. Using default XDR close-reason mapping. ") else: xdr_close_reason = xdr_close_reason_candidate + demisto.debug( + f"resolve_xdr_close_reason XSOAR->XDR custom close-reason exists, using {xsoar_close_reason}={xdr_close_reason}") else: demisto.debug(f"resolve_xdr_close_reason using default mapping {xsoar_close_reason}={xdr_close_reason}") diff --git a/Packs/ApiModules/Scripts/CoreIRApiModule/CoreIRApiModule.yml b/Packs/ApiModules/Scripts/CoreIRApiModule/CoreIRApiModule.yml index 8683ceb30d47..61f96a384a73 100644 --- a/Packs/ApiModules/Scripts/CoreIRApiModule/CoreIRApiModule.yml +++ b/Packs/ApiModules/Scripts/CoreIRApiModule/CoreIRApiModule.yml @@ -12,7 +12,7 @@ comment: Common Core IR Client, provides generic Infrastructure. scripttarget: 0 dependson: {} timeout: 0s -dockerimage: demisto/python3:3.10.14.99865 +dockerimage: demisto/python3:3.11.9.101916 fromversion: 5.0.0 tests: - No tests (auto formatted) diff --git a/Packs/Base/ReleaseNotes/1_34_27.md b/Packs/Base/ReleaseNotes/1_34_27.md new file mode 100644 index 000000000000..05d2fa1dc596 --- /dev/null +++ b/Packs/Base/ReleaseNotes/1_34_27.md @@ -0,0 +1,6 @@ + +#### Scripts + +##### CommonServerPython + +Added a functionality to read server configuration. diff --git a/Packs/Base/Scripts/CommonServerPython/CommonServerPython.py b/Packs/Base/Scripts/CommonServerPython/CommonServerPython.py index 28ff0f5c6b4d..fa8b204733d9 100644 --- a/Packs/Base/Scripts/CommonServerPython/CommonServerPython.py +++ b/Packs/Base/Scripts/CommonServerPython/CommonServerPython.py @@ -12021,6 +12021,36 @@ def is_time_sensitive(): return hasattr(demisto, 'isTimeSensitive') and demisto.isTimeSensitive() +def parse_json_string(json_string): + """ + Parse a JSON string into a Python dictionary. + + :type json_string: ``str`` + :param json_string: The JSON string to be parsed. + + :rtype: ``dict`` + :return: A Python dictionary representing the parsed JSON data. + """ + try: + data = json.loads(json_string) + return data + except json.JSONDecodeError as error: # type: ignore[attr-defined] + demisto.error("Error decoding JSON: {error}".format(error=error)) + return {} + + +def get_server_config(): + """ + Retrieves XSOAR server configuration. + + :rtype: ``dict`` + :return: The XSOAR server configuration. + """ + response = demisto.internalHttpRequest(method='GET', uri='/system/config') + body = parse_json_string(response.get('body')) + server_config = body.get('sysConf', {}) + return server_config + from DemistoClassApiModule import * # type:ignore [no-redef] # noqa:E402 diff --git a/Packs/Base/Scripts/CommonServerPython/CommonServerPython_test.py b/Packs/Base/Scripts/CommonServerPython/CommonServerPython_test.py index 53bcb7894e74..60fb78083954 100644 --- a/Packs/Base/Scripts/CommonServerPython/CommonServerPython_test.py +++ b/Packs/Base/Scripts/CommonServerPython/CommonServerPython_test.py @@ -18,18 +18,21 @@ import CommonServerPython import demistomock as demisto -from CommonServerPython import xml2json, json2xml, entryTypes, formats, tableToMarkdown, underscoreToCamelCase, \ - flattenCell, date_to_timestamp, datetime, timedelta, camelize, pascalToSpace, argToList, \ - remove_nulls_from_dictionary, is_error, get_error, hash_djb2, fileResult, is_ip_valid, get_demisto_version, \ - IntegrationLogger, parse_date_string, IS_PY3, PY_VER_MINOR, DebugLogger, b64_encode, parse_date_range, \ - return_outputs, is_filename_valid, convert_dict_values_bytes_to_str, \ - argToBoolean, ipv4Regex, ipv4cidrRegex, ipv6cidrRegex, urlRegex, ipv6Regex, domainRegex, batch, FeedIndicatorType, \ - encode_string_results, safe_load_json, remove_empty_elements, aws_table_to_markdown, is_demisto_version_ge, \ - appendContext, auto_detect_indicator_type, handle_proxy, get_demisto_version_as_str, get_x_content_info_headers, \ - url_to_clickable_markdown, WarningsHandler, DemistoException, SmartGetDict, JsonTransformer, \ - remove_duplicates_from_list_arg, DBotScoreType, DBotScoreReliability, Common, send_events_to_xsiam, ExecutionMetrics, \ - response_to_context, is_integration_command_execution, is_xsiam_or_xsoar_saas, is_xsoar, is_xsoar_on_prem, \ - is_xsoar_hosted, is_xsoar_saas, is_xsiam, send_data_to_xsiam, censor_request_logs, censor_request_logs, safe_sleep +from CommonServerPython import (xml2json, json2xml, entryTypes, formats, tableToMarkdown, underscoreToCamelCase, + flattenCell, date_to_timestamp, datetime, timedelta, camelize, pascalToSpace, argToList, + remove_nulls_from_dictionary, is_error, get_error, hash_djb2, fileResult, is_ip_valid, + get_demisto_version, IntegrationLogger, parse_date_string, IS_PY3, PY_VER_MINOR, DebugLogger, + b64_encode, parse_date_range, return_outputs, is_filename_valid, convert_dict_values_bytes_to_str, + argToBoolean, ipv4Regex, ipv4cidrRegex, ipv6cidrRegex, urlRegex, ipv6Regex, domainRegex, batch, + FeedIndicatorType, encode_string_results, safe_load_json, remove_empty_elements, + aws_table_to_markdown, is_demisto_version_ge, appendContext, auto_detect_indicator_type, + handle_proxy, get_demisto_version_as_str, get_x_content_info_headers, url_to_clickable_markdown, + WarningsHandler, DemistoException, SmartGetDict, JsonTransformer, remove_duplicates_from_list_arg, + DBotScoreType, DBotScoreReliability, Common, send_events_to_xsiam, ExecutionMetrics, + response_to_context, is_integration_command_execution, is_xsiam_or_xsoar_saas, is_xsoar, + is_xsoar_on_prem, is_xsoar_hosted, is_xsoar_saas, is_xsiam, send_data_to_xsiam, + censor_request_logs, censor_request_logs, safe_sleep, get_server_config + ) EVENTS_LOG_ERROR = \ """Error sending new events into XSIAM. @@ -9769,3 +9772,50 @@ def test_sleep_mocked_time(mocker): # Verify sleep duration based on mocked time difference assert sleep_mocker.call_count == 2 + + +def test_get_server_config(mocker): + mock_response = { + 'body': '{"sysConf":{"incident.closereasons":"CustomReason1, CustomReason 2, Foo","versn":40},"defaultMap":{}}\n', + 'headers': { + 'Content-Length': ['104'], + 'X-Xss-Protection': ['1; mode=block'], + 'X-Content-Type-Options': ['nosniff'], + 'Strict-Transport-Security': ['max-age=10886400000000000; includeSubDomains'], + 'Vary': ['Accept-Encoding'], + 'Server-Timing': ['7'], + 'Date': ['Wed, 03 Jul 2010 09:11:35 GMT'], + 'X-Frame-Options': ['DENY'], + 'Content-Type': ['application/json'] + }, + 'status': '200 OK', + 'statusCode': 200 + } + + mocker.patch.object(demisto, 'internalHttpRequest', return_value=mock_response) + server_config = get_server_config() + assert server_config == {'incident.closereasons': 'CustomReason1, CustomReason 2, Foo', 'versn': 40} + + +def test_get_server_config_fail(mocker): + mock_response = { + 'body': 'NOT A VALID JSON', + 'headers': { + 'Content-Length': ['104'], + 'X-Xss-Protection': ['1; mode=block'], + 'X-Content-Type-Options': ['nosniff'], + 'Strict-Transport-Security': ['max-age=10886400000000000; includeSubDomains'], + 'Vary': ['Accept-Encoding'], + 'Server-Timing': ['7'], + 'Date': ['Wed, 03 Jul 2010 09:11:35 GMT'], + 'X-Frame-Options': ['DENY'], + 'Content-Type': ['application/json'] + }, + 'status': '200 OK', + 'statusCode': 200 + } + + mocker.patch.object(demisto, 'internalHttpRequest', return_value=mock_response) + mocked_error = mocker.patch.object(demisto, 'error') + assert get_server_config() == {} + assert mocked_error.call_args[0][0] == 'Error decoding JSON: Expecting value: line 1 column 1 (char 0)' diff --git a/Packs/Base/pack_metadata.json b/Packs/Base/pack_metadata.json index fe70744f2b12..7809b945f12f 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.34.26", + "currentVersion": "1.34.27", "author": "Cortex XSOAR", "serverMinVersion": "6.0.0", "url": "https://www.paloaltonetworks.com/cortex", diff --git a/Packs/Core/ReleaseNotes/3_0_50.md b/Packs/Core/ReleaseNotes/3_0_50.md new file mode 100644 index 000000000000..672dcb686dc3 --- /dev/null +++ b/Packs/Core/ReleaseNotes/3_0_50.md @@ -0,0 +1,11 @@ + +#### Integrations + +##### Indicators detection + +Updated the CoreIRApiModule with support for custom XSOAR close-reasons in XSOAR-XDR close-reason mapping. + +##### Investigation & Response + +Updated the CoreIRApiModule with support for custom XSOAR close-reasons in XSOAR-XDR close-reason mapping. + diff --git a/Packs/Core/pack_metadata.json b/Packs/Core/pack_metadata.json index 4475ac546e47..938281cded61 100644 --- a/Packs/Core/pack_metadata.json +++ b/Packs/Core/pack_metadata.json @@ -2,7 +2,7 @@ "name": "Core - Investigation and Response", "description": "Automates incident response", "support": "xsoar", - "currentVersion": "3.0.49", + "currentVersion": "3.0.50", "author": "Cortex XSOAR", "url": "https://www.paloaltonetworks.com/cortex", "email": "", diff --git a/Packs/CortexXDR/Integrations/CortexXDRIR/CortexXDRIR.py b/Packs/CortexXDR/Integrations/CortexXDRIR/CortexXDRIR.py index c7daa184979d..4e8e5ee52b38 100644 --- a/Packs/CortexXDR/Integrations/CortexXDRIR/CortexXDRIR.py +++ b/Packs/CortexXDR/Integrations/CortexXDRIR/CortexXDRIR.py @@ -130,6 +130,64 @@ def filter_and_save_unseen_incident(incidents: List, limit: int, number_of_alrea return filtered_incidents +def get_xsoar_close_reasons(): + """ + Get the default XSOAR close-reasons in addition to custom close-reasons from server configuration. + """ + default_xsoar_close_reasons = list(XSOAR_RESOLVED_STATUS_TO_XDR.keys()) + custom_close_reasons = [] + try: + server_config = get_server_config() + demisto.debug(f'get_xsoar_close_reasons server-config: {str(server_config)}') + if server_config: + custom_close_reasons: list = argToList(server_config.get('incident.closereasons', '')) + except Exception as e: + demisto.error(f"Could not get server configuration: {e}") + return default_xsoar_close_reasons + custom_close_reasons + + +def validate_custom_close_reasons_mapping(mapping: str, direction: str): + """ Check validity of provided custom close-reason mappings. """ + + xdr_statuses = [status.replace("resolved_", "").replace("_", " ").title() for status in XDR_RESOLVED_STATUS_TO_XSOAR] + xsoar_statuses = get_xsoar_close_reasons() + + exception_message = ('Improper custom mapping ({direction}) provided: "{key_or_value}" is not a valid Cortex ' + '{xsoar_or_xdr} close-reason. Valid Cortex {xsoar_or_xdr} close-reasons are: {statuses}') + + def to_xdr_status(status): + return "resolved_" + "_".join(status.lower().split(" ")) + + custom_mapping = comma_separated_mapping_to_dict(mapping) + + valid_key = valid_value = True # If no mapping was provided. + + for key, value in custom_mapping.items(): + if direction == XSOAR_TO_XDR: + xdr_close_reason = to_xdr_status(value) + valid_key = key in xsoar_statuses + valid_value = xdr_close_reason in XDR_RESOLVED_STATUS_TO_XSOAR + elif direction == XDR_TO_XSOAR: + xdr_close_reason = to_xdr_status(key) + valid_key = xdr_close_reason in XDR_RESOLVED_STATUS_TO_XSOAR + valid_value = value in xsoar_statuses + + if not valid_key: + raise DemistoException( + exception_message.format(direction=direction, + key_or_value=key, + xsoar_or_xdr="XSOAR" if direction == XSOAR_TO_XDR else "XDR", + statuses=xsoar_statuses + if direction == XSOAR_TO_XDR else xdr_statuses)) + elif not valid_value: + raise DemistoException( + exception_message.format(direction=direction, + key_or_value=value, + xsoar_or_xdr="XDR" if direction == XSOAR_TO_XDR else "XSOAR", + statuses=xdr_statuses + if direction == XSOAR_TO_XDR else xsoar_statuses)) + + class Client(CoreClient): def __init__(self, base_url, proxy, verify, timeout, params=None): if not params: @@ -157,54 +215,12 @@ def test_module(self, first_fetch_time): raise # XSOAR -> XDR - self.validate_custom_mapping(mapping=self._params.get("custom_xsoar_to_xdr_close_reason_mapping"), - direction=XSOAR_TO_XDR) + validate_custom_close_reasons_mapping(mapping=self._params.get("custom_xsoar_to_xdr_close_reason_mapping"), + direction=XSOAR_TO_XDR) # XDR -> XSOAR - self.validate_custom_mapping(mapping=self._params.get("custom_xdr_to_xsoar_close_reason_mapping"), - direction=XDR_TO_XSOAR) - - def validate_custom_mapping(self, mapping: str, direction: str): - """ Check validity of provided custom close-reason mappings. """ - - xdr_statuses_to_xsoar = [status.replace("resolved_", "").replace("_", " ").title() - for status in XDR_RESOLVED_STATUS_TO_XSOAR] - xsoar_statuses_to_xdr = list(XSOAR_RESOLVED_STATUS_TO_XDR.keys()) - - exception_message = ('Improper custom mapping ({direction}) provided: "{key_or_value}" is not a valid Cortex ' - '{xsoar_or_xdr} close-reason. Valid Cortex {xsoar_or_xdr} close-reasons are: {statuses}') - - def to_xdr_status(status): - return "resolved_" + "_".join(status.lower().split(" ")) - - custom_mapping = comma_separated_mapping_to_dict(mapping) - - valid_key = valid_value = True # If no mapping was provided. - - for key, value in custom_mapping.items(): - if direction == XSOAR_TO_XDR: - xdr_close_reason = to_xdr_status(value) - valid_key = key in XSOAR_RESOLVED_STATUS_TO_XDR - valid_value = xdr_close_reason in XDR_RESOLVED_STATUS_TO_XSOAR - elif direction == XDR_TO_XSOAR: - xdr_close_reason = to_xdr_status(key) - valid_key = xdr_close_reason in XDR_RESOLVED_STATUS_TO_XSOAR - valid_value = value in XSOAR_RESOLVED_STATUS_TO_XDR - - if not valid_key: - raise DemistoException( - exception_message.format(direction=direction, - key_or_value=key, - xsoar_or_xdr="XSOAR" if direction == XSOAR_TO_XDR else "XDR", - statuses=xsoar_statuses_to_xdr - if direction == XSOAR_TO_XDR else xdr_statuses_to_xsoar)) - elif not valid_value: - raise DemistoException( - exception_message.format(direction=direction, - key_or_value=value, - xsoar_or_xdr="XDR" if direction == XSOAR_TO_XDR else "XSOAR", - statuses=xdr_statuses_to_xsoar - if direction == XSOAR_TO_XDR else xsoar_statuses_to_xdr)) + validate_custom_close_reasons_mapping(mapping=self._params.get("custom_xdr_to_xsoar_close_reason_mapping"), + direction=XDR_TO_XSOAR) def handle_fetch_starred_incidents(self, limit: int, page_number: int, request_data: dict) -> List: """ @@ -789,6 +805,7 @@ def resolve_xsoar_close_reason(xdr_close_reason: str): :param xdr_close_reason: XDR raw status/close reason e.g. 'resolved_false_positive'. :return: XSOAR close reason. """ + possible_xsoar_close_reasons = get_xsoar_close_reasons() # Check if incoming XDR close-reason has a non-default mapping to XSOAR close-reason. if demisto.params().get("custom_xdr_to_xsoar_close_reason_mapping"): @@ -802,7 +819,7 @@ def resolve_xsoar_close_reason(xdr_close_reason: str): xdr_close_reason.replace("resolved_", "").replace("_", " ").title() ) xsoar_close_reason = custom_xdr_to_xsoar_close_reason_mapping.get(title_cased_xdr_close_reason) - if xsoar_close_reason in XSOAR_RESOLVED_STATUS_TO_XDR: + if xsoar_close_reason in possible_xsoar_close_reasons: demisto.debug( f"XDR->XSOAR custom close-reason exists, using {xdr_close_reason}={xsoar_close_reason}" ) diff --git a/Packs/CortexXDR/Integrations/CortexXDRIR/CortexXDRIR.yml b/Packs/CortexXDR/Integrations/CortexXDRIR/CortexXDRIR.yml index 1190070c4cbb..bd3efda84455 100644 --- a/Packs/CortexXDR/Integrations/CortexXDRIR/CortexXDRIR.yml +++ b/Packs/CortexXDR/Integrations/CortexXDRIR/CortexXDRIR.yml @@ -3547,7 +3547,7 @@ script: Update one or more alerts with the provided arguments. Required license: Cortex XDR Prevent, Cortex XDR Pro per Endpoint, or Cortex XDR Pro per GB. name: xdr-update-alert - dockerimage: demisto/python3:3.10.14.99865 + dockerimage: demisto/python3:3.11.9.101916 isfetch: true isfetch:xpanse: false script: '' diff --git a/Packs/CortexXDR/Integrations/CortexXDRIR/CortexXDRIR_test.py b/Packs/CortexXDR/Integrations/CortexXDRIR/CortexXDRIR_test.py index d448dc09e2a8..fe455a085a96 100644 --- a/Packs/CortexXDR/Integrations/CortexXDRIR/CortexXDRIR_test.py +++ b/Packs/CortexXDR/Integrations/CortexXDRIR/CortexXDRIR_test.py @@ -7,8 +7,9 @@ import demistomock as demisto from CommonServerPython import urljoin, DemistoException -from CoreIRApiModule import XDR_RESOLVED_STATUS_TO_XSOAR -from CortexXDRIR import XSOAR_TO_XDR, XDR_TO_XSOAR +from CoreIRApiModule import XDR_RESOLVED_STATUS_TO_XSOAR, XSOAR_RESOLVED_STATUS_TO_XDR +from CortexXDRIR import XSOAR_TO_XDR, XDR_TO_XSOAR, get_xsoar_close_reasons + XDR_URL = 'https://api.xdrurl.com' ''' HELPER FUNCTIONS ''' @@ -94,7 +95,7 @@ def test_fetch_incidents_filtered_by_status(requests_mock, mocker): client = Client( base_url=f'{XDR_URL}/public_api/v1', verify=False, timeout=120, proxy=False) - incident_extra_data_under_investigation = load_test_data('./test_data/get_incident_extra_data_host_id_array.json')\ + incident_extra_data_under_investigation = load_test_data('./test_data/get_incident_extra_data_host_id_array.json') \ .get('reply', {}).get('incidents') incident_extra_data_new = load_test_data('./test_data/get_incident_extra_data_new_status.json').get('reply').get('incidents') mocker.patch.object(Client, 'get_multiple_incidents_extra_data', side_effect=[incident_extra_data_under_investigation, @@ -106,8 +107,8 @@ def test_fetch_incidents_filtered_by_status(requests_mock, mocker): next_run, incidents = fetch_incidents(client, '3 month', 'MyInstance', exclude_artifacts=False, statuses=statuses_to_fetch) assert len(incidents) == 2 - assert incidents[0]['name'] == "XDR Incident 1 - 'Local Analysis Malware' generated by XDR Agent detected on host AAAAAA "\ - "involving user Administrator" + assert incidents[0]['name'] == "XDR Incident 1 - 'Local Analysis Malware' generated by XDR Agent detected on host AAAAAA " \ + "involving user Administrator" assert incidents[1]['name'] == "XDR Incident 2 - 'Local Analysis Malware' generated by XDR Agent detected on host " \ "BBBBB involving user Administrator" @@ -216,8 +217,8 @@ def test_fetch_only_starred_incidents(self, mocker): starred=True, starred_incidents_fetch_window='3 days') assert len(incidents) == 2 - assert incidents[0]['name'] == "XDR Incident 3 - 'Local Analysis Malware' generated by XDR Agent detected"\ - " on host AAAAA involving user Administrator" + assert incidents[0]['name'] == "XDR Incident 3 - 'Local Analysis Malware' generated by XDR Agent detected" \ + " on host AAAAA involving user Administrator" last_run_obj = {'next_run': next_run, 'fetched_starred_incidents': {'3': True, '4': True} @@ -479,7 +480,7 @@ def test_get_remote_data_command_should_not_update(requests_mock, mocker): @pytest.mark.parametrize(argnames='incident_status', argvalues=XDR_RESOLVED_STATUS_TO_XSOAR.keys()) -def test_get_remote_data_command_should_close_issue(requests_mock, mocker, incident_status): +def test_get_remote_data_command_should_close_issue(capfd, requests_mock, mocker, incident_status): """ Given: - an XDR client @@ -536,7 +537,8 @@ def test_get_remote_data_command_should_close_issue(requests_mock, mocker, incid mocker.patch("CortexXDRIR.ALERTS_LIMIT_PER_INCIDENTS", new=50) mocker.patch.object(Client, 'save_modified_incidents_to_integration_context') mocker.patch.object(Client, 'get_multiple_incidents_extra_data', return_value=raw_incident['reply']) - response = get_remote_data_command(client, args) + with capfd.disabled(): + response = get_remote_data_command(client, args) sort_all_list_incident_fields(expected_modified_incident) assert response.mirrored_object == expected_modified_incident @@ -759,7 +761,7 @@ def test_get_incident_extra_data(mocker): """ from CortexXDRIR import get_incident_extra_data_command, Client - get_incident_extra_data_response = load_test_data('./test_data/get_incident_extra_data_host_id_array.json')\ + get_incident_extra_data_response = load_test_data('./test_data/get_incident_extra_data_host_id_array.json') \ .get('reply', {}).get('incidents', []) mocker.patch.object(Client, 'get_multiple_incidents_extra_data', return_value=get_incident_extra_data_response) mocker.patch("CortexXDRIR.ALERTS_LIMIT_PER_INCIDENTS", new=2) @@ -877,14 +879,14 @@ def test_test_module(capfd, custom_mapping, direction, should_raise_error): Then: - Ensure no error is raised, and return `ok` """ - from CortexXDRIR import Client + from CortexXDRIR import Client, validate_custom_close_reasons_mapping # using two different credentials object as they both fields need to be encrypted base_url = urljoin("dummy_url", '/public_api/v1') proxy = demisto.params().get('proxy') verify_cert = not demisto.params().get('insecure', False) - client = Client( + Client( base_url=base_url, proxy=proxy, verify=verify_cert, @@ -895,10 +897,10 @@ def test_test_module(capfd, custom_mapping, direction, should_raise_error): with capfd.disabled(): if should_raise_error: with pytest.raises(DemistoException): - client.validate_custom_mapping(mapping=custom_mapping, direction=direction) + validate_custom_close_reasons_mapping(mapping=custom_mapping, direction=direction) else: try: - client.validate_custom_mapping(mapping=custom_mapping, direction=direction) + validate_custom_close_reasons_mapping(mapping=custom_mapping, direction=direction) except DemistoException as e: pytest.fail(f"Unexpected exception raised for input {input}: {e}") @@ -1034,8 +1036,8 @@ def test_filter_and_save_unseen_incident_limit_test(): "creation_time": 1577836800000 }, { - "id": "2", - "creation_time": 1577836800001 + "id": "2", + "creation_time": 1577836800001 }] assert filter_and_save_unseen_incident(incident, 1, 1) == [{"id": "1", "creation_time": 1577836800000}] @@ -1285,7 +1287,7 @@ def test_sort_all_incident_data_fields_fetch_case_get_multiple_incidents_extra_d - Verify that alerts and artifacts are found. """ from CortexXDRIR import sort_incident_data, sort_all_list_incident_fields - incident_case_get_multiple_incidents_extra_data = load_test_data('./test_data/get_multiple_incidents_extra_data.json')\ + incident_case_get_multiple_incidents_extra_data = load_test_data('./test_data/get_multiple_incidents_extra_data.json') \ .get('reply').get('incidents')[0] incident_data = sort_incident_data(incident_case_get_multiple_incidents_extra_data) sort_all_list_incident_fields(incident_data) @@ -1441,7 +1443,8 @@ def test_update_alerts_in_xdr_request_called_with(): json_data={'request_data': {'alert_id_list': '1,2,3', 'update_data': - {'severity': 'High', 'status': 'resolved', 'comment': 'i am a test'} + {'severity': 'High', 'status': 'resolved', + 'comment': 'i am a test'} } }, headers={ @@ -1556,3 +1559,24 @@ def test_core_http_request_xpanse_tenant(mocker): mocker.patch.object(BaseClient, "_http_request", return_value={'reply': {"incidents": [{"incident": {"incident_id": "1"}}]}}) res = client.get_incidents(incident_id_list=['1']) assert res == [{'incident': {'incident_id': '1'}}] + + +def test_get_xsoar_close_reasons(mocker): + mock_response = { + 'body': '{"sysConf":{"incident.closereasons":"CustomReason1, CustomReason 2, Foo","versn":40},"defaultMap":{}}\n', + 'headers': { + 'Content-Length': ['104'], + 'X-Xss-Protection': ['1; mode=block'], + 'X-Content-Type-Options': ['nosniff'], + 'Strict-Transport-Security': ['max-age=10886400000000000; includeSubDomains'], + 'Vary': ['Accept-Encoding'], + 'Server-Timing': ['7'], + 'Date': ['Wed, 03 Jul 2010 09:11:35 GMT'], + 'X-Frame-Options': ['DENY'], + 'Content-Type': ['application/json'] + }, + 'status': '200 OK', + 'statusCode': 200 + } + mocker.patch.object(demisto, 'internalHttpRequest', return_value=mock_response) + assert get_xsoar_close_reasons() == list(XSOAR_RESOLVED_STATUS_TO_XDR.keys()) + ['CustomReason1', 'CustomReason 2', 'Foo'] diff --git a/Packs/CortexXDR/ReleaseNotes/6_1_54.md b/Packs/CortexXDR/ReleaseNotes/6_1_54.md new file mode 100644 index 000000000000..d40fc4231544 --- /dev/null +++ b/Packs/CortexXDR/ReleaseNotes/6_1_54.md @@ -0,0 +1,6 @@ + +#### Integrations + +##### Palo Alto Networks Cortex XDR - Investigation and Response + +Added support for custom XSOAR close-reasons in XSOAR-XDR close-reason mapping. diff --git a/Packs/CortexXDR/pack_metadata.json b/Packs/CortexXDR/pack_metadata.json index b6c8fd39b454..5f2aeb91f77a 100644 --- a/Packs/CortexXDR/pack_metadata.json +++ b/Packs/CortexXDR/pack_metadata.json @@ -2,7 +2,7 @@ "name": "Cortex XDR by Palo Alto Networks", "description": "Automates Cortex XDR incident response, and includes custom Cortex XDR incident views and layouts to aid analyst investigations.", "support": "xsoar", - "currentVersion": "6.1.53", + "currentVersion": "6.1.54", "author": "Cortex XSOAR", "url": "https://www.paloaltonetworks.com/cortex", "email": "", diff --git a/Packs/ctf01/ReleaseNotes/1_0_21.md b/Packs/ctf01/ReleaseNotes/1_0_21.md new file mode 100644 index 000000000000..7c0976bb9387 --- /dev/null +++ b/Packs/ctf01/ReleaseNotes/1_0_21.md @@ -0,0 +1,6 @@ + +#### Integrations + +##### Cortex XDR - IR CTF + +Updated the CoreIRApiModule with support for custom XSOAR close-reasons in XSOAR-XDR close-reason mapping. diff --git a/Packs/ctf01/pack_metadata.json b/Packs/ctf01/pack_metadata.json index a07445d1bb86..35c18fc22799 100644 --- a/Packs/ctf01/pack_metadata.json +++ b/Packs/ctf01/pack_metadata.json @@ -2,7 +2,7 @@ "name": "Capture The Flag - 01", "description": "XSOAR's Capture the flag (CTF)", "support": "xsoar", - "currentVersion": "1.0.20", + "currentVersion": "1.0.21", "serverMinVersion": "8.2.0", "author": "Cortex XSOAR", "url": "https://www.paloaltonetworks.com/cortex",