IronDefense

The IronDefense Integration for Cortex XSOAR allows users to interact with IronDefense alerts within Cortex XSOAR. The Integration provides the ability to rate alerts, update alert statuses, add comments to alerts, to report observed bad activity, get alerts, get events, and get IronDome information.

Network Security · IronNet

Details

IDIronDefense
ProviderIronNet Cybersecurity
CategoryNetwork Security
From Version5.0.0
Docker Imagedemisto/python3:3.12.13.10116658
Supported ModulesAgentix XSIAM

README

IronDefense gives users the ability to rate alerts, update alert statuses, add comments to alerts, and to report observed bad activity.

 

Configure IronDefense on XSOAR

  1. Navigate to Settings > Integrations  > Servers & Services.
  2. Search for IronDefense.
  3. Click Add instance to create and configure a new integration instance.
    • Name: a textual name for the integration instance.
    • IronAPI Host/IP
    • IronAPI Port
    • Username
    • Request Timeout (Sec)
  4. Click Test to validate the new instance.

Commands

You can execute these commands from the 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. Rate an alert: irondefense-rate-alert
  2. Add a comment to an alert: irondefense-comment-alert
  3. Set the status of an alert: irondefense-set-alert-status
  4. Submit an observed bad endpoint to create Threat Intelligence Rules (TIR): irondefense-report-observed-bad-activity

1. Rate an alert


Rates an IronDefense alert.

Base Command

irondefense-rate-alert

Input
Argument Name Description Required
alert_id The ID of the IronDefense alert. Required
severity The severity rating of the alert. Can be: "Undecided", "Benign", "Suspicious", "Malicious". Required
expectation Determines whether the rating was expected. Can be: "Unknown", "Expected", "Unexpected". Use "Unknown" if the rating is undecided. Required
comments Explains the rating of the alert. Required
share_comment_with_irondome Whether to share the comment with IronDome. Required

 

Context Output

There are no context output for this command.

 

2. Add a comment to an alert


Adds a comment to an IronDefense alert.

Base Command

irondefense-comment-alert

Input
Argument Name Description Required
alert_id The ID of the IronDefense alert. Required
comment Explains the rating of the alert. Required
share_comment_with_irondome Whether to share the comment with IronDome. Required

 

Context Output

There are no context output for this command.

 

3. Set the status of an alert


Sets the status of an IronDefense alert.

Base Command

irondefense-set-alert-status

Input
Argument Name Description Required
alert_id The ID of the IronDefense alert. Required
status The alert status to set. Can be: "Awaiting Review", "Under Review", "Closed". Required
comments Explains the status of the alert. Required
share_comment_with_irondome Whether to share the comment with IronDome. Required

 

Context Output

There are no context output for this command.

 

4. Submit an observed bad endpoint to create Threat Intelligence Rules (TIR)


Submits an observed bad endpoint to IronDefense to create Threat Intelligence Rules (TIR).

Base Command

irondefense-report-observed-bad-activity

Input
Argument Name Description Required
name The name of the Threat Intelligence Rule (TIR) to be created. Required
description A description of the observed bad endpoint. Required
ip The IP address of the observed bad endpoint. Optional
domain The domain name of the observed bad endpoint. Optional
activity_start_time The start time of the observed bad activity in RFC 3339 format. Required
activity_end_time The end time of the observed bad activity in RFC 3339 format. Required

 

Context Output

There are no context output for this command.

  

irondefense-get-event


Retrieves an IronDefense event.

Base Command

irondefense-get-event

Input

Argument Name Description Required
event_id The ID of the IronDefense event. Required

Context Output

Path Type Description
IronDefense.Event.id String The event ID.
IronDefense.Event.alert_id String The associated alert ID.
IronDefense.Event.category String The event category.
IronDefense.Event.sub_category String The event sub category.
IronDefense.Event.severity Number The event severity (0-1000).
IronDefense.Event.confidence Number The event confidence (real number between 0-1).
IronDefense.Event.created Date Time the event was created.
IronDefense.Event.updated Date Time the event was last updated.
IronDefense.Event.start_time Date The start time of this event’s activity.
IronDefense.Event.end_time Date The end time of this event’s activity.
IronDefense.Event.iron_dome_shared_time Date The time when the event was sent to IronDome - not present if not shared with irondome.
IronDefense.Event.is_whitelisted Boolean True if the event activity was whitelisted, false otherwise.
IronDefense.Event.is_blacklisted Boolean True if the event activity was blacklisted, false otherwise.
IronDefense.Event.src_ip String The source IP associated with this event.
IronDefense.Event.dst_ip String The destination IP associated with this event.
IronDefense.Event.dst_port Number The destination port associated with this event.
IronDefense.Event.ppp_domains String Any domains associated with this event.
IronDefense.Event.primary_app_protocol String The primary application protocol associated with this event.
IronDefense.Event.secondary_app_protocol String The secondary application protocol associated with this event.
IronDefense.Event.bytes_in Number The byte count of incoming traffic for this event.
IronDefense.Event.bytes_out Number The byte count of outgoing traffic for this event.
IronDefense.Event.total_bytes Number The byte count of the total traffic (in either direction) for this event.
IronDefense.Event.url String The related URL for this event, if applicable.
IronDefense.Event.raw_data_formats String The list of distinct raw data formats for this event.
IronDefense.Event.src_entity_attribute String The source entity attribute related to this event, if one could be determined.
IronDefense.Event.src_entity_attribute_type String The source entity attribute type related to this event, if one could be determined.
IronDefense.Event.dst_entity_attribute String The destination entity attribute related to this event, if one could be determined.
IronDefense.Event.dst_entity_attribute_type String The destination entity attribute type related to this event, if one could be determined.
IronDefense.Event.vue_url String The url for displaying the event within IronVUE.
IronDefense.Event.Context.name String The name of the context table.
IronDefense.Event.Context.columns String Column values for the context table.

irondefense-get-alerts


Pulls Alerts from IronDefense.

Base Command

irondefense-get-alerts

Input

Argument Name Description Required
alert_id The list of Alert IDs to filter by. Optional
category The list of Alert categories to filter by. Possible values are: C2, Action, Access, Recon, Other. Optional
sub_category The list of Alert sub categories to filter by. Optional
status The list of Alert status to filter by. Possible values are: Awaiting Review, Under Review, Closed. Optional
analyst_severity The list of Alert analyst severity to filter by. Possible values are: Undecided, Benign, Suspicious, Malicious. Optional
analyst_expectation The list of Alert analyst expectation to filter by. Possible values are: Unknown, Expected, Unexpected. Optional
min_severity The minimum Alert severity to filter by. (0-1000). Optional
max_severity The maximum Alert severity to filter by. (0-1000). Optional
min_created The minimum Alert created date to filter by in RFC 3339 format (E.g. 2017-10-13T07:20:50.52Z). Optional
max_created The maximum Alert created date to filter by in RFC 3339 format (E.g. 2017-10-13T07:20:50.52Z). Optional
min_updated The minimum Alert updated date to filter by in RFC 3339 format (E.g. 2017-10-13T07:20:50.52Z). Optional
max_updated The maximum Alert updated date to filter by in RFC 3339 format (E.g. 2017-10-13T07:20:50.52Z). Optional
min_first_event_created The minimum Alert first event created date to filter by in RFC 3339 format (E.g. 2017-10-13T07:20:50.52Z). Optional
max_first_event_created The maximum Alert first event created date to filter by in RFC 3339 format (E.g. 2017-10-13T07:20:50.52Z). Optional
min_last_event_created The minimum Alert last event created date to filter by in RFC 3339 format (E.g. 2017-10-13T07:20:50.52Z). Optional
max_last_event_created The maximum Alert last event created date to filter by in RFC 3339 format (E.g. 2017-10-13T07:20:50.52Z). Optional
min_first_event_start_time The minimum Alert first event start date to filter by in RFC 3339 format (E.g. 2017-10-13T07:20:50.52Z). Optional
max_first_event_start_time The maximum Alert first event start date to filter by in RFC 3339 format (E.g. 2017-10-13T07:20:50.52Z). Optional
min_last_event_end_time The minimum Alert last event end date to filter by in RFC 3339 format (E.g. 2017-10-13T07:20:50.52Z). Optional
max_last_event_end_time The maximum Alert last event end date to filter by in RFC 3339 format (E.g. 2017-10-13T07:20:50.52Z). Optional
analytic_version The list of Alert analytic versions to filter by. Optional
limit The limit on the number of Alerts to be returned. Optional
offset The number of results to skip - used for paging the results. Optional
sort The list of Alert fields to sort by. Optional

