From 56406147ba6417d5ad93bbfa093aeddc02754b69 Mon Sep 17 00:00:00 2001 From: William Denman Date: Fri, 5 Jan 2024 23:33:01 +0000 Subject: [PATCH 1/4] add support to pull indicators --- src/soarsdk/client.py | 12 +++++++++++- src/soarsdk/objects.py | 14 ++++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/src/soarsdk/client.py b/src/soarsdk/client.py index 2b3b8e8..46adb64 100644 --- a/src/soarsdk/client.py +++ b/src/soarsdk/client.py @@ -31,6 +31,7 @@ Pin, PhantomObject, Playbook, + Indicator ) @@ -855,6 +856,15 @@ def get_artifacts(self, params: dict = {}) -> list[Artifact]: ) return [Artifact(**artifact) for artifact in artifacts] + def get_indicator_by_value(self, indicator_value) -> Indicator: + """Returns an Indicator object based on it's value""" + params: dict = {"indicator_value": indicator_value} + + indicator: Indicator = self._handle_request( + method="GET", url="indicator_by_value?", params=params, return_data_only=False + ) + return Indicator(**indicator) + def get_playbooks(self, params: dict = {}) -> list[Playbook]: """Returns a list of Playbook objects based on the REST parameters provided""" playbooks: list[dict] = self._handle_request( @@ -1046,7 +1056,7 @@ def _handle_request( return_data_only: bool = kwargs.get("return_data_only", False) for key, value in params.items(): - if key not in ["start_time", "sort", "order"] and "__in" not in key: + if key not in ["start_time", "sort", "order", "indicator_value"] and "__in" not in key: if isinstance(value, str): params[key]: str = json.dumps(value) diff --git a/src/soarsdk/objects.py b/src/soarsdk/objects.py index f4982d0..5afcff3 100644 --- a/src/soarsdk/objects.py +++ b/src/soarsdk/objects.py @@ -175,6 +175,20 @@ def __hash__(self): """@todo look through previous scripts to find appropriate hashing for equality comparisons""" return hash(self.name + self.label + self.id) +class Indicator(PhantomObject): + def __init__(self, **kwargs): + super().__init__() + self.id: int = kwargs.get("id") + self.value: str = kwargs.get("value") + self.value_hash: str = kwargs.get("value_hash") + self.tenant: int = kwargs.get("tenant") + self.tags: list = kwargs.get("tags", []) + self.earliest_time: str = kwargs.get("earliest_time") + self.latest_time: str = kwargs.get("latest_time") + self.open_events: int = kwargs.get("open_events") + self.total_events: int = kwargs.get("total_events") + self.severity_counts = kwargs.get("severity_counts", []) + class Asset(PhantomObject): def __init__(self, **kwargs): From 8a769112161a0c9cc756a937f3eb7f61ad6f7911 Mon Sep 17 00:00:00 2001 From: William Denman Date: Sat, 6 Jan 2024 00:00:17 +0000 Subject: [PATCH 2/4] some tweaks to tests --- tests/sample_objects.json | 37 +++++++++++++++- tests/test_PhantomClient.py | 86 ++++++++++++------------------------- 2 files changed, 63 insertions(+), 60 deletions(-) diff --git a/tests/sample_objects.json b/tests/sample_objects.json index 541b0f3..f8d66e0 100644 --- a/tests/sample_objects.json +++ b/tests/sample_objects.json @@ -140,5 +140,40 @@ "parent_container": null, "parent_artifact": null } - ] + ], + "indicator": { + "tags": [ + "exclude_from_analysis" + ], + "id": 163, + "value": "www.internalwebsite.com", + "value_hash": "d5342b206053b160e4dc3e98eeac7248f084cdfbbecd0e234fa121187f95a51a", + "tenant": 0, + "earliest_time": "2023-12-28T00:53:51.316373Z", + "latest_time": "2023-12-28T19:42:34.375667Z", + "open_events": 15, + "total_events": 15, + "severity_counts": [ + { + "name": "informational", + "count": 0 + }, + { + "name": "critical", + "count": 0 + }, + { + "name": "high", + "count": 0 + }, + { + "name": "low", + "count": 15 + }, + { + "name": "medium", + "count": 0 + } + ] + } } \ No newline at end of file diff --git a/tests/test_PhantomClient.py b/tests/test_PhantomClient.py index b0fb216..1343cf5 100644 --- a/tests/test_PhantomClient.py +++ b/tests/test_PhantomClient.py @@ -7,7 +7,7 @@ import requests import soarsdk from soarsdk.client import PhantomClient -from soarsdk.objects import Artifact, Container, Playbook, Action, Pin, Asset, App +from soarsdk.objects import Artifact, Container, Indicator, Playbook, Action, Pin, Asset, App from soarsdk.exceptions import * from soarsdk.objects import PhantomObject from unittest.mock import Mock @@ -23,11 +23,22 @@ def setUp(self) -> None: label="workbench", artifacts=[Artifact(name="dummy", label="dummy")], ) + with patch("soarsdk.client.PhantomClient.test_authorization") as patched_auth: + self.phantom = PhantomClient( + url='https://example.test/', + session=requests.session() + ) + self.mock_container = Container(name="test", label="foobar") - self.test_data = json.load(open("tests/sample_objects.json")) + + with open('tests/sample_objects.json') as f: + test_data = json.load(f) + + self.test_data = test_data self.test_artifacts = self.test_data["artifacts"] self.test_containers = self.test_data["containers"] - + self.test_indicator = self.test_data["indicator"] + @patch("requests.Session.post") def test_create_container_throws_invalid_exception(self, mock_post): mock_response = Mock() @@ -68,7 +79,7 @@ def test_update_object(self): def test_run_playbooks_without_id(self): uninitialized_container: Container = Container(name="test_container") self.assertRaises( - soarsdk.Exceptions.ContainerNotInitialized, + soarsdk.exceptions.ContainerNotInitialized, self.phantom.run_playbooks, uninitialized_container, ) @@ -138,6 +149,12 @@ def get_mock_artifacts_response(self) -> Mock: mock_get_response.status_code = 200 mock_get_response.json.return_value: dict = self.test_artifacts return mock_get_response + + def get_mock_indicator_response(self) -> Mock: + mock_get_response = Mock() + mock_get_response.status_code = 200 + mock_get_response.json.return_value: dict = self.test_indicator + return mock_get_response def get_mock_containers_response(self) -> Mock: mock_get_response = Mock() @@ -249,15 +266,6 @@ def test_create_artifact(self, mock_artifacts, mock_post, mock_get): self.phantom.create_artifacts(test_container, test_artifact) self.assertEqual(test_artifact.container_id, test_container.id) - def test_modify_container_label(self): - """Changes the container label in the Phantom environment""" - self.container.label = "foobar" - self.phantom.modify_container_values(self.container) - assert self.container.label == "falcon_complete" - self.container.label = "workbench" - self.phantom.modify_container_values(self.container) - assert self.container.label == "workbench" - @patch("requests.Session.delete") def test_delete_artifact(self, mock_delete): mock_delete_response = Mock() @@ -317,39 +325,6 @@ def test_export_container_as_tar_bad_params(self): self.mock_container, ) - @patch("requests.Session.get") - def test_get_playbook_count_empty(self, mock_get): - mock_get_response = Mock() - mock_get_response.status_code = 200 - mock_get_response.json.return_value = {"count": 0, "num_pages": 0, "data": []} - mock_get.return_value = mock_get_response - playbook_count: int = self.phantom.get_playbook_count() - self.assertIsInstance(playbook_count, int) - - @patch("requests.Session.get") - def test_get_cronjob_count_empty(self, mock_get): - mock_get_response = Mock() - mock_get_response.status_code = 200 - mock_get_response.json.return_value = {"count": 0, "num_pages": 0, "data": []} - mock_get.return_value = mock_get_response - cron_assets: int = self.phantom.get_cronjob_count() - self.assertIsInstance(cron_assets, int) - - @patch("soarsdk.client.PhantomClient.get_app") - @patch("requests.Session.get") - def test_get_cronjob_count(self, mock_get, mock_get_app): - mock_get_response = Mock() - mock_get_response.status_code = 200 - mock_get_response.json.return_value = { - "count": 1, - "num_pages": 0, - "data": [{"id": 2, "name": "container_scheduler_asset"}], - } - mock_get.return_value = mock_get_response - mock_get_app.return_value = App(name="Container Scheduler", id=2) - cron_assets: int = self.phantom.get_cronjob_count() - self.assertIsInstance(cron_assets, int) - @patch("requests.Session.get") def test_get_containers(self, mock_get): params: dict = {"page_size": 1} @@ -365,6 +340,12 @@ def test_get_artifacts(self, mock_get): mock_get.return_value = self.get_mock_artifacts_response() for artifact in artifacts: self.assertIsInstance(artifact, Artifact) + + @patch("requests.Session.get") + def test_get_indicator_by_value(self, mock_get): + mock_get.return_value = self.get_mock_indicator_response() + indicator: Indicator = self.phantom.get_indicator_by_value("anything") + self.assertIsInstance(indicator, Indicator) def test_update_container_values_none_id(self): bad_container: Container = Container(name="test", label="foobar") @@ -372,19 +353,6 @@ def test_update_container_values_none_id(self): ContainerNotInitialized, self.phantom.update_container_values, bad_container ) - @patch("requests.Session.get") - def test_get_playbook_name_from_id(self, mock_get): - mock_get_response = Mock() - mock_get_response.status_code = 200 - mock_get_response.json.return_value = { - "count": 1, - "num_pages": 0, - "data": [{"id": 123, "name": "foobar"}], - } - mock_get.return_value = mock_get_response - sample_playbook_id: int = self.get_sample_playbook().id - self.assertIsInstance() - def test_update_Container_values_bad_param(self): self.assertRaises( ContainerNotInitialized, From 7cbd31b463fa288cf9b9bdd9b8f8727fd3291350 Mon Sep 17 00:00:00 2001 From: Tyler Chuba Date: Sat, 13 Jan 2024 16:49:11 -0500 Subject: [PATCH 3/4] version increment --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index deed8b1..64adcc0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -2,7 +2,7 @@ requires = ["wheel", 'setuptools==64.0.0'] [project] name = "soarsdk" -version = "1.0.1" +version = "1.0.2" description = "API Wrapper and Objects for Splunk SOAR" readme = "README.md" license = { file= "license.txt"} From d5c45a6ba3fe631455a2be4c3160f26a1e5beb2a Mon Sep 17 00:00:00 2001 From: Tyler Chuba Date: Sat, 13 Jan 2024 16:50:38 -0500 Subject: [PATCH 4/4] Adding missing due_time field to Container --- src/soarsdk/objects.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/soarsdk/objects.py b/src/soarsdk/objects.py index 5afcff3..10c5f28 100644 --- a/src/soarsdk/objects.py +++ b/src/soarsdk/objects.py @@ -405,6 +405,7 @@ def __init__(self, **kwargs): self.comments: list[str] = kwargs.get("comments", []) self.notes: list[Note] = kwargs.get("notes", []) self.data: list[dict] = kwargs.get("data", {}) + self.due_time: str = kwargs.get("due_time") @property def artifact_count(self) -> int: