def prepare_case_template(subject, indicatorlevel, emailbody, casetype, tag,
                          templatename):
    hive_address = ''.join(settings.stored_hive_address[0])
    hive_api = ''.join(settings.stored_api_key[0])

    #Define the connection to thehive installation (including the generated API key).
    api = TheHiveApi(hive_address, hive_api, None, {'http': '', 'https': ''})

    # Prepare the sample Case based on a Template
    print(
        str(datetime.datetime.now()) + "  Preparing the case for " + casetype)

    case = Case(
        title=subject,
        tlp=indicatorlevel,
        tags=[tag],
        description=emailbody,
        template=templatename,
        #       		type=alerttype,
        #        	source='instance1',
        #        	sourceRef=sourceRef,
        #		artifacts="")
    )

    # Create the Case

    id = None
    #################################################
    response = api.create_case(case)
    #################################################

    if response.status_code == 201:
        #    		print(json.dumps(response.json(), indent=4, sort_keys=True))
        print('')
        id = response.json()['id']
        simple_id = response.json()['caseId']
    else:
        print('ko: {}/{}'.format(response.status_code, response.text))
        sys.exit(0)

# Get all the details of the created case
    print(
        str(datetime.datetime.now()) + "  Getting created case {}".format(id))
    response = api.get_case(id)
    if response.status_code == requests.codes.ok:
        #    		print(json.dumps(response.json(), indent=4, sort_keys=True))
        print(
            str(datetime.datetime.now()) + "  Observable succesfully created.")

    else:
        print('ko: {}/{}'.format(response.status_code, response.text))

    return id, simple_id, emailbody
