SearchIncidentsV2

Searches Demisto incidents. A summarized version of this scrips is available with the summarizedversion argument. This automation runs using the default Limited User role, unless you explicitly change the permissions. For more information, see the section about permissions here: - For Cortex XSOAR 6 see https://docs-cortex.paloaltonetworks.com/r/Cortex-XSOAR/6.x/Cortex-XSOAR-Playbook-Design-Guide/Automations - For Cortex XSOAR 8 Cloud see https://docs-cortex.paloaltonetworks.com/r/Cortex-XSOAR/8/Cortex-XSOAR-Cloud-Documentation/Create-a-script - For Cortex XSOAR 8.7 On-prem see https://docs-cortex.paloaltonetworks.com/r/Cortex-XSOAR/8.7/Cortex-XSOAR-On-prem-Documentation/Create-a-script https://docs-cortex.paloaltonetworks.com/r/Cortex-XSOAR/6.10/Cortex-XSOAR-Administrator-Guide/Automations

python · Common Scripts

Source

from enum import Enum

import demistomock as demisto  # noqa: F401
from CommonServerPython import *  # noqa: F401

DEFAULT_LIMIT = 100
DEFAULT_PAGE_SIZE = 100
STARTING_PAGE_NUMBER = 1


class AlertSeverity(Enum):
    UNKNOWN = 0
    INFO = 0.5
    LOW = 1
    MEDIUM = 2
    HIGH = 3
    CRITICAL = 4


class AlertStatus(Enum):
    PENDING = 0
    ACTIVE = 1
    DONE = 2
    ARCHIVE = 3


def check_if_found_incident(res: List):
    if res and isinstance(res, list) and isinstance(res[0].get("Contents"), dict):
        if "data" not in res[0]["Contents"]:
            raise DemistoException(res[0].get("Contents"))
        elif res[0]["Contents"]["data"] is None:
            return False
        return True
    else:
        raise DemistoException(f"failed to get incidents from xsoar.\nGot: {res}")


def is_valid_args(args: Dict):
    array_args: List[str] = ["id", "name", "status", "notstatus", "reason", "level", "owner", "type", "query"]
    error_msg: List[str] = []
    for _key, value in args.items():
        if _key in array_args:
            try:
                if _key == "id":
                    if not isinstance(value, int | str | list):
                        error_msg.append(
                            f"Error while parsing the incident id with the value: {value}. The given type: "
                            f"{type(value)} is not a valid type for an ID. The supported id types are: int, list and str"
                        )
                    elif isinstance(value, str):
                        _ = bytes(value, "utf-8").decode("unicode_escape")
                elif _key == "query":
                    _ = bytes(value.replace("\\", "\\\\"), "utf-8").decode("unicode_escape")
                else:
                    _ = bytes(value, "utf-8").decode("unicode_escape")
            except UnicodeDecodeError as ex:
                error_msg.append(f'Error while parsing the argument: "{_key}" \nError:\n- "{ex!s}"')

    if len(error_msg) != 0:
        raise DemistoException("\n".join(error_msg))

    return True


def apply_filters(incidents: List, args: Dict):
    names_to_filter = set(argToList(args.get("name")))
    types_to_filter = set(argToList(args.get("type")))

    filtered_incidents = []
    for incident in incidents:
        incident_id, incident_type = incident.get("id"), incident.get("type")
        if names_to_filter and incident["name"] not in names_to_filter:
            continue
        demisto.debug(f"{incident_id=}, {incident_type=}")
        if types_to_filter and incident["type"] not in types_to_filter:
            continue
        filtered_incidents.append(incident)

    return filtered_incidents


def summarize_incidents(args: dict, incidents: List[dict], platform: str):
    summarized_fields = [
        "id",
        "name",
        "type",
        "severity",
        "status",
        "owner",
        "created",
        "closed",
    ]
    if platform == "x2" or platform == "unified_platform":
        summarized_fields.append("alertLink")
    else:
        summarized_fields.append("incidentLink")
    if args.get("add_fields_to_summarize_context"):
        summarized_fields += args.get("add_fields_to_summarize_context", "").split(",")
        summarized_fields = [x.strip() for x in summarized_fields]  # clear out whitespace
    summarized_incidents = []
    for incident in incidents:
        summarized_incident = {
            field: incident.get(field, incident["CustomFields"].get(field, "n/a")) for field in summarized_fields
        }
        summarized_incidents.append(summarized_incident)
    return summarized_incidents


