McAfee ESM v2

This integration runs queries and receives alarms from McAfee Enterprise Security Manager (ESM). Supports version 10 and above.

Analytics & SIEM · McAfee ESM

Details

IDMcAfee ESM v2
ProviderTrellix
CategoryAnalytics & SIEM
From Version5.0.0
Docker Imagedemisto/python3:3.12.13.10116658
Supported ModulesAgentix XSIAM

README

Run queries and receive alarms from Intel Security ESM.
This integration was integrated and tested with version 11.3 of McAfee ESM v2.
Previous versions have been declared EOL by the vendor.

Configure McAfee ESM v2 in Cortex

Parameter Description Required
url Base URL (e.g. https://example.com) True
credentials Username True
version Version: (one of 10.0, 10.1, 10.2, 10.3, 11.1, 11.3) True
isFetch Fetch incidents False
incidentType Incident type False
fetchType Fetch Types: cases, alarms, both (relevant only for fetch incident mode) False
startingFetchID Start fetch after ID: (relevant only for fetch incident mode) False
fetchLimitCases Fetch cases limit False
fetchTime First fetch timestamp (<number> <time unit>, e.g., 12 hours, 7 days, 3 months, 1 year) False
fetchLimitAlarms Fetch alarms limit False
timezone McAfee ESM Timezone in hours (e.g if ESM timezone is +0300 => then insert 3) False
insecure Trust any certificate (not secure) False
proxy Use system proxy settings False

Required Permissions

Component Permission
Alarms Alarm Management and View Data
Cases Incident Management Administrator and Incident Management User
Watchlists Watchlists

Commands

You can execute these commands from the 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.

esm-fetch-fields


Gets all fields that can be used in query filters, including type information for each field

Base Command

esm-fetch-fields

Input

There are no input arguments for this command.

Context Output

There is no context output for this command.

Command Example


#### Human Readable Output

>### Fields
>
>|name|types|
>|---|---|
>| AppID | STRING |
>| CommandID | STRING |
>| DomainID | STRING |
>| HostID | STRING |
>| ObjectID | STRING |
>| UserIDDst | STRING |
>| UserIDSrc | STRING |
>| URL | SSTRING |
>| Database_Name | STRING |
>| Message_Text | SSTRING |
>| Response_Time | UINT32 |
>| Application_Protocol | STRING |
>| Object_Type | STRING |
>| Filename | SSTRING |
>| From | SSTRING |
>| To | SSTRING |
>| Cc | SSTRING |
>| Bcc | SSTRING |
>| Subject | SSTRING |
>| Method | STRING |
>| User_Agent | SSTRING |
>| Cookie | SSTRING |
>| Referer | SSTRING |
>| File_Operation | STRING |
>| File_Operation_Succeeded | STRING |
>| Destination_Filename | SSTRING |
>| User_Nickname | STRING |
>| Contact_Name | STRING |
>| Contact_Nickname | STRING |
>| Client_Version | SSTRING |
>| Job_Name | SSTRING |
>| Language | SSTRING |
>| SWF_URL | SSTRING |
>| TC_URL | SSTRING |
>| RTMP_Application | SSTRING |
>| Version | SSTRING |
>| Local_User_Name | SSTRING |
>| NAT_Details | UINT16,IPV4 |
>| Network_Layer | SIGID |
>| Transport_Layer | SIGID |
>| Session_Layer | SIGID |
>| Application_Layer | SIGID |
>| HTTP_Layer | SIGID |
>| HTTP_Req_URL | SSTRING |
>| HTTP_Req_Cookie | SSTRING |
>| HTTP_Req_Referer | SSTRING |
>| HTTP_Req_Host | SSTRING |
>| HTTP_Req_Method | SSTRING |
>| HTTP_User_Agent | SSTRING |
>| DNS_Name | SSTRING |
>| DNS_Type | STRING |
>| DNS_Class | STRING |
>| Query_Response | STRING |
>| Authoritative_Answer | STRING |
>| SNMP_Operation | STRING |
>| SNMP_Item_Type | STRING |
>| SNMP_Version | STRING |
>| SNMP_Error_Code | STRING |
>| NTP_Client_Mode | STRING |
>| NTP_Server_Mode | STRING |
>| NTP_Request | STRING |
>| NTP_Opcode | STRING |
>| SNMP_Item | SSTRING |
>| Interface | STRING |
>| Direction | STRING |
>| Sensor_Name | STRING |
>| Sensor_UUID | SSTRING |
>| Sensor_Type | STRING |
>| Signature_Name | SSTRING |
>| Threat_Name | SSTRING |
>| Destination_Hostname | SSTRING |
>| Category | SSTRING |
>| Process_Name | SSTRING |
>| Grid_Master_IP | IP |
>| Response_Code | STRING |
>| Device_Port | UINT64 |
>| Device_IP | IP |
>| PID | UINT64 |
>| Target_Context | SSTRING |
>| Source_Context | SSTRING |
>| Target_Class | SSTRING |
>| Policy_Name | SSTRING |
>| Destination_Zone | SSTRING |
>| Source_Zone | SSTRING |
>| Queue_ID | STRLIT |
>| Delivery_ID | SSTRING |
>| Recipient_ID | SSTRING |
>| Spam_Score | FLOAT |
>| Mail_ID | SSTRING |
>| To_Address | SSTRING |
>| From_Address | SSTRING |
>| Message_ID | SSTRING |
>| Request_Type | SSTRING |
>| SQL_Statement | SSTRING |
>| External_EventID | UINT64 |
>| Event_Class | SSTRING |
>| Description | SSTRING |
>| File_Hash | GUID |
>| Mainframe_Job_Name | SSTRING |
>| External_SubEventID | UINT64 |
>| Destination_UserID | SSTRING |
>| Source_UserID | SSTRING |
>| Volume_ID | SSTRING |
>| Step_Name | SSTRING |
>| Step_Count | SSTRING |
>| LPAR_DB2_Subsystem | SSTRING |
>| Logical_Unit_Name | SSTRING |
>| Job_Type | SSTRING |
>| FTP_Command | SSTRING |
>| File_Type | SSTRING |
>| DB2_Plan_Name | SSTRING |
>| Catalog_Name | SSTRING |
>| Access_Resource | SSTRING |
>| Table_Name | SSTRING |
>| External_DB2_Server | SSTRING |
>| External_Application | SSTRING |
>| Creator_Name | SSTRING |
>| Return_Code | STRING |
>| Database_ID | SSTRING |
>| Incoming_ID | SSTRING |
>| Handle_ID | UINT64 |
>| Destination_Network | SSTRING |
>| Source_Network | SSTRING |
>| Malware_Insp_Result | SSTRING |
>| Malware_Insp_Action | SSTRING |
>| External_Hostname | SSTRING |
>| Privileged_User | SSTRING |
>| Facility | SSTRING |
>| Area | SSTRING |
>| Instance_GUID | GUID |
>| Logon_Type | SSTRING |
>| Operating_System | SSTRING |
>| File_Path | SSTRING |
>| Agent_GUID | GUID |
>| Reputation | UINT64 |
>| URL_Category | SSTRING |
>| Session_Status | SSTRING |
>| Destination_Logon_ID | SSTRING |
>| Source_Logon_ID | SSTRING |
>| UUID | GUID |
>| External_SessionID | SSTRING |
>| Management_Server | SSTRING |
>| Detection_Method | SSTRING |
>| Target_Process_Name | SSTRING |
>| Analyzer_DAT_Version | FLOAT |
>| Forwarding_Status | SSTRING |
>| Reason | SSTRING |
>| Threat_Handled | SSTRING |
>| Threat_Category | SSTRING |
>| Device_Action | SSTRING |
>| Database_GUID | GUID |
>| SQL_Command | SSTRING |
>| Destination_Directory | SSTRING |
>| Directory | SSTRING |
>| Mailbox | SSTRING |
>| Handheld_ID | UINT64 |
>| Policy_ID | UINT64 |
>| Server_ID | UINT64 |
>| Registry_Value | SSTRING |
>| Registry_Key | SSTRING |
>| Caller_Process | SSTRING |
>| DAT_Version | FLOAT |
>| Interface_Dest | SSTRING |
>| Datacenter_Name | SSTRING |
>| Datacenter_ID | SSTRING |
>| Virtual_Machine_ID | SSTRING |
>| Virtual_Machine_Name | SSTRING |
>| PCAP_Name | SSTRING |
>| Search_Query | SSTRING |
>| Service_Name | SSTRING |
>| External_Device_Name | SSTRING |
>| External_Device_ID | SSTRING |
>| External_Device_Type | SSTRING |
>| Organizational_Unit | SSTRING |
>| Privileges | SSTRING |
>| Reputation_Name | SSTRING |
>| Vulnerability_References | SSTRING |
>| Web_Domain | SSTRING |
>| Sub_Status | SSTRING |
>| Status | SSTRING |
>| Access_Privileges | SSTRING |
>| Rule_Name | SSTRING |
>| App_Layer_Protocol | SSTRING |
>| Group_Name | SSTRING |
>| Authentication_Type | SSTRING |
>| New_Value | SSTRING |
>| Old_Value | SSTRING |
>| Security_ID | SSTRING |
>| SHA1 | SSTRING |
>| Reputation_Score | FLOAT |
>| Parent_File_Hash | GUID |
>| File_ID | SSTRING |
>| Engine_List | SSTRING |
>| Device_URL | SSTRING |
>| Attacker_IP | IPV4 |
>| Victim_IP | IPV4 |
>| Incident_ID | INT64 |
>| Attribute_Type | SSTRING |
>| Access_Mask | SSTRING |
>| Object_GUID | GUID |
>| VPN_Feature_Name | SSTRING |
>| Reputation_Server_IP | IP |
>| DNS_Server_IP | IP |
>| Hash_Type | SSTRING |
>| Hash | SSTRING |
>| Subcategory | SSTRING |
>| Wireless_SSID | SSTRING |
>| Share_Name | SSTRING |
>| CnC_Host | SSTRING |
>| Device_Confidence | UINT64 |
>| SHA256 | SSTRING |
>| AppID | STRING |
>| CommandID | STRING |
>| DSIDSigID | SIGID |
>| Action | UINT8 |
>| ASNGeoDst | UINT64 |
>| DSID | UINT64 |
>| ZoneDst | UINT16 |
>| SigID | SIGID |
>| GUIDSrc | GUID |
>| NDDevIDSrc | UINT16 |
>| ID | UINT64 |
>| Protocol | UINT8 |
>| NormID | UINT32 |
>| ZoneSrc | UINT16 |
>| FirstTime | UINT32 |
>| SrcPort | UINT16 |
>| AvgSeverity | FLOAT |
>| DstPort | UINT16 |
>| SrcIP | IP |
>| GUIDDst | GUID |
>| DstIP | IP |
>| NDDevIDDst | UINT16 |
>| SrcMac | MAC_ADDRESS |
>| SessionID | UINT64 |
>| ASNGeoSrc | UINT64 |
>| DstMac | MAC_ADDRESS |
>| LastTime | UINT32 |

### esm-search

***
Perform a query against Mcafee ESM SIEM

#### Base Command

`esm-search`

#### Input

| **Argument Name** | **Description** | **Required** |
| --- | --- | --- |
| timeRange | The time period for the search. Can be LAST_3_DAYS, LAST_2_DAYS, LAST_24_HOURS, PREVIOUS_DAY, CURRENT_DAY, LAST_HOUR, LAST_30_MINUTES, LAST_10_MINUTES, LAST_MINUTE, CUSTOM, PREVIOUS_YEAR, CURRENT_YEAR, PREVIOUS_QUARTER, CURRENT_QUARTER, PREVIOUS_MONTH, CURRENT_MONTH, PREVIOUS_WEEK, or CURRENT_WEEK. | Optional |
| filters | Filter on the query results, should be a JSON string, of the format EsmFilter (read more on that here - https://&lt;esm-ip&gt;:&lt;esm-port&gt;/rs/esm/help/types/EsmFilter) | Required |
| queryType | Type of query to run. Can be "EVENT", "FLOW", or "ASSETS". Default is "EVENT". | Optional |
| timeOut | Maximum time to wait before timeout (in minutes). Default is 30. | Optional |
| customStart | If the timeRange argument is set to CUSTOM, the start time for the time range. For example: 2017-06-01T12:48:16.734Z | Optional |
| customEnd | If the timeRange argument is set to CUSTOM, the end time for the time range. For example: 2017-06-01T12:48:16.734Z | Optional |
| fields | The fields that will be selected when this query is executed. | Optional |
| limit | Query results can be limited to a maximum row count. | Optional |

#### Context Output

There is no context output for this command.

#### Command Example

```!esm-search timeRange="CURRENT_YEAR" filters="[{\"type\":\"EsmFieldFilter\",\"field\":{\"name\":\"SrcIP\"},\"operator\":\"IN\"}]" limit="3"```

#### Context Example

{
“McAfeeESM”: {
“results”: [
{
“ActionName”: “success”,
“AlertDstIP”: “192.168.1.111”,
“AlertDstPort”: “0”,
“AlertIPSIDAlertID”: “144115188075855872|779674”,
“AlertLastTime”: “2020-01-01T05:48:20Z”,
“AlertProtocol”: “n/a”,
“AlertSrcIP”: “22.22.22.22”,
“AlertSrcPort”: “0”
},
{
“ActionName”: “success”,
“AlertDstIP”: “192.168.1.111”,
“AlertDstPort”: “0”,
“AlertIPSIDAlertID”: “144115188075855872|779675”,
“AlertLastTime”: “2020-01-01T05:48:22Z”,
“AlertProtocol”: “n/a”,
“AlertSrcIP”: “22.22.22.22”,
“AlertSrcPort”: “0”
},
{
“ActionName”: “success”,
“AlertDstIP”: “192.168.1.111”,
“AlertDstPort”: “0”,
“AlertIPSIDAlertID”: “144115188075855872|779676”,
“AlertLastTime”: “2020-01-01T10:51:57Z”,
“AlertProtocol”: “n/a”,
“AlertSrcIP”: “33.33.33.33”,
“AlertSrcPort”: “0”
}
]
}
}


#### Human Readable Output

>Search results
>
>|Alert.IPSIDAlertID|Alert.SrcIP|Alert.SrcPort|Alert.DstIP|Alert.DstPort|Alert.Protocol|Alert.LastTime|Action.Name|
>|--|--|--|--|--|--|--|--|
>| 144115188075855872\|779674|22.22.22.22|0|192.168.1.111|0|n/a|2020-01-01T05:48:20Z|success |
>| 144115188075855872\|779675|22.22.22.22|0|192.168.1.111|0|n/a|2020-01-01T05:48:22Z|success |
>| 144115188075855872\|779676|33.33.33.33|0|192.168.1.111|0|n/a|2020-01-01T10:51:57Z|success |

### esm-fetch-alarms

***
Retrieves a list of triggered alarms.

#### Base Command

`esm-fetch-alarms`

#### Input

| **Argument Name** | **Description** | **Required** |
| --- | --- | --- |
| timeRange | The time period for the search. Can be LAST_3_DAYS, LAST_2_DAYS, LAST_24_HOURS, PREVIOUS_DAY, CURRENT_DAY, LAST_HOUR, LAST_30_MINUTES, LAST_10_MINUTES, LAST_MINUTE, CUSTOM, PREVIOUS_YEAR, CURRENT_YEAR, PREVIOUS_QUARTER, CURRENT_QUARTER, PREVIOUS_MONTH, CURRENT_MONTH, PREVIOUS_WEEK, or CURRENT_WEEK. | Optional |
| customStart | If the timeRange argument is set to CUSTOM, the start time for the time range. For example: 2017-06-01T12:48:16.734Z | Optional |
| customEnd | If the timeRange argument is set to CUSTOM, the end time for the time range. For example: 2017-06-01T12:48:16.734Z | Optional |
| assignedUser | User assigned to handle the triggered alarm. Use the 'ME' option to use the instance user, or use [format EsmUser](https://&lt;esm-ip&gt;:&lt;esm-port&gt;/rs/esm/help/types/EsmUser).  | Optional |

#### Context Output

| **Path** | **Type** | **Description** |
| --- | --- | --- |
| McAfeeESM.Alarm.ID | number | Alarm ID. |
| McAfeeESM.Alarm.summary | string | Alarm summary. |
| McAfeeESM.Alarm.assignee | string | Alarm assignee. |
| McAfeeESM.Alarm.severity | number | Alarm severity. |
| McAfeeESM.Alarm.triggeredDate | date | Alarm triggered date. |
| McAfeeESM.Alarm.acknowledgedDate | date | Alarm acknowledged date. |
| McAfeeESM.Alarm.acknowledgedUsername | string | Alarm acknowledged username. |
| McAfeeESM.Alarm.alarmName | string | Alarm name. |
| McAfeeESM.Alarm.conditionType | number | Alarm condition type. |

#### Command Example

```!esm-fetch-alarms timeRange=CURRENT_MONTH```

#### Context Example

{
“McAfeeESM”: {
“Alarm”: [
{
“ID”: 42710,
“acknowledgedDate”: “”,
“acknowledgedUsername”: “”,
“alarmName”: “Alarm Test”,
“assignee”: “ANALYST”,
“conditionType”: 22,
“severity”: 50,
“summary”: “Event rate exceeded 10 by 17”,
“triggeredDate”: “2020-06-24T13:05:43Z”
},
{
“ID”: 42709,
“acknowledgedDate”: “”,
“acknowledgedUsername”: “”,
“alarmName”: “Alarm Test”,
“assignee”: “ANALYST”,
“conditionType”: 22,
“severity”: 50,
“summary”: “Event rate exceeded 10 by 1”,
“triggeredDate”: “2020-06-24T12:53:12Z”
},
{
“ID”: 42708,
“acknowledgedDate”: “”,
“acknowledgedUsername”: “”,
“alarmName”: “Alarm Test”,
“assignee”: “ANALYST”,
“conditionType”: 22,
“severity”: 50,
“summary”: “Event rate exceeded 10 by 2”,
“triggeredDate”: “2020-06-24T11:32:08Z”
}
]
}
}


#### Human Readable Output

>### Alarms
>
>|id|acknowledgedDate|acknowledgedUsername|alarmName|assignee|conditionType|severity|summary|triggeredDate|
>|---|---|---|---|---|---|---|---|---|
>| 42710 |  |  | Alarm Test | ANALYST | 22 | 50 | Event rate exceeded 10 by 17 | 2020-06-24T13:05:43Z |
>| 42709 |  |  | Alarm Test | ANALYST | 22 | 50 | Event rate exceeded 10 by 1 | 2020-06-24T12:53:12Z |
>| 42708 |  |  | Alarm Test | ANALYST | 22 | 50 | Event rate exceeded 10 by 2 | 2020-06-24T11:32:08Z |

### esm-get-case-list

***
Gets a list of cases from McAfee ESM.

#### Base Command

`esm-get-case-list`

#### Input

| **Argument Name** | **Description** | **Required** |
| --- | --- | --- |
| since | Filters for cases that were opened before this date. In the format "&lt;number&gt;&lt;timeunit&gt;", for example: 1 day,30 minutes,2 weeks,6 months,1 year | Optional |

#### Context Output

| **Path** | **Type** | **Description** |
| --- | --- | --- |
| McAfeeESM.Case.ID | number | Case ID. |
| McAfeeESM.Case.Summary | string | The summary of the case. |
| McAfeeESM.Case.Status | string | The status of the case. |
| McAfeeESM.Case.OpenTime | date | The date and time when the case was opened. |
| McAfeeESM.Case.Severity | number | The severity of the case. |

#### Command Example

```!esm-get-case-list since="1 month"```

#### Context Example

{
“McAfeeESM”: {
“Case”: [
{
“ID”: 33262,
“OpenTime”: “2020-06-23T06:38:03Z”,
“Severity”: 50,
“Status”: “Open”,
“Summary”: “Signature ID ‘Failed User Logon’ (306-31) match found”
},
{
“ID”: 33261,
“OpenTime”: “2020-06-22T12:04:09Z”,
“Severity”: 50,
“Status”: “Open”,
“Summary”: “Signature ID ‘Failed User Logon’ (306-31) match found”
},
{
“ID”: 33264,
“OpenTime”: “2020-06-23T12:13:08Z”,
“Severity”: 50,
“Status”: “Open”,
“Summary”: “Signature ID ‘Failed User Logon’ (306-31) match found”
}
]
}
}


#### Human Readable Output

>### cases since 1 month
>
>|ID|OpenTime|Severity|Status|Summary|
>|---|---|---|---|---|
>| 33262 | 2020-06-23T06:38:03Z | 50 | Open | Signature ID 'Failed User Logon' (306-31) match found |
>| 33261 | 2020-06-22T12:04:09Z | 50 | Open | Signature ID 'Failed User Logon' (306-31) match found |
>| 33264 | 2020-06-23T12:13:08Z | 50 | Open | Signature ID 'Failed User Logon' (306-31) match found |

### esm-add-case

***
Adds a case to the system.

#### Base Command

`esm-add-case`

#### Input

| **Argument Name** | **Description** | **Required** |
| --- | --- | --- |
| summary | The name of the case. | Required |
| status | The status of the case. Run the esm-get-case-statuses command to view all statuses. | Optional |
| assignee | User assigned to the case. | Optional |
| severity | The severity of the case (1 - 100). | Optional |
| organization | The organization assigned to the case. Run the esm-get-organization-list command to view all organizations. | Optional |

#### Context Output

| **Path** | **Type** | **Description** |
| --- | --- | --- |
| McAfeeESM.Case.ID | number | The ID of the case. |
| McAfeeESM.Case.Summary | string | The summary of the case. |
| McAfeeESM.Case.Status | string | The status of the case. |
| McAfeeESM.Case.OpenTime | date | The open time of the case. |
| McAfeeESM.Case.Severity | number | The severity of the case. |
| McAfeeESM.Case.Assignee | string | The assignee of the case. |
| McAfeeESM.Case.Organization | string | The organization of the case. |
| McAfeeESM.Case.EventList | Unknown | List of the case's events. |
| McAfeeESM.Case.Notes | Unknown | List of the case's notes. |

#### Command Example

```!esm-add-case summary="McAfee ESM v2 add case"```

#### Context Example

{
“McAfeeESM”: {
“Case”: {
“Assignee”: “ANALYST”,
“ID”: 33272,
“OpenTime”: “2020-06-24T13:10:01Z”,
“Organization”: “None”,
“Severity”: 1,
“Status”: “Open”,
“Summary”: “McAfee ESM v2 add case”
}
}
}


#### Human Readable Output

>### Case
>
>|Assignee|ID|OpenTime|Organization|Severity|Status|Summary|
>|---|---|---|---|---|---|---|
>| ANALYST | 33272 | 2020-06-24T13:10:01Z | None | 1 | Open | McAfee ESM v2 add case |

### esm-edit-case

***
Edit the details of an existing case.

#### Base Command

`esm-edit-case`

#### Input

| **Argument Name** | **Description** | **Required** |
| --- | --- | --- |
| id | The ID of the case. | Required |
| summary | The name of the case. | Optional |
| severity | The new severity of the case (1 - 100). | Optional |
| assignee | User assigned to the case. | Optional |
| status | The new status of the case. Run the esm-get-case-statuses command to view all statuses. | Optional |
| organization | The organization assigned to the case. Run the esm-get-organization-list command to view all organizations. | Optional |

#### Context Output

| **Path** | **Type** | **Description** |
| --- | --- | --- |
| McAfeeESM.Case.ID | number | The ID of the case. |
| McAfeeESM.Case.Summary | string | The summary of the case. |
| McAfeeESM.Case.Status | string | The status of the case. |
| McAfeeESM.Case.OpenTime | date | The open time of the case. |
| McAfeeESM.Case.Severity | number | The severity of the case. |
| McAfeeESM.Case.Assignee | string | The assignee of the case. |
| McAfeeESM.Case.Organization | string | The organization of the case. |
| McAfeeESM.Case.EventList | Unknown | List of the case's events. |
| McAfeeESM.Case.Notes | Unknown | List of the case's notes. |

#### Command Example

```!esm-edit-case id="33266" summary="McAfee ESM v2 edit case"```

#### Context Example

{
“McAfeeESM”: {
“Case”: {
“Assignee”: “ANALYST”,
“ID”: 33266,
“OpenTime”: “2020-06-24T10:54:21Z”,
“Organization”: “None”,
“Severity”: 1,
“Status”: “Open”,
“Summary”: “McAfee ESM v2 edit case”
}
}
}


#### Human Readable Output

>### Case
>
>|Assignee|ID|OpenTime|Organization|Severity|Status|Summary|
>|---|---|---|---|---|---|---|
>| ANALYST | 33266 | 2020-06-24T10:54:21Z | None | 1 | Open | McAfee ESM v2 edit case |

### esm-get-case-statuses

***
Gets a list of valid case statuses from the system.

#### Base Command

`esm-get-case-statuses`

#### Input

There are no input arguments for this command.

#### Context Output

There is no context output for this command.

#### Command Example

```!esm-get-case-statuses```

#### Human Readable Output

>### case statuses
>
>|id|name|default|showInCasePane|
>|---|---|---|---|
>| 2 | Closed | false | false |
>| 11830 | McAfee_ESM_v2_add_case | false | false |
>| 1 | Open | true | true |
>| 11725 | Research_1563355610148 | false | true |
>| 11825 | TestMcAfee_ESM_v2 | false | false |
>| 11758 | bbbb | false | false |
>| 11776 | test | false | true |
>| 11777 | test1 | false | false |
>| 11268 | test2 | false | true |
>| 11267 | test3 | false | true |
>| 11890 | test_delete_case | false | false |
>| 11889 | test_edit_case | false | false |

### esm-edit-case-status

***
Edits the status of a case.

#### Base Command

`esm-edit-case-status`

#### Input

| **Argument Name** | **Description** | **Required** |
| --- | --- | --- |
| original_name | The name of the case status to edit. | Required |
| new_name | The new name for the case status. | Required |
| show_in_case_pane | Whether the status will display in the case pane. Can be "True" or "False". Default is "True". | Optional |

#### Context Output

There is no context output for this command.

#### Command Example

```!esm-edit-case-status original_name=test_edit_case new_name=edited_case```

#### Human Readable Output
>
>Edited case status with ID: 11889

### esm-get-case-detail

***
Gets the details of an existing case.

#### Base Command

`esm-get-case-detail`

#### Input

| **Argument Name** | **Description** | **Required** |
| --- | --- | --- |
| id | The ID of the case. | Required |

#### Context Output

| **Path** | **Type** | **Description** |
| --- | --- | --- |
| McAfeeESM.Case.ID | number | The ID of the case. |
| McAfeeESM.Case.Summary | string | The summary of the case. |
| McAfeeESM.Case.Status | string | The status of the case. |
| McAfeeESM.Case.OpenTime | date | The open time of the case. |
| McAfeeESM.Case.Severity | number | The severity of the case. |
| McAfeeESM.Case.Assignee | string | The assignee of the case. |
| McAfeeESM.Case.Organization | string | The organization of the case. |
| McAfeeESM.Case.EventList | Unknown | List of the case's events. |
| McAfeeESM.Case.Notes | Unknown | List of the case's notes. |

#### Command Example

```!esm-get-case-detail id="33264"```

#### Context Example

{
“McAfeeESM”: {
“Case”: {
“Assignee”: “ANALYST”,
“ID”: 33264,
“OpenTime”: “2020-06-23T12:13:08Z”,
“Organization”: “None”,
“Severity”: 50,
“Status”: “Open”,
“Summary”: “Signature ID ‘Failed User Logon’ (306-31) match found”
}
}
}


#### Human Readable Output

>### Case
>
>|Assignee|ID|OpenTime|Organization|Severity|Status|Summary|
>|---|---|---|---|---|---|---|
>| ANALYST | 33264 | 2020-06-23T12:13:08Z | None | 50 | Open | Signature ID 'Failed User Logon' (306-31) match found |

### esm-get-case-event-list

***
Gets case event details.

#### Base Command

`esm-get-case-event-list`

#### Input

| **Argument Name** | **Description** | **Required** |
| --- | --- | --- |
| ids | Comma-separated list of event IDs. | Required |

#### Context Output

| **Path** | **Type** | **Description** |
| --- | --- | --- |
| McAfeeESM.CaseEvent.ID | string | The ID of the event. |
| McAfeeESM.CaseEvent.LastTime | date | The time the event was last updated. |
| McAfeeESM.CaseEvent.Message | string | The message of the event. |

#### Command Example

```!esm-get-case-event-list ids="42687"```

### esm-add-case-status

***
Adds a status to the specified case.

#### Base Command

`esm-add-case-status`

#### Input

| **Argument Name** | **Description** | **Required** |
| --- | --- | --- |
| name | The name of the case status. | Required |
| show_in_case_pane | Whether the status will display in the case pane. Can be "True" or "False". Default is "True". | Optional |

#### Context Output

There is no context output for this command.

#### Command Example

```!esm-add-case-status name=test_add_case```

#### Human Readable Output

>Added case status : test_add_case

### esm-delete-case-status

***
Deletes the status of a case.

#### Base Command

`esm-delete-case-status`

#### Input

| **Argument Name** | **Description** | **Required** |
| --- | --- | --- |
| name | The name of the case status to delete. | Required |

#### Context Output

There is no context output for this command.

#### Command Example

```!esm-delete-case-status name=test_delete_case```

#### Human Readable Output

>Deleted case status with ID: 11890

### esm-get-organization-list

***
Gets a case organization.

#### Base Command

`esm-get-organization-list`

#### Input

There are no input arguments for this command.

#### Context Output

| **Path** | **Type** | **Description** |
| --- | --- | --- |
| McAfeeESM.Organization.ID | number | Organization ID. |
| McAfeeESM.Organization.Name | string | Organization name. |

#### Command Example

```!esm-get-organization-list```

#### Context Example

{
“McAfeeESM”: {
“Organization”: [
{
“ID”: 2,
“Name”: “ABC”
},
{
“ID”: 1,
“Name”: “Org”
}
]
}
}


#### Human Readable Output

>### Organizations
>
>|id|name|
>|---|---|
>| 2 | ABC |
>| 1 | Org |

### esm-get-user-list

***
Gets a list of all users.

#### Base Command

`esm-get-user-list`

#### Input

There are no input arguments for this command.

#### Context Output

| **Path** | **Type** | **Description** |
| --- | --- | --- |
| McAfeeESM.User.ID | number | The ID of the user. |
| McAfeeESM.User.Name | string | The ESM user name. |
| McAfeeESM.User.Email | string | The email address of the user. |
| McAfeeESM.User.SMS | string | The SMS details of the user. |
| McAfeeESM.User.IsMaster | boolean | Whether the user is a master user. |
| McAfeeESM.User.IsAdmin | boolean | Whether the user is an admin. |

#### Command Example

```!esm-get-user-list```

#### Context Example

{
“McAfeeESM”: {
“User”: [
{
“Email”: “”,
“Groups”: “[]”,
“ID”: 6,
“IsAdmin”: false,
“IsMaster”: false,
“Name”: “abcd”,
“SMS”: “”
},
{
“Email”: “”,
“Groups”: “[1, 2]”,
“ID”: 7,
“IsAdmin”: true,
“IsMaster”: true,
“Name”: “gavrieltest”,
“SMS”: “”
},
{
“Email”: “”,
“Groups”: “[2]”,
“ID”: 1,
“IsAdmin”: false,
“IsMaster”: true,
“Name”: “ANALYST”,
“SMS”: “”
}
]
}
}


#### Human Readable Output

>### User list
>
>|ID|Name|Email|Groups|IsMaster|IsAdmin|SMS|
>|---|---|---|---|---|---|---|
>| 6 | abcd |  | [] | false | false |  |
>| 7 | gavrieltest |  | [1, 2] | true | true |  |
>| 1 | ANALYST |  | [2] | true | false |  |

### esm-acknowledge-alarms

***
Marks triggered alarms as acknowledged.

#### Base Command

`esm-acknowledge-alarms`

#### Input

| **Argument Name** | **Description** | **Required** |
| --- | --- | --- |
| alarmIds | Comma-separated list of triggered alarm IDs to be marked as acknowledged. | Required |

#### Context Output

There is no context output for this command.

#### Command Example

```!esm-acknowledge-alarms alarmIds="42710"```

#### Human Readable Output

>Alarms has been Acknowledged.

### esm-unacknowledge-alarms

***
Marks triggered alarms as unacknowledged.

#### Base Command

`esm-unacknowledge-alarms`

#### Input

| **Argument Name** | **Description** | **Required** |
| --- | --- | --- |
| alarmIds | Comma-separated list of triggered alarm IDs to be marked as unacknowledged. | Required |

#### Context Output

There is no context output for this command.

#### Command Example

```!esm-unacknowledge-alarms alarmIds="42687"```

#### Human Readable Output

>Alarms has been Unacknowledged.

### esm-delete-alarms

***
Deletes triggered alarms.

#### Base Command

`esm-delete-alarms`

#### Input

| **Argument Name** | **Description** | **Required** |
| --- | --- | --- |
| alarmIds | Comma-separated list of triggered alarm IDs to delete. | Required |

#### Context Output

There is no context output for this command.

#### Command Example

```!esm-delete-alarms alarmIds="42709"```

#### Human Readable Output

>Alarms has been Deleted.

### esm-get-alarm-event-details

***
Gets the details for the triggered alarm.

#### Base Command

`esm-get-alarm-event-details`

#### Input

| **Argument Name** | **Description** | **Required** |
| --- | --- | --- |
| eventId | The event for which to get the details. Run the esm-list-alarm-events command to get the ID. | Required |

#### Context Output

| **Path** | **Type** | **Description** |
| --- | --- | --- |
| McAfeeESM.AlarmEvent.ID | string | Event ID. |
| McAfeeESM.AlarmEvent.SubType | string | Event type. |
| McAfeeESM.AlarmEvent.Severity | number | Event severity. |
| McAfeeESM.AlarmEvent.Message | string | Event message. |
| McAfeeESM.AlarmEvent.LastTime | date | Event time. |
| McAfeeESM.AlarmEvent.SrcIP | string | Source IP of the event. |
| McAfeeESM.AlarmEvent.DstIP | string | Destination IP of the event. |
| McAfeeESM.AlarmEvent.Cases | Unknown | A list of cases related to the event. |
| McAfeeESM.AlarmEvent.Cases.ID | string | Case ID. |
| McAfeeESM.AlarmEvent.Cases.OpenTime | date | Case creation time. |
| McAfeeESM.AlarmEvent.Cases.Severity | number | Case severity. |
| McAfeeESM.AlarmEvent.Cases.Status | string | Case status. |
| McAfeeESM.AlarmEvent.Cases.Summary | string | Case summary. |
| McAfeeESM.AlarmEvent.DstMac | string | Destination MAC address of the event. |
| McAfeeESM.AlarmEvent.SrcMac | string | Source MAC address of the event. |
| McAfeeESM.AlarmEvent.DstPort | string | Destination port of the event. |
| McAfeeESM.AlarmEvent.SrcPort | string | Source port of the event. |
| McAfeeESM.AlarmEvent.FirstTime | date | The first time for the event. |
| McAfeeESM.AlarmEvent.NormalizedDescription | string | Normalized description of the event. |

#### Command Example

```!esm-get-alarm-event-details eventId=144115188075855872|802641```

#### Context Example

{
“McAfeeESM”: {
“AlarmEvent”: {
“Case”: [
{
“ID”: 33260,
“OpenTime”: “2020-06-22T06:16:24Z”,
“Severity”: 50,
“Status”: “Open”,
“Summary”: “Signature ID ‘Failed User Logon’ (306-31) match found”
}
],
“DstIP”: “192.168.1.111”,
“DstMac”: “00:00:00:00:00:00”,
“DstPort”: “0”,
“FirstTime”: “2020-06-22T06:16:05Z”,
“ID”: 802641,
“LastTime”: “2020-06-22T06:16:05Z”,
“Message”: “Failed User Logon”,
“NormalizedDescription”: “The Login category indicates events related to logging in to hosts or services. Belongs to Authentication: The authentication category indicates events relating to system access.”,
“Severity”: 25,
“SrcIP”: “44.44.44.44”,
“SrcMac”: “00:00:00:00:00:00”,
“SrcPort”: “0”,
“SubType”: “failure”
}
}
}


#### Human Readable Output

>### Alarm events
>
>|Case|DstIP|DstMac|DstPort|FirstTime|ID|LastTime|Message|NormalizedDescription|Severity|SrcIP|SrcMac|SrcPort|SubType|
>|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
>| {'ID': 33260, 'OpenTime': '2020-06-22T06:16:24Z', 'Severity': 50, 'Status': 'Open', 'Summary': "Signature ID 'Failed User Logon' (306-31) match found"} | 192.168.1.111 | 00:00:00:00:00:00 | 0 | 2020-06-22T06:16:05Z | 802641 | 2020-06-22T06:16:05Z | Failed User Logon | The Login category indicates events related to logging in to hosts or services.  Belongs to Authentication: The authentication category indicates events relating to system access. | 25 | 44.44.44.44 | 00:00:00:00:00:00 | 0 | failure |

### esm-list-alarm-events

***
Gets a list of events related to the alarm.

#### Base Command

`esm-list-alarm-events`

#### Input

| **Argument Name** | **Description** | **Required** |
| --- | --- | --- |
| alarmId | The alarm for which to get the details. Run the esm-fetch-alarms command to get the ID. | Required |

#### Context Output

| **Path** | **Type** | **Description** |
| --- | --- | --- |
| McAfeeESM.AlarmEvent.ID | string | Event ID. |
| McAfeeESM.AlarmEvent.SubType | string | Event type. |
| McAfeeESM.AlarmEvent.Severity | number | Event severity. |
| McAfeeESM.AlarmEvent.Message | string | Event message. |
| McAfeeESM.AlarmEvent.LastTime | date | Event time. |
| McAfeeESM.AlarmEvent.SrcIP | string | Source IP of the event. |
| McAfeeESM.AlarmEvent.DstIP | string | Destination IP of the event. |
| McAfeeESM.AlarmEvent.Cases | Unknown | A list of cases related to the event. |
| McAfeeESM.AlarmEvent.Cases.ID | string | Case ID. |
| McAfeeESM.AlarmEvent.Cases.OpenTime | date | Case creation time. |
| McAfeeESM.AlarmEvent.Cases.Severity | number | Case severity. |
| McAfeeESM.AlarmEvent.Cases.Status | string | Case status. |
| McAfeeESM.AlarmEvent.Cases.Summary | string | Case summary. |

#### Command Example

```!esm-list-alarm-events alarmId=42687```

#### Context Example

{
“McAfeeESM”: {
“AlarmEvent”: {
“DstIP”: “192.168.1.111”,
“DstMac”: null,
“DstPort”: null,
“FirstTime”: null,
“ID”: “144115188075855872|802641”,
“LastTime”: “2020-06-22T06:16:05Z”,
“Message”: “Failed User Logon”,
“NormalizedDescription”: null,
“Severity”: 25,
“SrcIP”: “11.11.11.11”,
“SrcMac”: null,
“SrcPort”: null,
“SubType”: “failure”
}
}
}


#### Human Readable Output

>### Alarm events
>
>|DstIP|DstMac|DstPort|FirstTime|ID|LastTime|Message|NormalizedDescription|Severity|SrcIP|SrcMac|SrcPort|SubType|
>|---|---|---|---|---|---|---|---|---|---|---|---|---|
>| 192.168.1.111 |  |  |  | 144115188075855872\|802641 | 2020-06-22T06:16:05Z | Failed User Logon |  | 25 | 11.11.11.11 |  |  | failure |

### esm-create-watchlist

***
Create a new watchlist.

#### Base Command

`esm-create-watchlist`

#### Input

| **Argument Name** | **Description** | **Required** |
| --- | --- | --- |
| name | The new watchlist name. | Required |
| type | The type of the new watchlist. | Required |

#### Context Output

| **Path** | **Type** | **Description** |
| --- | --- | --- |
| McAfeeESM.Watchlist.name | string | The watchlist name |
| McAfeeESM.Watchlist.id | number | The watchlist id |
| McAfeeESM.Watchlist.type | string | The watchlist type |

#### Command Example

```!esm-create-watchlist name=test_watchlist type=IPAddress```

#### Context Example

{
“McAfeeESM”: {
“Watchlist”: {
“id”: 54,
“name”: “test_watchlist”,
“type”: “IPAddress”
}
}
}


#### Human Readable Output

>Watchlist test_watchlist created.

### esm-delete-watchlist

***
Delete a watchlist.

#### Base Command

`esm-delete-watchlist`

#### Input

| **Argument Name** | **Description** | **Required** |
| --- | --- | --- |
| ids | the watch list ids to delete. | Optional |
| names | the watch list names to delete. | Optional |

#### Context Output

There is no context output for this command.

#### Command Example

```!esm-delete-watchlist names=test_watchlist```

#### Human Readable Output

>Watchlists removed

### esm-watchlist-add-entry

***
Create a new watchlist entry.

#### Base Command

`esm-watchlist-add-entry`

#### Input

| **Argument Name** | **Description** | **Required** |
| --- | --- | --- |
| watchlist_name | The watchlist name. | Optional |
| watchlist_id | The watchlist id. | Optional |
| values | The values you want to add to watchlist. (CSV format) | Required |

#### Context Output

There is no context output for this command.

#### Command Example

```!esm-watchlist-add-entry watchlist_name=test_watchlist values=1.1.1.1,2.2.2.2```

#### Human Readable Output

>Watchlist successfully updated.

### esm-watchlist-delete-entry

***
Delete watchlist entry.

#### Base Command

`esm-watchlist-delete-entry`

#### Input

| **Argument Name** | **Description** | **Required** |
| --- | --- | --- |
| watchlist_name | The watchlist name. | Optional |
| watchlist_id | The watchlist id. | Optional |
| values | The values you want to remove from watchlist.  (CSV format) | Required |

#### Context Output

There is no context output for this command.

#### Command Example

```!esm-watchlist-delete-entry watchlist_name=test_watchlist values=1.1.1.1,2.2.2.2```

#### Human Readable Output

>Watchlist successfully updated.

### esm-watchlist-list-entries

***
Get watchlist entries.

#### Base Command

`esm-watchlist-list-entries`

#### Input

| **Argument Name** | **Description** | **Required** |
| --- | --- | --- |
| watchlist_name | The watchlist name. | Optional |
| watchlist_id | The watchlist id. | Optional |
| limit | max count of values. | Required |
| offset | values offset. | Required |

#### Context Output

| **Path** | **Type** | **Description** |
| --- | --- | --- |
| McAfeeESM.Watchlist.data | Unknown | The watchlist data |
| McAfeeESM.Watchlist.name | string | The watchlist name |

#### Command Example

```!esm-watchlist-list-entries watchlist_name=test_watchlist```

#### Context Example

{
“McAfeeESM”: {
“Watchlist”: {
“data”: [
“1.1.1.1”,
“2.2.2.2”
],
“name”: “test_watchlist”
}
}
}
```

Human Readable Output

results from test_watchlist watchlist

data
1.1.1.1,
2.2.2.2,

esm-get-watchlists


Returns a list of watchlists’ names and IDs.

Base Command

esm-get-watchlists

Input

Argument Name Description Required
hidden Whether to include hidden watchlists. Can be true or false. Possible values are: true, false. Default is true. Required
dynamic Whether to include dynamic watchlists. Can be true or false. Possible values are: true, false. Default is true. Required
write_only Whether to include write only watchlists. Can be true or false. Possible values are: true, false. Default is false. Required
indexed_only Whether to include indexed only watchlists. Can be true or false. Possible values are: true, false. Default is false. Required

Context Output

Path Type Description
McAfeeESM.Watchlist.name string The name of the watchlist.
McAfeeESM.Watchlist.id number The ID of the watchlist.
McAfeeESM.Watchlist.type string The type of the watchlist.

Configuration parameters

  • url — Base URL (e.g. https://example.com) (required)
  • credentials — Username (required)
  • version — Version: (one of 10.0, 10.1, 10.2, 10.3, 11.1, 11.3) (required)
  • isFetch — Fetch incidents
  • incidentType — Incident type
  • incidentFetchInterval — Incidents Fetch Interval
  • fetchType — Fetch Types: cases, alarms, both (relevant only for fetch incident mode)
  • startingFetchID — Start fetch after ID: (relevant only for fetch incident mode)
  • fetchLimitCases — Fetch cases limit
  • fetchTime — First fetch timestamp (<number> <time unit>, e.g., 12 hours, 7 days, 3 months, 1 year)
  • fetchLimitAlarms — Fetch alarms limit
  • timezone — McAfee ESM Timezone in hours (e.g if ESM timezone is +0300 => then insert 3)
  • insecure — Trust any certificate (not secure)
  • proxy — Use system proxy settings

Commands (25)

  • esm-acknowledge-alarms

    Marks triggered alarms, as acknowledged.

  • esm-add-case

    Adds a case to the system.

  • esm-add-case-status

    Adds a status to the specified case.

  • esm-create-watchlist

    Creates a new watchlist.

  • esm-delete-alarms

    Deletes triggered alarms.

  • esm-delete-case-status

    Deletes the status of a case.

  • esm-delete-watchlist

    Deletes a watchlist.

  • esm-edit-case

    Edit the details of an existing case.

  • esm-edit-case-status

    Edits the status of a case.

  • esm-fetch-alarms

    Retrieves a list of triggered alarms.

  • esm-fetch-fields

    Returns all fields that can be used in query filters, including type information for each field.

  • esm-get-alarm-event-details

    Gets the details for the triggered alarm.

  • esm-get-case-detail

    Returns the details of an existing case.

  • esm-get-case-event-list

    Returns case event details.

  • esm-get-case-list

    Returns a list of cases from McAfee ESM.

  • esm-get-case-statuses

    Returns a list of valid case statuses from the system.

  • esm-get-organization-list

    Returns a case organization.

  • esm-get-user-list

    Returns a list of all users.

  • esm-get-watchlists

    Returns a list of watchlists' names and IDs.

  • esm-list-alarm-events

    Gets a list of events related to the alarm.

  • esm-search

    Perform a query against McAfee ESM SIEM.

  • esm-unacknowledge-alarms

    Marks triggered alarms, as unacknowledged.

  • esm-watchlist-add-entry

    Creates a new watchlist entry.

  • esm-watchlist-delete-entry

    Deletes a watchlist entry.

  • esm-watchlist-list-entries

    Returns a list of watchlist entries.

import itertools
import time
from collections.abc import Callable
from datetime import datetime, timedelta

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

disable_warnings()

CONTEXT_INTEGRATION_NAME = "McAfeeESM."


class EmptyFile(Exception):
    pass


class McAfeeESMClient(BaseClient):
    demisto_format = "%Y-%m-%dT%H:%M:%SZ"

    def __init__(self, params: dict):
        self.args = demisto.args()
        self.__user_name = params.get("credentials", {}).get("identifier", "")
        self.__password = params.get("credentials", {}).get("password", "")
        self.difference = int(params.get("timezone", 0))
        self.version = params.get("version", "10.2")
        super().__init__(
            "{}/rs/esm/v2/".format(params.get("url", "").strip("/")),
            proxy=params.get("proxy", False),
            verify=not params.get("insecure", False),
        )
        self._headers = {"Content-Type": "application/json"}
        self.__login()
        self.__cache: dict = {"users": [], "org": [], "status": []}

    def __del__(self):
        self.__logout()
        super().__del__()

    def _is_status_code_valid(self, *_other):  # noqa
        return True

    def __request(self, mcafee_command: str, data: Union[str, dict] = None, params: dict = None):
        if data:
            data = json.dumps(data)
        result = self._http_request("POST", mcafee_command, data=data, params=params, resp_type="request", timeout=60)
        if result.ok:
            if result.content:
                return result.json()
            else:
                return {}
        else:
            raise DemistoException(f"{mcafee_command} failed with error[{result.content.decode()}].")

    def __login(self):
        params = {
            "username": base64.b64encode(self.__user_name.encode("ascii")).decode(),
            "password": base64.b64encode(self.__password.encode("ascii")).decode(),
            "locale": "en_US",
        }
        res = self._http_request("POST", "login", data=json.dumps(params), resp_type="response", timeout=20)
        self._headers["Cookie"] = "JWTToken={}".format(res.cookies.get("JWTToken"))
        self._headers["X-Xsrf-Token"] = res.headers.get("Xsrf-Token")
        if None in (self._headers["X-Xsrf-Token"], self._headers["Cookie"]):
            raise DemistoException(
                f"Failed login\nurl: {self._base_url}login\nresponse status: {res.status_code}\nresponse: {res.text}\n"
            )

    def __logout(self):
        self._http_request("DELETE", "logout", resp_type="response")

    def test_module(self) -> tuple[str, dict, str]:
        params = demisto.params()

        # check credentials
        self.get_organization_list(raw=True)

        # check fetch parameters
        if params.get("isFetch"):
            start_id = params.get("startingFetchID", "0")
            if not start_id.isdigit():
                raise DemistoException(f'Invalid startingFetchID value. Expected: numeric value, Received "{start_id}"')

        return "ok", {}, "ok"

    def __username_and_id(self, user_name: str = None, user_id: str = None) -> dict:
        """

        :param user_name: the user name for search (the user id)
        :param user_id: the user id for search (the user name)
        :return: {"name": <user name>, "id": <user id>}
        """
        if user_name:
            if user_name.lower() == "me":
                user_name = self.__user_name

            looking_in = "username"
            looking_for = user_name
        elif user_id:
            looking_in = "id"
            looking_for = user_id
        else:
            return {}

        if not self.__cache.get("users"):
            _, _, self.__cache["users"] = self.get_user_list()
        for user in self.__cache["users"]:
            if user.get(looking_in) == looking_for:
                return {"id": user.get("id"), "name": user.get("username")}

        demisto.debug(f"{looking_for} is not a {looking_in}(user).")
        return {}

    def __org_and_id(self, org_name: str = None, org_id: str = None) -> dict:
        """

        :param org_name: the org name for search (the org id)
        :param org_id: the org id for search (the org name)
        :return: {"name": <org name>, "id": <org id>}
        """
        if not org_id:
            if not org_name:
                org_name = "None"

            looking_for = org_name
            looking_in = "name"
        else:
            looking_for = org_id
            looking_in = "id"
        if not self.__cache["org"]:
            _, _, self.__cache["org"] = self.get_organization_list(raw=True)
        for org in self.__cache["org"]:
            if org.get(looking_in) == looking_for:
                return org
        demisto.debug(f"{looking_for} is not a {looking_in}(org).")
        return {}

    def __status_and_id(self, status_name: str = None, status_id: str = None) -> dict:
        """

        :param status_name: the status name for search (the status id)
        :param status_id: the status id for search (the status name)
        :return: {"name": <status name>, "id": <status id>}
        """
        if not status_id:
            looking_for = status_name if status_name else "Open"
            looking_in = "name"
        else:
            looking_for = status_id
            looking_in = "id"
        if not self.__cache["status"]:

            def filter_statuses_data(status_dict: dict):
                try:
                    status_dict.pop("showInCasePane")
                    status_dict.pop("default")
                except KeyError:
                    pass
                return status_dict

            self.__cache["status"] = list(map(filter_statuses_data, (self.get_case_statuses(raw=True))[2]))
        for status in self.__cache["status"]:
            if status.get(looking_in) == looking_for:
                return status
        demisto.debug(f"{looking_for} is not a {looking_in}(status).")
        return {}

    def get_user_list(self, raw: bool = False) -> tuple[str, dict, dict]:
        """
        :param raw: ignore the human outputs if True
        :return: list of all Users
        """
        path = "userGetUserList"
        headers = ["ID", "Name", "Email", "Groups", "IsMaster", "IsAdmin", "SMS"]
        raw_response = self.__request(path, data={"authPW": {"value": self.__password}})
        result = raw_response
        context_entry: list = [dict] * len(result)
        human_readable = ""
        if not raw:
            for i in range(len(result)):
                context_entry[i] = {
                    "ID": result[i].get("id"),
                    "Name": result[i].get("username"),
                    "Email": result[i].get("email"),
                    "SMS": result[i].get("sms"),
                    "IsMaster": result[i].get("master"),
                    "IsAdmin": result[i].get("admin"),
                }
                if "groups" in result[i]:
                    context_entry[i]["Groups"] = "".join(str(result[i]["groups"]))

            human_readable = tableToMarkdown(name="User list", t=context_entry, headers=headers)
        returned_context_entry = {f"{CONTEXT_INTEGRATION_NAME}User(val.ID && val.ID == obj.ID)": context_entry}
        return human_readable, returned_context_entry, raw_response

    def get_organization_list(self, raw: bool = False) -> tuple[str, dict, list[dict]]:
        """
        :param raw: ignore the human outputs if True
        :return: list of all organizations
        """
        path = "caseGetOrganizationList"
        raw_response = self.__request(path)
        entry: list = [None] * len(raw_response)
        context_entry: dict = {}
        human_readable: str = ""
        if not raw:
            for i in range(len(raw_response)):
                entry[i] = {"ID": raw_response[i].get("id"), "Name": raw_response[i].get("name")}
            context_entry = {f"{CONTEXT_INTEGRATION_NAME}Organization(val.ID && val.ID == obj.ID)": entry}
            human_readable = tableToMarkdown(name="Organizations", t=raw_response)

        return human_readable, context_entry, raw_response

    def get_case_list(self, start_time: str = None, raw: bool = False) -> tuple[str, dict, list]:
        """
        :param raw: ignore the human outputs if True
        :return: list of all Users
        """
        path = "caseGetCaseList"
        since = self.args.get("since", "1 year")
        context_entry = []
        human_readable: str = ""
        if not raw and not start_time:
            _, start_time, _ = set_query_times(since=since, difference=self.difference)
            start_time = convert_time_format(str(start_time), difference=self.difference)
        raw_response: list = self.__request(path)
        result = raw_response
        for case in result:
            case = dict_times_set(case, self.difference)
            if not start_time or not start_time > case.get("openTime"):
                temp_case = {
                    "ID": case.get("id"),
                    "Summary": case.get("summary"),
                    "OpenTime": case.get("openTime"),
                    "Severity": case.get("severity"),
                }
                if "statusId" in case:
                    status_id = case.get("statusId", {})
                    if isinstance(status_id, dict):
                        status_id = status_id.get("value")
                    temp_case["Status"] = self.__status_and_id(status_id=status_id).get("name")
                context_entry.append(temp_case)
        if not raw:
            human_readable = tableToMarkdown(name=f"cases since {since}", t=context_entry)
        returned_context_entry = {f"{CONTEXT_INTEGRATION_NAME}Case(val.ID && val.ID == obj.ID)": context_entry}
        return human_readable, returned_context_entry, raw_response

    def get_case_event_list(self) -> tuple[str, dict, list[dict]]:
        path = "caseGetCaseEventsDetail"
        ids = argToList(self.args.get("ids"))
        raw_response = self.__request(path, data={"eventIds": {"list": ids}})
        result = raw_response
        case_event: list = [None] * len(result)
        for i in range(len(result)):
            result[i] = dict_times_set(result[i], self.difference)
            case_event[i] = {
                "ID": result[i].get("id"),
                "LastTime": result[i].get("lastTime"),
                "Message": result[i].get("message"),
            }

        context_entry = {f"{CONTEXT_INTEGRATION_NAME}CaseEvent(val.ID && val.ID == obj.ID)": case_event}
        human_readable = tableToMarkdown(name="case event list", t=result)
        return human_readable, context_entry, raw_response

    def get_case_detail(self, case_id: str = None, raw: bool = False) -> tuple[str, dict, dict]:
        path = "caseGetCaseDetail"
        raw_response = self.__request(path, data={"id": case_id if case_id else self.args.get("id")})
        result = raw_response
        result = dict_times_set(result, difference=self.difference)
        status_id = result.get("statusId", {})
        if not isinstance(status_id, int):
            status_id = status_id.get("value")
        context_entry = {
            "Assignee": self.__username_and_id(user_id=result.get("assignedTo")).get("name"),
            "ID": result.get("id"),
            "Summary": result.get("summary"),
            "Status": self.__status_and_id(status_id=status_id).get("name"),
            "OpenTime": result.get("openTime"),
            "Severity": result.get("severity"),
            "Organization": self.__org_and_id(org_id=result.get("orgId")).get("name"),
            "EventList": result.get("eventList"),
            "Notes": result.get("notes"),
        }
        human_readable = ""
        readable_outputs = context_entry
        del readable_outputs["Notes"]
        del readable_outputs["EventList"]
        if not raw:
            human_readable = tableToMarkdown(name="Case", t=readable_outputs)
        returned_context_entry = {f"{CONTEXT_INTEGRATION_NAME}Case(val.ID && val.ID == obj.ID)": context_entry}
        return human_readable, returned_context_entry, raw_response

    def get_case_statuses(self, raw: bool = False) -> tuple[str, dict, dict]:
        path = "caseGetCaseStatusList"
        headers = ["id", "name", "default", "showInCasePane"]
        raw_response = self.__request(path)
        human_readable = ""
        if not raw:
            human_readable = tableToMarkdown(name="case statuses", t=raw_response, headers=headers)
        return human_readable, {}, raw_response

    def add_case(self) -> tuple[str, dict, dict]:
        path = "caseAddCase"

        assignee = self.args.get("assignee")
        if not assignee:
            assignee = "ME"

        case_details = {
            "summary": self.args.get("summary"),
            "assignedTo": self.__username_and_id(user_name=assignee).get("id"),
            "severity": self.args.get("severity"),
            "orgId": self.__org_and_id(org_name=self.args.get("organization")).get("id"),
            "statusId": {"value": self.__status_and_id(status_name=self.args.get("status")).get("id")},
        }
        result = self.__request(path, data={"caseDetail": case_details})
        human_readable, context_entry, raw_response = self.get_case_detail(result.get("value"))
        return human_readable, context_entry, raw_response

    def edit_case(self) -> tuple[str, dict, dict]:
        path = "caseEditCase"
        _, _, result = self.get_case_detail(case_id=self.args.get("id"))
        if "organization" in self.args:
            result["orgId"] = self.__org_and_id(org_name=self.args.get("organization")).get("id")
        if "summary" in self.args:
            result["summary"] = self.args["summary"]
        if "assignee" in self.args:
            result["assignedTo"] = self.args["assignee"]
        if "severity" in self.args:
            result["severity"] = self.args["severity"]
        if "status" in self.args:
            result["statusId"] = {"value": self.__status_and_id(status_name=self.args["status"]).get("id")}
        if "notes" in self.args:
            result["notes"] = self.args["notes"]

        self.__request(path, data={"caseDetail": result})
        return self.get_case_detail(case_id=self.args.get("id"))

    def add_case_status(self) -> tuple[str, dict, dict]:
        path = "caseAddCaseStatus"
        status_details = {"name": self.args.get("name"), "default": False}
        if "should_show_in_case_pane" in self.args:
            status_details["showInCasePane"] = self.args["should_show_in_case_pane"]
        raw_response = self.__request(path, data={"status": status_details})
        self.__cache["status"] = {}
        status_id = status_details["name"]
        return f"Added case status : {status_id}", {}, raw_response

    def edit_case_status(self) -> tuple[str, dict, dict]:
        path = "caseEditCaseStatus"
        status_id = self.__status_and_id(status_name=self.args.get("original_name")).get("id")
        status_details = {"status": {"id": status_id, "name": self.args.get("new_name")}}

        if "show_in_case_pane" in self.args:
            status_details["status"]["showInCasePane"] = self.args.get("show_in_case_pane")
        raw_response = self.__request(path, data=status_details)
        self.__cache["status"] = {}
        return f"Edited case status with ID: {status_id}", {}, raw_response

    def delete_case_status(self) -> tuple[str, dict, dict]:
        path = "caseDeleteCaseStatus"
        status_id = self.__status_and_id(status_name=self.args.get("name")).get("id")
        self.__request(path, data={"statusId": {"value": status_id}})
        self.__cache["status"] = {}
        return f"Deleted case status with ID: {status_id}", {}, {}

    def fetch_fields(self) -> tuple[str, dict, dict[str, list]]:
        path = "qryGetFilterFields"
        raw_response = self.__request(path)
        result = raw_response
        for field_type in result:
            field_type["types"] = ",".join(set(field_type["types"]))
        human_readable = tableToMarkdown(name="Fields", t=result)
        return human_readable, {}, raw_response

    def fetch_alarms(
        self, since: str = None, start_time: str = None, end_time: str = None, raw: bool = False
    ) -> tuple[str, dict, list]:
        path = "alarmGetTriggeredAlarms"
        human_readable = ""
        context_entry: list = []
        since = since if since else self.args.get("timeRange")
        start_time = start_time if start_time else self.args.get("customStart")
        end_time = end_time if end_time else self.args.get("customEnd")

        since, start_time, end_time = set_query_times(since, start_time, end_time, self.difference)
        params = {"triggeredTimeRange": since}
        if since == "CUSTOM":
            params["customStart"] = start_time
            params["customEnd"] = end_time

        data = {}
        if assigned_user := self.args.get("assignedUser"):
            if assigned_user.lower() == "me":
                assigned_user = self.__user_name
            data = {"assignedUser": {"username": assigned_user, "id": self.__username_and_id(user_name=assigned_user).get("id")}}

        demisto.debug(f"sending request to fetch alarms with {start_time=}, {end_time=}")
        raw_response = self.__request(path, data=data, params=params)
        result = raw_response

        for i in range(len(result)):
            result[i] = dict_times_set(result[i], self.difference)

        if not raw:
            context_entry = [None] * len(result)
            for i in range(len(result)):
                context_entry[i] = {
                    "ID": result[i].get("id"),
                    "summary": result[i].get("summary"),
                    "assignee": result[i].get("assignee"),
                    "severity": result[i].get("severity"),
                    "triggeredDate": result[i].get("triggeredDate"),
                    "acknowledgedDate": result[i].get("acknowledgedDate"),
                    "acknowledgedUsername": result[i].get("acknowledgedUsername"),
                    "alarmName": result[i].get("alarmName"),
                    "conditionType": result[i].get("conditionType"),
                }

            table_headers = [
                "id",
                "acknowledgedDate",
                "acknowledgedUsername",
                "alarmName",
                "assignee",
                "conditionType",
                "severity",
                "summary",
                "triggeredDate",
            ]
            human_readable = tableToMarkdown(name="Alarms", t=result, headers=table_headers)
        returned_context_entry = {f"{CONTEXT_INTEGRATION_NAME}Alarm(val.ID && val.ID == obj.ID)": context_entry}
        return human_readable, returned_context_entry, raw_response

    def acknowledge_alarms(self) -> tuple[str, dict, dict]:
        try:
            self.__handle_alarms("Acknowledge")
        except DemistoException as error:
            # bug in ESM API performs the job but an error is return.
            if not expected_errors(error):
                raise error
        return "Alarms has been Acknowledged.", {}, {}

    def unacknowledge_alarms(self) -> tuple[str, dict, dict]:
        try:
            self.__handle_alarms("Unacknowledge")
        except DemistoException as error:
            # bug in ESM API performs the job but an error is return.
            if not expected_errors(error):
                raise error
        return "Alarms has been Unacknowledged.", {}, {}

    def delete_alarm(self) -> tuple[str, dict, dict]:
        self.__handle_alarms("Delete")
        return "Alarms has been Deleted.", {}, {}

    def __handle_alarms(self, command: str):
        path = f"alarm{command}TriggeredAlarm"
        alarm_ids = argToList(str(self.args.get("alarmIds")))
        alarm_ids = [int(i) for i in alarm_ids]
        data = {"triggeredIds": {"alarmIdList": alarm_ids} if not self.version < "11.3" else alarm_ids}
        self.__request(path, data=data)

    def get_alarm_event_details(self) -> tuple[str, dict, dict]:
        path = "ipsGetAlertData"
        raw_response = self.__request(path, data={"id": self.args.get("eventId")})
        result = raw_response
        result = dict_times_set(result, self.difference)
        context_entry = self.__alarm_event_context_and_times_set(result)
        human_readable = tableToMarkdown(name="Alarm events", t=context_entry)
        return human_readable, {f"{CONTEXT_INTEGRATION_NAME}AlarmEvent": context_entry}, raw_response

    def list_alarm_events(self) -> tuple[str, dict, dict]:
        path = "notifyGetTriggeredNotificationDetail"
        raw_response = self.__request(path, data={"id": self.args.get("alarmId")})
        result = raw_response
        result = dict_times_set(result, self.difference)
        human_readable: str = ""
        context_entry: list = []
        if "events" in result:
            context_entry = [dict] * len(result["events"])
            for event in range(len(result["events"])):
                context_entry[event] = self.__alarm_event_context_and_times_set(result["events"][event])
            human_readable = tableToMarkdown(name="Alarm events", t=context_entry)

        return (
            human_readable,
            {f"{CONTEXT_INTEGRATION_NAME}AlarmEvent(val.ID && val.ID == obj.ID)": context_entry},
            raw_response,
        )

    def complete_search(self):
        time_out = int(self.args.get("timeOut", 30))
        interval = min(10, time_out)
        search_id = self.__search()
        i = 0
        while not self.__generic_polling(search_id):
            i += 1
            time.sleep(interval)  # pylint: disable=sleep-exists
            if i * interval >= time_out:
                raise DemistoException(f"Search: {search_id} time out.")

        return self.__search_fetch_result(search_id)

    def __search(self) -> int:
        path = "qryExecuteDetail"
        query_type = self.args.get("queryType")
        time_range = self.args.get("timeRange")
        custom_start = self.args.get("customStart")
        custom_end = self.args.get("customEnd")
        offset = self.args.get("offset")
        time_range, custom_start, custom_end = set_query_times(time_range, custom_start, custom_end, self.difference)
        time_config = {"timeRange": time_range}
        if time_range == "CUSTOM":
            time_config["customStart"] = custom_start
            time_config["customEnd"] = custom_end
        params = {"reverse": False, "type": query_type if query_type else "EVENT"}
        config = {"filters": json.loads(self.args.get("filters")), "limit": self.args.get("limit", 0)}
        fields = self.args.get("fields")
        if fields:
            config["fields"] = [{"name": field} for field in argToList(fields)]
        if offset:
            config["offset"] = offset

        config.update(time_config)
        result = self.__request(path, data={"config": config}, params=params)
        return result.get("resultID")

    def __generic_polling(self, search_id: Union[str, int]) -> bool:
        if not search_id:
            search_id = self.args.get("SearchID")
        path = "qryGetStatus"
        status = self.__request(path, data={"resultID": str(search_id)})
        return status.get("complete")

    def __search_fetch_result(self, search_id: int) -> tuple[str, dict, dict]:
        path = "qryGetResults"
        params = {"startPos": 0, "reverse": False, "numRows": self.args.get("ratePerFetch", 50)}
        result_ready = False
        raw_response: dict[str, list] = {"columns": [], "rows": []}

        while not result_ready:
            try:
                temp = self.__request(path, data={"resultID": search_id}, params=params)
                if not raw_response["columns"]:
                    raw_response["columns"] = temp.get("columns")
                if len(temp.get("rows", {})) < params["numRows"]:
                    result_ready = True

                raw_response["rows"].extend(temp.get("rows"))
                params["startPos"] += params["numRows"]

            except DemistoException as error:
                if not expected_errors(error):
                    raise
                else:
                    result_ready = True
        result = raw_response
        result = table_times_set(result, self.difference)
        entry: list = [{}] * len(result["rows"])
        headers = [str(field.get("name")).replace(".", "") for field in result["columns"]]
        for i in range(len(result["rows"])):
            entry[i] = {headers[j]: result["rows"][i]["values"][j] for j in range(len(headers))}

        condition = (
            "(val.AlertIPSIDAlertID && val.AlertIPSIDAlertID == obj.AlertIPSIDAlertID)" if "AlertIPSIDAlertID" in headers else ""
        )
        context_entry = {f"{CONTEXT_INTEGRATION_NAME}results{condition}": entry}
        return search_readable_outputs(result), context_entry, raw_response

    def __alarm_event_context_and_times_set(self, result: dict) -> dict:
        context_entry = {
            "ID": result.get("eventId", result.get("alertId")),
            "SubType": result.get("subtype", result.get("eventSubType")),
            "Severity": result.get("severity"),
            "Message": result.get("ruleName", result.get("ruleMessage")),
            "LastTime": result.get("lastTime"),
            "SrcIP": result.get("srcIp", result.get("sourceIp")),
            "DstIP": result.get("destIp", result.get("destIp")),
            "DstMac": result.get("destMac"),
            "SrcMac": result.get("srcMac"),
            "DstPort": result.get("destPort"),
            "SrcPort": result.get("srcPort"),
            "FirstTime": result.get("firstTime"),
            "NormalizedDescription": result.get("normDesc"),
        }
        if "cases" in result:
            cases: list = [None] * len(result["cases"])
            for i in range(len(result["cases"])):
                case_status = self.__status_and_id(status_id=result["cases"][i].get("statusId", {}).get("value"))
                cases[i] = {
                    "ID": result["cases"][i].get("id"),
                    "OpenTime": result["cases"][i].get("openTime"),
                    "Severity": result["cases"][i].get("severity"),
                    "Status": case_status.get("name"),
                    "Summary": result["cases"][i].get("summary"),
                }
            context_entry["Case"] = cases
        return context_entry

    def fetch_incidents(self, params: dict):
        last_run = demisto.getLastRun()
        current_run = {}
        incidents = []
        if params.get("fetchType", "alarms") in ("alarms", "both"):
            start_time = last_run.get("alarms", {}).get("time", parse_date_range(params.get("fetchTime"), self.demisto_format)[0])
            start_id = int(last_run.get("alarms", {}).get("id", params.get("startingFetchID")))
            temp_incidents, current_run["alarms"] = self.__alarms_to_incidents(
                start_time, start_id, int(params.get("fetchLimitAlarms", 5))
            )
            incidents.extend(temp_incidents)

        if params.get("fetchType") in ("cases", "both"):
            start_id = int(last_run.get("cases", {}).get("id", params.get("startingFetchID")))
            temp_incidents, current_run["cases"] = self.__cases_to_incidents(
                start_id=start_id, limit=int(params.get("fetchLimitCases", 5))
            )
            incidents.extend(temp_incidents)

        demisto.setLastRun(current_run)
        demisto.incidents(incidents)

    def __alarms_to_incidents(self, start_time: str, start_id: int = 0, limit: int = 1) -> tuple[list, dict]:
        current_time = datetime.utcnow().strftime(self.demisto_format)
        current_run = {}
        _, _, all_alarms = self.fetch_alarms(start_time=start_time, end_time=current_time, raw=True)
        all_alarms = filtering_incidents(all_alarms, start_id=start_id, limit=limit)
        if all_alarms:
            current_run["time"] = all_alarms[0].get("triggeredDate", start_time)
            current_run["id"] = all_alarms[0]["id"]
            current_run_time = current_run["time"]
            demisto.debug(f"{len(all_alarms)=}, setting current time to {current_run_time=}")
        else:
            current_run["time"] = start_time
            current_run["id"] = start_id
            demisto.debug(f"No alarms were found, setting current time to {start_time=}")
        all_alarms = create_incident(all_alarms, alarms=True)
        return all_alarms, current_run

    def __cases_to_incidents(self, start_id: int = 0, limit: int = 1) -> tuple[list, dict]:
        _, _, all_cases = self.get_case_list(raw=True)
        all_cases = filtering_incidents(all_cases, start_id=start_id, limit=limit)
        current_run = {"id": all_cases[0].get("id", start_id) if all_cases else start_id}
        all_cases = create_incident(all_cases, alarms=False)
        return all_cases, current_run

    def __get_watchlists(self, args: dict):
        command = "sysGetWatchlists"
        params = {
            "hidden": args.get("hidden", True),
            "dynamic": args.get("dynamic", True),
            "writeOnly": args.get("write_only", False),
            "indexedOnly": args.get("indexed_only", False),
        }
        return self.__request(command, params=params)

    def __get_watchlist_id(self, watchlist_name: str):
        try:
            return list(filter(lambda x: x.get("name") == watchlist_name, self.__get_watchlists({})))[0].get("id")
        except IndexError:
            raise DemistoException(f"Can not find the watchlist {watchlist_name}")

    def get_watchlists_names_and_ids(self):
        raw_watch_lists = self.__get_watchlists(self.args)
        watch_lists = list(map(format_watchlist_params, raw_watch_lists))
        human_readable = tableToMarkdown("McAfee ESM Watchlist", t=watch_lists)
        return human_readable, {f"{CONTEXT_INTEGRATION_NAME}Watchlist": watch_lists}, raw_watch_lists

    def add_watchlist(self):
        command = "sysAddWatchlist"
        watchlist_name = self.args.get("name")
        watchlist_type = self.args.get("type")
        data = {
            "watchlist": {
                "name": watchlist_name,
                "type": {"name": watchlist_type, "id": 0},
                "customType": {"name": "", "id": 0},
                "dynamic": "False",
                "enabled": "True",
            }
        }
        watchlist_id = self.__request(command, data=data)
        context_entry = {
            "name": watchlist_name,
            "id": watchlist_id.get("value"),
            "type": watchlist_type,
        }
        human_readable = f"Watchlist {watchlist_name} created."
        return human_readable, {f"{CONTEXT_INTEGRATION_NAME}Watchlist": context_entry}, watchlist_id

    def delete_watchlist(self):
        command = "sysRemoveWatchlist"
        ids_to_delete = argToList(self.args.get("ids", ""))
        ids_to_delete.extend(list(map(self.__get_watchlist_id, argToList(self.args.get("names")))))
        if self.version.startswith("11."):
            data = {"ids": {"watchlistIdList": ids_to_delete}}
            self.__request(command, data)
        else:
            for single_id in ids_to_delete:
                data = {"id": single_id}
                self.__request(command, data)
        return "Watchlists removed", {}, {}

    def watchlist_add_entry(self):
        command = "sysAddWatchlistValues"
        watchlist_id = self.args.get("watchlist_id")
        data = {
            "watchlist": watchlist_id if watchlist_id else self.__get_watchlist_id(self.args.get("watchlist_name", "")),
            "values": argToList(self.args.get("values", "")),
        }
        raw_response = self.__request(command, data=data)
        human_readable = "Watchlist successfully updated."
        return human_readable, {}, raw_response

    def watchlist_delete_entry(self):
        command = "sysRemoveWatchlistValues"
        watchlist_id = self.args.get("watchlist_id")
        data = {
            "watchlist": watchlist_id if watchlist_id else self.__get_watchlist_id(self.args.get("watchlist_name", "")),
            "values": argToList(self.args.get("values", "")),
        }
        self.__request(command, data=data)
        human_readable = "Watchlist successfully updated."
        return human_readable, {}, {}

    def __get_watchlist_file_id(self, watchlist_id: int):
        command = "sysGetWatchlistDetails"
        result = self.__request(command, data={"id": watchlist_id})
        # v10.x uses 'valueCount' while v11.x uses 'recordCount'.
        count_results = result.get("recordCount") or result.get("valueCount")
        if not count_results:
            raise EmptyFile
        value_file = result.get("valueFile", {})
        file_token = value_file.get("fileToken", value_file.get("id"))
        watchlist_name = result.get("name")
        return file_token, watchlist_name

    def watchlist_data_list(self):
        watchlist_id = self.args.get("watchlist_id")
        watchlist_id = watchlist_id if watchlist_id else self.__get_watchlist_id(self.args.get("watchlist_name", ""))

        try:
            file_token, watchlist_name = self.__get_watchlist_file_id(watchlist_id)
        except EmptyFile:
            return "the watchlist is empty.", {}, {}

        else:
            max_values = int(self.args.get("limit", 50))
            offset = int(self.args.get("offset", 0))
            file_data = []
            for i, line in enumerate(self.watchlist_values(file_token)):
                if i < offset:
                    continue
                file_data.append(line)
                if len(file_data) >= max_values:
                    break
            human_readable = tableToMarkdown(f"results from {watchlist_name} watchlist", t={"data": file_data})
            context_entry = {f"{CONTEXT_INTEGRATION_NAME}Watchlist": {"data": file_data, "name": watchlist_name}}
            return human_readable, context_entry, file_data

    def watchlist_values(self, file_token: str, buff_size=400):
        """

        :param file_token: the token file (McAfee API call needed for the file creation)
        :param buff_size: the size of the bytes in every API call.
        :return: generate values (string)
        """
        command = "sysGetWatchlistValues"
        data = {"file": {"id": file_token}}
        end = ""
        for i in itertools.count(start=0):
            params = {"pos": i * buff_size, "count": buff_size}
            result = self.__request(command, data=data, params=params)
            file_data = "{}{}".format(end, result.get("data", ""))
            file_data = file_data.split("\n")
            more_data_exist = buff_size == result.get("bytesRead") or not file_data
            if more_data_exist:
                end = file_data[-1]
                file_data = file_data[:-1]

            yield from (line for line in file_data if line)

            if not more_data_exist:
                break


def filtering_incidents(incidents_list: list, start_id: int, limit: int = 1):
    """

    :param incidents_list: list of al incidents
    :param start_id: id to start from
    :param limit: limit
    :return: the filtered incidents
    """
    filtered_incident = []
    ignored_incident_ids = []
    for incident in incidents_list:
        if int(incident.get("id", 0)) > start_id:
            filtered_incident.append(incident)
        else:
            ignored_incident_ids.append(incident.get("id"))

    demisto.debug(f"filtered {len(ignored_incident_ids)} incidents by {start_id=}.\n{ignored_incident_ids=}")

    filtered_incident.sort(key=lambda incident: int(incident.get("id", 0)), reverse=True)
    if limit != 0:
        incidents_size = min(limit, len(filtered_incident))
        filtered_incident = filtered_incident[-incidents_size:]

    return filtered_incident


def expected_errors(error: DemistoException) -> bool:
    """

    :param error: the error
    :return: if the error is not real error
    """
    expected_error: list[str] = [
        "qryGetResults failed with error[Error deserializing EsmQueryResults, see logs for more information "  # noqa: W504
        + "(Error deserializing EsmQueryResults, see logs for more information "  # noqa: W504
        + "(Internal communication error, see logs for more details))].",
        "alarmUnacknowledgeTriggeredAlarm failed with error[ERROR_BadRequest (60)].",
        "alarmAcknowledgeTriggeredAlarm failed with error[ERROR_BadRequest (60)].",
    ]
    return str(error) in expected_error


def time_format(current_time: str, difference: int = 0) -> str:
    """

    :param current_time: the current time in the current format
    :param difference: the time zone offset
    :return: the time in the new format and in UTC time
    """
    to_return: str = ""
    try:
        to_return = convert_time_format(current_time, difference=difference, mcafee_format="%Y/%m/%d %H:%M:%S")
    except ValueError as error:
        if str(error) != f"time data '{current_time}' does not match format '%Y/%m/%d %H:%M:%S'":
            raise error

        try:
            to_return = convert_time_format(current_time, difference=difference, mcafee_format="%m/%d/%Y %H:%M:%S")
        except ValueError as error_2:
            if str(error_2) != f"time data '{current_time}' does not match format '%m/%d/%Y %H:%M:%S'":
                raise error_2

            try:
                to_return = convert_time_format(current_time, difference=difference, mcafee_format="%d-%m-%Y %H:%M:%S")
            except ValueError as error_3:
                if str(error_3) != f"time data '{current_time}' does not match format '%d-%m-%Y %H:%M:%S'":
                    raise error_3
                else:
                    raise ValueError(f"time data '{current_time}' does not match the time format.")
    return to_return


def convert_time_format(
    current_time: str, difference: int = 0, to_demisto: bool = True, mcafee_format: str = "%Y/%m/%d %H:%M:%S"
) -> str:
    """

    :param current_time: the current_time
    :param difference: the difference (e.g. time zone)
    :param to_demisto: true if we want change the time zone from McAfee ESM time to demisto (e.g. UTC)
    :param mcafee_format: the standard format in McAfee Machine
    :return: the new format
    """
    if not current_time.endswith("(GMT)"):
        if not to_demisto and not current_time.endswith("Z"):
            current_time += "Z"
        datetime_obj = datetime.strptime(current_time, mcafee_format if to_demisto else McAfeeESMClient.demisto_format)
        datetime_obj -= timedelta(hours=difference if to_demisto else -1 * difference)
    else:
        datetime_obj = datetime.strptime(current_time, "%m/%d/%Y %H:%M:%S(GMT)")

    return datetime_obj.strftime(McAfeeESMClient.demisto_format)


def set_query_times(
    since: str = None, start_time: str = None, end_time: str = None, difference: int = 0
) -> tuple[str | None, str | None, str | None]:
    """
    checks all time args
    :param since: since from args
    :param start_time: start_time from args
    :param end_time: end_time from args
    :param difference: the difference (e.g. time zone)
    :return: the args in the machine time and after validation
    """
    if not since:
        since = "CUSTOM"
    elif start_time or end_time:
        raise ValueError("Invalid set times.")
    if since != "CUSTOM" and " " in since:
        start_time, _ = parse_date_range(since, "%Y/%m/%d %H:%M:%S")
    else:
        if start_time:
            start_time = convert_time_format(start_time, difference=difference, to_demisto=False)
        if end_time:
            end_time = convert_time_format(end_time, difference=difference, to_demisto=False)

        if start_time and end_time and start_time > end_time:
            raise ValueError("Invalid set times.")
    return since, start_time, end_time


def list_times_set(list_to_set: list, indexes: list, difference: int = 0) -> list:
    """

    :param list_to_set: the raw list
    :param indexes: a list of the indexes for time fields
    :param difference: the difference (e.g. time zone)
    :return: the data list with utc times
    """
    for i in indexes:
        if list_to_set[i]:
            list_to_set[i] = time_format(list_to_set[i], difference=difference)
    return list_to_set


def dict_times_set(dict_to_set: dict, difference: int = 0) -> dict:
    """

    :param dict_to_set: the raw dict
    :param difference: the difference (e.g. time zone)
    :return: the data dict with utc times
    """
    for field in dict_to_set:
        if dict_to_set[field]:
            if "time" in field.lower() or "date" in field.lower():
                dict_to_set[field] = time_format(dict_to_set[field], difference=difference)
            elif isinstance(dict_to_set[field], dict):
                dict_to_set[field] = dict_times_set(dict_to_set[field], difference)
            elif isinstance(dict_to_set[field], list):
                for i in range(len(dict_to_set[field])):
                    if isinstance(dict_to_set[field][i], dict):
                        dict_to_set[field][i] = dict_times_set(dict_to_set[field][i], difference)
    return dict_to_set


def time_fields(field_list: list[dict]) -> list:
    """

    :param field_list: the list of fields for a given query
    :return: all fields (names only) that have a time value
    """
    indexes_list = []
    for i in range(len(field_list)):
        if "time" in field_list[i]["name"].lower() or "date" in field_list[i]["name"].lower():
            indexes_list.append(i)
    return indexes_list


def table_times_set(table_to_set: dict, difference: int = 0) -> dict:
    """

    :param table_to_set: the raw event/ alarm
    :param difference: the difference (e.g. time zone)
    :return: the event/ alarm with utc time
    """
    indexes_list = time_fields(table_to_set["columns"])
    for dict_ in table_to_set["rows"]:
        dict_["values"] = list_times_set(dict_.get("values"), indexes_list, difference)
    return table_to_set


def search_readable_outputs(table: dict) -> str:
    """

    :param table: the raw data for a search
    :return: md format table
    """
    if "columns" in table and "rows" in table:
        line_1 = line_2 = "|"
        for header in table.get("columns", []):
            line_1 += str(header.get("name")) + "|"
            line_2 += "--|"
        rows = table["rows"]
        data: list = [str] * len(rows)
        for i in range(len(rows)):
            middle = "~".join(rows[i].get("values", []))
            middle = middle.replace("|", "\\|")
            middle = middle.replace("~", "|")
            data[i] = f"| {middle} |"

        start = f"Search results\n{line_1}\n{line_2}\n"
        return start + "\n".join(data)
    else:
        return ""


def create_incident(raw_incidents: list[dict], alarms: bool) -> list[dict[str, dict]]:
    incidents = []
    for incident in raw_incidents:
        alarm_id = str(incident.get("id"))
        summary = str(incident.get("summary"))
        incident_type = "alarm" if alarms else "case"
        incidents.append(
            {
                "name": f"McAfee ESM {incident_type}. id: {alarm_id}. {summary}",
                "severity": mcafee_severity_to_demisto(incident.get("severity", 0)),
                "occurred": incident.get("triggeredDate", incident.get("openTime", "")),
                "rawJSON": json.dumps(incident),
            }
        )
    return incidents


def mcafee_severity_to_demisto(severity: int) -> int:
    if severity > 65:
        return 3
    elif severity > 32:
        return 2
    elif severity > 0:
        return 1
    else:
        return 0


def format_watchlist_params(raw_watchlist_params: dict):
    return {
        "id": raw_watchlist_params.get("id"),
        "name": raw_watchlist_params.get("name"),
        "type": dict_safe_get(raw_watchlist_params, ["type", "name"]),
    }


def main():
    client = McAfeeESMClient(demisto.params())
    command = demisto.command()
    commands: dict[str, Callable] = {
        "test-module": client.test_module,
        "esm-fetch-fields": client.fetch_fields,
        "esm-get-organization-list": client.get_organization_list,
        "esm-fetch-alarms": client.fetch_alarms,
        "esm-add-case": client.add_case,
        "esm-get-case-detail": client.get_case_detail,
        "esm-edit-case": client.edit_case,
        "esm-get-case-statuses": client.get_case_statuses,
        "esm-edit-case-status": client.edit_case_status,
        "esm-get-case-event-list": client.get_case_event_list,
        "esm-add-case-status": client.add_case_status,
        "esm-delete-case-status": client.delete_case_status,
        "esm-get-case-list": client.get_case_list,
        "esm-get-user-list": client.get_user_list,
        "esm-acknowledge-alarms": client.acknowledge_alarms,
        "esm-unacknowledge-alarms": client.unacknowledge_alarms,
        "esm-delete-alarms": client.delete_alarm,
        "esm-get-alarm-event-details": client.get_alarm_event_details,
        "esm-list-alarm-events": client.list_alarm_events,
        "esm-search": client.complete_search,
        "esm-get-watchlists": client.get_watchlists_names_and_ids,
        "esm-create-watchlist": client.add_watchlist,
        "esm-delete-watchlist": client.delete_watchlist,
        "esm-watchlist-add-entry": client.watchlist_add_entry,
        "esm-watchlist-delete-entry": client.watchlist_delete_entry,
        "esm-watchlist-list-entries": client.watchlist_data_list,
    }
    try:
        if command == "fetch-incidents":
            client.fetch_incidents(demisto.params())
        elif command in commands:
            human_readable, context_entry, raw_response = commands[command]()
            return_results(CommandResults(readable_output=human_readable, outputs=context_entry, raw_response=raw_response))
        else:
            raise NotImplementedError(f"{command} is not a demisto command.")

    except Exception as error:
        return_error(str(error), error)


if __name__ in ("__main__", "__builtin__", "builtins"):
    main()