Example #2
0
class TheHive(AppBase):
    """
    An example of a Walkoff App.
    Inherit from the AppBase class to have Redis, logging, and console logging set up behind the scenes.
    """

    __version__ = "1.1.0"
    app_name = "thehive"

    def __init__(self, redis, logger, console_logger=None):
        """
        Each app should have this __init__ to set up Redis and logging.
        :param redis:
        :param logger:
        :param console_logger:
        """
        super().__init__(redis, logger, console_logger)

    # async def run_analyzer(self, apikey, url, title_query):
    #    self.thehive = TheHiveApi(url, apikey, cert=False)

    #    response = self.thehive.find_cases(query=String("title:'%s'" % title_query), range='all', sort=[])
    #    return response.text

    def __connect_thehive(self, url, apikey, organisation):
        if organisation:
            self.thehive = TheHiveApi(url,
                                      apikey,
                                      cert=False,
                                      organisation=organisation)
        else:
            self.thehive = TheHiveApi(url, apikey, cert=False)

    async def search_case_title(self, apikey, url, organisation, title_query):
        self.__connect_thehive(url, apikey, organisation)

        response = self.thehive.find_cases(query=ContainsString(
            "title", title_query),
                                           range="all",
                                           sort=[])

        return response.text

    async def custom_search(self,
                            apikey,
                            url,
                            organisation,
                            search_for,
                            custom_query,
                            range="all"):
        self.__connect_thehive(url, apikey, organisation)

        try:
            custom_query = json.loads(custom_query)
        except:
            # raise IOError("Invalid JSON payload received.")
            pass

        if search_for == "alert":
            response = self.thehive.find_alerts(query=custom_query,
                                                range="all",
                                                sort=[])
        else:
            response = self.thehive.find_cases(query=custom_query,
                                               range="all",
                                               sort=[])

        if (response.status_code == 200 or response.status_code == 201
                or response.status_code == 202):
            return response.text
        else:
            raise IOError(response.text)

    async def add_case_artifact(
        self,
        apikey,
        url,
        organisation,
        case_id,
        data,
        datatype,
        tags=None,
        tlp=None,
        ioc=None,
        sighted=None,
        description="",
    ):
        self.__connect_thehive(url, apikey, organisation)

        tlp = int(tlp) if tlp else 2
        ioc = True if ioc.lower() == "true" else False
        sighted = True if sighted.lower() == "true" else False
        if not description:
            description = "Created by shuffle"

        tags = (tags.split(", ")
                if ", " in tags else tags.split(",") if "," in tags else [])

        item = thehive4py.models.CaseObservable(
            dataType=datatype,
            data=data,
            tlp=tlp,
            ioc=ioc,
            sighted=sighted,
            tags=tags,
            message=description,
        )

        return self.thehive.create_case_observable(case_id, item).text

    async def search_alert_title(self,
                                 apikey,
                                 url,
                                 organisation,
                                 title_query,
                                 search_range="0-25"):
        self.__connect_thehive(url, apikey, organisation)

        # Could be "all" too
        if search_range == "":
            search_range = "0-25"

        response = self.thehive.find_alerts(query=ContainsString(
            "title", title_query),
                                            range=search_range,
                                            sort=[])

        return response.text

    async def create_case(
        self,
        apikey,
        url,
        organisation,
        template,
        title,
        description="",
        tlp=1,
        severity=1,
        tags="",
    ):
        self.__connect_thehive(url, apikey, organisation)
        if tags:
            if ", " in tags:
                tags = tags.split(", ")
            elif "," in tags:
                tags = tags.split(",")
            else:
                tags = [tags]
        else:
            tags = []

        # Wutface fix
        if not tlp:
            tlp = 1
        if not severity:
            severity = 1

        if isinstance(tlp, str):
            if not tlp.isdigit():
                return "TLP needs to be a number from 0-2, not %s" % tlp
            tlp = int(tlp)
        if isinstance(severity, str):
            if not severity.isdigit():
                return "Severity needs to be a number from 0-2, not %s" % tlp

            severity = int(severity)

        if tlp > 3 or tlp < 0:
            return "TLP needs to be a number from 0-3, not %d" % tlp
        if severity > 2 or severity < 0:
            return "Severity needs to be a number from 0-2, not %d" % tlp

        Casetemplate = template if template else None

        case = thehive4py.models.Case(
            title=title,
            tlp=tlp,
            severity=severity,
            tags=tags,
            description=description,
            template=Casetemplate,
        )

        try:
            ret = self.thehive.create_case(case)
            return ret.text
        except requests.exceptions.ConnectionError as e:
            return "ConnectionError: %s" % e

    async def create_alert(
        self,
        apikey,
        url,
        organisation,
        type,
        source,
        sourceref,
        title,
        description="",
        tlp=1,
        severity=1,
        tags="",
        artifacts="",
    ):
        self.__connect_thehive(url, apikey, organisation)
        if tags:
            if ", " in tags:
                tags = tags.split(", ")
            elif "," in tags:
                tags = tags.split(",")
            else:
                tags = [tags]
        else:
            tags = []

        # Wutface fix
        if not tlp:
            tlp = 1
        if not severity:
            severity = 1

        if isinstance(tlp, str):
            if not tlp.isdigit():
                return "TLP needs to be a number from 0-3, not %s" % tlp

            tlp = int(tlp)
        if isinstance(severity, str):
            if not severity.isdigit():
                return "Severity needs to be a number from 1-3, not %s" % severity

            severity = int(severity)

        if tlp > 3 or tlp < 0:
            return "TLP needs to be a number from 0-3, not %d" % tlp
        if severity > 3 or severity < 1:
            return "Severity needs to be a number from 1-3, not %d" % severity

        all_artifacts = []
        if artifacts != "":
            # print("ARTIFACTS: %s" % artifacts)
            if isinstance(artifacts, str):
                # print("ITS A STRING!")
                try:
                    artifacts = json.loads(artifacts)
                except:
                    print("[ERROR] Error in parsing artifacts!")

            # print("ART HERE: %s" % artifacts)
            # print("ART: %s" % type(artifacts))
            if isinstance(artifacts, list):
                print("ITS A LIST!")
                for item in artifacts:
                    print("ITEM: %s" % item)
                    try:
                        artifact = thehive4py.models.AlertArtifact(
                            dataType=item["data_type"],
                            data=item["data"],
                        )

                        try:
                            artifact["message"] = item["message"]
                        except:
                            pass

                        if item["data_type"] == "ip":
                            try:
                                if item["is_private_ip"]:
                                    message += " IP is private."
                            except:
                                pass

                        all_artifacts.append(artifact)
                    except KeyError as e:
                        print("Error in artifacts: %s" % e)

        alert = thehive4py.models.Alert(
            title=title,
            tlp=tlp,
            severity=severity,
            tags=tags,
            description=description,
            type=type,
            source=source,
            sourceRef=sourceref,
            artifacts=all_artifacts,
        )

        try:
            ret = self.thehive.create_alert(alert)
            return ret.text
        except requests.exceptions.ConnectionError as e:
            return "ConnectionError: %s" % e

    async def create_alert_artifact(
        self,
        apikey,
        url,
        organisation,
        alert_id,
        dataType,
        data,
        message=None,
        tlp="2",
        ioc="False",
        sighted="False",
        ignoreSimilarity="False",
        tags=None,
    ):
        self.__connect_thehive(url, apikey, organisation, version=4)

        if tlp:
            tlp = int(tlp)
        else:
            tlp = 2

        ioc = ioc.lower().strip() == "true"
        sighted = sighted.lower().strip() == "true"
        ignoreSimilarity = ignoreSimilarity.lower().strip() == "true"

        if tags:
            tags = [x.strip() for x in tags.split(",")]
        else:
            tags = []

        alert_artifact = thehive4py.models.AlertArtifact(
            dataType=dataType,
            data=data,
            message=message,
            tlp=tlp,
            ioc=ioc,
            sighted=sighted,
            ignoreSimilarity=ignoreSimilarity,
            tags=tags,
        )

        try:
            ret = self.thehive.create_alert_artifact(alert_id, alert_artifact)
        except requests.exceptions.ConnectionError as e:
            return "ConnectionError: %s" % e

        if ret.status_code > 299:
            raise ConnectionError(ret.text)

        return ret.text

    # Gets an item based on input. E.g. field_type = Alert
    async def get_item(self, apikey, url, organisation, field_type, cur_id):
        self.__connect_thehive(url, apikey, organisation)

        newstr = ""
        ret = ""
        if field_type.lower() == "alert":
            ret = self.thehive.get_alert(cur_id + "?similarity=1")
        elif field_type.lower() == "case":
            ret = self.thehive.get_case(cur_id)
        elif field_type.lower() == "case_observables":
            ret = self.thehive.get_case_observables(cur_id)
        elif field_type.lower() == "case_task":
            ret = self.thehive.get_case_task(cur_id)
        elif field_type.lower() == "case_tasks":
            ret = self.thehive.get_case_tasks(cur_id)
        elif field_type.lower() == "case_template":
            ret = self.thehive.get_case_tasks(cur_id)
        elif field_type.lower() == "linked_cases":
            ret = self.thehive.get_linked_cases(cur_id)
        elif field_type.lower() == "task_log":
            ret = self.thehive.get_task_log(cur_id)
        elif field_type.lower() == "task_logs":
            ret = self.thehive.get_task_logs(cur_id)
        else:
            return (
                "%s is not implemented. See https://github.com/frikky/shuffle-apps for more info."
                % field_type)

        return ret.text

    async def close_alert(self, apikey, url, organisation, alert_id):
        self.__connect_thehive(url, apikey, organisation)
        return self.thehive.mark_alert_as_read(alert_id).text

    async def reopen_alert(self, apikey, url, organisation, alert_id):
        self.__connect_thehive(url, apikey, organisation)
        return self.thehive.mark_alert_as_unread(alert_id).text

    async def create_case_from_alert(self,
                                     apikey,
                                     url,
                                     organisation,
                                     alert_id,
                                     case_template=None):
        self.__connect_thehive(url, apikey, organisation)
        response = self.thehive.promote_alert_to_case(
            alert_id=alert_id, case_template=case_template)
        return response.text

    async def merge_alert_into_case(self, apikey, url, organisation, alert_id,
                                    case_id):
        self.__connect_thehive(url, apikey, organisation)
        req = url + f"/api/alert/{alert_id}/merge/{case_id}"
        ret = requests.post(req, auth=self.thehive.auth)
        return ret.text

    # Not sure what the data should be
    async def update_field(self, apikey, url, organisation, field_type, cur_id,
                           field, data):
        # This is kinda silly but..
        if field_type.lower() == "alert":
            newdata = {}

            if data.startswith("%s"):
                ticket = self.thehive.get_alert(cur_id)
                if ticket.status_code != 200:
                    pass

                newdata[field] = "%s%s" % (ticket.json()[field], data[2:])
            else:
                newdata[field] = data

            # Bleh
            url = "%s/api/alert/%s" % (url, cur_id)
            if field == "status":
                if data == "New" or data == "Updated":
                    url = "%s/markAsUnread" % url
                elif data == "Ignored":
                    url = "%s/markAsRead" % url

                ret = requests.post(
                    url,
                    headers={
                        "Content-Type": "application/json",
                        "Authorization": "Bearer %s" % apikey,
                    },
                )
            else:
                ret = requests.patch(
                    url,
                    headers={
                        "Content-Type": "application/json",
                        "Authorization": "Bearer %s" % apikey,
                    },
                    json=newdata,
                )

            return str(ret.status_code)
        else:
            return (
                "%s is not implemented. See https://github.com/frikky/walkoff-integrations for more info."
                % field_type)

    # https://github.com/TheHive-Project/TheHiveDocs/tree/master/api/connectors/cortex
    async def delete_alert_artifact(self, apikey, url, organisation,
                                    artifact_id):
        self.__connect_thehive(url, apikey, organisation, version=4)
        return self.thehive.delete_alert_artifact(artifact_id).text

    # https://github.com/TheHive-Project/TheHiveDocs/tree/master/api/connectors/cortex
    async def run_analyzer(self, apikey, url, organisation, cortex_id,
                           analyzer_id, artifact_id):
        self.__connect_thehive(url, apikey, organisation)
        return self.thehive.run_analyzer(cortex_id, artifact_id,
                                         analyzer_id).text

    # Creates a task log in TheHive with file
    async def create_task_log(self,
                              apikey,
                              url,
                              organisation,
                              task_id,
                              message,
                              filedata={}):
        if filedata["success"] == False:
            return "No file to upload. Skipping message."

        headers = {
            "Authorization": "Bearer %s" % apikey,
        }

        files = {}
        if len(filedata["data"]) > 0:
            files = {
                "attachment": (filedata["filename"], filedata["data"]),
            }

        data = {"_json": """{"message": "%s"}""" % message}
        response = requests.post(
            "%s/api/case/task/%s/log" % (url, task_id),
            headers=headers,
            files=files,
            data=data,
        )
        return response.text

    # Creates an observable as a file in a case
    async def create_case_file_observable(self, apikey, url, organisation,
                                          case_id, tags, filedata):
        if filedata["success"] == False:
            return "No file to upload. Skipping message."

        headers = {
            "Authorization": "Bearer %s" % apikey,
        }

        if tags:
            if ", " in tags:
                tags = tags.split(", ")
            elif "," in tags:
                tags = tags.split(",")
            else:
                tags = [tags]

        files = {}
        if len(filedata["data"]) > 0:
            files = {
                "attachment": (filedata["filename"], filedata["data"]),
            }

        outerarray = {"dataType": "file", "tags": tags}
        data = {"_json": """%s""" % json.dumps(outerarray)}
        response = requests.post(
            "%s/api/case/%s/artifact" % (url, case_id),
            headers=headers,
            files=files,
            data=data,
            verify=False,
        )
        return response.text

    # Get all artifacts of a given case
    async def get_case_artifacts(
        self,
        apikey,
        url,
        organisation,
        case_id,
        dataType,
    ):
        self.__connect_thehive(url, apikey, organisation)

        query = And(Eq("dataType", dataType)) if dataType else {}

        # Call the API
        response = self.thehive.get_case_observables(
            case_id, query=query, sort=["-startDate", "+ioc"], range="all")

        # Display the result
        if response.status_code == 200:
            # Get response data
            list = response.json()

            # Display response data
            return (json.dumps(list, indent=4, sort_keys=True)
                    if list else json.dumps(
                        {
                            "status": 200,
                            "message": "No observable results"
                        },
                        indent=4,
                        sort_keys=True,
                    ))
        else:
            return f"Failure: {response.status_code}/{response.text}"

    async def close_case(
        self,
        apikey,
        url,
        organisation,
        id,
        resolution_status="",
        impact_status="",
        summary="",
    ):

        self.__connect_thehive(url, apikey, organisation)
        case = self.thehive.case(id)
        case.status = "Resolved"
        case.summary = summary
        case.resolutionStatus = resolution_status
        case.impactStatus = impact_status

        result = self.thehive.update_case(
            case,
            fields=[
                "status",
                "summary",
                "resolutionStatus",
                "impactStatus",
            ],
        )

        return json.dumps(result.json(), indent=4, sort_keys=True)

    # Update TheHive Case
    async def update_case(
        self,
        apikey,
        url,
        organisation,
        id,
        title="",
        description="",
        severity=None,
        owner="",
        flag=None,
        tlp=None,
        pap=None,
        tags="",
        status="",
        custom_fields=None,
        custom_json=None,
    ):
        self.__connect_thehive(url, apikey, organisation)

        # Get current case data and update fields if new data exists
        case = self.thehive.get_case(id).json()
        print(case)

        case_title = title if title else case["title"]
        case_description = description if description else case["description"]
        case_severity = int(severity) if severity else case["severity"]
        case_owner = owner if owner else case["owner"]
        case_flag = ((False if flag.lower() == "false" else True)
                     if flag else case["flag"])
        case_tlp = int(tlp) if tlp else case["tlp"]
        case_pap = int(pap) if pap else case["pap"]
        case_tags = tags.split(",") if tags else case["tags"]
        case_tags = tags.split(",") if tags else case["tags"]

        case_status = status if status else case["status"]
        case_customFields = case["customFields"]

        # Prepare the customfields
        customfields = CustomFieldHelper()
        if case_customFields:
            for key, value in case_customFields.items():
                if list(value)[0] == "integer":
                    customfields.add_integer(key, list(value.items())[0][1])
                elif list(value)[0] == "string":
                    customfields.add_string(key, list(value.items())[0][1])
                elif list(value)[0] == "boolean":
                    customfields.add_boolean(key, list(value.items())[0][1])
                elif list(value)[0] == "float":
                    customfields.add_float(key, list(value.items())[0][1])
                else:
                    print(
                        f'The value type "{value}" of the field {key} is not suported by the function.'
                    )

        custom_fields = json.loads(custom_fields) if custom_fields else {}
        for key, value in custom_fields.items():
            if type(value) == int:
                customfields.add_integer(key, value)
            elif type(value) == str:
                customfields.add_string(key, value)
            elif type(value) == bool:
                customfields.add_boolean(key, value)
            elif type(value) == float:
                customfields.add_float(key, value)
            else:
                print(
                    f'The value type "{value}" of the field {key} is not suported by the function.'
                )

        customfields = customfields.build()

        custom_json = json.loads(custom_json) if custom_json else {}

        # Prepare the fields to be updated
        case = Case(
            id=id,
            title=case_title,
            description=case_description,
            severity=case_severity,
            owner=case_owner,
            flag=case_flag,
            tlp=case_tlp,
            pap=case_pap,
            tags=case_tags,
            status=case_status,
            customFields=customfields,
            json=custom_json,
        )

        # resolutionStatus=case_resolutionStatus,

        result = self.thehive.update_case(
            case,
            fields=[
                "title",
                "description",
                "severity",
                "owner",
                "flag",
                "tlp",
                "pap",
                "tags",
                "customFields",
                "status",
            ],
        )

        return json.dumps(result.json(), indent=4, sort_keys=True)

    # Get TheHive Organisations
    async def get_organisations(
        self,
        apikey,
        url,
        organisation,
    ):
        headers = {
            "Authorization": f"Bearer {apikey}",
            "Content-Type": "application/json",
        }

        response = requests.get(
            f"{url}/api/organisation",
            headers=headers,
            verify=False,
        )

        return response.text

    # Create TheHive Organisation
    async def create_organisation(
        self,
        apikey,
        url,
        organisation,
        name,
        description,
    ):
        headers = {
            "Authorization": f"Bearer {apikey}",
            "Content-Type": "application/json",
        }

        data = {"name": f"{name}", "description": f"{description}"}

        response = requests.post(
            f"{url}/api/organisation",
            headers=headers,
            json=data,
            verify=False,
        )

        return response.text

    # Create User in TheHive
    async def create_user(
        self,
        apikey,
        url,
        organisation,
        login,
        name,
        profile,
    ):
        headers = {
            "Authorization": f"Bearer {apikey}",
            "Content-Type": "application/json",
        }

        data = {
            "login": f"{login}",
            "name": f"{name}",
            "profile": f"{profile}",
            "organisation": f"{organisation}",
        }

        response = requests.post(
            f"{url}/api/v1/user",
            headers=headers,
            json=data,
            verify=False,
        )

        return response.text
Example #3
0
class TheHive(AppBase):
    """
    An example of a Walkoff App.
    Inherit from the AppBase class to have Redis, logging, and console logging set up behind the scenes.
    """

    __version__ = "1.0.0"
    app_name = "thehive"

    def __init__(self, redis, logger, console_logger=None):
        """
        Each app should have this __init__ to set up Redis and logging.
        :param redis:
        :param logger:
        :param console_logger:
        """
        super().__init__(redis, logger, console_logger)

    # async def run_analyzer(self, apikey, url, title_query):
    #    self.thehive = TheHiveApi(url, apikey, cert=False)

    #    response = self.thehive.find_cases(query=String("title:'%s'" % title_query), range='all', sort=[])
    #    return response.text

    async def search_cases(self, apikey, url, title_query):
        self.thehive = TheHiveApi(url, apikey, cert=False)

        response = self.thehive.find_cases(query=ContainsString(
            "title", title_query),
                                           range="all",
                                           sort=[])
        return response.text

    async def search_query(self, apikey, url, search_for, custom_query):
        self.thehive = TheHiveApi(url, apikey, cert=False)

        try:
            query = json.loads(custom_query)
        except:
            raise IOError("Invalid JSON payload received.")

        if search_for == "alert":
            response = self.thehive.find_alerts(query=query,
                                                range="all",
                                                sort=[])
        else:
            response = self.thehive.find_cases(query=query,
                                               range="all",
                                               sort=[])

        if response.status_code == 200:
            return response.text
        else:
            raise IOError(response.text)

    async def add_observable(self, apikey, url, case_id, data, datatype, tags):
        self.thehive = TheHiveApi(url, apikey, cert=False)

        if tags:
            if ", " in tags:
                tags = tags.split(", ")
            elif "," in tags:
                tags = tags.split(",")
            else:
                tags = [tags]
        else:
            tags = []

        item = thehive4py.models.CaseObservable(
            dataType=datatype,
            data=data,
            tlp=1,
            ioc=False,
            sighted=False,
            tags=["Shuffle"],
            message="Created by shuffle",
        )

        return self.thehive.create_case_observable(case_id, item).text

    async def search_alerts(self,
                            apikey,
                            url,
                            title_query,
                            search_range="0-25"):
        self.thehive = TheHiveApi(url, apikey, cert=False)

        # Could be "all" too
        if search_range == "":
            search_range = "0-25"

        response = self.thehive.find_alerts(query=ContainsString(
            "title", title_query),
                                            range=search_range,
                                            sort=[])
        return response.text

    async def create_case(self,
                          apikey,
                          url,
                          title,
                          description="",
                          tlp=1,
                          severity=1,
                          tags=""):
        self.thehive = TheHiveApi(url, apikey, cert=False)
        if tags:
            if ", " in tags:
                tags = tags.split(", ")
            elif "," in tags:
                tags = tags.split(",")
            else:
                tags = [tags]
        else:
            tags = []

        # Wutface fix
        if not tlp:
            tlp = 1
        if not severity:
            severity = 1

        if isinstance(tlp, str):
            if not tlp.isdigit():
                return "TLP needs to be a number from 0-2, not %s" % tlp
            tlp = int(tlp)
        if isinstance(severity, str):
            if not severity.isdigit():
                return "Severity needs to be a number from 0-2, not %s" % tlp

            severity = int(severity)

        if tlp > 3 or tlp < 0:
            return "TLP needs to be a number from 0-3, not %d" % tlp
        if severity > 2 or severity < 0:
            return "Severity needs to be a number from 0-2, not %d" % tlp

        case = thehive4py.models.Case(
            title=title,
            tlp=tlp,
            severity=severity,
            tags=tags,
            description=description,
        )

        try:
            ret = self.thehive.create_case(case)
            return ret.text
        except requests.exceptions.ConnectionError as e:
            return "ConnectionError: %s" % e

    async def create_alert(
        self,
        apikey,
        url,
        type,
        source,
        sourceref,
        title,
        description="",
        tlp=1,
        severity=1,
        tags="",
    ):
        self.thehive = TheHiveApi(url, apikey, cert=False)
        if tags:
            if ", " in tags:
                tags = tags.split(", ")
            elif "," in tags:
                tags = tags.split(",")
            else:
                tags = [tags]
        else:
            tags = []

        # Wutface fix
        if not tlp:
            tlp = 1
        if not severity:
            severity = 1

        if isinstance(tlp, str):
            if not tlp.isdigit():
                return "TLP needs to be a number from 0-3, not %s" % tlp

            tlp = int(tlp)
        if isinstance(severity, str):
            if not severity.isdigit():
                return "Severity needs to be a number from 1-3, not %s" % severity

            severity = int(severity)

        if tlp > 3 or tlp < 0:
            return "TLP needs to be a number from 0-3, not %d" % tlp
        if severity > 3 or severity < 1:
            return "Severity needs to be a number from 1-3, not %d" % severity

        alert = thehive4py.models.Alert(
            title=title,
            tlp=tlp,
            severity=severity,
            tags=tags,
            description=description,
            type=type,
            source=source,
            sourceRef=sourceref,
        )

        try:
            ret = self.thehive.create_alert(alert)
            return ret.text
        except requests.exceptions.ConnectionError as e:
            return "ConnectionError: %s" % e

    async def create_alert_artifact(self,
                                    apikey,
                                    url,
                                    alert_id,
                                    dataType,
                                    data,
                                    message=None,
                                    tlp="2",
                                    ioc="False",
                                    sighted="False",
                                    ignoreSimilarity="False",
                                    tags=None):
        self.thehive = TheHiveApi(url, apikey, cert=False, version=4)

        if tlp:
            tlp = int(tlp)
        else:
            tlp = 2

        ioc = ioc.lower().strip() == "true"
        sighted = sighted.lower().strip() == "true"
        ignoreSimilarity = ignoreSimilarity.lower().strip() == "true"

        if tags:
            tags = [x.strip() for x in tags.split(",")]
        else:
            tags = []

        alert_artifact = thehive4py.models.AlertArtifact(
            dataType=dataType,
            data=data,
            message=message,
            tlp=tlp,
            ioc=ioc,
            sighted=sighted,
            ignoreSimilarity=ignoreSimilarity,
            tags=tags)

        try:
            ret = self.thehive.create_alert_artifact(alert_id, alert_artifact)
        except requests.exceptions.ConnectionError as e:
            return "ConnectionError: %s" % e
        if ret.status_code > 299:
            raise ConnectionError(ret.text)

        return ret.text

    # Gets an item based on input. E.g. field_type = Alert
    async def get_item(self, apikey, url, field_type, cur_id):
        self.thehive = TheHiveApi(url, apikey, cert=False)

        newstr = ""
        ret = ""
        if field_type.lower() == "alert":
            ret = self.thehive.get_alert(cur_id + "?similarity=1")
        elif field_type.lower() == "case":
            ret = self.thehive.get_case(cur_id)
        elif field_type.lower() == "case_observables":
            ret = self.thehive.get_case_observables(cur_id)
        elif field_type.lower() == "case_task":
            ret = self.thehive.get_case_task(cur_id)
        elif field_type.lower() == "case_tasks":
            ret = self.thehive.get_case_tasks(cur_id)
        elif field_type.lower() == "case_template":
            ret = self.thehive.get_case_tasks(cur_id)
        elif field_type.lower() == "linked_cases":
            ret = self.thehive.get_linked_cases(cur_id)
        elif field_type.lower() == "task_log":
            ret = self.thehive.get_task_log(cur_id)
        elif field_type.lower() == "task_logs":
            ret = self.thehive.get_task_logs(cur_id)
        else:
            return (
                "%s is not implemented. See https://github.com/frikky/shuffle-apps for more info."
                % field_type)

        return ret.text

    async def close_alert(self, apikey, url, alert_id):
        self.thehive = TheHiveApi(url, apikey, cert=False)
        return self.thehive.mark_alert_as_read(alert_id).text

    async def reopen_alert(self, apikey, url, alert_id):
        self.thehive = TheHiveApi(url, apikey, cert=False)
        return self.thehive.mark_alert_as_unread(alert_id).text

    async def create_case_from_alert(self,
                                     apikey,
                                     url,
                                     alert_id,
                                     case_template=None):
        self.thehive = TheHiveApi(url, apikey, cert=False)
        response = self.thehive.promote_alert_to_case(
            alert_id=alert_id, case_template=case_template)
        return response.text

    async def merge_alert_into_case(self, apikey, url, alert_id, case_id):
        self.thehive = TheHiveApi(url, apikey, cert=False)
        req = url + f"/api/alert/{alert_id}/merge/{case_id}"
        ret = requests.post(req, auth=self.thehive.auth)
        return ret.text

    # Not sure what the data should be
    async def update_field(self, apikey, url, field_type, cur_id, field, data):
        # This is kinda silly but..
        if field_type.lower() == "alert":
            newdata = {}

            if data.startswith("%s"):
                ticket = self.thehive.get_alert(cur_id)
                if ticket.status_code != 200:
                    pass

                newdata[field] = "%s%s" % (ticket.json()[field], data[2:])
            else:
                newdata[field] = data

            # Bleh
            url = "%s/api/alert/%s" % (url, cur_id)
            if field == "status":
                if data == "New" or data == "Updated":
                    url = "%s/markAsUnread" % url
                elif data == "Ignored":
                    url = "%s/markAsRead" % url

                ret = requests.post(
                    url,
                    headers={
                        "Content-Type": "application/json",
                        "Authorization": "Bearer %s" % apikey,
                    },
                )
            else:
                ret = requests.patch(
                    url,
                    headers={
                        "Content-Type": "application/json",
                        "Authorization": "Bearer %s" % apikey,
                    },
                    json=newdata,
                )

            return str(ret.status_code)
        else:
            return (
                "%s is not implemented. See https://github.com/frikky/walkoff-integrations for more info."
                % field_type)

    # https://github.com/TheHive-Project/TheHiveDocs/tree/master/api/connectors/cortex
    async def run_analyzer(self, apikey, url, cortex_id, analyzer_id,
                           artifact_id):
        self.thehive = TheHiveApi(url, apikey, cert=False)
        return self.thehive.run_analyzer(cortex_id, artifact_id,
                                         analyzer_id).text

    # Creates a task log in TheHive with file
    async def create_task_log(self,
                              apikey,
                              url,
                              task_id,
                              message,
                              filedata={}):
        if filedata["success"] == False:
            return "No file to upload. Skipping message."

        headers = {
            "Authorization": "Bearer %s" % apikey,
        }

        files = {}
        if len(filedata["data"]) > 0:
            files = {
                "attachment": (filedata["filename"], filedata["data"]),
            }

        data = {"_json": """{"message": "%s"}""" % message}
        response = requests.post(
            "%s/api/case/task/%s/log" % (url, task_id),
            headers=headers,
            files=files,
            data=data,
        )
        return response.text

    # Creates an observable as a file in a case
    async def create_case_file_observable(self, apikey, url, case_id, tags,
                                          filedata):
        if filedata["success"] == False:
            return "No file to upload. Skipping message."

        headers = {
            "Authorization": "Bearer %s" % apikey,
        }

        if tags:
            if ", " in tags:
                tags = tags.split(", ")
            elif "," in tags:
                tags = tags.split(",")
            else:
                tags = [tags]

        files = {}
        if len(filedata["data"]) > 0:
            files = {
                "attachment": (filedata["filename"], filedata["data"]),
            }

        outerarray = {"dataType": "file", "tags": tags}
        data = {"_json": """%s""" % json.dumps(outerarray)}
        response = requests.post(
            "%s/api/case/%s/artifact" % (url, case_id),
            headers=headers,
            files=files,
            data=data,
        )
        return response.text
Example #4
0
        tlp = 1
    elif magnitude >= 7 and magnitude < 9:
        sev = 3
        tlp = 2
    elif magnitude > 9:
        sev = 3
        tlp = 3
    mapper['sev'] = sev
    mapper['tlp'] = tlp
    return mapper


#Check for closed cases from theHive before resync.
get_cases = sl.get_records_by_val(case_table, 'case_id', 'status', 'Open')
for i in get_cases:
    if hive_api.get_case(i[0]).json()['status'] == 'Resolved':
        for i in sl.get_records_by_val(case_table, 'id', 'case_id', i[0]):
            if qr.get_offense_details(i[0])['status'] == 'OPEN':
                if qr.close_offense(i[0], offense_closing_text) == True:
                    sl.update_record(case_table, 'status', 'Closed', 'id',
                                     i[0])
                    sl.update_record(enrichment_table, 'status', 'Closed',
                                     'id', i[0])

#Process new offenses
qr_resp = qr.get_offenses(open=True, max_items=max_event_count)
new_offense_list = []
if qr_resp is not None:
    for i in qr_resp:
        # Check offense_start_from ID-number and process only bigger
        if i['id'] > offense_start_from:
print('Create Case')
print('-----------------------------')
id = None
response = api.create_case(case)
if response.status_code == 201:
    print(json.dumps(response.json(), indent=4, sort_keys=True))
    print('')
    id = response.json()['id']