def add_incidents_link(data: List, platform: str):
    # For XSIAM or Platform links
    if platform == "x2" or platform == "unified_platform":
        server_url = "https://" + demisto.getLicenseCustomField("Http_Connector.url")
        for incident in data:
            incident_link = urljoin(server_url, f'alerts?action:openAlertDetails={incident.get("id")}-investigation')
            incident["alertLink"] = incident_link
    # For XSOAR links
    else:
        server_url = demisto.demistoUrls().get("server")
        prefix = "" if is_demisto_version_ge("8.4.0") else "#"
        for incident in data:
            incident_link = urljoin(server_url, f'{prefix}/Details/{incident.get("id")}')
            incident["incidentLink"] = incident_link
    return data


def transform_to_alert_data(incidents: List):
    for incident in incidents:
        incident["hostname"] = incident.get("CustomFields", {}).get("hostname")
        incident["initiatedby"] = incident.get("CustomFields", {}).get("initiatedby")
        incident["targetprocessname"] = incident.get("CustomFields", {}).get("targetprocessname")
        incident["username"] = incident.get("CustomFields", {}).get("username")
        incident["status"] = AlertStatus(incident.get("status")).name
        incident["severity"] = AlertSeverity(incident.get("severity")).name

    return incidents


def search_incidents(args: Dict):  # pragma: no cover
    hr_prefix = ""
    is_summarized_version = argToBoolean(args.get("summarizedversion", False))
    platform = get_demisto_version().get("platform", "xsoar")
    if not is_valid_args(args):
        return None

    if fromdate := arg_to_datetime(args.get("fromdate", "30 days ago" if is_summarized_version else None)):
        from_date = fromdate.isoformat()
        args["fromdate"] = from_date

    if todate := arg_to_datetime(args.get("todate")):
        to_date = todate.isoformat()
        args["todate"] = to_date

    if args.get("trimevents"):
        if platform == "xsoar" or platform == "xsoar_hosted":
            raise ValueError("The trimevents argument is not supported in XSOAR.")

        if args.get("trimevents") == "0":
            args.pop("trimevents")

    if includeinformational := argToBoolean(args.get("includeinformational", False)):
        if not (args.get("fromdate") and args.get("todate")):
            raise ValueError("The includeinformational argument requires fromdate and todate arguments.")

        if (datetime.utcnow() - fromdate).total_seconds() > 5 * 60 * 60:  # type: ignore
            args["fromdate"] = arg_to_datetime("5 hours ago").isoformat()  # type: ignore
            hr_prefix = (
                f"fromdate: {fromdate} is more than 5 hours ago. We support "
                f"querying informational incidents for up to the last 5 hours. The fromdate has been"
                f" adjusted to 5 hours ago."
            )
        args["includeinformational"] = includeinformational

    else:
        args.pop("includeinformational", None)

    # handle list of ids
    if args.get("id"):
        args["id"] = ",".join(argToList(args.get("id"), transform=str))

    res: List = execute_command("getIncidents", args, extract_contents=False)
    incident_found: bool = check_if_found_incident(res)
    if not incident_found:
        if hr_prefix:
            hr_prefix = f"{hr_prefix}\n"
        if platform == "x2" or platform == "unified_platform":
            return f"{hr_prefix}Alerts not found.", {}, {}
        return f"{hr_prefix}Incidents not found.", {}, {}
    limit = arg_to_number(args.get("limit")) or DEFAULT_LIMIT
    all_found_incidents = res[0]["Contents"]["data"]
    demisto.debug(f"Amount of incidents before filtering = {len(all_found_incidents)} with args {args} before pagination")
    page_size = args.get("size") or DEFAULT_PAGE_SIZE
    more_pages = len(all_found_incidents) == page_size
    all_found_incidents = add_incidents_link(apply_filters(all_found_incidents, args), platform)
    demisto.debug(f"Amount of incidents after filtering = {len(all_found_incidents)} before pagination")
    page = STARTING_PAGE_NUMBER

    if all_found_incidents and "todate" not in args:
        # In case todate is not part of the argumetns we add it to avoid duplications
        first_incident = all_found_incidents[0]
        args["todate"] = first_incident.get("created")
        demisto.info(f"Setting todate argument to be {first_incident.get('created')} to avoid duplications")

    while more_pages and len(all_found_incidents) < limit:
        args["page"] = page
        current_page_found_incidents = execute_command("getIncidents", args).get("data") or []

        # When current_page_found_incidents is None it means the requested page was empty
        if not current_page_found_incidents:
            break

        demisto.debug(f"before filtering {len(current_page_found_incidents)=} {args=} {page=}")
        more_pages = len(current_page_found_incidents) == page_size

        current_page_found_incidents = add_incidents_link(apply_filters(current_page_found_incidents, args), platform)
        demisto.debug(f"after filtering = {len(current_page_found_incidents)=}")
        all_found_incidents.extend(current_page_found_incidents)
        page += 1

    all_found_incidents = all_found_incidents[:limit]

    additional_headers: List[str] = []
    if is_summarized_version:
        all_found_incidents = summarize_incidents(args, all_found_incidents, platform)
        if args.get("add_fields_to_summarize_context"):
            additional_headers = args.get("add_fields_to_summarize_context", "").split(",")

    headers: List[str]
    if platform == "x2" or platform == "unified_platform":
        headers = [
            "id",
            "name",
            "severity",
            "details",
            "hostname",
            "initiatedby",
            "status",
            "owner",
            "targetprocessname",
            "username",
            "alertLink",
        ]

        all_found_incidents = transform_to_alert_data(all_found_incidents)
        md = tableToMarkdown(
            name="Alerts found",
            t=all_found_incidents,
            headers=headers + additional_headers,
            removeNull=True,
            url_keys=["alertLink"],
        )
    else:
        headers = ["id", "name", "severity", "status", "owner", "created", "closed", "incidentLink"]
        md = tableToMarkdown(
            name="Incidents found", t=all_found_incidents, headers=headers + additional_headers, url_keys=["incidentLink"]
        )

    if hr_prefix:
        md = f"{hr_prefix}\n{md}"
    demisto.debug(f"amount of all the incidents that were found {len(all_found_incidents)}")

    return md, all_found_incidents, res


