Skip to content

Commit 5f9e13c

Browse files
authored
Merge pull request #439 from TheHive-Project/434-review-alert-endpoints
#434 - Review alert endpoints
2 parents a9247a8 + d45871d commit 5f9e13c

File tree

2 files changed

+154
-77
lines changed

2 files changed

+154
-77
lines changed

tests/test_alert_endpoint.py

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,19 @@ def test_merge_into_case(
9999
merged_alert = thehive.alert.get(alert_id=alert_id)
100100
assert merged_alert.get("caseId") == merged_case["_id"]
101101

102+
def test_import_into_case(
103+
self, thehive: TheHiveApi, test_alert: OutputAlert, test_case: OutputCase
104+
):
105+
alert_id = test_alert["_id"]
106+
case_id = test_case["_id"]
107+
108+
imported_case = thehive.alert.import_into_case(
109+
alert_id=alert_id, case_id=case_id
110+
)
111+
imported_alert = thehive.alert.get(alert_id=alert_id)
112+
113+
assert imported_alert.get("caseId") == imported_case["_id"]
114+
102115
def test_bulk_merge_into_case(
103116
self, thehive: TheHiveApi, test_alerts: List[OutputAlert], test_case: OutputCase
104117
):
@@ -136,6 +149,16 @@ def test_bulk_delete(self, thehive: TheHiveApi, test_alerts: List[OutputAlert]):
136149
with pytest.raises(TheHiveError):
137150
thehive.alert.get(alert_id)
138151

152+
def test_get_similar_observables(
153+
self, thehive: TheHiveApi, test_alerts: List[OutputAlert]
154+
):
155+
156+
similar_observables = thehive.alert.get_similar_observables(
157+
alert_id=test_alerts[0]["_id"], alert_or_case_id=test_alerts[1]["_id"]
158+
)
159+
160+
assert similar_observables == []
161+
139162
def test_create_and_get_observable(
140163
self, thehive: TheHiveApi, test_alert: OutputAlert
141164
):
@@ -262,11 +285,12 @@ def test_add_and_download_attachment(
262285
)
263286

264287
for attachment, path in zip(added_attachments, download_attachment_paths):
265-
thehive.alert.download_attachment(
266-
alert_id=test_alert["_id"],
267-
attachment_id=attachment["_id"],
268-
attachment_path=path,
269-
)
288+
with pytest.deprecated_call():
289+
thehive.alert.download_attachment(
290+
alert_id=test_alert["_id"],
291+
attachment_id=attachment["_id"],
292+
attachment_path=path,
293+
)
270294

271295
for original, downloaded in zip(attachment_paths, download_attachment_paths):
272296
with open(original) as original_fp, open(downloaded) as downloaded_fp:

thehive4py/endpoints/alert.py

Lines changed: 125 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import json as jsonlib
2+
import warnings
23
from typing import Any, Dict, List, Optional
34

45
from thehive4py.endpoints._base import EndpointBase
@@ -56,30 +57,30 @@ def get(self, alert_id: str) -> OutputAlert:
5657

5758
return self._session.make_request("GET", path=f"/api/v1/alert/{alert_id}")
5859

59-
def update(self, alert_id: str, fields: InputUpdateAlert) -> None:
60-
"""Update an alert.
60+
def delete(self, alert_id: str) -> None:
61+
"""Delete an alert.
6162
6263
Args:
6364
alert_id: The id of the alert.
64-
fields: The fields of the alert to update.
6565
6666
Returns:
6767
N/A
6868
"""
69-
return self._session.make_request(
70-
"PATCH", path=f"/api/v1/alert/{alert_id}", json=fields
71-
)
69+
return self._session.make_request("DELETE", path=f"/api/v1/alert/{alert_id}")
7270

73-
def delete(self, alert_id: str) -> None:
74-
"""Delete an alert.
71+
def update(self, alert_id: str, fields: InputUpdateAlert) -> None:
72+
"""Update an alert.
7573
7674
Args:
7775
alert_id: The id of the alert.
76+
fields: The fields of the alert to update.
7877
7978
Returns:
8079
N/A
8180
"""
82-
return self._session.make_request("DELETE", path=f"/api/v1/alert/{alert_id}")
81+
return self._session.make_request(
82+
"PATCH", path=f"/api/v1/alert/{alert_id}", json=fields
83+
)
8384

8485
def bulk_update(self, fields: InputBulkUpdateAlert) -> None:
8586
"""Update multiple alerts with the same values.
@@ -94,17 +95,22 @@ def bulk_update(self, fields: InputBulkUpdateAlert) -> None:
9495
"PATCH", path="/api/v1/alert/_bulk", json=fields
9596
)
9697

97-
def bulk_delete(self, ids: List[str]) -> None:
98-
"""Delete multiple alerts.
98+
def promote_to_case(
99+
self, alert_id: str, fields: InputPromoteAlert = {}
100+
) -> OutputCase:
101+
"""Promote an alert into a case.
99102
100103
Args:
101-
ids: The ids of the alerts to delete.
104+
alert_id: The id of the alert.
105+
fields: Override for the fields of the case created from the alert.
102106
103107
Returns:
104-
N/A
108+
The case from the promoted alert.
105109
"""
106110
return self._session.make_request(
107-
"POST", path="/api/v1/alert/delete/_bulk", json={"ids": ids}
111+
"POST",
112+
path=f"/api/v1/alert/{alert_id}/case",
113+
json=fields,
108114
)
109115

110116
def follow(self, alert_id: str) -> None:
@@ -129,56 +135,93 @@ def unfollow(self, alert_id: str) -> None:
129135
"""
130136
self._session.make_request("POST", path=f"/api/v1/alert/{alert_id}/unfollow")
131137

132-
def promote_to_case(
133-
self, alert_id: str, fields: InputPromoteAlert = {}
134-
) -> OutputCase:
135-
"""Promote an alert into a case.
138+
def merge_into_case(self, alert_id: str, case_id: str) -> OutputCase:
139+
"""Merge an alert into an existing case.
136140
137141
Args:
138-
alert_id: The id of the alert.
139-
fields: Override for the fields of the case created from the alert.
142+
alert_id: The id of the alert to merge.
143+
case_id: The id of the case to merge the alert into.
140144
141145
Returns:
142-
The case from the promoted alert.
146+
The case into which the alert was merged.
143147
"""
144148
return self._session.make_request(
145-
"POST",
146-
path=f"/api/v1/alert/{alert_id}/case",
147-
json=fields,
149+
"POST", path=f"/api/v1/alert/{alert_id}/merge/{case_id}"
148150
)
149151

150-
def create_observable(
151-
self,
152-
alert_id: str,
153-
observable: InputObservable,
154-
observable_path: Optional[str] = None,
155-
) -> List[OutputObservable]:
156-
"""Create an observable in an alert.
152+
def import_into_case(self, alert_id: str, case_id: str) -> OutputCase:
153+
"""Import alert observables and procedures into an existing case.
157154
158155
Args:
159-
alert_id: The id of the alert.
160-
observable: The fields of the observable to create.
161-
observable_path: Optional path in case of a file based observable.
156+
alert_id: The id of the alert to merge.
157+
case_id: The id of the case to merge the alert into.
162158
163159
Returns:
164-
The created alert observables.
160+
The case into which the alert observables/procedures were imported.
165161
"""
162+
return self._session.make_request(
163+
"POST", path=f"/api/v1/alert/{alert_id}/import/{case_id}"
164+
)
166165

167-
kwargs = self._build_observable_kwargs(
168-
observable=observable, observable_path=observable_path
166+
def bulk_merge_into_case(self, case_id: str, alert_ids: List[str]) -> OutputCase:
167+
"""Merge an alert into an existing case.
168+
169+
Args:
170+
case_id: The id of the case to merge the alerts into.
171+
alert_ids: The list of alert ids to merge.
172+
173+
Returns:
174+
The case into which the alerts were merged.
175+
"""
176+
return self._session.make_request(
177+
"POST",
178+
path="/api/v1/alert/merge/_bulk",
179+
json={"caseId": case_id, "alertIds": alert_ids},
180+
)
181+
182+
def bulk_delete(self, ids: List[str]) -> None:
183+
"""Delete multiple alerts.
184+
185+
Args:
186+
ids: The ids of the alerts to delete.
187+
188+
Returns:
189+
N/A
190+
"""
191+
return self._session.make_request(
192+
"POST", path="/api/v1/alert/delete/_bulk", json={"ids": ids}
169193
)
194+
195+
def get_similar_observables(
196+
self, alert_id: str, alert_or_case_id: str
197+
) -> List[OutputObservable]:
198+
"""Get similar observables between an alert and another alert or case.
199+
200+
Args:
201+
alert_id: The id of the alert to use as base for observable similarity.
202+
alert_or_case_id: The id of the alert/case to get similar observables from.
203+
204+
Returns:
205+
The list of similar observables.
206+
"""
170207
return self._session.make_request(
171-
"POST", path=f"/api/v1/alert/{alert_id}/observable", **kwargs
208+
"GET",
209+
path=f"/api/v1/alert/{alert_id}/similar/{alert_or_case_id}/observables",
172210
)
173211

174212
def add_attachment(
175-
self, alert_id: str, attachment_paths: List[str]
213+
self,
214+
alert_id: str,
215+
attachment_paths: List[str],
216+
can_rename: bool = True,
176217
) -> List[OutputAttachment]:
177218
"""Create an attachment in an alert.
178219
179220
Args:
180221
alert_id: The id of the alert.
181222
attachment_paths: List of paths to the attachments to create.
223+
can_rename: If set to True, the files can be renamed if they already exist
224+
with the same name.
182225
183226
Returns:
184227
The created alert attachments.
@@ -188,71 +231,81 @@ def add_attachment(
188231
for attachment_path in attachment_paths
189232
]
190233
return self._session.make_request(
191-
"POST", f"/api/v1/alert/{alert_id}/attachments", files=files
234+
"POST",
235+
f"/api/v1/alert/{alert_id}/attachments",
236+
data={"canRename": can_rename},
237+
files=files,
192238
)["attachments"]
193239

194-
def download_attachment(
195-
self, alert_id: str, attachment_id: str, attachment_path: str
196-
) -> None:
197-
"""Download an alert attachment.
240+
def delete_attachment(self, alert_id: str, attachment_id: str) -> None:
241+
"""Delete an alert attachment.
198242
199243
Args:
200244
alert_id: The id of the alert.
201245
attachment_id: The id of the alert attachment.
202-
attachment_path: The local path to download the attachment to.
203246
204247
Returns:
205248
N/A
206249
"""
250+
207251
return self._session.make_request(
208-
"GET",
209-
path=f"/api/v1/alert/{alert_id}/attachment/{attachment_id}/download",
210-
download_path=attachment_path,
252+
"DELETE", path=f"/api/v1/alert/{alert_id}/attachment/{attachment_id}"
211253
)
212254

213-
def delete_attachment(self, alert_id: str, attachment_id: str) -> None:
214-
"""Delete an alert attachment.
255+
def download_attachment(
256+
self, alert_id: str, attachment_id: str, attachment_path: str
257+
) -> None:
258+
"""Download an alert attachment.
259+
260+
!!! warning
261+
Deprecated: use [organisation.download_attachment]
262+
[thehive4py.endpoints.organisation.OrganisationEndpoint.download_attachment]
263+
instead
215264
216265
Args:
217266
alert_id: The id of the alert.
218267
attachment_id: The id of the alert attachment.
268+
attachment_path: The local path to download the attachment to.
219269
220270
Returns:
221271
N/A
222272
"""
223273

224-
return self._session.make_request(
225-
"DELETE", path=f"/api/v1/alert/{alert_id}/attachment/{attachment_id}"
274+
warnings.warn(
275+
message=(
276+
"Deprecated: use the organisation.download_attachment method instead"
277+
),
278+
category=DeprecationWarning,
279+
stacklevel=2,
226280
)
227-
228-
def merge_into_case(self, alert_id: str, case_id: str) -> OutputCase:
229-
"""Merge an alert into an existing case.
230-
231-
Args:
232-
alert_id: The id of the alert to merge.
233-
case_id: The id of the case to merge the alert into.
234-
235-
Returns:
236-
The case into which the alert was merged.
237-
"""
238281
return self._session.make_request(
239-
"POST", path=f"/api/v1/alert/{alert_id}/merge/{case_id}"
282+
"GET",
283+
path=f"/api/v1/alert/{alert_id}/attachment/{attachment_id}/download",
284+
download_path=attachment_path,
240285
)
241286

242-
def bulk_merge_into_case(self, case_id: str, alert_ids: List[str]) -> OutputCase:
243-
"""Merge an alert into an existing case.
287+
def create_observable(
288+
self,
289+
alert_id: str,
290+
observable: InputObservable,
291+
observable_path: Optional[str] = None,
292+
) -> List[OutputObservable]:
293+
"""Create an observable in an alert.
244294
245295
Args:
246-
case_id: The id of the case to merge the alerts into.
247-
alert_ids: The list of alert ids to merge.
296+
alert_id: The id of the alert.
297+
observable: The fields of the observable to create.
298+
observable_path: Optional path in case of a file based observable.
248299
249300
Returns:
250-
The case into which the alerts were merged.
301+
The created alert observables.
251302
"""
303+
304+
kwargs = self._build_observable_kwargs(
305+
observable=observable, observable_path=observable_path
306+
)
252307
return self._session.make_request(
253-
"POST",
254-
path="/api/v1/alert/merge/_bulk",
255-
json={"caseId": case_id, "alertIds": alert_ids},
308+
"POST", path=f"/api/v1/alert/{alert_id}/observable", **kwargs
256309
)
257310

258311
def find(

0 commit comments

Comments
 (0)