PhishLabs IOC EIR

Get Email Incident Reports from PhishLabs.

Data Enrichment & Threat Intelligence · PhishLabs

Details

IDPhishLabs IOC EIR
ProviderFortra
CategoryData Enrichment & Threat Intelligence
From Version5.0.0
Docker Imagedemisto/python3:3.12.13.10116658
Supported ModulesAgentix XSIAM

README

This integration was integrated and tested with V1.0 of PhishLabs IOC EIR api

Use Cases

  • Get live EIR from PhishLabs
  • Get EIR by filters from PhishLabs

Detailed Description

Phishlabs Email Incident Response (EIR) is a solution that protects against threats that make it past your email security stack and into your employee inboxes. With Email Incident Response, enterprises can detect, prevent, and respond to these threats.

  • Suspicious Email Analysis
  • Email Threat Intelligence

Configure PhishLabs IOC EIR on Cortex XSOAR

  1. Navigate to Settings > Integrations  Servers & Services.
  2. Search for PhishLabs IOC EIR.
  3. Click Add instance to create and configure a new integration instance.
    • Name: a textual name for the integration instance.
    • Server URL (e.g. https://example.net)
    • User
    • Source Reliability. Reliability of the source providing the intelligence data. (The default value is B - Usually reliable)
    • Fetch incidents
    • First fetch timestamp ( e.g., 12 hours, 7 days)
    • Fetch limit
    • Trust any certificate (not secure)
    • Use system proxy settings
  4. Click Test to validate the new instance.

Fetch Incidents

Fetch incidents done by the following configuration:

  • Fetch limit - limit amount of incidents by fetch
  • First fetch timestamp - date for starting collecting incidents (1 days ago, 1 hours ago etc)
  • Incident type
[
  {
    "name": "PhishLabs IOC - EIR: INC0528925",
    "occurred": "2019-10-15T16:31:09Z",
    "rawJSON": {
            "id": "INC0528925",
            "service": "EIR",
            "title": "Deploymentliste release 10.0 in PROD am 15.10.2019",
            "description": "",
            "status": "Closed",
            "details": {
                "caseType": "Response",
                "classification": "No Threat Detected",
                "subClassification": "No Threat Detected",
                "severity": null,
                "emailReportedBy": "johnnydepp@gmail.com",
                "submissionMethod": "Attachment",
                "sender": "johnnydepp@gmail.com",
                "emailBody": "Test",
                "urls": [
                    {
                        "url": "google.com",
                        "malicious": false,
                        "maliciousDomain": false
                    }
                ],
                "attachments": [],
                "furtherReviewReason": null,
                "offlineUponReview": false
            },
            "created": "2019-10-15T16:31:08Z",
            "modified": "2019-10-15T16:31:09Z",
            "closed": "2019-10-15T16:31:09Z",
            "duration": 0
        }
  }
]

Commands

You can execute these commands from the Cortex XSOAR CLI, as part of an automation, or in a playbook. After you successfully execute a command, a DBot message appears in the War Room with the command details.

  1. phishlabs-ioc-eir-get-incidents
  2. phishlabs-ioc-eir-get-incident-by-id

1. phishlabs-ioc-eir-get-incidents


Get EIR incidents from PhishLabs-IOC EIR service (dafault limit 25 incidents)

Base Command

phishlabs-ioc-eir-get-incidents

Input
Argument Name Description Required
status Filter incidents that are opened or closed. Optional
created_after Return Incidents created on or after the given timestamp Timestamp is in RFC3339 format(2019-04-12T23:20:50Z) Optional
created_before Return Incidents created on or before the given timestamp Timestamp is in RFC3339 format(2019-04-12T23:20:50Z) Optional
closed_after Return Incidents closed on or after the given timestamp Timestamp is in RFC3339 format(2019-04-12T23:20:50Z) Optional
closed_before Return Incidents closed on or before the given timestamp Timestamp is in RFC3339 format(2019-04-12T23:20:50Z) Optional
sort Return Incidents sorted by the given column. Optional
direction Return Incidents sorted by the given order. This will be applied to the given sort parameter. Optional
limit Limit amounts of incidents (0-50, default 25) Optional
offset Offset from last incident Optional
period Period to query on 1 days, 2 hours Optional

 

Context Output
Path Type Description
PhishLabsIOC.EIR.CaseType String Incident reason type
PhishLabsIOC.EIR.Classification String Incident classification
PhishLabsIOC.EIR.SubClassification String Detailed classification
PhishLabsIOC.EIR.Severity String Incident severity
PhishLabsIOC.EIR.SubmissionMethod String Email submission method
PhishLabsIOC.EIR.FurtherReviewReason String Incident further review reason
PhishLabsIOC.EIR.ID String Id of incident
PhishLabsIOC.EIR.Title String Title of reported incident
PhishLabsIOC.EIR.Description String Description of reporeted incident
PhishLabsIOC.EIR.Status Boolean Status of reported incident
PhishLabsIOC.EIR.Created Date Date of incident creation
PhishLabsIOC.EIR.Modified Date Date of incident last modified
PhishLabsIOC.EIR.Closed Date Date of incident closing
PhishLabsIOC.EIR.Duration Number Duration until closing incident in seconds
PhishLabsIOC.EIR.EmailReportedBy String User who reported the incident
PhishLabsIOC.EIR.Email.EmailBody String Email body
PhishLabsIOC.EIR.Email.Sender String Email sender
PhishLabsIOC.EIR.Email.URL.URL String Url found in body
PhishLabsIOC.EIR.Email.URL.Malicious Boolean Is the url malicious?
PhishLabsIOC.EIR.Email.URL.MaliciousDomain Boolean Is the url domain malicious?
PhishLabsIOC.EIR.Email.Attachment.FileName String Name of the attached file
PhishLabsIOC.EIR.Email.Attachment.MimeType String Attachemt mime type
PhishLabsIOC.EIR.Email.Attachment.MD5 String Attachemt md5 hash
PhishLabsIOC.EIR.Email.Attachment.SHA256 String Attachemt sha256 hash
PhishLabsIOC.EIR.Email.Attachment.Malicious Boolean Is the file malicious?
Email.To String The recipient of the email.
Email.From String The sender of the email.
Email.Body/HTML String The plain-text version of the email.
File.Name String The full file name (including file extension).
File.SHA256 Unknown The SHA256 hash of the file.
File.MD5 String The MD5 hash of the file.
File.Malicious.Vendor String The vendor that reported the file as malicious.
File.Malicious.Description String A description explaining why the file was determined to be malicious.
URL.Data String The URL
URL.Malicious.Vendor String The vendor reporting the URL as malicious.
URL.Malicious.Description String A description of the malicious URL.
DBotScore.Indicator String The indicator that was tested.
DBotScore.Type String The indicator type.
DBotScore.Vendor String The vendor used to calculate the score.
DBotScore.Score String The actual score.

 

Command Example

!phishlabs-ioc-eir-get-incidents limit=3

Context Example
{
    "DBotScore": [
        {
            "Indicator": "https://google.com",
            "Score": 1,
            "Type": "URL",
            "Vendor": "PhishLabs IOC - EIR"
        }
    ],
    "Email": [
        {
            "Body/HTML": "Example body",
            "From": "LinkedIn Sales Navigator  not@domain.com",
            "To": "Michael Mammele not@domain.com"
        },
        {
            "Body/HTML": "Example body",
            "From": "Tony Prince not@domain.com",
            "To": "Tony Prince not@domain.com"
        },
        {
            "Body/HTML": "Example body",
            "From": "FileDoc2 not@domain.com",
            "To": "John LaCour not@domain.com"
        }
    ],
    "File": [],
    "PhishLabsIOC": {
        "EIR": [
            {
                "CaseType": "Link",
                "Classification": "No Threat Detected",
                "Closed": "2019-11-05T23:23:06Z",
                "Created": "2019-11-05T22:05:52Z",
                "Description": "",
                "Duration": 4635,
                "Email": {
                    "Attachment": [],
                    "EmailBody": "Example body",
                    "Sender": "LinkedIn Sales Navigator  not@domain.com",
                    "URL": [
                        {
                            "Malicious": false,
                            "MaliciousDomain": false,
                            "URL": "https://google.com"
                        }
                    ]
                },
                "EmailReportedBy": "Michael Mammele not@domain.com",
                "FurtherReviewReason": null,
                "ID": "INC0682881",
                "Modified": "2019-11-05T23:23:06Z",
                "Severity": null,
                "Status": "Closed",
                "SubClassification": "No Threat Detected",
                "SubmissionMethod": "Attachment",
                "Title": "See who else can influence your deals"
            }
    ]
}
Human Readable Output

PhishLabs IOC - EIR - incidents

ID Title Status Created Classification SubClassification EmailReportedBy
INC0682881 See who else can influence your deals Closed 2019-11-05T22:05:52Z No Threat Detected No Threat Detected Michael Mammele not@domain.com
INC0682040 FW: Tuesday, November 5, 2019 Closed 2019-11-05T20:30:48Z Malicious Link - Phishing Tony Prince not@domain.com
INC0681982 Tuesday, November 5, 2019 Closed 2019-11-05T20:25:22Z Malicious Link - Phishing John LaCour not@domain.com

2. phishlabs-ioc-eir-get-incident-by-id


Returns a single Incident based on the given ID.

Base Command

phishlabs-ioc-eir-get-incident-by-id

Input
Argument Name Description Required
incident_id ID of Incident, Get it from previous command Required

 

Context Output
Path Type Description
PhishLabsIOC.EIR.CaseType String Incident reason type
PhishLabsIOC.EIR.Classification String Incident classification
PhishLabsIOC.EIR.SubClassification String Detailed classification
PhishLabsIOC.EIR.Severity String Incident severity
PhishLabsIOC.EIR.SubmissionMethod String Email submission method
PhishLabsIOC.EIR.FurtherReviewReason String Incident further review reason
PhishLabsIOC.EIR.ID String Id of incident
PhishLabsIOC.EIR.Title String Title of reported incident
PhishLabsIOC.EIR.Description String Description of reporeted incident
PhishLabsIOC.EIR.Status Boolean Status of reported incident
PhishLabsIOC.EIR.Created Date Date of incident creation
PhishLabsIOC.EIR.Modified Date Date of incident last modified
PhishLabsIOC.EIR.Closed Date Date of incident closing
PhishLabsIOC.EIR.Duration Number Duration until closing incident in seconds
PhishLabsIOC.EIR.EmailReportedBy String User who reported the incident
PhishLabsIOC.EIR.Email.EmailBody String Email body
PhishLabsIOC.EIR.Email.Sender String Email sender
PhishLabsIOC.EIR.Email.URL.URL String Url found in body
PhishLabsIOC.EIR.Email.URL.Malicious Boolean Is the url malicious?
PhishLabsIOC.EIR.Email.URL.MaliciousDomain Boolean Is the url domain malicious?
PhishLabsIOC.EIR.Email.Attachment.FileName String Name of the attached file
PhishLabsIOC.EIR.Email.Attachment.MimeType String Attachemt mime type
PhishLabsIOC.EIR.Email.Attachment.MD5 String Attachemt md5 hash
PhishLabsIOC.EIR.Email.Attachment.SHA256 String Attachemt sha256 hash
PhishLabsIOC.EIR.Email.Attachment.Malicious Boolean Is the file malicious?
Email.To String The recipient of the email.
Email.From String The sender of the email.
Email.Body/HTML String The plain-text version of the email.
File.Name String The full file name (including file extension).
File.SHA256 Unknown The SHA256 hash of the file.
File.MD5 String The MD5 hash of the file.
File.Malicious.Vendor String The vendor that reported the file as malicious.
File.Malicious.Description String A description explaining why the file was determined to be malicious.
URL.Data String The URL
URL.Malicious.Vendor String The vendor reporting the URL as malicious.
URL.Malicious.Description String A description of the malicious URL.
DBotScore.Indicator String The indicator that was tested.
DBotScore.Type String The indicator type.
DBotScore.Vendor String The vendor used to calculate the score.
DBotScore.Score String The actual score.
Command Example

!phishlabs-ioc-eir-get-incident-by-id incident_id=INC0671150

Context Example
{
    "DBotScore": [
        {
            "Indicator": "https://google.com",
            "Score": 1,
            "Type": "URL",
            "Vendor": "PhishLabs IOC - EIR"
        }
    ],
    "Email": [
        {
            "Body/HTML": "Example body",
            "From": "LinkedIn Sales Navigator  not@domain.com",
            "To": "Michael Mammele not@domain.com"
        }
    ],
    "File": [],
    "PhishLabsIOC": {
        "EIR": [
            {
                "CaseType": "Link",
                "Classification": "No Threat Detected",
                "Closed": "2019-11-05T23:23:06Z",
                "Created": "2019-11-05T22:05:52Z",
                "Description": "",
                "Duration": 4635,
                "Email": {
                    "Attachment": [],
                    "EmailBody": "Example body",
                    "Sender": "LinkedIn Sales Navigator  not@domain.com",
                    "URL": [
                        {
                            "Malicious": false,
                            "MaliciousDomain": false,
                            "URL": "https://google.com"
                        }
                    ]
                },
                "EmailReportedBy": "Michael Mammele not@domain.com",
                "FurtherReviewReason": null,
                "ID": "INC0682881",
                "Modified": "2019-11-05T23:23:06Z",
                "Severity": null,
                "Status": "Closed",
                "SubClassification": "No Threat Detected",
                "SubmissionMethod": "Attachment",
                "Title": "See who else can influence your deals"
            }
    ]
}

Configuration parameters

  • url — Server URL (e.g. https://example.net) (required)
  • credentials — User (required)
  • integrationReliability — Source Reliability (required)
  • isFetch — Fetch incidents
  • incidentType — Incident type
  • incidentFetchInterval — Incidents Fetch Interval
  • fetchTime — First fetch timestamp (<number> <time unit>, e.g., 12 hours, 7 days)
  • fetchLimit — Fetch limit
  • insecure — Trust any certificate (not secure)
  • proxy — Use system proxy settings

Commands (2)

  • phishlabs-ioc-eir-get-incident-by-id

    Returns a single Incident based on the given ID.

  • phishlabs-ioc-eir-get-incidents

    Returns EIR incidents from PhishLabs-IOC EIR service. The default number of incidents returned is 25.

import demistomock as demisto
from CommonServerPython import *

from CommonServerUserPython import *

""" IMPORTS """
from datetime import datetime
from typing import Any, AnyStr

import urllib3

# Disable insecure warnings
urllib3.disable_warnings()

"""GLOBALS/PARAMS
Attributes:
    INTEGRATION_NAME:
        Name of the integration as shown in the integration UI, for example: Microsoft Graph User.

    INTEGRATION_COMMAND_NAME:
        Command names should be written in all lower-case letters,
        and each word separated with a hyphen, for example: msgraph-user.

    INTEGRATION_CONTEXT_NAME:
        Context output names should be written in camel case, for example: MSGraphUser.
"""
INTEGRATION_NAME = "PhishLabs IOC - EIR"
INTEGRATION_COMMAND_NAME = "phishlabs-ioc-eir"
INTEGRATION_CONTEXT_NAME = "PhishLabsIOC"


class Client(BaseClient):
    def __init__(self, base_url, verify=True, proxy=False, auth=None, reliability=DBotScoreReliability.B):
        BaseClient.__init__(self, base_url=base_url, verify=verify, proxy=proxy, auth=auth)

        self.reliability = reliability

    def test_module(self) -> dict:
        """Performs basic GET request to check if the API is reachable and authentication is successful.

        Returns:
            Response json
        """
        return self.get_incidents(limit=1, period=demisto.params().get("fetchTime"))

    def get_incidents(
        self,
        status: str | None = None,
        created_after: str | None = None,
        created_before: str | None = None,
        closed_before: str | None = None,
        closed_after: str | None = None,
        sort: str | None = None,
        direction: str | None = None,
        limit: str | int = 25,
        offset: str | int = 0,
        period: str = None,
    ) -> dict:
        """
        Query the specified kwargs with default parameters if not defined
        Args:
            status: open,closed
            created_after: Timestamp is in RFC3339 format
            created_before: Timestamp is in RFC3339 format
            closed_before: Timestamp is in RFC3339 format
            closed_after: Timestamp is in RFC3339 format
            sort: created_at,closed_at
            direction: asc,desc
            limit: Limit amounts of incidents (0-50, default 50)
            offset: Offset from last incident
            period: timestamp (<number> <time unit>, e.g., 12 hours, 7 days)

        Returns:
            Raw response json as dictionary
        """
        suffix = "/incidents/EIR"
        params: dict[str, Any] = {}
        if period:
            created_after, created_before = parse_date_range(date_range=period, date_format="%Y-%m-%dT%H:%M:%SZ")
            params = {"created_after": created_after, "created_before": created_before}
        else:
            params = {
                "created_after": created_after,
                "created_before": created_before,
                "closed_before": closed_before,
                "closed_after": closed_after,
            }
        params.update({"status": status, "sort": sort, "direction": direction, "limit": limit, "offset": offset})
        demisto.debug(f"Params sent to API are: {params!s}")
        return self._http_request("GET", url_suffix=suffix, params=assign_params(**params))

    def get_incident_by_id(self, incident_id: str) -> dict:
        """Query incident by ID

        Args:
            incident_id: ID of incident

        Returns:
            Response JSON as dictionary
        """
        suffix = f"/incidents/EIR/{incident_id}"
        return self._http_request("GET", url_suffix=suffix)


""" HELPER FUNCTIONS """


@logger
def indicator_ec(indicator: dict, type_ec: AnyStr) -> dict:
    """indicator convert to ec format
    Get an indicator from raw response and concert to demisto entry context format

    Args:
        indicator: raw response dictionary
        type_ec: type of entry context

    Returns:
         indicator entry context
    """
    ec: dict = {}
    if type_ec == "url-phishlabs":
        ec = {
            "URL": indicator.get("url"),
            "Malicious": indicator.get("malicious"),
            "MaliciousDomain": indicator.get("maliciousDomain"),
        }
    elif type_ec == "attach-phishlabs":
        ec = {
            "fileName": indicator.get("fileName"),
            "MimeType": indicator.get("mimeType"),
            "MD5": indicator.get("md5"),
            "SHA256": indicator.get("sha256"),
            "Malicious": indicator.get("malicious"),
        }
    elif type_ec == "email-ec":
        ec = {"To": indicator.get("emailReportedBy"), "From": indicator.get("sender"), "Body/HTML": indicator.get("emailBody")}

    return ec


@logger
def indicator_dbot_ec(client: Client, indicator: dict, type_ec: AnyStr) -> tuple[dict, dict]:
    """Indicator convert to ec and dbotscore ec
    Get an indicator from raw response and concert to demisto entry context format and demisto dbotscore entry context
    format.

    Args:
        client: Client object
        indicator: raw response dictionary
        type_ec: type of entry context

    Returns:
        dbotscore entry context, indicator entry context
    """
    dbotscore: dict = {}
    ec: dict = {}
    if type_ec == "url-ec":
        ec = {"Data": indicator.get("url"), "Malicious": {"Vendor": INTEGRATION_NAME, "Description": indicator.get("malicious")}}
        dbotscore = {
            "Indicator": indicator.get("url"),
            "Type": "URL",
            "Vendor": INTEGRATION_NAME,
            "Score": Common.DBotScore.BAD if indicator.get("malicious") == "true" else Common.DBotScore.GOOD,
            "Reliability": client.reliability,
        }
    elif type_ec == "file-ec":
        ec = {
            "Name": indicator.get("fileName"),
            "SHA256": indicator.get("sha256"),
            "MD5": indicator.get("md5"),
            "Malicious": {"Vendor": INTEGRATION_NAME, "Description": indicator.get("malicious")},
        }
        dbotscore = {
            "Indicator": indicator.get("fileName"),
            "Type": "File",
            "Vendor": INTEGRATION_NAME,
            "Score": Common.DBotScore.BAD if indicator.get("malicious") == "true" else Common.DBotScore.GOOD,
            "Reliability": client.reliability,
        }

    return dbotscore, ec


@logger
def indicators_to_list_ec(client: Client, indicators: list, type_ec: AnyStr) -> tuple[list, list] | list:
    """Unpack list of indicators to demisto ec format
    Convert list of indicators from raw response to demisto entry context format lists

    Args:
        client: Client object
        indicators: lit of indicators from raw response
        type_ec: type of indicators
    Returns:
         List of indicators entry context and if not integration context also dbotscore
    """
    dbots: list = []
    ecs: list = []
    if type_ec in ["url-ec", "file-ec"]:
        for indicator in indicators:
            dbotscore, ec = indicator_dbot_ec(client, indicator, type_ec)
            ecs.append(ec)
            dbots.append(dbotscore)
        return ecs, dbots
    else:
        for indicator in indicators:
            ec = indicator_ec(indicator, type_ec)
            ecs.append(ec)
        return ecs


@logger
def raw_response_to_context(client: Client, incidents: list | Any) -> tuple[list, list, list, list, list]:
    """
    Convert incidents list from raw response to demisto entry context list format
    Args:
        client: Client object
        incidents: Incidents list

    Returns:
        Entry contexts of phishLabs, emails, files, urls, dbotScores
    """
    phishlabs_ec: list = []
    email_ec: list = []
    file_ec: list = []
    url_ec: list = []
    dbots_ec: list = []
    for incident in incidents:
        sc_incident: dict = incident.get("details", {})
        # Phishlabs entry context
        phishlabs: dict = {
            "CaseType": sc_incident.get("caseType"),
            "Classification": sc_incident.get("classification"),
            "SubClassification": sc_incident.get("subClassification"),
            "Severity": sc_incident.get("severity"),
            "EmailReportedBy": sc_incident.get("emailReportedBy"),
            "SubmissionMethod": sc_incident.get("submissionMethod"),
            "FurtherReviewReason": sc_incident.get("furtherReviewReason"),
            "ID": incident.get("id"),
            "Title": incident.get("title"),
            "Description": incident.get("description"),
            "Status": incident.get("status"),
            "Created": incident.get("created"),
            "Modified": incident.get("modified"),
            "Closed": incident.get("closed"),
            "Duration": incident.get("duration"),
            "Email": {
                "EmailBody": sc_incident.get("emailBody"),
                "Sender": sc_incident.get("sender"),
                "URL": indicators_to_list_ec(client, sc_incident.get("urls", []), type_ec="url-phishlabs"),
                "Attachment": indicators_to_list_ec(client, sc_incident.get("attachments", []), type_ec="attach-phishlabs"),
            },
        }
        phishlabs_ec.append(phishlabs)
        # Email entry context
        email = indicator_ec(sc_incident, type_ec="email-ec")
        email_ec.append(email)
        # Files + dbot entry context
        files, dbotscores_files = indicators_to_list_ec(client, sc_incident.get("attachments", []), type_ec="file-ec")
        file_ec += files
        dbots_ec += dbotscores_files
        # Urls + dbot entry context
        urls, dbotscores_urls = indicators_to_list_ec(client, sc_incident.get("urls", []), type_ec="url-ec")
        url_ec += urls
        dbots_ec += dbotscores_urls

    return phishlabs_ec, email_ec, file_ec, url_ec, dbots_ec


""" COMMANDS """


@logger
def test_module_command(client: Client, *_) -> tuple[None, None, str]:
    """Performs a basic GET request to check if the API is reachable and authentication is successful.

    Args:
        client: Client object with request
        *_: Usually demisto.args()

    Returns:
        'ok' if test successful.

    Raises:
        DemistoException: If test failed.
    """
    results = client.test_module()

    if "incidents" in results:
        return None, None, "ok"
    raise DemistoException(f"Test module failed, {results}")


def fetch_incidents_per_status(
    client: Client,
    created_after: str,
    offset: int,
    limit_page: int,
    sort: str,
    direction: str,
    limit_incidents: int,
    status: str,
    raws: list,
    incidents_raw: list,
):
    """

    Gets both closed and open incidents from Phishlabs for the given timeframe.

    """
    total: int = 0
    raw_response = client.get_incidents(
        created_after=created_after, offset=offset, limit=limit_page, sort=sort, direction=direction, status=status
    )
    while raw_response.get("metadata", {}).get("count") and total < limit_incidents:
        raws.append(raw_response)
        incidents_raw += raw_response.get("incidents", [])
        total += int(raw_response.get("metadata", {}).get("count"))
        offset += int(raw_response.get("metadata", {}).get("count"))
        if total >= limit_incidents:
            break
        if limit_incidents - total < 50:
            limit_page = limit_incidents - total
        raw_response = client.get_incidents(
            offset=offset, created_after=created_after, limit=limit_page, sort=sort, direction=direction, status=status
        )
    return raws, incidents_raw


@logger
def fetch_incidents_command(
    client: Client, fetch_time: str, limit: str, last_ids: set, last_run: str | None = None
) -> tuple[list[dict[str, Any]], dict]:
    """Uses to fetch incidents into Demisto
    Documentation: https://github.com/demisto/content/tree/master/docs/fetching_incidents

    Args:
        client: Client object with request
        fetch_time: From when to fetch if first time, e.g. `3 days`
        limit: limit of incidents in a fetch
        last_run: Last fetch object occurs.
        last_ids: Last IDs with same time from last fetches.

    Returns:
        incidents, new last_run
    """
    # Init
    raws: list = []
    incidents_raw: list = []
    # Set last run time
    occurred_format = "%Y-%m-%dT%H:%M:%SZ"
    if not last_run:
        datetime_new_last_run, _ = parse_date_range(date_range=fetch_time, date_format=occurred_format)
    else:
        datetime_new_last_run = last_run
    # Query incidents by limit and creation time
    offset = 0
    limit_incidents = int(limit)
    limit_page = min(50, limit_incidents)
    # Fetch open Phishlabs incidents
    raws, incidents_raw = fetch_incidents_per_status(
        client=client,
        created_after=datetime_new_last_run,
        offset=offset,
        limit_page=limit_page,
        limit_incidents=limit_incidents,
        sort="created_at",
        direction="asc",
        status="open",
        raws=raws,
        incidents_raw=incidents_raw,
    )
    # Fetch closed Phishlabs incidents
    raws, incidents_raw = fetch_incidents_per_status(
        client=client,
        created_after=datetime_new_last_run,
        offset=offset,
        limit_page=limit_page,
        limit_incidents=limit_incidents,
        sort="created_at",
        direction="asc",
        status="closed",
        raws=raws,
        incidents_raw=incidents_raw,
    )

    # Sort incidents by created time
    incidents_raw = sorted(incidents_raw, key=lambda inc: datetime.strptime(inc.get("created"), "%Y-%m-%dT%H:%M:%SZ"))

    processed_incident_ids = set()

    # Gather incidents by demisto format
    incidents_report: list[dict] = []
    demisto.debug(f"Got {len(incidents_raw)} incidents from the API.")
    if incidents_raw:
        for incident_raw in incidents_raw:
            if len(incidents_report) >= limit_incidents:
                break

            # We need to remove duplicates
            if incident_raw.get("id") in processed_incident_ids:
                demisto.debug(f"Skipping duplicate incident with id {incident_raw.get('id')}")
                continue

            # We need to be sure we didnt fetch the last incident again
            if incident_raw.get("id") in last_ids:
                demisto.debug(f"Skipping duplicate incident (from last run) with id {incident_raw.get('id')}")
                continue

            # Mark the incident ID as processed
            processed_incident_ids.add(incident_raw.get("id"))

            # Take the last touched incident time and id for last_run object
            current_created = incident_raw.get("created")
            current_created_datetime = datetime.strptime(current_created, occurred_format)
            if datetime_new_last_run and current_created_datetime > datetime.strptime(datetime_new_last_run, occurred_format):  # noqa: E501 # type: ignore
                last_ids = set()
            datetime_new_last_run = incident_raw.get("created")
            last_ids.add(incident_raw.get("id"))

            # Creates incident entry
            occurred = incident_raw.get("created")
            incidents_report.append(
                {
                    "name": f"{INTEGRATION_NAME}: {incident_raw.get('id')}",
                    "occurred": occurred,
                    "rawJSON": json.dumps(incident_raw),
                }
            )

    demisto.debug(
        f"finished processing incidents. returning {len(incidents_report)} incidents,"
        f"with {datetime_new_last_run!s} as last run,"
        f"and {last_ids!s} as lastIds"
    )

    # Return results
    return incidents_report, {"lastRun": datetime_new_last_run, "lastIds": list(last_ids)}


@logger
def get_incidents_command(client: Client, args: dict) -> tuple[object, dict, list | dict]:
    """Lists all incidents and return outputs in Demisto's context entry

    Args:
        client: Client object with request
        args: demisto.args() dictionary

    Returns:
        human readable (markdown format), raw response and entry context
    """
    raw_response: dict = client.get_incidents(
        status=args.get("status"),
        created_after=args.get("created_after"),
        created_before=args.get("created_before"),
        closed_before=args.get("closed_before"),
        closed_after=args.get("closed_after"),
        sort=args.get("sort"),
        direction=args.get("direction"),
        limit=args.get("limit", 25),
        offset=args.get("offset", 0),
        period=args.get("period"),
    )
    if raw_response:
        title = f"{INTEGRATION_NAME} - incidents"
        phishlabs_ec, emails_ec, files_ec, urls_ec, dbots_ec = raw_response_to_context(client, raw_response.get("incidents"))
        context_entry: dict = {
            outputPaths.get("dbotscore"): dbots_ec,
            outputPaths.get("file"): files_ec,
            outputPaths.get("url"): urls_ec,
            "Email(val.Address && val.Address == obj.Address)": emails_ec,
            f"{INTEGRATION_CONTEXT_NAME}(val.EIR.ID && val.EIR.ID === obj.EIR.ID && "
            f"val.EIR.Modified && val.EIR.Modified === obj.EIR.Modified)": {"EIR": phishlabs_ec},
        }
        human_readable = tableToMarkdown(
            name=title,
            t=phishlabs_ec,
            headers=["ID", "Title", "Status", "Created", "Classification", "SubClassification", "EmailReportedBy"],
            removeNull=True,
        )

        return human_readable, context_entry, raw_response
    else:
        return f"{INTEGRATION_NAME} - Could not find any results for given query", {}, {}


@logger
def get_incident_by_id_command(client: Client, args: dict) -> tuple[object, dict, dict]:
    """Lists all events and return outputs in Demisto's context entry

    Args:
        client: Client object with request
        args: demisto.args() dictionary

    Returns:
        human readable (markdown format), raw response and entry context
    """
    incident_id = args.get("incident_id", "")
    raw_response: dict = client.get_incident_by_id(incident_id)
    if raw_response:
        title = f"{INTEGRATION_NAME} - incidents"
        phishlabs_ec, emails_ec, files_ec, urls_ec, dbots_ec = raw_response_to_context(client, raw_response.get("incidents"))
        context_entry: dict = {
            outputPaths.get("dbotscore"): dbots_ec,
            outputPaths.get("file"): files_ec,
            outputPaths.get("url"): urls_ec,
            "Email(val.Address && val.Address == obj.Address)": emails_ec,
            f"{INTEGRATION_CONTEXT_NAME}(val.EIR.ID && val.EIR.ID === obj.EIR.ID && "
            f"val.EIR.Modified && val.EIR.Modified === obj.EIR.Modified)": {"EIR": phishlabs_ec},
        }
        human_readable = tableToMarkdown(
            name=title,
            t=phishlabs_ec,
            headers=["ID", "Title", "Status", "Created", "Classification", "SubClassification", "EmailReportedBy"],
            removeNull=True,
        )

        return human_readable, context_entry, raw_response
    else:
        return f"{INTEGRATION_NAME} - Could not find any results for given query", {}, {}


""" COMMANDS MANAGER / SWITCH PANEL """


def main():
    params = demisto.params()
    base_url = urljoin(params.get("url"), "idapi/v1")
    verify_ssl = not params.get("insecure", False)
    reliability = demisto.params().get("integrationReliability")
    reliability = reliability if reliability else DBotScoreReliability.B

    if DBotScoreReliability.is_valid_type(reliability):
        reliability = DBotScoreReliability.get_dbot_score_reliability_from_str(reliability)
    else:
        return_error("Please provide a valid value for the Source Reliability parameter.")

    proxy = params.get("proxy")
    client = Client(
        base_url=base_url,
        verify=verify_ssl,
        proxy=proxy,
        auth=(params.get("credentials", {}).get("identifier"), params.get("credentials", {}).get("password")),
        reliability=reliability,
    )

    command = demisto.command()
    demisto.debug(f"Command being called is {command}")
    commands = {
        "test-module": test_module_command,
        f"{INTEGRATION_COMMAND_NAME}-get-incidents": get_incidents_command,
        f"{INTEGRATION_COMMAND_NAME}-get-incident-by-id": get_incident_by_id_command,
    }
    try:
        if command == "fetch-incidents":
            last_run = demisto.getLastRun()
            last_ids = set(last_run.get("lastIds")) if last_run.get("lastIds") else set()
            incidents, new_last_run = fetch_incidents_command(
                client,
                fetch_time=params.get("fetchTime"),
                last_run=last_run.get("lastRun"),
                limit=params.get("fetchLimit"),
                last_ids=last_ids,
            )
            demisto.incidents(incidents)
            demisto.setLastRun(new_last_run)
        else:
            readable_output, outputs, raw_response = commands[command](client=client, args=demisto.args())
            return_outputs(readable_output, outputs, raw_response)
    # Log exceptions
    except Exception as e:
        err_msg = f"Error in {INTEGRATION_NAME} Integration [{e}]"
        return_error(err_msg, error=e)


if __name__ == "builtins":
    main()