Context Output

Path Type Description
IronDefense.Alert.id string The ID of the alert.
IronDefense.Alert.category string The category of the alert.
IronDefense.Alert.sub_category string The subcategory of the alert.
IronDefense.Alert.severity Number The severity score of the alert. (0-1000)
IronDefense.Alert.status String The status of the alert.
IronDefense.Alert.analyst_severity String The analyst severity of the alert.
IronDefense.Alert.analyst_expectation String The analyst expectation of the alert.
IronDefense.Alert.created Date Time the alert was created in IronDefense.
IronDefense.Alert.updated Date Time the alert was last updated in IronDefense.
IronDefense.Alert.event_count Number The number of non-whitelisted events associated with this alert.
IronDefense.Alert.FirstEventCreated String The earliest created date of any associated event on this alert.
IronDefense.Alert.last_event_created String The last created date of any associated event on this alert.
IronDefense.Alert.raw_data_format String List of distinct raw data formats for this event.
IronDefense.Alert.aggregation_criteria String Criteria used to build alert, specific to the event context fields.
IronDefense.Alert.vue_url String The url for displaying the alert within IronVUE.
IronDefense.Query.GetAlerts.limit Number The maximum number of results that were requested to be returned.
IronDefense.Query.GetAlerts.offset Number The number of results that were skipped - used for paging the results.
IronDefense.Query.GetAlerts.total Number The total number of results possible from the query.

irondefense-get-alert-irondome-information


Retrieves IronDome information for an IronDefense alert.

Base Command

irondefense-get-alert-irondome-information

Input

Argument Name Description Required
alert_id The ID of the IronDefense alert. Required

Context Output

Path Type Description
IronDome.Correlations.alert_id String The alert ID the correlations are associated with.
IronDome.Correlations.dome_tag String The IronDome tag.
IronDome.Correlations.correlation.correlations.ip String The IP correlated on (if an IP correlation).
IronDome.Correlations.correlation.correlations.domain String The Domain correlated on (if a Domain correlation).
IronDome.Correlations.correlation.correlations.behavior Boolean True if the correlation was behavior-based.
IronDome.Correlations.correlation.correlations.enterprise_correlations Number The number of enterprise correlations.
IronDome.Correlations.correlation.correlations.community_correlations Number The number of community correlations.
IronDome.CorrelationParticipation.alert_id String The alert ID the correlation participants are associated with.
IronDome.CorrelationParticipation.correlation_participation.dome_tag String The IronDome tag.
IronDome.CorrelationParticipation.correlation_participation.behavior.malicious_count Number Count of malicious ratings.
IronDome.CorrelationParticipation.correlation_participation.behavior.suspicious_count Number Count of suspicious ratings.
IronDome.CorrelationParticipation.correlation_participation.behavior.benign_count Number Count of benign ratings.
IronDome.CorrelationParticipation.correlation_participation.behavior.whitelisted_count Number Count of whitelisted ratings.
IronDome.CorrelationParticipation.correlation_participation.behavior.comments_count Number Count of comments made.
IronDome.CorrelationParticipation.correlation_participation.behavior.activity_count Number Count of activity.
IronDome.CorrelationParticipation.correlation_participation.behavior.resource_owner Boolean True if the caller is the resource owner.
IronDome.CorrelationParticipation.correlation_participation.behavior.first_seen Date The time the activity was first seen.
IronDome.CorrelationParticipation.correlation_participation.behavior.last_seen Date The time the activity was last seen.
IronDome.CorrelationParticipation.correlation_participation.domain.malicious_count Number Count of malicious ratings.
IronDome.CorrelationParticipation.correlation_participation.domain.suspicious_count Number Count of suspicious ratings.
IronDome.CorrelationParticipation.correlation_participation.domain.benign_count Number Count of benign ratings.
IronDome.CorrelationParticipation.correlation_participation.domain.whitelisted_count Number Count of whitelisted ratings.
IronDome.CorrelationParticipation.correlation_participation.domain.comments_count Number Count of comments made.
IronDome.CorrelationParticipation.correlation_participation.domain.activity_count Number Count of activity.
IronDome.CorrelationParticipation.correlation_participation.domain.resource_owner Boolean True if the caller is the resource owner.
IronDome.CorrelationParticipation.correlation_participation.domain.first_seen Date The time the activity was first seen.
IronDome.CorrelationParticipation.correlation_participation.domain.last_seen Date The time the activity was last seen.
IronDome.CorrelationParticipation.correlation_participation.ip.malicious_count Number Count of malicious ratings.
IronDome.CorrelationParticipation.correlation_participation.ip.suspicious_count Number Count of suspicious ratings.
IronDome.CorrelationParticipation.correlation_participation.ip.benign_count Number Count of benign ratings.
IronDome.CorrelationParticipation.correlation_participation.ip.whitelisted_count Number Count of whitelisted ratings.
IronDome.CorrelationParticipation.correlation_participation.ip.comments_count Number Count of comments made.
IronDome.CorrelationParticipation.correlation_participation.ip.activity_count Number Count of activity.
IronDome.CorrelationParticipation.correlation_participation.ip.resource_owner Boolean True if the caller is the resource owner.
IronDome.CorrelationParticipation.correlation_participation.ip.first_seen Date The time the activity was first seen.
IronDome.CorrelationParticipation.correlation_participation.ip.last_seen Date The time the activity was last seen.
IronDome.CommunityComments.alert_id String The alert ID associated with the community comments.
IronDome.CommunityComments.community_comments.created Date The time that the comment was created.
IronDome.CommunityComments.community_comments.comment String The comment text.
IronDome.CommunityComments.community_comments.dome_tags String The IronDome tags related to the comment.
IronDome.CommunityComments.community_comments.enterprise Boolean True if enterprise.
IronDome.CommunityComments.community_comments.self Boolean True if the comment was made by the caller.
IronDome.CognitiveSystemScore.alert_id String The alert ID associated with the cognitive system score.
IronDome.CognitiveSystemScore.cognitive_system_score Number The cognitive system score of the alert.
IronDome.Notification.alert_id String The alert ID associated with the IronDome notification.
IronDome.Notification.dome_notification.id String The unique ID of the notification.
IronDome.Notification.dome_notification.category String The category of Dome notification.
IronDome.Notification.dome_notification.created Date The category of Dome notification.
IronDome.Notification.dome_notification.dome_tags String The IronDome tags related to this notification.
IronDome.Notification.dome_notification.alert_ids String The IDs of the alerts related to this notification.
IronDome.Notification.dome_notification.mismatch_details.enterprise_severities.analyst_severity String The mismatched severity across correlated alerts within the enterprise.
IronDome.Notification.dome_notification.mismatch_details.enterprise_severities.count Number The count of mismatched severity with this analyst rating.
IronDome.Notification.dome_notification.mismatch_details.community_severities.analyst_severity String The mismatched severity across correlated alerts within the community.
IronDome.Notification.dome_notification.mismatch_details.community_severities.count Number The count of mismatched severity with this analyst rating.
IronDome.Notification.dome_notification.severity_details.analyst_severity String The analyst severity of the alert.
IronDome.Notification.dome_notification.comment_details.comment String The comment text in the notification.
IronDome.Notification.dome_notification.severity_suspicious_details.domains String The list of domains correlated on a suspicious alert.
IronDome.Notification.dome_notification.severity_suspicious_details.ips String The list of IPs correlated on a suspicious alert.
IronDome.Notification.dome_notification.severity_suspicious_details.comments String The comments about this suspicious alert correlation.
IronDome.Notification.dome_notification.severity_malicious_details.domains String The list of domains correlated on a malicious alert.
IronDome.Notification.dome_notification.severity_malicious_details.ips String The list of IPs correlated on a malicious alert.
IronDome.Notification.dome_notification.severity_malicious_details.comments String The comments about this malicious alert correlation.
IronDome.Notification.dome_notification.severity_malicious_details.generated_threat_intel_rules.ip String The IP of a Threat Intelligence rule that was automatically created based on an IronDome correlation.
IronDome.Notification.dome_notification.severity_malicious_details.generated_threat_intel_rules.domain String The domain of a Threat Intelligence rule that was automatically created based on an IronDome correlation.
IronDome.Notification.dome_notification.severity_malicious_details.generated_threat_intel_rules.rule_id String The rule ID of a Threat Intelligence rule that was automatically created based on an IronDome correlation.
IronDome.Notification.dome_notification.severity_malicious_details.generated_threat_intel_rules.vue_url String The IronVUE URL of a Threat Intelligence rule that was automatically created based on an IronDome correlation.
IronDome.Notification.dome_notification.high_cognitive_system_score_details.enterprise_alert_score Number The alert score for the enterprise.
IronDome.Notification.dome_notification.high_cognitive_system_score_details.cognitive_system_score Number The cognitive system score.

