import json
import bs4
import CiscoWebExFeed
import pytest
from bs4 import BeautifulSoup
from CommonServerPython import * # noqa: F401
def util_load_json(path):
with open(path, encoding="utf-8") as f:
return json.loads(f.read())
def MockedClient(Client):
client = Client(base_url="test")
return client
__BASE_URL = "https://help.webex.com/en-us/WBX264/How-Do-I-Allow-Webex-Meetings-Traffic-on-My-Network"
DOMAIN_TABLE = [
["Client Type", "Domain(s)"],
["domain1", "*.d1.com\t\t\t*.d5.com"],
["domain2", "*.d2.com"],
["Long message without domain name"],
]
IP_LIST = [
[
"1.1.1.1/1 (CIDR) or 8.8.8.8 - 8.8.8.8 (net range)",
"1.1.1.1/1 (CIDR) or 8.8.8.8 - 8.8.8.8 (net range)",
"1.2.3.4/5 (CIDR) or 8.8.8.8 - 8.8.8.8 (net range)",
]
]
HTML_DOMAIN_SECTION = util_load_json("test_data/tests_data_1.json")
HTML_IP_SECTION = """
- 1.1.1.1/1 (CIDR) or 8.8.8.8 - 8.8.8.8 (net range)
"""
FETCH_INDICATORS_INPUT_1 = {"CIDR": ["ipmock"], "DOMAIN": ["domainmock"]}
FETCH_INDICATORS_OUTPUT_1 = [
{"value": "ipmock", "type": "Domain", "fields": {"tags": ("very_good", "very_bad"), "trafficlightprotocol": "very_yellow"}},
{
"value": "domainmock",
"type": "Domain",
"fields": {"tags": ("very_good", "very_bad"), "trafficlightprotocol": "very_yellow"},
},
]
FETCH_INDICATORS_NO_ENRICH_OUTPUT_1 = [
{
"value": "ipmock",
"type": "Domain",
"fields": {"tags": ("very_good", "very_bad"), "trafficlightprotocol": "very_yellow"},
"enrichmentExcluded": True,
},
{
"value": "domainmock",
"type": "Domain",
"fields": {"tags": ("very_good", "very_bad"), "trafficlightprotocol": "very_yellow"},
"enrichmentExcluded": True,
},
]
FETCH_INDICATORS_INPUT_2 = {"CIDR": ["ipmock1", "ipmock2"], "DOMAIN": ["domainmock1", "domainmock2"]}
FETCH_INDICATORS_OUTPUT_2 = [
{"value": "ipmock1", "type": "Domain", "fields": {"tags": ("very_good", "very_bad"), "trafficlightprotocol": "very_yellow"}},
{"value": "ipmock2", "type": "Domain", "fields": {"tags": ("very_good", "very_bad"), "trafficlightprotocol": "very_yellow"}},
{
"value": "domainmock1",
"type": "Domain",
"fields": {"tags": ("very_good", "very_bad"), "trafficlightprotocol": "very_yellow"},
},
{
"value": "domainmock2",
"type": "Domain",
"fields": {"tags": ("very_good", "very_bad"), "trafficlightprotocol": "very_yellow"},
},
]
def test_grab_domains():
"""
Given:
- Raw list of tuples that contains domain name and domain url, returned by api call:
first array is the title, 2 seconds arrays are data, last array is message.
When:
- Filtered list contains domain's urls only
Then:
- Return domains list without errors
"""
from CiscoWebExFeed import grab_domains
expected_result = ["*.d1.com", "*.d2.com", "*.d5.com"]
assert sorted(grab_domains(DOMAIN_TABLE)) == expected_result
def test_grab_CIDR_ips():
"""
Given:
- Raw list that contains ips CIDR and NET RANGE, returned by api call:
When:
- Calling grab_CIDR_ips
Then:
- Return CIDR ips list without without duplicates
"""
from CiscoWebExFeed import grab_CIDR_ips
expected_result = ["1.1.1.1/1", "1.2.3.4/5"]
assert sorted(grab_CIDR_ips(IP_LIST)) == expected_result
def test_grab_domain_table():
"""
Given: a beautiful soup object that is similar to the domain table
When:
- grab_domain_table(soup)
Then:
- the function should return a list of lists that contains the domain table
"""
from CiscoWebExFeed import grab_domain_table
soup = BeautifulSoup(HTML_DOMAIN_SECTION, "html.parser")
expected_result = util_load_json("test_data/tests_data_2.json")
assert grab_domain_table(soup) == expected_result
def test_grab_ip_table():
"""
Given: a soup object that is similar to the ip table
When:
- grab_ip_table(soup)
Then:
- the function should return a list of lists that contains the ips from the table
"""
from CiscoWebExFeed import grab_ip_table
soup = BeautifulSoup(HTML_IP_SECTION, "html.parser")
expected_result = [["1.1.1.1/1 (CIDR) or 8.8.8.8 - 8.8.8.8 (net range)"]]
assert grab_ip_table(soup) == expected_result
@pytest.mark.parametrize("input, expected", [("1.1.1.1/16", "CIDR"), ("*.example.com", "DomainGlob"), ("example.com", "Domain")])
def test_check_indicator_type__diffrent_inputs(input, expected):
"""
Given: A indicator of type: ip, domain or domain glob
When:
- check_indicator_type is called
Then:
- the function should return the correct indicator type
"""
from CiscoWebExFeed import check_indicator_type
assert check_indicator_type(input) == expected
@pytest.mark.parametrize(
"input, limit, expected",
[
(
"Both",
1,
"### Indicators from Webex:\n|value|type|\n|---|---|\n| ipmock1 | mocked_type |\n| domainmock1 | mocked_type |\n",
),
(
"CIDR",
2,
"### Indicators from Webex:\n|value|type|\n|---|---|\n| ipmock1 | mocked_type |\n| ipmock2 | mocked_type |\n",
),
(
"DOMAIN",
5,
"### Indicators from Webex:\n|value|type|\n|---|---|\n| domainmock1 | mocked_type |\n| domainmock2 | mocked_type |\n",
),
],
)
def test_get_indicators_command__diffrent_indicator_type_and_limit_as_input(mocker, input, expected, limit):
"""
Given:
- a limit and an indicator type
When:
- get_indicators_command is called
Then:
- the function should return the expectetd result with the correct limit and indicator type
"""
from CiscoWebExFeed import Client, get_indicators_command
client = MockedClient(Client)
mocker.patch.object(Client, "all_raw_data", return_value="gg")
mocker.patch.object(CiscoWebExFeed, "check_indicator_type", return_value="mocked_type")
mocker.patch.object(
CiscoWebExFeed,
"parse_indicators_from_response",
return_value={"CIDR": ["ipmock1", "ipmock2"], "DOMAIN": ["domainmock1", "domainmock2"]},
)
res = get_indicators_command(client=client, limit=limit, indicator_type=input)
assert res.readable_output == expected
def test_get_indicators_command__wrong_indicator_type(mocker):
"""
Given:
- illegal indicator type as input
When:
- get_indicators_command is called
Then:
- the function should return the expectetd error message
"""
from CiscoWebExFeed import Client, get_indicators_command
client = MockedClient(Client)
mocker.patch.object(Client, "all_raw_data", return_value="gg")
mocker.patch.object(CiscoWebExFeed, "check_indicator_type")
mocker.patch.object(
CiscoWebExFeed,
"parse_indicators_from_response",
return_value={"CIDR": ["ipmock1", "ipmock2"], "DOMAIN": ["domainmock1", "domainmock2"]},
)
with pytest.raises(DemistoException) as e:
get_indicators_command(client=client, indicator_type="mock_type")
assert e.value.message == "The indicator_type argument must be one of the following: Both, CIDR, DOMAIN"
@pytest.mark.parametrize(
"input, expected",
[(FETCH_INDICATORS_INPUT_1, FETCH_INDICATORS_OUTPUT_1), (FETCH_INDICATORS_INPUT_2, FETCH_INDICATORS_OUTPUT_2)],
)
def test_fetch_indicators_command__different_sizes_of_inputs(mocker, input, expected):
"""
Given:
- tags and tlp_color
When:
- the fetch_indicators_command is called and uses the output of parse_indicators_from_response as input
Then:
- the function should return the expectetd result with the correct tags and tlp_color
"""
from CiscoWebExFeed import Client, fetch_indicators_command
client = MockedClient(Client)
mocker.patch.object(Client, "all_raw_data", return_value="gg")
mocker.patch.object(CiscoWebExFeed, "parse_indicators_from_response", return_value=input)
expected_result = expected
assert fetch_indicators_command(client=client, tags=("very_good", "very_bad"), tlp_color="very_yellow") == expected_result
def test_fetch_indicators_command__exclude_enrichment(mocker):
"""
Given:
- Exclude enrichment parameter is used
When:
- Calling the fetch_indicators_command
Then:
- The indicators should include the enrichmentExcluded field if exclude is True.
"""
from CiscoWebExFeed import Client, fetch_indicators_command
input = FETCH_INDICATORS_INPUT_1
expected_result = FETCH_INDICATORS_NO_ENRICH_OUTPUT_1
client = MockedClient(Client)
mocker.patch.object(Client, "all_raw_data", return_value="gg")
mocker.patch.object(CiscoWebExFeed, "parse_indicators_from_response", return_value=input)
assert (
fetch_indicators_command(client=client, tags=("very_good", "very_bad"), tlp_color="very_yellow", enrichment_excluded=True)
== expected_result
)
def test_parse_indicators_from_response__fail_to_parse(mocker, requests_mock):
"""
Given:
- a response from the website that is not in the expected format
When:
- parse_indicators_from_response is called
Then:
- the function should return the expected error message
"""
from CiscoWebExFeed import parse_indicators_from_response
mocker.patch.object(bs4, ("BeautifulSoup"))
mocker.patch.object(CiscoWebExFeed, "grab_domain_table")
mocker.patch.object(CiscoWebExFeed, "grab_ip_table")
mocker.patch.object(CiscoWebExFeed, "grab_CIDR_ips")
mocker.patch.object(CiscoWebExFeed, "grab_domains", side_effect=DemistoException("No domains to grab"))
mocked_response = requests_mock.get({__BASE_URL}, json={"name": "awesome-mock"})
mocked_response.text = "mocked text"
with pytest.raises(DemistoException) as e:
parse_indicators_from_response(mocked_response)
assert e.value.message == "Failed to parse the response from the website. Error: No domains to grab"
@pytest.mark.parametrize("CIDR_results, domain_results", [("domainmock", None), (None, "domainmock")])
def test_parse_indicators_from_response__ip_or_domain_indicators_are_None(mocker, requests_mock, CIDR_results, domain_results):
"""
Given:
- a response from the website, (CIDR or domains) with the value None
When:
- parse_indicators_from_response is called
Then:
- the function should return the expected error message
"""
from CiscoWebExFeed import parse_indicators_from_response
mocker.patch.object(bs4, ("BeautifulSoup"))
mocker.patch.object(CiscoWebExFeed, "grab_domain_table")
mocker.patch.object(CiscoWebExFeed, "grab_ip_table")
mocker.patch.object(CiscoWebExFeed, "grab_CIDR_ips", return_value=CIDR_results)
mocker.patch.object(CiscoWebExFeed, "grab_domains", return_value=domain_results)
mocked_response = requests_mock.get({__BASE_URL}, json={"name": "awesome-mock"})
mocked_response.text = "mocked text"
with pytest.raises(DemistoException) as e:
parse_indicators_from_response(mocked_response)
assert e.value.message == "Did not find the expected indicators in the response from the website"