diff --git a/Packs/RecordedFuture/Integrations/RecordedFuture/RecordedFuture.py b/Packs/RecordedFuture/Integrations/RecordedFuture/RecordedFuture.py index 24298c5a947..5ee2b68426b 100644 --- a/Packs/RecordedFuture/Integrations/RecordedFuture/RecordedFuture.py +++ b/Packs/RecordedFuture/Integrations/RecordedFuture/RecordedFuture.py @@ -15,7 +15,7 @@ # disable insecure warnings requests.packages.urllib3.disable_warnings() # type: ignore -__version__ = '2.5.1' +__version__ = "2.5.2" # === === === === === === === === === === === === === === === @@ -39,47 +39,47 @@ def determine_hash(hash_value: str) -> str: """Determine hash type by length.""" hash_length = len(hash_value) if hash_length == 128: - return 'SHA512' + return "SHA512" elif hash_length == 64: - return 'SHA256' + return "SHA256" elif hash_length == 40: - return 'SHA1' + return "SHA1" elif hash_length == 32: - return 'MD5' + return "MD5" elif hash_length == 8: - return 'CRC32' + return "CRC32" else: - return 'CTPH' + return "CTPH" def create_indicator( entity: str, entity_type: str, score: int, - description: str = '', + description: str = "", location: Dict[str, Any] = None, ) -> Common.Indicator: """Create an Indicator object.""" demisto_params = demisto.params() if location is None: - location = dict() + location = {} thresholds = { - 'file': int(demisto_params.get('file_threshold', 65)), - 'ip': int(demisto_params.get('ip_threshold', 65)), - 'domain': int(demisto_params.get('domain_threshold', 65)), - 'url': int(demisto_params.get('url_threshold', 65)), - 'cve': int(demisto_params.get('cve_threshold', 65)), + "file": int(demisto_params.get("file_threshold", 65)), + "ip": int(demisto_params.get("ip_threshold", 65)), + "domain": int(demisto_params.get("domain_threshold", 65)), + "url": int(demisto_params.get("url_threshold", 65)), + "cve": int(demisto_params.get("cve_threshold", 65)), } dbot_score = translate_score(score, thresholds[entity_type]) dbot_description = ( - f'Score above {thresholds[entity_type]}' + f"Score above {thresholds[entity_type]}" if dbot_score == Common.DBotScore.BAD - else '' + else "" ) - dbot_vendor = 'Recorded Future v2' - if entity_type == 'ip': + dbot_vendor = "Recorded Future v2" + if entity_type == "ip": return Common.IP( entity, Common.DBotScore( @@ -88,12 +88,12 @@ def create_indicator( dbot_vendor, dbot_score, dbot_description, - reliability=demisto.params().get('integrationReliability'), + reliability=demisto.params().get("integrationReliability"), ), - asn=location.get('asn', None), - geo_country=location.get('location', dict()).get('country', None), + asn=location.get("asn", None), + geo_country=location.get("location", {}).get("country", None), ) - elif entity_type == 'domain': + elif entity_type == "domain": return Common.Domain( entity, Common.DBotScore( @@ -102,32 +102,32 @@ def create_indicator( dbot_vendor, dbot_score, dbot_description, - reliability=demisto.params().get('integrationReliability'), + reliability=demisto.params().get("integrationReliability"), ), ) - elif entity_type == 'file': + elif entity_type == "file": dbot_obj = Common.DBotScore( entity, DBotScoreType.FILE, dbot_vendor, dbot_score, dbot_description, - reliability=demisto.params().get('integrationReliability'), + reliability=demisto.params().get("integrationReliability"), ) hash_type = determine_hash(entity) - if hash_type == 'MD5': + if hash_type == "MD5": return Common.File(dbot_obj, md5=entity) - elif hash_type == 'SHA1': + elif hash_type == "SHA1": return Common.File(dbot_obj, sha1=entity) - elif hash_type == 'SHA256': + elif hash_type == "SHA256": return Common.File(dbot_obj, sha256=entity) - elif hash_type == 'SHA512': + elif hash_type == "SHA512": return Common.File(dbot_obj, sha512=entity) else: return Common.File(dbot_obj) - elif entity_type == 'cve': - return Common.CVE(entity, '', '', '', description) - elif entity_type == 'url': + elif entity_type == "cve": + return Common.CVE(entity, "", "", "", description) + elif entity_type == "url": return Common.URL( entity, Common.DBotScore( @@ -136,12 +136,12 @@ def create_indicator( dbot_vendor, dbot_score, dbot_description, - reliability=demisto.params().get('integrationReliability'), + reliability=demisto.params().get("integrationReliability"), ), ) else: raise Exception( - f'Could not create indicator for this type of entity: {entity_type}' + f"Could not create indicator for this type of entity: {entity_type}" ) @@ -154,8 +154,8 @@ class Client(BaseClient): def whoami(self) -> Dict[str, Any]: return self._http_request( - method='get', - url_suffix='info/whoami', + method="get", + url_suffix="info/whoami", timeout=60, ) @@ -164,45 +164,45 @@ def _key_extraction(self, data, keys_to_keep): def _clean_calling_context(self, calling_context): calling_context_keys_to_keep = {"args", "command", "params", "context"} - context_keys_to_keep = { - "Incidents", - "IntegrationInstance", - "ParentEntry" - } - incidents_keys_to_keep = { - "name", - "type", - "id" - } - parent_entry_keys_to_keep = { - "entryTask", - "scheduled", - "recurrent" - } + context_keys_to_keep = {"Incidents", "IntegrationInstance", "ParentEntry"} + incidents_keys_to_keep = {"name", "type", "id"} + parent_entry_keys_to_keep = {"entryTask", "scheduled", "recurrent"} if context := calling_context.get("context", None): context = self._key_extraction(context, context_keys_to_keep) if incidents := context.get("Incidents", {}): - incidents = [self._key_extraction(incident, incidents_keys_to_keep) for incident in incidents] + incidents = [ + self._key_extraction(incident, incidents_keys_to_keep) + for incident in incidents + ] context["Incidents"] = incidents if parent_entry := context.get("ParentEntry", {}): - parent_entry = self._key_extraction(parent_entry, parent_entry_keys_to_keep) + parent_entry = self._key_extraction( + parent_entry, parent_entry_keys_to_keep + ) context["ParentEntry"] = parent_entry calling_context["context"] = context - calling_context = self._key_extraction(calling_context, calling_context_keys_to_keep) + calling_context = self._key_extraction( + calling_context, calling_context_keys_to_keep + ) return calling_context def _get_writeback_data(self): if ( - demisto.params().get('collective_insights') == "On" - and demisto.callingContext - ): + demisto.params().get("collective_insights") == "On" + and demisto.args().get("collective_insights") != "off" + ) or demisto.args().get("collective_insights") == "on": + do_track = True + else: + do_track = False + + if do_track and demisto.callingContext: calling_context = copy.deepcopy(demisto.callingContext) - calling_context.get('context', dict()).pop('ExecutionContext', None) + calling_context.get("context", {}).pop("ExecutionContext", None) calling_context = self._clean_calling_context(calling_context) return calling_context @@ -211,17 +211,17 @@ def _get_writeback_data(self): def _call(self, url_suffix, **kwargs): json_data = { - 'demisto_command': demisto.command(), - 'demisto_args': demisto.args(), + "demisto_command": demisto.command(), + "demisto_args": demisto.args(), } request_kwargs = { - 'method': 'post', - 'url_suffix': url_suffix, - 'json_data': json_data, - 'timeout': 90, - 'retries': 3, - 'status_list_to_retry': STATUS_TO_RETRY, + "method": "post", + "url_suffix": url_suffix, + "json_data": json_data, + "timeout": 90, + "retries": 3, + "status_list_to_retry": STATUS_TO_RETRY, } request_kwargs.update(kwargs) @@ -229,23 +229,23 @@ def _call(self, url_suffix, **kwargs): # This need to be after 'request_kwargs.update(kwargs)'. calling_context = self._get_writeback_data() if calling_context: - request_kwargs['json_data']['callingContext'] = calling_context + request_kwargs["json_data"]["callingContext"] = calling_context try: response = self._http_request(**request_kwargs) - if isinstance(response, dict) and response.get('return_error'): + if isinstance(response, dict) and response.get("return_error"): # This will raise the Exception or call "demisto.results()" for the error and sys.exit(0). - return_error(**response['return_error']) + return_error(**response["return_error"]) except DemistoException as err: - if '404' in str(err): + if "404" in str(err): return CommandResults( - outputs_prefix='', - outputs=dict(), - raw_response=dict(), - readable_output='No results found.', - outputs_key_field='', + outputs_prefix="", + outputs={}, + raw_response={}, + readable_output="No results found.", + outputs_key_field="", ) else: raise err @@ -255,53 +255,53 @@ def _call(self, url_suffix, **kwargs): def fetch_incidents(self) -> Dict[str, Any]: """Fetch incidents.""" return self._call( - url_suffix=f'/v2/alert/fetch_incidents', + url_suffix="/v2/alert/fetch_incidents", json_data={ - 'demisto_command': demisto.command(), - 'demisto_args': demisto.args(), - 'demisto_params': demisto.params(), - 'demisto_last_run': demisto.getLastRun(), + "demisto_command": demisto.command(), + "demisto_args": demisto.args(), + "demisto_params": demisto.params(), + "demisto_last_run": demisto.getLastRun(), }, timeout=120, ) def entity_search(self) -> Dict[str, Any]: """Search for entities with entity type.""" - return self._call(url_suffix='/v2/search') + return self._call(url_suffix="/v2/search") def entity_lookup(self) -> Dict[str, Any]: """Entity lookup.""" - return self._call(url_suffix='/v2/lookup/reputation', timeout=120) + return self._call(url_suffix="/v2/lookup/reputation", timeout=120) def get_intelligence(self) -> Dict[str, Any]: """Entity enrich.""" - return self._call(url_suffix='/v2/lookup/intelligence') + return self._call(url_suffix="/v2/lookup/intelligence") def get_links(self) -> Dict[str, Any]: """Entity enrich.""" - return self._call(url_suffix='/v2/lookup/links') + return self._call(url_suffix="/v2/lookup/links") def get_single_alert(self) -> dict: """Get a single alert""" - return self._call(url_suffix='/v2/alert/lookup') + return self._call(url_suffix="/v2/alert/lookup") def get_alerts(self) -> Dict[str, Any]: """Get alerts.""" - return self._call(url_suffix='/v2/alert/search') + return self._call(url_suffix="/v2/alert/search") def get_alert_rules(self) -> Dict[str, Any]: """Get alert rules.""" - return self._call(url_suffix='/v2/alert/rule') + return self._call(url_suffix="/v2/alert/rule") def alert_set_status(self, data=None): """Update alert.""" # If data is None - we have alert_id and status in demisto.args(). return self._call( - url_suffix='/v2/alert/set_status', + url_suffix="/v2/alert/set_status", json_data={ - 'demisto_command': demisto.command(), - 'demisto_args': demisto.args(), - 'alerts_update_data': data, + "demisto_command": demisto.command(), + "demisto_args": demisto.args(), + "alerts_update_data": data, }, ) @@ -309,29 +309,29 @@ def alert_set_note(self, data=None): """Update alert.""" # If data is None - we have alert_id and note in demisto.args(). return self._call( - url_suffix='/v2/alert/set_note', + url_suffix="/v2/alert/set_note", json_data={ - 'demisto_command': demisto.command(), - 'demisto_args': demisto.args(), - 'alerts_update_data': data, + "demisto_command": demisto.command(), + "demisto_args": demisto.args(), + "alerts_update_data": data, }, ) def get_triage(self) -> Dict[str, Any]: """SOAR triage lookup.""" - return self._call(url_suffix='/v2/lookup/triage') + return self._call(url_suffix="/v2/lookup/triage") def get_threat_map(self) -> Dict[str, Any]: - return self._call(url_suffix='/v2/threat/actors') + return self._call(url_suffix="/v2/threat/actors") def get_threat_links(self) -> Dict[str, Any]: - return self._call(url_suffix='/v2/links/search') + return self._call(url_suffix="/v2/links/search") def get_detection_rules(self) -> Dict[str, Any]: - return self._call(url_suffix='/v2/detection_rules/search') + return self._call(url_suffix="/v2/detection_rules/search") def submit_detection_to_collective_insight(self) -> Dict[str, Any]: - return self._call(url_suffix='/v2/collective-insights/detections') + return self._call(url_suffix="/v2/collective-insights/detections") # === === === === === === === === === === === === === === === @@ -354,32 +354,32 @@ def _process_result_actions( # In case API returned a str - we don't want to call "response.get()" on a str object. return None # type: ignore - result_actions: Union[List[dict], None] = response.get('result_actions') + result_actions: Union[List[dict], None] = response.get("result_actions") if not result_actions: return None # type: ignore command_results: List[CommandResults] = list() for action in result_actions: - if 'create_indicator' in action: - indicator = create_indicator(**action['create_indicator']) - if 'CommandResults' in action: + if "create_indicator" in action: + indicator = create_indicator(**action["create_indicator"]) + if "CommandResults" in action: # Custom CommandResults. - command_results_kwargs = action['CommandResults'] - command_results_kwargs['indicator'] = indicator + command_results_kwargs = action["CommandResults"] + command_results_kwargs["indicator"] = indicator command_results.append(CommandResults(**command_results_kwargs)) else: # Default CommandResults after indicator creation. command_results.append( CommandResults( readable_output=tableToMarkdown( - 'New indicator was created.', indicator.to_context() + "New indicator was created.", indicator.to_context() ), indicator=indicator, ) ) - elif 'CommandResults' in action: - command_results.append(CommandResults(**action['CommandResults'])) + elif "CommandResults" in action: + command_results.append(CommandResults(**action["CommandResults"])) return command_results @@ -391,14 +391,17 @@ def fetch_incidents(self) -> None: # 404 case. return - if response.get('incidents') is not None: - incidents = response['incidents'] - demisto_last_run = response['demisto_last_run'] + if ( + response.get("incidents") is not None + and response.get("demisto_last_run") + ): + incidents = response["incidents"] + demisto_last_run = response["demisto_last_run"] demisto.incidents(incidents) demisto.setLastRun(demisto_last_run) - update_alert_status = response.pop('alerts_update_data', None) + update_alert_status = response.pop("alerts_update_data", None) if update_alert_status: self.client.alert_set_status(update_alert_status) @@ -470,6 +473,7 @@ def collective_insight_command(self) -> List[CommandResults]: response = self.client.submit_detection_to_collective_insight() return self._process_result_actions(response=response) + # === === === === === === === === === === === === === === === # === === === === === === === MAIN === === === === === === == # === === === === === === === === === === === === === === === @@ -479,17 +483,19 @@ def main() -> None: # pragma: no cover """Main method used to run actions.""" try: demisto_params = demisto.params() - base_url = demisto_params.get('server_url', '').rstrip('/') - verify_ssl = not demisto_params.get('insecure', False) - proxy = demisto_params.get('proxy', False) - api_token = demisto_params.get("token_credential", {}).get("password") or demisto_params.get("token") + base_url = demisto_params.get("server_url", "").rstrip("/") + verify_ssl = not demisto_params.get("insecure", False) + proxy = demisto_params.get("proxy", False) + api_token = demisto_params.get("token_credential", {}).get( + "password" + ) or demisto_params.get("token") if not api_token: - return_error('Please provide a valid API token') + return_error("Please provide a valid API token") headers = { - 'X-RFToken': api_token, - 'X-RF-User-Agent': ( - f'RecordedFuture.py/{__version__} ({platform.platform()}) ' - f'XSOAR/{__version__} ' + "X-RFToken": api_token, + "X-RF-User-Agent": ( + f"RecordedFuture.py/{__version__} ({platform.platform()}) " + f"XSOAR/{__version__} " f'RFClient/{__version__} (Cortex_XSOAR_{demisto.demistoVersion()["version"]})' ), } @@ -499,7 +505,7 @@ def main() -> None: # pragma: no cover command = demisto.command() actions = Actions(client) - if command == 'test-module': + if command == "test-module": # This is the call made when pressing the integration Test button. # Returning 'ok' indicates that the integration works like it suppose to and # connection to the service is successful. @@ -508,65 +514,68 @@ def main() -> None: # pragma: no cover try: client.whoami() - return_results('ok') + return_results("ok") except Exception as err: message = str(err) try: - error = json.loads(str(err).split('\n')[1]) - if 'fail' in error.get('result', dict()).get('status', ''): - message = error.get('result', dict())['message'] + error = json.loads(str(err).split("\n")[1]) + if "fail" in error.get("result", {}).get("status", ""): + message = error.get("result", {})["message"] except Exception: message = ( - 'Unknown error. Please verify that the API' - f' URL and Token are correctly configured. RAW Error: {err}' + "Unknown error. Please verify that the API" + f" URL and Token are correctly configured. RAW Error: {err}" ) - raise DemistoException(f'Failed due to - {message}') + raise DemistoException(f"Failed due to - {message}") - elif command == 'fetch-incidents': + elif command == "fetch-incidents": actions.fetch_incidents() - elif command == 'recordedfuture-malware-search': + elif command == "recordedfuture-malware-search": return_results(actions.malware_search_command()) - elif command in ['url', 'ip', 'domain', 'file', 'cve']: + elif command in ["url", "ip", "domain", "file", "cve"]: return_results(actions.lookup_command()) - elif command == 'recordedfuture-intelligence': + elif command == "recordedfuture-intelligence": return_results(actions.intelligence_command()) - elif command == 'recordedfuture-links': + elif command == "recordedfuture-links": return_results(actions.get_links_command()) - elif command == 'recordedfuture-single-alert': + elif command == "recordedfuture-single-alert": return_results(actions.get_single_alert_command()) - elif command == 'recordedfuture-alerts': + elif command == "recordedfuture-alerts": return_results(actions.get_alerts_command()) - elif command == 'recordedfuture-alert-rules': + elif command == "recordedfuture-alert-rules": return_results(actions.get_alert_rules_command()) - elif command == 'recordedfuture-alert-set-status': + elif command == "recordedfuture-alert-set-status": return_results(actions.alert_set_status_command()) - elif command == 'recordedfuture-alert-set-note': + elif command == "recordedfuture-alert-set-note": return_results(actions.alert_set_note_command()) - elif command == 'recordedfuture-threat-assessment': + elif command == "recordedfuture-threat-assessment": return_results(actions.triage_command()) - elif command == 'recordedfuture-threat-map': + elif command == "recordedfuture-threat-map": return_results(actions.threat_actors_command()) - elif command == 'recordedfuture-threat-links': + elif command == "recordedfuture-threat-links": return_results(actions.threat_links_command()) - elif command == 'recordedfuture-detection-rules': + elif command == "recordedfuture-detection-rules": return_results(actions.detection_rules_command()) - elif command == 'recordedfuture-collective-insight': + elif command == "recordedfuture-collective-insight": return_results(actions.collective_insight_command()) except Exception as e: - return_error(message=f'Failed to execute {demisto.command()} command: {str(e)}') + return_error( + message=f"Failed to execute {demisto.command()} command: {str(e)}", + error=e, + ) -if __name__ in ('__main__', '__builtin__', 'builtins'): +if __name__ in ("__main__", "__builtin__", "builtins"): main() diff --git a/Packs/RecordedFuture/Integrations/RecordedFuture/RecordedFuture.yml b/Packs/RecordedFuture/Integrations/RecordedFuture/RecordedFuture.yml index 365771117dc..b52312578c2 100644 --- a/Packs/RecordedFuture/Integrations/RecordedFuture/RecordedFuture.yml +++ b/Packs/RecordedFuture/Integrations/RecordedFuture/RecordedFuture.yml @@ -154,7 +154,7 @@ script: script: '' type: python subtype: python3 - dockerimage: demisto/python3:3.10.14.91134 + dockerimage: demisto/python3:3.11.10.111039 commands: - name: domain description: Gets a quick indicator of the risk associated with a domain. @@ -164,6 +164,14 @@ script: default: true isArray: true description: The domain for which to get the reputation. + - name: collective_insights + description: Save IOC to Collective Insights? If not specified - uses demisto param setting value. + required: false + default: false + auto: PREDEFINED + predefined: + - on + - off outputs: - contextPath: DBotScore.Indicator description: The indicator that was tested. @@ -231,6 +239,14 @@ script: default: true isArray: true description: IP address for which to get the reputation. + - name: collective_insights + description: Save IOC to Collective Insights? If not specified - uses demisto param setting value. + required: false + default: false + auto: PREDEFINED + predefined: + - on + - off outputs: - contextPath: DBotScore.Indicator description: The indicator that was tested. @@ -298,6 +314,14 @@ script: isArray: true default: true description: File hash for which to check the reputation. Can be an MD5, SHA1, SHA256, SHA512, CRC32 or CTPH. + - name: collective_insights + description: Save IOC to Collective Insights? If not specified - uses demisto param setting value. + required: false + default: false + auto: PREDEFINED + predefined: + - on + - off outputs: - contextPath: DBotScore.Indicator description: The indicator that was tested. @@ -380,6 +404,14 @@ script: default: true isArray: true description: CVE for which to get the reputation. + - name: collective_insights + description: Save IOC to Collective Insights? If not specified - uses demisto param setting value. + required: false + default: false + auto: PREDEFINED + predefined: + - on + - off outputs: - contextPath: DBotScore.Indicator description: The indicator that was tested. @@ -441,6 +473,14 @@ script: default: true isArray: true description: URL for which to get the reputation. + - name: collective_insights + description: Save IOC to Collective Insights? If not specified - uses demisto param setting value. + required: false + default: false + auto: PREDEFINED + predefined: + - on + - off outputs: - contextPath: DBotScore.Indicator description: The indicator that was tested. @@ -2866,6 +2906,7 @@ script: - contextPath: RecordedFuture.ThreatMap.links description: Recorded Future threat actor links by type. type: string + - name: recordedfuture-threat-links description: Search links. arguments: @@ -2923,6 +2964,7 @@ script: description: Recorded Future link section. - contextPath: RecordedFuture.Links.links.attributes description: Recorded Future link attributes. + - name: recordedfuture-detection-rules description: Search detection rules. arguments: @@ -3000,6 +3042,7 @@ script: - contextPath: RecordedFuture.DetectionRules.rules.file_name description: Recorded Future Detection rules file_name. type: String + - name: recordedfuture-collective-insight description: Post detection to collective insight. arguments: diff --git a/Packs/RecordedFuture/Integrations/RecordedFuture/RecordedFuture_test.py b/Packs/RecordedFuture/Integrations/RecordedFuture/RecordedFuture_test.py index 4dc53e36ca8..31ddd7bcdf6 100644 --- a/Packs/RecordedFuture/Integrations/RecordedFuture/RecordedFuture_test.py +++ b/Packs/RecordedFuture/Integrations/RecordedFuture/RecordedFuture_test.py @@ -2,12 +2,12 @@ def create_client(): import os from RecordedFuture import Client - base_url = 'https://api.recordedfuture.com/gw/xsoar/' + base_url = "https://api.recordedfuture.com/gw/xsoar/" verify_ssl = True - token = os.environ.get('RF_TOKEN') + token = os.environ.get("RF_TOKEN") headers = { - 'X-RFToken': token, - 'X-RF-User-Agent': "RecordedFuture.py/2.4 (Linux-5.13.0-1031-aws-x86_64-with) " + "X-RFToken": token, + "X-RF-User-Agent": "RecordedFuture.py/2.4 (Linux-5.13.0-1031-aws-x86_64-with) " "XSOAR/2.4 RFClient/2.4 (Cortex_XSOAR_6.5.0)", } @@ -37,28 +37,28 @@ def test_translate_score(self): def test_determine_hash(self): from RecordedFuture import determine_hash - assert determine_hash(hash_value='s' * 128) == 'SHA512' - assert determine_hash(hash_value='s' * 64) == 'SHA256' - assert determine_hash(hash_value='s' * 40) == 'SHA1' - assert determine_hash(hash_value='s' * 32) == 'MD5' - assert determine_hash(hash_value='s' * 8) == 'CRC32' - assert determine_hash(hash_value='s' * 50) == 'CTPH' - assert determine_hash(hash_value='s' * 10) == 'CTPH' - assert determine_hash(hash_value='s') == 'CTPH' + assert determine_hash(hash_value="s" * 128) == "SHA512" + assert determine_hash(hash_value="s" * 64) == "SHA256" + assert determine_hash(hash_value="s" * 40) == "SHA1" + assert determine_hash(hash_value="s" * 32) == "MD5" + assert determine_hash(hash_value="s" * 8) == "CRC32" + assert determine_hash(hash_value="s" * 50) == "CTPH" + assert determine_hash(hash_value="s" * 10) == "CTPH" + assert determine_hash(hash_value="s") == "CTPH" def test_create_indicator_ip(self, mocker): from RecordedFuture import create_indicator from CommonServerPython import Common, DBotScoreType mock_return_value = mocker.Mock() - mocker.patch('CommonServerPython.Common.IP', return_value=mock_return_value) - dbot_score_spy = mocker.spy(Common, 'DBotScore') + mocker.patch("CommonServerPython.Common.IP", return_value=mock_return_value) + dbot_score_spy = mocker.spy(Common, "DBotScore") - entity = '8.8.8.8' - entity_type = 'ip' + entity = "8.8.8.8" + entity_type = "ip" score = 45 - description = 'test_description' - location = {'asn': 'test_asn', 'location': {'country': 'test_country'}} + description = "test_description" + location = {"asn": "test_asn", "location": {"country": "test_country"}} result = create_indicator( entity=entity, @@ -73,9 +73,9 @@ def test_create_indicator_ip(self, mocker): dbot_score_spy.assert_called_once_with( entity, DBotScoreType.IP, - 'Recorded Future v2', + "Recorded Future v2", Common.DBotScore.SUSPICIOUS, - '', + "", # reliability=DBotScoreReliability.B reliability=None, ) @@ -88,8 +88,8 @@ def test_create_indicator_ip(self, mocker): # We can't assert it with `==` as the Common.IP does not implement `__eq__` method. assert mock_call.kwargs == { - 'asn': 'test_asn', - 'geo_country': 'test_country', + "asn": "test_asn", + "geo_country": "test_country", } def test_create_indicator_domain(self, mocker): @@ -97,13 +97,13 @@ def test_create_indicator_domain(self, mocker): from CommonServerPython import Common, DBotScoreType mock_return_value = mocker.Mock() - mocker.patch('CommonServerPython.Common.Domain', return_value=mock_return_value) - dbot_score_spy = mocker.spy(Common, 'DBotScore') + mocker.patch("CommonServerPython.Common.Domain", return_value=mock_return_value) + dbot_score_spy = mocker.spy(Common, "DBotScore") - entity = 'google.com' - entity_type = 'domain' + entity = "google.com" + entity_type = "domain" score = 45 - description = 'test_description' + description = "test_description" result = create_indicator( entity=entity, @@ -117,9 +117,9 @@ def test_create_indicator_domain(self, mocker): dbot_score_spy.assert_called_once_with( entity, DBotScoreType.DOMAIN, - 'Recorded Future v2', + "Recorded Future v2", Common.DBotScore.SUSPICIOUS, - '', + "", # reliability=DBotScoreReliability.B reliability=None, ) @@ -133,13 +133,13 @@ def test_create_indicator_url(self, mocker): from CommonServerPython import Common, DBotScoreType mock_return_value = mocker.Mock() - mocker.patch('CommonServerPython.Common.URL', return_value=mock_return_value) - dbot_score_spy = mocker.spy(Common, 'DBotScore') + mocker.patch("CommonServerPython.Common.URL", return_value=mock_return_value) + dbot_score_spy = mocker.spy(Common, "DBotScore") - entity = 'https://google.com' - entity_type = 'url' + entity = "https://google.com" + entity_type = "url" score = 45 - description = 'test_description' + description = "test_description" result = create_indicator( entity=entity, @@ -153,9 +153,9 @@ def test_create_indicator_url(self, mocker): dbot_score_spy.assert_called_once_with( entity, DBotScoreType.URL, - 'Recorded Future v2', + "Recorded Future v2", Common.DBotScore.SUSPICIOUS, - '', + "", # reliability=DBotScoreReliability.B reliability=None, ) @@ -169,12 +169,12 @@ def test_create_indicator_cve(self, mocker): from CommonServerPython import Common mock_return_value = mocker.Mock() - mocker.patch('CommonServerPython.Common.CVE', return_value=mock_return_value) + mocker.patch("CommonServerPython.Common.CVE", return_value=mock_return_value) - entity = 'CVE-123' - entity_type = 'cve' + entity = "CVE-123" + entity_type = "cve" score = 45 - description = 'test_description' + description = "test_description" result = create_indicator( entity=entity, @@ -187,9 +187,9 @@ def test_create_indicator_cve(self, mocker): mock_call = Common.CVE.mock_calls[0] assert mock_call.args[0] == entity - assert mock_call.args[1] == '' - assert mock_call.args[2] == '' - assert mock_call.args[3] == '' + assert mock_call.args[1] == "" + assert mock_call.args[2] == "" + assert mock_call.args[3] == "" assert mock_call.args[4] == description def test_create_indicator_file(self, mocker): @@ -197,15 +197,15 @@ def test_create_indicator_file(self, mocker): from CommonServerPython import Common, DBotScoreType mock_return_value = mocker.Mock() - mocker.patch('CommonServerPython.Common.File', return_value=mock_return_value) - dbot_score_spy = mocker.spy(Common, 'DBotScore') + mocker.patch("CommonServerPython.Common.File", return_value=mock_return_value) + dbot_score_spy = mocker.spy(Common, "DBotScore") - entity_type = 'file' + entity_type = "file" score = 45 - description = 'test_description' + description = "test_description" # MD5. - entity = 's' * 32 + entity = "s" * 32 result = create_indicator( entity=entity, entity_type=entity_type, @@ -218,19 +218,19 @@ def test_create_indicator_file(self, mocker): dbot_score_spy.assert_called_once_with( entity, DBotScoreType.FILE, - 'Recorded Future v2', + "Recorded Future v2", Common.DBotScore.SUSPICIOUS, - '', + "", # reliability=DBotScoreReliability.B reliability=None, ) mock_call = Common.File.mock_calls[0] assert mock_call.args[0].indicator == entity - assert mock_call.kwargs.get('md5') == entity + assert mock_call.kwargs.get("md5") == entity # SHA1. - entity = 's' * 40 + entity = "s" * 40 result = create_indicator( entity=entity, entity_type=entity_type, @@ -243,19 +243,19 @@ def test_create_indicator_file(self, mocker): dbot_score_spy.assert_called_with( entity, DBotScoreType.FILE, - 'Recorded Future v2', + "Recorded Future v2", Common.DBotScore.SUSPICIOUS, - '', + "", # reliability=DBotScoreReliability.B reliability=None, ) mock_call = Common.File.mock_calls[-1] assert mock_call.args[0].indicator == entity - assert mock_call.kwargs.get('sha1') == entity + assert mock_call.kwargs.get("sha1") == entity # SHA256. - entity = 's' * 64 + entity = "s" * 64 result = create_indicator( entity=entity, entity_type=entity_type, @@ -268,19 +268,19 @@ def test_create_indicator_file(self, mocker): dbot_score_spy.assert_called_with( entity, DBotScoreType.FILE, - 'Recorded Future v2', + "Recorded Future v2", Common.DBotScore.SUSPICIOUS, - '', + "", # reliability=DBotScoreReliability.B reliability=None, ) mock_call = Common.File.mock_calls[-1] assert mock_call.args[0].indicator == entity - assert mock_call.kwargs.get('sha256') == entity + assert mock_call.kwargs.get("sha256") == entity # SHA512. - entity = 's' * 128 + entity = "s" * 128 result = create_indicator( entity=entity, entity_type=entity_type, @@ -293,19 +293,19 @@ def test_create_indicator_file(self, mocker): dbot_score_spy.assert_called_with( entity, DBotScoreType.FILE, - 'Recorded Future v2', + "Recorded Future v2", Common.DBotScore.SUSPICIOUS, - '', + "", # reliability=DBotScoreReliability.B reliability=None, ) mock_call = Common.File.mock_calls[-1] assert mock_call.args[0].indicator == entity - assert mock_call.kwargs.get('sha512') == entity + assert mock_call.kwargs.get("sha512") == entity # CRC32. - entity = 's' * 20 # Length different from any previous hashes. + entity = "s" * 20 # Length different from any previous hashes. result = create_indicator( entity=entity, entity_type=entity_type, @@ -318,9 +318,9 @@ def test_create_indicator_file(self, mocker): dbot_score_spy.assert_called_with( entity, DBotScoreType.FILE, - 'Recorded Future v2', + "Recorded Future v2", Common.DBotScore.SUSPICIOUS, - '', + "", # reliability=DBotScoreReliability.B reliability=None, ) @@ -334,13 +334,13 @@ class TestRFClient: def test_whoami(self, mocker): client = create_client() - mock_http_request = mocker.patch.object(client, '_http_request') + mock_http_request = mocker.patch.object(client, "_http_request") client.whoami() mock_http_request.assert_called_once_with( - method='get', - url_suffix='info/whoami', + method="get", + url_suffix="info/whoami", timeout=60, ) @@ -353,7 +353,7 @@ def test_get_writeback_data_writeback_off(self, mocker): client = create_client() - mocker.patch.object(demisto, 'params', return_value={'writeback': False}) + mocker.patch.object(demisto, "params", return_value={"writeback": False}) assert client._get_writeback_data() is None def test_get_writeback_data_writeback_on(self, mocker): @@ -365,16 +365,14 @@ def test_get_writeback_data_writeback_on(self, mocker): client = create_client() mocker.patch.object( - demisto, 'params', return_value={'collective_insights': 'On'} + demisto, "params", return_value={"collective_insights": "On"} ) demisto.callingContext = { - 'context': {'ExecutionContext': 'to be removed', 'Incidents': []} + "context": {"ExecutionContext": "to be removed", "Incidents": []} } - assert client._get_writeback_data() == { - 'context': {'Incidents': []} - } + assert client._get_writeback_data() == {"context": {"Incidents": []}} # def test_call_writeback_on(self, mocker): @@ -388,48 +386,48 @@ def test_call_writeback_on(self, mocker): STATUS_TO_RETRY = [500, 501, 502, 503, 504] # This is needed for CommonServerPython module to not add demisto.params() into callingContext. - os.environ['COMMON_SERVER_NO_AUTO_PARAMS_REMOVE_NULLS'] = 'True' + os.environ["COMMON_SERVER_NO_AUTO_PARAMS_REMOVE_NULLS"] = "True" # Mock demisto command and args. - mock_command_name = 'command_name' - mock_command_args = {'arg1': 'arg1_value', 'arg2': 'arg2_value'} + mock_command_name = "command_name" + mock_command_args = {"arg1": "arg1_value", "arg2": "arg2_value"} - mocker.patch.object(demisto, 'command', return_value=mock_command_name) - mocker.patch.object(demisto, 'args', return_value=mock_command_args) + mocker.patch.object(demisto, "command", return_value=mock_command_name) + mocker.patch.object(demisto, "args", return_value=mock_command_args) # Mock data for writeback. mocker.patch.object( demisto, - 'params', + "params", return_value={ - 'collective_insights': "On", + "collective_insights": "On", }, ) mock_calling_context = { - 'context': {'ExecutionContext': 'to be removed', 'Incidents': []}, - 'other': 'data', + "context": {"ExecutionContext": "to be removed", "Incidents": []}, + "other": "data", } demisto.callingContext = mock_calling_context client = create_client() - mock_http_request = mocker.patch.object(client, '_http_request') + mock_http_request = mocker.patch.object(client, "_http_request") - mock_url_suffix = 'mock_url_suffix' + mock_url_suffix = "mock_url_suffix" client._call(url_suffix=mock_url_suffix) json_data = { - 'demisto_command': mock_command_name, - 'demisto_args': mock_command_args, - 'callingContext': { - 'context': {'Incidents': []}, + "demisto_command": mock_command_name, + "demisto_args": mock_command_args, + "callingContext": { + "context": {"Incidents": []}, }, } mock_http_request.assert_called_once_with( - method='post', + method="post", url_suffix=mock_url_suffix, json_data=json_data, timeout=90, @@ -448,45 +446,45 @@ def test_call_writeback_off(self, mocker): STATUS_TO_RETRY = [500, 501, 502, 503, 504] # This is needed for CommonServerPython module to not add demisto.params() into callingContext. - os.environ['COMMON_SERVER_NO_AUTO_PARAMS_REMOVE_NULLS'] = 'True' + os.environ["COMMON_SERVER_NO_AUTO_PARAMS_REMOVE_NULLS"] = "True" # Mock demisto command and args. - mock_command_name = 'command_name' - mock_command_args = {'arg1': 'arg1_value', 'arg2': 'arg2_value'} + mock_command_name = "command_name" + mock_command_args = {"arg1": "arg1_value", "arg2": "arg2_value"} - mocker.patch.object(demisto, 'command', return_value=mock_command_name) - mocker.patch.object(demisto, 'args', return_value=mock_command_args) + mocker.patch.object(demisto, "command", return_value=mock_command_name) + mocker.patch.object(demisto, "args", return_value=mock_command_args) # Mock data for writeback. mocker.patch.object( demisto, - 'params', + "params", return_value={ - 'collective_insights': "Off", + "collective_insights": "Off", }, ) mock_calling_context = { - 'context': {'ExecutionContext': 'to be removed', 'other': 'data'}, - 'other': 'data', + "context": {"ExecutionContext": "to be removed", "other": "data"}, + "other": "data", } demisto.callingContext = mock_calling_context client = create_client() - mock_http_request = mocker.patch.object(client, '_http_request') + mock_http_request = mocker.patch.object(client, "_http_request") - mock_url_suffix = 'mock_url_suffix' + mock_url_suffix = "mock_url_suffix" client._call(url_suffix=mock_url_suffix) json_data = { - 'demisto_command': mock_command_name, - 'demisto_args': mock_command_args, + "demisto_command": mock_command_name, + "demisto_args": mock_command_args, } mock_http_request.assert_called_once_with( - method='post', + method="post", url_suffix=mock_url_suffix, json_data=json_data, timeout=90, @@ -505,30 +503,30 @@ def test_call_with_kwargs(self, mocker): STATUS_TO_RETRY = [500, 501, 502, 503, 504] # This is needed for CommonServerPython module to not add demisto.params() into callingContext. - os.environ['COMMON_SERVER_NO_AUTO_PARAMS_REMOVE_NULLS'] = 'True' + os.environ["COMMON_SERVER_NO_AUTO_PARAMS_REMOVE_NULLS"] = "True" # Mock demisto command and args. - mock_command_name = 'command_name' - mock_command_args = {'arg1': 'arg1_value', 'arg2': 'arg2_value'} + mock_command_name = "command_name" + mock_command_args = {"arg1": "arg1_value", "arg2": "arg2_value"} - mocker.patch.object(demisto, 'command', return_value=mock_command_name) - mocker.patch.object(demisto, 'args', return_value=mock_command_args) + mocker.patch.object(demisto, "command", return_value=mock_command_name) + mocker.patch.object(demisto, "args", return_value=mock_command_args) client = create_client() - mock_http_request = mocker.patch.object(client, '_http_request') + mock_http_request = mocker.patch.object(client, "_http_request") - mock_url_suffix = 'mock_url_suffix' + mock_url_suffix = "mock_url_suffix" client._call(url_suffix=mock_url_suffix, timeout=120, any_other_kwarg=True) json_data = { - 'demisto_command': mock_command_name, - 'demisto_args': mock_command_args, + "demisto_command": mock_command_name, + "demisto_args": mock_command_args, } mock_http_request.assert_called_once_with( - method='post', + method="post", url_suffix=mock_url_suffix, json_data=json_data, timeout=120, @@ -546,22 +544,22 @@ def test_call_returns_response(self, mocker): import demistomock as demisto # This is needed for CommonServerPython module to not add demisto.params() into callingContext. - os.environ['COMMON_SERVER_NO_AUTO_PARAMS_REMOVE_NULLS'] = 'True' + os.environ["COMMON_SERVER_NO_AUTO_PARAMS_REMOVE_NULLS"] = "True" # Mock demisto command and args. - mock_command_name = 'command_name' - mock_command_args = {'arg1': 'arg1_value', 'arg2': 'arg2_value'} + mock_command_name = "command_name" + mock_command_args = {"arg1": "arg1_value", "arg2": "arg2_value"} - mocker.patch.object(demisto, 'command', return_value=mock_command_name) - mocker.patch.object(demisto, 'args', return_value=mock_command_args) + mocker.patch.object(demisto, "command", return_value=mock_command_name) + mocker.patch.object(demisto, "args", return_value=mock_command_args) client = create_client() - mock_response = {'response': {'data': 'mock data'}} + mock_response = {"response": {"data": "mock data"}} - mocker.patch.object(client, '_http_request', return_value=mock_response) + mocker.patch.object(client, "_http_request", return_value=mock_response) - mock_url_suffix = 'mock_url_suffix' + mock_url_suffix = "mock_url_suffix" response = client._call(url_suffix=mock_url_suffix) assert response == mock_response @@ -577,36 +575,36 @@ def test_call_response_processing_return_error(self, mocker): STATUS_TO_RETRY = [500, 501, 502, 503, 504] # This is needed for CommonServerPython module to not add demisto.params() into callingContext. - os.environ['COMMON_SERVER_NO_AUTO_PARAMS_REMOVE_NULLS'] = 'True' + os.environ["COMMON_SERVER_NO_AUTO_PARAMS_REMOVE_NULLS"] = "True" # Mock demisto command and args. - mock_command_name = 'command_name' - mock_command_args = {'arg1': 'arg1_value', 'arg2': 'arg2_value'} + mock_command_name = "command_name" + mock_command_args = {"arg1": "arg1_value", "arg2": "arg2_value"} - mocker.patch.object(demisto, 'command', return_value=mock_command_name) - mocker.patch.object(demisto, 'args', return_value=mock_command_args) + mocker.patch.object(demisto, "command", return_value=mock_command_name) + mocker.patch.object(demisto, "args", return_value=mock_command_args) - mock_return_error = mocker.patch('RecordedFuture.return_error') + mock_return_error = mocker.patch("RecordedFuture.return_error") client = create_client() mock_http_request = mocker.patch.object( client, - '_http_request', - return_value={'return_error': {'message': 'mock error'}}, + "_http_request", + return_value={"return_error": {"message": "mock error"}}, ) - mock_url_suffix = 'mock_url_suffix' + mock_url_suffix = "mock_url_suffix" client._call(url_suffix=mock_url_suffix) json_data = { - 'demisto_command': mock_command_name, - 'demisto_args': mock_command_args, + "demisto_command": mock_command_name, + "demisto_args": mock_command_args, } mock_http_request.assert_called_once_with( - method='post', + method="post", url_suffix=mock_url_suffix, json_data=json_data, timeout=90, @@ -614,7 +612,7 @@ def test_call_response_processing_return_error(self, mocker): status_list_to_retry=STATUS_TO_RETRY, ) - mock_return_error.assert_called_once_with(message='mock error') + mock_return_error.assert_called_once_with(message="mock error") def test_call_response_processing_404(self, mocker): """ @@ -628,39 +626,39 @@ def test_call_response_processing_404(self, mocker): STATUS_TO_RETRY = [500, 501, 502, 503, 504] # This is needed for CommonServerPython module to not add demisto.params() into callingContext. - os.environ['COMMON_SERVER_NO_AUTO_PARAMS_REMOVE_NULLS'] = 'True' + os.environ["COMMON_SERVER_NO_AUTO_PARAMS_REMOVE_NULLS"] = "True" # Mock demisto command and args. - mock_command_name = 'command_name' - mock_command_args = {'arg1': 'arg1_value', 'arg2': 'arg2_value'} + mock_command_name = "command_name" + mock_command_args = {"arg1": "arg1_value", "arg2": "arg2_value"} - mocker.patch.object(demisto, 'command', return_value=mock_command_name) - mocker.patch.object(demisto, 'args', return_value=mock_command_args) + mocker.patch.object(demisto, "command", return_value=mock_command_name) + mocker.patch.object(demisto, "args", return_value=mock_command_args) - mocker.patch('RecordedFuture.return_error') + mocker.patch("RecordedFuture.return_error") client = create_client() def mock_http_request_method(*args, **kwargs): # Imitate how CommonServerPython handles bad responses (when status code not in ok_codes, # or if ok_codes=None - it uses requests.Response.ok to check whether response is good). - raise DemistoException('404') + raise DemistoException("404") - mocker.patch.object(client, '_http_request', mock_http_request_method) + mocker.patch.object(client, "_http_request", mock_http_request_method) - spy_http_request = mocker.spy(client, '_http_request') + spy_http_request = mocker.spy(client, "_http_request") - mock_url_suffix = 'mock_url_suffix' + mock_url_suffix = "mock_url_suffix" result = client._call(url_suffix=mock_url_suffix) json_data = { - 'demisto_command': mock_command_name, - 'demisto_args': mock_command_args, + "demisto_command": mock_command_name, + "demisto_args": mock_command_args, } spy_http_request.assert_called_once_with( - method='post', + method="post", url_suffix=mock_url_suffix, json_data=json_data, timeout=90, @@ -670,49 +668,49 @@ def mock_http_request_method(*args, **kwargs): assert isinstance(result, CommandResults) - assert result.outputs_prefix == '' - assert result.outputs_key_field == '' + assert result.outputs_prefix == "" + assert result.outputs_key_field == "" assert result.outputs == {} assert result.raw_response == {} - assert result.readable_output == 'No results found.' + assert result.readable_output == "No results found." def test_fetch_incidents(self, mocker): import os import demistomock as demisto # This is needed for CommonServerPython module to not add demisto.params() into callingContext. - os.environ['COMMON_SERVER_NO_AUTO_PARAMS_REMOVE_NULLS'] = 'True' + os.environ["COMMON_SERVER_NO_AUTO_PARAMS_REMOVE_NULLS"] = "True" # Mock demisto command and args. - mock_command_name = 'command_name' - mock_command_args = {'arg1': 'arg1_value', 'arg2': 'arg2_value'} - mock_params = {'param1': 'param1 value'} + mock_command_name = "command_name" + mock_command_args = {"arg1": "arg1_value", "arg2": "arg2_value"} + mock_params = {"param1": "param1 value"} - mocker.patch.object(demisto, 'command', return_value=mock_command_name) - mocker.patch.object(demisto, 'args', return_value=mock_command_args) - mocker.patch.object(demisto, 'params', return_value=mock_params) + mocker.patch.object(demisto, "command", return_value=mock_command_name) + mocker.patch.object(demisto, "args", return_value=mock_command_args) + mocker.patch.object(demisto, "params", return_value=mock_params) mock_last_run_dict = {"lastRun": "2022-08-31T12:12:20+00:00"} - mocker.patch.object(demisto, 'getLastRun', return_value=mock_last_run_dict) + mocker.patch.object(demisto, "getLastRun", return_value=mock_last_run_dict) client = create_client() - mock_call_response = {'response': {'data': 'mock response'}} + mock_call_response = {"response": {"data": "mock response"}} mock_call = mocker.patch.object( - client, '_call', return_value=mock_call_response + client, "_call", return_value=mock_call_response ) response = client.fetch_incidents() mock_call.assert_called_once_with( json_data={ - 'demisto_command': mock_command_name, - 'demisto_args': mock_command_args, - 'demisto_last_run': mock_last_run_dict, - 'demisto_params': mock_params, + "demisto_command": mock_command_name, + "demisto_args": mock_command_args, + "demisto_last_run": mock_last_run_dict, + "demisto_params": mock_params, }, timeout=120, - url_suffix='/v2/alert/fetch_incidents', + url_suffix="/v2/alert/fetch_incidents", ) assert response == mock_call_response @@ -722,25 +720,25 @@ def test_entity_search(self, mocker): import demistomock as demisto # This is needed for CommonServerPython module to not add demisto.params() into callingContext. - os.environ['COMMON_SERVER_NO_AUTO_PARAMS_REMOVE_NULLS'] = 'True' + os.environ["COMMON_SERVER_NO_AUTO_PARAMS_REMOVE_NULLS"] = "True" # Mock demisto command and args. - mock_command_name = 'command_name' - mock_command_args = {'arg1': 'arg1_value', 'arg2': 'arg2_value'} + mock_command_name = "command_name" + mock_command_args = {"arg1": "arg1_value", "arg2": "arg2_value"} - mocker.patch.object(demisto, 'command', return_value=mock_command_name) - mocker.patch.object(demisto, 'args', return_value=mock_command_args) + mocker.patch.object(demisto, "command", return_value=mock_command_name) + mocker.patch.object(demisto, "args", return_value=mock_command_args) client = create_client() - mock_call_response = {'response': {'data': 'mock response'}} + mock_call_response = {"response": {"data": "mock response"}} mock_call = mocker.patch.object( - client, '_call', return_value=mock_call_response + client, "_call", return_value=mock_call_response ) response = client.entity_search() - mock_call.assert_called_once_with(url_suffix='/v2/search') + mock_call.assert_called_once_with(url_suffix="/v2/search") assert response == mock_call_response @@ -749,25 +747,25 @@ def test_get_intelligence(self, mocker): import demistomock as demisto # This is needed for CommonServerPython module to not add demisto.params() into callingContext. - os.environ['COMMON_SERVER_NO_AUTO_PARAMS_REMOVE_NULLS'] = 'True' + os.environ["COMMON_SERVER_NO_AUTO_PARAMS_REMOVE_NULLS"] = "True" # Mock demisto command and args. - mock_command_name = 'command_name' - mock_command_args = {'arg1': 'arg1_value', 'arg2': 'arg2_value'} + mock_command_name = "command_name" + mock_command_args = {"arg1": "arg1_value", "arg2": "arg2_value"} - mocker.patch.object(demisto, 'command', return_value=mock_command_name) - mocker.patch.object(demisto, 'args', return_value=mock_command_args) + mocker.patch.object(demisto, "command", return_value=mock_command_name) + mocker.patch.object(demisto, "args", return_value=mock_command_args) client = create_client() - mock_call_response = {'response': {'data': 'mock response'}} + mock_call_response = {"response": {"data": "mock response"}} mock_call = mocker.patch.object( - client, '_call', return_value=mock_call_response + client, "_call", return_value=mock_call_response ) response = client.get_intelligence() - mock_call.assert_called_once_with(url_suffix='/v2/lookup/intelligence') + mock_call.assert_called_once_with(url_suffix="/v2/lookup/intelligence") assert response == mock_call_response @@ -776,25 +774,25 @@ def test_get_links(self, mocker): import demistomock as demisto # This is needed for CommonServerPython module to not add demisto.params() into callingContext. - os.environ['COMMON_SERVER_NO_AUTO_PARAMS_REMOVE_NULLS'] = 'True' + os.environ["COMMON_SERVER_NO_AUTO_PARAMS_REMOVE_NULLS"] = "True" # Mock demisto command and args. - mock_command_name = 'command_name' - mock_command_args = {'arg1': 'arg1_value', 'arg2': 'arg2_value'} + mock_command_name = "command_name" + mock_command_args = {"arg1": "arg1_value", "arg2": "arg2_value"} - mocker.patch.object(demisto, 'command', return_value=mock_command_name) - mocker.patch.object(demisto, 'args', return_value=mock_command_args) + mocker.patch.object(demisto, "command", return_value=mock_command_name) + mocker.patch.object(demisto, "args", return_value=mock_command_args) client = create_client() - mock_call_response = {'response': {'data': 'mock response'}} + mock_call_response = {"response": {"data": "mock response"}} mock_call = mocker.patch.object( - client, '_call', return_value=mock_call_response + client, "_call", return_value=mock_call_response ) response = client.get_links() - mock_call.assert_called_once_with(url_suffix='/v2/lookup/links') + mock_call.assert_called_once_with(url_suffix="/v2/lookup/links") assert response == mock_call_response @@ -803,25 +801,25 @@ def test_get_single_alert(self, mocker): import demistomock as demisto # This is needed for CommonServerPython module to not add demisto.params() into callingContext. - os.environ['COMMON_SERVER_NO_AUTO_PARAMS_REMOVE_NULLS'] = 'True' + os.environ["COMMON_SERVER_NO_AUTO_PARAMS_REMOVE_NULLS"] = "True" # Mock demisto command and args. - mock_command_name = 'command_name' - mock_command_args = {'arg1': 'arg1_value', 'arg2': 'arg2_value'} + mock_command_name = "command_name" + mock_command_args = {"arg1": "arg1_value", "arg2": "arg2_value"} - mocker.patch.object(demisto, 'command', return_value=mock_command_name) - mocker.patch.object(demisto, 'args', return_value=mock_command_args) + mocker.patch.object(demisto, "command", return_value=mock_command_name) + mocker.patch.object(demisto, "args", return_value=mock_command_args) client = create_client() - mock_call_response = {'response': {'data': 'mock response'}} + mock_call_response = {"response": {"data": "mock response"}} mock_call = mocker.patch.object( - client, '_call', return_value=mock_call_response + client, "_call", return_value=mock_call_response ) response = client.get_single_alert() - mock_call.assert_called_once_with(url_suffix='/v2/alert/lookup') + mock_call.assert_called_once_with(url_suffix="/v2/alert/lookup") assert response == mock_call_response @@ -830,25 +828,25 @@ def test_get_alerts(self, mocker): import demistomock as demisto # This is needed for CommonServerPython module to not add demisto.params() into callingContext. - os.environ['COMMON_SERVER_NO_AUTO_PARAMS_REMOVE_NULLS'] = 'True' + os.environ["COMMON_SERVER_NO_AUTO_PARAMS_REMOVE_NULLS"] = "True" # Mock demisto command and args. - mock_command_name = 'command_name' - mock_command_args = {'arg1': 'arg1_value', 'arg2': 'arg2_value'} + mock_command_name = "command_name" + mock_command_args = {"arg1": "arg1_value", "arg2": "arg2_value"} - mocker.patch.object(demisto, 'command', return_value=mock_command_name) - mocker.patch.object(demisto, 'args', return_value=mock_command_args) + mocker.patch.object(demisto, "command", return_value=mock_command_name) + mocker.patch.object(demisto, "args", return_value=mock_command_args) client = create_client() - mock_call_response = {'response': {'data': 'mock response'}} + mock_call_response = {"response": {"data": "mock response"}} mock_call = mocker.patch.object( - client, '_call', return_value=mock_call_response + client, "_call", return_value=mock_call_response ) response = client.get_alerts() - mock_call.assert_called_once_with(url_suffix='/v2/alert/search') + mock_call.assert_called_once_with(url_suffix="/v2/alert/search") assert response == mock_call_response @@ -857,25 +855,25 @@ def test_get_alert_rules(self, mocker): import demistomock as demisto # This is needed for CommonServerPython module to not add demisto.params() into callingContext. - os.environ['COMMON_SERVER_NO_AUTO_PARAMS_REMOVE_NULLS'] = 'True' + os.environ["COMMON_SERVER_NO_AUTO_PARAMS_REMOVE_NULLS"] = "True" # Mock demisto command and args. - mock_command_name = 'command_name' - mock_command_args = {'arg1': 'arg1_value', 'arg2': 'arg2_value'} + mock_command_name = "command_name" + mock_command_args = {"arg1": "arg1_value", "arg2": "arg2_value"} - mocker.patch.object(demisto, 'command', return_value=mock_command_name) - mocker.patch.object(demisto, 'args', return_value=mock_command_args) + mocker.patch.object(demisto, "command", return_value=mock_command_name) + mocker.patch.object(demisto, "args", return_value=mock_command_args) client = create_client() - mock_call_response = {'response': {'data': 'mock response'}} + mock_call_response = {"response": {"data": "mock response"}} mock_call = mocker.patch.object( - client, '_call', return_value=mock_call_response + client, "_call", return_value=mock_call_response ) response = client.get_alert_rules() - mock_call.assert_called_once_with(url_suffix='/v2/alert/rule') + mock_call.assert_called_once_with(url_suffix="/v2/alert/rule") assert response == mock_call_response @@ -884,31 +882,31 @@ def test_alert_set_status(self, mocker): import demistomock as demisto # This is needed for CommonServerPython module to not add demisto.params() into callingContext. - os.environ['COMMON_SERVER_NO_AUTO_PARAMS_REMOVE_NULLS'] = 'True' + os.environ["COMMON_SERVER_NO_AUTO_PARAMS_REMOVE_NULLS"] = "True" # Mock demisto command and args. - mock_command_name = 'command_name' - mock_command_args = {'arg1': 'arg1_value', 'arg2': 'arg2_value'} + mock_command_name = "command_name" + mock_command_args = {"arg1": "arg1_value", "arg2": "arg2_value"} - mocker.patch.object(demisto, 'command', return_value=mock_command_name) - mocker.patch.object(demisto, 'args', return_value=mock_command_args) + mocker.patch.object(demisto, "command", return_value=mock_command_name) + mocker.patch.object(demisto, "args", return_value=mock_command_args) client = create_client() - mock_call_response = {'response': {'data': 'mock response'}} + mock_call_response = {"response": {"data": "mock response"}} mock_call = mocker.patch.object( - client, '_call', return_value=mock_call_response + client, "_call", return_value=mock_call_response ) - alert_data = {'mock': 'data'} + alert_data = {"mock": "data"} response = client.alert_set_status(alert_data) mock_call.assert_called_once_with( - url_suffix='/v2/alert/set_status', + url_suffix="/v2/alert/set_status", json_data={ - 'demisto_command': mock_command_name, - 'demisto_args': mock_command_args, - 'alerts_update_data': alert_data, + "demisto_command": mock_command_name, + "demisto_args": mock_command_args, + "alerts_update_data": alert_data, }, ) @@ -917,11 +915,11 @@ def test_alert_set_status(self, mocker): response = client.alert_set_status() mock_call.assert_called_with( - url_suffix='/v2/alert/set_status', + url_suffix="/v2/alert/set_status", json_data={ - 'demisto_command': mock_command_name, - 'demisto_args': mock_command_args, - 'alerts_update_data': None, + "demisto_command": mock_command_name, + "demisto_args": mock_command_args, + "alerts_update_data": None, }, ) @@ -932,31 +930,31 @@ def test_alert_set_note(self, mocker): import demistomock as demisto # This is needed for CommonServerPython module to not add demisto.params() into callingContext. - os.environ['COMMON_SERVER_NO_AUTO_PARAMS_REMOVE_NULLS'] = 'True' + os.environ["COMMON_SERVER_NO_AUTO_PARAMS_REMOVE_NULLS"] = "True" # Mock demisto command and args. - mock_command_name = 'command_name' - mock_command_args = {'arg1': 'arg1_value', 'arg2': 'arg2_value'} + mock_command_name = "command_name" + mock_command_args = {"arg1": "arg1_value", "arg2": "arg2_value"} - mocker.patch.object(demisto, 'command', return_value=mock_command_name) - mocker.patch.object(demisto, 'args', return_value=mock_command_args) + mocker.patch.object(demisto, "command", return_value=mock_command_name) + mocker.patch.object(demisto, "args", return_value=mock_command_args) client = create_client() - mock_call_response = {'response': {'data': 'mock response'}} + mock_call_response = {"response": {"data": "mock response"}} mock_call = mocker.patch.object( - client, '_call', return_value=mock_call_response + client, "_call", return_value=mock_call_response ) - alert_data = {'mock': 'data'} + alert_data = {"mock": "data"} response = client.alert_set_note(alert_data) mock_call.assert_called_once_with( - url_suffix='/v2/alert/set_note', + url_suffix="/v2/alert/set_note", json_data={ - 'demisto_command': mock_command_name, - 'demisto_args': mock_command_args, - 'alerts_update_data': alert_data, + "demisto_command": mock_command_name, + "demisto_args": mock_command_args, + "alerts_update_data": alert_data, }, ) @@ -965,11 +963,11 @@ def test_alert_set_note(self, mocker): response = client.alert_set_note() mock_call.assert_called_with( - url_suffix='/v2/alert/set_note', + url_suffix="/v2/alert/set_note", json_data={ - 'demisto_command': mock_command_name, - 'demisto_args': mock_command_args, - 'alerts_update_data': None, + "demisto_command": mock_command_name, + "demisto_args": mock_command_args, + "alerts_update_data": None, }, ) @@ -980,25 +978,25 @@ def test_get_triage(self, mocker): import demistomock as demisto # This is needed for CommonServerPython module to not add demisto.params() into callingContext. - os.environ['COMMON_SERVER_NO_AUTO_PARAMS_REMOVE_NULLS'] = 'True' + os.environ["COMMON_SERVER_NO_AUTO_PARAMS_REMOVE_NULLS"] = "True" # Mock demisto command and args. - mock_command_name = 'command_name' - mock_command_args = {'arg1': 'arg1_value', 'arg2': 'arg2_value'} + mock_command_name = "command_name" + mock_command_args = {"arg1": "arg1_value", "arg2": "arg2_value"} - mocker.patch.object(demisto, 'command', return_value=mock_command_name) - mocker.patch.object(demisto, 'args', return_value=mock_command_args) + mocker.patch.object(demisto, "command", return_value=mock_command_name) + mocker.patch.object(demisto, "args", return_value=mock_command_args) client = create_client() - mock_call_response = {'response': {'data': 'mock response'}} + mock_call_response = {"response": {"data": "mock response"}} mock_call = mocker.patch.object( - client, '_call', return_value=mock_call_response + client, "_call", return_value=mock_call_response ) response = client.get_triage() - mock_call.assert_called_once_with(url_suffix='/v2/lookup/triage') + mock_call.assert_called_once_with(url_suffix="/v2/lookup/triage") assert response == mock_call_response @@ -1007,25 +1005,25 @@ def test_get_threat_map(self, mocker): import demistomock as demisto # This is needed for CommonServerPython module to not add demisto.params() into callingContext. - os.environ['COMMON_SERVER_NO_AUTO_PARAMS_REMOVE_NULLS'] = 'True' + os.environ["COMMON_SERVER_NO_AUTO_PARAMS_REMOVE_NULLS"] = "True" # Mock demisto command and args. - mock_command_name = 'threat_map' - mock_command_args = {'arg1': 'arg1_value', 'arg2': 'arg2_value'} + mock_command_name = "threat_map" + mock_command_args = {"arg1": "arg1_value", "arg2": "arg2_value"} - mocker.patch.object(demisto, 'command', return_value=mock_command_name) - mocker.patch.object(demisto, 'args', return_value=mock_command_args) + mocker.patch.object(demisto, "command", return_value=mock_command_name) + mocker.patch.object(demisto, "args", return_value=mock_command_args) client = create_client() - mock_call_response = {'response': {'data': 'threat map response'}} + mock_call_response = {"response": {"data": "threat map response"}} mock_call = mocker.patch.object( - client, '_call', return_value=mock_call_response + client, "_call", return_value=mock_call_response ) response = client.get_threat_map() - mock_call.assert_called_once_with(url_suffix='/v2/threat/actors') + mock_call.assert_called_once_with(url_suffix="/v2/threat/actors") assert response == mock_call_response @@ -1034,25 +1032,25 @@ def test_get_threat_links(self, mocker): import demistomock as demisto # This is needed for CommonServerPython module to not add demisto.params() into callingContext. - os.environ['COMMON_SERVER_NO_AUTO_PARAMS_REMOVE_NULLS'] = 'True' + os.environ["COMMON_SERVER_NO_AUTO_PARAMS_REMOVE_NULLS"] = "True" # Mock demisto command and args. - mock_command_name = 'threat_links' - mock_command_args = {'arg1': 'arg1_value', 'arg2': 'arg2_value'} + mock_command_name = "threat_links" + mock_command_args = {"arg1": "arg1_value", "arg2": "arg2_value"} - mocker.patch.object(demisto, 'command', return_value=mock_command_name) - mocker.patch.object(demisto, 'args', return_value=mock_command_args) + mocker.patch.object(demisto, "command", return_value=mock_command_name) + mocker.patch.object(demisto, "args", return_value=mock_command_args) client = create_client() - mock_call_response = {'response': {'data': 'threat links response'}} + mock_call_response = {"response": {"data": "threat links response"}} mock_call = mocker.patch.object( - client, '_call', return_value=mock_call_response + client, "_call", return_value=mock_call_response ) response = client.get_threat_links() - mock_call.assert_called_once_with(url_suffix='/v2/links/search') + mock_call.assert_called_once_with(url_suffix="/v2/links/search") assert response == mock_call_response @@ -1061,25 +1059,25 @@ def test_get_detection_rules(self, mocker): import demistomock as demisto # This is needed for CommonServerPython module to not add demisto.params() into callingContext. - os.environ['COMMON_SERVER_NO_AUTO_PARAMS_REMOVE_NULLS'] = 'True' + os.environ["COMMON_SERVER_NO_AUTO_PARAMS_REMOVE_NULLS"] = "True" # Mock demisto command and args. - mock_command_name = 'detection_rules' - mock_command_args = {'arg1': 'arg1_value', 'arg2': 'arg2_value'} + mock_command_name = "detection_rules" + mock_command_args = {"arg1": "arg1_value", "arg2": "arg2_value"} - mocker.patch.object(demisto, 'command', return_value=mock_command_name) - mocker.patch.object(demisto, 'args', return_value=mock_command_args) + mocker.patch.object(demisto, "command", return_value=mock_command_name) + mocker.patch.object(demisto, "args", return_value=mock_command_args) client = create_client() - mock_call_response = {'response': {'data': 'detection rules response'}} + mock_call_response = {"response": {"data": "detection rules response"}} mock_call = mocker.patch.object( - client, '_call', return_value=mock_call_response + client, "_call", return_value=mock_call_response ) response = client.get_detection_rules() - mock_call.assert_called_once_with(url_suffix='/v2/detection_rules/search') + mock_call.assert_called_once_with(url_suffix="/v2/detection_rules/search") assert response == mock_call_response @@ -1088,25 +1086,27 @@ def test_submit_collective_insight(self, mocker): import demistomock as demisto # This is needed for CommonServerPython module to not add demisto.params() into callingContext. - os.environ['COMMON_SERVER_NO_AUTO_PARAMS_REMOVE_NULLS'] = 'True' + os.environ["COMMON_SERVER_NO_AUTO_PARAMS_REMOVE_NULLS"] = "True" # Mock demisto command and args. - mock_command_name = 'collective_insight' - mock_command_args = {'arg1': 'arg1_value', 'arg2': 'arg2_value'} + mock_command_name = "collective_insight" + mock_command_args = {"arg1": "arg1_value", "arg2": "arg2_value"} - mocker.patch.object(demisto, 'command', return_value=mock_command_name) - mocker.patch.object(demisto, 'args', return_value=mock_command_args) + mocker.patch.object(demisto, "command", return_value=mock_command_name) + mocker.patch.object(demisto, "args", return_value=mock_command_args) client = create_client() - mock_call_response = {'response': {'data': 'collective insight response'}} + mock_call_response = {"response": {"data": "collective insight response"}} mock_call = mocker.patch.object( - client, '_call', return_value=mock_call_response + client, "_call", return_value=mock_call_response ) response = client.submit_detection_to_collective_insight() - mock_call.assert_called_once_with(url_suffix='/v2/collective-insights/detections') + mock_call.assert_called_once_with( + url_suffix="/v2/collective-insights/detections" + ) assert response == mock_call_response @@ -1128,7 +1128,7 @@ def test_process_result_actions_404(self, mocker): # Test if response is CommandResults # (case when we got 404 on response, and it was processed in self.client._call() method). - response = CommandResults(readable_output='Mock') + response = CommandResults(readable_output="Mock") result_actions = actions._process_result_actions(response=response) assert result_actions == [response] @@ -1139,7 +1139,7 @@ def test_process_result_actions_response_is_not_dict(self, mocker): actions = Actions(mock_client) # Test if response is not CommandResults and not Dict. - response = 'Mock string - not CommandResults and not dict' + response = "Mock string - not CommandResults and not dict" result_actions = actions._process_result_actions(response=response) # type: ignore assert result_actions is None @@ -1152,20 +1152,20 @@ def test_process_result_actions_no_or_empty_result_actions_in_response( actions = Actions(mock_client) # Test no results_actions in response. - response = {'data': 'mock'} + response = {"data": "mock"} result_actions = actions._process_result_actions(response=response) assert result_actions is None # Test case when bool(results_actions) in response is False. - response = {'data': 'mock', 'result_actions': None} + response = {"data": "mock", "result_actions": None} result_actions = actions._process_result_actions(response=response) assert result_actions is None - response = {'data': 'mock', 'result_actions': []} + response = {"data": "mock", "result_actions": []} result_actions = actions._process_result_actions(response=response) assert result_actions is None - response = {'data': 'mock', 'result_actions': {}} + response = {"data": "mock", "result_actions": {}} result_actions = actions._process_result_actions(response=response) assert result_actions is None @@ -1176,15 +1176,15 @@ def test_process_result_actions_command_results_only(self, mocker): actions = Actions(mock_client) response = { - 'data': 'mock', - 'result_actions': [ + "data": "mock", + "result_actions": [ { - 'CommandResults': { - 'outputs_prefix': 'mock_outputs_prefix', - 'outputs': 'mock_outputs', - 'raw_response': 'mock_raw_response', - 'readable_output': 'mock_readable_output', - 'outputs_key_field': 'mock_outputs_key_field', + "CommandResults": { + "outputs_prefix": "mock_outputs_prefix", + "outputs": "mock_outputs", + "raw_response": "mock_raw_response", + "readable_output": "mock_readable_output", + "outputs_key_field": "mock_outputs_key_field", }, } ], @@ -1197,11 +1197,11 @@ def test_process_result_actions_command_results_only(self, mocker): assert isinstance(r_a, CommandResults) - assert r_a.outputs_prefix == 'mock_outputs_prefix' - assert r_a.outputs == 'mock_outputs' - assert r_a.raw_response == 'mock_raw_response' - assert r_a.readable_output == 'mock_readable_output' - assert r_a.outputs_key_field == 'mock_outputs_key_field' + assert r_a.outputs_prefix == "mock_outputs_prefix" + assert r_a.outputs == "mock_outputs" + assert r_a.raw_response == "mock_raw_response" + assert r_a.readable_output == "mock_readable_output" + assert r_a.outputs_key_field == "mock_outputs_key_field" def test_process_result_actions_create_indicator_and_default_command_results( self, mocker @@ -1210,22 +1210,22 @@ def test_process_result_actions_create_indicator_and_default_command_results( spy_create_indicator = mocker.spy( RecordedFuture, - 'create_indicator', + "create_indicator", ) mock_client = mocker.Mock() actions = RecordedFuture.Actions(mock_client) response = { - 'data': 'mock', - 'result_actions': [ + "data": "mock", + "result_actions": [ { - 'create_indicator': { - 'entity': 'mock_entity', - 'entity_type': 'ip', - 'score': 15, - 'description': 'mock_description', - 'location': {'country': 'mock_country', 'ans': 'mock_asn'}, + "create_indicator": { + "entity": "mock_entity", + "entity_type": "ip", + "score": 15, + "description": "mock_description", + "location": {"country": "mock_country", "ans": "mock_asn"}, }, } ], @@ -1233,11 +1233,11 @@ def test_process_result_actions_create_indicator_and_default_command_results( result_actions = actions._process_result_actions(response=response) spy_create_indicator.assert_called_once_with( - entity='mock_entity', - entity_type='ip', + entity="mock_entity", + entity_type="ip", score=15, - description='mock_description', - location={'country': 'mock_country', 'ans': 'mock_asn'}, + description="mock_description", + location={"country": "mock_country", "ans": "mock_asn"}, ) assert len(result_actions) == 1 @@ -1247,13 +1247,13 @@ def test_process_result_actions_create_indicator_and_default_command_results( assert isinstance(r_a, RecordedFuture.CommandResults) assert r_a.readable_output == ( - '### New indicator was created.\n' - '|DBotScore(val.Indicator && val.Indicator == obj.Indicator && val.Vendor == ' - 'obj.Vendor && val.Type == obj.Type)|IP(val.Address && val.Address == ' - 'obj.Address)|\n' - '|---|---|\n' - '| Indicator: mock_entity
Type: ip
Vendor: Recorded Future v2
Score: ' - '0 | Address: mock_entity |\n' + "### New indicator was created.\n" + "|DBotScore(val.Indicator && val.Indicator == obj.Indicator && val.Vendor == " + "obj.Vendor && val.Type == obj.Type)|IP(val.Address && val.Address == " + "obj.Address)|\n" + "|---|---|\n" + "| Indicator: mock_entity
Type: ip
Vendor: Recorded Future v2
Score: " + "0 | Address: mock_entity |\n" ) def test_process_result_actions_create_indicator_and_command_results(self, mocker): @@ -1261,29 +1261,29 @@ def test_process_result_actions_create_indicator_and_command_results(self, mocke spy_create_indicator = mocker.spy( RecordedFuture, - 'create_indicator', + "create_indicator", ) mock_client = mocker.Mock() actions = RecordedFuture.Actions(mock_client) response = { - 'data': 'mock', - 'result_actions': [ + "data": "mock", + "result_actions": [ { - 'create_indicator': { - 'entity': 'mock_entity', - 'entity_type': 'ip', - 'score': 15, - 'description': 'mock_indicator_description', + "create_indicator": { + "entity": "mock_entity", + "entity_type": "ip", + "score": 15, + "description": "mock_indicator_description", }, - 'CommandResults': { - 'outputs_prefix': 'mock_outputs_prefix', - 'outputs': 'mock_outputs', - 'raw_response': 'mock_raw_response', - 'readable_output': 'mock_readable_output', - 'outputs_key_field': 'mock_outputs_key_field', - 'indicator': 'indicator', + "CommandResults": { + "outputs_prefix": "mock_outputs_prefix", + "outputs": "mock_outputs", + "raw_response": "mock_raw_response", + "readable_output": "mock_readable_output", + "outputs_key_field": "mock_outputs_key_field", + "indicator": "indicator", }, } ], @@ -1291,10 +1291,10 @@ def test_process_result_actions_create_indicator_and_command_results(self, mocke result_actions = actions._process_result_actions(response=response) spy_create_indicator.assert_called_once_with( - entity='mock_entity', - entity_type='ip', + entity="mock_entity", + entity_type="ip", score=15, - description='mock_indicator_description', + description="mock_indicator_description", ) assert len(result_actions) == 1 @@ -1303,20 +1303,20 @@ def test_process_result_actions_create_indicator_and_command_results(self, mocke assert isinstance(r_a, RecordedFuture.CommandResults) - assert r_a.outputs_prefix == 'mock_outputs_prefix' - assert r_a.outputs == 'mock_outputs' - assert r_a.raw_response == 'mock_raw_response' - assert r_a.readable_output == 'mock_readable_output' - assert r_a.outputs_key_field == 'mock_outputs_key_field' + assert r_a.outputs_prefix == "mock_outputs_prefix" + assert r_a.outputs == "mock_outputs" + assert r_a.raw_response == "mock_raw_response" + assert r_a.readable_output == "mock_readable_output" + assert r_a.outputs_key_field == "mock_outputs_key_field" assert r_a.indicator.to_context() == { - 'DBotScore(val.Indicator && val.Indicator == obj.Indicator && val.Vendor == obj.Vendor && val.Type == obj.Type)': { - 'Indicator': 'mock_entity', - 'Score': 0, - 'Type': 'ip', - 'Vendor': 'Recorded Future v2', + "DBotScore(val.Indicator && val.Indicator == obj.Indicator && val.Vendor == obj.Vendor && val.Type == obj.Type)": { + "Indicator": "mock_entity", + "Score": 0, + "Type": "ip", + "Vendor": "Recorded Future v2", }, - 'IP(val.Address && val.Address == obj.Address)': {'Address': 'mock_entity'}, + "IP(val.Address && val.Address == obj.Address)": {"Address": "mock_entity"}, } def test_fetch_incidents_with_incidents_present(self, mocker): @@ -1326,31 +1326,31 @@ def test_fetch_incidents_with_incidents_present(self, mocker): client = create_client() mock_incidents_value = [ - {'mock_incident_key1': 'mock_incident_value1'}, - {'mock_incident_key2': 'mock_incident_value2'}, + {"mock_incident_key1": "mock_incident_value1"}, + {"mock_incident_key2": "mock_incident_value2"}, ] - mock_demisto_last_run_value = 'mock_demisto_last_run' + mock_demisto_last_run_value = "mock_demisto_last_run" - mock_alerts_update_data_value = 'mock_alerts_update_data_value' + mock_alerts_update_data_value = "mock_alerts_update_data_value" mock_client_fetch_incidents_response = { - 'incidents': mock_incidents_value, - 'demisto_last_run': mock_demisto_last_run_value, - 'data': 'mock', - 'alerts_update_data': mock_alerts_update_data_value, + "incidents": mock_incidents_value, + "demisto_last_run": mock_demisto_last_run_value, + "data": "mock", + "alerts_update_data": mock_alerts_update_data_value, } mock_client_fetch_incidents = mocker.patch.object( - client, 'fetch_incidents', return_value=mock_client_fetch_incidents_response + client, "fetch_incidents", return_value=mock_client_fetch_incidents_response ) mock_client_alert_set_status = mocker.patch.object( client, - 'alert_set_status', + "alert_set_status", ) - mock_demisto_incidents = mocker.patch.object(demisto, 'incidents') - mock_demisto_set_last_run = mocker.patch.object(demisto, 'setLastRun') + mock_demisto_incidents = mocker.patch.object(demisto, "incidents") + mock_demisto_set_last_run = mocker.patch.object(demisto, "setLastRun") actions = Actions(client) @@ -1371,20 +1371,20 @@ def test_malware_search_command(self, mocker): client = create_client() - mock_response = 'mock_response' + mock_response = "mock_response" mock_client_entity_search = mocker.patch.object( - client, 'entity_search', return_value=mock_response + client, "entity_search", return_value=mock_response ) actions = Actions(client) mock_process_result_actions_return_value = ( - 'mock_process_result_actions_return_value' + "mock_process_result_actions_return_value" ) mock_process_result_actions = mocker.patch.object( actions, - '_process_result_actions', + "_process_result_actions", return_value=mock_process_result_actions_return_value, ) @@ -1401,20 +1401,20 @@ def test_lookup_command(self, mocker): client = create_client() - mock_response = 'mock_response' + mock_response = "mock_response" mock_client_entity_lookup = mocker.patch.object( - client, 'entity_lookup', return_value=mock_response + client, "entity_lookup", return_value=mock_response ) actions = Actions(client) mock_process_result_actions_return_value = ( - 'mock_process_result_actions_return_value' + "mock_process_result_actions_return_value" ) mock_process_result_actions = mocker.patch.object( actions, - '_process_result_actions', + "_process_result_actions", return_value=mock_process_result_actions_return_value, ) @@ -1431,20 +1431,20 @@ def test_intelligence_command(self, mocker): client = create_client() - mock_response = 'mock_response' + mock_response = "mock_response" mock_client_get_intelligence = mocker.patch.object( - client, 'get_intelligence', return_value=mock_response + client, "get_intelligence", return_value=mock_response ) actions = Actions(client) mock_process_result_actions_return_value = ( - 'mock_process_result_actions_return_value' + "mock_process_result_actions_return_value" ) mock_process_result_actions = mocker.patch.object( actions, - '_process_result_actions', + "_process_result_actions", return_value=mock_process_result_actions_return_value, ) @@ -1461,20 +1461,20 @@ def test_get_links_command(self, mocker): client = create_client() - mock_response = 'mock_response' + mock_response = "mock_response" mock_client_get_links = mocker.patch.object( - client, 'get_links', return_value=mock_response + client, "get_links", return_value=mock_response ) actions = Actions(client) mock_process_result_actions_return_value = ( - 'mock_process_result_actions_return_value' + "mock_process_result_actions_return_value" ) mock_process_result_actions = mocker.patch.object( actions, - '_process_result_actions', + "_process_result_actions", return_value=mock_process_result_actions_return_value, ) @@ -1491,20 +1491,20 @@ def test_get_single_alert_command_with_result_actions(self, mocker): client = create_client() - mock_response = 'mock_response' + mock_response = "mock_response" mock_client_get_single_alert = mocker.patch.object( - client, 'get_single_alert', return_value=mock_response + client, "get_single_alert", return_value=mock_response ) actions = Actions(client) mock_process_result_actions_return_value = ( - 'mock_process_result_actions_return_value' + "mock_process_result_actions_return_value" ) mock_process_result_actions = mocker.patch.object( actions, - '_process_result_actions', + "_process_result_actions", return_value=mock_process_result_actions_return_value, ) @@ -1522,10 +1522,10 @@ def test_get_single_alert_command_without_result_actions(self, mocker): client = create_client() - mock_response = 'mock_response' + mock_response = "mock_response" mock_client_get_single_alert = mocker.patch.object( - client, 'get_single_alert', return_value=mock_response + client, "get_single_alert", return_value=mock_response ) actions = Actions(client) @@ -1533,7 +1533,7 @@ def test_get_single_alert_command_without_result_actions(self, mocker): mock_process_result_actions_return_value = None mock_process_result_actions = mocker.patch.object( actions, - '_process_result_actions', + "_process_result_actions", return_value=mock_process_result_actions_return_value, ) @@ -1551,10 +1551,10 @@ def test_get_alerts_command(self, mocker): client = create_client() - mock_response = 'mock_response' + mock_response = "mock_response" mock_client_get_alerts = mocker.patch.object( - client, 'get_alerts', return_value=mock_response + client, "get_alerts", return_value=mock_response ) actions = Actions(client) @@ -1570,10 +1570,10 @@ def test_get_alert_rules_command(self, mocker): client = create_client() - mock_response = 'mock_response' + mock_response = "mock_response" mock_client_get_alert_rules = mocker.patch.object( - client, 'get_alert_rules', return_value=mock_response + client, "get_alert_rules", return_value=mock_response ) actions = Actions(client) @@ -1589,20 +1589,20 @@ def test_alert_set_status_command(self, mocker): client = create_client() - mock_response = 'mock_response' + mock_response = "mock_response" mock_client_alert_set_status = mocker.patch.object( - client, 'alert_set_status', return_value=mock_response + client, "alert_set_status", return_value=mock_response ) actions = Actions(client) mock_process_result_actions_return_value = ( - 'mock_process_result_actions_return_value' + "mock_process_result_actions_return_value" ) mock_process_result_actions = mocker.patch.object( actions, - '_process_result_actions', + "_process_result_actions", return_value=mock_process_result_actions_return_value, ) @@ -1619,20 +1619,20 @@ def test_alert_set_note_command(self, mocker): client = create_client() - mock_response = 'mock_response' + mock_response = "mock_response" mock_client_alert_set_note = mocker.patch.object( - client, 'alert_set_note', return_value=mock_response + client, "alert_set_note", return_value=mock_response ) actions = Actions(client) mock_process_result_actions_return_value = ( - 'mock_process_result_actions_return_value' + "mock_process_result_actions_return_value" ) mock_process_result_actions = mocker.patch.object( actions, - '_process_result_actions', + "_process_result_actions", return_value=mock_process_result_actions_return_value, ) @@ -1649,20 +1649,20 @@ def test_triage_command(self, mocker): client = create_client() - mock_response = 'mock_response' + mock_response = "mock_response" mock_client_get_triage = mocker.patch.object( - client, 'get_triage', return_value=mock_response + client, "get_triage", return_value=mock_response ) actions = Actions(client) mock_process_result_actions_return_value = ( - 'mock_process_result_actions_return_value' + "mock_process_result_actions_return_value" ) mock_process_result_actions = mocker.patch.object( actions, - '_process_result_actions', + "_process_result_actions", return_value=mock_process_result_actions_return_value, ) @@ -1679,20 +1679,20 @@ def test_threat_map_command(self, mocker): client = create_client() - mock_response = 'mock_threat_map' + mock_response = "mock_threat_map" mock_client_get_threat_map = mocker.patch.object( - client, 'get_threat_map', return_value=mock_response + client, "get_threat_map", return_value=mock_response ) actions = Actions(client) mock_process_result_actions_return_value = ( - 'mock_process_result_actions_return_value' + "mock_process_result_actions_return_value" ) mock_process_result_actions = mocker.patch.object( actions, - '_process_result_actions', + "_process_result_actions", return_value=mock_process_result_actions_return_value, ) @@ -1709,18 +1709,18 @@ def test_threat_links_command(self, mocker): client = create_client() - mock_response = 'mock_threat_links' + mock_response = "mock_threat_links" mock_client_get_threat_links = mocker.patch.object( - client, 'get_threat_links', return_value=mock_response + client, "get_threat_links", return_value=mock_response ) actions = Actions(client) - mock_process_result_actions_return_value = 'return_value' + mock_process_result_actions_return_value = "return_value" mock_process_result_actions = mocker.patch.object( actions, - '_process_result_actions', + "_process_result_actions", return_value=mock_process_result_actions_return_value, ) @@ -1735,18 +1735,18 @@ def test_detection_rules_command(self, mocker): client = create_client() - mock_response = 'mock_detection_rules' + mock_response = "mock_detection_rules" mock_get_detection_rules = mocker.patch.object( - client, 'get_detection_rules', return_value=mock_response + client, "get_detection_rules", return_value=mock_response ) actions = Actions(client) - mock_process_result_actions_return_value = 'return_value' + mock_process_result_actions_return_value = "return_value" mock_process_result_actions = mocker.patch.object( actions, - '_process_result_actions', + "_process_result_actions", return_value=mock_process_result_actions_return_value, ) @@ -1761,18 +1761,18 @@ def test_collective_insight_command(self, mocker): client = create_client() - mock_response = 'mock_collective_insight' + mock_response = "mock_collective_insight" mock_submit_detection_to_collective_insight = mocker.patch.object( - client, 'submit_detection_to_collective_insight', return_value=mock_response + client, "submit_detection_to_collective_insight", return_value=mock_response ) actions = Actions(client) - mock_process_result_actions_return_value = 'return_value' + mock_process_result_actions_return_value = "return_value" mock_process_result_actions = mocker.patch.object( actions, - '_process_result_actions', + "_process_result_actions", return_value=mock_process_result_actions_return_value, ) @@ -1798,7 +1798,7 @@ def test_test_module(self, mocker): mocker.patch.object(RecordedFuture.Client, "whoami") mocked_return_res = mocker.patch.object(RecordedFuture, "return_results") RecordedFuture.main() - mocked_return_res.assert_called_with('ok') + mocked_return_res.assert_called_with("ok") def test_test_module_with_boom(self, mocker): import RecordedFuture @@ -1819,8 +1819,9 @@ def test_test_module_with_boom(self, mocker): RecordedFuture.main() mocked_return_err.assert_called_with( message=( - f'Failed to execute {demisto.command()} command: Failed due to - ' - 'Unknown error. Please verify that the API URL and Token are correctly configured. ' - 'RAW Error: Side effect triggered' - ) + f"Failed to execute {demisto.command()} command: " + "Failed due to - Unknown error. Please verify that the API URL and Token are correctly configured. " + "RAW Error: Side effect triggered" + ), + error=mocker.ANY, ) diff --git a/Packs/RecordedFuture/Integrations/RecordedFutureEventCollector/RecordedFutureEventCollector.yml b/Packs/RecordedFuture/Integrations/RecordedFutureEventCollector/RecordedFutureEventCollector.yml index ba8b8f0d9c0..65770ce681a 100644 --- a/Packs/RecordedFuture/Integrations/RecordedFutureEventCollector/RecordedFutureEventCollector.yml +++ b/Packs/RecordedFuture/Integrations/RecordedFutureEventCollector/RecordedFutureEventCollector.yml @@ -55,7 +55,7 @@ script: name: limit description: Gets events from Recorded Future. name: recorded-future-get-events - dockerimage: demisto/python3:3.10.14.91134 + dockerimage: demisto/python3:3.11.10.111039 isfetchevents: true script: '-' subtype: python3 diff --git a/Packs/RecordedFuture/Integrations/RecordedFutureLists/RecordedFutureLists.yml b/Packs/RecordedFuture/Integrations/RecordedFutureLists/RecordedFutureLists.yml index f1e270d1e27..168be18aa60 100644 --- a/Packs/RecordedFuture/Integrations/RecordedFutureLists/RecordedFutureLists.yml +++ b/Packs/RecordedFuture/Integrations/RecordedFutureLists/RecordedFutureLists.yml @@ -31,7 +31,7 @@ script: script: '-' type: python subtype: python3 - dockerimage: demisto/python3:3.10.14.91134 + dockerimage: demisto/python3:3.11.10.111039 commands: - name: recordedfuture-lists-search description: Search for lists in Recorded Future. diff --git a/Packs/RecordedFuture/Integrations/RecordedFuturePlaybookAlerts/README.md b/Packs/RecordedFuture/Integrations/RecordedFuturePlaybookAlerts/README.md index b1d7dade1af..3b51c57ee8f 100644 --- a/Packs/RecordedFuture/Integrations/RecordedFuturePlaybookAlerts/README.md +++ b/Packs/RecordedFuture/Integrations/RecordedFuturePlaybookAlerts/README.md @@ -1,5 +1,7 @@ -Fetch & triage Recorded Future Playbook Alerts -This integration was integrated and tested with version 1.0.2 of RecordedFuturePlaybookAlerts +# Recorded Future - Playbook Alerts Integration + +Fetch & triage Recorded Future Playbook Alerts. + ## Configure Recorded Future - Playbook Alerts on Cortex XSOAR @@ -7,19 +9,19 @@ This integration was integrated and tested with version 1.0.2 of RecordedFutureP 2. Search for Recorded Future - Playbook Alerts. 3. Click **Add instance** to create and configure a new integration instance. - | **Parameter** | **Description** | **Required** | - | --- | --- | --- | - | API URL (e.g., https://api.recordedfuture.com/gw/xsoar/) | | True | - | API Token | | True | - | Trust any certificate (not secure) | | False | - | Use system proxy settings | | False | - | Fetch incidents | | False | - | First Incidient Fetch: Time Range | Limit incidents to include in the first fetch by time range. Input format: "NN hours" or "NN days". E.g., input "5 days" to fetch all incidents created in the last 5 days. | False | - | Playbook Alerts: Fetched Categories | Some listed Playbook alert Categories might be unavailable due to limitations in the current Recorded Future subscription | False | - | Maximum number of incidents per fetch | | False | - | Playbook Alerts: Fetched Statuses | | False | - | Playbook Alerts: Fetched Priorities Threshold | Returns alerts with this selected priority or higher. High > Moderate > Informational | False | - | Incident type | | False | + | **Parameter** | **Description** | **Required** | + |----------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------| + | API URL (e.g., https://api.recordedfuture.com/gw/xsoar/) | | True | + | API Token | | True | + | Trust any certificate (not secure) | | False | + | Use system proxy settings | | False | + | Fetch incidents | | False | + | First Incidient Fetch: Time Range | Limit incidents to include in the first fetch by time range. Input format: "NN hours" or "NN days". E.g., input "5 days" to fetch all incidents created in the last 5 days. | False | + | Playbook Alerts: Fetched Categories | Some listed Playbook alert Categories might be unavailable due to limitations in the current Recorded Future subscription | False | + | Maximum number of incidents per fetch | | False | + | Playbook Alerts: Fetched Statuses | | False | + | Playbook Alerts: Fetched Priorities Threshold | Returns alerts with this selected priority or higher. High > Moderate > Informational | False | + | Incident type | | False | 4. Click **Test** to validate the URLs, token, and connection. @@ -48,232 +50,238 @@ The integration pulls in Playbook alerts from Recorded Future base on its update You can execute these commands from the Cortex XSOAR CLI, as part of an automation, or in a playbook. After you successfully execute a command, a DBot message appears in the War Room with the command details. -### recordedfuture-playbook-alerts-details + +### recordedfuture-playbook-alerts-search *** -Get Playbook alert details by id. +Search playbook alerts based on filters. #### Base Command -`recordedfuture-playbook-alerts-details` +`recordedfuture-playbook-alerts-search` #### Input -| **Argument Name** | **Description** | **Required** | -| --- | --- | --- | -| alert_ids | Ids of the playbook alert that should be fetched. | Required | -| detail_sections | What evidence sections to include in the fetch, fetches all available if not specified. Possible values are: status, action, summary, log, whois, dns. | Optional | +| **Argument Name** | **Description** | **Required** | +|-----------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------|--------------| +| category | The playbook alert categories to retrieve. Default is all_available. Possible values are: all_available, domain_abuse, vulnerability, code_repo_leakage. | Optional | +| limit | The maximum number of alerts to fetch. | Optional | +| time_since_update | The amount of time since the last update. E.g., "2 hours" or "7 days" ago. | Optional | +| playbook_alert_status | The statuses to retrieve. Defaults to only new status if not specified. Possible values are: new, in-progress, dismissed, resolved. | Optional | +| priority | Actions priority assigned in Recorded Future. Possible values are: high, moderate, informational. | Optional | +| order_search_by | The order by which to search for playbook alerts. Possible values are: updated, created. | Optional | ##### Command Example -```!recordedfuture-playbook-alerts-details alert_ids="12312312-1231-1231-1231-123123123123" detail_sections="status,log"``` +```!recordedfuture-playbook-alerts-search``` +```!recordedfuture-playbook-alerts-search category=domain_abuse``` +```!recordedfuture-playbook-alerts-search category=vulnerability``` +```!recordedfuture-playbook-alerts-search limit=10``` +```!recordedfuture-playbook-alerts-search playbook_alert_status=in-progress``` +```!recordedfuture-playbook-alerts-search priority=high``` +```!recordedfuture-playbook-alerts-search order_search_by=updated``` + #### Context Output -| **Path** | **Type** | **Description** | -| --- | --- | --- | -| RecordedFuture.PlaybookAlerts.playbook_alert_id | String | Unique id of the playbook alert | -| RecordedFuture.PlaybookAlerts.category | String | Playbook alert category | -| RecordedFuture.PlaybookAlerts.priority | String | Recommended Priority of the alert | -| RecordedFuture.PlaybookAlerts.status | String | Current alert status in Recorded Future | -| RecordedFuture.PlaybookAlerts.title | String | Title of the alert | -| RecordedFuture.PlaybookAlerts.updated | date | Date of last update | -| RecordedFuture.PlaybookAlerts.created | date | Date of creation | -| RecordedFuture.PlaybookAlerts.organization_id | String | Organization uhash | -| RecordedFuture.PlaybookAlerts.organization_name | String | Plaintext Organization name | -| RecordedFuture.PlaybookAlerts.assignee_id | String | uhash of the assigned user | -| RecordedFuture.PlaybookAlerts.assignee_name | String | name of the assigned user | -| RecordedFuture.PlaybookAlerts.owner_id | String | uhash of the enterprise that owns the alert | -| RecordedFuture.PlaybookAlerts.owner_name | String | Name of the enterprise that owns the alert | -| RecordedFuture.PlaybookAlerts.panel_status.playbook_alert_id | String | Unique id of the playbook alert | -| RecordedFuture.PlaybookAlerts.panel_status.category | String | Playbook alert category | -| RecordedFuture.PlaybookAlerts.panel_status.priority | String | Recommended Priority of the alert | -| RecordedFuture.PlaybookAlerts.panel_status.status | String | Current alert status in Recorded Future | -| RecordedFuture.PlaybookAlerts.panel_status.title | String | Title of the alert | -| RecordedFuture.PlaybookAlerts.panel_status.updated | date | Date of last update | -| RecordedFuture.PlaybookAlerts.panel_status.created | date | Date of creation | -| RecordedFuture.PlaybookAlerts.panel_status.organization_id | String | Organization uhash | -| RecordedFuture.PlaybookAlerts.panel_status.organization_name | String | Plaintext Organization name | -| RecordedFuture.PlaybookAlerts.panel_status.assignee_id | String | uhash of the assigned user | -| RecordedFuture.PlaybookAlerts.panel_status.assignee_name | unknown | name of the assigned user | -| RecordedFuture.PlaybookAlerts.panel_status.owner_id | String | uhash of the enterprise that owns the alert | -| RecordedFuture.PlaybookAlerts.panel_status.owner_name | String | Name of the enterprise that owns the alert | -| RecordedFuture.PlaybookAlerts.panel_status.case_rule_id | String | Id of the playbook alert category | -| RecordedFuture.PlaybookAlerts.panel_status.case_rule_label | String | Name of the playbook alert category | -| RecordedFuture.PlaybookAlerts.panel_status.context_list.context | Array | Context of entity connected to the Playbook alert. | -| RecordedFuture.PlaybookAlerts.panel_status.created | String | Date marking the creation of the Playbook alert in Recorded Future | -| RecordedFuture.PlaybookAlerts.panel_status.entity_criticality | String | Criticality of the Playbook alert | -| RecordedFuture.PlaybookAlerts.panel_status.entity_id | String | Id of the entity in Recorded Future | -| RecordedFuture.PlaybookAlerts.panel_status.entity_name | String | Name of the entity | -| RecordedFuture.PlaybookAlerts.panel_status.risk_score | String | Risk score of the entity in Recorded Future | -| RecordedFuture.PlaybookAlerts.panel_status.targets | Array | List of targets of the Playbook alert | -| RecordedFuture.PlaybookAlerts.panel_status.lifecycle_stage | String | Indicates what lifecycle the vulerability is in | -| RecordedFuture.PlaybookAlerts.panel_summary.explanation | String | Entails the explanation to the triggering of the Playbook alert | -| RecordedFuture.PlaybookAlerts.panel_summary.resolved_record_list.context_list.context | String | Context of entity connected to the Playbook alert. | -| RecordedFuture.PlaybookAlerts.panel_summary.resolved_record_list.criticality | String | Level of criticality | -| RecordedFuture.PlaybookAlerts.panel_summary.resolved_record_list.entity | String | ID of the entitiy in Recorded Future | -| RecordedFuture.PlaybookAlerts.panel_summary.resolved_record_list.record_type | String | Type of record A, CNAME or MX | -| RecordedFuture.PlaybookAlerts.panel_summary.resolved_record_list.risk_score | String | Risk score of the entity in Recorded Future | -| RecordedFuture.PlaybookAlerts.panel_summary.screenshots.description | String | Description of the image | -| RecordedFuture.PlaybookAlerts.panel_summary.screenshots.image_id | String | ID of the screenshot in recorded future | -| RecordedFuture.PlaybookAlerts.panel_summary.screenshots.tag | String | Image Analisys tag | -| RecordedFuture.PlaybookAlerts.panel_summary.screenshots.created | String | When the image was created | -| RecordedFuture.PlaybookAlerts.panel_summary.screenshots.base64 | String | The image binary encoded as a base64 string | -| RecordedFuture.PlaybookAlerts.panel_summary.summary.targets.name | String | Target affected by the vulnerability | -| RecordedFuture.PlaybookAlerts.panel_summary.summary.lifecycle_stage | String | The current lifecycle stage of the Playbook Alert | -| RecordedFuture.PlaybookAlerts.panel_summary.summary.riskrules.rule | String | Name of the rule that triggered | -| RecordedFuture.PlaybookAlerts.panel_summary.summary.riskrules.description | String | Short description of the trigger \(E.g 13 sightings on 1 source..\) | -| RecordedFuture.PlaybookAlerts.panel_summary.affected_products.name | String | Name of of affected product | -| RecordedFuture.PlaybookAlerts.panel_summary.insikt_notes.id | String | The id of the Insikt note | -| RecordedFuture.PlaybookAlerts.panel_summary.insikt_notes.title | String | The title of the Insikt note | -| RecordedFuture.PlaybookAlerts.panel_summary.insikt_notes.topic | String | The topic of the Insikt note | -| RecordedFuture.PlaybookAlerts.panel_summary.insikt_notes.published | String | The time at which the Insikt note was published | -| RecordedFuture.PlaybookAlerts.panel_summary.insikt_notes.fragment | String | A fragment of the Insikt note text | -| RecordedFuture.PlaybookAlerts.panel_log.id | String | Log id in Recorded Future | -| RecordedFuture.PlaybookAlerts.panel_log.actor_id | String | Id of the actor | -| RecordedFuture.PlaybookAlerts.panel_log.created | String | When was the log created | -| RecordedFuture.PlaybookAlerts.panel_log.modified | String | When was the log last modified | -| RecordedFuture.PlaybookAlerts.panel_log.action_priority | String | The priority of the Playbook alert | -| RecordedFuture.PlaybookAlerts.panel_log.message | String | Log message | -| RecordedFuture.PlaybookAlerts.panel_log.changes.assigne_change.old | String | Previous assignee | -| RecordedFuture.PlaybookAlerts.panel_log.changes.assigne_change.new | String | New assignee | -| RecordedFuture.PlaybookAlerts.panel_log.changes.assigne_change.type | String | Type of change | -| RecordedFuture.PlaybookAlerts.panel_log.changes.status_change.old | String | Previous status | -| RecordedFuture.PlaybookAlerts.panel_log.changes.status_change.new | String | New status | -| RecordedFuture.PlaybookAlerts.panel_log.changes.status_change.type | String | Type of change | -| RecordedFuture.PlaybookAlerts.panel_log.changes.title_change.old | String | Previous title | -| RecordedFuture.PlaybookAlerts.panel_log.changes.title_change.new | String | New title | -| RecordedFuture.PlaybookAlerts.panel_log.changes.title_change.type | String | Type of change | -| RecordedFuture.PlaybookAlerts.panel_log.changes.priority_change.old | String | Previous priority | -| RecordedFuture.PlaybookAlerts.panel_log.changes.priority_change.new | String | New priority | -| RecordedFuture.PlaybookAlerts.panel_log.changes.priority_change.type | String | Type of change | -| RecordedFuture.PlaybookAlerts.panel_log.changes.reopen_strategy_change.old | String | Previous reopen strategy | -| RecordedFuture.PlaybookAlerts.panel_log.changes.reopen_strategy_change.new | String | New reopen strategy | -| RecordedFuture.PlaybookAlerts.panel_log.changes.reopen_strategy_change.type | String | Type of change | -| RecordedFuture.PlaybookAlerts.panel_log.changes.entities_change.removed | String | Removed entity | -| RecordedFuture.PlaybookAlerts.panel_log.changes.entities_change.added | String | Added entity | -| RecordedFuture.PlaybookAlerts.panel_log.changes.entities_change.type | String | Type of change | -| RecordedFuture.PlaybookAlerts.panel_log.changes.related_entities_change.removed | String | Removed related entity | -| RecordedFuture.PlaybookAlerts.panel_log.changes.related_entities_change.added | String | Added related entity | -| RecordedFuture.PlaybookAlerts.panel_log.changes.related_entities_changetype | String | Type of change | -| RecordedFuture.PlaybookAlerts.panel_log.changes.description_change.old | String | Previous description | -| RecordedFuture.PlaybookAlerts.panel_log.changes.description_change.new | String | New description | -| RecordedFuture.PlaybookAlerts.panel_log.changes.description_change.type | String | Type of change | -| RecordedFuture.PlaybookAlerts.panel_log.changes.external_id_change.old | String | Previous external ID | -| RecordedFuture.PlaybookAlerts.panel_log.changes.external_id_change.new | String | New external ID | -| RecordedFuture.PlaybookAlerts.panel_log.changes.external_id_change.type | String | Type of change | -| RecordedFuture.PlaybookAlerts.panel_action.action | String | The name of the action | -| RecordedFuture.PlaybookAlerts.panel_action.updated | String | When was the action last updated | -| RecordedFuture.PlaybookAlerts.panel_action.assignee_name | String | Full name of the assignee | -| RecordedFuture.PlaybookAlerts.panel_action.assignee_id | String | ID of the assignee | -| RecordedFuture.PlaybookAlerts.panel_action.status | String | The status of the action | -| RecordedFuture.PlaybookAlerts.panel_action.description | String | A short description of the action | -| RecordedFuture.PlaybookAlerts.panel_action.link | String | A link associated with the action | -| RecordedFuture.PlaybookAlerts.panel_dns.ip_list.record | String | The DNS record | -| RecordedFuture.PlaybookAlerts.panel_dns.ip_list.risk_score | String | Risk score associated with the record | -| RecordedFuture.PlaybookAlerts.panel_dns.ip_list.criticality | String | The level of criticality | -| RecordedFuture.PlaybookAlerts.panel_dns.ip_list.record_type | String | Type of record A, CNAME or MX | -| RecordedFuture.PlaybookAlerts.panel_dns.ip_list.context_list.context | String | Labels of malicious behavior types that can be associated with an entity. | -| RecordedFuture.PlaybookAlerts.panel_dns.mx_list.record | String | The DNS record | -| RecordedFuture.PlaybookAlerts.panel_dns.mx_list.risk_score | String | Risk score associated with the record | -| RecordedFuture.PlaybookAlerts.panel_dns.mx_list.criticality | String | The level of criticality | -| RecordedFuture.PlaybookAlerts.panel_dns.mx_list.record_type | String | Type of record A, CNAME or MX | -| RecordedFuture.PlaybookAlerts.panel_dns.mx_list.context_list.context | String | Labels of malicious behavior types that can be associated with an entity. | -| RecordedFuture.PlaybookAlerts.panel_dns.ns_list.record | String | The DNS record | -| RecordedFuture.PlaybookAlerts.panel_dns.ns_list.risk_score | String | Risk score associated with the record | -| RecordedFuture.PlaybookAlerts.panel_dns.ns_list.criticality | String | The level of criticality | -| RecordedFuture.PlaybookAlerts.panel_dns.ns_list.record_type | String | Type of record A, CNAME or MX | -| RecordedFuture.PlaybookAlerts.panel_dns.ns_list.context_list.context | String | Labels of malicious behavior types that can be associated with an entity. | -| RecordedFuture.PlaybookAlerts.panel_whois.body.added | String | When the whois information was added | -| RecordedFuture.PlaybookAlerts.panel_whois.body.attribute | String | Attribute, either whois or whoisContancts | -| RecordedFuture.PlaybookAlerts.panel_whois.body.entity | String | Id of whois entity | -| RecordedFuture.PlaybookAlerts.panel_whois.body.provider | String | Name of provider | -| RecordedFuture.PlaybookAlerts.panel_whois.body.value.createdDate | String | When was it created | -| RecordedFuture.PlaybookAlerts.panel_whois.body.value.nameServers | Array | List of name server IDs | -| RecordedFuture.PlaybookAlerts.panel_whois.body.value.privateRegistration | Bool | Boolean indicating private registration | -| RecordedFuture.PlaybookAlerts.panel_whois.body.value.registrarName | String | Name of the registrar | -| RecordedFuture.PlaybookAlerts.panel_whois.body.value.status | String | Status of registrar | -| RecordedFuture.PlaybookAlerts.panel_whois.body.value.city | String | Contact located in this city | -| RecordedFuture.PlaybookAlerts.panel_whois.body.value.country | String | Contact located in this city | -| RecordedFuture.PlaybookAlerts.panel_whois.body.value.name | String | Name of contact | -| RecordedFuture.PlaybookAlerts.panel_whois.body.value.organization | String | Name of contact organization | -| RecordedFuture.PlaybookAlerts.panel_whois.body.value.postalCode | String | Postal code of contact organization | -| RecordedFuture.PlaybookAlerts.panel_whois.body.value.state | String | Contact located in state | -| RecordedFuture.PlaybookAlerts.panel_whois.body.value.street1 | String | Street name of contact | -| RecordedFuture.PlaybookAlerts.panel_whois.body.value.telephone | String | Phone number of contact | -| RecordedFuture.PlaybookAlerts.panel_whois.body.value.type | String | Type of contact | +| **Path** | **Type** | **Description** | +|-------------------------------------------------|----------|----------------------------------------------| +| RecordedFuture.PlaybookAlerts.playbook_alert_id | String | Unique ID of the playbook alert. | +| RecordedFuture.PlaybookAlerts.category | String | Playbook alert category. | +| RecordedFuture.PlaybookAlerts.priority | String | Recommended Priority of the alert. | +| RecordedFuture.PlaybookAlerts.status | String | Current alert status in Recorded Future. | +| RecordedFuture.PlaybookAlerts.title | String | Title of the alert. | +| RecordedFuture.PlaybookAlerts.updated | date | Date of last update. | +| RecordedFuture.PlaybookAlerts.created | date | Date of creation. | +| RecordedFuture.PlaybookAlerts.organization_id | String | Organization uhash. | +| RecordedFuture.PlaybookAlerts.organization_name | String | Plaintext Organization name. | +| RecordedFuture.PlaybookAlerts.assignee_id | String | uhash of the assigned user. | +| RecordedFuture.PlaybookAlerts.assignee_name | unknown | name of the assigned user. | +| RecordedFuture.PlaybookAlerts.owner_id | String | uhash of the enterprise that owns the alert. | +| RecordedFuture.PlaybookAlerts.owner_name | String | Name of the enterprise that owns the alert. | -### recordedfuture-playbook-alerts-update + +### recordedfuture-playbook-alerts-details *** -Update the status of one or multiple Playbook alerts +Get Playbook alert details by ID. #### Base Command -`recordedfuture-playbook-alerts-update` +`recordedfuture-playbook-alerts-details` #### Input -| **Argument Name** | **Description** | **Required** | -| --- | --- | --- | -| alert_ids | Ids of the playbook alerts that will be updated. | Required | -| new_status | New status to set for all alerts in alert_ids. Possible values are: new, in-progress, dismissed, resolved. | Required | +| **Argument Name** | **Description** | **Required** | +|-------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------|--------------| +| alert_ids | IDs of the playbook alert that should be fetched. | Required | +| detail_sections | What evidence sections to include in the fetch. Fetches all available if not specified. Possible values are: status, action, summary, log, whois, dns. | Optional | ##### Command Example -```!recordedfuture-playbook-alerts-update alert_ids="12312312-1231-1231-1231-123123123123" new_status="New"``` +```!recordedfuture-playbook-alerts-details alert_ids="12312312-1231-1231-1231-123123123123" detail_sections="status,log"``` #### Context Output -| **Path** | **Type** | **Description** | -| --- | --- | --- | -| RecordedFuture.PlaybookAlerts.playbook_alert_id | string | Unique id of the playbook alert in Recorded Future | -| RecordedFuture.PlaybookAlerts.current_status | string | Current status of playbook alert in Recorded Future | -| RecordedFuture.PlaybookAlerts.title | string | Title of the playbook alert in Recorded Future | -| RecordedFuture.PlaybookAlerts.status_message | string | Message describing the outcome of the update | +| **Path** | **Type** | **Description** | +|---------------------------------------------------------------------------------------|----------|---------------------------------------------------------------------------| +| RecordedFuture.PlaybookAlerts.playbook_alert_id | String | Unique ID of the playbook alert. | +| RecordedFuture.PlaybookAlerts.category | String | Playbook alert category. | +| RecordedFuture.PlaybookAlerts.priority | String | Recommended Priority of the alert. | +| RecordedFuture.PlaybookAlerts.status | String | Current alert status in Recorded Future. | +| RecordedFuture.PlaybookAlerts.title | String | Title of the alert. | +| RecordedFuture.PlaybookAlerts.updated | date | Date of last update. | +| RecordedFuture.PlaybookAlerts.created | date | Date of creation. | +| RecordedFuture.PlaybookAlerts.organization_id | String | Organization uhash. | +| RecordedFuture.PlaybookAlerts.organization_name | String | Plaintext Organization name. | +| RecordedFuture.PlaybookAlerts.assignee_id | String | uhash of the assigned user. | +| RecordedFuture.PlaybookAlerts.assignee_name | String | name of the assigned user. | +| RecordedFuture.PlaybookAlerts.owner_id | String | uhash of the enterprise that owns the alert. | +| RecordedFuture.PlaybookAlerts.owner_name | String | Name of the enterprise that owns the alert. | +| RecordedFuture.PlaybookAlerts.panel_status.playbook_alert_id | String | Unique ID of the playbook alert. | +| RecordedFuture.PlaybookAlerts.panel_status.category | String | Playbook alert category. | +| RecordedFuture.PlaybookAlerts.panel_status.priority | String | Recommended Priority of the alert. | +| RecordedFuture.PlaybookAlerts.panel_status.status | String | Current alert status in Recorded Future. | +| RecordedFuture.PlaybookAlerts.panel_status.title | String | Title of the alert. | +| RecordedFuture.PlaybookAlerts.panel_status.updated | date | Date of last update. | +| RecordedFuture.PlaybookAlerts.panel_status.created | date | Date of creation | +| RecordedFuture.PlaybookAlerts.panel_status.organization_id | String | Organization uhash. | +| RecordedFuture.PlaybookAlerts.panel_status.organization_name | String | Plaintext Organization name. | +| RecordedFuture.PlaybookAlerts.panel_status.assignee_id | String | uhash of the assigned user. | +| RecordedFuture.PlaybookAlerts.panel_status.assignee_name | unknown | name of the assigned user. | +| RecordedFuture.PlaybookAlerts.panel_status.owner_id | String | uhash of the enterprise that owns the alert. | +| RecordedFuture.PlaybookAlerts.panel_status.owner_name | String | Name of the enterprise that owns the alert. | +| RecordedFuture.PlaybookAlerts.panel_status.case_rule_id | String | ID of the playbook alert category. | +| RecordedFuture.PlaybookAlerts.panel_status.case_rule_label | String | Name of the playbook alert category. | +| RecordedFuture.PlaybookAlerts.panel_status.context_list.context | Array | Context of entity connected to the Playbook alert. | +| RecordedFuture.PlaybookAlerts.panel_status.created | String | Date marking the creation of the Playbook alert in Recorded Future. | +| RecordedFuture.PlaybookAlerts.panel_status.entity_criticality | String | Criticality of the Playbook alert. | +| RecordedFuture.PlaybookAlerts.panel_status.entity_id | String | ID of the entity in Recorded Future. | +| RecordedFuture.PlaybookAlerts.panel_status.entity_name | String | Name of the entity. | +| RecordedFuture.PlaybookAlerts.panel_status.risk_score | String | Risk score of the entity in Recorded Future. | +| RecordedFuture.PlaybookAlerts.panel_status.targets | Array | List of targets of the Playbook alert. | +| RecordedFuture.PlaybookAlerts.panel_status.lifecycle_stage | String | Indicates what lifecycle the vulerability is in. | +| RecordedFuture.PlaybookAlerts.panel_summary.explanation | String | Entails the explanation to the triggering of the Playbook alert. | +| RecordedFuture.PlaybookAlerts.panel_summary.resolved_record_list.context_list.context | String | Context of entity connected to the Playbook alert. | +| RecordedFuture.PlaybookAlerts.panel_summary.resolved_record_list.criticality | String | Level of criticality. | +| RecordedFuture.PlaybookAlerts.panel_summary.resolved_record_list.entity | String | ID of the entitiy in Recorded Future. | +| RecordedFuture.PlaybookAlerts.panel_summary.resolved_record_list.record_type | String | Type of record A, CNAME or MX. | +| RecordedFuture.PlaybookAlerts.panel_summary.resolved_record_list.risk_score | String | Risk score of the entity in Recorded Future. | +| RecordedFuture.PlaybookAlerts.panel_summary.screenshots.description | String | Description of the image. | +| RecordedFuture.PlaybookAlerts.panel_summary.screenshots.image_id | String | ID of the screenshot in recorded future. | +| RecordedFuture.PlaybookAlerts.panel_summary.screenshots.tag | String | Image Analisys tag. | +| RecordedFuture.PlaybookAlerts.panel_summary.screenshots.created | String | When the image was created. | +| RecordedFuture.PlaybookAlerts.panel_summary.screenshots.base64 | String | The image binary encoded as a base64 string. | +| RecordedFuture.PlaybookAlerts.panel_summary.summary.targets.name | String | Target affected by the vulnerability. | +| RecordedFuture.PlaybookAlerts.panel_summary.summary.lifecycle_stage | String | The current lifecycle stage of the Playbook Alert. | +| RecordedFuture.PlaybookAlerts.panel_summary.summary.riskrules.rule | String | Name of the rule that triggered. | +| RecordedFuture.PlaybookAlerts.panel_summary.summary.riskrules.description | String | Short description of the trigger \(E.g 13 sightings on 1 source..\). | +| RecordedFuture.PlaybookAlerts.panel_summary.affected_products.name | String | Name of of affected product. | +| RecordedFuture.PlaybookAlerts.panel_summary.insikt_notes.id | String | The ID of the Insikt note. | +| RecordedFuture.PlaybookAlerts.panel_summary.insikt_notes.title | String | The title of the Insikt note. | +| RecordedFuture.PlaybookAlerts.panel_summary.insikt_notes.topic | String | The topic of the Insikt note. | +| RecordedFuture.PlaybookAlerts.panel_summary.insikt_notes.published | String | The time at which the Insikt note was published. | +| RecordedFuture.PlaybookAlerts.panel_summary.insikt_notes.fragment | String | A fragment of the Insikt note text. | +| RecordedFuture.PlaybookAlerts.panel_log.id | String | Log ID in Recorded Future. | +| RecordedFuture.PlaybookAlerts.panel_log.actor_id | String | ID of the actor. | +| RecordedFuture.PlaybookAlerts.panel_log.created | String | When was the log created. | +| RecordedFuture.PlaybookAlerts.panel_log.modified | String | When was the log last modified. | +| RecordedFuture.PlaybookAlerts.panel_log.action_priority | String | The priority of the Playbook alert. | +| RecordedFuture.PlaybookAlerts.panel_log.message | String | Log message. | +| RecordedFuture.PlaybookAlerts.panel_log.changes.assigne_change.old | String | Previous assignee. | +| RecordedFuture.PlaybookAlerts.panel_log.changes.assigne_change.new | String | New assignee. | +| RecordedFuture.PlaybookAlerts.panel_log.changes.assigne_change.type | String | Type of change. | +| RecordedFuture.PlaybookAlerts.panel_log.changes.status_change.old | String | Previous status. | +| RecordedFuture.PlaybookAlerts.panel_log.changes.status_change.new | String | New status. | +| RecordedFuture.PlaybookAlerts.panel_log.changes.status_change.type | String | Type of change. | +| RecordedFuture.PlaybookAlerts.panel_log.changes.title_change.old | String | Previous title. | +| RecordedFuture.PlaybookAlerts.panel_log.changes.title_change.new | String | New title. | +| RecordedFuture.PlaybookAlerts.panel_log.changes.title_change.type | String | Type of change. | +| RecordedFuture.PlaybookAlerts.panel_log.changes.priority_change.old | String | Previous priority. | +| RecordedFuture.PlaybookAlerts.panel_log.changes.priority_change.new | String | New priority. | +| RecordedFuture.PlaybookAlerts.panel_log.changes.priority_change.type | String | Type of change. | +| RecordedFuture.PlaybookAlerts.panel_log.changes.reopen_strategy_change.old | String | Previous reopen strategy. | +| RecordedFuture.PlaybookAlerts.panel_log.changes.reopen_strategy_change.new | String | New reopen strategy. | +| RecordedFuture.PlaybookAlerts.panel_log.changes.reopen_strategy_change.type | String | Type of change. | +| RecordedFuture.PlaybookAlerts.panel_log.changes.entities_change.removed | String | Removed entity. | +| RecordedFuture.PlaybookAlerts.panel_log.changes.entities_change.added | String | Added entity. | +| RecordedFuture.PlaybookAlerts.panel_log.changes.entities_change.type | String | Type of change. | +| RecordedFuture.PlaybookAlerts.panel_log.changes.related_entities_change.removed | String | Removed related entity. | +| RecordedFuture.PlaybookAlerts.panel_log.changes.related_entities_change.added | String | Added related entity. | +| RecordedFuture.PlaybookAlerts.panel_log.changes.related_entities_changetype | String | Type of change. | +| RecordedFuture.PlaybookAlerts.panel_log.changes.description_change.old | String | Previous description. | +| RecordedFuture.PlaybookAlerts.panel_log.changes.description_change.new | String | New description. | +| RecordedFuture.PlaybookAlerts.panel_log.changes.description_change.type | String | Type of change. | +| RecordedFuture.PlaybookAlerts.panel_log.changes.external_id_change.old | String | Previous external ID. | +| RecordedFuture.PlaybookAlerts.panel_log.changes.external_id_change.new | String | New external ID. | +| RecordedFuture.PlaybookAlerts.panel_log.changes.external_id_change.type | String | Type of change. | +| RecordedFuture.PlaybookAlerts.panel_action.action | String | The name of the action. | +| RecordedFuture.PlaybookAlerts.panel_action.updated | String | When was the action last updated. | +| RecordedFuture.PlaybookAlerts.panel_action.assignee_name | String | Full name of the assignee. | +| RecordedFuture.PlaybookAlerts.panel_action.assignee_id | String | ID of the assignee. | +| RecordedFuture.PlaybookAlerts.panel_action.status | String | The status of the action. | +| RecordedFuture.PlaybookAlerts.panel_action.description | String | A short description of the action. | +| RecordedFuture.PlaybookAlerts.panel_action.link | String | A link associated with the action. | +| RecordedFuture.PlaybookAlerts.panel_dns.ip_list.record | String | The DNS record. | +| RecordedFuture.PlaybookAlerts.panel_dns.ip_list.risk_score | String | Risk score associated with the record. | +| RecordedFuture.PlaybookAlerts.panel_dns.ip_list.criticality | String | The level of criticality. | +| RecordedFuture.PlaybookAlerts.panel_dns.ip_list.record_type | String | Type of record A, CNAME or MX. | +| RecordedFuture.PlaybookAlerts.panel_dns.ip_list.context_list.context | String | Labels of malicious behavior types that can be associated with an entity. | +| RecordedFuture.PlaybookAlerts.panel_dns.mx_list.record | String | The DNS record. | +| RecordedFuture.PlaybookAlerts.panel_dns.mx_list.risk_score | String | Risk score associated with the record. | +| RecordedFuture.PlaybookAlerts.panel_dns.mx_list.criticality | String | The level of criticality. | +| RecordedFuture.PlaybookAlerts.panel_dns.mx_list.record_type | String | Type of record A, CNAME or MX. | +| RecordedFuture.PlaybookAlerts.panel_dns.mx_list.context_list.context | String | Labels of malicious behavior types that can be associated with an entity. | +| RecordedFuture.PlaybookAlerts.panel_dns.ns_list.record | String | The DNS record. | +| RecordedFuture.PlaybookAlerts.panel_dns.ns_list.risk_score | String | Risk score associated with the record. | +| RecordedFuture.PlaybookAlerts.panel_dns.ns_list.criticality | String | The level of criticality. | +| RecordedFuture.PlaybookAlerts.panel_dns.ns_list.record_type | String | Type of record A, CNAME or MX. | +| RecordedFuture.PlaybookAlerts.panel_dns.ns_list.context_list.context | String | Labels of malicious behavior types that can be associated with an entity. | +| RecordedFuture.PlaybookAlerts.panel_whois.body.added | String | When the whois information was added. | +| RecordedFuture.PlaybookAlerts.panel_whois.body.attribute | String | Attribute, either whois or whoisContancts. | +| RecordedFuture.PlaybookAlerts.panel_whois.body.entity | String | ID of whois entity. | +| RecordedFuture.PlaybookAlerts.panel_whois.body.provider | String | Name of provider. | +| RecordedFuture.PlaybookAlerts.panel_whois.body.value.createdDate | String | When was it created. | +| RecordedFuture.PlaybookAlerts.panel_whois.body.value.nameServers | Array | List of name server IDs. | +| RecordedFuture.PlaybookAlerts.panel_whois.body.value.privateRegistration | Bool | Boolean indicating private registration. | +| RecordedFuture.PlaybookAlerts.panel_whois.body.value.registrarName | String | Name of the registrar. | +| RecordedFuture.PlaybookAlerts.panel_whois.body.value.status | String | Status of registrar. | +| RecordedFuture.PlaybookAlerts.panel_whois.body.value.city | String | Contact located in this city. | +| RecordedFuture.PlaybookAlerts.panel_whois.body.value.country | String | Contact located in this city. | +| RecordedFuture.PlaybookAlerts.panel_whois.body.value.name | String | Name of contact. | +| RecordedFuture.PlaybookAlerts.panel_whois.body.value.organization | String | Name of contact organization. | +| RecordedFuture.PlaybookAlerts.panel_whois.body.value.postalCode | String | Postal code of contact organization. | +| RecordedFuture.PlaybookAlerts.panel_whois.body.value.state | String | Contact located in state. | +| RecordedFuture.PlaybookAlerts.panel_whois.body.value.street1 | String | Street name of contact. | +| RecordedFuture.PlaybookAlerts.panel_whois.body.value.telephone | String | Phone number of contact. | +| RecordedFuture.PlaybookAlerts.panel_whois.body.value.type | String | Type of contact. | -### recordedfuture-playbook-alerts-search +### recordedfuture-playbook-alerts-update *** -Search playbook alerts based on filters +Update the status of one or multiple Playbook alerts #### Base Command -`recordedfuture-playbook-alerts-search` +`recordedfuture-playbook-alerts-update` #### Input -| **Argument Name** | **Description** | **Required** | -| --- | --- | --- | -| category | filter what playbook alert categories that is wanted. (default = all available). Possible values are: all_available, domain_abuse, vulnerability, code_repo_leakage. | Optional | -| limit | Limits the number of alerts to fetch. | Optional | -| time_since_update | Time between now and e.g. "2 hours" or "7 days" ago. | Optional | -| playbook_alert_status | Filter what statuses are fetched, defaults to only new status if not specified. Possible values are: new, in-progress, dismissed, resolved. | Optional | -| priority | Actions pritority assigned in Recorded Future. Possible values are: high, moderate, informational. | Optional | -| order_search_by | Actions pritority assigned in Recorded Future. Possible values are: updated, created. | Optional | +| **Argument Name** | **Description** | **Required** | +|-------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------| +| alert_ids | IDs of the playbook alerts that will be updated. | Required | +| new_status | New status to set for all alerts in alert_ids. Possible values are: new, in-progress, dismissed, resolved. | Required | +| comment | Add a comment to all alerts in alert_ids. | Optional | +| reopen | Set the reopen strategy for the alert. Reopen on significant updates or keep the alert Resolved. Default: reopen on significant updates. Can only be used with new_status=resolved. | Optional | ##### Command Example -```!recordedfuture-playbook-alerts-search``` -```!recordedfuture-playbook-alerts-search category=domain_abuse``` -```!recordedfuture-playbook-alerts-search category=vulnerability``` -```!recordedfuture-playbook-alerts-search limit=10``` -```!recordedfuture-playbook-alerts-search playbook_alert_status=in-progress``` -```!recordedfuture-playbook-alerts-search priority=high``` -```!recordedfuture-playbook-alerts-search order_search_by=updated``` - +```!recordedfuture-playbook-alerts-update alert_ids="12312312-1231-1231-1231-123123123123" new_status="New"``` #### Context Output -| **Path** | **Type** | **Description** | -| --- | --- | --- | -| RecordedFuture.PlaybookAlerts.playbook_alert_id | String | Unique id of the playbook alert | -| RecordedFuture.PlaybookAlerts.category | String | Playbook alert category | -| RecordedFuture.PlaybookAlerts.priority | String | Recommended Priority of the alert | -| RecordedFuture.PlaybookAlerts.status | String | Current alert status in Recorded Future | -| RecordedFuture.PlaybookAlerts.title | String | Title of the alert | -| RecordedFuture.PlaybookAlerts.updated | date | Date of last update | -| RecordedFuture.PlaybookAlerts.created | date | Date of creation | -| RecordedFuture.PlaybookAlerts.organization_id | String | Organization uhash | -| RecordedFuture.PlaybookAlerts.organization_name | String | Plaintext Organization name | -| RecordedFuture.PlaybookAlerts.assignee_id | String | uhash of the assigned user | -| RecordedFuture.PlaybookAlerts.assignee_name | unknown | name of the assigned user | -| RecordedFuture.PlaybookAlerts.owner_id | String | uhash of the enterprise that owns the alert | -| RecordedFuture.PlaybookAlerts.owner_name | String | Name of the enterprise that owns the alert | +| **Path** | **Type** | **Description** | +|-------------------------------------------------|----------|------------------------------------------------------| +| RecordedFuture.PlaybookAlerts.playbook_alert_id | string | Unique ID of the playbook alert in Recorded Future. | +| RecordedFuture.PlaybookAlerts.current_status | string | Current status of playbook alert in Recorded Future. | +| RecordedFuture.PlaybookAlerts.title | string | Title of the playbook alert in Recorded Future. | +| RecordedFuture.PlaybookAlerts.status_message | string | Message describing the outcome of the update. | + +--- \ No newline at end of file diff --git a/Packs/RecordedFuture/Integrations/RecordedFuturePlaybookAlerts/RecordedFuturePlaybookAlerts.py b/Packs/RecordedFuture/Integrations/RecordedFuturePlaybookAlerts/RecordedFuturePlaybookAlerts.py index 9b7a7b73645..41f0f672e53 100644 --- a/Packs/RecordedFuture/Integrations/RecordedFuturePlaybookAlerts/RecordedFuturePlaybookAlerts.py +++ b/Packs/RecordedFuture/Integrations/RecordedFuturePlaybookAlerts/RecordedFuturePlaybookAlerts.py @@ -1,10 +1,12 @@ import demistomock as demisto # noqa: F401 from CommonServerPython import * # noqa: F401 + """Recorded Future Playbook alerts Integration for Demisto.""" import platform import json import base64 +from typing import Any, Dict, List, Optional # flake8: noqa: F402,F405 lgtm @@ -13,7 +15,11 @@ # disable insecure warnings requests.packages.urllib3.disable_warnings() # type: ignore -__version__ = '1.0.2' +__version__ = "1.0.3" + +TIMEOUT_60 = 60 +TIMEOUT_90 = 90 +TIMEOUT_120 = 120 # === === === === === === === === === === === === === === === @@ -25,31 +31,40 @@ class Client(BaseClient): def whoami(self) -> Dict[str, Any]: return self._http_request( - method='get', - url_suffix='info/whoami', - timeout=60, + method="get", + url_suffix="info/whoami", + timeout=TIMEOUT_60, ) - def _call(self, url_suffix, **kwargs): + def _call(self, url_suffix: str, **kwargs): json_data = { - 'demisto_command': demisto.command(), - 'demisto_args': demisto.args(), + "demisto_command": demisto.command(), + "demisto_args": demisto.args(), + "demisto_params": demisto.params(), + "demisto_last_run": demisto.getLastRun(), } - if 'demisto_args' in kwargs.keys(): - if args := kwargs.get('demisto_args'): - json_data.update({'demisto_args': args}) - kwargs.pop('demisto_args') - method = kwargs.get('method', 'post') + overwrite_keys = ( + "demisto_command", + "demisto_args", + "demisto_params", + "demisto_last_run", + ) + for k in overwrite_keys: + if k in kwargs: + v = kwargs.pop(k) + json_data[k] = v + + method = kwargs.get("method", "post") request_kwargs = { - 'method': method, - 'url_suffix': url_suffix, - 'json_data': json_data, - 'timeout': 90, - 'retries': 3, - 'status_list_to_retry': STATUS_TO_RETRY, + "method": method, + "url_suffix": url_suffix, + "json_data": json_data, + "timeout": TIMEOUT_90, + "retries": 3, + "status_list_to_retry": STATUS_TO_RETRY, } request_kwargs.update(kwargs) @@ -57,69 +72,51 @@ def _call(self, url_suffix, **kwargs): try: response = self._http_request(**request_kwargs) - if isinstance(response, dict) and response.get('return_error'): + if isinstance(response, dict) and response.get("return_error"): # This will raise the Exception or call "demisto.results()" for the error and sys.exit(0). - return_error(**response['return_error']) + return_error(**response["return_error"]) + + return response except DemistoException as err: - if '404' in str(err): + if "404" in str(err): return CommandResults( - outputs_prefix='', - outputs=dict(), - raw_response=dict(), - readable_output='No results found.', - outputs_key_field='', + outputs_prefix="", + outputs={}, + raw_response={}, + readable_output="No results found.", + outputs_key_field="", ) + elif err.res is not None: + try: + error_response_json = err.res.json() + # This will raise the Exception or call "demisto.results()" for the error and sys.exit(0). + return_error(message=error_response_json["message"]) + except (json.JSONDecodeError, KeyError): + raise err else: raise err - return response + ####################################################### + ################## Playbook alerts #################### + ####################################################### def fetch_incidents(self) -> Dict[str, Any]: """Fetch incidents.""" return self._call( - url_suffix=f'/v2/playbook_alert/fetch', - json_data={ - 'demisto_command': demisto.command(), - 'demisto_args': demisto.args(), - 'demisto_params': demisto.params(), - 'demisto_last_run': demisto.getLastRun(), - }, - timeout=120, + url_suffix="/v2/playbook_alert/fetch", + timeout=TIMEOUT_120, ) - ####################################################### - ################## Playbook alerts #################### - ####################################################### + def search_playbook_alerts(self) -> Dict[str, Any]: + return self._call(url_suffix="/v2/playbook_alert/search") def details_playbook_alerts(self) -> Dict[str, Any]: - parsed_args = demisto.args() - if alert_ids := parsed_args.get('alert_ids'): - parsed_args['alert_ids'] = alert_ids.split(",") - if sections := parsed_args.get('detail_sections'): - parsed_args["detail_sections"] = sections.split(",") """Get details of a playbook alert""" - return self._call( - url_suffix='/v2/playbook_alert/lookup', demisto_args=parsed_args - ) + return self._call(url_suffix="/v2/playbook_alert/lookup") def update_playbook_alerts(self) -> Dict[str, Any]: - parsed_args = demisto.args() - if ids := parsed_args.get('alert_ids'): - parsed_args["alert_ids"] = ids.split(",") - return self._call( - url_suffix='/v2/playbook_alert/update', demisto_args=parsed_args - ) - - def search_playbook_alerts(self) -> Dict[str, Any]: - parsed_args = demisto.args() - if categories := parsed_args.get('category'): - parsed_args["category"] = categories.split(",") - if statuses := parsed_args.get('playbook_alert_status'): - parsed_args["playbook_alert_status"] = statuses.split(",") - return self._call( - url_suffix='/v2/playbook_alert/search', demisto_args=parsed_args - ) + return self._call(url_suffix="/v2/playbook_alert/update") # === === === === === === === === === === === === === === === @@ -142,18 +139,22 @@ def _process_result_actions( # In case API returned a str - we don't want to call "response.get()" on a str object. return None # type: ignore - result_actions: Union[List[dict], None] = response.get('result_actions') + result_actions: Union[List[dict], None] = response.get("result_actions") if not result_actions: return None # type: ignore command_results: List[CommandResults] = list() for action in result_actions: - if 'CommandResults' in action: - command_results.append(CommandResults(**action['CommandResults'])) + if "CommandResults" in action: + command_results.append(CommandResults(**action["CommandResults"])) return command_results + ####################################################### + ################## Playbook alerts #################### + ####################################################### + def fetch_incidents(self) -> None: response = self.client.fetch_incidents() @@ -163,47 +164,50 @@ def fetch_incidents(self) -> None: return for _key, _val in response.items(): - if _key == 'demisto_last_run': + if _key == "demisto_last_run": demisto.setLastRun(_val) - if _key == 'incidents': - for incident in _val: - attachments = list() - incident_json = json.loads(incident.get("rawJSON", "{}")) - if incident_json.get("panel_evidence_summary", {}).get( - "screenshots" - ): - for screenshot_data in incident_json["panel_evidence_summary"][ - "screenshots" - ]: - file_name = f'{screenshot_data.get("image_id", "").replace("img:","")}.png' - file_data = screenshot_data.get("base64", "") - file = fileResult(file_name, base64.b64decode(file_data)) - attachment = { - "description": screenshot_data.get('description'), - "name": file.get("File"), - "path": file.get("FileID"), - "showMediaFile": True, - } - attachments.append(attachment) - incident['attachment'] = attachments - + if _key == "incidents": + self._transform_incidents_attachments(_val) demisto.incidents(_val) ####################################################### ################## Playbook alerts #################### ####################################################### - def playbook_alert_details_command(self) -> List[CommandResults]: + def playbook_alert_search_command(self) -> Optional[List[CommandResults]]: + response = self.client.search_playbook_alerts() + return self._process_result_actions(response=response) + + def playbook_alert_details_command(self) -> Optional[List[CommandResults]]: response = self.client.details_playbook_alerts() return self._process_result_actions(response=response) - def playbook_alert_update_command(self) -> List[CommandResults]: + def playbook_alert_update_command(self) -> Optional[List[CommandResults]]: response = self.client.update_playbook_alerts() return self._process_result_actions(response=response) - def playbook_alert_search_command(self) -> List[CommandResults]: - response = self.client.search_playbook_alerts() - return self._process_result_actions(response=response) + @staticmethod + def _transform_incidents_attachments(incidents: list) -> None: + for incident in incidents: + attachments = [] + incident_json = json.loads(incident.get("rawJSON", "{}")) + if incident_json.get("panel_evidence_summary", {}).get("screenshots"): + for screenshot_data in incident_json["panel_evidence_summary"][ + "screenshots" + ]: + file_name = ( + f"{screenshot_data.get('image_id', '').replace('img:', '')}.png" + ) + file_data = screenshot_data.get("base64", "") + file = fileResult(file_name, base64.b64decode(file_data)) + attachment = { + "description": screenshot_data.get("description"), + "name": file.get("File"), + "path": file.get("FileID"), + "showMediaFile": True, + } + attachments.append(attachment) + incident["attachment"] = attachments # === === === === === === === === === === === === === === === @@ -211,70 +215,89 @@ def playbook_alert_search_command(self) -> List[CommandResults]: # === === === === === === === === === === === === === === === +def get_client() -> Client: + demisto_params = demisto.params() + base_url = demisto_params.get("server_url", "").rstrip("/") + verify_ssl = not demisto_params.get("unsecure", False) + handle_proxy() + + api_token = demisto_params["token"].get("password") + + if not api_token: + return_error(message="Please provide a valid API token") + + headers = { + "X-RFToken": api_token, + "X-RF-User-Agent": ( + f"RecordedFuturePlaybookAlerts.py/{__version__} ({platform.platform()}) " + f"(Cortex_XSOAR_{demisto.demistoVersion()['version']})" + ), + } + + client = Client( + base_url=base_url, + verify=verify_ssl, + headers=headers, + ) + + return client + + def main() -> None: """Main method used to run actions.""" try: - demisto_params = demisto.params() - base_url = demisto_params.get('server_url', '').rstrip('/') - verify_ssl = not demisto_params.get('insecure', False) - proxy = demisto_params.get('proxy', False) - - headers = { - 'X-RFToken': demisto_params['token'].get('password'), - 'X-RF-User-Agent': ( - f'RecordedFuturePlaybookAlerts.py/{__version__} ({platform.platform()}) ' - f'XSOAR/{__version__} ' - f'RFClient/{__version__} (Cortex_XSOAR_{demisto.demistoVersion()["version"]})' - ), - } - client = Client( - base_url=base_url, verify=verify_ssl, headers=headers, proxy=proxy - ) + client = get_client() command = demisto.command() actions = Actions(client) - if command == 'test-module': + if command == "test-module": # This is the call made when pressing the integration Test button. - # Returning 'ok' indicates that the integration works like it suppose to and + # Returning "ok" indicates that the integration works like it suppose to and # connection to the service is successful. - # Returning 'ok' will make the test result be green. + # Returning "ok" will make the test result be green. # Any other response will make the test result be red. try: client.whoami() - return_results('ok') + return_results("ok") except Exception as err: message = str(err) try: - error = json.loads(str(err).split('\n')[1]) - if 'fail' in error.get('result', dict()).get('status', ''): - message = error.get('result', dict())['message'] + error = json.loads(str(err).split("\n")[1]) + if "fail" in error.get("result", {}).get("status", ""): + message = error.get("result", {})["message"] except Exception: message = ( - 'Unknown error. Please verify that the API' - f' URL and Token are correctly configured. RAW Error: {err}' + "Unknown error. Please verify that the API" + f" URL and Token are correctly configured. RAW Error: {err}" ) - raise DemistoException(f'Failed due to - {message}') + raise DemistoException(f"Failed due to - {message}") - elif command == 'fetch-incidents': + elif command == "fetch-incidents": actions.fetch_incidents() ####################################################### ################## Playbook alerts #################### ####################################################### - elif command == 'recordedfuture-playbook-alerts-details': + elif command == "recordedfuture-playbook-alerts-search": + return_results(actions.playbook_alert_search_command()) + + elif command == "recordedfuture-playbook-alerts-details": return_results(actions.playbook_alert_details_command()) - elif command == 'recordedfuture-playbook-alerts-update': + elif command == "recordedfuture-playbook-alerts-update": return_results(actions.playbook_alert_update_command()) - elif command == 'recordedfuture-playbook-alerts-search': - return_results(actions.playbook_alert_search_command()) + else: + return_error(message=f"Unknown command: {command}") except Exception as e: - return_error(message=f'Failed to execute {demisto.command()} command: {str(e)}') + return_error( + message=f"Failed to execute {demisto.command()} command. Error: {str(e)}", + error=e, + ) -if __name__ in ('__main__', '__builtin__', 'builtins'): +if __name__ in ("__main__", "__builtin__", "builtins"): main() diff --git a/Packs/RecordedFuture/Integrations/RecordedFuturePlaybookAlerts/RecordedFuturePlaybookAlerts.yml b/Packs/RecordedFuture/Integrations/RecordedFuturePlaybookAlerts/RecordedFuturePlaybookAlerts.yml index d1e13ac7b8b..34400fc23d9 100644 --- a/Packs/RecordedFuture/Integrations/RecordedFuturePlaybookAlerts/RecordedFuturePlaybookAlerts.yml +++ b/Packs/RecordedFuture/Integrations/RecordedFuturePlaybookAlerts/RecordedFuturePlaybookAlerts.yml @@ -83,7 +83,7 @@ script: script: '-' type: python subtype: python3 - dockerimage: demisto/python3:3.10.14.91134 + dockerimage: demisto/python3:3.11.10.111039 commands: - name: recordedfuture-playbook-alerts-details description: Get Playbook alert details by id. @@ -92,7 +92,7 @@ script: required: true description: Ids of the playbook alert that should be fetched. - name: detail_sections - description: What evidence sections to include in the fetch, fetches all available if not specified. + description: What evidence sections to include in the fetch. Fetches all available if not specified. auto: PREDEFINED predefined: - status @@ -512,6 +512,16 @@ script: - in-progress - dismissed - resolved + - name: comment + description: Add comment to all alerts in alert_ids. + required: false + - name: reopen + description: 'Set the reopen strategy for the alert. Reopen on significant updates or keep the alert Resolved. Default: reopen on significant updates. Can only be used with new_status=resolved.' + required: false + auto: PREDEFINED + predefined: + - never + - significant_updates outputs: - contextPath: RecordedFuture.PlaybookAlerts.playbook_alert_id description: Unique id of the playbook alert in Recorded Future. @@ -530,7 +540,7 @@ script: description: Search playbook alerts based on filters. arguments: - name: category - description: filter what playbook alert categories that is wanted. (default = all available). + description: The playbook alert categories to retrieve. Default is all_available. auto: PREDEFINED predefined: - all_available @@ -538,9 +548,9 @@ script: - vulnerability - code_repo_leakage - name: limit - description: Limits the number of alerts to fetch. + description: The maximum number of alerts to fetch. - name: time_since_update - description: Time between now and e.g. "2 hours" or "7 days" ago. + description: The amount of time since the last update. E.g., "2 hours" or "7 days" ago. - name: playbook_alert_status auto: PREDEFINED predefined: @@ -548,7 +558,7 @@ script: - in-progress - dismissed - resolved - description: Filter what statuses are fetched, defaults to only new status if not specified. + description: The statuses to retrieve. Defaults to only new status if not specified. - name: priority auto: PREDEFINED predefined: @@ -561,7 +571,7 @@ script: predefined: - updated - created - description: Actions pritority assigned in Recorded Future. + description: The order by which to search for playbook alerts. outputs: - contextPath: RecordedFuture.PlaybookAlerts.playbook_alert_id description: Unique id of the playbook alert. diff --git a/Packs/RecordedFuture/Integrations/RecordedFuturePlaybookAlerts/RecordedFuturePlaybookAlerts_test.py b/Packs/RecordedFuture/Integrations/RecordedFuturePlaybookAlerts/RecordedFuturePlaybookAlerts_test.py index 6a99058efb4..09ce9bf1920 100644 --- a/Packs/RecordedFuture/Integrations/RecordedFuturePlaybookAlerts/RecordedFuturePlaybookAlerts_test.py +++ b/Packs/RecordedFuture/Integrations/RecordedFuturePlaybookAlerts/RecordedFuturePlaybookAlerts_test.py @@ -1,13 +1,16 @@ +import pytest + + def create_client(): import os from RecordedFuturePlaybookAlerts import Client, __version__ - base_url = 'https://api.recordedfuture.com/gw/xsoar/' + base_url = "https://api.recordedfuture.com/gw/xsoar/" verify_ssl = True - token = os.environ.get('RF_TOKEN') + token = os.environ.get("RF_TOKEN") headers = { - 'X-RFToken': token, - 'X-RF-User-Agent': f"RecordedFuturePlaybookAlerts.py/{__version__} (Linux-5.13.0-1031-aws-x86_64-with) " + "X-RFToken": token, + "X-RF-User-Agent": f"RecordedFuturePlaybookAlerts.py/{__version__} (Linux-5.13.0-1031-aws-x86_64-with) " "XSOAR/2.4 RFClient/2.4 (Cortex_XSOAR_6.5.0)", } @@ -18,13 +21,13 @@ class TestRFClient: def test_whoami(self, mocker): client = create_client() - mock_http_request = mocker.patch.object(client, '_http_request') + mock_http_request = mocker.patch.object(client, "_http_request") client.whoami() mock_http_request.assert_called_once_with( - method='get', - url_suffix='info/whoami', + method="get", + url_suffix="info/whoami", timeout=60, ) @@ -39,30 +42,36 @@ def test_call_with_kwargs(self, mocker): STATUS_TO_RETRY = [500, 501, 502, 503, 504] # This is needed for CommonServerPython module to not add demisto.params() into callingContext. - os.environ['COMMON_SERVER_NO_AUTO_PARAMS_REMOVE_NULLS'] = 'True' + os.environ["COMMON_SERVER_NO_AUTO_PARAMS_REMOVE_NULLS"] = "True" # Mock demisto command and args. - mock_command_name = 'command_name' - mock_command_args = {'arg1': 'arg1_value', 'arg2': 'arg2_value'} + mock_command_name = "command_name" + mock_command_args = {"arg1": "arg1_value", "arg2": "arg2_value"} + mock_params = {"param1": "param1 value"} + mock_last_run_dict = {"lastRun": "2022-08-31T12:12:20+00:00"} - mocker.patch.object(demisto, 'command', return_value=mock_command_name) - mocker.patch.object(demisto, 'args', return_value=mock_command_args) + mocker.patch.object(demisto, "command", return_value=mock_command_name) + mocker.patch.object(demisto, "args", return_value=mock_command_args) + mocker.patch.object(demisto, "params", return_value=mock_params) + mocker.patch.object(demisto, "getLastRun", return_value=mock_last_run_dict) client = create_client() - mock_http_request = mocker.patch.object(client, '_http_request') + mock_http_request = mocker.patch.object(client, "_http_request") - mock_url_suffix = 'mock_url_suffix' + mock_url_suffix = "mock_url_suffix" client._call(url_suffix=mock_url_suffix, timeout=120, any_other_kwarg=True) json_data = { - 'demisto_command': mock_command_name, - 'demisto_args': mock_command_args, + "demisto_command": mock_command_name, + "demisto_args": mock_command_args, + "demisto_last_run": mock_last_run_dict, + "demisto_params": mock_params, } mock_http_request.assert_called_once_with( - method='post', + method="post", url_suffix=mock_url_suffix, json_data=json_data, timeout=120, @@ -80,22 +89,26 @@ def test_call_returns_response(self, mocker): import demistomock as demisto # This is needed for CommonServerPython module to not add demisto.params() into callingContext. - os.environ['COMMON_SERVER_NO_AUTO_PARAMS_REMOVE_NULLS'] = 'True' + os.environ["COMMON_SERVER_NO_AUTO_PARAMS_REMOVE_NULLS"] = "True" # Mock demisto command and args. - mock_command_name = 'command_name' - mock_command_args = {'arg1': 'arg1_value', 'arg2': 'arg2_value'} + mock_command_name = "command_name" + mock_command_args = {"arg1": "arg1_value", "arg2": "arg2_value"} + mock_params = {"param1": "param1 value"} + mock_last_run_dict = {"lastRun": "2022-08-31T12:12:20+00:00"} - mocker.patch.object(demisto, 'command', return_value=mock_command_name) - mocker.patch.object(demisto, 'args', return_value=mock_command_args) + mocker.patch.object(demisto, "command", return_value=mock_command_name) + mocker.patch.object(demisto, "args", return_value=mock_command_args) + mocker.patch.object(demisto, "params", return_value=mock_params) + mocker.patch.object(demisto, "getLastRun", return_value=mock_last_run_dict) client = create_client() - mock_response = {'response': {'data': 'mock data'}} + mock_response = {"response": {"data": "mock data"}} - mocker.patch.object(client, '_http_request', return_value=mock_response) + mocker.patch.object(client, "_http_request", return_value=mock_response) - mock_url_suffix = 'mock_url_suffix' + mock_url_suffix = "mock_url_suffix" response = client._call(url_suffix=mock_url_suffix) assert response == mock_response @@ -111,36 +124,42 @@ def test_call_response_processing_return_error(self, mocker): STATUS_TO_RETRY = [500, 501, 502, 503, 504] # This is needed for CommonServerPython module to not add demisto.params() into callingContext. - os.environ['COMMON_SERVER_NO_AUTO_PARAMS_REMOVE_NULLS'] = 'True' + os.environ["COMMON_SERVER_NO_AUTO_PARAMS_REMOVE_NULLS"] = "True" # Mock demisto command and args. - mock_command_name = 'command_name' - mock_command_args = {'arg1': 'arg1_value', 'arg2': 'arg2_value'} + mock_command_name = "command_name" + mock_command_args = {"arg1": "arg1_value", "arg2": "arg2_value"} + mock_params = {"param1": "param1 value"} + mock_last_run_dict = {"lastRun": "2022-08-31T12:12:20+00:00"} - mocker.patch.object(demisto, 'command', return_value=mock_command_name) - mocker.patch.object(demisto, 'args', return_value=mock_command_args) + mocker.patch.object(demisto, "command", return_value=mock_command_name) + mocker.patch.object(demisto, "args", return_value=mock_command_args) + mocker.patch.object(demisto, "params", return_value=mock_params) + mocker.patch.object(demisto, "getLastRun", return_value=mock_last_run_dict) - mock_return_error = mocker.patch('RecordedFuturePlaybookAlerts.return_error') + mock_return_error = mocker.patch("RecordedFuturePlaybookAlerts.return_error") client = create_client() mock_http_request = mocker.patch.object( client, - '_http_request', - return_value={'return_error': {'message': 'mock error'}}, + "_http_request", + return_value={"return_error": {"message": "mock error"}}, ) - mock_url_suffix = 'mock_url_suffix' + mock_url_suffix = "mock_url_suffix" client._call(url_suffix=mock_url_suffix) json_data = { - 'demisto_command': mock_command_name, - 'demisto_args': mock_command_args, + "demisto_command": mock_command_name, + "demisto_args": mock_command_args, + "demisto_last_run": mock_last_run_dict, + "demisto_params": mock_params, } mock_http_request.assert_called_once_with( - method='post', + method="post", url_suffix=mock_url_suffix, json_data=json_data, timeout=90, @@ -148,7 +167,7 @@ def test_call_response_processing_return_error(self, mocker): status_list_to_retry=STATUS_TO_RETRY, ) - mock_return_error.assert_called_once_with(message='mock error') + mock_return_error.assert_called_once_with(message="mock error") def test_call_response_processing_404(self, mocker): """ @@ -162,39 +181,45 @@ def test_call_response_processing_404(self, mocker): STATUS_TO_RETRY = [500, 501, 502, 503, 504] # This is needed for CommonServerPython module to not add demisto.params() into callingContext. - os.environ['COMMON_SERVER_NO_AUTO_PARAMS_REMOVE_NULLS'] = 'True' + os.environ["COMMON_SERVER_NO_AUTO_PARAMS_REMOVE_NULLS"] = "True" # Mock demisto command and args. - mock_command_name = 'command_name' - mock_command_args = {'arg1': 'arg1_value', 'arg2': 'arg2_value'} + mock_command_name = "command_name" + mock_command_args = {"arg1": "arg1_value", "arg2": "arg2_value"} + mock_params = {"param1": "param1 value"} + mock_last_run_dict = {"lastRun": "2022-08-31T12:12:20+00:00"} - mocker.patch.object(demisto, 'command', return_value=mock_command_name) - mocker.patch.object(demisto, 'args', return_value=mock_command_args) + mocker.patch.object(demisto, "command", return_value=mock_command_name) + mocker.patch.object(demisto, "args", return_value=mock_command_args) + mocker.patch.object(demisto, "params", return_value=mock_params) + mocker.patch.object(demisto, "getLastRun", return_value=mock_last_run_dict) - mocker.patch('RecordedFuturePlaybookAlerts.return_error') + mocker.patch("RecordedFuturePlaybookAlerts.return_error") client = create_client() def mock_http_request_method(*args, **kwargs): # Imitate how CommonServerPython handles bad responses (when status code not in ok_codes, # or if ok_codes=None - it uses requests.Response.ok to check whether response is good). - raise DemistoException('404') + raise DemistoException("404") - mocker.patch.object(client, '_http_request', mock_http_request_method) + mocker.patch.object(client, "_http_request", mock_http_request_method) - spy_http_request = mocker.spy(client, '_http_request') + spy_http_request = mocker.spy(client, "_http_request") - mock_url_suffix = 'mock_url_suffix' + mock_url_suffix = "mock_url_suffix" result = client._call(url_suffix=mock_url_suffix) json_data = { - 'demisto_command': mock_command_name, - 'demisto_args': mock_command_args, + "demisto_command": mock_command_name, + "demisto_args": mock_command_args, + "demisto_last_run": mock_last_run_dict, + "demisto_params": mock_params, } spy_http_request.assert_called_once_with( - method='post', + method="post", url_suffix=mock_url_suffix, json_data=json_data, timeout=90, @@ -204,49 +229,42 @@ def mock_http_request_method(*args, **kwargs): assert isinstance(result, CommandResults) - assert result.outputs_prefix == '' - assert result.outputs_key_field == '' - assert result.outputs == dict() - assert result.raw_response == dict() - assert result.readable_output == 'No results found.' + assert result.outputs_prefix == "" + assert result.outputs_key_field == "" + assert result.outputs == {} + assert result.raw_response == {} + assert result.readable_output == "No results found." def test_fetch_incidents(self, mocker): import os import demistomock as demisto # This is needed for CommonServerPython module to not add demisto.params() into callingContext. - os.environ['COMMON_SERVER_NO_AUTO_PARAMS_REMOVE_NULLS'] = 'True' + os.environ["COMMON_SERVER_NO_AUTO_PARAMS_REMOVE_NULLS"] = "True" # Mock demisto command and args. - mock_command_name = 'command_name' - mock_command_args = {'arg1': 'arg1_value', 'arg2': 'arg2_value'} - mock_params = {'param1': 'param1 value'} - - mocker.patch.object(demisto, 'command', return_value=mock_command_name) - mocker.patch.object(demisto, 'args', return_value=mock_command_args) - mocker.patch.object(demisto, 'params', return_value=mock_params) - + mock_command_name = "command_name" + mock_command_args = {"arg1": "arg1_value", "arg2": "arg2_value"} + mock_params = {"param1": "param1 value"} mock_last_run_dict = {"lastRun": "2022-08-31T12:12:20+00:00"} - mocker.patch.object(demisto, 'getLastRun', return_value=mock_last_run_dict) + + mocker.patch.object(demisto, "command", return_value=mock_command_name) + mocker.patch.object(demisto, "args", return_value=mock_command_args) + mocker.patch.object(demisto, "params", return_value=mock_params) + mocker.patch.object(demisto, "getLastRun", return_value=mock_last_run_dict) client = create_client() - mock_call_response = {'response': {'data': 'mock response'}} + mock_call_response = {"response": {"data": "mock response"}} mock_call = mocker.patch.object( - client, '_call', return_value=mock_call_response + client, "_call", return_value=mock_call_response ) response = client.fetch_incidents() mock_call.assert_called_once_with( - json_data={ - 'demisto_command': mock_command_name, - 'demisto_args': mock_command_args, - 'demisto_last_run': mock_last_run_dict, - 'demisto_params': mock_params, - }, timeout=120, - url_suffix='/v2/playbook_alert/fetch', + url_suffix="/v2/playbook_alert/fetch", ) assert response == mock_call_response @@ -256,27 +274,29 @@ def test_playbook_alert_search(self, mocker): import demistomock as demisto # This is needed for CommonServerPython module to not add demisto.params() into callingContext. - os.environ['COMMON_SERVER_NO_AUTO_PARAMS_REMOVE_NULLS'] = 'True' + os.environ["COMMON_SERVER_NO_AUTO_PARAMS_REMOVE_NULLS"] = "True" # Mock demisto command and args. - mock_command_name = 'command_name' - mock_command_args = {'arg1': 'arg1_value', 'arg2': 'arg2_value'} + mock_command_name = "command_name" + mock_command_args = {"arg1": "arg1_value", "arg2": "arg2_value"} + mock_params = {"param1": "param1 value"} + mock_last_run_dict = {"lastRun": "2022-08-31T12:12:20+00:00"} - mocker.patch.object(demisto, 'command', return_value=mock_command_name) - mocker.patch.object(demisto, 'args', return_value=mock_command_args) + mocker.patch.object(demisto, "command", return_value=mock_command_name) + mocker.patch.object(demisto, "args", return_value=mock_command_args) + mocker.patch.object(demisto, "params", return_value=mock_params) + mocker.patch.object(demisto, "getLastRun", return_value=mock_last_run_dict) client = create_client() - mock_call_response = {'response': {'data': 'mock response'}} + mock_call_response = {"response": {"data": "mock response"}} mock_call = mocker.patch.object( - client, '_call', return_value=mock_call_response + client, "_call", return_value=mock_call_response ) response = client.search_playbook_alerts() - mock_call.assert_called_once_with( - demisto_args=mock_command_args, url_suffix='/v2/playbook_alert/search' - ) + mock_call.assert_called_once_with(url_suffix="/v2/playbook_alert/search") assert response == mock_call_response @@ -284,32 +304,35 @@ def test_playbook_alert_details_multi_input(self, mocker): import os import demistomock as demisto - os.environ['COMMON_SERVER_NO_AUTO_PARAMS_REMOVE_NULLS'] = 'True' + os.environ["COMMON_SERVER_NO_AUTO_PARAMS_REMOVE_NULLS"] = "True" - mock_command_name = 'command_name' + mock_command_name = "command_name" mock_alert_ids = "input1,mock_value" mock_detail_sections = "input1,mock_value" mock_command_args = { - 'alert_ids': mock_alert_ids, - 'detail_sections': mock_detail_sections, + "alert_ids": mock_alert_ids, + "detail_sections": mock_detail_sections, } - mock_args_processed = {k: v.split(",") for k, v in mock_command_args.items()} + # mock_args_processed = {k: v.split(",") for k, v in mock_command_args.items()} - mocker.patch.object(demisto, 'command', return_value=mock_command_name) - mocker.patch.object(demisto, 'args', return_value=mock_command_args) + mock_params = {"param1": "param1 value"} + mock_last_run_dict = {"lastRun": "2022-08-31T12:12:20+00:00"} + + mocker.patch.object(demisto, "command", return_value=mock_command_name) + mocker.patch.object(demisto, "args", return_value=mock_command_args) + mocker.patch.object(demisto, "params", return_value=mock_params) + mocker.patch.object(demisto, "getLastRun", return_value=mock_last_run_dict) client = create_client() mock_call_response = {"resonse": {"data": "mock respose"}} mock_call = mocker.patch.object( - client, '_call', return_value=mock_call_response + client, "_call", return_value=mock_call_response ) response = client.details_playbook_alerts() - mock_call.assert_called_once_with( - demisto_args=mock_args_processed, url_suffix='/v2/playbook_alert/lookup' - ) + mock_call.assert_called_once_with(url_suffix="/v2/playbook_alert/lookup") assert response == mock_call_response @@ -317,29 +340,32 @@ def test_playbook_alert_update_multi_input(self, mocker): import os import demistomock as demisto - os.environ['COMMON_SERVER_NO_AUTO_PARAMS_REMOVE_NULLS'] = 'True' + os.environ["COMMON_SERVER_NO_AUTO_PARAMS_REMOVE_NULLS"] = "True" - mock_command_name = 'command_name' + mock_command_name = "command_name" mock_alert_ids = "input1,input2" - mock_command_args = {'alert_ids': mock_alert_ids} - mock_args_processed = {k: v.split(",") for k, v in mock_command_args.items()} + mock_command_args = {"alert_ids": mock_alert_ids} + # mock_args_processed = {k: v.split(",") for k, v in mock_command_args.items()} - mocker.patch.object(demisto, 'command', return_value=mock_command_name) - mocker.patch.object(demisto, 'args', return_value=mock_command_args) + mock_params = {"param1": "param1 value"} + mock_last_run_dict = {"lastRun": "2022-08-31T12:12:20+00:00"} + + mocker.patch.object(demisto, "command", return_value=mock_command_name) + mocker.patch.object(demisto, "args", return_value=mock_command_args) + mocker.patch.object(demisto, "params", return_value=mock_params) + mocker.patch.object(demisto, "getLastRun", return_value=mock_last_run_dict) client = create_client() mock_call_response = {"resonse": {"data": "mock respose"}} mock_call = mocker.patch.object( - client, '_call', return_value=mock_call_response + client, "_call", return_value=mock_call_response ) response = client.update_playbook_alerts() - mock_call.assert_called_once_with( - demisto_args=mock_args_processed, url_suffix='/v2/playbook_alert/update' - ) + mock_call.assert_called_once_with(url_suffix="/v2/playbook_alert/update") assert response == mock_call_response @@ -347,32 +373,35 @@ def test_playbook_alert_search_multi_input(self, mocker): import os import demistomock as demisto - os.environ['COMMON_SERVER_NO_AUTO_PARAMS_REMOVE_NULLS'] = 'True' + os.environ["COMMON_SERVER_NO_AUTO_PARAMS_REMOVE_NULLS"] = "True" - mock_command_name = 'command_name' + mock_command_name = "command_name" mock_alert_ids = "ajdaojw,1woodaw" mock_detail_sections = "sdadwa,adinhw0ijd" mock_command_args = { - 'category': mock_alert_ids, - 'playbook_alert_status': mock_detail_sections, + "category": mock_alert_ids, + "playbook_alert_status": mock_detail_sections, } - mock_args_processed = {k: v.split(",") for k, v in mock_command_args.items()} + # mock_args_processed = {k: v.split(",") for k, v in mock_command_args.items()} + + mock_params = {"param1": "param1 value"} + mock_last_run_dict = {"lastRun": "2022-08-31T12:12:20+00:00"} - mocker.patch.object(demisto, 'command', return_value=mock_command_name) - mocker.patch.object(demisto, 'args', return_value=mock_command_args) + mocker.patch.object(demisto, "command", return_value=mock_command_name) + mocker.patch.object(demisto, "args", return_value=mock_command_args) + mocker.patch.object(demisto, "params", return_value=mock_params) + mocker.patch.object(demisto, "getLastRun", return_value=mock_last_run_dict) client = create_client() mock_call_response = {"resonse": {"data": "mock respose"}} mock_call = mocker.patch.object( - client, '_call', return_value=mock_call_response + client, "_call", return_value=mock_call_response ) response = client.search_playbook_alerts() - mock_call.assert_called_once_with( - demisto_args=mock_args_processed, url_suffix='/v2/playbook_alert/search' - ) + mock_call.assert_called_once_with(url_suffix="/v2/playbook_alert/search") assert response == mock_call_response @@ -381,27 +410,29 @@ def test_playbook_alert_details(self, mocker): import demistomock as demisto # This is needed for CommonServerPython module to not add demisto.params() into callingContext. - os.environ['COMMON_SERVER_NO_AUTO_PARAMS_REMOVE_NULLS'] = 'True' + os.environ["COMMON_SERVER_NO_AUTO_PARAMS_REMOVE_NULLS"] = "True" # Mock demisto command and args. - mock_command_name = 'command_name' - mock_command_args = {'arg1': 'arg1_value', 'arg2': 'arg2_value'} + mock_command_name = "command_name" + mock_command_args = {"arg1": "arg1_value", "arg2": "arg2_value"} + mock_params = {"param1": "param1 value"} + mock_last_run_dict = {"lastRun": "2022-08-31T12:12:20+00:00"} - mocker.patch.object(demisto, 'command', return_value=mock_command_name) - mocker.patch.object(demisto, 'args', return_value=mock_command_args) + mocker.patch.object(demisto, "command", return_value=mock_command_name) + mocker.patch.object(demisto, "args", return_value=mock_command_args) + mocker.patch.object(demisto, "params", return_value=mock_params) + mocker.patch.object(demisto, "getLastRun", return_value=mock_last_run_dict) client = create_client() - mock_call_response = {'response': {'data': 'mock response'}} + mock_call_response = {"response": {"data": "mock response"}} mock_call = mocker.patch.object( - client, '_call', return_value=mock_call_response + client, "_call", return_value=mock_call_response ) response = client.details_playbook_alerts() - mock_call.assert_called_once_with( - demisto_args=mock_command_args, url_suffix='/v2/playbook_alert/lookup' - ) + mock_call.assert_called_once_with(url_suffix="/v2/playbook_alert/lookup") assert response == mock_call_response @@ -410,30 +441,93 @@ def test_playbook_alert_update(self, mocker): import demistomock as demisto # This is needed for CommonServerPython module to not add demisto.params() into callingContext. - os.environ['COMMON_SERVER_NO_AUTO_PARAMS_REMOVE_NULLS'] = 'True' + os.environ["COMMON_SERVER_NO_AUTO_PARAMS_REMOVE_NULLS"] = "True" # Mock demisto command and args. - mock_command_name = 'command_name' - mock_command_args = {'arg1': 'arg1_value', 'arg2': 'arg2_value'} + mock_command_name = "command_name" + mock_command_args = {"arg1": "arg1_value", "arg2": "arg2_value"} + mock_params = {"param1": "param1 value"} + mock_last_run_dict = {"lastRun": "2022-08-31T12:12:20+00:00"} - mocker.patch.object(demisto, 'command', return_value=mock_command_name) - mocker.patch.object(demisto, 'args', return_value=mock_command_args) + mocker.patch.object(demisto, "command", return_value=mock_command_name) + mocker.patch.object(demisto, "args", return_value=mock_command_args) + mocker.patch.object(demisto, "params", return_value=mock_params) + mocker.patch.object(demisto, "getLastRun", return_value=mock_last_run_dict) client = create_client() - mock_call_response = {'response': {'data': 'mock response'}} + mock_call_response = {"response": {"data": "mock response"}} mock_call = mocker.patch.object( - client, '_call', return_value=mock_call_response + client, "_call", return_value=mock_call_response ) response = client.update_playbook_alerts() - mock_call.assert_called_once_with( - demisto_args=mock_command_args, url_suffix='/v2/playbook_alert/update' - ) + mock_call.assert_called_once_with(url_suffix="/v2/playbook_alert/update") assert response == mock_call_response + def test_call_DemistoException_res_json_error(self, mocker): + """Test _call when err.res.json() raises an exception.""" + import demistomock as demisto + from CommonServerPython import DemistoException + import json + + client = create_client() + + mock_command_name = "command_name" + mock_command_args = {} + mock_params = {} + mock_last_run_dict = {} + + mocker.patch.object(demisto, "command", return_value=mock_command_name) + mocker.patch.object(demisto, "args", return_value=mock_command_args) + mocker.patch.object(demisto, "params", return_value=mock_params) + mocker.patch.object(demisto, "getLastRun", return_value=mock_last_run_dict) + + class MockResponse: + def json(self): + raise json.JSONDecodeError("Expecting value", "doc", 0) + + def mock_http_request(*args, **kwargs): + err = DemistoException("Error with response") + err.res = MockResponse() + raise err + + mocker.patch.object(client, "_http_request", side_effect=mock_http_request) + + with pytest.raises(DemistoException): + client._call(url_suffix="mock_url_suffix") + + def test_call_DemistoException_res_None(self, mocker): + """Test _call when DemistoException has no response.""" + import demistomock as demisto + from CommonServerPython import DemistoException + + client = create_client() + + mock_command_name = "command_name" + mock_command_args = {} + mock_params = {} + mock_last_run_dict = {} + + mocker.patch.object(demisto, "command", return_value=mock_command_name) + mocker.patch.object(demisto, "args", return_value=mock_command_args) + mocker.patch.object(demisto, "params", return_value=mock_params) + mocker.patch.object(demisto, "getLastRun", return_value=mock_last_run_dict) + + def mock_http_request(*args, **kwargs): + err = DemistoException("Some error without response") + err.res = None + raise err + + mocker.patch.object(client, "_http_request", side_effect=mock_http_request) + + with pytest.raises(DemistoException) as excinfo: + client._call(url_suffix="mock_url_suffix") + + assert str(excinfo.value) == "Some error without response" + class TestActions: def test_init(self, mocker): @@ -452,7 +546,7 @@ def test_process_result_actions_404(self, mocker): # Test if response is CommandResults # (case when we got 404 on response, and it was processed in self.client._call() method). - response = CommandResults(readable_output='Mock') + response = CommandResults(readable_output="Mock") result_actions = actions._process_result_actions(response=response) assert result_actions == [response] @@ -463,7 +557,7 @@ def test_process_result_actions_response_is_not_dict(self, mocker): actions = Actions(mock_client) # Test if response is not CommandResults and not Dict. - response = 'Mock string - not CommandResults and not dict' + response = "Mock string - not CommandResults and not dict" result_actions = actions._process_result_actions(response=response) # type: ignore assert result_actions is None @@ -476,20 +570,20 @@ def test_process_result_actions_no_or_empty_result_actions_in_response( actions = Actions(mock_client) # Test no results_actions in response. - response = {'data': 'mock'} + response = {"data": "mock"} result_actions = actions._process_result_actions(response=response) assert result_actions is None # Test case when bool(results_actions) in response is False. - response = {'data': 'mock', 'result_actions': None} + response = {"data": "mock", "result_actions": None} result_actions = actions._process_result_actions(response=response) assert result_actions is None - response = {'data': 'mock', 'result_actions': list()} + response = {"data": "mock", "result_actions": []} result_actions = actions._process_result_actions(response=response) assert result_actions is None - response = {'data': 'mock', 'result_actions': dict()} + response = {"data": "mock", "result_actions": {}} result_actions = actions._process_result_actions(response=response) assert result_actions is None @@ -500,15 +594,15 @@ def test_process_result_actions_command_results_only(self, mocker): actions = Actions(mock_client) response = { - 'data': 'mock', - 'result_actions': [ + "data": "mock", + "result_actions": [ { - 'CommandResults': { - 'outputs_prefix': 'mock_outputs_prefix', - 'outputs': 'mock_outputs', - 'raw_response': 'mock_raw_response', - 'readable_output': 'mock_readable_output', - 'outputs_key_field': 'mock_outputs_key_field', + "CommandResults": { + "outputs_prefix": "mock_outputs_prefix", + "outputs": "mock_outputs", + "raw_response": "mock_raw_response", + "readable_output": "mock_readable_output", + "outputs_key_field": "mock_outputs_key_field", }, } ], @@ -521,11 +615,11 @@ def test_process_result_actions_command_results_only(self, mocker): assert isinstance(r_a, CommandResults) - assert r_a.outputs_prefix == 'mock_outputs_prefix' - assert r_a.outputs == 'mock_outputs' - assert r_a.raw_response == 'mock_raw_response' - assert r_a.readable_output == 'mock_readable_output' - assert r_a.outputs_key_field == 'mock_outputs_key_field' + assert r_a.outputs_prefix == "mock_outputs_prefix" + assert r_a.outputs == "mock_outputs" + assert r_a.raw_response == "mock_raw_response" + assert r_a.readable_output == "mock_readable_output" + assert r_a.outputs_key_field == "mock_outputs_key_field" def test_fetch_incidents_with_attachment(self, mocker): from RecordedFuturePlaybookAlerts import Actions @@ -539,33 +633,33 @@ def test_fetch_incidents_with_attachment(self, mocker): "screenshots": [ { "image_id": "an_id", - "base64": 'YWJhc2U2NHN0cmluZw==', + "base64": "YWJhc2U2NHN0cmluZw==", "description": "vivid description of image", } ] } } mock_incidents_value = { - 'name': 'incident_name', + "name": "incident_name", "rawJSON": json.dumps(screenshot_dict), } - mock_demisto_last_run_value = 'mock_demisto_last_run' + mock_demisto_last_run_value = "mock_demisto_last_run" mock_client_fetch_incidents_response = { - 'incidents': [mock_incidents_value], - 'demisto_last_run': mock_demisto_last_run_value, + "incidents": [mock_incidents_value], + "demisto_last_run": mock_demisto_last_run_value, } mock_client_fetch_incidents = mocker.patch.object( - client, 'fetch_incidents', return_value=mock_client_fetch_incidents_response + client, "fetch_incidents", return_value=mock_client_fetch_incidents_response ) - mock_demisto_incidents = mocker.patch.object(demisto, 'incidents') - mock_demisto_set_last_run = mocker.patch.object(demisto, 'setLastRun') + mock_demisto_incidents = mocker.patch.object(demisto, "incidents") + mock_demisto_set_last_run = mocker.patch.object(demisto, "setLastRun") mock_file_result = mocker.patch.object( csp, - 'fileResult', + "fileResult", return_value={"File": "mockfilepath", "FileID": "mock_file_id"}, ) @@ -594,26 +688,26 @@ def test_fetch_incidents_with_incidents_present(self, mocker): client = create_client() mock_incidents_value = [ - {'mock_incident_key1': 'mock_incident_value1'}, - {'mock_incident_key2': 'mock_incident_value2'}, + {"mock_incident_key1": "mock_incident_value1"}, + {"mock_incident_key2": "mock_incident_value2"}, ] - mock_demisto_last_run_value = 'mock_demisto_last_run' + mock_demisto_last_run_value = "mock_demisto_last_run" - mock_alerts_update_data_value = 'mock_alerts_update_data_value' + mock_alerts_update_data_value = "mock_alerts_update_data_value" mock_client_fetch_incidents_response = { - 'incidents': mock_incidents_value, - 'demisto_last_run': mock_demisto_last_run_value, - 'data': 'mock', - 'alerts_update_data': mock_alerts_update_data_value, + "incidents": mock_incidents_value, + "demisto_last_run": mock_demisto_last_run_value, + "data": "mock", + "alerts_update_data": mock_alerts_update_data_value, } mock_client_fetch_incidents = mocker.patch.object( - client, 'fetch_incidents', return_value=mock_client_fetch_incidents_response + client, "fetch_incidents", return_value=mock_client_fetch_incidents_response ) - mock_demisto_incidents = mocker.patch.object(demisto, 'incidents') - mock_demisto_set_last_run = mocker.patch.object(demisto, 'setLastRun') + mock_demisto_incidents = mocker.patch.object(demisto, "incidents") + mock_demisto_set_last_run = mocker.patch.object(demisto, "setLastRun") actions = Actions(client) @@ -629,20 +723,20 @@ def test_playbook_alert_details_command_with_result_actions(self, mocker): client = create_client() - mock_response = 'mock_response' + mock_response = "mock_response" mock_client_playbook_alert_details = mocker.patch.object( - client, 'details_playbook_alerts', return_value=mock_response + client, "details_playbook_alerts", return_value=mock_response ) actions = Actions(client) mock_process_result_actions_return_value = ( - 'mock_process_result_actions_return_value' + "mock_process_result_actions_return_value" ) mock_process_result_actions = mocker.patch.object( actions, - '_process_result_actions', + "_process_result_actions", return_value=mock_process_result_actions_return_value, ) @@ -660,10 +754,10 @@ def test_playbook_alert_details_command_without_result_actions(self, mocker): client = create_client() - mock_response = 'mock_response' + mock_response = "mock_response" mock_client_playbook_alert_details = mocker.patch.object( - client, 'details_playbook_alerts', return_value=mock_response + client, "details_playbook_alerts", return_value=mock_response ) actions = Actions(client) @@ -671,7 +765,7 @@ def test_playbook_alert_details_command_without_result_actions(self, mocker): mock_process_result_actions_return_value = None mock_process_result_actions = mocker.patch.object( actions, - '_process_result_actions', + "_process_result_actions", return_value=mock_process_result_actions_return_value, ) @@ -686,10 +780,10 @@ def test_playbook_alert_search_command_without_result_actions(self, mocker): client = create_client() - mock_response = 'mock_response' + mock_response = "mock_response" mock_client_playbook_alert_search = mocker.patch.object( - client, 'search_playbook_alerts', return_value=mock_response + client, "search_playbook_alerts", return_value=mock_response ) actions = Actions(client) @@ -697,7 +791,7 @@ def test_playbook_alert_search_command_without_result_actions(self, mocker): mock_process_result_actions_return_value = None mock_process_result_actions = mocker.patch.object( actions, - '_process_result_actions', + "_process_result_actions", return_value=mock_process_result_actions_return_value, ) @@ -712,20 +806,20 @@ def test_playbook_alert_update_command(self, mocker): client = create_client() - mock_response = 'mock_response' + mock_response = "mock_response" mock_client_alert_set_status = mocker.patch.object( - client, 'update_playbook_alerts', return_value=mock_response + client, "update_playbook_alerts", return_value=mock_response ) actions = Actions(client) mock_process_result_actions_return_value = ( - 'mock_process_result_actions_return_value' + "mock_process_result_actions_return_value" ) mock_process_result_actions = mocker.patch.object( actions, - '_process_result_actions', + "_process_result_actions", return_value=mock_process_result_actions_return_value, ) @@ -755,7 +849,7 @@ def test_test_module(self, mocker): RecordedFuturePlaybookAlerts, "return_results" ) RecordedFuturePlaybookAlerts.main() - mocked_return_res.assert_called_with('ok') + mocked_return_res.assert_called_with("ok") def test_test_module_with_boom(self, mocker): import RecordedFuturePlaybookAlerts @@ -778,8 +872,117 @@ def test_test_module_with_boom(self, mocker): RecordedFuturePlaybookAlerts.main() mocked_return_err.assert_called_with( message=( - f'Failed to execute {demisto.command()} command: Failed due to - ' - 'Unknown error. Please verify that the API URL and Token are correctly configured. ' - 'RAW Error: Side effect triggered' - ) + f"Failed to execute {demisto.command()} command. Error: Failed due to - " + "Unknown error. Please verify that the API URL and Token are correctly configured. " + "RAW Error: Side effect triggered" + ), + error=mocker.ANY, + ) + + def test_transform_incidents_attachments_without_screenshots(self, mocker): + """Test transforming incidents without screenshots.""" + from RecordedFuturePlaybookAlerts import Actions + import json + + incidents = [{"rawJSON": json.dumps({"panel_evidence_summary": {}})}] + + mock_fileResult = mocker.patch("RecordedFuturePlaybookAlerts.fileResult") + + Actions._transform_incidents_attachments(incidents) + + assert "attachment" not in incidents[0] + mock_fileResult.assert_not_called() + + def test_process_result_actions_with_invalid_actions(self, mocker): + """Test processing result actions with invalid keys.""" + from RecordedFuturePlaybookAlerts import Actions + from CommonServerPython import CommandResults + + actions = Actions(rf_client=None) + + response = { + "result_actions": [ + {"InvalidKey": {}}, + { + "CommandResults": { + "outputs_prefix": "mock_prefix", + "outputs": "mock_outputs", + } + }, + { + "CommandResults": { + "outputs_prefix": "another_prefix", + "outputs": "another_outputs", + } + }, + ] + } + + result = actions._process_result_actions(response) + + assert len(result) == 2 + assert isinstance(result[0], CommandResults) + assert result[0].outputs_prefix == "mock_prefix" + assert result[0].outputs == "mock_outputs" + assert isinstance(result[1], CommandResults) + assert result[1].outputs_prefix == "another_prefix" + assert result[1].outputs == "another_outputs" + + +class TestMain: + def test_main_with_unknown_command(self, mocker): + """Test main function with an unknown command.""" + import RecordedFuturePlaybookAlerts + import demistomock as demisto + + mocker.patch.object(demisto, "command", return_value="unknown-command") + mock_return_error = mocker.patch("RecordedFuturePlaybookAlerts.return_error") + mock_get_client = mocker.patch("RecordedFuturePlaybookAlerts.get_client") + + RecordedFuturePlaybookAlerts.main() + + mock_get_client.assert_called_once() + mock_return_error.assert_called_once_with( + message="Unknown command: unknown-command" + ) + + def test_get_client_no_api_token(self, mocker): + """Test get_client when no API token is provided.""" + import RecordedFuturePlaybookAlerts + import demistomock as demisto + + mock_params = { + "server_url": "https://api.recordedfuture.com/gw/xsoar/", + "unsecure": False, + "token": {"password": None}, + } + mocker.patch.object(demisto, "params", return_value=mock_params) + mock_return_error = mocker.patch("RecordedFuturePlaybookAlerts.return_error") + + RecordedFuturePlaybookAlerts.get_client() + + mock_return_error.assert_called_once_with( + message="Please provide a valid API token" + ) + + def test_main_exception_handling(self, mocker): + """Test main function's exception handling.""" + import RecordedFuturePlaybookAlerts + import demistomock as demisto + + mocker.patch.object(demisto, "command", return_value="test-module") + mock_get_client = mocker.patch("RecordedFuturePlaybookAlerts.get_client") + mock_get_client.return_value.whoami.side_effect = Exception("Test exception") + mock_return_error = mocker.patch("RecordedFuturePlaybookAlerts.return_error") + + RecordedFuturePlaybookAlerts.main() + + mock_get_client.assert_called_once() + mock_return_error.assert_called_once_with( + message=( + f"Failed to execute {demisto.command()} command. Error: Failed due to - " + "Unknown error. Please verify that the API URL and Token are correctly configured. " + "RAW Error: Test exception" + ), + error=mocker.ANY, ) diff --git a/Packs/RecordedFuture/ReleaseNotes/1_8_0.md b/Packs/RecordedFuture/ReleaseNotes/1_8_0.md new file mode 100644 index 00000000000..a6d0556e2aa --- /dev/null +++ b/Packs/RecordedFuture/ReleaseNotes/1_8_0.md @@ -0,0 +1,27 @@ +#### Integrations + +##### Recorded Future v2 + +- Added *collective_insights* argument to the following commands: + - ***ip*** + - ***domain*** + - ***url*** + - ***file*** + - ***cve*** +- Updated the Docker image to: *demisto/python3:3.11.10.111039*. + +##### Recorded Future - Playbook Alerts + +- Added the following arguments to the ***recordedfuture-playbook-alerts-update*** command: + - *reopen* + - *comment* + +- Updated the Docker image to: *demisto/python3:3.11.10.111039*. + +##### Recorded Future - Lists + +Updated the Docker image to: *demisto/python3:3.11.10.111039*. + +##### Recorded Future Event Collector + +Updated the Docker image to: *demisto/python3:3.11.10.111039*. diff --git a/Packs/RecordedFuture/pack_metadata.json b/Packs/RecordedFuture/pack_metadata.json index ef1b0aa33a5..22c67482c26 100644 --- a/Packs/RecordedFuture/pack_metadata.json +++ b/Packs/RecordedFuture/pack_metadata.json @@ -2,7 +2,7 @@ "name": "Recorded Future Intelligence", "description": "Recorded Future App, this pack is previously known as 'RecordedFuture v2'", "support": "partner", - "currentVersion": "1.7.13", + "currentVersion": "1.8.0", "author": "Recorded Future", "url": "https://www.recordedfuture.com/support/demisto-integration/", "email": "support@recordedfuture.com",