irondefense-get-events-from-alert


Retrieves IronDefense Events for a given Alert ID.

Base Command

irondefense-get-events-from-alert

Input

Argument Name Description Required
alert_id The ID of the IronDefense alert to retrieve events for. Required
limit The limit on the number of Events to be returned. Optional
offset The number of results to skip - used for paging the results. Optional

Context Output

Path Type Description
IronDefense.Event.id String The event ID.
IronDefense.Event.alert_id String The associated alert ID.
IronDefense.Event.category String The event category.
IronDefense.Event.sub_category String The event sub category.
IronDefense.Event.severity Number The event severity (0-1000).
IronDefense.Event.confidence Number The event confidence (real number between 0-1).
IronDefense.Event.created Date Time the event was created.
IronDefense.Event.updated Date Time the event was last updated.
IronDefense.Event.start_time Date The start time of this event’s activity.
IronDefense.Event.end_time Date The end time of this event’s activity.
IronDefense.Event.iron_dome_shared_time Date The time when the event was sent to IronDome - not present if not shared with irondome.
IronDefense.Event.is_whitelisted Boolean True if the event activity was whitelisted, false otherwise.
IronDefense.Event.is_blacklisted Boolean True if the event activity was blacklisted, false otherwise.
IronDefense.Event.src_ip String The source IP associated with this event.
IronDefense.Event.dst_ip String The destination IP associated with this event.
IronDefense.Event.dst_port Number The destination port associated with this event.
IronDefense.Event.app_domains String Any domains associated with this event.
IronDefense.Event.primary_app_protocol String The primary application protocol associated with this event.
IronDefense.Event.secondary_app_protocol String The secondary application protocol associated with this event.
IronDefense.Event.bytes_in Number The byte count of incoming traffic for this event.
IronDefense.Event.bytes_out Number The byte count of outgoing traffic for this event.
IronDefense.Event.total_bytes Number The byte count of the total traffic (in either direction) for this event.
IronDefense.Event.url String The related URL for this event, if applicable.
IronDefense.Event.raw_data_formats String The list of distinct raw data formats for this event.
IronDefense.Event.src_entity_attribute String The source entity attribute related to this event, if one could be determined.
IronDefense.Event.src_entity_attribute_type String The source entity attribute type related to this event, if one could be determined.
IronDefense.Event.dst_entity_attribute String The destination entity attribute related to this event, if one could be determined.
IronDefense.Event.dst_entity_attribute_type String The destination entity attribute type related to this event, if one could be determined.
IronDefense.Event.vue_url String The url for displaying the event within IronVUE.
IronDefense.Query.GetEvents.limit Number The maximum number of results that were requested to be returned.
IronDefense.Query.GetEvents.offset Number The number of results that were skipped - used for paging the results.
IronDefense.Query.GetEvents.total Number The total number of results possible from the query.

Configuration parameters

  • ironAPIHost — IronAPI Host/IP (required)
  • ironAPIPort — IronAPI Port (required)
  • credentials — Username (required)
  • requestTimeout — Request Timeout (Sec)
  • isFetch — Fetch incidents
  • incidentType — Incident type
  • incidentFetchInterval — Incidents Fetch Interval
  • enableAlertNotifications — Enable Alert Notification Ingestion (Refresh Not Supported)
  • alertLimit — Alert Notification Limit Per Request. (Max 500)
  • alertCategories — Alert Notification Categories to Exclude
  • alertSubCategories — Alert Notification SubCategories to Exclude, See https://ironnet.atlassian.net/wiki/x/hADvGg for values (Enter in comma separated list format)
  • alertSeverityLower — Lower Bound of Severity for Alert Notification (0-1000)
  • alertSeverityUpper — Upper Bound of Severity for Alert Notification (0-1000)
  • alertActions — Alert Notification Actions to Include (Alert Created enabled by default)
  • enableEventNotifications — Enable Event Notification Ingestion (Refresh Not Supported)
  • eventLimit — Event Notification Limit Per Request. (Max 500)
  • eventCategories — Event Notification Categories to Exclude
  • eventSubCategories — Event Notification SubCategories to Exclude, See https://ironnet.atlassian.net/wiki/x/hADvGg for values (Enter in comma separated list format)
  • eventSeverityLower — Lower Bound of Severity for Event Notification (0-1000)
  • eventSeverityUpper — Upper Bound of Severity for Event Notification (0-1000)
  • eventActions — Event Notification Actions to Include (Event Created enabled by default)
  • enableDomeNotifications — Enable Dome Notification Ingestion (Refresh Not Supported)
  • domeLimit — Dome Notification Limit Per Request. (Max 500)
  • domeCategories — Dome Notification Types to Exclude

Commands (8)

  • irondefense-comment-alert

    Adds a comment to an IronDefense alert.

  • irondefense-get-alert-irondome-information

    Retrieves IronDome information for an IronDefense alert.

  • irondefense-get-alerts

    Pulls Alerts from IronDefense.

  • irondefense-get-event

    Retrieves an IronDefense event.

  • irondefense-get-events-from-alert

    Retrieves IronDefense Events for a given Alert ID.

  • irondefense-rate-alert

    Rates an IronDefense alert.

  • irondefense-report-observed-bad-activity

    Submits an observed bad endpoint to IronDefense to create Threat. Intelligence Rules (TIR).

  • irondefense-set-alert-status

    Sets the status of an IronDefense alert.

import demistomock as demisto  # noqa: F401
from CommonServerPython import *  # noqa: F401
import functools
from http.client import HTTPException

import urllib3

# Disable insecure warnings
urllib3.disable_warnings()


class XsoarLogger:
    """Wrapper class to add a prefix to all logging statements so they can be located more easily in the logs"""

    def __init__(self, d, log_prefix=""):
        self.demisto = d
        self.log_prefix = log_prefix

    def debug(self, msg):
        demisto.debug(self.log_prefix + msg)

    def error(self, msg):
        demisto.error(self.log_prefix + msg)


