Пример #1
0
    async def update_case_observable(self, url, api_key, case_id, obs_id, description=None, tlp=0,
                                     is_ioc=False, is_sighted=False, tags=None, tags_mode=None):
        self.logger.info(f'Updating observable {obs_id} in case {case_id} in TheHive...')

        if not url.startswith("http"):
            url = f"http://{url}"

        api = TheHiveApi(url, api_key)
        obs_list = api.get_case_observables(case_id).json()
        obs_json = [obs for obs in obs_list if obs["id"] == obs_id][0]
        obs = CaseObservable(**obs_json)
        obs.id = obs_id

        if description:
            obs.description = description
        if tlp:
            obs.tlp = tlp
        if is_ioc is not None:
            obs.ioc = is_ioc
        if is_sighted is not None:
            obs.sighted = is_sighted
        if tags is not None:
            if tags_mode == "append":
                tags = obs.tags + tags
            obs.tags = tags

        r = api.update_case_observables(obs)

        if r.status_code == 200:
            return r.json()
        else:
            raise IOError(r.text)
Пример #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)

    # 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, version=3):
        if organisation:
            self.thehive = TheHiveApi(url,
                                      apikey,
                                      cert=False,
                                      organisation=organisation,
                                      version=version)
        else:
            self.thehive = TheHiveApi(url, apikey, cert=False, version=version)

    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

    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)

    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

    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

    def create_case(
        self,
        apikey,
        url,
        organisation,
        template,
        title,
        description="",
        tlp=None,
        pap=None,
        severity=None,
        flag=None,
        tags="",
        custom_fields=None,
        custom_json=None,
    ):
        self.__connect_thehive(url, apikey, organisation)

        flag = False if flag.lower() == "false" else True
        pap = int(pap) if pap else 2
        tlp = int(tlp) if tlp else 2
        severity = int(severity) if severity else 2
        tags = tags.split(",") if tags else []

        if tlp > 3 or tlp < 0:
            return f"TLP needs to be a number from 0-3, not {tlp}"
        if severity > 4 or severity < 1:
            return f"Severity needs to be a number from 1-4, not {severity}"

        Casetemplate = template if template else None

        # Prepare the customfields
        customfields = CustomFieldHelper()
        try:
            custom_fields = json.loads(custom_fields) if custom_fields else {}
        except json.decoder.JSONDecodeError:
            return "Custom fields need to be valid json"
        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.'
                )

        # Fields in JSON
        customfields = customfields.build()
        custom_json = json.loads(custom_json) if custom_json else {}

        case = thehive4py.models.Case(
            title=title,
            tlp=tlp,
            pap=pap,
            severity=severity,
            flag=flag,
            tags=tags,
            description=description,
            template=Casetemplate,
            customFields=customfields,
            json=custom_json,
        )

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

    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

    def add_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
    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

    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

    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

    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

    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
    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)

        elif field_type.lower() == 'case':
            return 'Use update_case action for updating a case.'
        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
    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
    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
    def add_task_log(
        self,
        apikey,
        url,
        organisation,
        task_id,
        message,
        filedata={},
        mime_type=None,
    ):
        try:
            if filedata["success"] == False:
                return "No file to upload. Skipping message."
        except Exception as e:
            print(f"[WARNING] Error in filedata handler for {filedata}: {e}")

        headers = {
            "Authorization": f"Bearer {apikey}",
        }

        organisation = organisation.strip()
        if organisation:
            headers["X-Organisation"] = organisation

        files = {}
        try:
            if len(filedata["data"]) > 0:
                files = {
                    "attachment":
                    (filedata["filename"], filedata["data"], mime_type),
                }
        except Exception as e:
            print(f"[WARNING] Error in file handler for {filedata} (2): {e}")

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

    # Creates an artifact as a file in a case
    def create_case_file_artifact(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

    # Create an observable as a file for an alert
    def create_alert_file_observable(self, apikey, url, organisation, alert_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/alert/%s/artifact" % (url, alert_id),
            headers=headers,
            files=files,
            data=data,
            verify=False,
        )
        return response.text

    # Get all artifacts of a given case
    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}"

    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
    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_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.'
                    )

        try:
            custom_fields = json.loads(custom_fields) if custom_fields else {}
        except json.decoder.JSONDecodeError:
            return "Custom fields need to be valid json"
        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
    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
    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
    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

    # Update TheHive case Artifact
    def update_case_artifact(
        self,
        apikey,
        url,
        organisation,
        id,
        description=None,
        tlp=None,
        ioc=None,
        sighted=None,
        tags=None,
        custom_json=None,
    ):
        self.__connect_thehive(url, apikey, organisation)
        # Get Artifact Data
        artifact = self.thehive.get_case_observable(id).json()

        # Prepare fields to be updated
        ## Message (description):
        artifact_message = (
            (artifact["message"] + " " +
             description[1:] if "*" == description[0] else description)
            if description else artifact["message"])

        ## TLP, PAP, IOC, Sighted
        artifact_tlp = int(tlp) if tlp else artifact["tlp"]
        artifact_ioc = ((False if ioc.lower() == "false" else True)
                        if ioc else artifact["ioc"])
        artifact_sighted = ((False if sighted.lower() == "false" else True)
                            if sighted else artifact["sighted"])

        ## Tags:
        if tags:
            if "*" == tags[0]:
                artifact_tags = tags[1:].split(",")
                artifact_tags.extend(artifact["tags"])
            else:
                artifact_tags = tags.split(",")
        else:
            artifact_tags = artifact["tags"]

        ## Custom Json:
        custom_json = json.loads(custom_json) if custom_json else {}

        artifact = CaseObservable(
            id=id,
            message=artifact_message,
            tlp=artifact_tlp,
            ioc=artifact_ioc,
            sighted=artifact_sighted,
            tags=artifact_tags,
            json=custom_json,
        )

        response = self.thehive.update_case_observables(
            artifact, fields=["message", "tlp", "ioc", "sighted", "tags"])

        return response.text

    # Create TheHive case Task
    def create_task(
        self,
        apikey,
        url,
        organisation,
        case_id,
        title,
        description=None,
        status=None,
        flag=None,
        group=None,
        custom_json=None,
    ):
        self.__connect_thehive(url, apikey, organisation)
        # Prepare flag field
        flag = False if flag.lower() == "false" else True
        start_date = (round(time.time() *
                            1000) if status.lower() == "inprogress" else None)

        case_task = CaseTask(
            title=title,
            description=description,
            status=status,
            startDate=start_date,
            flag=flag,
            group=group,
            json=custom_json,
        )

        response = self.thehive.create_case_task(case_id, case_task)

        return response.text

    # Close TheHive case Task
    def update_task(self, apikey, url, organisation, task_id, status):
        if status == "Completed":

            # Add EndDate Time before close
            headers = {
                "Authorization": f"Bearer {apikey}",
            }
            if organisation:
                headers["X-Organisation"] = organisation

            data = {"endDate": round(time.time() * 1000)}
            requests.patch(
                f"{url}/api/case/task/{task_id}",
                headers=headers,
                data=data,
            )
            task = CaseTask(
                id=task_id,
                status="Completed",
            )
        else:
            task = CaseTask(
                id=task_id,
                status=status,
            )

        response = self.thehive.update_case_task(task, fields=["status"])

        return response.text