else:
    print('ko: {}/{}'.format(response.status_code, response.text))
    sys.exit(0)

# Get all the details of the created case
print('Get created case {}'.format(id))
print('-----------------------------')
response = api.get_case(id)
if response.status_code == requests.codes.ok:
    print(json.dumps(response.json(), indent=4, sort_keys=True))
    print('')
else:
    print('ko: {}/{}'.format(response.status_code, response.text))

# Add a new task to the created case
print('Add a task {}'.format(id))
print('-----------------------------')
response = api.create_case_task(
    id,
    CaseTask(title='Yet Another Task',
             status='InProgress',
             owner='nabil',
             flag=True,
Example #6
0
class TheHive(AppBase):
    """
    An example of a Walkoff App.
    Inherit from the AppBase class to have Redis, logging, and console logging set up behind the scenes.
    """
    __version__ = "1.0.0"
    app_name = "thehive"

    def __init__(self, redis, logger, console_logger=None):
        """
        Each app should have this __init__ to set up Redis and logging.
        :param redis:
        :param logger:
        :param console_logger:
        """
        super().__init__(redis, logger, console_logger)

    #async def run_analyzer(self, apikey, url, title_query):
    #    self.thehive = TheHiveApi(url, apikey)

    #    response = self.thehive.find_cases(query=String("title:'%s'" % title_query), range='all', sort=[])
    #    return response.text


    async def search_cases(self, apikey, url, title_query):
        self.thehive = TheHiveApi(url, apikey)

        response = self.thehive.find_cases(query=String("title:'%s'" % title_query), range='all', sort=[])
        return response.text

    async def add_observable(self, apikey, url, case_id, data, datatype, tags):
        self.thehive = TheHiveApi(url, apikey)

        if tags:
            if ", " in tags:
                tags = tags.split(", ")
            elif "," in tags:
                tags = tags.split(",")
            else:
                tags = []
        else:
            tags = []

        item = thehive4py.models.CaseObservable(
            dataType=datatype,
            data=data,
            tlp=1,
            ioc=False,
            sighted=False,
            tags=["Shuffle"],
            message="Created by shuffle",
        )

        return self.thehive.create_case_observable(case_id, item).text

    async def search_alerts(self, apikey, url, title_query, search_range="0-25"):
        self.thehive = TheHiveApi(url, apikey)

        # Could be "all" too
        if search_range == "":
            search_range = "0-25"

        response = self.thehive.find_alerts(query=String("title:'%s'" % title_query), range=search_range, sort=[])
        return response.text

    async def create_case(self, apikey, url, title, description="", tlp=1, severity=1, tags=""):
        self.thehive = TheHiveApi(url, apikey)
        if tags:
            if ", " in tags:
                tags = tags.split(", ")
            elif "," in tags:
                tags = tags.split(",")
            else:
                tags = []
        else:
            tags = []

        # Wutface fix
        if not tlp:
            tlp = 1
        if not severity:
            severity = 1

        if isinstance(tlp, str):
            if not tlp.isdigit():
                return "TLP needs to be a number from 0-2, not %s" % tlp
            tlp = int(tlp)
        if isinstance(severity, str):
            if not severity.isdigit():
                return "Severity needs to be a number from 0-2, not %s" % tlp

            severity = int(severity)

        if tlp > 3 or tlp < 0:
            return "TLP needs to be a number from 0-3, not %d" % tlp
        if severity > 2 or severity < 0:
            return "Severity needs to be a number from 0-2, not %d" % tlp

        case = thehive4py.models.Case(
            title=title,
            tlp=tlp,
            severity=severity,
            tags=tags,
            description=description,
        )

        try:
            ret = self.thehive.create_case(case)
            return ret.text
        except requests.exceptions.ConnectionError as e:
            return "ConnectionError: %s" % e

    async def create_alert(self, apikey, url, type, source, sourceref, title, description="", tlp=1, severity=1, tags=""):
        self.thehive = TheHiveApi(url, apikey)
        if tags:
            if ", " in tags:
                tags = tags.split(", ")
            elif "," in tags:
                tags = tags.split(",")
            else:
                tags = []
        else:
            tags = []

        # Wutface fix
        if not tlp:
            tlp = 1
        if not severity:
            severity = 1

        if isinstance(tlp, str):
            if not tlp.isdigit():
                return "TLP needs to be a number from 0-2, not %s" % tlp
            tlp = int(tlp)
        if isinstance(severity, str):
            if not severity.isdigit():
                return "Severity needs to be a number from 0-2, not %s" % tlp

            severity = int(severity)

        if tlp > 2 or tlp < 0:
            return "TLP needs to be a number from 0-2, not %d" % tlp
        if severity > 2 or severity < 0:
            return "Severity needs to be a number from 0-2, not %d" % tlp

        alert = thehive4py.models.Alert(
            title=title,
            tlp=tlp,
            severity=severity,
            tags=tags,
            description=description,
            type=type,
            source=source,
            sourceRef=sourceref,
        )

        try:
            ret = self.thehive.create_alert(alert)
            return ret.text
        except requests.exceptions.ConnectionError as e:
            return "ConnectionError: %s" % e

    # Gets an item based on input. E.g. field_type = Alert
    async def get_item(self, apikey, url, field_type, cur_id): 
        self.thehive = TheHiveApi(url, apikey)

        newstr = ""
        ret = ""
        if field_type.lower() == "alert":
            ret = self.thehive.get_alert(cur_id + "?similarity=1") 
        elif field_type.lower() == "case":
            ret = self.thehive.get_case(cur_id)
        elif field_type.lower() == "case_observables":
            ret = self.thehive.get_case_observables(cur_id)
        elif field_type.lower() == "case_task":
            ret = self.thehive.get_case_task(cur_id)
        elif field_type.lower() == "case_tasks":
            ret = self.thehive.get_case_tasks(cur_id)
        elif field_type.lower() == "case_template":
            ret = self.thehive.get_case_tasks(cur_id)
        elif field_type.lower() == "linked_cases":
            ret = self.thehive.get_linked_cases(cur_id)
        elif field_type.lower() == "task_log":
            ret = self.thehive.get_task_log(cur_id)
        elif field_type.lower() == "task_logs":
            ret = self.thehive.get_task_logs(cur_id)
        else:
            return "%s is not implemented. See https://github.com/frikky/shuffle-apps for more info." % field_type

        return ret.text

    async def close_alert(self, apikey, url, alert_id):
        self.thehive = TheHiveApi(url, apikey)
        return self.thehive.mark_alert_as_read(alert_id).text

    async def reopen_alert(self, apikey, url, alert_id):
        self.thehive = TheHiveApi(url, apikey)
        return self.thehive.mark_alert_as_unread(alert_id).text

    async def create_case_from_alert(self, apikey, url, alert_id, case_template=None):
        self.thehive = TheHiveApi(url, apikey)
        response = self.thehive.promote_alert_to_case(alert_id=alert_id, case_template=case_template)
        return response.text

    async def merge_alert_into_case(self, apikey, url, alert_id, case_id):
        self.thehive = TheHiveApi(url, apikey)
        req = url + f"/api/alert/{alert_id}/merge/{case_id}"
        ret = requests.post(req, auth=self.thehive.auth)
        return ret.text

    # Not sure what the data should be
    async def update_field(self, apikey, url, field_type, cur_id, field, data):
        # This is kinda silly but..
        if field_type.lower() == "alert":
            newdata = {}

            if data.startswith("%s"): 
                ticket = self.thehive.get_alert(cur_id)
                if ticket.status_code != 200:
                    pass 
            
                newdata[field] = "%s%s" % (ticket.json()[field], data[2:])
            else:
                newdata[field] = data

            # Bleh
            url = "%s/api/alert/%s" % (url, cur_id)
            if field == "status":
                if data == "New" or data == "Updated":
                    url = "%s/markAsUnread" % url
                elif data == "Ignored": 
                    url = "%s/markAsRead" % url

                ret = requests.post(
                    url,
                    headers={
                        'Content-Type': 'application/json',
                        'Authorization': 'Bearer %s' % apikey
                    }
                )
            else:
                ret = requests.patch(
                    url,
                    headers={
                        'Content-Type': 'application/json',
                        'Authorization': 'Bearer %s' % apikey
                    }, 
                    json=newdata,
                )

            return str(ret.status_code)
        else:
            return "%s is not implemented. See https://github.com/frikky/walkoff-integrations for more info." % field_type

    # https://github.com/TheHive-Project/TheHiveDocs/tree/master/api/connectors/cortex
    async def run_analyzer(self, apikey, url, cortex_id, analyzer_id, artifact_id):
        self.thehive = TheHiveApi(url, apikey)
        return self.thehive.run_analyzer(cortex_id, artifact_id, analyzer_id).text
Example #7
0
class TheHive(AppBase):
    """
    An example of a Walkoff App.
    Inherit from the AppBase class to have Redis, logging, and console logging set up behind the scenes.
    """
    __version__ = "0.0.3"
    app_name = "thehive"

    def __init__(self, redis, logger, console_logger=None):
        """
        Each app should have this __init__ to set up Redis and logging.
        :param redis:
        :param logger:
        :param console_logger:
        """
        self.thehive = TheHiveApi(secret.url, secret.apikey)
        super().__init__(redis, logger, console_logger)

    async def show_secret(self):
        return "url=%s, apikey=%s" % (secret.url, secret.apikey)

    async def get_case_count(self, title_query):
        response = self.thehive.find_cases(query=String("title:'%s'" %
                                                        title_query),
                                           range='all',
                                           sort=[])
        casecnt = len(response.json())
        return casecnt

    async def string_contains(self, field, string_check):
        if string_check in field.lower():
            return True

        return False

    async def string_startswith(self, field, string_check):
        if field.lower().startswith(string_check):
            return True

        return False

    # Gets an item based on input. E.g. field_type = Alert
    async def get_item(self, field_type, cur_id):
        newstr = ""
        ret = ""
        if field_type.lower() == "alert":
            ret = self.thehive.get_alert(cur_id)
        elif field_type.lower() == "case":
            ret = self.thehive.get_case(cur_id)
        elif field_type.lower() == "case_observables":
            ret = self.thehive.get_case_observables(cur_id)
        elif field_type.lower() == "case_task":
            ret = self.thehive.get_case_task(cur_id)
        elif field_type.lower() == "case_tasks":
            ret = self.thehive.get_case_tasks(cur_id)
        elif field_type.lower() == "case_template":
            ret = self.thehive.get_case_tasks(cur_id)
        elif field_type.lower() == "linked_cases":
            ret = self.thehive.get_linked_cases(cur_id)
        elif field_type.lower() == "task_log":
            ret = self.thehive.get_task_log(cur_id)
        elif field_type.lower() == "task_logs":
            ret = self.thehive.get_task_logs(cur_id)
        else:
            return "%s is not implemented. See https://github.com/frikky/walkoff-integrations for more info." % field_type

        newstr = str(ret.json()).replace("\'", "\"")
        newstr = newstr.replace("True", "true")
        newstr = newstr.replace("False", "false")
        return newstr

    # Not sure what the data should be
    async def update_field_string(self, field_type, cur_id, field, data):
        # This is kinda silly but..
        if field_type.lower() == "alert":
            newdata = {}

            if data.startswith("%s"):
                ticket = self.thehive.get_alert(cur_id)
                if ticket.status_code != 200:
                    pass

                newdata[field] = "%s%s" % (ticket.json()[field], data[2:])
            else:
                newdata[field] = data

            # Bleh
            url = "%s/api/alert/%s" % (secret.url, cur_id)
            if field == "status":
                if data == "New" or data == "Updated":
                    url = "%s/markAsUnread" % url
                elif data == "Ignored":
                    url = "%s/markAsRead" % url

                ret = requests.post(url,
                                    headers={
                                        'Content-Type':
                                        'application/json',
                                        'Authorization':
                                        'Bearer %s' % secret.apikey
                                    })
            else:
                ret = requests.patch(
                    url,
                    headers={
                        'Content-Type': 'application/json',
                        'Authorization': 'Bearer %s' % secret.apikey
                    },
                    json=newdata,
                )

            return ret.status_code
        else:
            return 0
Example #8
0
class Reporter(Responder):
    """
    This Reporter class automates the effort of producing a case-report and optionally all of its associated data,
    such as observables and tasks.
    The primary function of this algorithm, is to take a JSON-structure gathered from TheHive's API, and filter it
    as per the provided filter-parameters given on activation.

    The algorithm assumes, that the dataset that's worked on, is either of any primitive type like int and string or
    a specific data structure list or dict, hence the dict- and list-builders.

    In short, the algorithm generates a new filtered tree-structure in JSON-format, which can be N-wide and N-deep.
    """
    def __init__(self):
        Responder.__init__(self)
        self.case_data_filter = [
            "endDate", "startDate", "title", "createdAt", "caseId", "pap",
            "tlp", "severity", "owner", "createdBy", "updatedBy", "summary",
            "tags", "resolutionStatus", "impactStatus", "status",
            "customFields"
        ]
        self.case_observables_filter = [
            "data", "dataType", "sighted", "tags", "createdAt", "createdBy",
            "pap", "tlp", "ioc", "startDate", "status"
        ]
        self.case_tasks_filter = [
            "caseTasks", "updatedBy", "createdAt", "flag", "description",
            "title", "createdBy", "updatedAt", "order", "status", "group"
        ]

        self.api_key = self.get_param('config.api_key', None,
                                      'Missing API-key')
        self.https_address = self.get_param('config.https_address',
                                            'localhost')
        self.https_port = self.get_param('config.https_port', 9000,
                                         'Missing thehive port')
        self.smtp_host = self.get_param('config.smtp_host', 'localhost')
        self.smtp_port = self.get_param('config.smtp_port', '25')
        self.mail_from = self.get_param('config.from', None,
                                        'Missing sender email address')
        self.api = TheHiveApi(
            f"https://{self.https_address}:{self.https_port}", self.api_key)

    def get_case_data(self, case_id):
        """
        Contacts the TheHive-API, and gets the case-data,. (Maybe not useful once the program is integrated with Cortex,
        as the JSON-object given upon a call of the responder, is matching the fetched data from this function.)

        Args:
            case_id (str): id of the case to be gathered from

        Returns:
            str: returns a string in JSON-format
        """
        case_data = self.api.get_case(case_id=case_id)

        if case_data.status_code == 200:
            json_data = {"caseData": case_data.json()}

            return json_data
        else:
            print(f'ko: {case_data.status_code}/{case_data.text}')
            sys.exit(0)

    def get_case_observables(self, case_id: str):
        """
        Contacts the TheHive-API, and gets the observables from given case_id

        Args:
            case_id (str): id of the case to be gathered from

        Returns:
            str: returns a string in JSON-format
        """
        case_data = self.api.get_case_observables(case_id=case_id)

        if case_data.status_code == 200:
            json_data = {"caseObservables": case_data.json()}

            return json_data
        else:
            print(f'ko: {case_data.status_code}/{case_data.text}')
            sys.exit(0)

    def get_case_tasks(self, case_id: str):
        """
        Contacts the TheHive-API, and gets the associated tasks from the given case-id.

        Args:
            case_id (str): id of the case to be gathered from

        Returns:
            str: returns a string in JSON-format
        """
        case_data = self.api.get_case_tasks(case_id=case_id)

        if case_data.status_code == 200:
            json_data = {"caseTasks": case_data.json()}

            return json_data
        else:
            print(f'ko: {case_data.status_code}/{case_data.text}')
            sys.exit(0)

    def dict_builder(self,
                     data: dict,
                     filter: list,
                     result_obj: object = None,
                     path: list = None):
        """
        1 of 2 algorithms to recursively generate a JSON-structure, which is filtered by the provided filters given upon
        activation of this module in Cortex.

        Args:
            data (dict): new node of values, which can contain either a new node(list or dict) or a leaf (primitive type)
            filter: (list): target list of values which the new JSON-should contain
            result_obj: the new JSON-object, which holds the current progress of the algorithm
            path: used to keep track of the depth of the new JSON-object, to maintain the original object-structure

        Examples:
            FILTER_CANDIDATES = ["caseData", "title", "description"] - This takes all the data from caseData dict,
            and will not go deeper into it to search for "title" and "description", since they are a subset of "caseData".

        Returns:
            str: the JSON-string and the underlying levels recursively built
        """

        if path is None:
            path = []
        if result_obj is None:
            result_obj = {}

        for key, value in data.items():
            if filter.__contains__(key) or path.__contains__(filter):
                extended_path = self.extend_path(key, path)
                self.add_primitive_value(result_obj, '<br>', extended_path,
                                         value, 50)
                path.remove(key)

            elif isinstance(value, dict) or isinstance(value, list):
                extended_path = self.extend_path(key, path)
                if isinstance(value, dict):
                    exec(f'result_obj{extended_path} = {{}}')
                    self.dict_builder(value, filter, result_obj, path)

                elif isinstance(value, list):
                    exec(f'result_obj{extended_path} = []')
                    self.list_builder(value, filter, result_obj, path)

                self.check_for_empty_object(key, extended_path, result_obj,
                                            path)

        return result_obj

    def list_builder(self,
                     data: list,
                     filter: list,
                     result_obj: object = None,
                     path: list = None):
        """
        1 of 2 algorithms to recursively generate a JSON-structure, which is filtered by the provided filters given upon
        activation of this module in Cortex.

        Args:
            data (dict): new node of values, which can contain either a new node(list or dict) or a leaf (primitive type)
            filter: (list): target list of values which the new JSON-should contain
            result_obj: the new JSON-object, which holds the current progress of the algorithm
            path: used to keep track of the depth of the new JSON-object, to maintain the original object-structure

        Examples:
            FILTER_CANDIDATES = ["caseData", "title", "description"] - This takes all the data from caseData dict,
            and will not go deeper into it to search for "title" and "description", since they are a subset of "caseData".

        Returns:
            str: the JSON-string and the underlying levels built recursively.
        """
        if path is None:
            path = []
        if result_obj is None:
            result_obj = {}

        for value in data:
            if filter.__contains__(value) or path.__contains__(filter):
                key_chain = self.build_path(path)
                self.add_primitive_value(result_obj, '<br>', key_chain, value,
                                         50)

            else:
                if isinstance(value, dict):
                    exec(f'result_obj{self.build_path(path)}.append({{}})')
                elif isinstance(value, list):
                    exec(f'result_obj{self.build_path(path)}.append([])')

                base_list = f'result_obj{self.build_path(path)}'
                base_index = eval(f'len({base_list}) - 1')
                extended_path = self.extend_path(base_index, path)

                if isinstance(value, dict):
                    self.dict_builder(value, filter, result_obj, path)
                elif isinstance(value, list):
                    self.list_builder(value, filter, result_obj, path)

                self.check_for_empty_object(base_index, extended_path,
                                            result_obj, path)

        return result_obj

    def extend_path(self, key, path: list):
        """
        adds another level to the tree-like JSON-object
        Args:
            key: name of the new node to be added.
            path: previous nodes, to enable chained index-operators.

        Returns:
            list: expanded list, representing the addition of an additional level.
        """
        path.append(key)
        return self.build_path(path)

    def add_primitive_value(self,
                            result_obj,
                            string_separator: str,
                            extended_path: str,
                            value,
                            string_width=64):
        """
        Adds a new leaf-type value to the JSON-tree structure.
        Checks for long strings, and adds separator to them.

        Args:
            string_width:
            string_separator:
            extended_path:
            value:

        """
        if len(str(value)) > string_width and isinstance(value, str):
            formatted_value = self.insert(string_separator, value,
                                          string_width)
            exec(f"result_obj{extended_path} = \'{formatted_value}\'")
        else:
            exec(f"result_obj{extended_path} = value")

    @staticmethod
    def build_path(path: list):
        """
        Builds a chain of index operators, based on the elements in the provided path list.
        Args:
            path: list of index values.

        Returns:
            str: stringified chain of index operators
        """

        eval_string = ""
        for element in path:
            if isinstance(element, int):
                eval_string += f"[{element}]"
            else:
                eval_string += f"[\'{element}\']"

        return eval_string

    @staticmethod
    def insert(separator, string, line_width=64):
        """
             Helpermethod used to format strings of text.

             Args:
                 separator: operator to insert into text.
                 string: the text to add the elements to.
                 line_width: insert after how many characters.

             Returns:
                 str: formatted strings with the added elements in every interval.
             """
        words = iter(string.split())
        lines = []
        current = next(words)

        for word in words:
            if len(current) + 1 + len(word) > line_width:
                lines.append(current)
                current = word
            else:
                current += " " + word

        lines.append(current)
        result = separator.join(lines)
        return result

    @staticmethod
    def check_for_empty_object(key, key_chain: list, result_obj: object,
                               path: list):
        """
        Helpermethod for cleaning up empty objects in current level.

        Args:
            key: current tail element of the kay-chain to be removed.
            key_chain: generated chain of index brackets.
            result_obj: current build of JSON-object.
            path: current level in tree structure.
        """

        if len(eval(f'result_obj{key_chain}')) == 0:
            exec(f'del result_obj{key_chain}')
        path.remove(key)

    def send_mail(self, report_name, report_body):
        """
        Sends a mail with the generated HTML-page, which acts as a report over the case and the observables and tasks.
        Args:
            report_body:
            report_name:

        """

        mail_to = None
        if self.data_type == 'thehive:case':
            # Search recipient address in tags
            tags = self.get_param('data.tags', None,
                                  'recipient address not found in tags')
            mail_tags = [t[5:] for t in tags if t.startswith('mail:')]
            if mail_tags:
                mail_to = mail_tags.pop()
            else:
                self.error('recipient address not found in observables')
        elif self.data_type == 'thehive:alert':
            # Search recipient address in artifacts
            artifacts = self.get_param(
                'data.artifacts', None,
                'recipient address not found in observables')
            mail_artifacts = [
                a['data'] for a in artifacts
                if a.get('dataType') == 'mail' and 'data' in a
            ]
            if mail_artifacts:
                mail_to = mail_artifacts.pop()
            else:
                self.error('recipient address not found in observables')
        else:
            self.error('Invalid dataType')

        msg = EmailMessage()
        msg['Subject'] = f'Conscia Incident and Response - Case# {self.get_param("data.caseId", None, "Missing case")}'
        msg['From'] = self.mail_from
        msg['To'] = mail_to
        msg.set_content('Case Report')

        msg.add_attachment(report_body,
                           subtype='html',
                           filename=f'{report_name}.html')

        with smtplib.SMTP_SSL(self.smtp_host, self.smtp_port) as smtp:
            smtp.send_message(msg)

        self.report({'message': 'message sent'})

    def operations(self, raw):
        return [self.build_operation('AddTagToCase', tag='report sent')]

    def run(self):
        Responder.run(self)

        case_id = self.get_param("data._id")
        filtered = self.dict_builder(self.get_case_data(case_id),
                                     self.case_data_filter)
        filtered.update(
            self.dict_builder(self.get_case_observables(case_id),
                              self.case_observables_filter))
        filtered.update(
            self.dict_builder(self.get_case_tasks(case_id),
                              self.case_tasks_filter))

        with open("templates/report_template_plus_jinja.html", "r") as file:
            html_template = file.read()

        template = Template(html_template)
        html_report = template.render(data=filtered["caseData"],
                                      observables=filtered["caseObservables"],
                                      tasks=filtered["caseTasks"])

        self.send_mail("test_case", html_report)