class IronDefense:
    """Main class for performing plugin actions"""

    def __init__(self, demisto, session, host, port, credentials, logger, request_timeout=60.0):
        self.demisto = demisto
        self.session = session
        self.host = host
        self.port = port
        self.base_url = f"https://{host}:{port}/IronApi"
        self.credentials = credentials
        self.request_timeout = request_timeout
        self.logger = logger

        self.session.headers.update({"Content-Type": "application/json"})
        self._configure_session_auth(self.demisto.getIntegrationContext())

    """ HELPER FUNCTIONS """

    def _get_jwt(self, context):
        if context is None:
            return None

        try:
            return context.get("JWT")
        except KeyError:
            return None

    def _configure_session_auth(self, data):
        self.logger.debug("Getting jwt...")

        jwt = self._get_jwt(data)
        if jwt:
            # set the auth token if it exists
            self.session.headers.update({"Authorization": "Bearer " + jwt})

    def _http_request(self, method, uri, body="{}", headers={}, params={}, auth=None, files=None):
        # Makes an API call with the given arguments
        resp = self.session.request(
            method,
            self.base_url + uri,
            data=body,
            headers=headers,
            verify=False,
            params=params,
            files=files,
            timeout=self.request_timeout,
            auth=auth,
        )
        if resp.status_code == 401:
            if auth is not None:
                # incorrect creds have been provided
                return resp

            # the session has expired so we need to log in again
            self.logger.debug("Login required!")

            username = self.credentials.get("identifier")
            password = self.credentials.get("password")

            # retry the original request with basic auth credentials
            return self._http_request(method, uri, body=body, params=params, files=files, auth=(username, password))
        elif resp.status_code >= 500:
            self.logger.error("A server error has occurred. The response is: " + json.dumps(resp.json()))

        if auth is not None:
            # persist the jwt
            jwt = resp.headers.get("auth-token")
            self.demisto.setIntegrationContext({"JWT": jwt})

        return resp

    def _get_error_msg_from_response(self, resp):
        err_msg = resp.json().get("msg")
        if err_msg is None:
            err_msg = resp.text
        return err_msg

    def event_context_table_to_dict_list(self, event_context_table):
        # convert context from column format to row format for display
        num_rows = functools.reduce(
            lambda acc, col: len(col.get("values")) if len(col.get("values")) > acc else acc,
            event_context_table.get("columns"),
            0,
        )
        new_table_data: list[dict[str, str]] = [{} for row in range(num_rows)]

        for column in event_context_table.get("columns"):
            # add the column to the table
            for row in range(num_rows):
                # check if the column has the row data
                if len(column.get("values")) - row > 0:
                    # insert column data
                    val = [
                        str(list(d.values())[0]) if len(list(d.values())) > 0 else ""
                        for d in column.get("values")[row].get("data")
                    ]

                    table_row = new_table_data[row]
                    column_name = column.get("name")
                    table_row[column_name] = ",".join(val)
                else:
                    # This column is missing data so just insert an empty string
                    new_table_data[row][column.get("name")] = ""

        return new_table_data

    def event_context_table_to_dict(self, event_context_table):
        table_data = {}
        for column in event_context_table.get("columns"):
            val = [str(list(d.values())[0]) if len(list(d.values())) > 0 else "" for d in column.get("values")[0].get("data")]
            table_data[column.get("name")] = ",".join(val)

        return table_data

    def event_context_table_contains_multi_columns(self, event_context_table):
        return any(len(column.get("values")) > 1 for column in event_context_table.get("columns"))

    def create_markdown_link(self, link_text, url):
        return f"[{link_text}]({url})"

    def create_dome_markdown_link(self, link_text, alert_id):
        url = f"https://{self.host}/alerts/irondome?filter=alertId%3D%3D{alert_id}"
        return f"[{link_text}]({url})"

    """MAIN FUNCTIONS"""

    def fetch_dome_incidents(self, dome_categories=None, dome_limit=200):
        self.logger.debug("Fetching Dome incidents...")
        res = []
        if dome_categories is not None:
            dome_cats = ["DNC_" + str(cat).replace(" ", "_").upper() for cat in dome_categories]
        else:
            dome_cats = []

        req_body = json.dumps({"limit": dome_limit})

        resp = self._http_request("POST", "/GetDomeNotifications", body=req_body)
        if resp.ok:
            # Filter notifications
            notifs = resp.json()
            self.logger.debug("json response is: " + json.dumps(resp.json()))
            for n in notifs["dome_notifications"]:
                if n["category"] not in dome_cats:
                    n["type"] = "dome"
                    notif = {
                        "name": str(n["category"]) + " IronDome Notification",
                        "details": "Received a {} IronDome Notification at {} from communities {}.".format(
                            n["category"], str(datetime.now()), n["dome_tags"]
                        ),
                        "occurred": n["created"],
                        "rawJSON": json.dumps(n),
                    }
                    res.append(notif)
        else:
            raise Exception("Fetch for DomeNotifications failed. Status code was " + str(resp.status_code))

        self.logger.debug(f"{len(res)} Dome incident(s) fetched")
        return res

    def fetch_alert_incidents(
        self,
        alert_categories=None,
        alert_subcategories=None,
        alert_severity_lower=None,
        alert_severity_upper=None,
        alert_limit=200,
        alert_actions=None,
    ):
        self.logger.debug("Fetching Alert incidents...")
        res = []

        if alert_categories is not None:
            alert_cats = [str(cat).replace(" ", "_").upper() for cat in alert_categories]
        else:
            alert_cats = []

        if alert_subcategories is not None:
            asc = alert_subcategories.split(",")
            alert_subcats = [str(subcat).replace(" ", "_").upper() for subcat in asc]
        else:
            alert_subcats = []

        if alert_actions is None or len(alert_actions) == 0:
            alert_actions_to_ingest = ["ANA_ALERT_CREATED"]
        else:
            alert_actions_to_ingest = ["ANA_" + str(alert_action).replace(" ", "_").upper() for alert_action in alert_actions]

        alert_sev_lower = int(alert_severity_lower) if alert_severity_lower is not None else 0
        alert_sev_upper = int(alert_severity_upper) if alert_severity_upper is not None else 1000

        req_body = json.dumps({"limit": alert_limit})

        resp = self._http_request("POST", "/GetAlertNotifications", body=req_body)
        if resp.ok:
            # Filter notifications
            notifs = resp.json()
            self.logger.debug("json response is: " + json.dumps(resp.json()))
            for alert_notification in notifs["alert_notifications"]:
                if alert_notification["alert"]:
                    alert = alert_notification["alert"]
                    action = alert_notification["alert_action"]
                    if (
                        alert["category"] not in alert_cats
                        and alert["sub_category"] not in alert_subcats
                        and alert_sev_lower <= int(alert["severity"]) <= alert_sev_upper
                        and action in alert_actions_to_ingest
                    ):
                        alert["type"] = "alert"
                        notif = {
                            "name": str(alert_notification["alert_action"]) + " Alert Notification",
                            "details": "Received a {} Alert Notification at {}.".format(
                                alert_notification["alert_action"], str(datetime.now())
                            ),
                            "occurred": alert["updated"],
                            "rawJSON": json.dumps(alert),
                        }
                        res.append(notif)
        else:
            raise Exception("Fetch for AlertNotifications failed. Status code was " + str(resp.status_code))

        self.logger.debug(f"{len(res)} Alert incident(s) fetched")
        return res

    def fetch_event_incidents(
        self,
        event_categories=None,
        event_subcategories=None,
        event_severity_lower=None,
        event_severity_upper=None,
        event_limit=200,
        event_actions=None,
    ):
        self.logger.debug("Fetching Event incidents...")
        res = []

        if event_categories is not None:
            event_cats = [str(cat).replace(" ", "_").upper() for cat in event_categories]
        else:
            event_cats = []

        if event_subcategories is not None:
            esc = event_subcategories.split(",")
            event_subcats = [str(subcat).replace(" ", "_").upper() for subcat in esc]
        else:
            event_subcats = []

        if event_actions is None or len(event_actions) == 0:
            event_actions_to_ingest = ["ENA_EVENT_CREATED"]
        else:
            event_actions_to_ingest = ["ENA_" + str(event_action).replace(" ", "_").upper() for event_action in event_actions]

        event_sev_lower = int(event_severity_lower) if event_severity_lower is not None else 0
        event_sev_upper = int(event_severity_upper) if event_severity_upper is not None else 1000

        req_body = json.dumps({"limit": event_limit})

        resp = self._http_request("POST", "/GetEventNotifications", body=req_body)
        if resp.ok:
            # Filter notifications
            notifs = resp.json()
            self.logger.debug("json response is: " + json.dumps(resp.json()))
            for event_notification in notifs["event_notifications"]:
                if event_notification["event"]:
                    event = event_notification["event"]
                    action = event_notification["event_action"]
                    if (
                        event["category"] not in event_cats
                        and event["sub_category"] not in event_subcats
                        and event_sev_lower <= int(event["severity"]) <= event_sev_upper
                        and action in event_actions_to_ingest
                    ):
                        event["type"] = "event"
                        notif = {
                            "name": str(event_notification["event_action"]) + " Event Notification",
                            "details": "Received a {} Event Notification at {}.".format(
                                event_notification["event_action"], str(datetime.now())
                            ),
                            "occurred": event["updated"],
                            "rawJSON": json.dumps(event),
                        }
                        res.append(notif)
        else:
            raise Exception("Fetch for EventNotifications failed. Status code was " + str(resp.status_code))

        self.logger.debug(f"{len(res)} Event incident(s) fetched")
        return res

    def test_module(self):
        self.logger.debug("Testing module...")
        username = self.credentials.get("identifier")
        password = self.credentials.get("password")
        resp = self._http_request("POST", "/Login", auth=(username, password))
        if resp.status_code == 200:
            self.logger.debug("Success!")
            return "ok"
        else:
            return "Test failed ({}): {}".format(str(resp.status_code), resp.json()["msg"])

    def update_analyst_ratings(
        self, alert_id, severity="SEVERITY_UNDECIDED", expectation="EXP_UNKNOWN", comments="", share_irondome=False
    ):
        self.logger.debug(
            f"Submitting analyst rating: Alert ID={alert_id} Severity={severity} Expected={expectation} "
            f"Comments={comments} Share w/IronDome={share_irondome}"
        )

        req_body = {
            "alert_id": alert_id,
            "analyst_severity": "SEVERITY_" + severity.upper(),
            "analyst_expectation": "EXP_" + expectation.upper(),
            "comment": comments,
            "share_comment_with_irondome": share_irondome,
        }
        response = self._http_request("POST", "/RateAlert", body=json.dumps(req_body))
        if response.status_code != 200:
            err_msg = self._get_error_msg_from_response(response)
            self.logger.error(
                f"Failed to rate alert ({alert_id}). The response failed with status code {response.status_code}."
                f" The response was: {response.text}"
            )
            raise HTTPException(f"Failed to rate alert {alert_id} ({response.status_code}): {err_msg}")
        else:
            self.logger.debug(f"Successfully submitted rating for alert ({alert_id})")
            return "Submitted analyst rating to IronDefense!"

    def add_comment_to_alert(self, alert_id, comment="", share_irondome=False):
        self.logger.debug(f"Submitting comment: Alert ID={alert_id} Comment={comment} Share w/IronDome={share_irondome}")

        req_body = {"alert_id": alert_id, "comment": comment, "share_comment_with_irondome": share_irondome}
        response = self._http_request("POST", "/CommentOnAlert", body=json.dumps(req_body))
        if response.status_code != 200:
            err_msg = self._get_error_msg_from_response(response)
            self.logger.error(
                f"Failed to add comment to alert ({alert_id}). The response failed with status code {response.status_code}."
                f" The response was: {response.text}"
            )
            raise HTTPException(f"Failed to add comment to alert {alert_id} ({response.status_code}): {err_msg}")
        else:
            self.logger.debug(f"Successfully added comment to alert ({alert_id})")
            return "Submitted comment to IronDefense!"

    def set_alert_status(self, alert_id, status="STATUS_NONE", comments="", share_irondome=False):
        self.logger.debug(
            f"Submitting status: Alert ID={alert_id} Status={status} Comments={comments} Share w/IronDome={share_irondome}"
        )

        req_body = {
            "alert_id": alert_id,
            "status": "STATUS_" + status.upper().replace(" ", "_"),
            "comment": comments,
            "share_comment_with_irondome": share_irondome,
        }
        response = self._http_request("POST", "/SetAlertStatus", body=json.dumps(req_body))
        if response.status_code != 200:
            err_msg = self._get_error_msg_from_response(response)
            self.logger.error(
                f"Failed to set status for alert ({alert_id}). The response failed with status code {response.status_code}."
                f" The response was: {response.text}"
            )
            raise HTTPException(f"Failed to set status for alert {alert_id} ({response.status_code}): {err_msg}")
        else:
            self.logger.debug(f"Successfully submitted status for alert ({alert_id})")
            return "Submitted status to IronDefense!"

    def report_observed_bad_activity(
        self,
        name,
        description="",
        ip="",
        domain="",
        activity_start_time="1970-01-01T00:00:00Z",
        activity_end_time="1970-01-01T00:00:00Z",
    ):
        self.logger.debug(
            f"Submitting observed bad activity: Name={name} Description={description} IP={ip} Domain={domain} "
            f"Activity Start Time={activity_start_time} Activity End Time={activity_end_time}"
        )

        req_body = {
            "name": name,
            "description": description,
            "ip": ip,
            "domain": domain,
            "activity_start_time": activity_start_time,
            "activity_end_time": activity_end_time,
        }
        response = self._http_request("POST", "/ReportObservedBadActivity", body=json.dumps(req_body))
        if response.ok:
            self.logger.debug(f"Successfully submitted observed bad activity for IP={ip} and Domain={domain}")
            return "Submitted observed bad activity to IronDefense!"
        else:
            err_msg = self._get_error_msg_from_response(response)
            self.logger.error(
                f"Failed to submit observed bad activity for IP={ip} and Domain={domain}. The response failed with"
                f" status code {response.status_code}. The response was: {response.text}"
            )
            raise HTTPException(
                f"Failed to submit observed bad activity for IP={ip} and Domain={domain} ({response.status_code}): {err_msg}"
            )

    def get_event(self, event_id):
        self.logger.debug(f"Retrieving Event: Event ID={event_id}")

        req_body = {
            "event_id": event_id,
        }
        response = self._http_request("POST", "/GetEvent", body=json.dumps(req_body))
        if response.status_code != 200:
            err_msg = self._get_error_msg_from_response(response)
            self.logger.error(
                f"Failed to retrieve event with ID ({event_id}). The response failed with status code {response.status_code}."
                f" The response was: {response.text}"
            )
            raise HTTPException(f"Failed to retrieve event with ID {event_id} ({response.status_code}): {err_msg}")
        else:
            self.logger.debug(f"Successfully retrieved event ({event_id})")
            return response.json()

    def get_events(self, alert_id, limit=None, offset=None):
        self.logger.debug(f"Retrieving Events: Alert ID={alert_id}, Limit={limit} Offset={offset}")

        req_body = {"alert_id": alert_id}

        constraint = {}
        if limit is not None and limit != "":
            constraint["limit"] = int(limit)
        if offset is not None and offset != "":
            constraint["offset"] = int(offset)
        req_body["constraint"] = constraint

        response = self._http_request("POST", "/GetEvents", body=json.dumps(req_body))
        if response.status_code != 200:
            err_msg = self._get_error_msg_from_response(response)
            self.logger.error(
                f"Failed to retrieve events with alert ID ({alert_id}). The response failed with status code"
                f" {response.status_code}. "
                f"The response was: {response.text}"
            )
            raise HTTPException(f"Failed to retrieve event with ID {alert_id} ({response.status_code}): {err_msg}")
        else:
            self.logger.debug(f"Successfully retrieved events for alert ({alert_id})")
            events = response.json()
            return events

    def get_alerts(
        self,
        alert_id=None,
        category=None,
        sub_category=None,
        status=None,
        analyst_severity=None,
        analyst_expectation=None,
        min_severity=None,
        max_severity=None,
        min_created=None,
        max_created=None,
        min_updated=None,
        max_updated=None,
        min_first_event_created=None,
        max_first_event_created=None,
        min_last_event_created=None,
        max_last_event_created=None,
        min_first_event_start_time=None,
        max_first_event_start_time=None,
        min_last_event_end_time=None,
        max_last_event_end_time=None,
        analytic_version=None,
        limit=None,
        offset=None,
        sort=None,
    ):
        self.logger.debug(
            f"Getting alerts: AlertID={alert_id} Category={category} SubCategory={sub_category} Status={status}"
            f" AnalystSeverity={analyst_severity} "
            f"AnalystExpectation={analyst_expectation} MinSeverity={min_severity} MaxSeverity={max_severity} "
            f"MinCreated={min_created} MaxCreated= {max_created} MinUpdated={min_updated}"
            f"MaxUpdated={max_updated} MinFirstEventCreated={min_first_event_created} "
            f"MaxFirstEventCreated={max_first_event_created} MinLastEventCreated={min_last_event_created}"
            f"MaxLastEventCreated={max_last_event_created} MinFirstEventStartTime={min_first_event_start_time}"
            f" MaxFirstEventStartTime={max_first_event_start_time} MinLastEventEndTime={min_last_event_end_time}"
            f"MaxLastEventEndTime={max_last_event_end_time} AnalyticVersion={analytic_version} "
            f"Limit={limit} Offset={offset} sort={sort}"
        )

        req_body = {}
        if alert_id:
            req_body["alert_id"] = alert_id.split(",")
        if category:
            req_body["category"] = [str(cat).replace(" ", "_").upper() for cat in category.split(",")]
        if sub_category:
            req_body["sub_category"] = [str(sub_cat).replace(" ", "_").upper() for sub_cat in sub_category.split(",")]
        if status:
            req_body["status"] = ["STATUS_" + str(stat).replace(" ", "_").upper() for stat in status.split(",")]
        if analyst_severity:
            req_body["analyst_severity"] = [
                "SEVERITY_" + str(aseverity).replace(" ", "_").upper() for aseverity in analyst_severity.split(",")
            ]
        if analyst_expectation:
            req_body["analyst_expectation"] = [
                "EXP_" + str(aexpectation).replace(" ", "_").upper() for aexpectation in analyst_expectation.split(",")
            ]
        if analytic_version:
            req_body["analytic_version"] = analytic_version.split(",")
        if sort:
            req_body["sort"] = sort
        if min_severity is not None and min_severity != "" and max_severity is not None and max_severity != "":
            req_body["severity"] = {"lower_bound": int(min_severity), "upper_bound": int(max_severity)}
        if min_created and max_created:
            req_body["created"] = {"start": min_created, "end": max_created}
        if min_updated and max_updated:
            req_body["updated"] = {"start": min_updated, "end": max_updated}
        if min_first_event_created and max_first_event_created:
            req_body["first_event_created"] = {"start": min_first_event_created, "end": max_first_event_created}
        if min_last_event_created and max_last_event_created:
            req_body["last_event_created"] = {"start": min_last_event_created, "end": max_last_event_created}
        if min_first_event_start_time and max_first_event_start_time:
            req_body["first_event_start_time"] = {"start": min_first_event_start_time, "end": max_first_event_start_time}
        if min_last_event_end_time and max_last_event_end_time:
            req_body["last_event_end_time"] = {"start": min_last_event_end_time, "end": max_last_event_end_time}
        constraint = {}
        if limit is not None and limit != "":
            constraint["limit"] = int(limit)
        if offset is not None and offset != "":
            constraint["offset"] = int(offset)
        req_body["constraint"] = constraint

        response = self._http_request("POST", "/GetAlerts", body=json.dumps(req_body))
        if response.ok:
            self.logger.debug("Successfully retrieved alerts")
            return response.json()
        else:
            err_msg = self._get_error_msg_from_response(response)
            self.logger.error(
                f"Failed to retrieve alerts. The response failed with status code {response.status_code}."
                f" The response was: {err_msg}"
            )
            raise HTTPException(f"Failed to retrieve alerts ({response.status_code}): {err_msg}")

    def get_alert_irondome_information(self, alert_id):
        self.logger.debug(f"Retrieving Alert IronDome Information: Alert ID={alert_id}")

        req_body = {
            "alert_id": alert_id,
        }
        response = self._http_request("POST", "/GetAlertIronDomeInformation", body=json.dumps(req_body))
        if response.status_code != 200:
            err_msg = self._get_error_msg_from_response(response)
            self.logger.error(
                f"Failed to retrieve IronDome information for alert with ID ({alert_id}). The response failed "
                f"with status code {response.status_code}. The response was: {response.text}"
            )
            raise HTTPException(
                f"Failed to retrieve IronDome information for alert with ID {alert_id} ({response.status_code}): {err_msg}"
            )
        else:
            self.logger.debug(f"Successfully retrieved IronDome information for alert ({alert_id})")
            dome_alert_info = response.json()
            return dome_alert_info


