diff --git a/Packs/Flashpoint/Integrations/Ignite/Ignite.py b/Packs/Flashpoint/Integrations/Ignite/Ignite.py index 8bb5488ad163..a1580fe7c1ce 100644 --- a/Packs/Flashpoint/Integrations/Ignite/Ignite.py +++ b/Packs/Flashpoint/Integrations/Ignite/Ignite.py @@ -1,13 +1,12 @@ """Ignite Main File.""" -from copy import deepcopy import ipaddress - -import requests -import urllib3 import re +from copy import deepcopy import demistomock as demisto +import requests +import urllib3 from CommonServerPython import * # noqa # pylint: disable=unused-wildcard-import from CommonServerUserPython import * # noqa @@ -323,15 +322,17 @@ def http_request(self, method, url_suffix, params=None, json_data=None): return resp_json - def get_indicator(self, indicator_value: str, indicator_type: str): + def get_indicator(self, indicator_value: str, indicator_type: str, exact_match: bool = False): """ Get an indicator by its type and value. :param indicator_type: The indicator type. :param indicator_value: The indicator value. + :param exact_match: Whether to perform an exact match. If true, the indicator value is enclosed in quotes. :return: The indicator response. """ + indicator_value = f'"{indicator_value}"' if exact_match else indicator_value params = {"ioc_types": indicator_type, "ioc_value": indicator_value, "embed": "all"} return self.http_request("GET", URL_SUFFIX["LIST_INDICATORS"], params=params) @@ -1696,7 +1697,7 @@ def filename_lookup_command(client: Client, filename: str) -> CommandResults: ) -def ip_lookup_command(client: Client, ip: str) -> CommandResults: +def ip_lookup_command(client: Client, ip: str, exact_match: bool = False) -> CommandResults: """ Lookup a particular ip-address. @@ -1705,6 +1706,7 @@ def ip_lookup_command(client: Client, ip: str) -> CommandResults: : param client: object of client class : param ip: ip-address + : param exact_match: Whether to perform an exact match. If true, the indicator value is enclosed in quotes. : return: command output """ if not is_ip_valid(ip, True): @@ -1714,9 +1716,9 @@ def ip_lookup_command(client: Client, ip: str) -> CommandResults: return CommandResults(readable_output=f"Skipping internal IP: {ip}") if is_ipv6_valid(ip): - response = client.get_indicator(ip, "ipv6") + response = client.get_indicator(ip, "ipv6", exact_match) else: - response = client.get_indicator(ip, "ipv4") + response = client.get_indicator(ip, "ipv4", exact_match) items = response.get("items", []) if items: @@ -2116,16 +2118,16 @@ def indicator_get_command(client: Client, args: dict) -> CommandResults: ) -def url_lookup_command(client: Client, url: str) -> CommandResults: +def url_lookup_command(client: Client, url: str, exact_match: bool = False) -> CommandResults: """ Lookup a particular url. :param client: object of client class :param url: url as indicator - + :param exact_match: Whether to perform an exact match. If true, the indicator value is enclosed in quotes. :return: command output """ - response = client.get_indicator(url, "url") + response = client.get_indicator(url, "url", exact_match) items = response.get("items", []) if items: @@ -2236,16 +2238,17 @@ def url_lookup_command(client: Client, url: str) -> CommandResults: return command_results -def domain_lookup_command(client: Client, domain: str) -> CommandResults: +def domain_lookup_command(client: Client, domain: str, exact_match: bool = False) -> CommandResults: """ Lookup a particular domain. :param client: object of client class :param domain: domain + :param exact_match: Whether to perform an exact match. If true, the indicator value is enclosed in quotes. :return: command output """ - response = client.get_indicator(domain, "domain") + response = client.get_indicator(domain, "domain", exact_match) items = response.get("items", []) if items: @@ -2350,16 +2353,17 @@ def domain_lookup_command(client: Client, domain: str) -> CommandResults: return CommandResults(indicator=domain_ioc, readable_output=human_readable, raw_response=response) -def file_lookup_command(client: Client, file: str) -> CommandResults: +def file_lookup_command(client: Client, file: str, exact_match: bool = False) -> CommandResults: """ Lookup a particular file hash. :param client: object of client class :param file: file as indicator + :param exact_match: Whether to perform an exact match. If true, the indicator value is enclosed in quotes. :return: command output """ - response = client.get_indicator(file, "file") + response = client.get_indicator(file, "file", exact_match) items = response.get("items", []) if items: @@ -2977,11 +2981,15 @@ def main(): raise ValueError(MESSAGES["MISSING_REQUIRED_ARGS"].format(command)) indicator_list = argToList(args.get(command)) indicator_list = [indicator.strip() for indicator in indicator_list if indicator.strip()] + exact_match = argToBoolean(args.get("exact_match", False)) results = [] if not indicator_list: raise ValueError(MESSAGES["MISSING_REQUIRED_ARGS"].format(command)) for indicator in indicator_list: - results.append(REPUTATION_COMMAND_TO_FUNCTION[command](client, indicator)) + arguments = (client, indicator) + if exact_match: + arguments += (exact_match,) # type: ignore + results.append(REPUTATION_COMMAND_TO_FUNCTION[command](*arguments)) return_results(results) elif COMMAND_TO_FUNCTION.get(command): diff --git a/Packs/Flashpoint/Integrations/Ignite/Ignite.yml b/Packs/Flashpoint/Integrations/Ignite/Ignite.yml index a1c39284e8a0..f972077e2861 100644 --- a/Packs/Flashpoint/Integrations/Ignite/Ignite.yml +++ b/Packs/Flashpoint/Integrations/Ignite/Ignite.yml @@ -928,6 +928,14 @@ script: default: true description: A comma-separated list of IP addresses. isArray: true + - name: exact_match + required: false + defaultValue: "False" + description: Whether to perform an exact match on the IP address value. + auto: PREDEFINED + predefined: + - "True" + - "False" outputs: - contextPath: DBotScore.Indicator description: The indicator that was tested. @@ -2587,6 +2595,14 @@ script: default: true description: A comma-separated list of URLs. isArray: true + - name: exact_match + required: false + defaultValue: "False" + description: Whether to perform an exact match on the URL value. + auto: PREDEFINED + predefined: + - "True" + - "False" outputs: - contextPath: DBotScore.Indicator description: The indicator that was tested. @@ -2791,6 +2807,14 @@ script: default: true description: A comma-separated list of domains. isArray: true + - name: exact_match + required: false + defaultValue: "False" + description: Whether to perform an exact match on the domain value. + auto: PREDEFINED + predefined: + - "True" + - "False" outputs: - contextPath: DBotScore.Indicator description: The indicator that was tested. @@ -2995,6 +3019,14 @@ script: default: true description: List of files. isArray: true + - name: exact_match + required: false + defaultValue: "False" + description: Whether to perform an exact match on the file hash value. + auto: PREDEFINED + predefined: + - "True" + - "False" outputs: - contextPath: DBotScore.Indicator description: The indicator that was tested. @@ -3215,7 +3247,7 @@ script: description: Looks up the "File" type indicator details. The reputation of the file is decided from the indicator score if it is found in the Ignite IOC database. script: '' type: python - dockerimage: demisto/python3:3.12.11.4508456 + dockerimage: demisto/python3:3.12.12.6947692 isfetch: true runonce: false subtype: python3 diff --git a/Packs/Flashpoint/Integrations/Ignite/Ignite_test.py b/Packs/Flashpoint/Integrations/Ignite/Ignite_test.py index dc3dd9c223e0..1062a8b7ee8b 100644 --- a/Packs/Flashpoint/Integrations/Ignite/Ignite_test.py +++ b/Packs/Flashpoint/Integrations/Ignite/Ignite_test.py @@ -1212,7 +1212,8 @@ def test_filename_empty_response(mock_return, requests_mock, mocker): @patch("demistomock.results") -def test_domain_lookup_command_success(mock_return, requests_mock, mocker): +@pytest.mark.parametrize("exact_match", [True, False]) +def test_domain_lookup_command_success(mock_return, requests_mock, mocker, exact_match): """ Test case for successful execution of domain look up command through main function when it returns reputation about given domain indicator. @@ -1234,13 +1235,18 @@ def test_domain_lookup_command_success(mock_return, requests_mock, mocker): requests_mock.get(url, json=domain_lookup_reputation, status_code=200) params = {**BASIC_PARAMS, "integrationReliability": "B - Usually reliable"} - args = {"domain": "dummy_domain.com"} + domain_value = "dummy_domain.com" + args = {"domain": domain_value, "exact_match": exact_match} mocker.patch.object(demisto, "params", return_value=params) mocker.patch.object(demisto, "command", return_value="domain") mocker.patch.object(demisto, "args", return_value=args) main() + last_request = requests_mock.last_request + domain_value = f'"{domain_value}"' if exact_match else domain_value + assert last_request.qs["ioc_value"] == [domain_value] + assert hr_output_for_domain_lookup_reputation == mock_return.call_args.args[0].get("HumanReadable") assert domain_lookup_reputation_context == mock_return.call_args.args[0].get("EntryContext") assert domain_lookup_reputation == mock_return.call_args.args[0].get("Contents") @@ -1306,7 +1312,8 @@ def test_domain_lookup_command_when_invalid_value_is_provided(mocker): @patch("demistomock.results") -def test_ip_lookup_command_success(mock_return, requests_mock, mocker): +@pytest.mark.parametrize("exact_match", [True, False]) +def test_ip_lookup_command_success(mock_return, requests_mock, mocker, exact_match): """ Test case for successful execution of ip look up command through main function when it returns reputation about given ip indicator. @@ -1327,13 +1334,18 @@ def test_ip_lookup_command_success(mock_return, requests_mock, mocker): url = f'{MOCK_URL}{URL_SUFFIX["LIST_INDICATORS"]}' requests_mock.get(url, json=ip_lookup_reputation, status_code=200) params = {**BASIC_PARAMS, "integrationReliability": "B - Usually reliable"} - args = {"ip": "0.0.0.1"} + ip_value = "0.0.0.1" + args = {"ip": ip_value, "exact_match": exact_match} mocker.patch.object(demisto, "params", return_value=params) mocker.patch.object(demisto, "command", return_value="ip") mocker.patch.object(demisto, "args", return_value=args) mocker.patch("Ignite.is_ip_address_internal", return_value=False) main() + last_request = requests_mock.last_request + ip_value = f'"{ip_value}"' if exact_match else ip_value + assert last_request.qs["ioc_value"] == [ip_value] + assert hr_output_for_ip_lookup_reputation == mock_return.call_args.args[0].get("HumanReadable") assert ip_lookup_reputation_context == mock_return.call_args.args[0].get("EntryContext") assert ip_lookup_reputation == mock_return.call_args.args[0].get("Contents") @@ -1761,7 +1773,8 @@ def test_indicator_get_command_when_invalid_value_is_provided(mocker): @patch("demistomock.results") -def test_url_lookup_command_success(mock_return, requests_mock, mocker): +@pytest.mark.parametrize("exact_match", [True, False]) +def test_url_lookup_command_success(mock_return, requests_mock, mocker, exact_match): """ Test case for successful execution of url lookup command through main function when it returns reputation about given url. @@ -1781,12 +1794,18 @@ def test_url_lookup_command_success(mock_return, requests_mock, mocker): requests_mock.get(f'{MOCK_URL}{URL_SUFFIX["LIST_INDICATORS"]}', json=url_reputation, status_code=200) params = {**BASIC_PARAMS, "integrationReliability": "B - Usually reliable"} - args = {"url": "http://dummy.com"} + url_value = "http://dummy.com" + args = {"url": url_value, "exact_match": exact_match} mocker.patch.object(demisto, "params", return_value=params) mocker.patch.object(demisto, "command", return_value="url") mocker.patch.object(demisto, "args", return_value=args) main() + + last_request = requests_mock.last_request + url_value = f'"{url_value}"' if exact_match else url_value + assert last_request.qs["ioc_value"] == [url_value] + assert url_reputation_hr == mock_return.call_args.args[0].get("HumanReadable") assert url_reputation_context == mock_return.call_args.args[0].get("EntryContext") assert url_reputation == mock_return.call_args.args[0].get("Contents") @@ -1850,7 +1869,8 @@ def test_url_lookup_command_when_invalid_value_is_provided(mocker): @patch("demistomock.results") -def test_file_lookup_command_success(mock_return, requests_mock, mocker): +@pytest.mark.parametrize("exact_match", [True, False]) +def test_file_lookup_command_success(mock_return, requests_mock, mocker, exact_match): """ Test case for successful execution of file command through main function when it returns reputation about given file. @@ -1871,12 +1891,18 @@ def test_file_lookup_command_success(mock_return, requests_mock, mocker): url = f'{MOCK_URL}{URL_SUFFIX["LIST_INDICATORS"]}' requests_mock.get(url, json=file_reputation, status_code=200) params = {**BASIC_PARAMS, "integrationReliability": "B - Usually reliable"} - args = {"file": "00000000000000000000000000000001"} + file_value = "00000000000000000000000000000001" + args = {"file": file_value, "exact_match": exact_match} mocker.patch.object(demisto, "params", return_value=params) mocker.patch.object(demisto, "command", return_value="file") mocker.patch.object(demisto, "args", return_value=args) main() + + last_request = requests_mock.last_request + file_value = f'"{file_value}"' if exact_match else file_value + assert last_request.qs["ioc_value"] == [file_value] + assert file_reputation_hr == mock_return.call_args.args[0].get("HumanReadable") assert file_reputation_context == mock_return.call_args.args[0].get("EntryContext") assert file_reputation == mock_return.call_args.args[0].get("Contents") diff --git a/Packs/Flashpoint/Integrations/Ignite/README.md b/Packs/Flashpoint/Integrations/Ignite/README.md index 4daf51f2891f..5942f3f0eaaa 100644 --- a/Packs/Flashpoint/Integrations/Ignite/README.md +++ b/Packs/Flashpoint/Integrations/Ignite/README.md @@ -1014,6 +1014,7 @@ Looks up the "IP" type indicator details. The reputation of the IP address is de | **Argument Name** | **Description** | **Required** | | --- | --- | --- | | ip | A comma-separated list of IP addresses. | Required | +| exact_match | Whether to perform an exact match on the IP address value. Possible values are: True, False. Default is False. | Optional | #### Context Output @@ -2047,6 +2048,7 @@ Looks up the "URL" type indicator details. The reputation of the URL is decided | **Argument Name** | **Description** | **Required** | | --- | --- | --- | | url | A comma-separated list of URLs. | Required | +| exact_match | Whether to perform an exact match on the URL value. Possible values are: True, False. Default is False. | Optional | #### Context Output @@ -2242,6 +2244,7 @@ Looks up the "Domain" type indicator details. The reputation of the domain is de | **Argument Name** | **Description** | **Required** | | --- | --- | --- | | domain | A comma-separated list of domains. | Required | +| exact_match | Whether to perform an exact match on the domain value. Possible values are: True, False. Default is False. | Optional | #### Context Output @@ -2435,6 +2438,7 @@ Looks up the "File" type indicator details. The reputation of the file is decide | **Argument Name** | **Description** | **Required** | | --- | --- | --- | | file | List of files. | Required | +| exact_match | Whether to perform an exact match on the file hash value. Possible values are: True, False. Default is False. | Optional | #### Context Output diff --git a/Packs/Flashpoint/ReleaseNotes/2_1_5.md b/Packs/Flashpoint/ReleaseNotes/2_1_5.md new file mode 100644 index 000000000000..eb33c07998fe --- /dev/null +++ b/Packs/Flashpoint/ReleaseNotes/2_1_5.md @@ -0,0 +1,7 @@ + +#### Integrations + +##### Flashpoint Ignite + +- Added support for *exact_match* argument in the **domain**, **ip**, **url**, **file** commands. +- Updated the Docker image to: *demisto/python3:3.12.12.6947692*. diff --git a/Packs/Flashpoint/pack_metadata.json b/Packs/Flashpoint/pack_metadata.json index 7fa3a3edbeee..592b27496cc3 100644 --- a/Packs/Flashpoint/pack_metadata.json +++ b/Packs/Flashpoint/pack_metadata.json @@ -2,7 +2,7 @@ "name": "Flashpoint", "description": "Use the Flashpoint integration to reduce business risk.", "support": "partner", - "currentVersion": "2.1.4", + "currentVersion": "2.1.5", "author": "Flashpoint", "url": "https://flashpoint.my.site.com/helpcenter/s/", "email": "support@flashpoint-intel.com",