Пример #3
0
class Gmail(Responder):
    def __init__(self):
        Responder.__init__(self)
        self.service = self.get_param("config.service", None,
                                      "Service service missing")
        self.__scopes = [
            "https://mail.google.com/",
            "https://www.googleapis.com/auth/gmail.settings.basic",
        ]
        self.__thehive_url = self.get_param("config.thehive_url", None,
                                            "The Hive URL missing")
        self.__thehive_api_key = self.get_param("config.thehive_api_key", None,
                                                "The Hive API key missing")
        self.__gmail_domain = self.get_param("config.gmail_domain", None,
                                             "The Hive API key missing")
        self.__gmail_service_account = {
            "type":
            "service_account",
            "project_id":
            self.get_param("config.gmail_project_id", None,
                           "Project ID missing"),
            "private_key_id":
            self.get_param("config.gmail_private_key_id", None,
                           "Private Key ID missing"),
            "private_key":
            self.get_param("config.gmail_private_key", None,
                           "Private Key (PEM format) missing").replace(
                               "\\n", "\n"),
            "client_email":
            self.get_param("config.gmail_client_email", None,
                           "Client email missing"),
            "client_id":
            self.get_param("config.gmail_client_id", None,
                           "Client id missing"),
            "auth_uri":
            "https://accounts.google.com/o/oauth2/auth",
            "token_uri":
            "https://oauth2.googleapis.com/token",
            "auth_provider_x509_cert_url":
            "https://www.googleapis.com/oauth2/v1/certs",
            "client_x509_cert_url":
            "https://www.googleapis.com/robot/v1/metadata/x509/{}".format(
                quote(
                    self.get_param("config.gmail_client_email", None,
                                   "Client email missing")))
        }

        self.__gmail_service = None
        self.__hive_service = None

    def __not_found(self):
        self.error("service named {} not found.".format(self.service))

    def __get_gmail_subjects(self, caseId, query):
        """
        Get all email addresses of a case for the correspondig gsuite domain

        Returns: Array of Observable objects on success
        """
        response = self.__hive_service.get_case_observables(caseId,
                                                            query=query)
        if response.status_code == 200:
            return response.json()
        else:
            self.error("Failed to get valid response for query: {}".format(
                response.status_code, response.text))

    def __get_filter_tag(self, tags):
        """
        Get the correct tag for a dataType in a list of tags

        Returns: tag string on success else None
        """
        for tag in tags:
            if "gmail_filter:{}".format(self.get_param("data.data")) in tag:
                return tag
        self.error(
            "No valid filter tag found on observable. Tags: {}".format(tags))

    def hive_check_permissions(self, user):
        """
        Checks if the `user` does provide read,write permissions to the hive
        
        Returns: True if read,write permissions are available, otherwise False
        """
        roles = user["roles"]
        if "read" in roles and "write" in roles:
            return True
        else:
            return False

    def hive_auth(self, url, api_key):
        self.__hive_service = TheHiveApi(url, api_key)
        try:
            self.__hive_service.health()
        except TheHiveException as e:
            self.error(
                "Responder needs TheHive connection but failed: {}".format(e))

        user_object = self.__hive_service.get_current_user().json()
        if not self.hive_check_permissions(user_object):
            self.error(
                "API key of `{}` is missing `read` or `write` role. Users current roles: {}"
                .format(user_object["name"], user_object["roles"]))

    def gmail_impersonate(self, subject):
        """Peforms OAuth2 auth for a given service account, scope and a delegated subject

        Args:
            subject (str): email adress of the user, whos data shall be accessed (delegation)

        Returns:
            google.auth.service_account.Credentials if valid otherwise None
        """
        credentials = service_account.Credentials.from_service_account_info(
            info=self.__gmail_service_account,
            scopes=self.__scopes,
            subject=subject)

        if credentials.has_scopes(self.__scopes):
            return build("gmail", "v1", credentials=credentials)
        else:
            self.error(
                "Gmail service account creation failed. Aborting responder")

    def trash_message(self, case_id, query):
        """Moves specified message into trash. this emails can be recovered if false-positive
        """
        gmail_observables = self.__get_gmail_subjects(
            case_id,
            And(Eq("dataType", "mail"), EndsWith("data", self.__gmail_domain)))
        for observable in gmail_observables:
            resource = self.gmail_impersonate(observable["data"])
            try:
                response = resource.users().messages().list(
                    userId=observable["data"], q=query).execute()
                for message in response.get("messages", []):
                    resource.users().messages().delete(
                        userId=observable["data"], id=message["id"]).execute()
                    observable["tags"].append("gmail_delete:{}".format(
                        message["id"]))
            except GoogleAuthError as e:
                self.error("Gmail oauth failed: {}".format(e))
            except HttpError as e:
                self.error("Gmail api failed: {}".format(e))

        for observable in gmail_observables:
            self.__hive_service.update_case_observables(
                CaseObservable(**observable), fields=["tags"])
        self.report({'message': "Deleted message"})

    def block_messages(self, case_id, query):
        """Automatically labels matching emails according to query argument.
        gmail search syntax can be used in query. https://support.google.com/mail/answer/7190?hl=en
        """
        new_filter = {
            "criteria": {
                "query": query
            },
            "action":
            {  # based on https://developers.google.com/gmail/api/guides/labels
                "addLabelIds": ["TRASH"],
                "removeLabelIds": ["INBOX"]
            }
        }

        gmail_observables = self.__get_gmail_subjects(
            case_id,
            And(Eq("dataType", "mail"), EndsWith("data", self.__gmail_domain)))
        for observable in gmail_observables:
            resource = self.gmail_impersonate(observable["data"])
            try:
                gmail_filter = resource.users().settings().filters().create(
                    userId=observable["data"], body=new_filter).execute()
            except GoogleAuthError as e:
                self.error("Gmail oauth failed: {}".format(e))
            except HttpError as e:
                self.error("Gmail api failed: {}".format(e))
            observable["tags"].append("gmail_filter:{}:{}".format(
                self.get_param("data.data"), gmail_filter["id"]))

        for observable in gmail_observables:
            self.__hive_service.update_case_observables(
                CaseObservable(**observable), fields=["tags"])
        self.report({'message': "Added filters"})

    def unblock_messages(self, case_id):
        """Delete a previous created filter by filter ID
        """
        gmail_observables = self.__get_gmail_subjects(
            case_id,
            query=And(Eq("dataType", "mail"),
                      And(EndsWith("data", self.__gmail_domain))))
        for observable in gmail_observables:
            tag = self.__get_filter_tag(
                observable["tags"]
            )  # a tag should look like gmail_filters:domain:1235123121
            resource = self.gmail_impersonate(observable["data"])
            try:
                print("deleteing: {}".format(tag.split(":")[-1]))
                resource.users().settings().filters().delete(
                    userId=observable["data"],
                    id=tag.split(":")[-1]).execute()
            except GoogleAuthError as e:
                self.error("Gmail oauth failed: {}".format(e))
            except HttpError as e:
                self.error("Gmail api failed: {}".format(e))
            observable["tags"].remove(tag)

        for observable in gmail_observables:
            self.__hive_service.update_case_observables(
                CaseObservable(**observable), fields=["tags"])
        self.report({'message': "Removed filters"})

    def deletemessage(self, observable, dataType, caseId):
        if dataType != "other":
            self.error(
                "{} needs gmail query of type 'other' but {} given".format(
                    self.get_param("config.service"), dataType))
        self.trash_message(caseId, observable)

    def unblockdomain(self, observable, dataType, caseId):
        if dataType != "domain":
            self.error("{} needs data of type 'domain' but {} given".format(
                self.get_param("config.service"), dataType))
        self.unblock_messages(caseId)

    def unblocksender(self, observable, dataType, caseId):
        if dataType != "mail":
            self.error("{} needs data of type 'mail' but {} given".format(
                self.get_param("config.service"), dataType))
        self.unblock_messages(caseId)

    def blocksender(self, observable, dataType, caseId):
        if dataType != "mail":
            self.error("{} needs data of type 'mail' but {} given".format(
                self.get_param("config.service"), dataType))
        self.block_messages(caseId, "from: {}".format(observable))

    def blockdomain(self, observable, dataType, caseId):
        if dataType != "domain":
            self.error("{} needs data of type 'domain' but {} given".format(
                self.get_param("config.service"), dataType))
        self.block_messages(caseId, "from: {}".format(observable))

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

        self.hive_auth(self.__thehive_url, self.__thehive_api_key)

        dataType = self.get_param("data.dataType")
        observable = self.get_param("data.data")
        caseId = self.get_param("data._parent")

        action = getattr(
            self, self.service,
            self.__not_found)  # call respective func or fail with default
        action(observable, dataType, caseId)

    def operations(self, raw):
        return [self.build_operation('AddTagToArtifact', tag='gmail:handled')]