Securonix

Use the Securonix integration to manage incidents, threats, lookup tables, whitelists and watchlists.

Analytics & SIEM · Securonix

Details

IDSecuronix
ProviderSecuronix
CategoryAnalytics & SIEM
From Version5.0.0
Docker Imagedemisto/python3:3.12.13.10116658
Supported ModulesAgentix XSIAM

README

Overview

Use the Securonix integration to manage incidents, threats, lookup tables, whitelists and watchlists. Integration was built and tested with SNYPR Versions: 6.4 (Nov 2024 R2 release).

This integration supports both cloud and on-prem instances of Securonix.
To configure a cloud base instance use the Tenant parameter only.
To configure an on-prem instance, use both the Host and Tenant parameters.

Use cases

  1. Fetch newly created incidents from Securonix.
  2. Fetch newly created threats from Securonix.
  3. Get, update and create Securonix incidents, add comments and perform actions on the Securonix incidents.
  4. Get, update, create and delete records from the whitelist.
  5. Get, update, create and delete records from the lookup table.
  6. Get, update, and create records from the watchlist.

Pre-Requisites

On Securonix

Ensure the integration user satisfies below mentioned pre-requisites:

  1. A role “ROLE_CASE_ADMIN” must be assigned
  2. For the target Securonix platform, enable the “Show Incidents To All Users” option under “INCIDENT MANAGEMENT SETTINGS” as shown below:

    Incident Management Settings

Configure Securonix on Cortex XSOAR

  1. Navigate to Settings > Integrations > Instances.
  2. Search for Securonix.
  3. Click Add instance to create and configure a new integration instance.
  4. To fetch Securonix Incidents refer to the section “Configuration for fetching Securonix Incident as an XSOAR Incident”.
  5. To fetch Securonix Threats refer to the section “Configuration for fetching Securonix Threat as an XSOAR Incident”.

Configuration Parameters