def main():  # pragma: no cover
    args: Dict = demisto.args()
    is_summarized_version = argToBoolean(args.get("summarizedversion", False))
    try:
        readable_output, outputs, raw_response = search_incidents(args)
        if search_results_label := args.get("searchresultslabel"):
            for output in outputs:
                output["searchResultsLabel"] = search_results_label
        results = CommandResults(
            outputs_prefix="foundIncidents",
            outputs_key_field="id",
            readable_output=readable_output,
            outputs=outputs,
            raw_response=raw_response,
            # in summarized version, ignore auto extract
            ignore_auto_extract=is_summarized_version,
        )
        return_results(results)
    except DemistoException as error:
        return_error(str(error), error)


if __name__ in ("__main__", "__builtin__", "builtins"):  # pragma: no cover
    main()

README

Searches Demisto incidents. A summarized version of this scrips is available with the summarizedversion argument.

Permissions


This automation runs using the default Limited User role, unless you explicitly change the permissions.
For more information, see the section about permissions here: <~XSOAR>For Cortex XSOAR 6, see the https://docs-cortex.paloaltonetworks.com/r/Cortex-XSOAR/6.x/Cortex-XSOAR-Playbook-Design-Guide/Automations for Cortex XSOAR 8 Cloud, see the https://docs-cortex.paloaltonetworks.com/r/Cortex-XSOAR/8/Cortex-XSOAR-Cloud-Documentation/Create-a-script for Cortex XSOAR 8 On-prem, see the https://docs-cortex.paloaltonetworks.com/r/Cortex-XSOAR/8.7/Cortex-XSOAR-On-prem-Documentation/Create-a-script.</~XSOAR><~XSIAM>https://docs-cortex.paloaltonetworks.com/r/Cortex-XSIAM/Cortex-XSIAM-Administrator-Guide/Permission-Management</~XSIAM>

