From 2ea9b65381ff9abe28fc12c6188585bb52a5050f Mon Sep 17 00:00:00 2001 From: cr8mtdev <> Date: Fri, 17 May 2024 08:16:16 +0200 Subject: [PATCH] Add multiple sharing options * Case: list existing case shares, delete existing case share, create new case share * CaseTask: create new case task share * CaseObservable: create new case observable share * Add new model "CaseShare" to models.py * Add new exception "CaseSharingException" to exceptions.py --- thehive4py/api.py | 128 ++++++++++++++++++++++++++++++++++++++- thehive4py/exceptions.py | 5 ++ thehive4py/models.py | 22 +++++++ 3 files changed, 154 insertions(+), 1 deletion(-) diff --git a/thehive4py/api.py b/thehive4py/api.py index e8748937..651bce0f 100644 --- a/thehive4py/api.py +++ b/thehive4py/api.py @@ -1406,4 +1406,130 @@ def update_alert_artifact(self, artifact_id, alert_artifact, fields=[]): try: return requests.patch(req, headers={'Content-Type': 'application/json'}, json=data, proxies=self.proxies, auth=self.auth, verify=self.cert) except requests.exceptions.RequestException as e: - raise CaseObservableException("Case observable update error: {}".format(e)) \ No newline at end of file + raise CaseObservableException("Case observable update error: {}".format(e)) + + + def create_case_share(self, case_id, case_share): + + """ + Create a case share + + Arguments: + case_id (str): Case identifier + case_share: Instance of [CaseShare][thehive4py.models.CaseShare] + + Returns: + response (requests.Response): Response object including a JSON description of a case share + + Raises: + CaseSharingException: An error occured during the case share creation + + """ + + req = self.url + "/api/case/{}/shares".format(case_id) + data = {"shares": [case_share.jsonify(excludes=['id'])]} + + try: + return requests.post(req, headers={'Content-Type': 'application/json'}, json=data, proxies=self.proxies, auth=self.auth, verify=self.cert) + except requests.exceptions.RequestException as e: + raise CaseSharingException("Case share creation error: {}".format(e)) + + def delete_case_share(self, case_id, organisation): + + """ + Delete a case share + + Arguments: + case_id (str): Case identifier + organisation (str): Name of the organization from which the share should be deleted + + Returns: + response (requests.Response): Response object including a JSON description of a case share + + Raises: + CaseSharingException: An error occured during the case share deletion + + """ + + req = self.url + "/api/case/{}/shares".format(case_id) + data = {"organisations": [organisation]} + + try: + return requests.delete(req, headers={'Content-Type': 'application/json'}, json=data, proxies=self.proxies, auth=self.auth, verify=self.cert) + except requests.exceptions.RequestException as e: + raise CaseSharingException("Case share deletion error: {}".format(e)) + + def list_case_shares(self, case_id): + + """ + List all organizations a case is shared with + + Arguments: + case_id (str): Case identifier + + Returns: + response (requests.Response): Response object including a JSON description of all organizations a case is shared with + + Raises: + CaseSharingException: An error occured while retrieving the sharing information + + """ + + req = self.url + "/api/case/{}/shares".format(case_id) + + try: + return requests.get(req, headers={'Content-Type': 'application/json'}, proxies=self.proxies, auth=self.auth, verify=self.cert) + except requests.exceptions.RequestException as e: + raise CaseSharingException("Case share retrieving error: {}".format(e)) + +def create_case_task_share(self, task_id, organisation): + + """ + Share a specific case task + !! Note: To successfully create the share the case has to be already shared with the specified organisation + + Arguments: + task_id (str): Id of the task to share + organisation (str): Name of the organization to share the task with + + Returns: + response (requests.Response): Response object including a JSON description of a case task share + + Raises: + CaseTaskException: An error occured during case task sharing + + """ + + req = self.url + "/api/case/task/{}/shares".format(task_id) + data = {"organisations": [organisation]} + + try: + return requests.post(req, headers={'Content-Type': 'application/json'}, json=data, proxies=self.proxies, auth=self.auth, verify=self.cert) + except requests.exceptions.RequestException as e: + raise CaseTaskException("Case task share creation error: {}".format(e)) + +def create_case_observable_share(self, observable_id, organisation): + + """ + Share a specific case observable + !! Note: To successfully create the share the case has to be already shared with the specified organisation + + Arguments: + observable_id (str): Id of the observable to share + organisation (str): Name of the organization to share the observable with + + Returns: + response (requests.Response): Response object including a JSON description of a case observable share + + Raises: + CaseTaskException: An error occured during case observable sharing + + """ + + req = self.url + "/api/case/artifact/{}/shares".format(observable_id) + data = {"organisations": [organisation]} + + try: + return requests.post(req, headers={'Content-Type': 'application/json'}, json=data, proxies=self.proxies, auth=self.auth, verify=self.cert) + except requests.exceptions.RequestException as e: + raise CaseTaskException("Case observable share creation error: {}".format(e)) \ No newline at end of file diff --git a/thehive4py/exceptions.py b/thehive4py/exceptions.py index 80f39db3..e068104a 100644 --- a/thehive4py/exceptions.py +++ b/thehive4py/exceptions.py @@ -32,6 +32,11 @@ class CaseObservableException(CaseException): """ pass +class CaseSharingException(CaseException): + """ + Exception raised by failure of API calls related to `Case Sharing` handling + """ + pass class ObservableException(TheHiveException): """ diff --git a/thehive4py/models.py b/thehive4py/models.py index 477b603c..1f2f60bc 100644 --- a/thehive4py/models.py +++ b/thehive4py/models.py @@ -594,7 +594,29 @@ def __init__(self, **attributes): self.data = [{'attachment': (filename, file_object, mime)}] else: self.data = data + + +class CaseShare(JSONSerializable): + """ + Model class describing a case share as defined in TheHive + + Arguments: + organisationName (str): Name of the organization to share with or delete share from. Default: None + profile (str): Sharing profile. Default: read_only + tasks (str): Tasks to be shared. Default: None + observables (str): Observables to be shared. Default: None + json (JSON): If the field is not equal to None, the Task is instantiated using the JSON value instead of the arguements + """ + + def __init__(self, **attributes): + if attributes.get('json', False): + attributes = attributes['json'] + self.organisationName = attributes.get('organisationName', None) + self.profile = attributes.get('profile', None) + self.tasks = attributes.get('tasks', None) + self.observables = attributes.get('observables', None) + class Alert(JSONSerializable): """