""" COMMANDS MANAGER / SWITCH PANEL """


def fetch_incidents_command():
    # IronDome Notification related params
    dome_categories = PARAMS.get("domeCategories", None)  # pylint: disable=E0601
    dome_limit = int(PARAMS.get("domeLimit", 200))
    disable_dome_notifs = not PARAMS.get("enableDomeNotifications", False)
    # Alert Notification related params
    alert_categories = PARAMS.get("alertCategories", None)
    alert_subcategories = PARAMS.get("alertSubCategories", None)
    alert_severity_lower = PARAMS.get("alertSeverityLower", None)
    alert_severity_upper = PARAMS.get("alertSeverityUpper", None)
    alert_limit = int(PARAMS.get("alertLimit", 200))
    alert_actions = PARAMS.get("alertActions", None)
    disable_alert_notifs = not PARAMS.get("enableAlertNotifications", True)
    # Event Notification related params
    event_categories = PARAMS.get("eventCategories", None)
    event_subcategories = PARAMS.get("eventSubCategories", None)
    event_severity_lower = PARAMS.get("eventSeverityLower", None)
    event_severity_upper = PARAMS.get("eventSeverityUpper", None)
    event_limit = int(PARAMS.get("eventLimit", 200))
    event_actions = PARAMS.get("eventActions", None)
    disable_event_notifs = not PARAMS.get("enableEventNotifications", False)

    incidents: list = []
    if disable_dome_notifs and disable_alert_notifs and disable_event_notifs:
        LOGGER.debug("Ingestion of all notifications (Dome, Alert, Event) is disabled, not fetching")  # pylint: disable=E0601
    else:
        if disable_dome_notifs:
            LOGGER.debug("Ingestion of Dome Notifications is disabled")
        else:
            incs = IRON_DEFENSE.fetch_dome_incidents(dome_categories, dome_limit)  # pylint: disable=E0601
            incidents.extend(incs)
            # If the limit was reached, poll again
            poll_count = 1
            while len(incs) == dome_limit and poll_count < 10:
                incs = IRON_DEFENSE.fetch_dome_incidents(dome_categories, dome_limit)
                incidents.extend(incs)
                poll_count += 1

        if disable_alert_notifs:
            LOGGER.debug("Ingestion of Alert Notifications is disabled")
        else:
            incs = IRON_DEFENSE.fetch_alert_incidents(
                alert_categories, alert_subcategories, alert_severity_lower, alert_severity_upper, alert_limit, alert_actions
            )
            incidents.extend(incs)
            # If the limit was reached, poll again
            poll_count = 1
            while len(incs) == alert_limit and poll_count < 10:
                incs = IRON_DEFENSE.fetch_alert_incidents(
                    alert_categories, alert_subcategories, alert_severity_lower, alert_severity_upper, alert_limit, alert_actions
                )
                incidents.extend(incs)
                poll_count += 1

        if disable_event_notifs:
            LOGGER.debug("Ingestion of Event Notifications is disabled")
        else:
            incs = IRON_DEFENSE.fetch_event_incidents(
                event_categories, event_subcategories, event_severity_lower, event_severity_upper, event_limit, event_actions
            )
            incidents.extend(incs)
            # If the limit was reached, poll again
            poll_count = 1
            while len(incs) == event_limit and poll_count < 10:
                incs = IRON_DEFENSE.fetch_event_incidents(
                    event_categories, event_subcategories, event_severity_lower, event_severity_upper, event_limit, event_actions
                )
                incidents.extend(incs)
                poll_count += 1

    demisto.incidents(incidents)