Parameter Name Help Text Required (Yes / No)
Host (Overrides the default hostname: https://{tenant}.net/Snypr)   No
Tenant   Yes
Username   Yes
Password   Yes
Type of entity to fetch The type of Securonix entity to fetch. Supported entities are “Incident” and “Threat”. No
Tenant Name Name of the tenant to fetch threats from. This parameter is optional for Non MSSP users. No
Incidents to fetch Selecting “all” will fetch incidents updated in the given time range.
Selecting “opened” will fetch incidents opened in the given time range.
Selecting “closed” will fetch incidents closed in the given time range.
No
Set the default incident severity   No
First fetch time range The date or relative timestamp from where to start fetching incidents.

Supported formats: <number> <time unit>, e.g., 1 hour, 30 minutes, 7 days, 3 months, 1 year. Default is 1 hour.
No
The maximum number of incidents to fetch each time. If the value is greater than 200, it will be considered as 200. The maximum is 200. No
Incident Mirroring Direction The mirroring direction in which to mirror the incidents. You can mirror “Incoming” (from Securonix to XSOAR), “Outgoing” (from XSOAR to Securonix), or in both directions. No
Close respective Securonix incident after fetching If enabled, the integration will close the respective Securonix incident after fetching it in XSOAR. Following fields will be required for this functionality:

1. Securonix action name to map with XSOAR’s active state for Outgoing mirroring
2. Securonix status to map with XSOAR’s active state for Outgoing mirroring
3. Securonix action name to map with XSOAR’s closed state for Outgoing mirroring
4. Securonix status to map with XSOAR’s closed state for Outgoing mirroring
No
Securonix workflow state(s) that can be considered as Close state in XSOAR for Incoming mirroring If the Securonix incident is in any one of the state mentioned here, then the incident will be Closed on XSOAR. Supports comma-separated values. No
Securonix action name to map with XSOAR’s active state for Outgoing mirroring Provide an action name to map with XSOAR’s active state. E.g. IN PROGRESS. No
Securonix status to map with XSOAR’s active state for Outgoing mirroring Provide a workflow status to map with XSOAR’s active state. E.g. In Progress. No
Securonix action name to map with XSOAR’s closed state for Outgoing mirroring Provide an action name to map with XSOAR’s Closed state. E.g. CLOSED No
Securonix status to map with XSOAR’s closed state for Outgoing mirroring Provide a workflow status to map with XSOAR’s closed state. E.g. Completed No
Comment Entry Tag Choose the tag to add to an entry to mirror it as a comment in Securonix. No
Securonix Retry Count Numbers of retries to be performed. (Recommended is 3) No
Securonix Retry Delay(In Seconds) The delay between two retries. Range in 30 to 300 Seconds (5 minutes). Anything less than 30 seconds is considered 30 seconds, and anything more than 300 seconds is considered 300 seconds. (Recommended is 30 seconds) No
Securonix Retry Delay Type Delay type of retry mechanism. (Recommended is Exponential) No
Trust any certificate (not secure) Indicates whether to allow connections without verifying SSL certificate’s validity. No
Use system proxy settings Indicates whether to use XSOAR’s system proxy settings to connect to the API. No
  1. Click Test to validate the URLs, token, and connection.

Configuration for fetching Securonix Incident as an XSOAR Incident

To fetch Securonix Incident follow the next steps:

  1. Select Fetches incidents.
  2. Under Classifier, select “N/A”.
  3. Under Incident type, select “Securonix Incident”.
  4. Under Mapper (incoming), select “Securonix Incident - Incoming Mapper” for default mapping.
  5. Enter the connection parameters. (Host, Tenant, Username & Password)
  6. Under the Type of entity to fetch, select “Incident”.
  7. Select the “Incidents to fetch”:
    • all - This will fetch incidents updated in the given time range.
    • opened - This will fetch incidents created in the given time range.
    • closed - This will fetch incidents closed in the given time range.
  8. Update “Set default incident severity”, “First Fetch time range” & “Max Fetch Count” based on your requirement.
  9. Select the Incident Mirroring Direction:
    • Incoming - Mirrors changes from the Securonix incident into the Cortex XSOAR incident.
    • Outgoing - Mirrors changes from the Cortex XSOAR incident to the Securonix incident.
    • Incoming And Outgoing - Mirrors changes both Incoming and Outgoing directions on incidents.
    • None - Turns off incident mirroring.
  10. Enter the relevant values for “State” & “Action” values for mirroring.
    • Below table indicates which fields are required for the respective mirroring type.
Mirroring Type Securonix workflow States for Incoming mirroring Securonix State for XSOAR Active State Securonix Action for XSOAR Active Action Securonix State for XSOAR Closed State Securonix Action for XSOAR Closed Action
Incoming Yes No No No No
Outgoing No Yes Yes Yes Yes
Incoming and Outgoing Yes Yes Yes Yes Yes
  1. Enter the relevant Comment Entry Tag.
    Note: This value is mapped to the dbotMirrorTags incident field in Cortex XSOAR, which defines how Cortex XSOAR handles comments when you tag them in the War Room. This is required for mirroring comments from Cortex XSOAR to Securonix.
  2. Optional: Check the “Close respective Securonix incident after fetching” parameter, if you want to close the Securonix Incident once it is fetched in the XSOAR.
    Below Parameters are required if this option is checked:
    • Securonix action name for XSOAR’s active state for Outgoing
    • Securonix status for XSOAR’s active state for Outgoing
    • Securonix action name for XSOAR’s close state for Outgoing
    • Securonix status for XSOAR’s close state for Outgoing
  3. Enter the relevant values for Securonix Retry parameters “Count”, “Delay” & “Delay Type”.

Notes for mirroring:

  • This feature is compliant with XSOAR version 6.0 and above.
  • When mirroring incidents, you can make changes in Securonix that will be reflected in Cortex XSOAR, or vice versa. You can also attach files in Securonix Incident which will then be available in the XSOAR incident.
  • The mirroring settings apply only for incidents that are fetched after applying the settings. Pre-existing comments are not fetched/mirrored at the time of incident creation.
  • For mirroring to work flawlessly, a three-state workflow(similar to XSOAR) must be configured on the Securonix Incident side.
  • The mirroring is strictly tied to Incident type “Securonix Incident” & Incoming mapper “Securonix Incident - Incoming Mapper” if you want to change or use your custom incident type/mapper then make sure changes related to these are present.
  • If you want to use the mirror mechanism and you’re using custom mappers, then the incoming mapper must contain the following fields: dbotMirrorDirection, dbotMirrorId, dbotMirrorInstance, dbotMirrorTags and securonixcloseincident.
  • To use a custom mapper, you must first duplicate the mapper and update the fields in the copy of the mapper. (Refer to the “Create a custom mapper consisting of the default Securonix mapper” section for more information.)
  • Following new fields are introduced in the response of the incident to enable the mirroring:
    • mirror_direction: This field determines the mirroring direction for the incident. It is a required field for XSOAR to enable mirroring support.
    • mirror_tags: This field determines what would be the tag needed to mirror the XSOAR entry out to Securonix. It is a required field for XSOAR to enable mirroring support.
    • mirror_instance: This field determines from which instance the XSOAR incident was created. It is a required field for XSOAR to enable mirroring support.
    • close_sx_incident: This field determines whether to close the respective Securonix incident once fetched in the XSOAR based on the instance configuration. It is required for closing the respective incident on Securonix. This will be used in the playbook to close the securonix incident.

Configuration for fetching Securonix Threat as an XSOAR Incident

To fetch Securonix Threat follow the next steps:

  1. Select Fetches incidents.
  2. Under Classifier, select “N/A”.
  3. Under Incident type, select Securonix Threat.
  4. Under Mapper (incoming), select Securonix Threat - Incoming Mapper for default mapping.
  5. Under Type of entity to fetch, select Threat.
  6. Enter the Tenant Name in case of MSSP user.
  7. Enter the connection parameters. (Host, Tenant, Username & Password)
  8. Enter the “The maximum number of incidents to fetch each time”. The recommended number of threats to fetch is 100 considering the API implications, although 200 is allowed.
  9. Enter the relevant values for Securonix Retry parameters “Count”, “Delay” & “Delay Type”.

Create a custom mapper consisting of the default Securonix mapper

  1. Go to the settings -> Object setup -> Incidents.
  2. Navigate to the “Classification and Mapping” tab.
  3. Select the Mapper “Securonix incident - Incoming Mapper”.
  4. Create a copy of that mapper and click on it. (You can rename the mapper.)
  5. Under the Incident Type dropdown, verify that the type of Mapper is “Securonix Incident”.
  6. Click on “Choose data path” and map it to the custom field:
    • Find the context field you want to map to this incident field on the right side and click on its value.
    • Then you will see the path you’ve selected under your newly added field.
    • Note: You can also type the path manually.
  7. Click “Save Version”.
  8. Created mapper will appear in the drop-down for the “Mapper (incoming)” integration instance settings fields.
  9. Select the newly added mapper at the time of instance configuration.

Create a custom layout consisting of the default Securonix layout

  1. Go to the settings -> Object setup -> Incidents
  2. Navigate to the “Layouts” tab.
  3. Select the layout “Securonix Incident Information”.
  4. Create a copy of that layout and click on it. (You can rename the layout.)
  5. Select the newly created layout and click on edit.
    • To create a new section, drag and drop the “New Section” widget into the layout.
    • To add a new field to the layout, navigate to the “Fields and Buttons” section and search for the field. Drag and drop the field widget in the layout.
  6. Once done, select “Save Version”.
  7. Navigate to the “Incident Type” tab and select “Securonix Incident” type and detach it.
  8. Attach the newly created layout.
  9. Reattach the same “Incident Type” again else this incident type will not receive any new updates.

Note: It is recommended to use out-of-the-box mappers, layout & incident types for better visualization and meaningful mappings. If you are changing any out-of-the-box mappers/layout then it might not render all the fields as per the expectation.

Troubleshooting

Receive Notification on an Incident Fetch Error

The administrator and Cortex XSOAR users on the recipient’s list receive a notification when an integration experiences an incident fetch error. Cortex XSOAR users can select their notification method, such as email, from their user preferences. Refer to Cortex XSOAR 6.13 documentation or Cortex XSOAR 8 Cloud documentation or Cortex XSOAR 8.7 On-prem documentation for more information.

Input and output entries of the playbook are not visible in the war room

Follow the below steps and add a new server configuration:

  1. Go to Settings > About > Troubleshooting.
  2. Add new server configuration as “task.auto.quiet.mode.enabled” and enter the value as “false” and save. Refer to this XSOAR documentation
  3. After this configuration, all the output entries for the execution of the playbook gets displayed in the war room.

The following are tips for handling issues with mirroring incidents between Securonix and Cortex XSOAR.

Issue Recommendation
Mirroring is not working. Open Context Data and search for dbot. Confirm the dbot fields are configured correctly either through the mapper for that specific incident type or using setIncident. Specifically, make sure the integration instance is configured correctly for the mirroring direction (Incoming, Outgoing, Both) - dbotMirrorId, dbotMirrorDirection, dbotMirrorInstance, dbotMirrorTags
Comments from before incident creation are not fetched/mirrored. Mirroring settings apply only for incidents that are fetched after applying the settings.
For example, if a user creates a Securonix incident with added comments and then defines a Securonix integration instance with mirroring in Cortex XSOAR, those comments are not fetched with the incident in Cortex XSOAR.
Incident is not closing. Verify the integration instance is configured correctly for the Mirrored Securonix action and state fields.
Required fields are not getting sent or not visible in UI. This may be a mapping issue, specifically if you have used a custom mapper make sure you’ve covered all the out of box mapper fields.
Comments from XSOAR have not been mirrored in Securonix Tag is required for mirroring comments from Cortex XSOAR to Securonix. There might be a reason the comment is not tagged as tag needs to be added manually.

Click Actions > Tags and add the “comments” tag (OR the specific tag name which was set up on Instance Configuration).
Viewing masked data on the XSOAR application If you observe masked data, it is highly likely that ‘Masking’ is enabled on your Securonix tenant. Please check with your Securonix Administrator for further details.

The securonix-list-violation-data command not returning the violations

  1. Check the from and to arguments provided to the command. It should be ranging in the violations’ generation time.

Note: The following steps are compatible with the Securonix Build version 6.4_Apr2024_R1.

  1. If the query arguments contain the special characters, then check and follow the below steps:
    1. Use the XSOAR automation browser to run the command.
    2. Don’t add the extra backslashes for the *, ? and \ characters. This is handled by the command itself. Before upgrading, revert any temporary backslash solutions that may have been provided.
  2. If the Securonix Policy Type incident field is one of these (Land Speed, DIRECTIVE, BEACONING, TIER2) then try to execute command again with the additional ‘policy_type’ argument. The value for the policy_type argument should be the same as the value for the Securonix Policy Type incident field.

The securonix-list-activity-data command not returning the activity data

  1. Check the from and to arguments provided to the command. It should be ranging in the activity data generation time.

Note: The following steps are compatible with the Securonix Build version 6.4_Apr2024_R1.

  1. If the query arguments contain the special characters, then check and follow the below steps:
    1. Use the XSOAR automation browser to run the command.
    2. Don’t add the extra backslashes for the *, ? and \ characters. This is handled by the command itself. Before upgrading, revert any temporary backslash solutions that may have been provided.

Command HR or War room entries are breaking in the table view

  1. There might be some special characters (restricted by Markdown viewer i.e, \*_{}[]()#+-!`|) in the value of field which is breaking the view.
  2. Impact is in only on the HR output, however user can get exact values from the command context for such fields if any.

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.

securonix-list-workflows


Gets a list of all available workflows.

Base Command

securonix-list-workflows

Input

There are no input arguments for this command.

Context Output
Path Type Description
Securonix.Workflows.Workflow String Workflow name.
Securonix.Workflows.Type String Workflow type.
Securonix.Workflows.Value String Workflow value.
Command Example

##### Context Example

```json
{
    "Securonix": {
        "Workflows": [
            {
                "Type": "USER",
                "Value": "admin",
                "Workflow": "SOCTeamReview"
            },
            {
                "Type": "USER",
                "Value": "admin",
                "Workflow": "ActivityOutlierWorkflow"
            },
            {
                "Type": "USER",
                "Value": "admin",
                "Workflow": "AccessCertificationWorkflow"
            },
            {
                "Type": "USER",
                "Value": "admin",
                "Workflow": "test"
            }
        ]
    }
}
Human Readable Output

Available workflows

Workflow Type Value
SOCTeamReview USER admin
ActivityOutlierWorkflow USER admin
AccessCertificationWorkflow USER admin
test USER admin

securonix-get-default-assignee-for-workflow


Gets the default assignee for the specified workflow.

Base Command

securonix-get-default-assignee-for-workflow

Input
Argument Name Description Required
workflow Workflow name. Required
Context Output
Path Type Description
Securonix.Workflows.Workflow String Workflow name.
Securonix.Workflows.Type String Workflow type.
Securonix.Workflows.Value String Workflow value.
Command Example

!securonix-get-default-assignee-for-workflow workflow=SOCTeamReview

Context Example
{
    "Securonix": {
        "Workflows": {
            "Type": "USER",
            "Value": "admin",
            "Workflow": "SOCTeamReview"
        }
    }
}
Human Readable Output

Default assignee for the workflow SOCTeamReview is: admin.

securonix-list-possible-threat-actions


Gets a list available threat actions.

Base Command

securonix-list-possible-threat-actions

Input

There are no input arguments for this command.

Context Output
Path Type Description
Securonix.ThreatActions String A list of threat actions.
Command Example

##### Context Example

```json
{
    "Securonix": {
        "ThreatActions": [
            "Mark as concern and create incident",
            "Non-Concern",
            "Mark in progress (still investigating)"
        ]
    }
}
Human Readable Output

Possible threat actions are: Mark as concern and create incident, Non-Concern, Mark in progress (still investigating).

securonix-list-policies


Gets a list of all policies.

Base Command

securonix-list-policies

Input

There are no input arguments for this command.

Context Output
Path Type Description
Securonix.Policies.CreatedBy String Creator of the policy.
Securonix.Policies.CreatedOn Date Policy created date.
Securonix.Policies.Criticality String Policy criticality.
Securonix.Policies.Description String Policy description.
Securonix.Policies.Hql String Policy Hibernate Query Language.
Securonix.Policies.ID String Policy ID.
Securonix.Policies.Name String Policy name.
Command Example

##### Context Example

```json
{
    "Securonix": {
        "Policies": [
            {
                "CreatedBy": "admin",
                "CreatedOn": "2013-11-09T16:13:23Z",
                "Criticality": "Low",
                "Description": null,
                "Hql": "FROM AccessAccount AS accessaccount, Resources AS resources, AccessAccountUser AS accessaccountuser WHERE ((accessaccount.resourceid  = resources.id  AND accessaccountuser.id.accountid  = accessaccount.id )) AND ((accessaccountuser.id.userid  = '-1'))",
                "ID": "1",
                "Name": "Accounts that dont have Users"
            },
            {
                "CreatedBy": "admin",
                "CreatedOn": "2013-11-09T16:31:09Z",
                "Criticality": "Medium",
                "Description": null,
                "Hql": "FROM Users AS users, AccessAccountUser AS accessaccountuser, AccessAccount AS accessaccount, Resources AS resources WHERE ((users.id  = accessaccountuser.id.userid  AND accessaccountuser.id.accountid  = accessaccount.id  AND accessaccount.resourceid  = resources.id )) AND ((users.status  = '0'))",
                "ID": "2",
                "Name": "Accounts that belong to terminated user"
            },
           
        ]
    }
}
Human Readable Output

Policies

ID Name Criticality Created On Created By Description
1 Accounts that dont have Users Low 2013-11-09T16:13:23Z admin  
2 Accounts that belong to terminated user Medium 2013-11-09T16:31:09Z admin  

securonix-list-resource-groups


Gets a list of resource groups.

Base Command

securonix-list-resource-groups

Input

There are no input arguments for this command.

Context Output
Path Type Description
Securonix.ResourceGroups.Name String Resource group name.
Securonix.ResourceGroups.Type String Resource group type.
Command Example

##### Context Example

```json
{
    "Securonix": {
        "ResourceGroups": [
            {
                "Name": "Windows-CST1",
                "Type": "Microsoft Windows SNARE"
            },
            {
                "Name": "Websense Proxy",
                "Type": "Websense Proxy Server"
            },
            {
                "Name": "Palo Alto",
                "Type": "Palo Alto Next-Generation Firewall"
            },
            {
                "Name": "CDS1",
                "Type": "ControlsDS1"
            },
            {
                "Name": "Bluecoat",
                "Type": "Bluecoat Proxy"
            },
            {
                "Name": "Symantec-Email",
                "Type": "Symantec Message Security Gateway"
            },
            {
                "Name": "Proofpoint Email Gateway",
                "Type": "Proofpoint Email Gateway"
            },
            {
                "Name": "CiscoASA",
                "Type": "Cisco ASA"
            },
            {
                "Name": "CiscoAMP",
                "Type": "Cisco FireAMP"
            },
            {
                "Name": "PA800-adam",
                "Type": "Palo Alto Next-Generation Firewall"
            },
            {
                "Name": "CrowdStrike-PartnerAPI",
                "Type": "Crowdstrike Alerts Streaming"
            },
            {
                "Name": "squid-partners",
                "Type": "Squid Proxy"
            },
            {
                "Name": "Bluecoat_OP",
                "Type": "Bluecat_DHCP"
            },
            {
                "Name": "Bluecoat - Test",
                "Type": "Bluecoat Proxy"
            },
            {
                "Name": "Bluecoat_New",
                "Type": "Bluecoat Proxy"
            }
        ]
    }
}
Human Readable Output

Resource groups

Name Type
Windows-CST1 Microsoft Windows SNARE
Websense Proxy Websense Proxy Server
Palo Alto Palo Alto Next-Generation Firewall
CDS1 ControlsDS1
Bluecoat Bluecoat Proxy
Symantec-Email Symantec Message Security Gateway
Proofpoint Email Gateway Proofpoint Email Gateway
CiscoASA Cisco ASA
CiscoAMP Cisco FireAMP
PA800-adam Palo Alto Next-Generation Firewall
CrowdStrike-PartnerAPI Crowdstrike Alerts Streaming
squid-partners Squid Proxy
Bluecoat_OP Bluecat_DHCP
Bluecoat - Test Bluecoat Proxy
Bluecoat_New Bluecoat Proxy

securonix-list-users


Gets a list of users.

Base Command

securonix-list-users

Input

There are no input arguments for this command.

Context Output
Path Type Description
Securonix.Users.LastName String User last name.
Securonix.Users.SkipEncryption String Whether user encryption was skipped.
Securonix.Users.Riskscore String User risk score.
Securonix.Users.EmployeeID String User Employee ID.
Securonix.Users.Masked String Whether the user is masked.
Securonix.Users.Division String User division.
Securonix.Users.Criticality String User criticality.
Securonix.Users.Status String User status.
Securonix.Users.Department String User department.
Securonix.Users.Title String User title.
Securonix.Users.FirstName String User first name.
Securonix.Users.Email String User email address.
Command Example

##### Context Example

```json
{
    "Securonix": {
        "Users": [
            {
                "ContractEndDate": "2020-01-14T00:40:44Z",
                "Criticality": "Low",
                "Department": "Data Services",
                "Division": "Global Technology",
                "Email": "jon.doe@test.com",
                "EmployeeID": "1001",
                "FirstName": "jon",
                "LastName": "doe",
                "Masked": "false",
                "Riskscore": "0.0",
                "SkipEncryption": "false",
                "Status": "1",
                "Title": "Associate-Data Services"
            }
        ]
    }
}
Human Readable Output

Resource groups

First Name Last Name Criticality Title Email
jon doe Low Associate-Data Services jon.doe@test.com

securonix-list-activity-data


Gets a list of activity data for the specified resource group.

Base Command

securonix-list-activity-data

Input
Argument Name Description Required
from Start date/time for which to retrieve activity data (in the format MM/dd/yyyy HH:mm:ss). Required
to End date/time for which to retrieve activity data (in the format MM/dd/yyyy HH:mm:ss). Required
query Free-text query. For example, query=”resourcegroupname=WindowsSnare and policyname=Possible Privilege Escalation - Self Escalation”.
Note: Use the XSOAR automation browser to run the command if the “query” argument contains the special characters.
Optional
max Maximum number of activity records to retrieve. Default is 1000. Maximum allowed value is 10000. Optional
Context Output

| Path | Type | Description |
|—————————————————-| — | — |
| Securonix.ActivityData.Accountname | String | Account name. |
| Securonix.ActivityData.Accountresourcekey | String | Account source key. |
| Securonix.ActivityData.Agentfilename | String | Agent file name. |
| Securonix.ActivityData.Baseeventid | String | Base event ID. |
| Securonix.ActivityData.Categorizedtime | String | Categorized time. |
| Securonix.ActivityData.Categorybehavior | String | Category behavior. |
| Securonix.ActivityData.Categoryobject | String | Category object. |
| Securonix.ActivityData.Categoryseverity | String | Category severity. |
| Securonix.ActivityData.Collectionmethod | String | Collection method. |
| Securonix.ActivityData.Collectiontimestamp | String | Collection timestamp. |
| Securonix.ActivityData.Customnumber1 | Number | Custom number. |
| Securonix.ActivityData.Customstring13 | String | Custom string. |
| Securonix.ActivityData.Customstring17 | String | Custom string. |
| Securonix.ActivityData.Customtime1 | String | Custom time. |
| Securonix.ActivityData.Customtime2 | String | Custom time. |
| Securonix.ActivityData.Datetime | String | Date time. |
| Securonix.ActivityData.Dayofmonth | String | Day of month. |
| Securonix.ActivityData.Dayofweek | String | Day of week. |
| Securonix.ActivityData.Dayofyear | String | Day of year. |
| Securonix.ActivityData.Destinationntdomain | String | Destination NT domain. |
| Securonix.ActivityData.Destinationprocessname | String | Destination process name. |
| Securonix.ActivityData.Destinationservicename | String | Destination service name. |
| Securonix.ActivityData.Destinationuserid | String | Destination user ID. |
| Securonix.ActivityData.Destinationusername | String | Destination username. |
| Securonix.ActivityData.Deviceaddress | String | Device address. |
| Securonix.ActivityData.Destinationuserprivileges | String | Destination user privileges. |
| Securonix.ActivityData.Devicecustomstring4 | String | Device custom string. |
| Securonix.ActivityData.Deviceeventcategory | String | Device event category. |
| Securonix.ActivityData.Deviceexternalid | String | Device external ID. |
| Securonix.ActivityData.Devicehostname | String | Device hostname. |
| Securonix.ActivityData.Ehash | String | Event hash. |
| Securonix.ActivityData.EventID | String | Event ID. |
| Securonix.ActivityData.Eventoutcome | String | Event outcome. |
| Securonix.ActivityData.Eventtime | String | Time the event occurred. |
| Securonix.ActivityData.Filepath | String | File path. |
| Securonix.ActivityData.Filepermission | String | File permission. |
| Securonix.ActivityData.Hour | String | Date time hour. |
| Securonix.ActivityData.ID | String | Activity ID. |
| Securonix.ActivityData.Ingestionnodeid | String | Ingestion node ID. |
| Securonix.ActivityData.Ipaddress | String | IP address. |
| Securonix.ActivityData.Ipaddress_Long | String | IP address long. |
| Securonix.ActivityData.JobID | String | Deprecated. Use the Securonix.ActivityData.Jobid field. |
| Securonix.ActivityData.Jobid | String | Job ID. |
| Securonix.ActivityData.Jobstarttime | String | Job start time. |
| Securonix.ActivityData.Message | String | Message. |
| Securonix.ActivityData.Minute | String | Date time minute. |
| Securonix.ActivityData.Month | String | Month. |
| Securonix.ActivityData.Oldfileid | String | Old file ID. |
| Securonix.ActivityData.Oldfilepath | String | Old file path. |
| Securonix.ActivityData.Others | String | Others. |
| Securonix.ActivityData.Poprocessedtime | String | PO processed time. |
| Securonix.ActivityData.Publishedtime | String | Published time. |
| Securonix.ActivityData.Rawevent | String | Raw event. |
| Securonix.ActivityData.Raweventsize | String | Raw event size. |
| Securonix.ActivityData.Receivedtime | String | Received time. |
| Securonix.ActivityData.Resourcename | String | Resource name. |
| Securonix.ActivityData.ResourceGroupCategory | String | Resource group category. |
| Securonix.ActivityData.ResourceGroupFunctionality | String | Resource group functionality. |
| Securonix.ActivityData.ResourceGroupID | String | Resource group ID. |
| Securonix.ActivityData.ResourceGroupName | String | Resource group name. |
| Securonix.ActivityData.ResourceGroupTimezoneoffset | String | Resource Group Timezone offset. |
| Securonix.ActivityData.ResourceGroupTypeID | String | Resource group resource type ID. |
| Securonix.ActivityData.ResourceGroupVendor | String | Resource group vendor. |
| Securonix.ActivityData.Resourcegroupid | String | Resource Group ID. |
| Securonix.ActivityData.Resourcegroupname | String | Resource Group Name. |
| Securonix.ActivityData.Resourcehostname | String | Resource host name. |
| Securonix.ActivityData.Resourcehostname_Long | String | Resource host name long. |
| Securonix.ActivityData.Resourcename | String | Resource name. |
| Securonix.ActivityData.Resourcetype | String | Resource type. |
| Securonix.ActivityData.Sessionid | String | Session ID. |
| Securonix.ActivityData.Sourceaddress | String | Source address. |
| Securonix.ActivityData.Sourceaddress_Long | String | Source address long. |
| Securonix.ActivityData.Sourcehostname | String | Source hostname. |
| Securonix.ActivityData.Sourcentdomain | String | Source domain. |
| Securonix.ActivityData.Sourceport | String | Source port. |
| Securonix.ActivityData.Sourceprocessname | String | Source process name. |
| Securonix.ActivityData.Sourceuserid | String | Source user ID. |
| Securonix.ActivityData.Sourceusername | String | Source username. |
| Securonix.ActivityData.TenantID | String | Tenant ID. |
| Securonix.ActivityData.Tenantname | String | Tenant name. |
| Securonix.ActivityData.Timeline | String | Time when the activity occurred, in Epoch time. |
| Securonix.ActivityData.Timeline_By_Hour | String | Timeline by hour. |
| Securonix.ActivityData.Timeline_By_Minute | String | Timeline by minute. |
| Securonix.ActivityData.Timeline_By_Month | String | Timeline by month. |
| Securonix.ActivityData.Timeline_By_Week | String | Timeline by week. |
| Securonix.ActivityData.Timestamp | String | Timestamp. |
| Securonix.ActivityData.Transactionstring1 | String | Transaction string 1. |
| Securonix.ActivityData.Userid | String | User ID. |
| Securonix.ActivityData.Week | String | Week. |
| Securonix.ActivityData.Year | String | Year. |
| Securonix.ActivityData.Indexed_At_Tdt | String | Indexed at TDT. |
| Securonix.ActivityData._Version
| String | Activity version. |
| Securonix.Activity.totalDocuments | Number | Total number of events. |
| Securonix.Activity.message | String | Message from the API. |
| Securonix.Activity.queryId | String | Query Id for the pagination. |
| Securonix.Activity.command_name | String | The command name. |

Command example

!securonix-list-activity-data from="01/12/2024 10:00:00" to="01/15/2024 12:01:00"

Context Example
{
    "Securonix": {
        "Activity": {
            "queryId": "spotter_web_service_00000000-0000-0000-0000-000000000001",
            "totalDocuments": 1182,
            "command_name": "securonix-list-activity-data"
        },
        "ActivityData": [
            {
                "Accountname": "ACCOUNT_001",
                "Accountresourcekey": "00000000000~000000000.0000.com~pipe_line_test~0000~-1",
                "Agentfilename": "test.txt",
                "Categorybehavior": "Account Create",
                "Categoryobject": "Account Management",
                "Categoryseverity": "0",
                "Collectionmethod": "file",
                "Collectiontimestamp": "1690803374000",
                "Destinationusername": "TEST134044",
                "Devicehostname": "HOST.com",
                "EventID": "00000000-0000-0000-0000-000000000001",
                "Eventoutcome": "Success",
                "Filepath": "N/A",
                "Ingestionnodeid": "CONSOLE",
                "Jobstarttime": "1690803374000",
                "Message": "A user account was created.",
                "Publishedtime": "1690803374572",
                "Receivedtime": "1690803420706",
                "Resourcename": "HOST.com",
                "Sourceusername": "USER",
                "TenantID": "2",
                "Tenantname": "Response-Automation",
                "Timeline": "1670911200000"
            },
            {
                "Accountname": "ACCOUNT_002",
                "Accountresourcekey": "00000000000~000000000.0000.com~pipe_line_test~0000~-2",
                "Agentfilename": "test.txt",
                "Categorybehavior": "Account Create",
                "Categoryobject": "Account Management",
                "Categoryseverity": "0",
                "Collectionmethod": "file",
                "Collectiontimestamp": "1690803374000",
                "Destinationusername": "TEST134044",
                "Devicehostname": "HOST.com",
                "EventID": "00000000-0000-0000-0000-000000000002",
                "Eventoutcome": "Success",
                "Filepath": "N/A",
                "Ingestionnodeid": "CONSOLE",
                "Jobstarttime": "1690803374000",
                "Message": "A user account was created.",
                "Publishedtime": "1690803374572",
                "Receivedtime": "1690803420500",
                "Resourcename": "HOST.com",
                "Sourceusername": "USER",
                "TenantID": "2",
                "Tenantname": "Response-Automation",
                "Timeline": "1670911200000"
            }
        ]
    }
}
Human Readable Output

Activity data

EventID Message Accountname Timeline Devicehostname Accountresourcekey
00000000-0000-0000-0000-000000000001 A user account was created. ACCOUNT_001 2024-01-13T06:00:00.000Z HOST.com 00000000000~000000000.0000.com~pipe_line_test~0000~-1
00000000-0000-0000-0000-000000000002 A user account was created. ACCOUNT_002 2024-01-13T06:00:00.000Z HOST.com 00000000000~000000000.0000.com~pipe_line_test~0000~-2

securonix-list-violation-data


Gets a list activity data for an account name.

Base Command

securonix-list-violation-data

Input
Argument Name Description Required
from Start date/time for which to retrieve activity data (in the format MM/dd/yyyy HH:mm:ss). Required
to End date/time for which to retrieve activity data (in the format MM/dd/yyyy HH:mm:ss). Required
query Free-text query. For example, query=”resourcegroupname=WindowsSnare and policyname=Possible Privilege Escalation - Self Escalation”.
Note: Use the XSOAR automation browser to run the command if the “query” argument contains the special characters.
Optional
query_id Paginate next set of results. Optional
policy_type Provide the policy type for retrying if the violations are not found in the initial attempt. The types of policies that can be retried are: “Land Speed”, “TIER2”, “DIRECTIVE”, “BEACONING”. Optional
max Maximum number of violations to retrieve. Default is 1000. Optional
Context Output
Path Type Description
Securonix.ViolationData.Accountname String Account name.
Securonix.ViolationData.Agentfilename String Agent file name.
Securonix.ViolationData.Baseeventid String Base event ID.
Securonix.ViolationData.Categorybehavior String Category behavior.
Securonix.ViolationData.Category String Violation category.
Securonix.ViolationData.Categoryobject String Category object.
Securonix.ViolationData.Categoryseverity String Category severity.
Securonix.ViolationData.Destinationaddress String Destination address.
Securonix.ViolationData.Destinationntdomain String Destination nt domain.
Securonix.ViolationData.Destinationuserid String Destination user ID.
Securonix.ViolationData.Gestinationusername String Destination username.
Securonix.ViolationData.Deviceaddress String Device address.
Securonix.ViolationData.Deviceeventcategory String Device event category.
Securonix.ViolationData.Deviceexternalid String Device external ID.
Securonix.ViolationData.Devicehostname String Device hostname.
Securonix.ViolationData.EventID String Event ID.
Securonix.ViolationData.Eventoutcome String Event outcome.
Securonix.ViolationData.Eventtime String Time the event occurred.
Securonix.ViolationData.Generationtime String Time that the violation was generated in Securonix.
Securonix.ViolationData.Invalid String Whether the violation is valid.
Securonix.ViolationData.JobID String Job ID.
Securonix.ViolationData.Jobstarttime String Job start time.
Securonix.ViolationData.Policyname String Policy name.
Securonix.ViolationData.Resourcename String Resource name.
Securonix.ViolationData.ResourceGroupID String Resource group ID.
Securonix.ViolationData.ResourceGroupName String Resource group name.
Securonix.ViolationData.Riskscore String Risk score.
Securonix.ViolationData.Riskthreatname String Risk threat name.
Securonix.ViolationData.Sessionid String Session ID.
Securonix.ViolationData.Sourcehostname String Source hostname.
Securonix.ViolationData.Sourcentdomain String Source nt domain.
Securonix.ViolationData.Sourceuserid String Source user ID.
Securonix.ViolationData.Sourceusername String Source username.
Securonix.ViolationData.Sourceuserprivileges String Source user privileges.
Securonix.ViolationData.TenantID String Tenant ID.
Securonix.ViolationData.Tenantname String Tenant name.
Securonix.ViolationData.Timeline String Time when the activity occurred, in Epoch time.
Securonix.ViolationData.Createdate String Create date.
Securonix.ViolationData.Criticality String Violation criticality.
Securonix.ViolationData.DataSourceID String Data source ID.
Securonix.ViolationData.Department String Department affected by the violation.
Securonix.ViolationData.EmployeeID String Employee ID.
Securonix.ViolationData.Encrypted String Whether the violation is encrypted.
Securonix.ViolationData.Firstname String First name of the user that violated the policy.
Securonix.ViolationData.Fullname String Full name of the user that violated the policy.
Securonix.ViolationData.ID String ID of the user that violated the policy.
Securonix.ViolationData.LanID String LAN ID associated with the policy violation.
Securonix.ViolationData.Lastname String Last name of the user that violated the policy.
Securonix.ViolationData.Lastsynctime String Last sync time, in Epoch time.
Securonix.ViolationData.Masked String Whether the violation is masked.
Securonix.ViolationData.Mergeuniquecode String Merge unique code.
Securonix.ViolationData.Riskscore String Risk score.
Securonix.ViolationData.Skipencryption String Skip encryption.
Securonix.ViolationData.Status String Status of the policy violation.
Securonix.ViolationData.Timezoneoffset String Timezone offset.
Securonix.ViolationData.Title String Title.
Securonix.ViolationData.Uniquecode String Unique code.
Securonix.ViolationData.UserID String Last sync time, in Epoch time.
Securonix.ViolationData.Workemail String Work email address of the user that violated the policy.
Securonix.ViolationData.Violator String Violator.
Securonix.Violation.totalDocuments Number Total number of events.
Securonix.Violation.message String Message from the API.
Securonix.Violation.queryId String Query Id for the pagination.
Command Example

!securonix-list-violation-data from="01/01/2023 00:00:00" to="03/10/2023 00:00:00"

Context Example
{
    "Securonix": {
        "ViolationData": [
        {
            "Accountname": "TESTDPB30",
            "Categorizedtime": "Morning",
            "Category": "Account Compromise",
            "Categoryseverity": "0",
            "Datetime": "1676960205747",
            "Dayofmonth": "21",
            "Dayofweek": "3",
            "Dayofyear": "52",
            "Eventid": "test_event",
            "GenerationTime": "02/21/2023 00:17:19",
            "Hour": "0",
            "ID": "-1",
            "Invalid": "false",
            "Ipaddress": "IP Address",
            "Jobid": "140",
            "Jobstarttime": "1676960199000",
            "Month": "1",
            "Policyname": "Policy1",
            "Resourcecomments": "ingestion_2.0",
            "Resourcegroupid": "6",
            "Resourcegroupname": "test",
            "Resourcename": "mac30",
            "Resourcetype": "mvk",
            "Riskthreatname": "Abnormal amount of data egressed compared to peer",
            "TenantID": "2",
            "Tenantname": "test_tenant",
            "Timeline": "1676959200000",
            "Timeline_By_Hour": "1676980800000",
            "Timeline_By_Minute": "1676960100000",
            "Timeline_By_Month": "1675231200000",
            "Timeline_By_Week": "1676786400000",
            "Transactionstring1": "Logon failure 30",
            "Userid": "-1",
            "Violator": "RTActivityAccount",
            "Week": "8",
            "Year": "2023"
        },
        {
            "Accountname": "TESTDPB30",
            "Categorizedtime": "Morning",
            "Category": "Account Compromise",
            "Categoryseverity": "0",
            "Datetime": "1676960205747",
            "Dayofmonth": "21",
            "Dayofweek": "3",
            "Dayofyear": "52",
            "Eventid": "Event ID",
            "GenerationTime": "02/21/2023 00:17:19",
            "Hour": "0",
            "ID": "-1",
            "Invalid": "false",
            "Ipaddress": "IP Address",
            "Jobid": "140",
            "Jobstarttime": "1676960199000",
            "Month": "1",
            "Policyname": "Policy2",
            "Resourcecomments": "ingestion_2.0",
            "Resourcegroupid": "6",
            "Resourcegroupname": "tets",
            "Resourcename": "mac30",
            "Resourcetype": "mvk",
            "Riskthreatname": "Abnormal attempts to reset domain admin password",
            "TenantID": "2",
            "Tenantname": "test_tenant",
            "Timeline": "1676959200000",
            "Timeline_By_Hour": "1676980800000",
            "Timeline_By_Minute": "1676960100000",
            "Timeline_By_Month": "1675231200000",
            "Timeline_By_Week": "1676786400000",
            "Transactionstring1": "Logon failure 30",
            "Userid": "-1",
            "Violator": "RTActivityAccount",
            "Week": "8",
            "Year": "2023"
        }
        ],
        "Violation": {
            "totalDocuments": 2,
            "message": "",
            "queryId": "abcd1234"
        }
    }
}
Human Readable Output

Activity data

Policyname Accountname
Policy1 TESTDPB30
Policy2 TESTDPB30

Next page query id: abcd1234

securonix-list-incidents


Gets a list of incidents.

Base Command

securonix-list-incidents

Input
Argument Name Description Required
from Start time range for which to return incidents (<number> <time unit>, e.g., 1 hour, 30 minutes) Required
to End date/time for which to retrieve incidents (in the format MM/dd/yyyy HH:mm:ss) Default is current time. Optional
incident_types The incident type. Can be “updated”, “opened”, or “closed”. Supports multiple selections. Optional
Context Output
Path Type Description
Securonix.Incidents.ViolatorID String Incident Violator ID.
Securonix.Incidents.Entity String Incident entity.
Securonix.Incidents.Riskscore Number Incident risk score.
Securonix.Incidents.Priority String Incident priority.
Securonix.Incidents.Reason String Reason for the incident. Usually includes policy name and/or possible threat name.
Securonix.Incidents.IncidentStatus String Incident status.
Securonix.Incidents.WorkflowName String Incident workflow name.
Securonix.Incidents.Watchlisted Boolean Whether the incident is in a watchlist.
Securonix.Incidents.IncidentType String Incident type.
Securonix.Incidents.IncidentID String Incident ID.
Securonix.Incidents.LastUpdateDate Number Last update date of the incident in Epoch time.
Securonix.Incidents.Url String URL that links to the incident on Securonix.
Securonix.Incidents.ViolatorText String Incident violator text.
Securonix.Incidents.AssignedUser String User assigned to the incident.
Securonix.Incidents.IsWhitelisted Boolean Whether the incident is added to allow list.
Securonix.Incidents.Policystarttime Number Epoch time when the policy is first violated.
Securonix.Incidents.Policyendtime Number Epoch time when the policy is last violated.
Securonix.Incidents.Solrquery String Spotter query to fetch the related violations.
Command Example

!securonix-list-incidents from="5 days" incident_types=opened

Context Example
{
    "Securonix": {
        "Incidents": [
            {
                "AssignedUser": "Admin Admin",
                "Bulkactionallowed": true,
                "CaseEventEndTime": 1675849673983,
                "CaseEventStartTime": 1675845486324,
                "Casecreatetime": 1675849649900,
                "Entity": "RTActivityAccount",
                "IncidentID": "30107",
                "IncidentStatus": "COMPLETED",
                "IncidentType": "HighRiskRTActivityAccount",
                "IsWhitelisted": false,
                "LastUpdateDate": 1675849674032,
                "ParentCaseId": "",
                "Priority": "None",
                "Reason": [
                    "ResourceType: mvk",
                    "Policy: SandboxFeb8",
                    "Threat: Abnormal attempts to reset domain admin password"
                ],
                "Riskscore": 0,
                "SandBoxPolicy": true,
                "StatusCompleted": true,
                "TenantInfo": {
                "tenantid": 2,
                "tenantname": "test_tenant"
                },
                "Type": "HighRiskRTActivityAccount",
                "Url": "url",
                "ViolatorID": "TESTING2",
                "ViolatorText": "TESTING2",
                "Watchlisted": false,
                "WorkflowName": "SOCTeamReview",
                "Policystarttime": 1692950376801,
                "Policyendtime": 1695613655539,
                "Solrquery": "index = violation and ( ( @policyname = \"Response-PB-Resources-AutoPlay\" and @resourcename=\"Activityres17-Resource-549829\" )  ) AND @tenantname=\"Response-Automation\" AND datetime between \"02/07/2023 15:52:12\" \"02/07/2023 15:52:13\""
            },
            {
                "AssignedUser": "Admin Admin",
                "Bulkactionallowed": true,
                "CaseEventEndTime": 1675851019318,
                "CaseEventStartTime": 1675850440699,
                "Casecreatetime": 1675850942351,
                "Entity": "RTActivityAccount",
                "IncidentID": "30108",
                "IncidentStatus": "COMPLETED",
                "IncidentType": "HighRiskRTActivityAccount",
                "IsWhitelisted": true,
                "LastUpdateDate": 1675851019367,
                "ParentCaseId": "",
                "Priority": "None",
                "Reason": [
                    "ResourceType: mvk",
                    "Policy: SandboxFeb8",
                    "Threat: Abnormal attempts to reset domain admin password"
                ],
                "Riskscore": 0,
                "SandBoxPolicy": true,
                "StatusCompleted": true,
                "TenantInfo": {
                "tenantid": 2,
                "tenantname": "test_tenant"
                },
                "Type": "HighRiskRTActivityAccount",
                "Url": "url",
                "ViolatorID": "TESTING4",
                "ViolatorText": "TESTING4",
                "Watchlisted": false,
                "WorkflowName": "SOCTeamReview",
                "Policystarttime": 1692950376801,
                "Policyendtime": 1695613655539,
                "Solrquery": "index = violation and ( ( @policyname = \"Response-PB-Resources-AutoPlay\" and @resourcename=\"Activityres17-Resource-549829\" )  ) AND @tenantname=\"Response-Automation\" AND datetime between \"02/07/2023 15:52:12\" \"02/07/2023 15:52:13\""
            }
        ]
    }
}
Human Readable Output

Incidents

IncidentID Incident Status Incident Type Priority Reason
30107 COMPLETED HighRiskRTActivityAccount None ResourceType: mvk,
Policy: SandboxFeb8,
Threat: Abnormal attempts to reset domain admin password
30108 COMPLETED HighRiskRTActivityAccount None ResourceType: mvk,
Policy: SandboxFeb8,
Threat: Abnormal attempts to reset domain admin password

securonix-get-incident


Gets details of the specified incident.

Base Command

securonix-get-incident

Input
Argument Name Description Required
incident_id Incident ID. Required
Context Output
Path Type Description
Securonix.Incidents.ViolatorID String Incident violator ID.
Securonix.Incidents.Entity String Incident entity.
Securonix.Incidents.Riskscore Number Incident risk score.
Securonix.Incidents.Priority String Incident priority.
Securonix.Incidents.Reason String Reason for the incident. Usually includes policy name and/or possible threat name.
Securonix.Incidents.IncidentStatus String Incident status.
Securonix.Incidents.WorkflowName String Incident workflow name.
Securonix.Incidents.Watchlisted Boolean Whether the incident is in a watchlist.
Securonix.Incidents.IncidentType String Incident type.
Securonix.Incidents.IncidentID String Incident ID.
Securonix.Incidents.LastUpdateDate Number The time when the incident was last updated, in Epoch time.
Securonix.Incidents.Url String URL that links to the incident on Securonix.
Securonix.Incidents.ViolatorText String Incident violator text.
Securonix.Incidents.AssignedUser String User assigned to the incident.
Securonix.Incidents.IsWhitelisted Boolean Whether the incident is added to allow list.
Securonix.Incidents.Policystarttime Number Epoch time when the policy is first violated.
Securonix.Incidents.Policyendtime Number Epoch time when the policy is last violated.
Securonix.Incidents.Solrquery String Spotter query to fetch the related violations.
Command Example

!securonix-get-incident incident_id=30107

Context Example
{
    "Securonix": {
        "Incidents": {
            "AssignedUser": "Admin Admin",
            "Casecreatetime": 1579687173702,
            "Entity": "Users",
            "IncidentID": "30107",
            "IncidentStatus": "Open",
            "IncidentType": "Policy",
            "IsWhitelisted": false,
            "LastUpdateDate": 1579687173702,
            "ParentCaseId": "",
            "Priority": "Critical",
            "Reason": [
                "Resource: BLUECOAT",
                "Policy: Uploads to personal websites",
                "Threat: Data egress via network uploads"
            ],
            "Riskscore": 0,
            "SandBoxPolicy": false,
            "StatusCompleted": false,
            "TenantInfo": {
                "tenantcolor": "#000000",
                "tenantid": 1,
                "tenantname": "Securonix",
                "tenantshortcode": "SE"
            },
            "Url": "url",
            "ViolatorID": "9",
            "ViolatorSubText": "1009",
            "ViolatorText": "Judi Mcabee",
            "Watchlisted": false,
            "WorkflowName": "SOCTeamReview",
            "Policystarttime": 1692950376801,
            "Policyendtime": 1695613655539,
            "Solrquery": "index = violation and ( ( @policyname = \"Response-PB-Resources-AutoPlay\" and @resourcename=\"Activityres17-Resource-549829\" )  ) AND @tenantname=\"Response-Automation\" AND datetime between \"02/07/2023 15:52:12\" \"02/07/2023 15:52:13\""
        }
    }
}
Human Readable Output

Incident

Assigned User Casecreatetime Entity Incident Status Incident Type IncidentID Is Whitelisted Last Update Date Priority Reason Riskscore Sand Box Policy Status Completed Tenant Info Url Violator Sub Text Violator Text ViolatorID Watchlisted Workflow Name Policystarttime Policyendtime Solrquery
Admin Admin 1579687173702 Users Open Policy 30107 false 1579687173702 Critical Resource: BLUECOAT,Policy: Uploads to personal websites,Threat: Data egress via network uploads 0.0 false false tenantid: 1 tenantname: {name} url 1009 john smith 9 false SOCTeamReview 1692950376801 1695613655539 index = violation and ( ( @policyname = "Response-PB-Resources-AutoPlay" and @resourcename="Activityres17-Resource-549829" ) ) AND @tenantname="Response-Automation" AND datetime between "02/07/2023 15:52:12" "02/07/2023 15:52:13"

securonix-get-incident-status


Gets the status of the specified incident.

Base Command

securonix-get-incident-status

Input
Argument Name Description Required
incident_id Incident ID. Required
Context Output
Path Type Description
Securonix.Incidents.IncidentStatus String Incident status.
Securonix.Incidents.IncidentID String Incident ID.
Command Example

!securonix-get-incident-status incident_id=30107

Context Example
{
    "Securonix": {
        "Incidents": {
            "IncidentID": "30107",
            "IncidentStatus": "Open"
        }
    }
}
Human Readable Output

Incident 30107 status is Open.

securonix-get-incident-workflow


Gets the workflow of the specified incident.

Base Command

securonix-get-incident-workflow

Input
Argument Name Description Required
incident_id Incident ID. Required
Context Output
Path Type Description
Securonix.Incidents.Workflow String Incident workflow.
Securonix.Incidents.IncidentID String Incident ID.
Command Example

!securonix-get-incident-workflow incident_id=30107

Context Example
{
    "Securonix": {
        "Incidents": {
            "IncidentID": "30107",
            "WorkflowName": "SOCTeamReview"
        }
    }
}
Human Readable Output

Incident 30107 workflow is SOCTeamReview.

securonix-get-incident-available-actions


Gets a list of available actions for the specified incident.

Base Command

securonix-get-incident-available-actions

Input
Argument Name Description Required
incident_id Incident ID. Required
Context Output

There is no context output for this command.

Command Example

!securonix-get-incident-available-actions incident_id=30107

Context Example
{
    "Securonix": {
        "Incidents": {
            "AvailableActions": [
                "CLAIM",
                "ASSIGN TO ANALYST",
                "ASSIGN TO SECOPS"
            ],
            "IncidentID": "30107"
        }
    }
}
Human Readable Output

Incident 30107 available actions: [‘CLAIM’, ‘ASSIGN TO ANALYST’, ‘ASSIGN TO SECOPS’].

securonix-perform-action-on-incident


Performs an action on the specified incident.

Base Command

securonix-perform-action-on-incident

Input
Argument Name Description Required
incident_id Incident ID. Required
action Action to perform on the incident. You can see them using securonix-get-incident-available-actions. e.g: “CLAIM”, “ASSIGN TO SECOPS”, “ASSIGN TO ANALYST”, “RELEASE”, or “COMMENT”. Required
action_parameters The parameters, if needed, to perform the action. e.g, For the ASSIGN TO ANALYST action: assigntouserid={user_id},assignedTo=USER. Optional
Context Output

There is no context output for this command.

Command Example

!securonix-perform-action-on-incident action="Close Incident" incident_id=330365

Context Example
{}
Human Readable Output

Action Close Incident was performed on incident 330365.

securonix-add-comment-to-incident


Adds a comment to the specified incident.

Base Command

securonix-add-comment-to-incident

Input
Argument Name Description Required
incident_id Incident ID. Required
comment Comment to add to the incident. Required
Context Output

There is no context output for this command.

Command Example

!securonix-add-comment-to-incident incident_id=30107 comment="Just a comment"

Context Example
{}
Human Readable Output

Comment was added to the incident 30107 successfully.

securonix-list-watchlists


Gets a list of watchlists.

Base Command

securonix-list-watchlists

Input

There are no input arguments for this command.

Context Output
Path Type Description
Securonix.WatchlistsNames String Watchlist names.
Command Example

##### Context Example

```json
{
    "Securonix": {
        "WatchlistsNames": {
            "Bad_Performance_Review": "0",
            "Contractors-UpComing_Termination": "0",
            "Domain_Admin": "0",
            "Employees-UpComing_Terminations": "0",
            "Exiting_Behavior_Watchlist": "0",
            "Flight_Risk_Users_Watchlist": "0",
            "Privileged_Accounts": "0",
            "Privileged_Users": "0",
            "Recent_Hires": "0",
            "Recent_Transfers": "0",
            "Terminated_Contractors": "0",
            "Terminated_Employees": "0",
            "Test_watchlist": "0",
            "Test_watchlist2": "0"
        }
    }
}
Human Readable Output

Watchlists: Domain_Admin, Flight_Risk_Users_Watchlist, Recent_Transfers, Exiting_Behavior_Watchlist, Test_watchlist2, Bad_Performance_Review, Terminated_Contractors, Contractors-UpComing_Termination, Privileged_Accounts, Terminated_Employees, Test_watchlist, Privileged_Users, Recent_Hires, Employees-UpComing_Terminations.

securonix-get-watchlist


Gets information for the specified watchlist.

Base Command

securonix-get-watchlist

Input
Argument Name Description Required
watchlist_name Watchlist name. Required
Context Output
Path Type Description
Securonix.Watchlists.TenantID String Watchlist tenant ID.
Securonix.Watchlists.Tenantname String Watchlist tenant name.
Securonix.Watchlists.Type String Watchlist type.
Securonix.Watchlists.Watchlistname String Watchlist name.
Securonix.Watchlists.Events.ExpiryDate String Expiration date of the entity in the watchlist, in Epoch time.
Securonix.Watchlists.Events.Workemail String Work email address of the entity in the watchlist.
Securonix.Watchlists.Events.Fullname String Full name of the entity in the watchlist.
Securonix.Watchlists.Events.Reason String Reason that the entity is in the watchlist.
Securonix.Watchlists.Events.LanID String Lan ID of the entity in the watchlist.
Securonix.Watchlists.Events.Lastname String Last name of the entity in the watchlist.
Securonix.Watchlists.Events.EntityName String Entity name of the entity in the watchlist.
Securonix.Watchlists.Events.Title String Title of the entity in the watchlist.
Securonix.Watchlists.Events.Firstname String First name of the entity in the watchlist.
Securonix.Watchlists.Events.EmployeeID String Employee ID of the entity in the watchlist.
Securonix.Watchlists.Events.Masked String Whether the entity in the watchlist is masked.
Securonix.Watchlists.Events.Division String Division of the entity in the watchlist.
Securonix.Watchlists.Events.Departmant String Department of the entity in the watchlist.
Securonix.Watchlists.Events.Status String Status of the entity in the watchlist.
Command Example

!securonix-get-watchlist watchlist_name="test_watchlist"

Context Example
{
    "Securonix": {
        "Watchlists": {
            "Events": [{
                "Createdate": "1678438310720",
                "Entityname": "123",
                "Expired": "false",
                "Expirydate": "1681084799000",
                "Reason": "Added from web service..!",
                "Resourcegroupid": "-1",
                "Resourcename": "123",
                "Uniqueid": "2^~R^~-1|123",
                "Updatedate": "1678438310720"
            }],
            "TenantID": "2",
            "TenantName": "test_tenant",
            "Type": "Resources",
            "Watchlistname": "test_watchlist"
          }
    }
}
Human Readable Output

Watchlist test_watchlist of type Resources

Entityname Expired
123 false

securonix-create-watchlist


Creates a watchlist in Securonix.

Base Command

securonix-create-watchlist

Input
Argument Name Description Required
watchlist_name The name of the watchlist. Required
tenant_name Name of the tenant the watchlist belongs to.

The tenant name parameter is required for MSSP users.
Optional
Context Output
Path Type Description
Securonix.Watchlists.Watchlistname String Name of the Watchlist.
Securonix.Watchlists.TenantName String Tenant Name.
Command Example

!securonix-create-watchlist watchlist_name=test_watchlist

Context Example
{
    "Securonix": {
        "Watchlists": "test_watchlist"
    }
}
Human Readable Output

Watchlist test_watchlist was created successfully.

securonix-check-entity-in-watchlist


Checks if the specified entity is in a watchlist.

Base Command

securonix-check-entity-in-watchlist

Input
Argument Name Description Required
entity_name The name of the entity to check. For example: 1002. Required
watchlist_name The name of the watchlist in which to check the entity. Required
Context Output
Path Type Description
Securonix.EntityInWatchlist.Watchlistnames String The names of the watchlists in which the entity appears.
Securonix.EntityInWatchlist.EntityID String The entity ID.
Command Example

!securonix-check-entity-in-watchlist entity_name=1002 watchlist_name=test_watchlist

Context Example
{
    "Securonix": {
        "EntityInWatchlist": {
            "Entityname": "1002"
        }
    }
}
Human Readable Output

Entity unique identifier 1002 provided is not in the watchlist: test_watchlist.

securonix-add-entity-to-watchlist


Adds an entity to a watchlist.

Base Command

securonix-add-entity-to-watchlist

Input
Argument Name Description Required
watchlist_name The name of the watchlist to which to add the entity. Required
entity_type The entity type. Can be “Users”, “Activityaccount”, “RGActivityaccount”, “Resources”, or “Activityip”. Required
entity_name The name of the entity to add to the watchlist. For example: 1022. Required
expiry_days The number of days after which the entity will be removed from the watchlist. The default value is “30”. Optional
Context Output

There is no context output for this command.

Command Example

!securonix-add-entity-to-watchlist entity_type=Resources entity_name=123 watchlist_name=test_watchlist

Context Example
{}
Human Readable Output

Added successfully the entity 123 to the watchlist test_watchlist.

securonix-create-incident


Creates an incident. For more information about the required arguments, see the Securonix documentation.

Base Command

securonix-create-incident

Input
Argument Name Description Required
violation_name The violation name or policy name. For example: “Uploads to personal Websites”. Required
resource_group The resource group name. For example: “BLUECOAT”, “Palo Alto Firewall”. Required
entity_type The entity type. Can be “Users”, “Activityaccount”, “RGActivityaccount”, “Resources”, or “Activityip”. Required
entity_name The entity name associated with the violation. Can be “LanID” or “Workemail”. For more information, see the Securonix documentation. Required
action_name The action name. Can be “Mark as concern and create incident”, “Non-Concern”, or “Mark in progress (still investigating)”. Required
resource_name The resource name. For example: “BLUECOAT”, “Palo Alto Firewall”. Required
criticality The incident severity (criticality) for the new incident. Can be “Low”, “High”, or “Critical”. Optional
comment A comment for the new incident. Optional
workflow The workflow name. This argument is optional, but required when the action_name argument is set to “Mark as concern and create incident”. Can be “SOCTeamReview”, “ActivityOutlierWorkflow”, or “AccessCertificationWorkflow”. Optional
Context Output
Path Type Description
Securonix.Incidents.ViolatorID String The ID of the incident violator.
Securonix.Incidents.Entity String The incident entity.
Securonix.Incidents.Riskscore Number The incident risk score.
Securonix.Incidents.Priority String The incident priority.
Securonix.Incidents.Reason String The reason that the incident was created. Usually includes the policy name and/or possible threat name.
Securonix.Incidents.IncidentStatus String The incident status.
Securonix.Incidents.WorkflowName String The incident workflow name.
Securonix.Incidents.Watchlisted Boolean Whether the incident is in a watchlist.
Securonix.Incidents.IncidentType String The incident type.
Securonix.Incidents.IncidentID String The incident ID.
Securonix.Incidents.LastUpdateDate Number The time when the incident was last updated, in Epoch time.
Securonix.Incidents.Url String The URL that links to the incident on Securonix.
Securonix.Incidents.ViolatorText String Text of the incident violator.
Securonix.Incidents.AssignedUser String The user assigned to the incident.
Securonix.Incidents.IsWhitelisted Boolean Whether the incident is added to allow list.
Command Example

!securonix-create-incident action_name="Mark as concern and create incident" entity_name=MH1014 entity_type=Users resource_group="BLUECOAT" resource_name="BLUECOAT" violation_name="Uploads to personal Websites" workflow=SOCTeamReview comment=bgdfs criticality=Critical

Context Example
{
    "Securonix": {
        "Incidents": {
            "AssignedUser": "Admin Admin",
            "Casecreatetime": 1579687771677,
            "Entity": "Users",
            "IncidentID": "30134",
            "IncidentStatus": "Open",
            "IncidentType": "Policy",
            "IsWhitelisted": false,
            "LastUpdateDate": 1579687771677,
            "ParentCaseId": "",
            "Priority": "Critical",
            "Reason": [
                "Resource: BLUECOAT",
                "Policy: Uploads to personal websites",
                "Threat: Data egress via network uploads"
            ],
            "Riskscore": 0,
            "SandBoxPolicy": false,
            "StatusCompleted": false,
            "TenantInfo": {
                "tenantcolor": "#000000",
                "tenantid": 1,
                "tenantname": "Securonix",
                "tenantshortcode": "SE"
            },
            "Url": "{url}",
            "ViolatorID": "14",
            "ViolatorSubText": "1014",
            "ViolatorText": "john doe",
            "Watchlisted": false,
            "WorkflowName": "SOCTeamReview"
        }
    }
}
Human Readable Output

Incident was created successfully

Entity Incident Status Incident Type IncidentID Priority Reason Url
Users Open Policy 30134 Critical Resource: BLUECOAT,Policy: Uploads to personal websites,Threat: Data egress via network uploads url

securonix-threats-list


Retrieve a list of threats violated within a specified time range and get details about
the threat models and policies violated.

Base Command

securonix-threats-list

Input
Argument Name Description Required
date_from Start time range for which to return threats (Supported formats: 2 minutes, 2 hours, 2 days, 2 weeks, 2 months, 2 years, yyyy-mm-dd, yyyy-mm-ddTHH:MM:SSZ For example: 01 Jan 2023, 01 Feb 2023 04:45:33, 2023-01-26T14:05:44Z) Required
date_to End date/time for which to retrieve threats (Supported formats: 2 minutes, 2 hours, 2 days, 2 weeks, 2 months, 2 years, yyyy-mm-dd, yyyy-mm-ddTHH:MM:SSZ For example: 01 Jan 2023, 01 Feb 2023 04:45:33, 2023-01-26T14:05:44Z) Default is current time. Optional
page_size The number of results to retrieve. Default is 10. Optional
tenant_name Name of the tenant to fetch threats from. This parameter is optional for Non MSSP users. Optional
offset Sets the starting index for the returned results. Optional
Context Output
Path Type Description
Securonix.Threat.TenantID Number Tenant ID.
Securonix.Threat.Tenantname String Tenant Name.
Securonix.Threat.Violator String Violator of threat.
Securonix.Threat.EntityID String Threat entity ID.
Securonix.Threat.Resourcegroupname String Name of the resource group.
Securonix.Threat.ThreatName String Threat Name.
Securonix.Threat.Category String Category of threat.
Securonix.Threat.Resourcename String Name of the resource.
Securonix.Threat.Resourcetype String Type of the resource.
Securonix.Threat.GenerationTime Date Date and Time when the threat is generated.
Securonix.Threat.GenerationTime_Epoch Number Epoch time when the threat is generated.
Securonix.Threat.Policies Unknown List of policies violated.
Securonix.Threat.Policystarttime Number Epoch time when the policy is first violated.
Securonix.Threat.Policyendtime Number Epoch time when the policy is last violated.
Securonix.Threat.Solrquery String Spotter query to fetch the related violations.
Command Example

!securonix-threats-list date_from="1 day"

Context Example
{
    "Securonix": {
        "Threats": {
            "TenantID": 2,
            "Tenantname": "Response-Automation",
            "Violator": "Activityaccount",
            "EntityID": "VIOLATOR5-1673852881421",
            "Resourcegroupname": "RES-PLAYBOOK-DS-AUTOMATION",
            "ThreatName": "TM_Response-PB-ActivityAccount-Manual",
            "Category": "NONE",
            "Resourcename": "RES10-RESOURCE-302184",
            "Resourcetype": "Res-Playbook",
            "GenerationTime": "Mon, 16 Jan 2023 @ 01:53:31 AM",
            "GenerationTime_Epoch": 1673855611090,
            "Policies": [
                "Response-PB-ActivityAccount-Manual"
            ],
            "policystarttime": 1661161072000,
            "policyendtime": 1661161072000,
            "solrquery": "index = violation and ( ( @policyname = \"Response-PB-ActivityAccount-Manual\" and @ipaddress=\"127.0.0.1\" )  ) AND @tenantname=\"Response-Automation\" AND datetime between \"08/22/2022 04:37:52\" \"08/22/2022 04:37:53\""
        }
    }
}
Human Readable Output

Threats

ThreatName EntityID Violator Category Resourcegroupname Resourcename Resourcetype GenerationTime Policies TenantID Tenantname
TM_Response-PB-ActivityAccount-Manual VIOLATOR5-1673852881421 Activityaccount NONE RES-PLAYBOOK-DS-AUTOMATION RES10-RESOURCE-302184 Res-Playbook Mon, 16 Jan 2023 @ 01:53:31 AM Response-PB-ActivityAccount-Manual 2 Response-Automation

securonix-incident-activity-history-get


Retrieves incident activity history for a specified incident.

Base Command

securonix-incident-activity-history-get

Input

Argument Name Description Required
incident_id Incident ID for which to retrieve the activity history. Required

Context Output

Path Type Description
Securonix.IncidentHistory.caseid Number Incident ID.
Securonix.IncidentHistory.actiontaken String The type of action taken.
Securonix.IncidentHistory.status String The status of the incident.
Securonix.IncidentHistory.comment.Comments String Comment text.
Securonix.IncidentHistory.eventTime Number Timestamp in epoch when the action is taken.
Securonix.IncidentHistory.username String Username of the person who carried out the action.
Securonix.IncidentHistory.currentassignee String The current assignee of the incident.
Securonix.IncidentHistory.commentType String The type of the comment.
Securonix.IncidentHistory.currWorkflow String The current workflow of the incident.
Securonix.IncidentHistory.isPlayBookOutAvailable Boolean Whether or not the playbook is available.
Securonix.IncidentHistory.creator String The creator of the activity.
Securonix.IncidentHistory.lastStatus String The previous status of the incident.
Securonix.IncidentHistory.pastassignee String The previous assignee of the incident.
Securonix.IncidentHistory.prevWorkflow String The previous workflow of the incident.
Securonix.IncidentHistory.attachment String The name of the attached file.
Securonix.IncidentHistory.attachmentType String The type of the attachment.
Securonix.IncidentHistory.playBookOutput.playBookId Number The ID of the playbook.
Securonix.IncidentHistory.playBookOutput.playBookName String The name of the playbook.
Securonix.IncidentHistory.playBookOutput.playRunId String The playbook run ID.
Securonix.IncidentHistory.playBookOutput.executorId Number The ID of the executor.
Securonix.IncidentHistory.playBookOutput.executor String The name of the executor.
Securonix.IncidentHistory.playBookOutput.tasksForParticularRun.taskName String The name of the playbook task.
Securonix.IncidentHistory.playBookOutput.tasksForParticularRun.description String The description of the playbook task.
Securonix.IncidentHistory.playBookOutput.tasksForParticularRun.icon String Playbook icon.
Securonix.IncidentHistory.playBookOutput.tasksForParticularRun.taskId Number The ID of the playbook task.
Securonix.IncidentHistory.playBookOutput.tasksForParticularRun.lastExecutedTime Date The last execution time in epoch.
Securonix.IncidentHistory.playBookOutput.tasksForParticularRun.lastStatus String The last status of the playbook.
Securonix.IncidentHistory.playBookOutput.tasksForParticularRun.executedTask.executionId String The execution ID of the playbook.
Securonix.IncidentHistory.playBookOutput.tasksForParticularRun.executedTask.taskStartTime Date The start time of the task.
Securonix.IncidentHistory.playBookOutput.tasksForParticularRun.executedTask.taskEndTime Date The end time of the task.
Securonix.IncidentHistory.playBookOutput.tasksForParticularRun.executedTask.status String The status of the task.
Securonix.IncidentHistory.playBookOutput.tasksForParticularRun.connectionMetadata String Connection metadata.

Command example

!securonix-incident-activity-history-get incident_id=3235505

Context Example

{
    "Securonix": {
        "IncidentHistory": [
            {
                "caseid": "3235505",
                "eventTime": "Jan 18, 2023 2:34:21 AM",
                "isPlayBookOutAvailable": true,
                "playBookOutput": {
                    "executor": "Admin Admin",
                    "executorId": 41,
                    "playBookId": 104,
                    "playBookName": "Create Security Incident",
                    "playRunId": "Playbook Run ID",
                    "tasksForParticularRun": [
                        {
                            "connectionMetadata": "{\"source\":\"test\"}",
                            "description": "Create Security incidents",
                            "executedTask": {
                                "executionId": "104-c08be5e4-480d-4105-9ed7-232cf902fa83",
                                "status": "FINISHED",
                                "taskEndTime": 1674009264968,
                                "taskInfo": {
                                    "Incident created": [
                                        {
                                            "highlight": false,
                                            "isurl": false,
                                            "key": "INCIDENT NUMBER",
                                            "showonui": true,
                                            "value": "0724324"
                                        },
                                        {
                                            "highlight": false,
                                            "isurl": false,
                                            "key": "sys_id",
                                            "showonui": false,
                                            "value": "61a6b780dbeca910dabd266e13961933"
                                        },
                                        {
                                            "highlight": false,
                                            "isurl": false,
                                            "key": "lastViolationTime",
                                            "showonui": false,
                                            "value": "1674009261355"
                                        },
                                        {
                                            "highlight": false,
                                            "isurl": true,
                                            "key": "INCIDENT URL",
                                            "showonui": true,
                                            "value": "<URL of Securonix platform>"
                                        },
                                        {
                                            "highlight": false,
                                            "isurl": false,
                                            "key": "VIOLATION_COUNT",
                                            "showonui": true,
                                            "value": "15"
                                        }
                                    ]
                                },
                                "taskStartTime": 1674009261396
                            },
                            "icon": "test",
                            "lastExecutedTime": 1674009261396,
                            "lastStatus": "FINISHED",
                            "taskId": 104,
                            "taskName": "Create Security Incident"
                        }
                    ]
                }
            },
            {
                "actiontaken": "CREATED",
                "caseid": "3235505",
                "comment": [
                    {
                        "Comments": "Incident created while executing playbook - Create Security Incident"
                    }
                ],
                "commentType": [
                    "text"
                ],
                "creator": "admin",
                "currWorkflow": "SOCTeamReview",
                "currentassignee": "API_TEST_SS",
                "eventTime": "Jan 18, 2023 2:34:22 AM",
                "isPlayBookOutAvailable": false,
                "status": "Open",
                "username": "Admin Admin"
            },
            {
                "actiontaken": "CLOSE AS FIXED",
                "caseid": "3235505",
                "comment": [
                    {
                        "Comments": "Incident closed as part of AutoClosure"
                    }
                ],
                "commentType": [
                    "text"
                ],
                "creator": "admin",
                "currWorkflow": "DEFAULTWORKFLOW",
                "currentassignee": "API_TEST_SS",
                "eventTime": "Jan 20, 2023 5:08:42 AM",
                "isPlayBookOutAvailable": false,
                "lastStatus": "Open",
                "pastassignee": "API_TEST_SS",
                "prevWorkflow": "SOCTeamReview",
                "status": "COMPLETED",
                "username": "Admin Admin"
            }
        ]
    }
}

Human Readable Output

Incident activity history for ID: 3235505

Action Taken Username Event Time Status Last Status Comment Playbook ID Playbook Name Playbook Executor
CLOSE AS FIXED Admin Admin Jan 20, 2023 5:08:42 AM COMPLETED Open Incident closed as part of AutoClosure      
CREATED Admin Admin Jan 18, 2023 2:34:22 AM Open   Incident created while executing playbook - Create Security Incident      
    Jan 18, 2023 2:34:21 AM       104 Create Security Incident Admin Admin

securonix-incident-attachment-get


Retrieves the attachments available on the Securonix platform.

Base Command

securonix-incident-attachment-get

Input

Argument Name Description Required
incident_id Incident ID for which to retrieve the attachments. Required
attachment_type The type of attachment to retrieve. Supported options are csv, pdf, and txt. Comma-separated values are supported. Optional
from Start time for which to retrieve attachments.(Supported formats: 2 minutes, 2 hours, 2 days, 2 weeks, 2 months, 2 years, yyyy-mm-dd, yyyy-mm-ddTHH:MM:SSZ, yyyy-MM-ddTHH:mm:ss.SSSZ. For example: 01 Jan 2023, 01 Feb 2023 04:45:33, 2023-01-26T14:05:44Z, 2023-01-26T14:05:44.000Z) Optional
to End time for which to retrieve attachments.(Supported formats: 2 minutes, 2 hours, 2 days, 2 weeks, 2 months, 2 years, yyyy-mm-dd, yyyy-mm-ddTHH:MM:SSZ, yyyy-MM-ddTHH:mm:ss.SSSZ. For example: 01 Jan 2023, 01 Feb 2023 04:45:33, 2023-01-26T14:05:44Z, 2023-01-26T14:05:44.000Z) Optional

Context Output

Path Type Description
File.Size Number The size of the file in bytes.
File.SHA1 String The SHA1 hash of the file.
File.SHA256 String The SHA256 hash of the file.
File.SHA512 String The SHA512 hash of the file.
File.Name String The name of the file.
File.SSDeep String The SSDeep hash of the file.
File.EntryID String The entry ID of the file.
File.Info String File information.
File.Type String The file type.
File.MD5 String The MD5 hash of the file.
File.Extension String The file extension.
Securonix.Incidents.Attachments.IncidentID String Attachment Incident ID.
Securonix.Incidents.Attachments.Files Unknown Attachment File names.

Command example

!securonix-incident-attachment-get incident_id=12345678 from=2023-01-27T07:25:00Z to=2023-01-27T08:25:00Z

Context Example

{
    "Securonix": {
        "Incidents": {
            "Attachments": [
                {
                    "Files": "REST_API_Categories___SNYPR_6.4.pdf",
                    "IncidentID": 3422464053
                }
            ]
        }
    },
    "File": {
        "Type": "application/pdf",
        "Size": "6,157,973 bytes",
        "Info": "PDF document, version 1.4",
        "MD5": "ee0e57a311beb1c9a326b921625d31e4",
        "SHA1": "e60bb6364981039bca21285a5c35a41afcbcdbb",
        "SHA256": "b1c383bb218218b5a816841a7a91f1dcab08c1034d434fcefab70b4d804b7cc",
        "SHA512": "0c2208f9dd5c65b18fb88dc8dec81c412e2b22b6122f837827079c7dad9b27c5d691a4d09edf3583a8313fae2a7a620c86ff4a186e46273970e542d42ca4bb0",
        "SSDeep": "98304:EIkHaH04jKTu8dEp/i6fVm+RG9de2VaqhEIrST6k2WLcJVHvLx4jO1mzPyX:BYgjfyE46fbRGze2gTILk+VHvLx719",
    }
}

Human Readable Output

Incident ID: 3235505

Uploaded file: REST_API_Categories___SNYPR_6.4.pdfDownload

Property Type Size Info MD5 SHA1 SHA256 SHA512 SSDeep
Value application/pdf 6,157,973 bytes PDF document, version 1.4 ee0e57a311beb1c9a326b921625d31e4 ae60bb6364981039bca21285a5c35a41afcbcdbb 8b1c383bb218218b5a816841a7a91f1dcab08c1034d434fcefab70b4d804b7cc 60c2208f9dd5c65b18fb88dc8dec81c412e2b22b6122f837827079c7dad9b27c5d691a4d09edf3583a8313fae2a7a620c86ff4a186e46273970e542d42ca4bb0 98304:EIkHaH04jKTu8dEp/i6fVm+RG9de2VaqhEIrST6k2WLcJVHvLx4jO1mzPyX:BYgjfyE46fbRGze2gTILk+VHvLx719

securonix-whitelists-get


Gets a list of whitelists.

Base Command

securonix-whitelists-get

Input

Argument Name Description Required
tenant_name Name of the tenant the whitelist belongs to.

The tenant name parameter is required for MSSP users.
Optional

Context Output

Path Type Description
Securonix.Whitelist.WhitelistName String Name of the Whitelist.
Securonix.Whitelist.TenantName String Tenant Name.
Securonix.Whitelist.WhitelistType String Type of the Whitelist.

Command example


#### Context Example

```json
{
    "Securonix": {
        "Whitelist": [{
        "WhitelistName": "Dummy Whitelist 1",
        "WhitelistType": "Automated",
        "TenantName": "test_tenant"
      },
      {
        "WhitelistName": "Dummy Whitelist 2",
        "WhitelistType": "Automated",
        "TenantName": "test_tenant"
      }]
    }
}

Human Readable Output

Whitelists

WhitelistName WhitelistType TenantName
Dummy Whitelist 1 Automated test_tenant
Dummy Whitelist 2 Automated test_tenant

securonix-whitelist-entry-list


Gets information for the specified whitelist.

Base Command

securonix-whitelist-entry-list

Input

Argument Name Description Required
tenant_name Name of the tenant the whitelist belongs to.

The tenant name parameter is required for MSSP users.
Optional
whitelist_name Name of the whitelist that the user wants to list. Required

Context Output

Path Type Description
Securonix.Whitelist.WhitelistName String Name of the Whitelist.
Securonix.Whitelist.TenantName String Tenant Name.
Securonix.Whitelist.Entries.Entity/Attribute String Entity/Attribute which is whitelisted.
Securonix.Whitelist.Entries.ExpiryDate Date The date when the entity will be removed from the whitelist.

Command example

!securonix-whitelist-entry-list whitelist_name="test_whitelist"

Context Example

{
    "Securonix": {
        "Whitelist": {
            "Entries": [
              {
                "Entity/Attribute": "TEST123",
                "ExpiryDate": "09/28/2035 21:21:19"
              }
            ],
            "TenantName": "TenantAug02",
            "Whitelistname": "test_whitelist"
        }
    }
}

Human Readable Output

Whitelist: Dummy Threat Model MM

Entity/Attribute ExpiryDate
TEST123 09/28/2035 21:21:19

securonix-xsoar-state-mapping-get


Returns the state mapping of XSOAR with Securonix.

Base Command

securonix-xsoar-state-mapping-get

Input

This command does not have any arguments.

Context Output

Path Type Description
Securonix.StateMapping.ACTIVE.action String Securonix action name to map with XSOAR’s active state.
Securonix.StateMapping.ACTIVE.status String Securonix status to map with XSOAR’s active state.
Securonix.StateMapping.DONE.action String Securonix action name to map with XSOAR’s closed state.
Securonix.StateMapping.DONE.status String Securonix status to map with XSOAR’s closed state.

Command example


#### Context Example

```json
{
  "Securonix": {
    "StateMapping": {
      "ACTIVE": {
        "action": "Start Investigation",
        "status": "in progress"
      },
      "DONE": {
        "action": "Close Incident",
        "status": "completed"
      }
    }
  }
}

Human Readable Output

State Mapping

XSOAR Status Securonix Status Securonix Action Name
Active in progress Start Investigation
Closed completed Close Incident

securonix-whitelist-create


Creates a whitelist in Securonix.

Base Command

securonix-whitelist-create

Input

Argument Name Description Required
tenant_name Name of the tenant the whitelist belongs to.

The tenant name parameter is required for MSSP users.
Optional
whitelist_name Name of the whitelist that the user wants to create. Required
entity_type Type of entity that the whitelist is intended to hold. Optional
Context Output

There is no context output for this command.

Command Example

!securonix-whitelist-create whitelistname="test_whitelist" entity_type="Users"

Context Example
{}
Human Readable Output

Whitelist test_whitelist was created successfully.

securonix-whitelist-entry-add


Add entity or attribute to the specified whitelist entry.

Base Command

securonix-whitelist-entry-add

Input

Argument Name Description Required
tenant_name Name of the tenant the whitelist belongs to.

The tenant name parameter is required for MSSP users.
Optional
whitelist_name The name of the whitelist to which data is being added.

If whitelist_type is Attribute, then whitelist_name and violation_name should be the same.
Required
whitelist_type The type of the whitelist to which data is being added. (Supported Values are Global and Attribute.) Required
entity_type The type of entity being added. (Supported values are Users, Activityaccount, Activityip, Resources).

This parameter is required if whitelist_type is Global.
Optional
entity_id ID of the entity being added.

This parameter is required if whitelist_type is Global.
Optional
expiry_date The date when the entity will be removed from the whitelist.(In MM/DD/YYYY format) Optional
resource_name The resource name to which the account belongs.

This parameter is required if whitelist_type is Global and entity_type is Activityaccount.
Optional
resource_group_id The resource group id to which the account belongs.

This parameter is required if whitelist_type is Global and entity_type is Activityaccount.
Optional
attribute_name Name of the attribute being added. (Supported values are source ip, resourcetype,transactionstring)

This parameter is required if whitelist_type is Attribute.
Optional
attribute_value The attribute value being added.

This parameter is required if whitelist_type is Attribute.
Optional
violation_type Type of the violation. (Supported Values are Policy,ThreatModel,Functionality.)

This parameter is required if whitelist_type is Attribute.
Optional
violation_name Name of the violations. (Supported values are Policy names, ThreatModel names, Functionality names)

This parameter is required if whitelist_type is set to Attribute, and is the same as the whitelist name parameter.
Optional

Context Output

There is no context output for this command.

Command example

!securonix-whitelist-entry-add whitelist_name=whitelistdemo_Activityip whitelist_type=Global tenant_name=test_tenant entity_type=Activityip entity_id=0.0.0.1 expiry_date=04/02/2023

Context example

{}

Human Readable Output

Entity added to global whitelist Successfully.

securonix-lookup-table-create


Creates a lookup table.

Base Command

securonix-lookup-table-create

Input

Argument Name Description Required
name Name of the lookup table to create. Required
scope Scope of the lookup table. This argument is mandatory for MSSP users. Optional
tenant_name Name of the tenant in which to create a lookup table. This argument is mandatory for MSSP users. Optional
field_names A comma-separated string of column names. Required
encrypt A comma-separated string of column names for which data needs to be encrypted. Optional
key A comma-separated string of column names to be used as key Required
Context Output

There is no context output for this command.

Command Example

!securonix-lookup-table-create name=test_lookup_table field_names="samplefield,samplefield2" key="samplefield" tenant_name=test_tenant scope=Global

Context Example
{}
Human Readable Output

Lookup Table test_lookup_table created successfully

securonix-lookup-table-config-and-data-delete


Deletes the data and configuration of the provided lookup table.

Base Command

securonix-lookup-table-config-and-data-delete

Input

Argument Name Description Required
name Name of the lookup table to delete. Required

Context Output

Path Type Description
Securonix.LookupTable.lookupTableName String Name of the lookup table.
Securonix.LookupTable.isDeleted Boolean True, if the lookup table data and configuration deleted successfully.
Command Example

!securonix-lookup-table-config-and-data-delete name="test"

Context Example
{
  "Securonix": {
    "LookupTable": {
      "lookupTableName": "test",
      "isDeleted": true
    }
  }    
}
Human Readable Output

test and data deleted successfully

securonix-lookup-tables-list


Retrieves a list of lookup tables available within the Securonix platform.

Base Command

securonix-lookup-tables-list

Input

Argument Name Description Required
max Number of records to return. Optional
offset Specify from which record the data should be returned. Optional

Context Output

Path Type Description
Securonix.LookupTable.tenantName String Name of the tenant.
Securonix.LookupTable.lookupTableName String Name of the lookup table.
Securonix.LookupTable.totalRecords Number Number of records in the lookup table.
Securonix.LookupTable.scope String Scope of the lookup table.
Securonix.LookupTable.type String Type of the lookup table.

Command example

!securonix-lookup-tables-list max=2 offset=0

Context Example

{
    "Securonix": {
        "LookupTable": [
            {
                "lookupTableName": "NonBusinessDomains",
                "scope": "global",
                "tenantName": "All Tenants",
                "totalRecords": 2213,
                "type": "system"
            },
            {
                "lookupTableName": "CompressedFileExtensions",
                "scope": "meta",
                "tenantName": "All Tenants",
                "totalRecords": 240,
                "type": "system"
            }
        ]
    }
}

Human Readable Output

Lookup Tables

Tenant Name Lookup Table Name Total Records Scope Type of Lookup Table
All Tenants NonBusinessDomains 2213 global system
All Tenants CompressedFileExtensions 240 meta system

securonix-whitelist-entry-delete


Remove entity or attribute from the specified whitelist entry.

Base Command

securonix-whitelist-entry-delete

Input

Argument Name Description Required
tenant_name Name of the tenant the whitelist belongs to. Required
whitelist_name Name of the whitelist the user wants to delete the value from. Required
whitelist_type Type of whitelist that user wants to delete from. Possible values are: Global, Attribute. Optional
entity_id Entity ID value that needs to be removed from the whitelist.

This parameter is required if whitelist_type is set to “Global”.

Example:
- employeeId for type User
- accountname for type ActivityAccount
- resourcename for type Resources
- ipadress for type IpAddress.
Optional
attribute_name Name of the attribute being removed.

This parameter is required if whitelist_type is set to “Attribute”.

Example:
- accountname
- transactionstring
- sourcetype.
Optional
attribute_value The value of the attribute being removed.

This parameter is required if whitelist_type is “Attribute”.
Optional

Context Output

There is no context output for this command.

Command Example

!securonix-whitelist-entry-delete whitelistname="test_whitelist" entity_id="test_user"

Context Example
{}
Human Readable Output

test_user Item removed from whitelist Successfully.

securonix-lookup-table-entries-list


Retrieves the entries stored in a specified lookup table.

Base Command

securonix-lookup-table-entries-list

Input

Argument Name Description Required
name Lookup Table name. Required
query Use to filter the records. By default it will filter the records on key. To filter on other column use attribute argument. Optional
attribute Column name on which to filter the data. Default is key. Optional
max Number of records to retrieve. Default is 15. Optional
offset Specify from which record the data should be returned. Default is 0. Optional
page_num Specify a value to retrieve records from a specific page. Default is 1. Optional
sort Name of the column on which to sort the data. By default the data will be sorted on the key. Optional
order The order in which to sort the data. By default the data will be sorted in ascending order. Possible values are: asc, desc. Default is asc. Optional

Context Output

Path Type Description
Securonix.LookupTableEntries.lookupname String Name of the lookup table.
Securonix.LookupTableEntries.tenantid Number ID of the tenant.
Securonix.LookupTableEntries.lookupuniquekey String Unique key of the entry.
Securonix.LookupTableEntries.timestamp String The UTC timestamp indicates when the entry was added.
Securonix.LookupTableEntries.key String The value of the key field.
Securonix.LookupTableEntries.defaultenrichedevent Unknown Entry data.
Securonix.LookupTableEntries.tenantname String Name of the tenant.
Securonix.LookupTableEntries.entry.key String Key of the entry.
Securonix.LookupTableEntries.entry.value String Value of the entry.

Command example

!securonix-lookup-table-entries-list name="TEST_M" max=2 sort="ip" order=desc

Context Example

{
    "Securonix": {
        "LookupTableEntries": [
            {
                "defaultenrichedevent": [
                    "127.0.0.9",
                    "B5E3195FB4E1EB3BE797077CEE398C04",
                    "bad"
                ],
                "entry": [
                    {
                        "key": "reputation",
                        "value": "bad"
                    },
                    {
                        "key": "ip",
                        "value": "127.0.0.9"
                    },
                    {
                        "key": "id",
                        "value": "B5E3195FB4E1EB3BE797077CEE398C04"
                    }
                ],
                "key": "B5E3195FB4E1EB3BE797077CEE398C04",
                "lookupname": "TEST_M",
                "lookupuniquekey": "2^~TEST_M|B5E3195FB4E1EB3BE797077CEE398C04",
                "tenantid": 2,
                "tenantname": "novr3nonmssp",
                "timestamp": "Feb 18, 2023 5:50:16 AM"
            },
            {
                "defaultenrichedevent": [
                    "127.0.0.8",
                    "497B9EC67354A28D62A98E82299871CC",
                    "good"
                ],
                "entry": [
                    {
                        "key": "reputation",
                        "value": "good"
                    },
                    {
                        "key": "ip",
                        "value": "127.0.0.8"
                    },
                    {
                        "key": "id",
                        "value": "497B9EC67354A28D62A98E82299871CC"
                    }
                ],
                "key": "497B9EC67354A28D62A98E82299871CC",
                "lookupname": "TEST_M",
                "lookupuniquekey": "2^~TEST_M|497B9EC67354A28D62A98E82299871CC",
                "tenantid": 2,
                "tenantname": "novr3nonmssp",
                "timestamp": "Feb 18, 2023 5:50:16 AM"
            }
        ]
    }
}

Human Readable Output

Entries

Key Lookup Unique Key Tenant Name Timestamp id ip reputation
B5E3195FB4E1EB3BE797077CEE398C04 2^~TEST_M|B5E3195FB4E1EB3BE797077CEE398C04 novr3nonmssp Feb 18, 2023 5:50:16 AM B5E3195FB4E1EB3BE797077CEE398C04 127.0.0.9 bad
497B9EC67354A28D62A98E82299871CC 2^~TEST_M|497B9EC67354A28D62A98E82299871CC novr3nonmssp Feb 18, 2023 5:50:16 AM 497B9EC67354A28D62A98E82299871CC 127.0.0.8 good

securonix-lookup-table-entries-delete


Deletes the entries from the lookup table.

Base Command

securonix-lookup-table-entries-delete

Input

Argument Name Description Required
lookup_unique_keys Comma-separated list of lookup unique keys to delete. Required
name Name of the lookup table from which to delete the entries. Required

Context Output

There is no context output for this command.

Command example

!securonix-lookup-table-entries-delete name=TEST_M lookup_unique_keys=2^~TEST_M|158267F0BC6E7484E3C0F5964ABE9D2B,2^~TEST_M|175A9FFD55480ED376C992AC86ABE3D7

Context Example
{}

Human Readable Output

Successfully deleted following entries from TEST_M: 2^~TEST_M 158267F0BC6E7484E3C0F5964ABE9D2B, 2^~TEST_M 175A9FFD55480ED376C992AC86ABE3D7.

securonix-lookup-table-entry-add


Add entries to the provided lookup table.

Base Command

securonix-lookup-table-entry-add

Input

Argument Name Description Required
name Lookup Table name to which the data needs to be added. Required
tenant_name Name of the tenant to which the lookup table belongs. This argument is required for MSSP users and if the scope of the lookup table is “Meta”. Optional
json_data JSON formatted string containing the field names and values in the below format. To specify an expiration date for an entry, add “expiryDate” key (in the format of “MM/DD/YYYY”) in the respective JSON object.

E.g. [{“field1”: “Value1”, “field2”: “Value2”}, {“field1”: “Value3”, “field2”: “Value4”}].
Optional
file_entry_id War room entry of the file. To specify an expiration date for an entry, add “expiryDate” key (in the format of “MM/DD/YYYY”) in the respective JSON object. Optional

Context Output

There is no context output for this command.

Command example

!securonix-lookup-table-entry-add name="TEST_TABLE" json_data="[{\"id\": \"1\",\"ip\": \"127.0.0.1\",\"reputation\": \"bad\",\"expiryDate\":\"02/13/2023\"},{\"id\": \"2\",\"ip\": \"127.0.0.2\",\"reputation\": \"good\"}]"

Human Readable Output

Entries added to TEST_TABLE successfully

Configuration parameters

  • host — Host (Overrides the default hostname, https://{tenant}.net/Snypr)
  • tenant — Tenant (required)
  • username — Username (required)
  • password — Password (required)
  • entity_type_to_fetch — Type of entity to fetch
  • tenant_name — Tenant Name
  • isFetch — Fetch incidents
  • incident_status — Incidents to fetch
  • default_severity — Set default incident severity
  • incidentType — Incident type
  • incidentFetchInterval — Incidents Fetch Interval
  • fetch_time — First fetch time range
  • max_fetch — The maximum number of incidents to fetch each time.
  • mirror_direction — Incident Mirroring Direction
  • close_incident — Close respective Securonix incident after fetching
  • close_states_of_securonix — Securonix workflow state(s) that can be considered as Close state in XSOAR for Incoming mirroring
  • active_state_action_mapping — Securonix action name to map with XSOAR's active state for Outgoing mirroring
  • active_state_status_mapping — Securonix status to map with XSOAR's active state for Outgoing mirroring
  • closed_state_action_mapping — Securonix action name to map with XSOAR's closed state for Outgoing mirroring
  • closed_state_status_mapping — Securonix status to map with XSOAR's closed state for Outgoing mirroring
  • comment_tag — Comment Entry Tag
  • securonix_retry_count — Securonix Retry Count
  • securonix_retry_delay — Securonix Retry Delay
  • securonix_retry_delay_type — Securonix Retry Delay Type
  • unsecure — Trust any certificate (not secure)
  • proxy — Use system proxy settings

Commands (36)

  • securonix-add-comment-to-incident

    Adds a comment to the specified incident.

  • securonix-add-entity-to-watchlist

    Adds an entity to a watchlist.

  • securonix-check-entity-in-watchlist

    Checks if the specified entity is in a watchlist.

  • securonix-create-incident

    Creates an incident. For more information about the required arguments, see the Securonix documentation.

  • securonix-create-watchlist

    Creates a watchlist in Securonix.

  • securonix-get-default-assignee-for-workflow

    Gets the default assignee for the specified workflow.

  • securonix-get-incident

    Gets details of the specified incident.

  • securonix-get-incident-available-actions

    Gets a list of available actions for the specified incident.

  • securonix-get-incident-status

    Gets the status of the specified incident.

  • securonix-get-incident-workflow

    Gets the workflow of the specified incident.

  • securonix-get-watchlist

    Gets information for the specified watchlist.

  • securonix-incident-activity-history-get

    Retrieves incident activity history for a specified incident.

  • securonix-incident-attachment-get

    Retrieves the attachments available on the Securonix platform.

  • securonix-list-activity-data

    Gets a list of activity data for the specified resource group.

  • securonix-list-incidents

    Gets a list of incidents.

  • securonix-list-policies

    Gets a list of all policies.

  • securonix-list-possible-threat-actions

    Gets a list available threat actions.

  • securonix-list-resource-groups

    Gets a list of resource groups.

  • securonix-list-users

    Gets a list of users.

  • securonix-list-violation-data

    Gets a list activity data for an account name.

  • securonix-list-watchlists

    Gets a list of watchlists.

  • securonix-list-workflows

    Gets a list of all available workflows.

  • securonix-lookup-table-config-and-data-delete

    Deletes a lookup table with its data and configuration.

  • securonix-lookup-table-create

    Creates a lookup table.

  • securonix-lookup-table-entries-delete

    Deletes the entries from the lookup table.

  • securonix-lookup-table-entries-list

    Retrieves the entries stored in a specified lookup table.

  • securonix-lookup-table-entry-add

    Add entries to the provided lookup table.

  • securonix-lookup-tables-list

    Retrieves a list of lookup tables available within the Securonix platform.

  • securonix-perform-action-on-incident

    Performs an action on the specified incident.

  • securonix-threats-list

    Retrieve a list of threats violated within a specified time range and get details about the threat models and policies violated.

  • securonix-whitelist-create

    Creates a whitelist in Securonix.

  • securonix-whitelist-entry-add

    Add entity or attribute to the specified whitelist entry.

  • securonix-whitelist-entry-delete

    Remove entity or attribute from the specified whitelist entry.

  • securonix-whitelist-entry-list

    Gets information for the specified whitelist.

  • securonix-whitelists-get

    Gets a list of whitelists.

  • securonix-xsoar-state-mapping-get

    Returns the state mapping of XSOAR with Securonix.

import io
import json
from collections.abc import Callable
from datetime import datetime
from itertools import takewhile
from typing import Any
from zipfile import ZipFile

import dateparser
import demistomock as demisto  # noqa: F401
import urllib3
from CommonServerPython import *  # noqa: F401
from dateutil.parser import parse

# Disable insecure warnings
urllib3.disable_warnings()

# These parameters will be used for retry mechanism logging
TOTAL_RETRY_COUNT = 0
FULL_URL = None

# Valid Entity Type for Whitelists
VALID_ENTITY_TYPE = ["Users", "Activityaccount", "Resources", "Activityip"]

# Valid Whitelist Types
VALID_WHITELIST_TYPE = ["Global", "Attribute"]

# Special characters for spotter query
SPOTTER_SPECIAL_CHARACTERS = ["\\", "*", "?"]

# Markdown characters.
MARKDOWN_CHARS = r"\*_{}[]()#+-!"

# Mapping of user input of mirroring direction to XSOAR.
MIRROR_DIRECTION = {"None": None, "Incoming": "In", "Outgoing": "Out", "Incoming And Outgoing": "Both"}
# If any comment is added to the incident, then this will be the action we'll get through incident activity history
# command.
COMMENT_ACTION = "COMMENTS_ADDED"
# If any file is attached to the incident, then this will be the action we'll get through incident activity history
# command.
ATTACHMENT_ACTION = "ATTACHED_FILE"
# This will store the state mapping of XSOAR states with Securonix states.
XSOAR_TO_SECURONIX_STATE_MAPPING: dict = {}
# Policy types for which retry should have end time to the current time.
POLICY_TYPES_TO_RETRY = ["DIRECTIVE", "LAND SPEED", "TIER2", "BEACONING"]

MESSAGE = {
    "INVALID_MAX_VALUE": "Please provide a value for 'max' between 1 and 10,000.",
}


def reformat_resource_groups_outputs(text: str) -> str:
    """rg_*text -> ResourceGroupText
    Args:
        text: the text to transform
    Returns:
        A Camel Cased string.
    """
    suffix = text[3:]
    if suffix == "id":
        suffix = "ID"
    elif suffix == "resourcetypeid":
        suffix = "TypeID"
    else:
        suffix = suffix.title()
    return f"ResourceGroup{suffix}"


def reformat_outputs(text: str) -> str:
    """camelCase -> Camel Case, id -> ID
    Args:
        text: the text to transform
    Returns:
        A Demisto output standard string
    """
    if text.startswith("rg_"):
        return reformat_resource_groups_outputs(text)
    if text == "id":
        return "ID"
    if text in ["lanid", "u_lanid"]:
        return "LanID"
    if text == "jobId":
        return "JobID"
    if text == "eventId":
        return "EventID"
    if text in ["entityId", "entityid"]:
        return "EntityID"
    if text in ["tenantId", "tenantid"]:
        return "TenantID"
    if text == "incidentId":
        return "IncidentID"
    if text == "Datasourceid":
        return "DataSourceID"
    if text in ["employeeId", "employeeid", "u_employeeid"]:
        return "EmployeeID"
    if text == "violatorId":
        return "ViolatorID"
    if text == "threatname":
        return "ThreatName"
    if text == "generationtime":
        return "GenerationTime"
    if text == "generationtime_epoch":
        return "GenerationTime_Epoch"

    if text.startswith(("U_", "u_")):
        text = text[2:]
    return "".join(" " + char if char.isupper() else char.strip() for char in text).strip().title()


def parse_data_arr(data_arr: Any, fields_to_drop: list = [], fields_to_include: list = []):
    """Parse data as received from Securonix into Demisto's conventions
    Args:
        data_arr: a dictionary containing the data
        fields_to_drop: Fields to drop from the array of the data
        fields_to_include: Fields to include from the array of the data
    Returns:
        A Camel Cased dictionary with the relevant fields.
        readable: for the human readable
        outputs: for the entry context
    """
    if isinstance(data_arr, list):
        readable_arr, outputs_arr = [], []
        for data in data_arr:
            readable = {reformat_outputs(i): j for i, j in data.items() if i not in fields_to_drop}
            if fields_to_include:
                readable = {i: j for i, j in readable.items() if i in fields_to_include}
            readable_arr.append(readable)
            outputs_arr.append({k.replace(" ", ""): v for k, v in readable.copy().items()})
        return readable_arr, outputs_arr

    readable = {reformat_outputs(i): j for i, j in data_arr.items() if i not in fields_to_drop}
    if fields_to_include:
        readable = {i: j for i, j in readable.items() if i in fields_to_include}
    outputs = {k.replace(" ", ""): v for k, v in readable.copy().items()}

    return readable, outputs


def string_escape_MD(data: Any):
    """
    Escape any chars that might break a markdown string.

    :type data: ``Any``
    :param data: The data to be modified (required).

    :return: A modified data.
    :rtype: ``str``
    """
    if isinstance(data, str):
        data = "".join(["\\" + str(c) if c in MARKDOWN_CHARS else str(c) for c in data])
    elif isinstance(data, list):
        new_data = []
        for sub_data in data:
            if isinstance(sub_data, str):
                sub_data = "".join(["\\" + str(c) if c in MARKDOWN_CHARS else str(c) for c in sub_data])
            new_data.append(sub_data)
        data = new_data

    return data


def incident_priority_to_dbot_score(priority_str: str, default_severity: str):
    """Converts an priority string to DBot score representation
        alert severity. Can be one of:
        Low    ->  1
        Medium ->  2
        High   ->  3

    Args:
        priority_str: String representation of priority.
        default_severity: Default incoming incident severity

    Returns:
        Dbot representation of severity
    """
    if default_severity:
        priority = default_severity.lower()
    else:
        priority = priority_str.lower()

    if priority == "low":
        return 1
    if priority == "medium":
        return 2
    if priority == "high":
        return 3
    demisto.info(f"Securonix incident priority: {priority} is not known. Setting as unknown(DBotScore of 0).")
    return 0


def validate_configuration_parameters(params: dict[str, Any]):
    """
    Check whether entered configuration parameters are valid or not.

    :type: params: dict
    :param: Dictionary of demisto configuration parameter

    :return: raise ValueError if any configuration parameter is not in valid format else returns None
    :rtype: None
    """
    fetch_time = params.get("fetch_time")
    max_fetch = params.get("max_fetch")
    # Validate empty values
    if fetch_time is None:
        raise ValueError("Please provide First fetch time")
    if max_fetch is None:
        raise ValueError("Please provide max_fetch")
    # validate max_fetch
    arg_to_number(max_fetch, arg_name="max_fetch")
    # validate first_fetch parameter
    arg_to_datetime(fetch_time, "First fetch time")


class RetryExponential(Retry):
    """
    Create wrapper of urllib3.util.retry for Add extra logs before making a retry request with exponential delay
    """

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        diff = TOTAL_RETRY_COUNT - self.total  # type: ignore
        if 0 < diff <= TOTAL_RETRY_COUNT:
            demisto.debug(f"Performing retry {diff} with {self.get_backoff_time()} seconds delay for URL {FULL_URL}")


class RetryFixed(Retry):
    """
    Create wrapper of urllib3.util.retry for Add extra logs before making a retry request with fixed delay
    """

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        diff = TOTAL_RETRY_COUNT - self.total  # type: ignore
        if 0 < diff <= TOTAL_RETRY_COUNT:
            demisto.debug(f"Performing retry {diff} with {self.get_backoff_time()} seconds delay for URL {FULL_URL}.")

    # Overriding get_backoff_time logic for fixed backoff_factor
    def get_backoff_time(self) -> float:
        """
        Formula for computing the fixed backoff

        :rtype: float
        """
        consecutive_errors_len = len(list(takewhile(lambda x: x.redirect_location is None, reversed(self.history))))

        if consecutive_errors_len <= 1:
            return 0
        return min(self.DEFAULT_BACKOFF_MAX, int(self.backoff_factor))


def validate_mirroring_parameters(params: dict[str, Any]) -> None:
    """Validate mirroring specific configuration parameters.

    Args:
        params: The integration configuration parameters got from demisto.params()
    """
    mirror_direction = params.get("mirror_direction", "None").strip()
    close_states_of_securonix = params.get("close_states_of_securonix", "").strip().lower()
    active_state_action = params.get("active_state_action_mapping", "").strip()
    active_state_status = params.get("active_state_status_mapping", "").strip().lower()
    close_state_action = params.get("closed_state_action_mapping", "").strip()
    close_state_status = params.get("closed_state_status_mapping", "").strip().lower()
    close_incident = argToBoolean(params.get("close_incident", False))
    comment_entry_tag = params.get("comment_tag", "").strip()

    if mirror_direction == "None":
        return

    if mirror_direction == "Incoming" and (not close_states_of_securonix or not argToList(close_states_of_securonix)):
        raise ValueError(
            'Following field is required for Incoming Mirroring: "Securonix workflow state(s) that '
            'can be considered as Close state in XSOAR for Incoming mirroring".'
        )

    if mirror_direction == "Outgoing" and (
        not active_state_action
        or not active_state_status
        or not close_state_action
        or not close_state_status
        or not comment_entry_tag
    ):
        raise ValueError(
            'Following fields are required for Outgoing Mirroring: "Securonix action name to map '
            'with XSOAR\'s active state for Outgoing mirroring", "Securonix status to map with '
            'XSOAR\'s active state for Outgoing mirroring", "Securonix action name to map with '
            "XSOAR's closed state for Outgoing mirroring\", \"Securonix status to map with XSOAR's "
            'closed state for Outgoing mirroring", "Comment Entry Tag".'
        )

    if mirror_direction == "Incoming And Outgoing" and (
        not active_state_action
        or not active_state_status
        or not close_state_action
        or not close_state_status
        or not close_states_of_securonix
        or not argToList(close_states_of_securonix)
        or not comment_entry_tag
    ):
        raise ValueError(
            'Following fields are required for Incoming And Outgoing Mirroring: "Securonix workflow '
            'state(s) that can be considered as Close state in XSOAR for Incoming mirroring", '
            '"Securonix action name to map with XSOAR\'s active state for Outgoing mirroring", '
            '"Securonix status to map with XSOAR\'s active state for Outgoing mirroring", "Securonix'
            ' action name to map with XSOAR\'s closed state for Outgoing mirroring", "Securonix status'
            ' to map with XSOAR\'s closed state for Outgoing mirroring", "Comment Entry Tag".'
        )

    if close_incident and not active_state_action or not active_state_status or not close_state_action or not close_state_status:
        raise ValueError(
            'Following fields are required for closing incident on Securonix: "Securonix action name '
            'to map with XSOAR\'s active state for Outgoing mirroring", "Securonix status to map '
            'with XSOAR\'s active state for Outgoing mirroring", "Securonix action name to map with '
            "XSOAR's closed state for Outgoing mirroring\", \"Securonix status to map with XSOAR's "
            'closed state for Outgoing mirroring".'
        )


def validate_delete_whitelist_parameters(
    whitelist_type: str, entity_id: str, attribute_name: str, attribute_value: str, tenant_name: str
) -> None:
    """Validate parameters for delete whitelist entry command.

    Args:
        whitelist_type: Type of whitelist that user wants to delete from.
        entity_id: Entity ID value that needs to be removed from the whitelist.
        attribute_name: Name of the attribute being removed.
        attribute_value: The value of the attribute being removed.
        tenant_name: The name of the tenant the whitelist belongs to.

    Raises:
        ValueError: Raises ValueError if parameters are invalid.
    """
    # Validate whitelist_type parameter.
    if whitelist_type and whitelist_type not in VALID_WHITELIST_TYPE:
        raise ValueError(f"{whitelist_type} is an invalid value for whitelist_type.Valid whitelist types are {VALID_ENTITY_TYPE}")

    # Validate entity_id parameter.
    if whitelist_type == "Global" and not entity_id:
        raise ValueError("entity_id is required parameter for Global whitelist type.")

    # Validate attribute_name parameter.
    if whitelist_type == "Attribute" and not attribute_name:
        raise ValueError("attribute_name is required parameter for Attribute whitelist type.")

    # Validate attribute_value parameter.
    if whitelist_type == "Attribute" and not attribute_value:
        raise ValueError("attribute_value is required parameter for Attribute whitelist type.")

    # Validate tenant_name parameter.
    if not tenant_name:
        raise ValueError("tenant_name is a required parameter.")


def get_mirroring() -> dict:
    """Add mirroring related keys in an incident.

    Returns:
        Dict: A dictionary containing required key-value pairs for mirroring.
    """
    # Fetch the integration configuration parameters to determine the flow of the mirroring and mirror tags.
    params = demisto.params()
    mirror_direction = params.get("mirror_direction", "None").strip()
    mirror_tags = params.get("comment_tag", "").strip()

    return {
        "mirror_direction": MIRROR_DIRECTION.get(mirror_direction),
        "mirror_instance": demisto.integrationInstance(),
        "mirror_tags": mirror_tags,
    }


def filter_activity_entries_by_time(activity_data: list[dict[str, Any]], timestamp: int) -> list[dict[str, Any]]:
    """Filter the incident activity entries by the given timestamp.

    Args:
        activity_data (List[Dict[str, Any]]): A list of incident activity data.
        timestamp (int): The timestamp to filter the activity data.

    Returns:
        List[Dict[str, Any]]: Filtered incident activity entries.
    """
    filtered_activities = []

    for activity in activity_data:
        activity_timestamp = activity.get("eventTime", "")

        # If no event timestamp found in an entry, then skip that entry.
        if not activity_timestamp:
            demisto.debug(f"Skipping entry as no event timestamp found: {json.dumps(activity)}")
            continue

        if date_to_timestamp(parse(activity_timestamp)) >= timestamp:
            filtered_activities.append(activity)

    return filtered_activities


def filter_comment_activity_entries(activity_data: list[dict[str, Any]]) -> list[dict[str, Any]]:
    """Filter the comment entries from the given incident activity entries.

    Args:
        activity_data (List[Dict[str, Any]]): A list of incident activity data.

    Returns:
        List[Dict[str, Any]]: Filtered comment entries from incident activity entries.
    """
    comment_entries = []

    for activity in activity_data:
        activity_action = activity.get("actiontaken", "")

        if activity_action == COMMENT_ACTION:
            comment_entries.append(activity)

    return comment_entries


def filter_attachment_activity_entries(activity_data: list[dict[str, Any]]) -> list[dict[str, Any]]:
    """Filter the attachment entries from the given incident activity entries.

    Args:
        activity_data (List[Dict[str, Any]]): A list of incident activity data.

    Returns:
        List[Dict[str, Any]]: Filtered attachment entries from incident activity entries.
    """
    attachment_entries = []

    for activity in activity_data:
        activity_action = activity.get("actiontaken", "")

        if activity_action == ATTACHMENT_ACTION:
            attachment_entries.append(activity)

    return attachment_entries


def extract_names_of_attachments_from_entries(attachment_entries: list[dict[str, Any]]) -> list[str]:
    """Return names of the attachments for a list of attachment entries.

    Args:
        attachment_entries (List[Dict[str, Any]]): A list of attachment entries.

    Returns:
        List[str]: A list of attachment names.
    """
    attachment_names = [attachment.get("attachment") for attachment in attachment_entries]

    # Remove any None values from the list and return it.
    return list(filter(None, attachment_names))


def is_incident_closed_on_securonix(activity_data: list[dict[str, Any]], close_states_of_securonix: list[str]) -> bool:
    """Check whether the incident is closed on the Securonix.

    Args:
        activity_data: A list of activity data from which to determine whether the incident is closed or not.
        close_states_of_securonix: A list of Securonix states which defines the close state for XSOAR.

    Returns:
        bool: Indicating whether the incident is closed on Securonix or not.
    """
    incident_closed = []

    for activity in activity_data:
        current_status = activity.get("status", "").strip().lower()
        last_status = activity.get("lastStatus", "").strip().lower()

        if current_status != last_status and current_status in close_states_of_securonix:
            incident_closed.append(True)
        else:
            incident_closed.append(False)

    return any(incident_closed)


def extract_closing_comments(activity_data: list[dict[str, Any]], close_states_of_securonix: list[str]) -> str:
    """Extract the contents of the closing comments from activity data provided from Securonix.

    Args:
        activity_data: A list of activity data from which to extract the closing comments.
        close_states_of_securonix: A list of Securonix states which defines the close state for XSOAR.

    Returns:
        str: A string representing closing comments.
    """
    closing_comments = []

    for activity in activity_data:
        current_status = activity.get("status", "").strip().lower()
        last_status = activity.get("lastStatus", "").strip().lower()

        if current_status != last_status and current_status in close_states_of_securonix:
            comments_list = activity.get("comment", [])

            for _comment in comments_list:
                closing_comments.append(_comment.get("Comments", ""))

    if not closing_comments:
        closing_comments.append("Closing the XSOAR incident as Securonix incident is closed.")

    return " | ".join(closing_comments)


def escape_spotter_query(original_query: str) -> str:
    """Escape the special characters of the spotter query provided from Securonix Incident.

    Args:
        original_query: The original spotter query provided from Securonix Incident

    Returns:
        str: The spotter query escaped for special characters.
    """
    escaped_query = original_query
    for special_char in SPOTTER_SPECIAL_CHARACTERS:
        escaped_query = escaped_query.replace(special_char, f"\\{special_char}")
    return escaped_query


class Client(BaseClient):
    """
    Client to use in the Securonix integration. Overrides BaseClient
    """

    def __init__(
        self,
        tenant: str,
        server_url: str,
        username: str,
        password: str,
        verify: bool,
        proxy: bool,
        securonix_retry_count: int,
        securonix_retry_delay: int,
        securonix_retry_delay_type: str,
    ):
        super().__init__(base_url=server_url, verify=verify, proxy=proxy)
        self._username = username
        self._password = password
        self._tenant = tenant
        self._securonix_retry_count = securonix_retry_count
        self._securonix_retry_delay = securonix_retry_delay
        self._securonix_retry_delay_type = securonix_retry_delay_type
        self.session = requests.Session()

        # Fetch cached integration context.
        integration_context = get_integration_context()
        self._token = integration_context.get("token") or self._generate_token()

        # the following condition was added to overcome the security hardening happened in Python 3.10.
        # https://github.com/python/cpython/pull/25778
        # https://bugs.python.org/issue43998

        if IS_PY3 and PY_VER_MINOR >= 10 and not verify:
            self.session.mount("https://", SSLAdapter(verify=verify))

    def get_securonix_retry_count(self):
        return self._securonix_retry_count

    def get_securonix_retry_delay(self):
        return self._securonix_retry_delay

    def get_securonix_retry_delay_type(self):
        return self._securonix_retry_delay_type

    def implement_retry(
        self,
        retries: int = 0,
        status_list_to_retry: list = None,
        backoff_factor: int = 30,
        raise_on_redirect: bool = False,
        raise_on_status: bool = False,
    ):
        """
        Implements the retry mechanism.
        In the default case where retries = 0 the request will fail on the first time

        :type retries: ``int`` :param retries: How many retries should be made in case of a failure. when set to '0'-
        will fail on the first time

        :type status_list_to_retry: ``iterable``
        :param status_list_to_retry: A set of integer HTTP status codes that we should force a retry on.
            A retry is initiated if the request method is in ['GET', 'POST', 'PUT']
            and the response status code is in ``status_list_to_retry``.

        :type backoff_factor ``float``
        :param backoff_factor:
            A backoff factor to apply between attempts after the second try
            (most errors are resolved immediately by a second try without a
            delay). urllib3 will sleep for::

                {backoff factor} * (2 ** ({number of total retries} - 1))

            seconds. If the backoff_factor is 0.1, then :func:`.sleep` will sleep
            for [0.0s, 0.2s, 0.4s, ...] between retries. It will never be longer
            than :attr:`Retry.BACKOFF_MAX`.

            By default, backoff_factor set to 5

        :type raise_on_redirect ``bool``
        :param raise_on_redirect: Whether, if the number of redirects is
            exhausted, to raise a MaxRetryError, or to return a response with a
            response code in the 3xx range.

        :type raise_on_status ``bool``
        :param raise_on_status: Similar meaning to ``raise_on_redirect``:
            whether we should raise an exception, or return a response,
            if status falls in ``status_forcelist`` range and retries have
            been exhausted.
        """
        try:
            method_whitelist = (
                "allowed_methods"
                if hasattr(
                    Retry.DEFAULT,  # type: ignore[attr-defined]
                    "allowed_methods",
                )
                else "method_whitelist"
            )
            whitelist_kawargs = {method_whitelist: frozenset(["GET", "POST", "PUT"])}
            retry = None
            if self._securonix_retry_delay_type == "Fixed":
                demisto.debug("Securonix Retry delay type is Fixed")
                # Set DEFAULT_BACKOFF_MAX to 2hour(in seconds)
                RetryFixed.DEFAULT_BACKOFF_MAX = 7200
                retry = RetryFixed(
                    total=retries,
                    connect=0,
                    read=0,
                    backoff_factor=backoff_factor,
                    status=retries,
                    status_forcelist=status_list_to_retry,
                    raise_on_status=raise_on_status,
                    raise_on_redirect=raise_on_redirect,
                    **whitelist_kawargs,  # type: ignore[arg-type]
                )
            else:
                demisto.debug("Securonix Retry delay type is Exponential")
                # Set DEFAULT_BACKOFF_MAX to 2hour(in seconds)
                RetryExponential.DEFAULT_BACKOFF_MAX = 7200
                retry = RetryExponential(  # type: ignore
                    total=retries,
                    backoff_factor=backoff_factor,
                    connect=0,
                    read=0,
                    status=retries,
                    status_forcelist=status_list_to_retry,
                    raise_on_status=raise_on_status,
                    raise_on_redirect=raise_on_redirect,
                    **whitelist_kawargs,  # type: ignore[arg-type]
                )
            http_adapter = HTTPAdapter(max_retries=retry)

            # the following condition was added to overcome the security hardening happened in Python 3.10.
            # https://github.com/python/cpython/pull/25778
            # https://bugs.python.org/issue43998

            if self._verify:
                https_adapter = http_adapter
            elif IS_PY3 and PY_VER_MINOR >= 10:
                https_adapter = SSLAdapter(max_retries=retry, verify=self._verify)  # type: ignore[arg-type]
            else:
                https_adapter = http_adapter

            self.session.mount("https://", https_adapter)

        except NameError:
            pass

    def http_request(
        self,
        method,
        url_suffix,
        headers=None,
        params=None,
        response_type: str = "json",
        json=None,
        data=None,
        regenerate_access_token=True,
    ):
        """
        Generic request to Securonix
        """
        global FULL_URL
        FULL_URL = urljoin(self._base_url, url_suffix)
        status_list_to_retry = [429] + list(range(500, 600))
        if self._securonix_retry_count > 0:
            self.implement_retry(
                retries=self._securonix_retry_count,
                status_list_to_retry=status_list_to_retry,
                backoff_factor=self._securonix_retry_delay,
                raise_on_redirect=False,
                raise_on_status=True,
            )

        try:
            demisto.debug(f"Making HTTP request with URL {FULL_URL}")
            result = self.session.request(
                method,
                FULL_URL,
                params=params,
                headers=headers,
                verify=self._verify,
                json=json,
                data=data,
            )
            if result.status_code == 403 and regenerate_access_token:
                self._token = self._generate_token()
                headers["token"] = self._token
                return self.http_request(method, url_suffix, headers, params, response_type, json, data, False)
            if not result.ok:
                raise ValueError(f"Error in API call to Securonix {result.status_code}. Reason: {result.text}")
            try:
                if url_suffix == "/incident/attachments":
                    return result
                if response_type != "json":
                    return result.text
                return result.json()
            except Exception:
                raise ValueError(f"Failed to parse http response to JSON format. Original response body: \n{result.text}")

        except requests.exceptions.ConnectTimeout as exception:
            err_msg = (
                "Connection Timeout Error - potential reasons might be that the Server URL parameter"
                " is incorrect or that the Server is not accessible from your host."
            )
            raise Exception(f"{err_msg}\n{exception}")

        except requests.exceptions.SSLError as exception:
            err_msg = (
                "SSL Certificate Verification Failed - try selecting 'Trust any certificate' checkbox in"
                " the integration configuration."
            )
            raise Exception(f"{err_msg}\n{exception}")

        except requests.exceptions.ProxyError as exception:
            err_msg = (
                "Proxy Error - if the 'Use system proxy' checkbox in the integration configuration is"
                " selected, try clearing the checkbox."
            )
            raise Exception(f"{err_msg}\n{exception}")

        except requests.exceptions.ConnectionError as exception:
            error_class = str(exception.__class__)
            err_type = "<" + error_class[error_class.find("'") + 1 : error_class.rfind("'")] + ">"  # noqa: E203
            err_msg = (
                f"Error Type: {err_type}\n"
                f"Error Number: [{exception.errno}]\n"
                f"Message: {exception.strerror}\n"
                f"Verify that the tenant parameter is correct "
                f"and that you have access to the server from your host."
            )
            raise Exception(f"{err_msg}\n{exception}")

        except requests.exceptions.RetryError as exception:
            try:
                reason = f"Reason: {exception.args[0].reason.args[0]}"  # pylint: disable=no-member
            except Exception:  # noqa: disable=broad-except
                reason = ""
            err_msg = (
                f"Max Retries Error: Request attempts with {self._securonix_retry_count} retries and with "
                f"{self._securonix_retry_delay} seconds {self._securonix_retry_delay_type} delay "
                f"failed.\n{reason}"
            )
            if self._securonix_retry_delay_type == "Exponential":
                # For Exponential delay we are dividing it by 2 so for error message make it to original value
                err_msg = (
                    f"Max Retries Error: Request attempts with {self._securonix_retry_count} retries and with"
                    f" {self._securonix_retry_delay * 2} seconds {self._securonix_retry_delay_type} delay "
                    f"failed.\n{reason}"
                )
            demisto.error(err_msg)
            raise Exception(f"{err_msg}\n{exception}")

        except requests.exceptions.InvalidHeader as exception:
            set_integration_context({})
            raise Exception(f"Invalid token generated from the API.\n{exception}")

        except Exception as exception:
            raise Exception(str(exception))

    def _generate_token(self) -> str:
        """Generate a token

        Returns:
            token valid for 1 day
        """
        demisto.info("Generating new access token.")
        headers = {
            "username": self._username,
            "password": self._password,
            "validity": "1",
        }
        token = self.http_request("GET", "/token/generate", headers=headers, response_type="text")

        set_integration_context({"token": token})
        return token

    def list_workflows_request(self) -> dict:
        """List workflows.

        Returns:
            Response from API.
        """
        workflows = self.http_request("GET", "/incident/get", headers={"token": self._token}, params={"type": "workflows"})
        return workflows.get("result").get("workflows")

    def get_default_assignee_for_workflow_request(self, workflow: str) -> dict:
        """Get default assignee for a workflow..

        Args:
            workflow: workflow name

        Returns:
            Response from API.
        """
        params = {"type": "defaultAssignee", "workflow": workflow}
        default_assignee = self.http_request("GET", "/incident/get", headers={"token": self._token}, params=params)
        return default_assignee.get("result")

    def list_possible_threat_actions_request(self) -> dict:
        """List possible threat actions.

        Returns:
            Response from API.
        """

        threat_actions = self.http_request(
            "GET", "/incident/get", headers={"token": self._token}, params={"type": "threatActions"}
        )
        return threat_actions.get("result")

    def list_policies_request(self) -> dict:
        """List policies.

        Returns:
            Response from API.
        """

        policies = self.http_request("GET", "/policy/getAllPolicies", headers={"token": self._token}, response_type="xml")
        return policies

    def list_resource_groups_request(self) -> dict:
        """List resource groups.

        Returns:
            Response from API.
        """

        resource_groups = self.http_request("GET", "/list/resourceGroups", headers={"token": self._token}, response_type="xml")
        return resource_groups

    def list_users_request(self) -> dict:
        """List users.

        Returns:
            Response from API.
        """

        users = self.http_request("GET", "/list/allUsers", headers={"token": self._token}, response_type="xml")
        return users

    def list_activity_data_request(self, from_: str, to_: str, query: str = None, max_records: int = None) -> dict:
        """List activity data.

        Args:
            from_: eventtime start range in format MM/dd/yyyy HH:mm:ss.
            to_: eventtime end range in format MM/dd/yyyy HH:mm:ss.
            query: open query.
            max_records: maximum number of activity records to retrieve.

        Returns:
            Response from API.
        """
        params = {"query": "index=activity", "eventtime_from": from_, "eventtime_to": to_, "prettyJson": True}
        if max_records is not None:
            params["max"] = max_records
        remove_nulls_from_dictionary(params)
        if query:
            if re.findall(r"index\s*=\s*\w+", query):
                params["query"] = query
            else:
                params["query"] = f"{params['query']} AND {query}"
        activity_data = self.http_request("GET", "/spotter/index/search", headers={"token": self._token}, params=params)
        return activity_data

    def list_violation_data_request(
        self, from_: str, to_: str, query: str = None, query_id: str = None, max_violations: int | None = 1000
    ) -> dict:
        """List violation data.

        Args:
            from_: eventtime start range in format MM/dd/yyyy HH:mm:ss.
            to_: eventtime end range in format MM/dd/yyyy HH:mm:ss.
            query: open query.
            query_id: query_id to paginate violations.
            max_violations: max number of violations to return.

        Returns:
            Response from API.
        """
        params = {
            "query": "index=violation",
            "generationtime_from": from_,
            "generationtime_to": to_,
            "queryId": query_id,
            "prettyJson": True,
            "max": max_violations,
        }
        if query:
            if re.findall(r"index\s*=\s*\w+", query):
                params["query"] = query
            else:
                params["query"] = f"{params['query']} AND {query}"

        remove_nulls_from_dictionary(params)
        violation_data = self.http_request("GET", "/spotter/index/search", headers={"token": self._token}, params=params)
        return violation_data

    def list_incidents_request(
        self, from_epoch: str, to_epoch: str, incident_status: str, max_incidents: str = "200", offset: str = "0"
    ) -> dict:
        """List all incidents by sending a GET request.

        Args:
            from_epoch: from time in epoch
            to_epoch: to time in epoch
            incident_status: incident status e.g:closed, opened
            max_incidents: max incidents to get
            offset: offset to be used

        Returns:
            Response from API.
        """
        headers = {"token": self._token, "Accept": "application/vnd.snypr.app-v6.0+json"}
        params = {
            "type": "list",
            "from": from_epoch,
            "to": to_epoch,
            "rangeType": incident_status,
            "max": max_incidents,
            "order": "asc",
            "offset": offset,
        }
        incidents = self.http_request("GET", "/incident/get", headers=headers, params=params)
        return incidents.get("result").get("data")

    def get_incident_request(self, incident_id: str) -> dict:
        """get incident meta data by sending a GET request.

        Args:
            incident_id: incident ID.

        Returns:
            Response from API.
        """
        headers = {"token": self._token, "Accept": "application/vnd.snypr.app-v6.0+json"}
        params = {
            "type": "metaInfo",
            "incidentId": incident_id,
        }
        incident = self.http_request("GET", "/incident/get", headers=headers, params=params)
        return incident.get("result").get("data")

    def get_incident_status_request(self, incident_id: str) -> dict:
        """get incident meta data by sending a GET request.

        Args:
            incident_id: incident ID.

        Returns:
            Response from API.
        """
        params = {
            "type": "status",
            "incidentId": incident_id,
        }
        incident = self.http_request("GET", "/incident/get", headers={"token": self._token}, params=params)
        return incident.get("result")

    def get_incident_workflow_request(self, incident_id: str) -> dict:
        """get incident workflow by sending a GET request.

        Args:
            incident_id: incident ID.

        Returns:
            Response from API.
        """
        params = {
            "type": "workflow",
            "incidentId": incident_id,
        }
        incident = self.http_request("GET", "/incident/get", headers={"token": self._token}, params=params)
        return incident.get("result")

    def get_incident_available_actions_request(self, incident_id: str) -> dict:
        """get incident available actions by sending a GET request.

        Args:
            incident_id: incident ID.

        Returns:
            Response from API.
        """
        params = {
            "type": "actions",
            "incidentId": incident_id,
        }
        incident = self.http_request("GET", "/incident/get", headers={"token": self._token}, params=params)
        return incident.get("result")

    def get_incident_attachments_request(
        self, incident_id, attachment_type: str = None, attachment_from: int = None, attachment_to: int = None
    ):
        """Get incident attachments by sending a GET request.

        Args:
            incident_id: Incident ID.
            attachment_type: The type of attachment to retrieve. Supported options are
            csv, pdf, and txt. Comma-separated values are supported.
            attachment_from: Start time for which to retrieve attachments. (in the format YYYY-MM-DDTHH:MM:SS format)
            attachment_to: End time for which to retrieve attachments. (in the in the format YYYY-MM-DDTHH:MM:SS format)
            format)

        Returns:
            Response from API.
        """
        params = {
            "incidentId": incident_id,
            "attachmenttype": attachment_type,
            "datefrom": attachment_from,
            "dateto": attachment_to,
        }
        remove_nulls_from_dictionary(params)
        attachment_res = self.http_request("GET", "/incident/attachments", headers={"token": self._token}, params=params)
        return attachment_res

    def perform_action_on_incident_request(self, incident_id, action: str, action_parameters: str) -> dict:
        """get incident available actions by sending a GET request.

        Args:
            incident_id: incident ID.
            action: action to perform on the incident.
            action_parameters: parameters needed in order to perform the action.

        Returns:
            Response from API.
        """

        params = {"type": "actionInfo", "incidentId": incident_id, "actionName": action}
        if action_parameters:
            action_parameters_dict = {k: v.strip('"') for k, v in [i.split("=", 1) for i in action_parameters.split(",")]}
            params.update(action_parameters_dict)

        possible_action = self.http_request("GET", "/incident/get", headers={"token": self._token}, params=params)

        if "error" in possible_action:
            err_msg = possible_action.get("error")
            raise Exception(
                f"Failed to perform the action {action} on incident {incident_id}.\nError from Securonix is: {err_msg}"
            )

        incident = self.http_request("POST", "/incident/actions", headers={"token": self._token}, params=params)
        return incident.get("result")

    def add_comment_to_incident_request(self, incident_id: str, comment: str) -> dict:
        """add comment to an incident by sending a POST request.

        Args:
            incident_id: incident ID.
            comment: action to perform on the incident

        Returns:
            Response from API.
        """
        params = {"incidentId": incident_id, "comment": comment, "actionName": "comment"}
        incident = self.http_request("POST", "/incident/actions", headers={"token": self._token}, params=params)
        return incident.get("result")

    def create_incident_request(
        self,
        violation_name: str,
        resource_group: str,
        resource_name: str,
        entity_type: str,
        entity_name: str,
        action_name: str,
        workflow: str = None,
        comment: str = None,
        criticality: str = None,
    ) -> dict:
        """create an incident by sending a POST request.

        Args:
            violation_name: violation or policy name.
            resource_group: resource group name.
            resource_name: resource name.
            entity_type: entity type.
            entity_name: entity name.
            action_name: action name.
            workflow: workflow name.
            comment: comment on the incident.
            criticality: criticality for the incident.

        Returns:
            Response from API.
        """
        params = {
            "violationName": violation_name,
            "datasourceName": resource_group,
            "resourceName": resource_name,
            "entityType": entity_type,
            "entityName": entity_name,
            "actionName": action_name,
        }
        if workflow:
            params["workflow"] = workflow
        if comment:
            params["comment"] = comment
        if criticality:
            params["criticality"] = criticality

        response = self.http_request("POST", "/incident/actions", headers={"token": self._token}, params=params)
        return response

    def list_watchlist_request(self):
        """list watchlists by sending a GET request.

        Returns:
            Response from API.
        """
        watchlists = self.http_request("GET", "/incident/listWatchlist", headers={"token": self._token})
        return watchlists.get("result")

    def get_watchlist_request(self, watchlist_name: str) -> dict:
        """Get a watchlist by sending a GET request.

        Args:
            watchlist_name: watchlist name.

        Returns:
            Response from API.
        """
        params = {
            "query": f'index=watchlist AND watchlistname="{watchlist_name}"',
        }
        watchlist = self.http_request("GET", "/spotter/index/search", headers={"token": self._token}, params=params)
        return watchlist

    def create_watchlist_request(self, watchlist_name: str, tenant_name: str) -> dict:
        """Create a watchlist by sending a POST request.

        Args:
            watchlist_name: watchlist name.
            tenant_name: Name of the tenant the watchlist belongs to.

        Returns:
            Response from API.
        """
        params = {"watchlistname": watchlist_name, "tenantname": tenant_name}
        remove_nulls_from_dictionary(params)
        watchlist = self.http_request(
            "POST", "/incident/createWatchlist", headers={"token": self._token}, params=params, response_type="text"
        )
        return watchlist

    def check_entity_in_watchlist_request(self, entity_name: str, watchlist_name: str) -> dict:
        """Check if an entity is whitelisted by sending a GET request.

        Args:
            entity_name: Entity name.
            watchlist_name: Watchlist name.

        Returns:
            Response from API.
        """
        params = {"entityId": entity_name, "watchlistname": watchlist_name}
        response = self.http_request("GET", "/incident/checkIfWatchlisted", headers={"token": self._token}, params=params)
        return response

    def add_entity_to_watchlist_request(self, watchlist_name: str, entity_type: str, entity_name: str, expiry_days: str) -> dict:
        """Check if an entity is whitelisted by sending a GET request.

        Args:
            watchlist_name: Watchlist name.
            entity_type: Entity type.
            entity_name: Entity name.
            expiry_days: Expiry in days.
        Returns:
            Response from API.
        """
        params = {
            "watchlistname": watchlist_name,
            "entityType": entity_type,
            "entityId": entity_name,
            "expirydays": expiry_days,
            "resourcegroupid": "-1",
        }
        watchlist = self.http_request(
            "POST", "/incident/addToWatchlist", headers={"token": self._token}, params=params, response_type="txt"
        )
        return watchlist

    def list_threats_request(
        self, from_epoch: int, to_epoch: int, tenant_name: str, offset: int = 0, max_incidents: int = 10
    ) -> dict:
        """List all threats by sending a GET request.

        Args:
            from_epoch: from time in epoch
            to_epoch: to time in epoch
            tenant_name: tenant name
            offset: A page number to fetch from
            max_incidents: max incidents to get

        Returns:
            Response from API.
        """
        params = {
            "datefrom": from_epoch,
            "dateto": to_epoch,
            "tenantname": tenant_name,
            "max": max_incidents,
            "offset": offset,
        }
        headers = {"token": self._token, "Accept": "application/vnd.snypr.app-v1.0+json"}

        remove_nulls_from_dictionary(params)
        response = self.http_request("GET", "/sccWidget/getThreats", headers=headers, params=params)
        return response.get("Response", {}).get("threats", {})

    def get_incident_activity_history_request(self, incident_id: str) -> list:
        """Get incident activity history by sending a GET request.

        Args:
            incident_id (str): Incident ID for which to retrieve the activity history.

        Returns:
            Response from API.
        """
        params = {
            "type": "activityStreamInfo",
            "incidentId": incident_id,
        }
        incident = self.http_request("GET", "/incident/get", headers={"token": self._token}, params=params)
        return incident.get("result", {}).get("activityStreamData", [])

    def list_whitelists_request(self, tenant_name: str) -> list:
        """Get a whitelist information by sending a GET request.

        Args:
            tenant_name: Name of the tenant the whitelist belongs to.

        Returns:
            Response from API.
        """
        params = {"tenantname": tenant_name}
        remove_nulls_from_dictionary(params)
        whitelist = self.http_request("GET", "/incident/getlistofWhitelist", headers={"token": self._token}, params=params)
        return whitelist.get("result", [])

    def get_whitelist_entry_request(self, tenant_name: str, whitelist_name: str) -> dict:
        """Get a whitelist information by sending a GET request.

        Args:
            tenant_name: Name of the tenant the whitelist belongs to.
            whitelist_name: Name of the whitelist.

        Returns:
            Response from API.
        """
        params = {"tenantname": tenant_name, "whitelistname": whitelist_name}
        remove_nulls_from_dictionary(params)
        whitelist = self.http_request("GET", "/incident/listWhitelistEntities", headers={"token": self._token}, params=params)
        return whitelist.get("result", {})

    def add_whitelist_entry_request(
        self,
        tenant_name: str,
        whitelist_name: str,
        whitelist_type: str,
        entity_type: str,
        entity_id: str,
        expiry_date: str,
        resource_name: str,
        resource_group_id: str,
        attribute_name: str,
        attribute_value: str,
        violation_type: str,
        violation_name: str,
    ):
        """Add entry in whitelist by sending a POST request.

        Args:
            tenant_name: Name of the tenant the whitelist belongs to.
            whitelist_name: Name of the whitelist.
            whitelist_type: Type of the whitelist.
            entity_type: Entity Type is required if whitelist is global.
            entity_id: Entity ID is required if whitelist is global.
            expiry_date: Expiry Date in format(MM/DD/YYYY).
            resource_name: Resource name which the account belongs to.
            resource_group_id: Resource Group ID which the account belongs to.
            attribute_name: Attribute name.
            attribute_value: Attribute Value.
            violation_type: Violation Type.
            violation_name: Violation Name.

        Returns:
            Response from API.
        """
        params = {
            "tenantname": tenant_name,
            "whitelistname": whitelist_name,
            "whitelisttype": whitelist_type,
            "entitytype": entity_type,
            "entityid": entity_id,
            "expirydate": expiry_date,
            "resourcename": resource_name,
            "resourcegroupid": resource_group_id,
            "attributename": attribute_name,
            "attributevalue": attribute_value,
            "violationtype": violation_type,
            "violationname": violation_name,
        }
        remove_nulls_from_dictionary(params)
        response = self.http_request("POST", "/incident/addToWhitelist", headers={"token": self._token}, params=params)
        return response

    def create_whitelist_request(self, tenant_name: str, whitelist_name: str, entity_type: str) -> dict:
        """Create a whitelist by sending a POST request.

        Args:
            tenant_name: Name of the tenant the whitelist belongs to.
            whitelist_name: Name of the whitelist.
            entity_type: Type of entity that the whitelist is intended to hold.

        Returns:
            Response from API.
        """
        params = {"tenantname": tenant_name, "whitelistname": whitelist_name, "entitytype": entity_type}
        remove_nulls_from_dictionary(params)
        whitelist = self.http_request("POST", "/incident/createGlobalWhitelist", headers={"token": self._token}, params=params)
        return whitelist

    def delete_whitelist_entry_request(
        self,
        tenant_name: str,
        whitelist_name: str,
        whitelist_type: str,
        entity_id: str,
        attribute_name: str,
        attribute_value: str,
    ) -> dict:
        """Delete a whitelist entry by sending POST request.

        Args:
            tenant_name: Name of the tenant the whitelist belongs to.
            whitelist_name: Name of the whitelist.
            whitelist_type: Type of whitelist that user wants to delete from.
            entity_id: Entity ID value that needs to be removed from the whitelist.
            attribute_name: Name of the attribute being removed.
            attribute_value: The value of the attribute being removed.

        Returns:
            Response from API.
        """
        params = {
            "tenantname": tenant_name,
            "whitelistname": whitelist_name,
            "whitelisttype": whitelist_type,
            "entityid": entity_id,
            "attributename": attribute_name,
            "attributevalue": attribute_value,
        }
        remove_nulls_from_dictionary(params)
        return self.http_request("GET", "/incident/removeFromWhitelist", headers={"token": self._token}, params=params)

    def delete_lookup_table_config_and_data_request(self, name: str) -> str:
        """Delete a lookup table and its configuration data from Securonix.

        Args:
            name (str): Name of the lookup table.

        Returns:
            str: Response from API.
        """
        params = {"lookupTableName": name}
        return self.http_request(
            "DELETE",
            "/lookupTable/deleteLookupConfigAndData",
            headers={"token": self._token},
            params=params,
            response_type="text",
        )

    def get_lookup_tables_request(self, max_records: int | None = 50, offset: int | None = 0) -> list:
        """Get the list of lookup tables stored on the Securonix platform.

        Args:
            max_records (Optional[int]): Number of records to return. Default value is 50.
            offset (Optional[int]): Specify from which record the data should be returned.

        Returns:
            Response from API.
        """
        params = {"max": max_records, "offset": offset}
        return self.http_request("GET", "/lookupTable/listLookupTables", headers={"token": self._token}, params=params)

    def add_entry_to_lookup_table_request(self, name: str, entries: list[dict], tenant_name: str | None = None) -> str:
        """Adds the provided entries to the specified lookup table.

        Args:
            name (str): Name of the lookup table in which to add the data.
            entries (List[Dict]): List of entries to add to the table.
            tenant_name (Optional[str]): Tenant name to which the lookup table belongs to.
        """
        body = {"lookupTableName": name, "tenantName": tenant_name, "lookupTableData": entries}
        remove_nulls_from_dictionary(body)
        return self.http_request(
            "POST", "/lookupTable/addLookupTableData", headers={"token": self._token}, json=body, response_type="text"
        )

    def list_lookup_table_entries_request(
        self,
        name: str,
        query: str | None = None,
        attribute: str | None = "key",
        max_records: int | None = 15,
        offset: int | None = 0,
        page_num: int | None = 1,
        sort: str | None = None,
        order: str | None = "asc",
    ) -> list:
        """List the entries of the lookup table.

        Args:
            name (str): Name of the lookup table.
            query (Optional[str], optional): Query to filter the entries of the lookup table. Defaults to None.
            attribute (Optional[str], optional): Column name on which to filter the data. Defaults to 'key'.
            max_records (Optional[int], optional): Number of records to retrieve. Defaults to 15.
            offset (Optional[int], optional): Specify from which record the data should be returned. Defaults to 0.
            page_num (Optional[int], optional): Specify a value to retrieve the records from a specified page.
                Defaults to 1.
            sort (Optional[str]): Name of the column on which to sort the data.
            order (Optional[str]): The order in which to sort the data.

        Returns:
            List: List of lookup table entries.
        """
        headers = {"token": self._token, "Content-Type": "application/json"}

        body = {
            "lookupTableName": name,
            "query": query,
            "attribute": attribute,
            "max": max_records,
            "offset": offset,
            "pagenum": page_num,
            "sort": sort,
            "order": order,
        }
        remove_nulls_from_dictionary(body)
        payload = json.dumps(body)

        return self.http_request("GET", "/lookupTable/getLookupTableData", headers=headers, data=payload)

    def create_lookup_table_request(
        self, tenant_name: str, name: str, scope: str, field_names: list, encrypt: list, key: list
    ) -> dict:
        """Create a lookup table by sending a POST request.

        Args:
            tenant_name: Name of the tenant the whitelist belongs to.
            name: Lookup table name.
            scope: Scope of lookup table.
            field_names: Field names for lookup table.
            encrypt: Field name which data needs to be encrypted.
            key: Field name to be used as key.

        Returns:
            Response from API.
        """
        data: dict[str, Any] = {"lookupTableName": name, "lookupTableScope": scope, "tenantName": tenant_name}
        field_list: list = []
        for field in field_names:
            field_dic = {"fieldName": field, "encrypt": field in encrypt, "key": field in key}
            field_list.append(field_dic)
        data.update({"lookupFieldList": field_list})
        remove_nulls_from_dictionary(data)
        response = self.http_request(
            "POST", "/lookupTable/createLookupTable", headers={"token": self._token}, json=data, response_type="text"
        )
        return response

    def delete_lookup_table_entries(self, name: str, lookup_unique_keys: list[str]) -> str:
        """Delete entries from the lookup table.

        Args:
            name (str): Name of the lookup table.
            lookup_unique_keys (List[str]): List of keys to delete from the lookup table.

        Returns:
            str: Response from API.
        """
        data: dict[str, Any] = {"lookupTableName": name, "keyList": lookup_unique_keys}
        response = self.http_request(
            "DELETE", "/lookupTable/deleteLookupKeys", headers={"token": self._token}, json=data, response_type="text"
        )
        return response


def test_module(client: Client) -> str:
    """
    Performs basic get request to get incident samples
    """
    params = demisto.params()
    client.list_workflows_request()

    if params.get("isFetch"):
        validate_configuration_parameters(params)
        validate_mirroring_parameters(params=params)

        timestamp_format = "%Y-%m-%dT%H:%M:%S.%fZ"
        from_epoch = date_to_timestamp(parse_date_range("1 day", utc=True)[0], date_format=timestamp_format)
        to_epoch = date_to_timestamp(datetime.now(), date_format=timestamp_format)
        client.list_incidents_request(from_epoch, to_epoch, incident_status="opened")

    return "ok"


def list_workflows(client: Client, *_) -> tuple[str, dict, dict]:
    """List all workflows.

    Args:
        client: Client object with request.
        *_:

    Returns:
        Outputs.
    """
    workflows = client.list_workflows_request()
    workflows_readable, workflows_outputs = parse_data_arr(workflows)
    human_readable = tableToMarkdown(
        name="Available workflows:", t=workflows_readable, headers=["Workflow", "Type", "Value"], removeNull=True
    )
    entry_context = {"Securonix.Workflows(val.Workflow == obj.Workflow)": workflows_outputs}
    return human_readable, entry_context, workflows


def get_default_assignee_for_workflow(client: Client, args: dict) -> tuple[str, dict, dict]:
    """Perform action on an incident.

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

    Returns:
        Outputs.
    """
    workflow = str(args.get("workflow"))
    default_assignee = client.get_default_assignee_for_workflow_request(workflow)
    workflow_output = {
        "Workflow": workflow,
        "Type": default_assignee.get("type"),
        "Value": default_assignee.get("value"),
    }
    entry_context = {"Securonix.Workflows(val.Workflow === obj.Workflow)": workflow_output}
    human_readable = f"Default assignee for the workflow {workflow} is: {default_assignee.get('value')}."
    return human_readable, entry_context, default_assignee


def list_possible_threat_actions(client: Client, *_) -> tuple[str, dict, dict]:
    """List all workflows.

    Args:
        client: Client object with request.
        *_:

    Returns:
        Outputs.
    """
    threat_actions = client.list_possible_threat_actions_request()
    human_readable = f"Possible threat actions are: {', '.join(threat_actions)}."
    entry_context = {"Securonix.ThreatActions": threat_actions}
    return human_readable, entry_context, threat_actions


def list_policies(client: Client, *_) -> tuple[str, dict, dict]:
    """List all policies.

    Args:
        client: Client object with request.
        *_:

    Returns:
        Outputs.
    """
    policies_xml = client.list_policies_request()
    policies_json = xml2json(policies_xml)
    policies = json.loads(policies_json)
    policies_arr = policies.get("policies").get("policy")
    policies_readable, policies_outputs = parse_data_arr(policies_arr)
    headers = ["ID", "Name", "Criticality", "Created On", "Created By", "Description"]
    human_readable = tableToMarkdown(name="Policies:", t=policies_readable, headers=headers, removeNull=True)
    entry_context = {"Securonix.Policies(val.ID === obj.ID)": policies_outputs}

    return human_readable, entry_context, policies


def list_resource_groups(client: Client, *_) -> tuple[str, dict, dict]:
    """List all resource groups.

    Args:
        client: Client object with request.
        *_:

    Returns:
        Outputs.
    """
    resource_groups_xml = client.list_resource_groups_request()

    resource_groups_json = xml2json(resource_groups_xml)
    resource_groups = json.loads(resource_groups_json)
    resource_groups_arr = resource_groups.get("resourceGroups").get("resourceGroup")

    resource_groups_readable, resource_groups_outputs = parse_data_arr(resource_groups_arr)
    headers = ["Name", "Type"]
    human_readable = tableToMarkdown(name="Resource groups:", t=resource_groups_readable, headers=headers, removeNull=True)
    entry_context = {"Securonix.ResourceGroups(val.Name === obj.Name)": resource_groups_outputs}

    return human_readable, entry_context, resource_groups


def list_users(client: Client, *_) -> tuple[str, dict, dict]:
    """List all users.

    Args:
        client: Client object with request.
        *_:

    Returns:
        Outputs.
    """
    users_xml = client.list_users_request()

    users_json = xml2json(users_xml)
    users = json.loads(users_json)
    users_arr = users.get("users").get("user")

    users_readable, users_outputs = parse_data_arr(users_arr)
    headers = ["Employee Id", "First Name", "Last Name", "Criticality", "Title", "Email"]
    human_readable = tableToMarkdown(name="Resource groups:", t=users_readable, headers=headers, removeNull=True)
    entry_context = {"Securonix.Users(val.EmployeeID === obj.EmployeeID)": users_outputs}

    return human_readable, entry_context, users


def list_activity_data(client: Client, args) -> tuple[str, dict, dict]:
    """List activity data.

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

    Returns:
        Outputs.
    """
    from_ = args.get("from", "").strip()
    to_ = args.get("to", "").strip()
    query = escape_spotter_query(args.get("query", "").strip())
    max_records = arg_to_number(args.get("max", "1000"), arg_name="max")

    if max_records is not None and (max_records < 1 or max_records > 10000):
        raise ValueError(MESSAGE["INVALID_MAX_VALUE"])

    activity_data = client.list_activity_data_request(from_, to_, query, max_records)  # type: ignore

    if activity_data.get("error"):
        raise Exception(
            f"Failed to get activity data in the given time frame.\nError from Securonix is: {activity_data.get('errorMessage')}"
        )

    activity_events = activity_data.get("events")
    activity_readables, activity_outputs = parse_data_arr(activity_events)
    for index, activity in enumerate(activity_readables):
        if activity.get("Eventid"):
            activity["EventID"] = activity.get("Eventid")
            del activity["Eventid"]
        if index < len(activity_outputs) and activity_outputs[index].get("Eventid"):
            activity_outputs[index]["EventID"] = activity_outputs[index].get("Eventid")
            del activity_outputs[index]["Eventid"]
        if "Timeline" in activity:
            activity["Timeline"] = timestamp_to_datestring(activity.get("Timeline", 0), is_utc=True)
    headers = ["EventID", "Eventtime", "Message", "Accountname", "Timeline", "Devicehostname", "Accountresourcekey"]
    human_readable = tableToMarkdown(
        name="Activity data:",
        t=[
            {key: string_escape_MD(value) for key, value in activity_readable.items()} for activity_readable in activity_readables
        ],
        headers=headers,
        removeNull=True,
    )

    pagination_data = {
        "totalDocuments": activity_data.get("totalDocuments"),
        "message": activity_data.get("message"),
        "queryId": activity_data.get("queryId"),
        "command_name": "securonix-list-activity-data",
    }

    entry_context = {"Securonix.Activity(val.command_name === obj.command_name)": remove_empty_elements(pagination_data)}

    activity_outputs = remove_empty_elements(activity_outputs)
    if activity_outputs:
        entry_context["Securonix.ActivityData(val.EventID === obj.EventID)"] = activity_outputs

    return human_readable, entry_context, activity_data


def list_violation_data(client: Client, args) -> list[CommandResults]:
    """List violation data.

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

    Returns:
        Outputs.
    """
    from_ = args.get("from", "").strip()
    to_ = args.get("to", "").strip()
    query = escape_spotter_query(args.get("query", "").strip())
    query_id = args.get("query_id", "").strip()
    max_violations = arg_to_number(args.get("max", "1000"))

    if max_violations is not None and max_violations <= 0:
        raise ValueError(MESSAGE["INVALID_MAX_VALUE"])

    violation_data = client.list_violation_data_request(from_, to_, query, query_id, max_violations)

    if violation_data.get("error"):
        raise Exception(
            f"Failed to get violation data in the given time frame.\n"
            f"Error from Securonix is: {violation_data.get('errorMessage')}"
        )
    violation_events = violation_data.get("events")
    if len(violation_events) > 0:  # type: ignore[arg-type]
        violation_readables, violation_outputs = parse_data_arr(violation_events)
        headers = ["EventID", "Eventtime", "Message", "Policyname", "Accountname"]
        human_readable = tableToMarkdown(
            name="Activity data:",
            t=[
                {key: string_escape_MD(value) for key, value in violation_readable.items()}
                for violation_readable in violation_readables
            ],
            headers=headers,
            removeNull=True,
        )

        data = {
            "totalDocuments": violation_data.get("totalDocuments"),
            "message": violation_data.get("message"),
            "queryId": violation_data.get("queryId"),
        }

        return [
            CommandResults(
                outputs_prefix="Securonix.ViolationData",
                readable_output=human_readable,
                outputs=remove_empty_elements(violation_outputs),
                raw_response=violation_data,
                outputs_key_field=[
                    "Policyname",
                    "Violator",
                    "Resourcegroupid",
                    "Tenantname",
                    "Resourcename",
                    "EmployeeID",
                    "Accountname",
                    "Ipaddress",
                ],
            ),
            CommandResults(
                outputs_prefix="Securonix.Violation",
                outputs=remove_empty_elements(data),
                readable_output=f"#### Next page query id: {data.get('queryId')}",
            ),
        ]
    else:
        return [CommandResults(readable_output="There are no violation events.", outputs={}, raw_response=violation_data)]


def run_polling_command(client, args: dict, command_name: str, search_function: Callable):
    """
    For Scheduling command.

    Args:
        client: Client object with request.
        args: Command arguments.
        command_name: Name of the command.
        search_function: Callable object of command.

    Returns:
        Outputs.
    """
    command_results = []
    result = search_function(client, args)
    command_results.append(result)
    outputs = result[0].raw_response.get("events")
    delay_type = client.get_securonix_retry_delay_type()
    retry_count: int = client.get_securonix_retry_count()
    retry_delay: int = client.get_securonix_retry_delay()

    if len(outputs) == 0 and retry_count > 0:
        if delay_type == "Exponential":
            retry_delay = client.get_securonix_retry_delay() * 2
        retry_timeout: int = retry_delay * retry_count + retry_count * 1
        policy_type = args.get("policy_type", "").strip().upper()
        if policy_type in POLICY_TYPES_TO_RETRY:
            args["to"] = datetime.now().astimezone(timezone.utc).strftime(r"%m/%d/%Y %H:%M:%S")
        polling_args = {"polling": True, **args}
        scheduled_command = ScheduledCommand(
            command=command_name, next_run_in_seconds=retry_delay, args=polling_args, timeout_in_seconds=retry_timeout
        )
        command_results.append(CommandResults(scheduled_command=scheduled_command))
        return command_results
    return result


def list_incidents(client: Client, args: dict) -> tuple[str, dict, dict]:
    """List incidents.

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

    Returns:
        Outputs.
    """
    timestamp_format = "%Y-%m-%dT%H:%M:%S.%fZ"
    from_, _ = parse_date_range(args.get("from"), utc=True)
    from_epoch = date_to_timestamp(from_, date_format=timestamp_format)
    to_ = args.get("to") if "to_" in args else datetime.now()
    to_epoch = date_to_timestamp(to_, date_format=timestamp_format)
    incident_types = str(args.get("incident_types")) if "incident_types" in args else "opened"
    max_incidents = str(args.get("max", "50"))
    incidents = client.list_incidents_request(from_epoch, to_epoch, incident_types, max_incidents)

    total_incidents = incidents.get("totalIncidents")
    if not total_incidents or float(total_incidents) <= 0.0:
        return "No incidents where found in this time frame.", {}, incidents

    incidents_items: list = incidents.get("incidentItems", [])
    incidents_readables, incidents_outputs = parse_data_arr(incidents_items)
    headers = ["IncidentID", "Incident Status", "Incident Type", "Priority", "Reason"]
    human_readable = tableToMarkdown(
        name="Incidents:",
        t=[
            {key: string_escape_MD(value) for key, value in incidents_readable.items()}
            for incidents_readable in incidents_readables
        ],
        headers=headers,
        removeNull=True,
    )
    entry_context = {"Securonix.Incidents(val.IncidentID === obj.IncidentID)": incidents_outputs}
    return human_readable, entry_context, incidents


def get_incident(client: Client, args: dict) -> tuple[str, dict, dict]:
    """Get incident.

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

    Returns:
        Outputs.
    """
    incident_id = str(args.get("incident_id"))
    incident = client.get_incident_request(incident_id)

    incident_items = incident.get("incidentItems")
    if not incident_items:
        raise Exception("Incident ID is not in Securonix.")
    incident_readables, incident_outputs = parse_data_arr(incident_items)
    human_readable = tableToMarkdown(
        name="Incident:",
        t=[
            {key: string_escape_MD(value) for key, value in incident_readable.items()} for incident_readable in incident_readables
        ],
        removeNull=True,
    )
    entry_context = {"Securonix.Incidents(val.IncidentID === obj.IncidentID)": incident_outputs}
    return human_readable, entry_context, incident


def get_incident_status(client: Client, args: dict) -> tuple[str, dict, dict]:
    """Get incident.

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

    Returns:
        Outputs.
    """
    incident_id = str(args.get("incident_id"))
    incident = client.get_incident_status_request(incident_id)
    incident_status = incident.get("status")
    incident_outputs = {"IncidentID": incident_id, "IncidentStatus": incident_status}
    entry_context = {"Securonix.Incidents(val.IncidentID === obj.IncidentID)": incident_outputs}
    return f"Incident {incident_id} status is {incident_status}.", entry_context, incident


def get_incident_workflow(client: Client, args: dict) -> tuple[str, dict, dict]:
    """Get incident workflow.

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

    Returns:
        Outputs.
    """
    incident_id = str(args.get("incident_id"))

    incident = client.get_incident_workflow_request(incident_id)
    incident_workflow = incident.get("workflow")
    incident_outputs = {"IncidentID": incident_id, "WorkflowName": incident_workflow}
    entry_context = {"Securonix.Incidents(val.IncidentId === obj.IncidentId)": incident_outputs}
    return f"Incident {incident_id} workflow is {incident_workflow}.", entry_context, incident


def get_incident_available_actions(client: Client, args: dict) -> tuple[str, dict, dict]:
    """Get incident available actions.

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

    Returns:
        Outputs.
    """
    incident_id = str(args.get("incident_id"))

    incident_actions = client.get_incident_available_actions_request(incident_id)
    if not incident_actions:
        return f"Incident {incident_id} does not have any available actions.", {}, incident_actions
    actions = []
    for action_details in incident_actions:
        actions.append(action_details.get("actionName"))

    incident_outputs = {"IncidentID": incident_id, "AvailableActions": actions}
    entry_context = {"Securonix.Incidents(val.IncidentID === obj.IncidentID)": incident_outputs}
    return f"Incident {incident_id} available actions: {actions}.", entry_context, incident_actions


def get_incident_attachments(client: Client, args: dict, incident_id: str = None):
    """Get incident attachments.

    Args:
        client: Client object with request.
        args: Usually demisto.args()
        incident_id: Incident ID

    Returns:
        Outputs.
    """
    incident_id_ = args.get("incident_id", "").strip()
    attachment_type = ",".join(argToList(args.get("attachment_type")))
    attachment_from = args.get("from")
    attachment_to = args.get("to")
    if attachment_from:
        attachment_from = attachment_from.strip()
        attachment_from = date_to_timestamp(arg_to_datetime(attachment_from, arg_name="attachment_from"))
    if attachment_to:
        attachment_to = attachment_to.strip()
        attachment_to = date_to_timestamp(arg_to_datetime(attachment_to, arg_name="attachment_to"))

    if incident_id:
        attachments_res = client.get_incident_attachments_request(incident_id)
    else:
        attachments_res = client.get_incident_attachments_request(
            incident_id_,
            attachment_type,
            attachment_from,  # type: ignore
            attachment_to,
        )  # type: ignore
    try:
        # So if there is no attachments then in response status code will be 200 and in content there is json with
        # error field
        if "Content-Disposition" not in attachments_res.headers:
            return CommandResults(readable_output=f"#### No Attachments found for Incident ID: {incident_id_}")
    except requests.exceptions.JSONDecodeError:  # type: ignore
        # Here if API have attachments then it will return byte data so then res.json() raise decode error. Means we
        # received attachments that's in below code there is debug log
        demisto.debug("Retrieved attachment for incident.")

    content_disposition = attachments_res.headers.get("Content-Disposition")
    filename = content_disposition.split(";")[1].replace("filename=", "")
    file_list = []
    if filename.startswith(incident_id or incident_id_):
        zip_obj = ZipFile(io.BytesIO(attachments_res.content))
        zip_filenames = zip_obj.namelist()
        zip_obj.extractall(path=os.path.abspath(os.getcwd()))
        zip_obj.close()
        file_list.append(
            CommandResults(
                outputs_prefix="Securonix.Incidents.Attachments",
                outputs=[{"IncidentID": incident_id_, "Files": zip_filenames}],
                readable_output=f"### Incident ID: {incident_id_}",
            )
        )
        for name in zip_filenames:
            with open(name, "br") as file:
                file_list.append(fileResult(filename=name, data=file.read()))
        return file_list
    else:
        file_list.extend(
            [
                CommandResults(
                    outputs_prefix="Securonix.Incidents.Attachments",
                    outputs=[{"IncidentID": incident_id_, "Files": filename}],
                    readable_output=f"### Incident ID: {incident_id_}",
                ),
                fileResult(filename=filename, data=attachments_res.content),
            ]
        )
        return file_list


def perform_action_on_incident(client: Client, args: dict) -> tuple[str, dict, dict]:
    """Perform action on an incident.

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

    Returns:
        Outputs.
    """
    incident_id = str(args.get("incident_id"))
    action = str(args.get("action"))
    action_parameters = str(args.get("action_parameters", ""))
    incident_result = client.perform_action_on_incident_request(incident_id, action, action_parameters)
    if incident_result != "submitted":
        raise Exception(f"Failed to perform the action {action} on incident {incident_id}.")
    return f"Action {action} was performed on incident {incident_id}.", {}, incident_result


def add_comment_to_incident(client: Client, args: dict) -> tuple[str, dict, dict]:
    """Add comment to an incident.

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

    Returns:
        Outputs.
    """
    incident_id = str(args.get("incident_id"))
    comment = str(args.get("comment"))
    incident = client.add_comment_to_incident_request(incident_id, comment)
    if not incident:
        raise Exception(f"Failed to add comment to the incident {incident_id}.")
    return f"Comment was added to the incident {incident_id} successfully.", {}, incident


def create_incident(client: Client, args: dict) -> tuple[str, dict, dict]:
    """Create an incident.

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

    Returns:
        Outputs.
    """
    violation_name = str(args.get("violation_name"))
    resource_group = str(args.get("resource_group"))
    resource_name = str(args.get("resource_name"))
    entity_type = str(args.get("entity_type"))
    entity_name = str(args.get("entity_name"))
    action_name = str(args.get("action_name"))
    workflow = str(args.get("workflow")) if "workflow" in args else None
    comment = str(args.get("comment")) if "comment" in args else None
    criticality = str(args.get("criticality")) if "criticality" in args else None

    if "create incident" in action_name and not workflow:
        raise Exception(f"Creating an incident with the action: {action_name}, Supply a workflow.")
    response = client.create_incident_request(
        violation_name, resource_group, resource_name, entity_type, entity_name, action_name, workflow, comment, criticality
    )
    result = response.get("result")
    if not result:
        raise Exception(f"Failed to create the incident.\nResponse from Securonix is: {response!s}")

    message = response.get("messages")
    if message:
        if isinstance(message, list) and "Invalid" in message[0]:
            message = message[0]
            raise Exception(f"Failed to create the incident with message: \n{message}")
        if "Invalid" in message:
            raise Exception(f"Failed to create the incident with message: \n{message}")

    incident_data = result.get("data")
    incident_items = incident_data.get("incidentItems")
    incident_readable, incident_outputs = parse_data_arr(incident_items)
    headers = ["Entity", "Incident Status", "Incident Type", "IncidentID", "Priority", "Reason", "Url"]
    human_readable = tableToMarkdown(
        name="Incident was created successfully", t=incident_readable, headers=headers, removeNull=True
    )
    entry_context = {"Securonix.Incidents(val.IncidentID === obj.IncidentID)": incident_outputs}
    return human_readable, entry_context, response


def list_watchlists(client: Client, *_) -> tuple[str, dict, dict]:
    """List all watchlists.

    Args:
        client: Client object with request.

    Returns:
        Outputs.
    """
    watchlists = client.list_watchlist_request()
    if not watchlists:
        raise Exception("Failed to list watchlists.")

    human_readable = f"Watchlists: {', '.join(watchlists)}."
    entry_context = {"Securonix.WatchlistsNames": watchlists}
    return human_readable, entry_context, watchlists


def get_watchlist(client: Client, args) -> tuple[str, dict, dict]:
    """Get watchlist data.

    Args:
        client: Client object with request.
        args: Usually demisto.args()
    Returns:
        Outputs.
    """
    watchlist_name = args.get("watchlist_name", "").strip()
    watchlist = client.get_watchlist_request(watchlist_name)

    watchlist_events = watchlist.get("events")
    if not watchlist_events:
        raise Exception(
            "Watchlist does not contain items.\nMake sure the watchlist is not empty and that the watchlist name is correct."
        )
    fields_to_drop = ["decayflag", "tenantid", "tenantname", "watchlistname", "type"]
    watchlist_readable, watchlist_events_outputs = parse_data_arr(watchlist_events, fields_to_drop=fields_to_drop)
    watchlist_outputs = {
        "Watchlistname": watchlist_name,
        "Type": watchlist_events[0].get("type"),
        "TenantID": watchlist_events[0].get("tenantid"),
        "TenantName": watchlist_events[0].get("tenantname"),
        "Events": watchlist_events_outputs,
    }
    headers = ["Entityname", "Fullname", "Workemail", "Expired"]
    human_readable = tableToMarkdown(
        name=f"Watchlist {watchlist_name} of type {watchlist_outputs.get('Type')}: ",
        t=watchlist_readable,
        headers=headers,
        removeNull=True,
    )
    entry_context = {"Securonix.Watchlists(val.Watchlistname === obj.Watchlistname)": watchlist_outputs}
    return human_readable, entry_context, watchlist


def create_watchlist(client: Client, args) -> tuple[str, dict, dict]:
    """Create a watchlist.

    Args:
        client: Client object with request.
        args: Usually demisto.args()
    Returns:
        Outputs.
    """
    watchlist_name = args.get("watchlist_name", "").strip()
    tenant_name = args.get("tenant_name", "").strip()

    response = client.create_watchlist_request(watchlist_name, tenant_name)

    if "successfully" not in response:
        raise Exception(f"Failed to list watchlists.\nResponse from Securonix is: {response!s}")
    human_readable = f"Watchlist {watchlist_name} was created successfully."
    watchlist = {"Watchlistname": watchlist_name, "TenantName": tenant_name}
    remove_nulls_from_dictionary(watchlist)
    entry_context = {
        "Securonix.Watchlists(val.Watchlistname === obj.Watchlistname && val.TenantName === obj.TenantName)": watchlist
    }
    return human_readable, entry_context, response


def check_entity_in_watchlist(client: Client, args) -> tuple[str, dict, dict]:
    """Check if entity is in a watchlist.

    Args:
        client: Client object with request.
        args: Usually demisto.args()
    Returns:
        Outputs.
    """
    entity_name = args.get("entity_name")
    watchlist_name = args.get("watchlist_name")
    watchlist = client.check_entity_in_watchlist_request(entity_name, watchlist_name)

    result = watchlist.get("result")
    if result == "NO" or (isinstance(result, list) and result[0] == "NO"):
        human_readable = f"Entity unique identifier {entity_name} provided is not on the watchlist: {watchlist_name}."
        output = {"Entityname": entity_name}
    else:  # YES
        human_readable = f"The Entity unique identifier {entity_name} provided is on the watchlist: {watchlist_name}."
        output = {"Entityname": entity_name, "Watchlistname": watchlist_name}
    entry_context = {"Securonix.EntityInWatchlist(val.Entityname === obj.Entityname)": output}
    return human_readable, entry_context, watchlist


def add_entity_to_watchlist(client: Client, args) -> tuple[str, dict, dict]:
    """Adds an entity to a watchlist.

    Args:
        client: Client object with request.
        args: Usually demisto.args()
    Returns:
        Outputs.
    """
    watchlist_name = args.get("watchlist_name")
    entity_type = args.get("entity_type")
    entity_name = args.get("entity_name")
    expiry_days = args.get("expiry_days") if "expiry_days" in args else "30"

    response = client.add_entity_to_watchlist_request(watchlist_name, entity_type, entity_name, expiry_days)

    if "successfull" not in response:
        raise Exception(
            f"Failed to add entity {entity_name} to the watchlist {watchlist_name}.\nError from Securonix is: {response}."
        )
    human_readable = f"Added successfully the entity {entity_name} to the watchlist {watchlist_name}."
    return human_readable, {}, response


def list_threats(client: Client, args: dict[str, Any]) -> tuple[str, dict, dict]:
    """List threats violated within a specified time range and get details about the threat models and policies violated.

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

    Returns:
        Outputs.
    """
    date_from = date_to_timestamp(arg_to_datetime(args.get("date_from"), arg_name="date_from"))
    date_to = date_to_timestamp(
        arg_to_datetime(args.get("date_to", datetime.now().strftime("'%Y-%m-%dT%H:%M:%S'")), arg_name="date_to")
    )
    page_size = arg_to_number(args.get("page_size", 10), arg_name="page_size")
    tenant_name = args.get("tenant_name")
    offset = arg_to_number(args.get("offset", 0), arg_name="offset")

    threat_response = client.list_threats_request(date_from, date_to, tenant_name, offset, page_size)  # type: ignore
    threat_response = remove_empty_elements(threat_response)

    threat_readable, threats_outputs = parse_data_arr(threat_response)

    headers = [
        "ThreatName",
        "EntityID",
        "Violator",
        "Category",
        "Resourcegroupname",
        "Resourcename",
        "Resourcetype",
        "GenerationTime",
        "Policies",
        "TenantID",
        "Tenantname",
    ]
    human_readable = tableToMarkdown(name="Threats:", t=threat_readable, headers=headers, removeNull=True)
    entry_context = {
        "Securonix.Threat(val.EntityID === obj.EntityID && val.Resourcename === obj.Resourcename && val.Resourcetype "
        "=== obj.Resourcetype && val.Resourcegroupname === obj.Resourcegroupname && val.Policies.toString() === "
        "obj.Policies.toString())": threats_outputs
    }
    return human_readable, entry_context, threat_response


def get_incident_activity_history(client: Client, args: dict[str, Any]) -> tuple[str, dict, list]:
    """Get the incident activity history for the specified incident ID.

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

    Returns:
        Outputs.
    """
    incident_id = args.get("incident_id", "").strip()

    # Raises error when user has provided '  ' in input.
    if not incident_id:
        raise ValueError("Incident ID is a required parameter.")

    # Retrieve activity history for the specified incident ID.
    activity_history = client.get_incident_activity_history_request(incident_id)

    # Prepare entry context for the command.
    # As the response is in such a format, we can not determine a primary key for the context data.
    entry_context = {"Securonix.IncidentHistory": activity_history}

    # Prepare human-readable output for the command.
    activity_history_readable = [
        {
            "Action Taken": h.get("actiontaken"),
            "Username": h.get("username"),
            "Event Time": h.get("eventTime"),
            "Status": h.get("status"),
            "Last Status": h.get("lastStatus"),
            "Comment": "\n".join([c.get("Comments", "") for c in h.get("comment", [])]),
            "Playbook ID": h.get("playBookOutput", {}).get("playBookId"),
            "Playbook Name": h.get("playBookOutput", {}).get("playBookName"),
            "Playbook Executor": h.get("playBookOutput", {}).get("executor"),
            "Attachment Name": h.get("attachment"),
        }
        for h in activity_history
    ]

    # Reversing the human-readable list, as we want to show the latest activity first, rather than the old.
    activity_history_readable.reverse()

    headers = [
        "Action Taken",
        "Username",
        "Event Time",
        "Status",
        "Last Status",
        "Comment",
        "Playbook ID",
        "Playbook Name",
        "Playbook Executor",
        "Attachment Name",
    ]
    human_readable = tableToMarkdown(
        f"Incident activity history for ID: {incident_id}", t=activity_history_readable, headers=headers, removeNull=True
    )

    return human_readable, entry_context, activity_history


def list_whitelists(client: Client, args: dict[str, Any]) -> tuple[str, dict, list]:
    """List all whitelist.

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

    Returns:
        Outputs.
    """
    tenant_name = args.get("tenant_name", "").strip()

    whitelists = client.list_whitelists_request(tenant_name)

    whitelists_entries = []

    for whitelist in whitelists:
        whitelist_details = whitelist.split("|", 3)
        if len(whitelist_details) < 3:
            empty_details = 3 - len(whitelist_details)
            whitelist_details += "null" * empty_details
        whitelists_entries.append(
            {
                "WhitelistName": whitelist_details[0].strip().replace("null", ""),
                "WhitelistType": whitelist_details[1].strip().replace("null", ""),
                "TenantName": whitelist_details[2].strip().replace("null", ""),
            }
        )

    whitelists_entries = remove_empty_elements(whitelists_entries)

    headers = ["WhitelistName", "WhitelistType", "TenantName"]
    human_readable = tableToMarkdown(name="Whitelists:", t=whitelists_entries, headers=headers, removeNull=True)
    entry_context = {
        "Securonix.Whitelist(val.WhitelistName === obj.WhitelistName && val.TenantName === obj.TenantName)": whitelists_entries
    }

    return human_readable, entry_context, whitelists


def get_whitelist_entry(client: Client, args: dict[str, Any]) -> tuple[str, dict, dict]:
    """Get information for the specified whitelist.

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

    Returns:
        Outputs.
    """
    tenant_name = args.get("tenant_name", "").strip()
    whitelist_name = args.get("whitelist_name", "").strip()

    whitelist = client.get_whitelist_entry_request(tenant_name, whitelist_name)

    if not whitelist:
        raise Exception("Whitelist does not contain items.\nMake sure the whitelist_name is not empty and it is correct.")

    whitelist_entries = []

    for key, val in whitelist.items():
        whitelist_entries.append({"Entity/Attribute": key, "ExpiryDate": val})
    watchlist_outputs = {"WhitelistName": whitelist_name, "TenantName": tenant_name, "Entries": whitelist_entries}
    remove_nulls_from_dictionary(watchlist_outputs)

    headers = ["Entity/Attribute", "ExpiryDate"]
    human_readable = tableToMarkdown(
        name=f"Whitelist: {whitelist_name}", t=remove_empty_elements(whitelist_entries), headers=headers, removeNull=True
    )
    entry_context = {
        "Securonix.Whitelist(val.WhitelistName === obj.WhitelistName && val.TenantName === obj.TenantName)": watchlist_outputs
    }

    return human_readable, entry_context, whitelist


def add_whitelist_entry(client: Client, args) -> tuple[str, dict, dict]:
    """Adds an entry to a whitelist.

    Args:
        client: Client object with request.
        args: Usually demisto.args()
    Returns:
        Outputs.
    """
    tenant_name = args.get("tenant_name", "").strip()
    whitelist_name = args.get("whitelist_name", "").strip()
    whitelist_type = args.get("whitelist_type", "").strip()
    entity_type = args.get("entity_type", "").strip()
    entity_id = args.get("entity_id", "").strip()
    expiry_date = args.get("expiry_date", "").strip()
    resource_name = args.get("resource_name", "").strip()
    resource_group_id = args.get("resource_group_id", "").strip()
    attribute_name = args.get("attribute_name", "").strip()
    attribute_value = args.get("attribute_value", "").strip()
    violation_type = args.get("violation_type", "").strip()
    violation_name = args.get("violation_name", "").strip()

    if whitelist_type not in ["Global", "Attribute"]:
        raise Exception("Provide valid whitelist_type")

    if whitelist_type == "Global" and entity_type not in ["Users", "Activityaccount", "Resources", "Activityip"]:
        raise Exception("Provide valid entity_type")

    if whitelist_type == "Attribute":
        if attribute_name not in ["source ip", "resourcetype", "transactionstring"]:
            raise Exception("Provide valid attribute_name")

        if violation_type not in ["Policy", "ThreatModel", "Functionality"]:
            raise Exception("Provide valid violation_type")

    try:
        if expiry_date:
            datetime.strptime(expiry_date, "%m/%d/%Y")
    except ValueError:
        raise Exception("exipry_date is not in MM/DD/YYYY format")

    response = client.add_whitelist_entry_request(
        tenant_name,
        whitelist_name,
        whitelist_type,
        entity_type,
        entity_id,
        expiry_date,
        resource_name,
        resource_group_id,
        attribute_name,
        attribute_value,
        violation_type,
        violation_name,
    )
    if response.get("status_code") == 400:
        raise Exception(f"Failed to add entity to the whitelist.\nError from Securonix is: {response}.")
    human_readable = "Entity added to global whitelist Successfully."

    return human_readable, {}, response


def create_whitelist(client: Client, args) -> tuple[str, dict, dict]:
    """Create a whitelist.

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

    Returns:
        Outputs.
    """
    tenant_name = args.get("tenant_name", "").strip()
    whitelist_name = args.get("whitelist_name", "").strip()
    entity_type = args.get("entity_type", "").strip()

    if entity_type not in VALID_ENTITY_TYPE:
        raise Exception(f"{entity_type} is invalid entity_type. Valid entity types are {VALID_ENTITY_TYPE}")

    response = client.create_whitelist_request(tenant_name, whitelist_name, entity_type)

    if "successfully" not in str(response.get("messages")).lower():
        raise Exception(f"Failed to create whitelist.\nResponse from Securonix is: {response!s}")

    human_readable = f"Whitelist {whitelist_name} was created successfully."

    return human_readable, {}, response


def delete_lookup_table_config_and_data(client: Client, args: dict[str, Any]) -> tuple:
    """Delete a lookup table and its configuration data from Securonix.

    Args:
        client (Client): Client object with request.
        args: (Dict[str, Any]): Usually demisto.args().

    Returns:
        Outputs.
    """
    name = args.get("name", "").strip()

    if not name:
        raise ValueError("Lookup table name is a required argument.")

    response = client.delete_lookup_table_config_and_data_request(name=name)

    if "successfully" not in response.lower():
        raise Exception(f"Failed to delete lookup table and its data.\nResponse from Securonix is: {response!s}")

    human_readable = f"The table {name} has been deleted successfully on Securonix."

    entry_context = {
        "Securonix.LookupTable(val.lookupTableName === obj.lookupTableName)": {"lookupTableName": name, "isDeleted": True}
    }
    return human_readable, entry_context, response


def delete_whitelist_entry(client: Client, args) -> tuple[str, dict, dict]:
    """Delete an entry from the whitelist.

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

    Returns:
        Outputs.
    """
    tenant_name = args.get("tenant_name", "").strip()
    whitelist_name = args.get("whitelist_name", "").strip()
    whitelist_type = args.get("whitelist_type", "").strip()
    entity_id = args.get("entity_id", "").strip()
    attribute_name = args.get("attribute_name", "").strip()
    attribute_value = args.get("attribute_value", "").strip()

    validate_delete_whitelist_parameters(whitelist_type, entity_id, attribute_name, attribute_value, tenant_name)

    response = client.delete_whitelist_entry_request(
        tenant_name, whitelist_name, whitelist_type, entity_id, attribute_name, attribute_value
    )
    result = response.get("result", [])

    if "successfully" not in str(result).lower():
        raise Exception(f"Failed to remove entry from whitelist.\nResponse from Securonix is: {result!s}")

    human_readable = "".join(result).replace(" ..! ", ".")

    return human_readable, {}, response


def list_lookup_tables(client: Client, args: dict[str, Any]) -> tuple[str, dict, list]:
    """Retrieves a list of lookup tables available within the Securonix platform.

    Args:
        client (Client): Client object with request.
        args: (Dict[str, Any]): Usually demisto.args().

    Returns:
        Outputs.
    """
    max_records = arg_to_number(args.get("max", "50").strip() or "50")
    offset = arg_to_number(args.get("offset", "0").strip() or "0")

    lookup_tables = client.get_lookup_tables_request(max_records=max_records, offset=offset)

    lookup_table_readable = [
        {
            "Tenant Name": table.get("tenantName", ""),
            "Lookup Table Name": table.get("lookupTableName", ""),
            "Total Records": table.get("totalRecords", ""),
            "Scope": table.get("scope", ""),
            "Type of Lookup Table": table.get("type", ""),
        }
        for table in lookup_tables
    ]

    headers = ["Tenant Name", "Lookup Table Name", "Total Records", "Scope", "Type of Lookup Table"]
    human_readable = tableToMarkdown("Lookup Tables:", t=lookup_table_readable, headers=headers, removeNull=True)

    entry_context = {"Securonix.LookupTable(val.lookupTableName === obj.lookupTableName)": remove_empty_elements(lookup_tables)}

    return human_readable, entry_context, lookup_tables


def validate_expiry_time_of_lookup_table_entries(table_entries: Union[dict, list[dict]]) -> None:
    """Check whether the expiration time of the lookup table entries is valid.

    Args:
        table_entries (Union[Dict, List[Dict]]): Lookup table entries to add to the lookup table.
    """

    def is_expiration_time_in_valid_format(expiration_time: str) -> None:
        try:
            datetime.strptime(expiration_time, "%m/%d/%Y")
        except ValueError as exception:
            raise ValueError("The value of expiryDate field is not in MM/DD/YYYY format.") from exception

    if isinstance(table_entries, dict):
        expiration_time = table_entries.get("expiryDate")

        if expiration_time:
            is_expiration_time_in_valid_format(expiration_time)

    if isinstance(table_entries, list):
        for entry in table_entries:
            expiration_time = entry.get("expiryDate")

            if expiration_time:
                is_expiration_time_in_valid_format(expiration_time)


def add_entry_to_lookup_table(client: Client, args: dict[str, Any]) -> tuple:
    """Add entries to the lookup table.

    Args:
        client (Client): Client object with request.
        args: (Dict[str, Any]): Usually demisto.args().

    Returns:
        Outputs.
    """
    table_name = args.get("name", "").strip()
    tanant_name = args.get("tenant_name", "").strip()
    json_data = args.get("json_data", "").strip()
    entry_id = args.get("file_entry_id", "").strip()

    # Validate the command arguments.
    if not table_name:
        raise ValueError("Lookup table name is a required parameter.")

    if not json_data and not entry_id:
        raise ValueError("Either JSON data or file entry ID is required to add data to lookup table.")

    # File will take precedence over JSON data.
    if entry_id:
        file_obj = demisto.getFilePath(entry_id)
        file_path = file_obj.get("path")

        try:
            with open(file_path) as file:
                json_entries = json.loads(file.read())
        except json.JSONDecodeError as exception:
            raise Exception(f"Could not able to parse the provided JSON data. Error: {exception!s}") from exception
    else:
        try:
            json_entries = json.loads(json_data)
        except json.JSONDecodeError as exception:
            raise Exception(f"Could not able to parse the provided JSON data. Error: {exception!s}") from exception

    validate_expiry_time_of_lookup_table_entries(table_entries=json_entries)

    if isinstance(json_entries, dict):
        json_entries = [json_entries]

    response = client.add_entry_to_lookup_table_request(name=table_name, entries=json_entries, tenant_name=tanant_name)

    if "successfully" not in response.lower():
        raise Exception(f"Failed adding entries to the lookup table. Error from Securonix: {response!s}")

    return response, {}, response


def prepare_entry_contex_lookup_table_entries_list(entries: list[dict]) -> list[dict]:
    """Prepare entry context for list-lookup-table-entries command.

    Args:
        entries (List[Dict]): Response received from API.

    Returns:
        List[Dict]: Entry context list.
    """
    new_entries = []

    for entry in entries:
        new_entry: dict[str, Any] = {"entry": []}

        for key, value in entry.items():
            if key.startswith("value_"):
                new_entry["entry"].append(
                    {
                        "key": key[6:],  # Remove "value_" from the key.
                        "value": value,
                    }
                )
            else:
                new_entry[key] = value

        new_entries.append(new_entry)

    return new_entries


def prepare_human_readable_for_lookup_table_entries_list(entries: list[dict]) -> str:
    """Prepare human-readable string for lookup-table-entries-list command.

    Args:
        entries (List[Dict]): List of entries.

    Returns:
        str: Markdown string.
    """
    table = []

    for entry in entries:
        new_entry = {
            "Key": entry.get("key"),
            "Timestamp": entry.get("timestamp"),
            "Lookup Unique Key": entry.get("lookupuniquekey"),
            "Tenant Name": entry.get("tenantname"),
        }
        for e in entry.get("entry", []):
            new_entry[e["key"]] = e["value"]
        table.append(new_entry)

    return tableToMarkdown(name="Entries:", t=table, removeNull=True)


def list_lookup_table_entries(client: Client, args: dict[str, Any]) -> tuple:
    """List the entries of the provided lookup table.

    Args:
        client (Client): Client object with request.
        args (Dict[str, Any]): Usually demisto.args().

    Returns:
        Outputs.
    """
    name = args.get("name", "").strip()
    query = args.get("query", "").strip()
    attribute = args.get("attribute", "key").strip() or "key"
    max_records = arg_to_number(args.get("max", "15").strip() or "15")
    offset = arg_to_number(args.get("offset", "0").strip() or "0")
    page_num = arg_to_number(args.get("page_num", "1").strip() or "1")
    sort = args.get("sort", "").strip()
    order = args.get("order", "asc").strip().lower() or "asc"

    # Validate required parameters.
    if not name:
        raise ValueError("Lookup table name is a required argument.")

    # Validate order argument.
    if order and order not in ["asc", "desc"]:
        raise ValueError('Order argument must be "asc" or "desc".')

    response = client.list_lookup_table_entries_request(
        name=name,
        query=query,
        attribute=attribute,
        max_records=max_records,
        offset=offset,
        page_num=page_num,
        sort=sort,
        order=order,
    )

    entry_context_list: list[dict] = prepare_entry_contex_lookup_table_entries_list(response)
    human_readable = prepare_human_readable_for_lookup_table_entries_list(entry_context_list)

    entry_context = {
        "Securonix.LookupTableEntries(val.lookupuniquekey === obj.lookupuniquekey)": remove_empty_elements(entry_context_list)
    }

    return human_readable, entry_context, response


def create_lookup_table(client: Client, args) -> tuple[str, dict, dict]:
    """Create a lookup table.

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

    Returns:
        Outputs.
    """
    name = args.get("name", "").strip()
    scope = args.get("scope")
    tenant_name = args.get("tenant_name", "").strip()
    field_names = argToList(args.get("field_names", "").strip())
    encrypt = argToList(args.get("encrypt", "").strip())
    key = argToList(args.get("key", "").strip())

    response = client.create_lookup_table_request(tenant_name, name, scope, field_names, encrypt, key)
    if "successfully" not in response.lower():  # type: ignore[attr-defined]
        raise Exception(f"Failed to create lookup table.\nResponse from Securonix is: {response}")
    human_readable = f"Lookup Table {name} created successfully."

    return human_readable, {}, response


def delete_lookup_table_entries(client: Client, args: dict[str, Any]):
    """Delete entries from the lookup table.

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

    Returns:
        Outputs.
    """
    name = args.get("name", "").strip()
    lookup_unique_keys = argToList(args.get("lookup_unique_keys", "").strip())

    if not name:
        raise ValueError("Lookup table name is a required parameter.")

    if not lookup_unique_keys:
        raise ValueError("At least one lookup table key is required to execute the command.")

    response = client.delete_lookup_table_entries(name=name, lookup_unique_keys=lookup_unique_keys)
    human_readable = f"Successfully deleted following entries from {name}: {', '.join(lookup_unique_keys)}."

    return human_readable, {}, response


def fetch_securonix_incident(
    client: Client,
    fetch_time: str | None,
    incident_status: str,
    default_severity: str,
    max_fetch: str,
    last_run: dict,
    close_incident: bool,
) -> list:
    """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`
        incident_status: Incident statuses to fetch, can be: all, opened, closed, updated
        default_severity: Default incoming incident severity
        last_run: Last fetch object.
        max_fetch: maximum amount of incidents to fetch
        close_incident: Close respective Securonix incident.

    Returns:
        incidents, new last_run
    """
    timestamp_format = "%Y-%m-%dT%H:%M:%S.%fZ"
    if not last_run:  # if first time running
        new_last_run = {
            "from": int(
                arg_to_datetime(fetch_time, arg_name="First fetch time range").timestamp() * 1000  # type: ignore
            ),
            "to": int(datetime.now(tz=timezone.utc).timestamp() * 1000),
            "offset": 0,
        }
        demisto.debug(f"No last run object found, creating new last run object with value: {json.dumps(new_last_run)}")
    elif "time" in last_run:
        demisto.debug("Upgrading the last run object.")
        new_last_run = last_run
        new_last_run["from"] = date_to_timestamp(last_run.get("time"), date_format=timestamp_format)
        new_last_run["to"] = int(datetime.now(tz=timezone.utc).timestamp() * 1000)
        new_last_run["offset"] = 0
        del new_last_run["time"]
    else:
        new_last_run = last_run
        demisto.debug("Using the last run object got from the previous run.")

    demisto_incidents: list = []

    from_epoch = new_last_run.get("from")
    to_epoch = new_last_run.get("to")
    offset = new_last_run.get("offset")
    demisto.info(f"Fetching Securonix incidents. From: {from_epoch}. To: {to_epoch}. Offset: {offset}")

    if incident_status.lower() == "all":
        incident_status = "updated"

    securonix_incidents = client.list_incidents_request(
        from_epoch=str(from_epoch),
        to_epoch=str(to_epoch),
        incident_status=incident_status,
        max_incidents=max_fetch,
        offset=str(offset),
    )

    if securonix_incidents:
        already_fetched: list[str] = new_last_run.get("already_fetched", [])  # type: ignore
        incident_items = securonix_incidents.get("incidentItems", [])

        for incident in incident_items:
            incident_id = str(incident.get("incidentId", 0))
            violator_id = str(incident.get("violatorId", 0))
            reasons = incident.get("reason", [])
            policy_list: list[str] = []
            policy_stages_json = {}
            policy_stages_table = []
            if isinstance(reasons, list):
                for reason in reasons:
                    if isinstance(reason, str) and "PolicyType" in reason:
                        policy_type = reason.split(":")[-1].strip()
                        incident["policy_type"] = policy_type
                    if isinstance(reason, dict) and "Policies" in reason:
                        # Parse the policies.
                        policies = reason.get("Policies")
                        if not isinstance(policies, dict):
                            continue
                        policy_keys = list(policies.keys())
                        policy_keys.sort()
                        for stage_key in policy_keys:
                            stage_dict = policies.get(stage_key)
                            if not stage_dict or not isinstance(stage_dict, dict):
                                continue
                            stage_name = list(stage_dict.keys())[0]
                            stage_policies: list[str] = stage_dict.get(stage_name)  # type: ignore
                            if not stage_policies or not isinstance(stage_policies, list):
                                continue
                            stage_policies_str = ", ".join(str(policy) for policy in stage_policies)  # type: ignore
                            policy_list.extend(stage_policies)  # type: ignore
                            policy_stages_json[f"{stage_key}:{stage_name}"] = stage_policies  # noqa: E231
                            policy_stages_table.append(
                                {"Stage Name": f"{stage_key}:{stage_name}", "Policies": stage_policies_str}  # noqa: E231
                            )

            if policy_list:
                # Add the parsed policies to the incident.
                incident["policy_list"] = list(dict.fromkeys(policy_list))
                incident["policy_stages_json"] = policy_stages_json
                incident["policy_stages_table"] = policy_stages_table

            if incident_id not in already_fetched:
                incident.update(get_mirroring())

                if close_incident:
                    incident["close_sx_incident"] = True
                else:
                    incident["close_sx_incident"] = False

                incident_name = get_incident_name(incident, incident_id, violator_id)

                demisto_incidents.append(
                    {
                        "name": incident_name,
                        "occurred": timestamp_to_datestring(incident.get("lastUpdateDate")),
                        "severity": incident_priority_to_dbot_score(incident.get("priority"), default_severity),
                        "rawJSON": json.dumps(incident),
                    }
                )

                already_fetched.append(str(incident_id))

        # If incidents returned from API, then only update the offset value.
        if incident_items:
            new_offset = offset + len(incident_items)  # type: ignore
            new_from = from_epoch
            new_to = to_epoch
            demisto.debug(f"Updating the offset to {new_offset}.")
        # Else, reset the value of offset. From value would be the to_epoch of previous run.
        # And, To value would be current timestamp.
        else:
            new_offset = 0
            new_from = to_epoch
            new_to = int(datetime.now(tz=timezone.utc).timestamp() * 1000)
            demisto.debug(f"Resetting the offset to 0. New From is {new_from}. New To is {new_to}.")

        new_last_run.update(
            {
                "from": new_from,  # type: ignore
                "to": new_to,  # type: ignore
                "offset": new_offset,
                "already_fetched": already_fetched,  # type: ignore
            }
        )

    demisto.setLastRun({"value": json.dumps(new_last_run)})

    demisto.info(f"Creating {len(demisto_incidents)} new incidents.")
    return demisto_incidents


def fetch_securonix_threat(client: Client, fetch_time: str | None, tenant_name: str, max_fetch: str, last_run: dict) -> list:
    """Uses to fetch threats into Demisto.

    Args:
        client: Client object with request
        fetch_time: From when to fetch if first time, e.g. `3 days`
        tenant_name: Name of the tenant from which threat belongs to
        last_run: Last fetch object.
        max_fetch: maximum amount of incidents to fetch

    Returns:
        incidents, new last_run
    """
    timestamp_format = "%Y-%m-%dT%H:%M:%S.%fZ"
    if not last_run:  # if first time running
        new_last_run = {
            "time": arg_to_datetime(fetch_time, arg_name="First fetch time range").strftime(  # type: ignore
                timestamp_format
            )
        }
    else:
        new_last_run = last_run
    demisto_incidents: list = []
    from_epoch = date_to_timestamp(new_last_run.get("time"), date_format=timestamp_format)
    to_epoch = date_to_timestamp(datetime.now(), date_format=timestamp_format)
    # Get threats from Securonix
    demisto.info(f"Fetching Securonix Threats. From: {from_epoch}. To: {to_epoch}")

    offset = last_run.get("offset", 0)

    securonix_threats = client.list_threats_request(from_epoch, to_epoch, tenant_name, offset, max_fetch)  # type: ignore

    already_fetched = last_run.get("already_fetched", [])
    if securonix_threats:
        for threat in securonix_threats:
            threat_name = threat.get("threatname", "Securonix Threat")
            entity_id = threat.get("entityid", "")
            resource_name = threat.get("resourcename", "")
            resource_type = threat.get("resourcetype", "")
            resource_group_name = threat.get("resourcegroupname", "")
            policies = ", ".join(sorted(threat.get("policies", [])))

            if (entity_id, resource_name, resource_type, resource_group_name, policies) not in already_fetched:
                incident_name = f"{threat_name}, Entity ID: {entity_id}"

                demisto_incidents.append(
                    {
                        "name": incident_name,
                        "occurred": timestamp_to_datestring(threat.get("generationtime_epoch", datetime.now())),
                        "rawJSON": json.dumps(threat),
                    }
                )
                already_fetched.append((entity_id, resource_name, resource_type, resource_group_name, policies))

        new_last_run.update({"offset": offset + int(max_fetch), "already_fetched": already_fetched})
    else:
        now = datetime.now().strftime(timestamp_format)
        new_last_run.update({"offset": 0, "time": now})

    demisto.setLastRun({"value": json.dumps(new_last_run)})
    return demisto_incidents


def get_incident_name(incident: dict, incident_id: str, violator_id: str) -> str:
    """Get the incident name by concatenating the incident reasons if possible

    Args:
        incident: incident details
        incident_id: the incident id
        violator_id: the violator id

    Returns:
        incident name.
    """
    incident_reasons = incident.get("reason", [])
    try:
        incident_reason = ""
        for reason in incident_reasons:
            if isinstance(reason, str):
                if reason.startswith("Threat Model: "):
                    incident_reason += f"{reason[14:]}, "
                if reason.startswith("Policy: "):
                    incident_reason += f"{reason[8:]}, "
        if incident_reason:
            # Remove ", " last chars and concatenate with the incident ID
            incident_name = f"{incident_reason[:-2]}: {incident_id}"
        else:
            incident_name = f"Securonix Incident {incident_id}, Violator ID: {violator_id}"
    except ValueError:
        incident_name = f"Securonix Incident: {incident_id}."

    return incident_name


def get_modified_remote_data_command(client: Client, args: dict[str, Any]) -> GetModifiedRemoteDataResponse:
    """Retrieve the IDs of the incidents which are updated since the last updated.

    Args:
        client: XSOAR client to use.
        args:
            lastUpdate: When was the last time we retrieved data.

    Returns:
        GetModifiedRemoteDataResponse: List of incidents IDs which are modified since the last update.
    """
    # Retrieve the arguments passed with the command.
    command_args = GetModifiedRemoteDataArgs(args)

    # Parse the last update date got from the command arguments.
    command_last_run_date = dateparser.parse(command_args.last_update, settings={"TIMEZONE": "UTC"})

    demisto.debug(f"Last update date of get-modified-remote-data command is {command_last_run_date}.")

    # Convert the datetime object to epoch as the API requires the time in epoch format.
    from_epoch_time = date_to_timestamp(command_last_run_date)
    # End time for the API call will be current time.
    to_epoch_time = date_to_timestamp(datetime.now(tz=timezone.utc))

    offset = 0
    len_of_incidents = 0
    updated_incident_ids = []

    while True:
        offset += len_of_incidents

        list_incidents_resp = client.list_incidents_request(
            from_epoch=str(from_epoch_time),
            to_epoch=str(to_epoch_time),
            incident_status="updated",
            max_incidents="500",
            offset=str(offset),
        )

        len_of_incidents = len(list_incidents_resp.get("incidentItems", []))

        if len_of_incidents == 0:
            break

        # Extract the IDs of the incidents.
        updated_incident_ids.extend([inc.get("incidentId") for inc in list_incidents_resp.get("incidentItems", [])])

        if len(updated_incident_ids) >= 10000:
            break

    # Filter out None values if there are any.
    updated_incident_ids: list[str] = list(filter(None, updated_incident_ids))

    # Filter out any duplicate incident IDs.
    updated_incident_ids = list(set(updated_incident_ids))

    # At max 10,000 incidents should be updated.
    updated_incident_ids = updated_incident_ids[:10000]

    demisto.debug(f"Number of incidents modified between {from_epoch_time} to {to_epoch_time} are {len(updated_incident_ids)}.")
    demisto.debug(f"List of modified incident ids between {from_epoch_time} to {to_epoch_time} is {updated_incident_ids}.")

    return GetModifiedRemoteDataResponse(updated_incident_ids)


def get_remote_data_command(
    client: Client, args: dict[str, Any], close_states_of_securonix: list[str]
) -> Union[str, GetRemoteDataResponse]:
    """Return the updated incident and updated entries.

    Args:
        client: XSOAR client to use.
        args:
            id: Incident ID to retrieve.
            lastUpdate: When was the last time we retrieved data.
        close_states_of_securonix: List of Securonix incident states that can be considered as closed.

    Returns:
        Union[str, GetRemoteDataResponse]: first entry is the incident (which can be completely empty) and the new
            entries.
    """
    new_entries_to_return = []
    timestamp_format = "%Y-%m-%dT%H:%M:%S.%fZ"

    sx_incident_id: str = args.get("id")  # type: ignore
    demisto.debug(f"Getting update for remote {sx_incident_id}.")

    command_last_run_dt = arg_to_datetime(arg=args.get("lastUpdate"), arg_name="lastUpdate", required=True)
    command_last_run_epoch = date_to_timestamp(command_last_run_dt, date_format=timestamp_format)
    demisto.debug(f"The time when the last time get-remote-data command is called for current incident is {command_last_run_dt}.")

    # Retrieve the latest incident data from the Securonix platform.
    remote_incident_data = client.get_incident_request(incident_id=sx_incident_id)
    remote_incident_data = remote_incident_data.get("incidentItems", [])
    remote_incident_data = remote_incident_data[0]

    if not remote_incident_data:
        return "Incident was not found."

    # Check the last modified date of the incident fetched.
    incident_last_update_dt = arg_to_datetime(
        arg=remote_incident_data.get("lastUpdateDate"), arg_name="lastUpdateDate", required=True
    )
    incident_last_update_epoch = date_to_timestamp(incident_last_update_dt, date_format=timestamp_format)

    if command_last_run_epoch > incident_last_update_epoch:
        demisto.debug(f"Nothing new in the Securonix incident {sx_incident_id}.")
    else:
        demisto.debug(f"The Securonix incident {sx_incident_id} is updated.")

    activity_history = client.get_incident_activity_history_request(incident_id=sx_incident_id)
    filtered_history_entries = filter_activity_entries_by_time(activity_history, timestamp=command_last_run_epoch)

    # Update the status of XSOAR incident.
    close_incident = argToBoolean(demisto.params().get("close_incident", False))

    # Skip closing of XSOAR if the close Securonix incident checkbox is checked.
    if not close_incident:
        if is_incident_closed_on_securonix(filtered_history_entries, close_states_of_securonix):
            demisto.debug(f"Closing the XSOAR incident as its respective Securonix incident {sx_incident_id} is closed.")
            close_comments = extract_closing_comments(filtered_history_entries, close_states_of_securonix)

            new_entries_to_return.append(
                {
                    "Type": EntryType.NOTE,
                    "Contents": {"dbotIncidentClose": True, "closeNotes": close_comments, "closeReason": "Resolved"},
                    "ContentsFormat": EntryFormat.JSON,
                    "Note": True,
                }
            )
        else:
            demisto.debug(f"Not closing the XSOAR incident as its respective Securonix incident {sx_incident_id} is still open.")

    # Update the comments.
    comment_entries = filter_comment_activity_entries(filtered_history_entries)

    for entry in comment_entries:
        comments_text = []
        comments_list = entry.get("comment", {})

        for _comment in comments_list:
            comments_text.append(_comment.get("Comments"))

        comments_text: list[str] = list(filter(None, comments_text))

        if "Mirrored From XSOAR" in ", ".join(comments_text):
            demisto.debug("Skipping the comment as it is mirrored from XSOAR.")
            continue

        new_entries_to_return.append(
            {
                "Type": EntryType.NOTE,
                "Contents": f"[Mirrored From Securonix]\n"
                f"Added By: {entry.get('username')}\n"
                f"Added At: {entry.get('eventTime')} UTC\n"
                f"Comment Content: {', '.join(comments_text)}",
                "ContentsFormat": EntryFormat.TEXT,
                "Note": True,
            }
        )

    # Update the attachments.
    attachment_entries = filter_attachment_activity_entries(filtered_history_entries)
    attachment_names = extract_names_of_attachments_from_entries(attachment_entries)

    if attachment_entries:
        attachment_file_entries = get_incident_attachments(client, {}, incident_id=sx_incident_id)

        # Removing the first entry from the response, as it is CommandResults.
        for xsoar_file_entry in attachment_file_entries:
            if isinstance(xsoar_file_entry, CommandResults):
                continue

            if xsoar_file_entry.get("File", "") in attachment_names:
                new_entries_to_return.append(xsoar_file_entry)

    return GetRemoteDataResponse(remote_incident_data, new_entries_to_return)


def create_xsoar_to_securonix_state_mapping(params: dict[str, Any]) -> CommandResults:
    """Create a mapping of Securonix status and action with XSOAR's states.

    Args:
        params: The configuration parameters got from demisto.params()

    Returns:
        CommandResults: Standard CommandResults object.
    """
    global XSOAR_TO_SECURONIX_STATE_MAPPING

    active_state_action = params.get("active_state_action_mapping", "").strip()
    active_state_status = params.get("active_state_status_mapping", "").strip().lower()

    close_state_action = params.get("closed_state_action_mapping", "").strip()
    close_state_status = params.get("closed_state_status_mapping", "").strip().lower()

    XSOAR_TO_SECURONIX_STATE_MAPPING["ACTIVE"] = {"action": active_state_action, "status": active_state_status}

    XSOAR_TO_SECURONIX_STATE_MAPPING["DONE"] = {"action": close_state_action, "status": close_state_status}

    hr_table = [
        {
            "XSOAR Status": "Active",
            "Securonix Status": XSOAR_TO_SECURONIX_STATE_MAPPING["ACTIVE"]["status"],
            "Securonix Action Name": XSOAR_TO_SECURONIX_STATE_MAPPING["ACTIVE"]["action"],
        },
        {
            "XSOAR Status": "Closed",
            "Securonix Status": XSOAR_TO_SECURONIX_STATE_MAPPING["DONE"]["status"],
            "Securonix Action Name": XSOAR_TO_SECURONIX_STATE_MAPPING["DONE"]["action"],
        },
    ]

    human_readable = tableToMarkdown(
        "State Mapping:", t=hr_table, headers=["XSOAR Status", "Securonix Status", "Securonix Action Name"], removeNull=True
    )

    return CommandResults(
        outputs_prefix="Securonix.StateMapping",
        outputs=XSOAR_TO_SECURONIX_STATE_MAPPING,
        readable_output=human_readable,
        raw_response=XSOAR_TO_SECURONIX_STATE_MAPPING,
    )


def update_remote_system(client: Client, args: dict[str, Any]) -> str:
    """This command pushes local changes to the remote incident.

    Args:
        client (Client): XSOAR Client to use.
        args (Dict[str, Any]):
            args['data']: The data to send to the remote system.
            args['entries']: The entries to send to the remote system.
            args['incident_changed']: Boolean telling us if the local incident indeed changed or not.
            args['remote_incident_id']: The remote incident id.

    Returns:
        str: The remote incident ID.
    """
    parsed_args = UpdateRemoteSystemArgs(args)
    remote_incident_id = parsed_args.remote_incident_id
    xsoar_incident_id = parsed_args.data.get("id", "")
    new_entries = parsed_args.entries

    if new_entries:
        for entry in new_entries:
            demisto.debug(f"Sending the entry with ID: {entry.get('id')} and Type: {entry.get('type')}")

            entry_content = entry.get("contents", "")
            entry_user = entry.get("user", "dbot") or "dbot"

            comment_str = (
                f"[Mirrored From XSOAR] XSOAR Incident ID: {xsoar_incident_id}\nAdded By: {entry_user}\nComment: {entry_content}"
            )
            client.add_comment_to_incident_request(remote_incident_id, comment_str)

    close_incident = parsed_args.data.get("securonixcloseincident", False)

    if not close_incident and parsed_args.incident_changed and parsed_args.inc_status == IncidentStatus.DONE:
        delta_keys = parsed_args.delta.keys()
        if "closingUserId" not in delta_keys and "closeReason" not in delta_keys:
            return remote_incident_id

        close_notes = parsed_args.delta.get("closeNotes", "")
        close_reason = parsed_args.delta.get("closeReason", "")
        close_user_id = parsed_args.delta.get("closingUserId", "")

        closing_comment = (
            f"[Mirrored From XSOAR] XSOAR Incident ID: {xsoar_incident_id}\n"
            f"Closed By: {close_user_id}\nClose Reason: {close_reason}\nClose Notes: {close_notes}"
        )
        demisto.debug(f"Closing Comment: {closing_comment}")

        client.perform_action_on_incident_request(
            incident_id=remote_incident_id, action=XSOAR_TO_SECURONIX_STATE_MAPPING["DONE"]["action"], action_parameters=""
        )
        client.add_comment_to_incident_request(incident_id=remote_incident_id, comment=closing_comment)

    return remote_incident_id


def main():
    """
    PARSE AND VALIDATE INTEGRATION PARAMS
    """
    params = demisto.params()
    remove_nulls_from_dictionary(params)

    host = params.get("host", None)
    tenant = params.get("tenant")
    if not host:
        server_url = tenant
        if not tenant.startswith("http://") and not tenant.startswith("https://"):
            server_url = f"https://{tenant}"  # noqa: E231
        if not tenant.endswith(".securonix.net/Snypr/ws/"):
            server_url += ".securonix.net/Snypr/ws/"
    else:
        host = host.rstrip("/")
        if not host.endswith("/ws"):
            host += "/ws/"
        server_url = host

    username = params.get("username")
    password = params.get("password")
    verify = not params.get("unsecure", False)
    proxy = demisto.params().get("proxy") is True
    # Updating TOTAL_RETRY_COUNT to get user provided value
    global TOTAL_RETRY_COUNT
    TOTAL_RETRY_COUNT = arg_to_number(
        params.get("securonix_retry_count", "0"),  # type: ignore
        arg_name="securonix_retry_count",
    )
    TOTAL_RETRY_COUNT = min(TOTAL_RETRY_COUNT, 5)
    securonix_retry_delay_type = params.get("securonix_retry_delay_type", "Exponential")
    securonix_retry_delay = arg_to_number(params.get("securonix_retry_delay", "30"), arg_name="securonix_retry_delay")
    if securonix_retry_delay <= 30:  # type: ignore
        securonix_retry_delay = 30
    elif securonix_retry_delay >= 300:  # type: ignore
        securonix_retry_delay = 300
    if securonix_retry_delay_type == "Exponential":
        securonix_retry_delay = int(securonix_retry_delay / 2)  # type: ignore
    # Create a state mapping from XSOAR to Securonix.
    create_xsoar_to_securonix_state_mapping(params)

    command = demisto.command()
    LOG(f"Command being called in Securonix is: {command}")

    try:
        client = Client(
            tenant=tenant,
            server_url=server_url,
            username=username,
            password=password,
            verify=verify,
            proxy=proxy,
            securonix_retry_count=TOTAL_RETRY_COUNT,  # type: ignore
            securonix_retry_delay=securonix_retry_delay,  # type: ignore[arg-type]
            securonix_retry_delay_type=securonix_retry_delay_type,
        )
        commands: dict[str, Callable[[Client, dict[str, str]], tuple[str, dict[Any, Any], dict[Any, Any]]]] = {
            "securonix-list-workflows": list_workflows,
            "securonix-get-default-assignee-for-workflow": get_default_assignee_for_workflow,
            "securonix-list-possible-threat-actions": list_possible_threat_actions,
            "securonix-list-policies": list_policies,
            "securonix-list-resource-groups": list_resource_groups,
            "securonix-list-users": list_users,
            "securonix-list-activity-data": list_activity_data,
            "securonix-list-incidents": list_incidents,
            "securonix-get-incident": get_incident,
            "securonix-get-incident-status": get_incident_status,
            "securonix-get-incident-workflow": get_incident_workflow,
            "securonix-get-incident-available-actions": get_incident_available_actions,
            "securonix-perform-action-on-incident": perform_action_on_incident,
            "securonix-add-comment-to-incident": add_comment_to_incident,
            "securonix-create-incident": create_incident,
            "securonix-list-watchlists": list_watchlists,
            "securonix-get-watchlist": get_watchlist,
            "securonix-create-watchlist": create_watchlist,
            "securonix-check-entity-in-watchlist": check_entity_in_watchlist,
            "securonix-add-entity-to-watchlist": add_entity_to_watchlist,
            "securonix-threats-list": list_threats,
            "securonix-incident-activity-history-get": get_incident_activity_history,  # type: ignore[dict-item]
            "securonix-whitelists-get": list_whitelists,  # type: ignore[dict-item]
            "securonix-whitelist-entry-list": get_whitelist_entry,
            "securonix-whitelist-entry-add": add_whitelist_entry,
            "securonix-whitelist-create": create_whitelist,
            "securonix-lookup-table-config-and-data-delete": delete_lookup_table_config_and_data,  # type: ignore
            "securonix-whitelist-entry-delete": delete_whitelist_entry,
            "securonix-lookup-tables-list": list_lookup_tables,  # type: ignore[dict-item]
            "securonix-lookup-table-entry-add": add_entry_to_lookup_table,  # type: ignore[dict-item]
            "securonix-lookup-table-entries-list": list_lookup_table_entries,  # type: ignore[dict-item]
            "securonix-lookup-table-create": create_lookup_table,
            "securonix-lookup-table-entries-delete": delete_lookup_table_entries,
        }
        if command == "fetch-incidents":
            validate_mirroring_parameters(params=params)

            fetch_time = params.get("fetch_time", "1 hour")
            tenant_name = params.get("tenant_name")
            incident_status = params.get("incident_status") if "incident_status" in params else "opened"
            default_severity = params.get("default_severity", "")
            max_fetch_ = arg_to_number(params.get("max_fetch", "200"), arg_name="max_fetch")
            max_fetch = str(min(200, max_fetch_))  # type: ignore
            last_run = json.loads(demisto.getLastRun().get("value", "{}"))
            close_incident = argToBoolean(params.get("close_incident", False))

            if params.get("entity_type_to_fetch") == "Threat":
                incidents = fetch_securonix_threat(client, fetch_time, tenant_name, max_fetch, last_run=last_run)
            else:
                incidents = fetch_securonix_incident(
                    client,
                    fetch_time,
                    incident_status,
                    default_severity,
                    max_fetch,
                    last_run=last_run,
                    close_incident=close_incident,
                )

            demisto.incidents(incidents)
        elif command == "securonix-list-violation-data":
            return_results(
                run_polling_command(
                    client=client,
                    args=demisto.args(),
                    search_function=list_violation_data,
                    command_name="securonix-list-violation-data",
                )
            )
        elif command == "test-module":
            demisto.results(test_module(client))
        elif command == "securonix-incident-attachment-get":
            return_results(get_incident_attachments(client=client, args=demisto.args()))
        elif command == "get-remote-data":
            close_states_of_securonix = params.get("close_states_of_securonix", "").strip().lower()
            close_states_of_securonix = argToList(close_states_of_securonix)

            return_results(get_remote_data_command(client, demisto.args(), close_states_of_securonix))
        elif command == "get-modified-remote-data":
            return_results(get_modified_remote_data_command(client, demisto.args()))
        elif command == "securonix-xsoar-state-mapping-get":
            return_results(create_xsoar_to_securonix_state_mapping(params=params))
        elif command == "update-remote-system":
            return_results(update_remote_system(client, demisto.args()))
        elif command in commands:
            return_outputs(*commands[command](client, demisto.args()))
        else:
            raise NotImplementedError(f'Command "{command}" is not implemented.')

    except Exception as err:
        return_error(str(err))


if __name__ in ["__main__", "builtin", "builtins"]:
    main()