Script Data


Name Description
Script Type python3
Tags Utility
Cortex XSOAR Version 5.0.0

Used In


Sample usage of this script can be found in the following playbooks and scripts.

  • Endpoint Investigation Plan
  • ExtraHop - Ticket Tracking
  • Kaseya VSA 0-day - REvil Ransomware Supply Chain Attack
  • MDE - False Positive Incident Handling
  • MDE - True Positive Incident Handling
  • Prisma Cloud Correlate Alerts v2
  • Ransomware Enrich and Contain
  • SafeBreach - Create Incidents per Insight and Associate Indicators
  • SolarStorm and SUNBURST Hunting and Response Playbook
  • Spring Core and Cloud Function SpEL RCEs

Inputs


Argument Name Description
id A comma-separated list of incident IDs by which to filter the results.
name A comma-separated list of incident names by which to filter the results.
status A comma-separated list of incident statuses by which to filter the results. For example: assigned.
notstatus A comma-separated list of incident statuses to exclude from the results. For example: assigned.
reason A comma-separated list of incident close reasons by which to filter the results.
fromdate Filter by from date (e.g. “3 days ago” or 2006-01-02T15:04:05+07:00 or 2006-01-02T15:04:05Z). Default value is “30 days ago”.
todate Filter by to date (e.g. “3 days ago” or 2006-01-02T15:04:05+07:00 or 2006-01-02T15:04:05Z)
fromclosedate Filter by from close date (e.g. 2006-01-02T15:04:05+07:00 or 2006-01-02T15:04:05Z)
toclosedate Filter by to close date (e.g. 2006-01-02T15:04:05+07:00 or 2006-01-02T15:04:05Z)
fromduedate Filter by from due date (e.g. 2006-01-02T15:04:05+07:00 or 2006-01-02T15:04:05Z)
toduedate Filter by to due date (e.g. 2006-01-02T15:04:05+07:00 or 2006-01-02T15:04:05Z)
level Filter by Severity
owner Filter by incident owners
details Filter by incident details
type Filter by incident type
query Use free form query (use Lucene syntax) as filter. All other filters will be ignored when this filter is used.
page Filter by the page number (deprecated)
trimevents The number of events to return from the alert JSON. The default is 0, which returns all events.
Note that the count is from the head of the list, regardless of event time or other properties.
size Number of incidents per page (per fetch) (deprecated)
sort Sort in format of field.asc,field.desc,…
searchresultslabel If provided, the value of this argument will be set under the searchResultsLabel context key for each incident found.
summarizedversion If enabled runs a summarized version of this script. Disables auto-extract, sets fromDate to 30 days, and minimizes the context output. You can add sepcific fields to context using the add_fields_to_summarize_context argument. Default is false.
includeinformational Supported only in XSIAM. When the value is set to ‘True’, informational severity alerts will return as part of the results. The ‘fromdate’ and ‘todate’ arguments must be provided to use this argument. The maximum value currently supported for the ‘fromdate’ argument to retrieve informational incidents is 5 hours. If a value greater than this is provided, it will be adjusted to 5 hours ago. To retrieve only informational incidents, use the query argument and include this limitation within the query. Default is false.
limit The maximum number of incidents to be returned. Default is 100.

Outputs


Path Description Type
foundIncidents.id A list of incident IDs returned from the query. Unknown
foundIncidents.name A list of incident names returned from the query. Unknown
foundIncidents.severity A list of incident severities returned from the query. Unknown
foundIncidents.status A list of incident statuses returned from the query. Unknown
foundIncidents.owner A list of incident owners returned from the query. Unknown
foundIncidents.created A list of the incident create date returned from the query. Unknown
foundIncidents.closed A list of incident close dates returned from the query. Unknown
foundIncidents.labels An array of labels per incident returned from the query. Unknown
foundIncidents.details Details of the incidents returned from the query. Unknown
foundIncidents.dueDate A list of incident due dates returned from the query. Unknown
foundIncidents.phase A list of incident phases returned from the query. Unknown
foundIncidents.searchResultsLabel The value provided in the searchresultslabel argument. String

Script Example

!SearchIncidentsV2 name="Incident to search"