def test_module_command():
    results = IRON_DEFENSE.test_module()
    demisto.results(results)


def update_analyst_ratings_command():
    alert_id = demisto.getArg("alert_id")
    severity = demisto.getArg("severity")
    expectation = demisto.getArg("expectation")
    comments = demisto.getArg("comments")
    share_irondome_arg = argToBoolean(demisto.getArg("share_comment_with_irondome"))
    results = IRON_DEFENSE.update_analyst_ratings(
        alert_id, severity=severity, expectation=expectation, comments=comments, share_irondome=share_irondome_arg
    )
    demisto.results(results)


def add_comment_to_alert_command():
    alert_id = demisto.getArg("alert_id")
    comment = demisto.getArg("comment")
    share_irondome_arg = argToBoolean(demisto.getArg("share_comment_with_irondome"))
    results = IRON_DEFENSE.add_comment_to_alert(alert_id, comment=comment, share_irondome=share_irondome_arg)
    demisto.results(results)


def set_alert_status_command():
    alert_id = demisto.getArg("alert_id")
    status = demisto.getArg("status")
    comments = demisto.getArg("comments")
    share_irondome_arg = argToBoolean(demisto.getArg("share_comment_with_irondome"))
    results = IRON_DEFENSE.set_alert_status(alert_id, status=status, comments=comments, share_irondome=share_irondome_arg)
    demisto.results(results)


