Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

333 add a generic query endpoint and align case update fields with other update methods #334

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 22 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,23 @@
![TheHive Logo](https://strangebee.com/wp-content/uploads/2024/07/Icon4Nav_TheHive.png)

[![Discord](https://img.shields.io/discord/779945042039144498)](https://chat.thehive-project.org)
[![License](https://img.shields.io/github/license/TheHive-Project/TheHive4py)](./LICENSE)
[![Pypi Page](https://img.shields.io/pypi/dm/thehive4py)](https://pypi.org/project/thehive4py)
[![CICD Action Badge](https://github.com/TheHive-Project/TheHive4py/actions/workflows/main-cicd.yml/badge.svg)](https://github.com/TheHive-Project/TheHive4py/actions/workflows/main-cicd.yml)
<p align="center">
<a href="https://github.com/TheHive-Project/TheHive4py"><img src="https://strangebee.com/wp-content/uploads/2024/07/Icon4Nav_TheHive.png" alt="TheHive Logo"></a>
</p>
<p align="center">
<em>thehive4py - the de facto Python API client of <a href="https://strangebee.com/thehive/">TheHive</a></em>
</p>
<p align="center">
<a href="https://discord.com/invite/XhxG3vzM44" target="_blank">
<img src="https://img.shields.io/discord/779945042039144498" alt="Discord">
</a>
<a href="./LICENSE" target="_blank">
<img src="https://img.shields.io/github/license/TheHive-Project/TheHive4py" alt="License">
</a>
<a href="https://pypi.org/project/thehive4py" target="_blank">
<img src="https://img.shields.io/pypi/dm/thehive4py" alt="PyPI">
</a>
<a href="https://github.com/TheHive-Project/TheHive4py/actions/workflows/main-cicd.yml" target="_blank">
<img src="https://github.com/TheHive-Project/TheHive4py/actions/workflows/main-cicd.yml/badge.svg" alt="CICD">
</a>
</p>

# thehive4py

Expand Down Expand Up @@ -93,7 +107,7 @@ The above snippet will create a new alert with the minimally required fields and

## Add alert observables

To make your alerts more informative and actionable, you can add observables to them. Observables are specific pieces of data related to an alert. In this example, we'll enhance the previous alert with two observables: an IP address (`93.184.216.34`) and a domain (`example.com`).
To make your alerts more informative and actionable, you can add observables to them. Observables are specific pieces of data related to an alert. In this example, we'll enhance the previous alert with two observables: an IP address `93.184.216.34` and a domain `example.com`.

**Method 1: Adding observables individually**

Expand All @@ -116,9 +130,6 @@ This method is useful when you want to add observables to an alert after its ini

Alternatively, if you already know the observables when creating the alert, you can use the `observables` field within the alert creation method for a more concise approach:


Alternatively in case the observables are known during alert time we can use the `alert.create` method's `observables` field as a shortcut:

```python
my_alert = hive.alert.create(
alert={
Expand All @@ -141,7 +152,7 @@ By incorporating observables into your alerts, you provide valuable context and

## Update an alert

If you need to add or modify fields in an existing alert, you can easily update it using client's `alert.update` method. In this example, we'll add a tag (`my-tag`) and change the alert's title:
If you need to add or modify fields in an existing alert, you can easily update it using client's `alert.update` method. In this example, we'll add a tag `my-tag` and change the alert's title:

```python
hive.alert.update(
Expand Down Expand Up @@ -273,7 +284,6 @@ To contribute to `thehive4py`, follow these steps:

2. **Create a branch:** Once you have an issue, create a branch for your work. Use the following naming convention: `<issue-no>-title-of-branch`. For example, if you're working on issue #1 and updating the readme, name the branch `1-update-readme`.

3. **Commit prefix:** When making commits, prefix them with `#<issue-no> - commit message`. This practice helps in easy navigation and tracking of commits back to the corresponding issue. For instance, use `#1 - update readme` as the commit message.


## Run CI checks before pushing changes
Expand Down
2 changes: 1 addition & 1 deletion tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
@pytest.fixture(scope="session")
def test_config():
return TestConfig(
image_name="strangebee/thehive:5.3.0",
image_name="strangebee/thehive:5.3.4",
container_name="thehive4py-integration-tester",
user="[email protected]",
password="secret",
Expand Down
29 changes: 28 additions & 1 deletion tests/test_case_endpoint.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,39 @@ def test_update(self, thehive: TheHiveApi, test_case: OutputCase):
"title": "my updated case",
"description": "my updated description",
}
thehive.case.update(case_id=case_id, case=update_fields)
thehive.case.update(case_id=case_id, fields=update_fields)
updated_case = thehive.case.get(case_id=case_id)

for key, value in update_fields.items():
assert updated_case.get(key) == value

def test_update_with_deprecation_warning(
self, thehive: TheHiveApi, test_case: OutputCase
):
case_id = test_case["_id"]
update_fields: InputUpdateCase = {
"title": "my updated case",
"description": "my updated description",
}
with pytest.deprecated_call():
thehive.case.update(case_id=case_id, case=update_fields)
updated_case = thehive.case.get(case_id=case_id)

for key, value in update_fields.items():
assert updated_case.get(key) == value

def test_update_with_wrong_argument_error(
self, thehive: TheHiveApi, test_case: OutputCase
):
case_id = test_case["_id"]
update_fields: InputUpdateCase = {
"title": "my updated case",
"description": "my updated description",
}
wrong_kwargs = {"case_fields": update_fields, "what": "ever"}
with pytest.raises(TheHiveError, match=rf".*{list(wrong_kwargs.keys())}.*"):
thehive.case.update(case_id=case_id, **wrong_kwargs) # type:ignore

def test_bulk_update(self, thehive: TheHiveApi, test_cases: List[OutputCase]):
case_ids = [case["_id"] for case in test_cases]
update_fields: InputBulkUpdateCase = {
Expand Down
11 changes: 11 additions & 0 deletions tests/test_query_endpoint.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
from thehive4py.client import TheHiveApi
from thehive4py.types.alert import OutputAlert


class TestQueryEndpoint:
def test_simple_query(self, thehive: TheHiveApi, test_alert: OutputAlert):
queried_alerts = thehive.query.run(
query=[{"_name": "getAlert", "idOrName": test_alert["_id"]}],
)

assert [test_alert] == queried_alerts
4 changes: 4 additions & 0 deletions thehive4py/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
from thehive4py.endpoints.cortex import CortexEndpoint
from thehive4py.endpoints.custom_field import CustomFieldEndpoint
from thehive4py.endpoints.observable_type import ObservableTypeEndpoint
from thehive4py.endpoints.query import QueryEndpoint
from thehive4py.session import TheHiveSession


Expand Down Expand Up @@ -60,6 +61,9 @@ def __init__(
# connector endpoints
self.cortex = CortexEndpoint(self.session)

# standard endpoints
self.query = QueryEndpoint(self.session)

@property
def session_organisation(self) -> Optional[str]:
return self.session.headers.get("X-Organisation") # type:ignore
Expand Down
23 changes: 21 additions & 2 deletions thehive4py/endpoints/case.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import json as jsonlib
import warnings
from typing import List, Optional, Sequence, Union

from thehive4py.endpoints._base import EndpointBase
from thehive4py.errors import TheHiveError
from thehive4py.query import QueryExpr
from thehive4py.query.filters import FilterExpr
from thehive4py.query.page import Paginate
Expand Down Expand Up @@ -38,9 +40,26 @@ def get(self, case_id: CaseId) -> OutputCase:
def delete(self, case_id: CaseId) -> None:
self._session.make_request("DELETE", path=f"/api/v1/case/{case_id}")

def update(self, case_id: CaseId, case: InputUpdateCase) -> None:
def update(
self, case_id: CaseId, fields: Optional[InputUpdateCase] = {}, **kwargs
) -> None:

if not fields:
if "case" not in kwargs:
raise TheHiveError(
f"Unrecognized keyword arguments: {list(kwargs.keys())}. "
"Please use the `fields` argument to supply case update values."
)
warnings.warn(
message="The `case` argument has been deprecated to follow the same "
"convention like other update methods. Please use the `fields` "
"argument to prevent breaking changes in the future.",
category=DeprecationWarning,
)
fields = kwargs["case"]

return self._session.make_request(
"PATCH", path=f"/api/v1/case/{case_id}", json=case
"PATCH", path=f"/api/v1/case/{case_id}", json=fields
)

def bulk_update(self, fields: InputBulkUpdateCase) -> None:
Expand Down
14 changes: 14 additions & 0 deletions thehive4py/endpoints/query.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import typing

from thehive4py.endpoints._base import EndpointBase


class QueryEndpoint(EndpointBase):
def run(
self, query: typing.List[dict], exclude_fields: typing.List[str] = []
) -> typing.Any:
return self._session.make_request(
"POST",
path="/api/v1/query",
json={"query": query, "excludeFields": exclude_fields},
)
Loading