Context Example

{
    "foundIncidents": [
        {
            "CustomFields": {
                "detectionsla": {
                    "accumulatedPause": 0,
                    "breachTriggered": false,
                    "dueDate": "0001-01-01T00:00:00Z",
                    "endDate": "0001-01-01T00:00:00Z",
                    "lastPauseDate": "0001-01-01T00:00:00Z",
                    "runStatus": "idle",
                    "sla": 20,
                    "slaStatus": -1,
                    "startDate": "0001-01-01T00:00:00Z",
                    "totalDuration": 0
                },
                "remediationsla": {
                    "accumulatedPause": 0,
                    "breachTriggered": false,
                    "dueDate": "0001-01-01T00:00:00Z",
                    "endDate": "0001-01-01T00:00:00Z",
                    "lastPauseDate": "0001-01-01T00:00:00Z",
                    "runStatus": "idle",
                    "sla": 7200,
                    "slaStatus": -1,
                    "startDate": "0001-01-01T00:00:00Z",
                    "totalDuration": 0
                },
                "timetoassignment": {
                    "accumulatedPause": 0,
                    "breachTriggered": false,
                    "dueDate": "0001-01-01T00:00:00Z",
                    "endDate": "0001-01-01T00:00:00Z",
                    "lastPauseDate": "0001-01-01T00:00:00Z",
                    "runStatus": "idle",
                    "sla": 0,
                    "slaStatus": -1,
                    "startDate": "0001-01-01T00:00:00Z",
                    "totalDuration": 0
                },
                "urlsslverification": []
            },
            "ShardID": 0,
            "account": "",
            "activated": "0001-01-01T00:00:00Z",
            "allRead": false,
            "allReadWrite": false,
            "attachment": null,
            "autime": 1601389784162034000,
            "canvases": null,
            "category": "",
            "closeNotes": "",
            "closeReason": "",
            "closed": "0001-01-01T00:00:00Z",
            "closingUserId": "",
            "created": "2020-09-29T17:29:44.162034+03:00",
            "dbotCreatedBy": "admin",
            "dbotCurrentDirtyFields": null,
            "dbotDirtyFields": null,
            "dbotMirrorDirection": "",
            "dbotMirrorId": "",
            "dbotMirrorInstance": "",
            "dbotMirrorLastSync": "0001-01-01T00:00:00Z",
            "dbotMirrorTags": null,
            "details": "",
            "droppedCount": 0,
            "dueDate": "2020-10-09T17:29:44.162034+03:00",
            "feedBased": false,
            "hasRole": false,
            "id": "978",
            "investigationId": "",
            "isPlayground": false,
            "labels": [
                {
                    "type": "Instance",
                    "value": "admin"
                },
                {
                    "type": "Brand",
                    "value": "Manual"
                }
            ],
            "lastJobRunTime": "0001-01-01T00:00:00Z",
            "lastOpen": "0001-01-01T00:00:00Z",
            "linkedCount": 0,
            "linkedIncidents": null,
            "modified": "2020-09-29T17:29:44.162202+03:00",
            "name": "Incident to search",
            "notifyTime": "0001-01-01T00:00:00Z",
            "occurred": "2020-09-29T17:29:44.162034+03:00",
            "openDuration": 0,
            "owner": "admin",
            "parent": "",
            "phase": "",
            "playbookId": "",
            "previousAllRead": false,
            "previousAllReadWrite": false,
            "previousRoles": null,
            "rawCategory": "",
            "rawCloseReason": "",
            "rawJSON": "",
            "rawName": "Incident to search",
            "rawPhase": "",
            "rawType": "Unclassified",
            "reason": "",
            "reminder": "0001-01-01T00:00:00Z",
            "roles": null,
            "runStatus": "",
            "severity": 0,
            "sla": 0,
            "sortValues": [
                "_score"
            ],
            "sourceBrand": "Manual",
            "sourceInstance": "admin",
            "status": 0,
            "type": "Unclassified",
            "version": 1
        }
    ]
}

Human Readable Output

Incidents found

id name severity status owner created closed
978 Incident to search 0 0 admin 2020-09-29T17:29:44.162034+03:00 0001-01-01T00:00:00Z