def report_observed_bad_activity_command():
    name = demisto.getArg("name")
    description = demisto.getArg("description")
    ip = demisto.getArg("ip")
    domain = demisto.getArg("domain")
    activity_start_time = demisto.getArg("activity_start_time")
    activity_end_time = demisto.getArg("activity_end_time")
    results = IRON_DEFENSE.report_observed_bad_activity(
        name,
        description=description,
        ip=ip,
        domain=domain,
        activity_start_time=activity_start_time,
        activity_end_time=activity_end_time,
    )
    demisto.results(results)


def get_event_command():
    # get event data from IronAPI
    event_id = demisto.getArg("event_id")
    results = IRON_DEFENSE.get_event(event_id)

    # Output the event data
    event = results.get("event")
    vue_markdown_link = IRON_DEFENSE.create_markdown_link("Open in IronVue", event.get("vue_url"))
    event_readable_output = tableToMarkdown(
        f'IronDefense Event: {event.get("category")} - {event.get("sub_category")}\n{vue_markdown_link}', event
    )

    return_outputs(
        readable_output=event_readable_output,
        outputs={
            "IronDefense.Event(val.id == obj.id)": event,
        },
        raw_response=event,
    )

    # Output each context table
    context_tables = results.get("context")
    for table in context_tables:
        if IRON_DEFENSE.event_context_table_contains_multi_columns(table):
            output_table = IRON_DEFENSE.event_context_table_to_dict_list(table)
            headers = [*output_table[0]]
        else:
            output_table = IRON_DEFENSE.event_context_table_to_dict(table)
            headers = []

        return_outputs(
            readable_output=tableToMarkdown(f'Event Context: {table.get("name")}', output_table, headers=headers),
            outputs={
                "IronDefense.Event.Context(val.name == obj.name)": table,
            },
            raw_response=table,
        )


def get_events_command():
    alert_id = demisto.getArg("alert_id")
    limit = demisto.getArg("limit")
    offset = demisto.getArg("offset")

    results = IRON_DEFENSE.get_events(alert_id=alert_id, limit=limit, offset=offset)
    events = results.get("events")
    total_count = results.get("constraint").get("total")
    offset = results.get("constraint").get("offset")
    for i, event in enumerate(events):
        vue_markdown_link = IRON_DEFENSE.create_markdown_link("Open in IronVue", event.get("vue_url"))
        event_readable_output = tableToMarkdown(f"IronDefense Event {i + offset + 1}/{total_count}\n{vue_markdown_link}", event)
        # Send each event
        return_outputs(
            readable_output=event_readable_output,
            outputs={
                "IronDefense.Event(val.id == obj.id)": event,
            },
            raw_response=event,
        )

    # Send constraints
    constraint = results.get("constraint")
    return_outputs(
        readable_output=tableToMarkdown("Query Constraints", constraint),
        outputs={
            "IronDefense.Query.GetEvents": constraint,
        },
        raw_response=constraint,
    )


def get_alerts_command():
    alert_id = demisto.getArg("alert_id")
    category = demisto.getArg("category")
    sub_category = demisto.getArg("sub_category")
    status = demisto.getArg("status")
    analyst_severity = demisto.getArg("analyst_severity")
    analyst_expectation = demisto.getArg("analyst_expectation")
    min_severity = demisto.getArg("min_severity")
    max_severity = demisto.getArg("max_severity")
    min_created = demisto.getArg("min_created")
    max_created = demisto.getArg("max_created")
    min_updated = demisto.getArg("min_updated")
    max_updated = demisto.getArg("max_updated")
    min_first_event_created = demisto.getArg("min_first_event_created")
    max_first_event_created = demisto.getArg("max_first_event_created")
    min_last_event_created = demisto.getArg("min_last_event_created")
    max_last_event_created = demisto.getArg("max_last_event_created")
    min_first_event_start_time = demisto.getArg("min_first_event_start_time")
    max_first_event_start_time = demisto.getArg("max_first_event_start_time")
    min_last_event_end_time = demisto.getArg("min_last_event_end_time")
    max_last_event_end_time = demisto.getArg("max_last_event_end_time")
    analytic_version = demisto.getArg("analytic_version")
    limit = demisto.getArg("limit")
    offset = demisto.getArg("offset")
    sort = demisto.getArg("sort")
    results = IRON_DEFENSE.get_alerts(
        alert_id=alert_id,
        category=category,
        sub_category=sub_category,
        status=status,
        analyst_severity=analyst_severity,
        analyst_expectation=analyst_expectation,
        min_severity=min_severity,
        max_severity=max_severity,
        min_created=min_created,
        max_created=max_created,
        min_updated=min_updated,
        max_updated=max_updated,
        min_first_event_created=min_first_event_created,
        max_first_event_created=max_first_event_created,
        min_last_event_created=min_last_event_created,
        max_last_event_created=max_last_event_created,
        min_first_event_start_time=min_first_event_start_time,
        max_first_event_start_time=max_first_event_start_time,
        min_last_event_end_time=min_last_event_end_time,
        max_last_event_end_time=max_last_event_end_time,
        analytic_version=analytic_version,
        limit=limit,
        offset=offset,
        sort=sort,
    )
    alerts = results.get("alerts")
    total_count = results.get("constraint").get("total")
    offset = results.get("constraint").get("offset")
    for i, alert in enumerate(alerts):
        # Send each alert
        vue_markdown_link = IRON_DEFENSE.create_markdown_link("Open in IronVue", alert.get("vue_url"))
        alert_readable_output = tableToMarkdown(
            f'IronDefense Alert {i + offset + 1}/{total_count}: {alert.get("category")} -'
            f' {alert.get("sub_category")}\n'
            f'{vue_markdown_link}',
            alert,
        )
        return_outputs(
            readable_output=alert_readable_output,
            outputs={
                "IronDefense.Alert(val.id == obj.id)": alert,
            },
            raw_response=alert,
        )

    # Send constraints
    constraint = results.get("constraint")
    return_outputs(
        readable_output=tableToMarkdown("Query Constraints", constraint),
        outputs={
            "IronDefense.Query.GetAlerts": constraint,
        },
        raw_response=constraint,
    )


