From 9d7c45e386e26ffa2329b886f2631b3b59cd5f82 Mon Sep 17 00:00:00 2001 From: Anko59 Date: Fri, 27 Dec 2024 14:00:23 +0100 Subject: [PATCH 1/6] Endpoint for case templates --- .pre-commit-config.yaml | 2 +- tests/conftest.py | 24 ++++++++++ tests/test_case_template_endpoint.py | 63 +++++++++++++++++++++++++++ thehive4py/client.py | 2 + thehive4py/endpoints/__init__.py | 1 + thehive4py/endpoints/case_template.py | 47 ++++++++++++++++++++ thehive4py/types/case_template.py | 52 ++++++++++++++++++++++ 7 files changed, 190 insertions(+), 1 deletion(-) create mode 100644 tests/test_case_template_endpoint.py create mode 100644 thehive4py/endpoints/case_template.py create mode 100644 thehive4py/types/case_template.py diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 9aae4281..fc501306 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -8,4 +8,4 @@ repos: language: system pass_filenames: false always_run: true - stages: [push] \ No newline at end of file + stages: [pre-push] \ No newline at end of file diff --git a/tests/conftest.py b/tests/conftest.py index be67f2c2..419d7140 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -7,6 +7,7 @@ from thehive4py.helpers import now_to_ts from thehive4py.types.alert import InputAlert, OutputAlert from thehive4py.types.case import InputCase, OutputCase +from thehive4py.types.case_template import InputCaseTemplate, OutputCaseTemplate from thehive4py.types.comment import OutputComment from thehive4py.types.custom_field import OutputCustomField from thehive4py.types.observable import InputObservable, OutputObservable @@ -104,6 +105,17 @@ def test_case(thehive: TheHiveApi) -> OutputCase: ) +@pytest.fixture +def test_case_template(thehive: TheHiveApi) -> OutputCaseTemplate: + return thehive.case_template.create( + case_template={ + "name": "my first case template", + "description": "...", + "tags": ["whatever"], + } + ) + + @pytest.fixture def test_cases(thehive: TheHiveApi) -> List[OutputCase]: cases: List[InputCase] = [ @@ -113,6 +125,18 @@ def test_cases(thehive: TheHiveApi) -> List[OutputCase]: return [thehive.case.create(case=case) for case in cases] +@pytest.fixture +def test_case_templates(thehive: TheHiveApi) -> List[OutputCaseTemplate]: + case_templates: List[InputCaseTemplate] = [ + {"name": "my first case template", "description": "..."}, + {"name": "my second case template", "description": "..."}, + ] + return [ + thehive.case_template.create(case_template=case_template) + for case_template in case_templates + ] + + @pytest.fixture def test_observable(thehive: TheHiveApi, test_case: OutputCase) -> OutputObservable: return thehive.observable.create_in_case( diff --git a/tests/test_case_template_endpoint.py b/tests/test_case_template_endpoint.py new file mode 100644 index 00000000..85ac8039 --- /dev/null +++ b/tests/test_case_template_endpoint.py @@ -0,0 +1,63 @@ +from typing import List + +import pytest +from thehive4py.client import TheHiveApi +from thehive4py.errors import TheHiveError +from thehive4py.types.case_template import InputCaseTemplate, OutputCaseTemplate + + +class TestCaseTemplateEndpoint: + def test_create_and_get(self, thehive: TheHiveApi): + created_case_template = thehive.case_template.create( + case_template={ + "name": "my first template", + "description": "Template description", + } + ) + fetched_case_template = thehive.case_template.get(created_case_template["_id"]) + assert created_case_template == fetched_case_template + + def test_update(self, thehive: TheHiveApi, test_case_template: OutputCaseTemplate): + case_template_id = test_case_template["_id"] + update_fields: InputCaseTemplate = { + "name": "updated template name", + "description": "updated template description", + } + thehive.case_template.update( + case_template_id=case_template_id, fields=update_fields + ) + updated_case_template = thehive.case_template.get( + case_template_id=case_template_id + ) + + for key, value in update_fields.items(): + assert updated_case_template.get(key) == value + + def test_update_with_wrong_argument_error( + self, thehive: TheHiveApi, test_case_template: OutputCaseTemplate + ): + case_template_id = test_case_template["_id"] + update_fields: InputCaseTemplate = { + "name": "updated template name", + "description": "updated template description", + } + wrong_kwargs = {"template_fields": update_fields, "wrong_arg": "value"} + with pytest.raises(TheHiveError, match=rf".*{list(wrong_kwargs.keys())}.*"): + thehive.case_template.update(case_template_id=case_template_id, **wrong_kwargs) # type: ignore + + def test_delete(self, thehive: TheHiveApi, test_case_template: OutputCaseTemplate): + case_template_id = test_case_template["_id"] + thehive.case_template.delete(case_template_id=case_template_id) + with pytest.raises(TheHiveError): + thehive.case_template.get(case_template_id=case_template_id) + + def test_find( + self, + thehive: TheHiveApi, + test_case_templates: List[OutputCaseTemplate], + ): + filters = {"name": "my first template"} + found_templates = thehive.case_template.find(filters=filters) + names = [template["name"] for template in found_templates] + for test_template in test_case_templates: + assert test_template["name"] in names diff --git a/thehive4py/client.py b/thehive4py/client.py index 3a243406..b36d54ab 100644 --- a/thehive4py/client.py +++ b/thehive4py/client.py @@ -3,6 +3,7 @@ from thehive4py.endpoints import ( AlertEndpoint, CaseEndpoint, + CaseTemplateEndpoint, CommentEndpoint, ObservableEndpoint, OrganisationEndpoint, @@ -62,6 +63,7 @@ def __init__( # case management endpoints self.alert = AlertEndpoint(self.session) self.case = CaseEndpoint(self.session) + self.case_template = CaseTemplateEndpoint(self.session) self.comment = CommentEndpoint(self.session) self.observable = ObservableEndpoint(self.session) self.procedure = ProcedureEndpoint(self.session) diff --git a/thehive4py/endpoints/__init__.py b/thehive4py/endpoints/__init__.py index 39499f90..818fc6b3 100644 --- a/thehive4py/endpoints/__init__.py +++ b/thehive4py/endpoints/__init__.py @@ -1,5 +1,6 @@ from .alert import AlertEndpoint from .case import CaseEndpoint +from .case_template import CaseTemplateEndpoint from .comment import CommentEndpoint from .cortex import CortexEndpoint from .custom_field import CustomFieldEndpoint diff --git a/thehive4py/endpoints/case_template.py b/thehive4py/endpoints/case_template.py new file mode 100644 index 00000000..f7d7f8e9 --- /dev/null +++ b/thehive4py/endpoints/case_template.py @@ -0,0 +1,47 @@ +from thehive4py.endpoints._base import EndpointBase +from thehive4py.query import QueryExpr +from thehive4py.query.filters import FilterExpr +from thehive4py.query.page import Paginate +from thehive4py.query.sort import SortExpr +from thehive4py.types.case_template import OutputCaseTemplate, InputCaseTemplate +from typing import List, Optional + + +class CaseTemplateEndpoint(EndpointBase): + def find( + self, + filters: Optional[FilterExpr] = None, + sortby: Optional[SortExpr] = None, + paginate: Optional[Paginate] = None, + ) -> List[OutputCaseTemplate]: + query: QueryExpr = [ + {"_name": "listCaseTemplate"}, + *self._build_subquery(filters=filters, sortby=sortby, paginate=paginate), + ] + + return self._session.make_request( + "POST", + path="/api/v1/query", + json={"query": query}, + params={"name": "caseTemplate"}, + ) + + def get(self, case_template_id: str) -> OutputCaseTemplate: + return self._session.make_request( + "GET", path=f"/api/v1/caseTemplate/{case_template_id}" + ) + + def create(self, case_template: InputCaseTemplate) -> OutputCaseTemplate: + return self._session.make_request( + "POST", path="/api/v1/caseTemplate", json=case_template + ) + + def delete(self, case_template_id: str) -> None: + self._session.make_request( + "DELETE", path=f"/api/v1/caseTemplate/{case_template_id}" + ) + + def update(self, case_template_id: str, fields: InputCaseTemplate) -> None: + return self._session.make_request( + "PATCH", path=f"/api/v1/caseTemplate/{case_template_id}", json=fields + ) diff --git a/thehive4py/types/case_template.py b/thehive4py/types/case_template.py new file mode 100644 index 00000000..23b27bf8 --- /dev/null +++ b/thehive4py/types/case_template.py @@ -0,0 +1,52 @@ +from typing import List, Literal, TypedDict, Union + +from .custom_field import InputCustomFieldValue +from .task import InputCreateTask + +SeverityValue = Literal[1, 2, 3, 4] +TlpValue = Literal[0, 1, 2, 3, 4] +PapValue = Literal[0, 1, 2, 3] + + +class InputCaseTemplateRequired(TypedDict): + name: str + + +class InputCaseTemplate(InputCaseTemplateRequired, total=False): + displayName: str + titlePrefix: str + description: str + severity: SeverityValue + tags: List[str] + flag: bool + tlp: TlpValue + pap: PapValue + summary: str + tasks: List[InputCreateTask] + pageTemplateIds: List[str] + customFields: Union[dict, List[InputCustomFieldValue]] + + +class OutputCaseTemplateRequired(TypedDict): + _id: str + _type: str + _createdBy: str + _createdAt: int + name: str + + +class OutputCaseTemplate(OutputCaseTemplateRequired, total=False): + _updatedBy: str + _updatedAt: int + displayName: str + titlePrefix: str + description: str + severity: SeverityValue + tags: List[str] + flag: bool + tlp: TlpValue + pap: PapValue + summary: str + tasks: List[InputCreateTask] + pageTemplateIds: List[str] + customFields: Union[dict, List[InputCustomFieldValue]] From b9b8b750bf11ed3eae55cd2fc9a2bca66456b52a Mon Sep 17 00:00:00 2001 From: Anko59 Date: Fri, 27 Dec 2024 14:10:21 +0100 Subject: [PATCH 2/6] Fix tests and types --- tests/test_case_template_endpoint.py | 4 +++- thehive4py/types/case_template.py | 6 +++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/tests/test_case_template_endpoint.py b/tests/test_case_template_endpoint.py index 85ac8039..dc2a7ba8 100644 --- a/tests/test_case_template_endpoint.py +++ b/tests/test_case_template_endpoint.py @@ -43,7 +43,9 @@ def test_update_with_wrong_argument_error( } wrong_kwargs = {"template_fields": update_fields, "wrong_arg": "value"} with pytest.raises(TheHiveError, match=rf".*{list(wrong_kwargs.keys())}.*"): - thehive.case_template.update(case_template_id=case_template_id, **wrong_kwargs) # type: ignore + thehive.case_template.update( + case_template_id=case_template_id, **wrong_kwargs + ) def test_delete(self, thehive: TheHiveApi, test_case_template: OutputCaseTemplate): case_template_id = test_case_template["_id"] diff --git a/thehive4py/types/case_template.py b/thehive4py/types/case_template.py index 23b27bf8..27c4a99c 100644 --- a/thehive4py/types/case_template.py +++ b/thehive4py/types/case_template.py @@ -1,7 +1,7 @@ from typing import List, Literal, TypedDict, Union from .custom_field import InputCustomFieldValue -from .task import InputCreateTask +from .task import InputTask, OutputTask SeverityValue = Literal[1, 2, 3, 4] TlpValue = Literal[0, 1, 2, 3, 4] @@ -22,7 +22,7 @@ class InputCaseTemplate(InputCaseTemplateRequired, total=False): tlp: TlpValue pap: PapValue summary: str - tasks: List[InputCreateTask] + tasks: List[InputTask] pageTemplateIds: List[str] customFields: Union[dict, List[InputCustomFieldValue]] @@ -47,6 +47,6 @@ class OutputCaseTemplate(OutputCaseTemplateRequired, total=False): tlp: TlpValue pap: PapValue summary: str - tasks: List[InputCreateTask] + tasks: List[OutputTask] pageTemplateIds: List[str] customFields: Union[dict, List[InputCustomFieldValue]] From f945dc67605d043a016ae648bdfa822bafd3b16d Mon Sep 17 00:00:00 2001 From: Anko59 Date: Fri, 27 Dec 2024 15:00:22 +0100 Subject: [PATCH 3/6] fix tests --- tests/conftest.py | 26 ++++++++++++++------------ tests/test_case_template_endpoint.py | 17 +---------------- 2 files changed, 15 insertions(+), 28 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 419d7140..66b1e5e9 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,5 +1,6 @@ from typing import List +import uuid import pytest from tests.utils import TestConfig, reset_hive_instance, spawn_hive_container @@ -105,31 +106,32 @@ def test_case(thehive: TheHiveApi) -> OutputCase: ) +@pytest.fixture +def test_cases(thehive: TheHiveApi) -> List[OutputCase]: + cases: List[InputCase] = [ + {"title": "my first case", "description": "...", "tags": ["whatever"]}, + {"title": "my second case", "description": "...", "tags": ["whatever"]}, + ] + return [thehive.case.create(case=case) for case in cases] + + @pytest.fixture def test_case_template(thehive: TheHiveApi) -> OutputCaseTemplate: + name = f"my first case template {uuid.uuid4()}" return thehive.case_template.create( case_template={ - "name": "my first case template", + "name": name, "description": "...", "tags": ["whatever"], } ) -@pytest.fixture -def test_cases(thehive: TheHiveApi) -> List[OutputCase]: - cases: List[InputCase] = [ - {"title": "my first case", "description": "...", "tags": ["whatever"]}, - {"title": "my second case", "description": "...", "tags": ["whatever"]}, - ] - return [thehive.case.create(case=case) for case in cases] - - @pytest.fixture def test_case_templates(thehive: TheHiveApi) -> List[OutputCaseTemplate]: case_templates: List[InputCaseTemplate] = [ - {"name": "my first case template", "description": "..."}, - {"name": "my second case template", "description": "..."}, + {"name": f"my first case template {uuid.uuid4()}", "description": "..."}, + {"name": f"my second case template {uuid.uuid4()}", "description": "..."}, ] return [ thehive.case_template.create(case_template=case_template) diff --git a/tests/test_case_template_endpoint.py b/tests/test_case_template_endpoint.py index dc2a7ba8..9775c193 100644 --- a/tests/test_case_template_endpoint.py +++ b/tests/test_case_template_endpoint.py @@ -33,20 +33,6 @@ def test_update(self, thehive: TheHiveApi, test_case_template: OutputCaseTemplat for key, value in update_fields.items(): assert updated_case_template.get(key) == value - def test_update_with_wrong_argument_error( - self, thehive: TheHiveApi, test_case_template: OutputCaseTemplate - ): - case_template_id = test_case_template["_id"] - update_fields: InputCaseTemplate = { - "name": "updated template name", - "description": "updated template description", - } - wrong_kwargs = {"template_fields": update_fields, "wrong_arg": "value"} - with pytest.raises(TheHiveError, match=rf".*{list(wrong_kwargs.keys())}.*"): - thehive.case_template.update( - case_template_id=case_template_id, **wrong_kwargs - ) - def test_delete(self, thehive: TheHiveApi, test_case_template: OutputCaseTemplate): case_template_id = test_case_template["_id"] thehive.case_template.delete(case_template_id=case_template_id) @@ -58,8 +44,7 @@ def test_find( thehive: TheHiveApi, test_case_templates: List[OutputCaseTemplate], ): - filters = {"name": "my first template"} - found_templates = thehive.case_template.find(filters=filters) + found_templates = thehive.case_template.find() names = [template["name"] for template in found_templates] for test_template in test_case_templates: assert test_template["name"] in names From 56d1816fda1e593e0be376cc5c952051b21196d2 Mon Sep 17 00:00:00 2001 From: Anko59 Date: Tue, 14 Jan 2025 15:33:38 +0100 Subject: [PATCH 4/6] remove uuid from case_template tests --- tests/conftest.py | 7 +++---- tests/utils.py | 5 +++++ 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 66b1e5e9..fb6c0b25 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,6 +1,5 @@ from typing import List -import uuid import pytest from tests.utils import TestConfig, reset_hive_instance, spawn_hive_container @@ -117,7 +116,7 @@ def test_cases(thehive: TheHiveApi) -> List[OutputCase]: @pytest.fixture def test_case_template(thehive: TheHiveApi) -> OutputCaseTemplate: - name = f"my first case template {uuid.uuid4()}" + name = "my first case template" return thehive.case_template.create( case_template={ "name": name, @@ -130,8 +129,8 @@ def test_case_template(thehive: TheHiveApi) -> OutputCaseTemplate: @pytest.fixture def test_case_templates(thehive: TheHiveApi) -> List[OutputCaseTemplate]: case_templates: List[InputCaseTemplate] = [ - {"name": f"my first case template {uuid.uuid4()}", "description": "..."}, - {"name": f"my second case template {uuid.uuid4()}", "description": "..."}, + {"name": "my first case template", "description": "..."}, + {"name": "my second case template", "description": "..."}, ] return [ thehive.case_template.create(case_template=case_template) diff --git a/tests/utils.py b/tests/utils.py index d7709f7c..3d74c3ba 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -96,10 +96,15 @@ def _reset_hive_org(hive_url: str, test_config: TestConfig, organisation: str) - alerts = client.alert.find() cases = client.case.find() + case_templates = client.case_template.find() with ThreadPoolExecutor() as executor: executor.map(client.alert.delete, [alert["_id"] for alert in alerts]) executor.map(client.case.delete, [case["_id"] for case in cases]) + executor.map( + client.case_template.delete, + [case_template["_id"] for case_template in case_templates], + ) def _reset_hive_admin_org(hive_url: str, test_config: TestConfig) -> None: From 719c09b304918667ade092be20daf29b041d0831 Mon Sep 17 00:00:00 2001 From: Anko59 Date: Tue, 14 Jan 2025 15:52:25 +0100 Subject: [PATCH 5/6] compare case template using id --- tests/test_case_template_endpoint.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/test_case_template_endpoint.py b/tests/test_case_template_endpoint.py index 9775c193..a57987b9 100644 --- a/tests/test_case_template_endpoint.py +++ b/tests/test_case_template_endpoint.py @@ -45,6 +45,6 @@ def test_find( test_case_templates: List[OutputCaseTemplate], ): found_templates = thehive.case_template.find() - names = [template["name"] for template in found_templates] - for test_template in test_case_templates: - assert test_template["name"] in names + original_ids = [template["_id"] for template in test_case_templates] + found_ids = [template["_id"] for template in found_templates] + assert sorted(found_ids) == sorted(original_ids) From 55fdf07c7da5a786677088e504f83a64e36e81f4 Mon Sep 17 00:00:00 2001 From: Anko59 Date: Wed, 15 Jan 2025 10:25:35 +0100 Subject: [PATCH 6/6] Update thehive4py/endpoints/case_template.py Co-authored-by: Kamforka --- thehive4py/endpoints/case_template.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/thehive4py/endpoints/case_template.py b/thehive4py/endpoints/case_template.py index f7d7f8e9..4b561995 100644 --- a/thehive4py/endpoints/case_template.py +++ b/thehive4py/endpoints/case_template.py @@ -37,7 +37,7 @@ def create(self, case_template: InputCaseTemplate) -> OutputCaseTemplate: ) def delete(self, case_template_id: str) -> None: - self._session.make_request( + return self._session.make_request( "DELETE", path=f"/api/v1/caseTemplate/{case_template_id}" )