def get_alert_irondome_information_command():
    alert_id = demisto.getArg("alert_id")
    results = IRON_DEFENSE.get_alert_irondome_information(alert_id)

    if (
        len(results.get("correlations")) == 0
        and len(results.get("correlation_participation")) == 0
        and len(results.get("community_comments")) == 0
        and len(results.get("dome_notifications")) == 0
    ):
        demisto.results(f"No correlations found for alert ID: {alert_id}")
        return

    # Output correlations
    correlations = results.get("correlations")
    for correlation in correlations:
        dome_tag = correlation.get("dome_tag")
        correlation_data = correlation.get("correlations")
        output = {"alert_id": alert_id, "correlation": correlation}
        ip_correlations = list(filter(lambda corr: corr.get("ip") is not None, correlation_data))
        domain_correlations = list(filter(lambda corr: corr.get("domain") is not None, correlation_data))
        behavior_correlations = list(filter(lambda corr: corr.get("behavior") is not None, correlation_data))

        if len(ip_correlations) != 0:
            return_outputs(
                readable_output=tableToMarkdown(
                    f'IronDome IP Correlations in "{dome_tag}"', ip_correlations, headers=[*ip_correlations[0]]
                ),
                outputs={"IronDome.Correlations(val.alert_id = obj.alert.id)": output},
                raw_response=correlation,
            )

        if len(domain_correlations) != 0:
            return_outputs(
                readable_output=tableToMarkdown(
                    f'IronDome Domain Correlations in "{dome_tag}"', domain_correlations, headers=[*domain_correlations[0]]
                ),
                outputs={"IronDome.Correlations(val.alert_id = obj.alert.id)": output},
                raw_response=correlation,
            )

        if len(behavior_correlations) != 0:
            return_outputs(
                readable_output=tableToMarkdown(
                    f'IronDome Behavior Correlations in "{dome_tag}"', behavior_correlations, headers=[*behavior_correlations[0]]
                ),
                outputs={"IronDome.Correlations(val.alert_id = obj.alert.id)": output},
                raw_response=correlation,
            )

    # Output correlation participation
    correlation_participation = results.get("correlation_participation")
    for participant in correlation_participation:
        dome_tag = participant.get("dome_tag")
        output = {"alert_id": alert_id, "correlation_participation": participant}

        table_data = []

        # append each correlation context to display in the table, if it exists
        behavior = participant.get("behavior")
        if behavior is not None:
            table_data.append(behavior)
        domain = participant.get("behavior")
        if domain is not None:
            table_data.append(domain)
        ip = participant.get("ip")
        if ip is not None:
            table_data.append(ip)

        # Send the participant info
        return_outputs(
            readable_output=tableToMarkdown(
                f'IronDome Correlation Participation in "{dome_tag}"', table_data, headers=[*table_data[0]]
            ),
            outputs={"IronDome.CorrelationParticipation(val.alert_id = obj.alert.id)": output},
            raw_response=participant,
        )

    # Output comments
    community_comments = results.get("community_comments")
    community_comments_output = {
        "alert_id": alert_id,
        "community_comments": community_comments,
    }
    if len(community_comments) > 0:
        return_outputs(
            readable_output=tableToMarkdown("IronDome Community Comments", community_comments, headers=[*community_comments[0]]),
            outputs={"IronDome.CommunityComments(val.alert_id = obj.alert.id)": community_comments_output},
            raw_response=community_comments,
        )

    # Output cognitive system score
    cognitive_system_score = results.get("cognitive_system_score")
    cognitive_system_score_output = {
        "alert_id": alert_id,
        "cognitive_system_score": cognitive_system_score,
    }
    return_outputs(
        readable_output=f"### Cognitive System Score: {cognitive_system_score}",
        outputs={"IronDome.CognitiveSystemScore(val.alert_id = obj.alert.id)": cognitive_system_score_output},
        raw_response=cognitive_system_score,
    )

    # Output dome notifications
    dome_notifications = results.get("dome_notifications")
    for notification in dome_notifications:
        category = notification.get("category")
        output = {"alert_id": alert_id, "dome_notification": notification}
        return_outputs(
            readable_output=tableToMarkdown(f"IronDome Notification: {category}", notification),
            outputs={"IronDome.Notification(val.alert_id = obj.alert.id)": output},
            raw_response=notification,
        )

    return_outputs(
        readable_output=IRON_DEFENSE.create_dome_markdown_link("Open IronDome information in IronVue", alert_id), outputs={}
    )


COMMANDS = {
    "test-module": test_module_command,
    "fetch-incidents": fetch_incidents_command,
    "irondefense-rate-alert": update_analyst_ratings_command,
    "irondefense-comment-alert": add_comment_to_alert_command,
    "irondefense-set-alert-status": set_alert_status_command,
    "irondefense-report-observed-bad-activity": report_observed_bad_activity_command,
    "irondefense-get-event": get_event_command,
    "irondefense-get-events-from-alert": get_events_command,
    "irondefense-get-alerts": get_alerts_command,
    "irondefense-get-alert-irondome-information": get_alert_irondome_information_command,
}
COOKIE_KEY = "user_sid"
LOG_PREFIX = "IronDefense Integration: "

"""EXECUTION"""

if __name__ == "builtins":
    try:
        # Globals
        PARAMS = demisto.params()
        CREDENTIALS = PARAMS.get("credentials")
        HOST = PARAMS.get("ironAPIHost", "localhost")
        PORT = PARAMS.get("ironAPIPort", 443)
        REQUEST_TIMEOUT = float(PARAMS.get("requestTimeout", 60))
        LOGGER = XsoarLogger(demisto, LOG_PREFIX)

        # initialize the IronDefense object
        IRON_DEFENSE = IronDefense(demisto, requests.Session(), HOST, PORT, CREDENTIALS, LOGGER, request_timeout=REQUEST_TIMEOUT)

        LOGGER.debug("Invoking integration with Command: " + demisto.command())
        if demisto.command() in COMMANDS:
            COMMANDS[demisto.command()]()
        else:
            return_error("Command not found: " + demisto.command())

    except Exception as e:
        demisto.error(traceback.format_exc())
        return_error(str(e))