FortiSIEMV2
Use FortiSIEM v2 to fetch and update incidents, search events and manage watchlists of FortiSIEM.
Analytics & SIEM · FortiSIEM
Details
| ID | FortiSIEMV2 |
|---|---|
| Provider | Fortinet |
| Category | Analytics & SIEM |
| From Version | 6.0.0 |
| Docker Image | demisto/python3:3.12.13.10116658 |
| Supported Modules | Agentix XSIAM |
README
Use FortiSIEM v2 to fetch and update incidents, search events and manage FortiSIEM watchlists.
This integration was integrated and tested with FortiSIEMV2 version 6.3.2.
This is the default integration for this content pack when configured by the Data Onboarder in Cortex XSIAM.
Changes have been made that might affect your existing content.
If you are upgrading from a previous of this integration, see Breaking Changes.
Configure FortiSIEM v2 in Cortex
| Parameter | Description | Required |
|---|---|---|
| Server URL | For example: https://192.168.1.1 | True |
| Username | True | |
| Password | True | |
| Maximum incidents per fetch. | Default is 20. Maximum is 200. Setting a value greater than 20 may harm performance, if used with ‘Fetch With Events’ mode. | False |
| First fetch timestamp (number, time unit. e.g., 12 hours, 7 days). | False | |
| Filter incidents by status. | False | |
| Fetch Mode | Fetch With Events mode is currently available only for FortiSiem version 6.6 and earlier. Note that using Fetch With Events mode may affect performance. | False |
| Maximum events to fetch per incident. | Default is 20. Maximum is 50. | False |
| Use system proxy settings | False | |
| Trust any certificate (not secure) | False | |
| Incident type | False | |
| Fetch incidents | False |
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.
fortisiem-event-search
Initiate search process on events. Events are retrieved according to a constraint determined either by the query argument or by the filtering arguments. When using filtering arguments, an ‘AND’ operator is used between them. If the query argument is provided, it overrides the values in the filtering arguments.
Base Command
fortisiem-event-search
Input
| Argument Name | Description | Required |
|---|---|---|
| query | The query for filtering the relevant events. For example, “eventId=9071234812319593968 AND eventType=’type’”. You can retrieve the attributes’ names using the command’s filtering arguments or using the event attributes returned in the context output. | Optional |
| extended_data | Whether to extend the data. This affects the number of attributes returned. Possible values are: false, true. Default is true. | Optional |
| polling | Use Cortex XSOAR built-in polling to retrieve the result when it’s ready. Possible values are: true, false. Default is false. | Optional |
| search_id | The ID of the search query to retrieve its results. Intended for use by the polling process; does not need to be provided by the user. | Optional |
| limit | The number of results to retrieve. Minimum value is 1. Default is 50. | Optional |
| page | The page number of the results to retrieve. Minimum value is 1. Default is 1. | Optional |
| interval_in_seconds | How long to wait between command executions (in seconds) when ‘polling’ argument is true. Minimum value is 10 seconds. Default is 10. | Optional |
| timeout_in_seconds | The time in seconds until the polling sequence timeouts. Default is 60. | Optional |
| from_time | Start of the time filter for events. For example, “3 days ago”, “1 month”, “2019-10-10T12:22:00”, “2019-10-10”. | Required |
| to_time | End of the time filter for events. For example, “3 days ago”, “1 month”, “2019-10-10T12:22:00”, “2019-10-10”. | Required |
| eventId | Event ID. Filtering argument. | Optional |
| eventType | Event type. Filtering argument. | Optional |
| reptDevIpAddr | Reporting IP address. Filtering argument. | Optional |
| destAction | Destination action. Filtering argument. | Optional |
| destDomain | Destination domain. Filtering argument. | Optional |
| destIpAddr | Destination IP address. | Optional |
| destUser | Destination user. Filtering argument. | Optional |
| srcDomain | Source domain. Filtering argument. | Optional |
| srcGeoCountry | Source geo country. Filtering argument. | Optional |
| srcIpAddr | Source IP address. | Optional |
| user | The involved user in the event. Filtering argument. | Optional |
| destMACAddr | Destination MAC address. Filtering argument. | Optional |
| srcMACAddr | Source MAC address. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| FortiSIEM.EventsSearchInit.search_id | String | The ID of the search query that executed against the events. |
Command Example
!fortisiem-event-search query="eventType='ASA-Built-Conn'" from_time=2022-02-10 to_time=2022-02-14
Context Example
{
"FortiSIEM": {
"EventsSearchInit": {
"search_id": "46367,1644934487413"
}
}
}
Human Readable Output
Successfully Initiated search query
Search Id 46367,1644934487413
fortisiem-incident-update
Update attributes of the specified incident. Only the provided attributes are overwritten.
Base Command
fortisiem-incident-update
Input
| Argument Name | Description | Required |
|---|---|---|
| incident_id | The ID of the incident to update. | Required |
| comment | Override incident’s comment. | Optional |
| status | Update incident status. Possible values are: Active, Auto Cleared, Manually Cleared, System Cleared. | Optional |
| external_ticket_type | The type assigned to the incident ticket in an external ticket handling system. Possible values are: Low, Medium, High. | Optional |
| external_ticket_id | The ID of the incident in an external ticket handling system. | Optional |
| external_ticket_state | The state of the incident ticket in an external ticket handling system. Possible values are: New, Assigned, In Progress, Closed. | Optional |
| external_assigned_user | The user that the external ticket is assigned to. | Optional |
Context Output
There is no context output for this command.
Command Example
!fortisiem-incident-update incident_id=102 comment=demo-comment
Human Readable Output
The incident: 102 was successfully updated.
fortisiem-cmdb-devices-list
List CMDB (Centralized Management Database) devices with short information for each device. If you provide one of the exclude arguments, their values are excluded from the provided include arguments. For example, to list all devices in the range 192.168.20.1-192.168.20.100, but exclude 192.168.20.20, 192.168.20.25, use include_ip_range=’192.168.20.1-192.168.20.100’ and exclude_ip=’192.168.20.20, 192.168.20.25’. If no argument is provided, the command retrieves all devices.
Base Command
fortisiem-cmdb-devices-list
Input
| Argument Name | Description | Required |
|---|---|---|
| include_ip | Comma-separated list of IP addresses to include. For example: 1.1.1.1,2.2.2.2. | Optional |
| exclude_ip | Comma-separated list of IP addresses to exclude. For example: 1.1.1.1,2.2.2.2. | Optional |
| include_ip_range | Range of IP addresses to include. For example: 1.1.1.1-1.1.1.255. | Optional |
| exclude_ip_rage | Range of IP addresses to exclude. For example: 1.1.1.1-1.1.1.255. | Optional |
| limit | The number of results to retrieve. Minimum value is 1. Default is 50. | Optional |
| page | The page number of the results to retrieve. Minimum value is 1. Default is 1. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| FortiSIEM.Device.accessIp | String | Device Access IP. |
| FortiSIEM.Device.name | String | Device name. |
| FortiSIEM.Device.naturalId | String | Device unique ID. |
| FortiSIEM.Device.approved | Unknown | Whether or not the device is approved. |
| FortiSIEM.Device.unmanaged | Unknown | Whether or not the device is unmanaged. |
| FortiSIEM.Device.deviceType | Unknown | Device type. |
Command Example
!fortisiem-cmdb-devices-list limit=2 page=1
Context Example
{
"FortiSIEM": {
"Device": [
{
"accessIp": "192.168.30.124",
"approved": "true",
"deviceType": {
"model": "FortiSIEM",
"vendor": "Fortinet",
"version": "ANY"
},
"name": "fortisiem.demo.co",
"naturalId": "fortisiem.demo.co",
"organization": {
"@id": "1",
"@name": "Super"
},
"unmanaged": "false"
},
{
"accessIp": "192.168.30.254",
"approved": "true",
"deviceType": {
"model": "PAN-OS",
"vendor": "Palo Alto",
"version": "ANY"
},
"name": "Palo Alto",
"naturalId": "HOST%2d192.168.30.254",
"organization": {
"@id": "1",
"@name": "Super"
},
"unmanaged": "false"
}
]
}
}
Human Readable Output
List CMDB devices
Showing page 1 out of 1 total pages. Current page size: 2.
Name Access Ip Approved Unmanaged Device Type fortisiem.demo.co 192.168.30.124 true false vendor: Fortinet
model: FortiSIEM
version: ANYPalo Alto 192.168.30.254 true false vendor: Palo Alto
model: PAN-OS
version: ANY
fortisiem-cmdb-device-get
Retrieve full information of the specified devices.
Base Command
fortisiem-cmdb-device-get
Input
| Argument Name | Description | Required |
|---|---|---|
| ips | Comma-separated list of devices IP addresses. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| FortiSIEM.Device.accessIp | String | Device access IP. |
| FortiSIEM.Device.name | String | Device name. |
| FortiSIEM.Device.naturalId | String | Device unique ID. |
| FortiSIEM.Device.approved | Unknown | Whether or not the device is approved. |
| FortiSIEM.Device.unmanaged | Unknown | Whether or not the device is unmanaged. |
| FortiSIEM.Device.deviceType | Unknown | Device type. |
| FortiSIEM.Device.discoverMethod | String | Device discover method. |
| FortiSIEM.Device.discoverTime | Date | When the device was discovered. |
| FortiSIEM.Device.unmanaged | Unknown | Whether or not the device is unmanaged. |
| FortiSIEM.Device.updateMethod | Unknown | The update method of the device. |
Command Example
!fortisiem-cmdb-device-get ips=192.168.30.254
Context Example
{
"FortiSIEM": {
"Device": {
"accessIp": "192.168.30.254",
"approved": "true",
"creationMethod": "LOG",
"deviceType": {
"accessProtocols": "TELNET,SSH",
"category": "Appliance",
"jobWeight": "10",
"model": "PAN-OS",
"vendor": "Palo Alto",
"version": "ANY"
},
"discoverMethod": "LOG",
"discoverTime": "2021-11-23T07:58:48",
"eventParserList": "0",
"name": "Palo Alto",
"naturalId": "HOST%2d192.168.30.254",
"organization": {
"@id": "1",
"@name": "Super"
},
"primaryContactUser": "0",
"secondaryContactUser": "0",
"status": "2",
"unmanaged": "false",
"updateMethod": "MANUAL",
"version": "ANY",
"winMachineGuid": null
}
}
}
Human Readable Output
CMDB device 192.168.30.254
Name Access Ip Approved Unmanaged Device Type Discover Time Discover Method Palo Alto 192.168.30.254 true false accessProtocols: TELNET,SSH
category: Appliance
jobWeight: 10
model: PAN-OS
vendor: Palo Alto
version: ANY2021-11-23T07:58:48 LOG
fortisiem-monitored-organizations-list
List of monitored organizations in service provider deployments.
Base Command
fortisiem-monitored-organizations-list
Input
| Argument Name | Description | Required |
|---|---|---|
| limit | The maximum number of organizations to return. Default is 50. | Optional |
| page | The page number of the results to retrieve. Minimum value is 1. Default is 1. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| FortiSIEM.Organization.id | String | Organization ID. |
| FortiSIEM.Organization.name | String | Organization name. |
| FortiSIEM.Organization.disabled | String | Whether or not the organization is disabled. |
| FortiSIEM.Organization.domainId | String | Domain ID of the organization. |
| FortiSIEM.Organization.initialized | Unknown | Whether or not the organization is initialized. |
Command Example
!fortisiem-monitored-organizations-list limit=2 page=1
Context Example
{
"FortiSIEM": {
"Organization": {
"collectors": {
"collector": [
"EventCollector$null",
"EventCollector$null"
]
},
"creationTime": "2021-11-23T06:58:49",
"custId": "0",
"custProperties": null,
"disabled": "false",
"domainId": "1",
"entityVersion": "1",
"id": "500003",
"initialized": "true",
"lastModified": "2021-11-23T06:59:01",
"name": "Super",
"ownerId": "0",
"xmlId": "Domain$Super"
}
}
}
Human Readable Output
List Monitored Organizations
Showing page 1 out of 1 total pages. Current page size: 2.
Domain Id Name Cust Id Creation Time Last Modified Disabled 1 Super 0 2021-11-23T06:58:49 2021-11-23T06:59:01 false
fortisiem-event-list-by-incident
Lists events by the specified incident ID. Available for FortiSiem version 6.6 and earlier.
Base Command
fortisiem-event-list-by-incident
Input
| Argument Name | Description | Required |
|---|---|---|
| limit | The number of results to retrieve. Minimum value is 1. Default is 50. | Optional |
| incident_id | The incident ID from which the events were triggered. | Required |
| page | The page number of the results to retrieve. Minimum value is 1. Default is 1. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| FortiSIEM.Event.eventType | String | FortiSIEM event type. |
| FortiSIEM.Event.id | String | Event ID. |
| FortiSIEM.Event.receiveTime | Date | The date when the event was received by FortiSIEM. |
| FortiSIEM.Event.attributes | Unknown | Additional attributes of the event. |
| FortiSIEM.Event.nid | String | Event natural ID. |
| FortiSIEM.Event.index | Number | Event index in the list. |
| FortiSIEM.Event.custId | Number | The customer ID the event is related to. |
Command Example
!fortisiem-event-list-by-incident incident_id=102 limit=1 page=1
Context Example
{
"FortiSIEM": {
"Event": {
"attributes": {
"Connection Id": "0",
"Destination Interface SNMP Index": 29034,
"Destination TCP/UDP Port": 53,
"Event ID": 9071234812238931000,
"Event Parse Status": 1,
"Event Receive Time": 1640085152000,
"Event Type": "ASA-Built-Conn",
"External Event Receive Protocol": "NetFlow",
"IP Protocol": 17,
"Organization ID": 1,
"Received Bytes64": 136,
"Received Packets64": 1,
"Relaying IP": "192.168.30.254",
"Reporting Device": "Palo Alto",
"Reporting IP": "192.168.30.254",
"Reporting Model": "ASA",
"Reporting Vendor": "Cisco",
"Source IP": "192.168.1.1",
"Source Interface SNMP Index": 29054,
"Source TCP/UDP Port": 52377,
"System Event Category": 4,
"Total Bytes64": 136,
"Total Flows": 0,
"Total Packets64": 1
},
"custId": 1,
"dataStr": {},
"eventAttributes": [],
"eventType": "ASA-Built-Conn",
"id": 9071234812238931000,
"incidentId": "102",
"index": 0,
"nid": "9071234812238930440",
"rawMessage": null,
"receiveTime": "2021-12-21T11:12:32"
}
}
}
Human Readable Output
List Events Of incident: 102
Showing page 1 out of others that may exist. Current page size: 1.
Id Cust Id Index Event Type Receive Time 9071234812238930440 1 0 ASA-Built-Conn 2021-12-21T11:12:32
fortisiem-watchlist-list
List all watchlists from FortiSIEM database.
Base Command
fortisiem-watchlist-list
Input
| Argument Name | Description | Required |
|---|---|---|
| limit | The maximum number of watchlists to return. Default is 50. | Optional |
| entry_value | The entry value. For example, IP address, username, URL, etc. | Optional |
| page | The page number of the results to retrieve. Minimum value is 1. Default is 1. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| FortiSIEM.Watchlist.isCaseSensitive | Boolean | Whether or not watchlist is considered case sensitive. |
| FortiSIEM.Watchlist.naturalId | String | Watchlist unique ID. |
| FortiSIEM.Watchlist.displayName | String | Display name. |
| FortiSIEM.Watchlist.description | String | Watchlist description. |
| FortiSIEM.Watchlist.valuePattern | String | The value pattern of the watchlist. |
| FortiSIEM.Watchlist.ageOut | Date | Watchlist expiration time. |
| FortiSIEM.Watchlist.topGroup | Boolean | Whether or not the watchlist is top group. |
| FortiSIEM.Watchlist.entries | Unknown | The entries in the watchlist group. |
| FortiSIEM.Watchlist.dataCreationType | String | Watchlist data creation type. |
| FortiSIEM.Watchlist.valueType | String | The type of the values of the entries that reside in the watchlist. |
| FortiSIEM.Watchlist.name | String | Watchlist name. |
| FortiSIEM.Watchlist.id | Number | Watchlist ID. |
Command Example
!fortisiem-watchlist-list limit=1 page=1
Context Example
{
"FortiSIEM": {
"Watchlist": {
"ageOut": "1w",
"custId": 0,
"dataCreationType": null,
"description": "Accounts that lock out frequently",
"displayName": "Accounts Locked",
"entries": [
{
"ageOut": "Never",
"count": null,
"custId": 1,
"dataCreationType": null,
"description": null,
"entryValue": "PVVol_A001_A000356_POWER23",
"expiredTime": 0,
"firstSeen": null,
"id": 1059255,
"lastSeen": null,
"naturalId": "PVVol_A001_A000356_POWER23_1641924540972",
"state": "Enabled",
"triggeringRules": "Datastore Space Warning"
}
],
"id": 500496,
"isCaseSensitive": false,
"name": "PH_DYNLIST_ACCT_LOCKOUT",
"naturalId": "PH_DYNLIST_ACCT_LOCKOUT",
"topGroup": false,
"valuePattern": null,
"valueType": "STRING"
}
}
}
Human Readable Output
List Watchlist Groups
Showing page 1 out of 34 total pages. Current page size: 1.
Id Name Display Name Description Value Type 500496 PH_DYNLIST_ACCT_LOCKOUT Accounts Locked Accounts that lock out frequently STRING
fortisiem-watchlist-get
Get watchlist by the specified watchlist or entry ID.
Base Command
fortisiem-watchlist-get
Input
| Argument Name | Description | Required |
|---|---|---|
| watchlist_ids | Comma-separated list of watchlist group IDs. | Optional |
| entry_id | Comma-separated list of entry IDs that reside in the watchlist. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| FortiSIEM.Watchlist.isCaseSensitive | Boolean | Whether or not watchlist is considered case sensitive. |
| FortiSIEM.Watchlist.naturalId | String | Watchlist unique ID. |
| FortiSIEM.Watchlist.displayName | String | Watchlist display name. |
| FortiSIEM.Watchlist.description | String | Watchlist description. |
| FortiSIEM.Watchlist.valuePattern | Unknown | Watchlist entries value pattern. |
| FortiSIEM.Watchlist.ageOut | Date | Watchlist expiration date. |
| FortiSIEM.Watchlist.topGroup | Boolean | Whether or not the watchlist is top group. |
| FortiSIEM.Watchlist.entries | Unknown | Watchlist entries. |
| FortiSIEM.Watchlist.dataCreationType | Unknown | Data creation type of watchlist. |
| FortiSIEM.Watchlist.valueType | String | Watchlist entries value type. |
| FortiSIEM.Watchlist.name | String | Watchlist name. |
| FortiSIEM.Watchlist.id | Number | Watchlist ID. |
Command Example
!fortisiem-watchlist-get watchlist_ids=500504
Context Example
{
"FortiSIEM": {
"Watchlist": {
"ageOut": "1w",
"custId": 0,
"dataCreationType": null,
"description": "End nodes that are triggered violations - like visiting unauthorized websites, failed Anti-virus updates, P2P traffic etc",
"displayName": "Policy Violators",
"entries": [
{
"ageOut": "Never",
"count": 2,
"custId": 1,
"dataCreationType": "USER",
"description": "test-add-entry",
"entryValue": "10.10.10.10",
"expiredTime": 0,
"firstSeen": "2022-01-01T00:00:00",
"id": 1576443,
"lastSeen": "2022-01-10T00:00:00",
"naturalId": "10.10.10.10_1641772800000",
"state": "Enabled",
"triggeringRules": null
},
{
"ageOut": "Never",
"count": null,
"custId": 1,
"dataCreationType": null,
"description": null,
"entryValue": "1.1.1.1",
"expiredTime": 0,
"firstSeen": null,
"id": 1334351,
"lastSeen": null,
"naturalId": "1.1.1.1_1642502059988",
"state": "Enabled",
"triggeringRules": "Datastore Space Warning"
},
{
"ageOut": "Never",
"count": null,
"custId": 1,
"dataCreationType": null,
"description": null,
"entryValue": "1.1.1.2",
"expiredTime": 0,
"firstSeen": null,
"id": 1334352,
"lastSeen": null,
"naturalId": "1.1.1.2_1642502188543",
"state": "Enabled",
"triggeringRules": "Datastore Space Warning"
},
{
"ageOut": "Never",
"count": null,
"custId": 1,
"dataCreationType": null,
"description": null,
"entryValue": "169.254.230.24",
"expiredTime": 0,
"firstSeen": null,
"id": 1236150,
"lastSeen": null,
"naturalId": "169.254.230.24_1642502028914",
"state": "Enabled",
"triggeringRules": "Datastore Space Warning"
},
{
"ageOut": "Never",
"count": 10,
"custId": 1,
"dataCreationType": "USER",
"description": null,
"entryValue": "7.1.1.10",
"expiredTime": 0,
"firstSeen": "2021-10-07T10:09:29",
"id": 1236141,
"lastSeen": "2021-10-07T10:09:29",
"naturalId": "7.1.1.10_1633601369215",
"state": "Enabled",
"triggeringRules": "Datastore Space Warning"
}
],
"id": 500504,
"isCaseSensitive": false,
"name": "PH_DYNLIST_POL_VIOLATION_ISSUE",
"naturalId": "PH_DYNLIST_POL_VIOLATION_ISSUE",
"topGroup": false,
"valuePattern": null,
"valueType": "IP"
}
}
}
Human Readable Output
Get Watchlist 500504
Id Name Display Name Description Value Type 500504 PH_DYNLIST_POL_VIOLATION_ISSUE Policy Violators End nodes that are triggered violations - such as visiting unauthorized websites, failed Anti-Virus updates, P2P traffic, etc. IP Watchlist Entries
Id State Entry Value Triggering Rules Count First Seen Last Seen 1576443 Enabled 10.10.10.10 2 2022-01-01T00:00:00 2022-01-10T00:00:00 1334351 Enabled 1.1.1.1 Datastore Space Warning 1334352 Enabled 1.1.1.2 Datastore Space Warning 1236150 Enabled 169.254.230.24 Datastore Space Warning 1236141 Enabled 7.1.1.10 Datastore Space Warning 10 2021-10-07T10:09:29 2021-10-07T10:09:29
fortisiem-watchlist-add
Add a watchlist group. You can also add an entry to the watchlist.
Base Command
fortisiem-watchlist-add
Input
| Argument Name | Description | Required |
|---|---|---|
| description | Watchlist description. | Optional |
| display_name | Display name for watchlist group. | Required |
| is_case_sensitive | Whether entry values are case sensitive. Possible values are: false, true. Default is false. | Optional |
| data_creation_type | Which entity created the data. Possible values are: USER, SYSTEM. Default is USER. | Optional |
| value_type | Entries value type. Possible values are: STRING, IP, NUMBER, DATE. Default is STRING. | Optional |
| age_out | The time period after which items expire from the watchlist group if there is no activity during that time. For example, “3 days”, “in 2 weeks”, “1 month”. By default, items never expire from the watchlist. | Optional |
| entry_inclusive | Whether the entry is active. Possible values are: false, true. Default is true. | Optional |
| entry_value | Entry value. | Optional |
| entry_age_out | The time period after which entries expire from the watchlist group if there is no activity during that time. For example, “3 days”, “in 2 weeks”, “1 month”. By default, entries never expire from the watchlist. | Optional |
| entry_count | Entry count. | Optional |
| entry_first_seen | The first time the entry was seen (number, time unit. e.g., 12 hours, 7 days). | Optional |
| entry_last_seen | The last time the entry was seen. For example, “3 days ago”, “1 month”, “2019-10-10T12:22:00”, “2019-10-10”. | Optional |
| entry_trigger_rules | The triggering rules associates with the entry. Should be a comma-separated list of rule names. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| FortiSIEM.Watchlist.isCaseSensitive | Boolean | Whether or not watchlist is considered case sensitive. |
| FortiSIEM.Watchlist.naturalId | String | Watchlist unique ID. |
| FortiSIEM.Watchlist.displayName | String | Watchlist display name. |
| FortiSIEM.Watchlist.description | String | Watchlist description. |
| FortiSIEM.Watchlist.valuePattern | String | Entries value pattern. |
| FortiSIEM.Watchlist.ageOut | String | Watchlist expiration date. |
| FortiSIEM.Watchlist.topGroup | Boolean | Whether or not the watchlist is top group. |
| FortiSIEM.Watchlist.entries | Unknown | Watchlist entries. |
| FortiSIEM.Watchlist.dataCreationType | String | The entity that created the watchlist. |
| FortiSIEM.Watchlist.valueType | String | The value type of the entries in the watchlist. |
| FortiSIEM.Watchlist.name | String | Watchlist name. |
| FortiSIEM.Watchlist.id | Number | Watchlist ID. |
Command Example
!fortisiem-watchlist-add display_name=readme-demo data_creation_type=SYSTEM description="readme-watchlist" value_type=IP
Context Example
{
"FortiSIEM": {
"Watchlist": {
"ageOut": null,
"custId": 1,
"dataCreationType": "USER",
"description": "readme-watchlist",
"displayName": "readme-demo",
"entries": null,
"id": 1244296,
"isCaseSensitive": false,
"name": "PH_SYS_Group_DyWatchList_1644929683070",
"naturalId": "PH_SYS_Group_DyWatchList_1644929683070",
"topGroup": false,
"valuePattern": null,
"valueType": "IP"
}
}
}
Human Readable Output
Added new Watchlist group: readme-demo
id name displayName description valueType 1244296 PH_SYS_Group_DyWatchList_1644929683070 readme-demo readme-watchlist IP
fortisiem-watchlist-entry-add
Add watchlist entry to one or more watchlist groups.
Base Command
fortisiem-watchlist-entry-add
Input
| Argument Name | Description | Required |
|---|---|---|
| watchlist_id | The watchlist ID to add the entry to. | Required |
| inclusive | Whether or not the entry is active. Possible values are: false, true. Default is true. | Optional |
| count | Entry count. | Optional |
| triggering_rules | The triggering rules associated with the entry. Should be a comma-separated list of rules names. | Optional |
| value | The entry value. | Required |
| age_out | The time period after which the entry expires from the watchlist group if there is no activity during that time. For example, “3 days”, “in 2 weeks”, “1 month”. By default, entries never expire from the watchlist. | Optional |
| last_seen | The last time the entry was seen. For example, “3 days ago”, “1 month”, “2019-10-10T12:22:00”, “2019-10-10”. | Optional |
| first_seen | The first time the entry was seen. For example, “3 days ago”, “1 month”, “2019-10-10T12:22:00”, “2019-10-10”. | Optional |
| data_creation_type | Which entity created the data. Possible values are: USER, SYSTEM. Default is USER. | Optional |
| description | Entry description. | Optional |
Context Output
There is no context output for this command.
Command Example
!fortisiem-watchlist-entry-add value=10.10.10.10 watchlist_id=500504 count=2 description=test-add-entry first_seen=2022-01-01 last_seen=2022-01-10
Human Readable Output
Successfully added Entry: 10.10.10.10 to Watchlist: 500504.
fortisiem-watchlist-entry-update
Update watchlist entry. This command overrides all existing values in the entry’s attribute. Fill in all relevant arguments to avoid deletion of data.
Base Command
fortisiem-watchlist-entry-update
Input
| Argument Name | Description | Required |
|---|---|---|
| data_creation_type | Which entity created the data. Possible values are: USER, SYSTEM. Default is USER. | Optional |
| first_seen | The first time the entry was seen. For example, “3 days ago”, “1 month”, “2019-10-10T12:22:00”, “2019-10-10”. | Optional |
| count | Entry count. | Optional |
| triggering_rules | The triggering rules associated with the entry. Should be a comma-separated list of rules names. | Optional |
| description | Entry description. | Optional |
| entry_id | The ID of the entry to update. | Required |
| inclusive | Whether the entry is active. Possible values are: false, true. Default is true. | Optional |
| value | The entry value. | Required |
| expired_time | When the entry was expired (number, time unit. e.g, 12 hours, 7 days). | Optional |
| age_out | The time period after which the entry expires from the watchlist group if there is no activity during that time. For example, “3 days ago”, “in 2 weeks”, “1 month”. By default, the item never expires from the watchlist. | Optional |
| last_seen | The first time the entry was seen. For example, “3 days”, “1 month”, “2019-10-10T12:22:00”, “2019-10-10”. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| FortiSIEM.WatchlistEntry.lastSeen | Date | The last time the entry was seen. |
| FortiSIEM.WatchlistEntry.naturalId | String | Entry unique ID. |
| FortiSIEM.WatchlistEntry.dataCreationType | String | Entry data creation type. |
| FortiSIEM.WatchlistEntry.firstSeen | Date | The first time the entry was seen. |
| FortiSIEM.WatchlistEntry.count | Number | The number of times the entry was seen. |
| FortiSIEM.WatchlistEntry.triggeringRules | String | The triggering rules associated with the entry. |
| FortiSIEM.WatchlistEntry.description | String | Entry description. |
| FortiSIEM.WatchlistEntry.id | Number | Entry ID. |
| FortiSIEM.WatchlistEntry.state | String | Entry state. |
| FortiSIEM.WatchlistEntry.entryValue | String | Entry value. |
| FortiSIEM.WatchlistEntry.expiredTime | Date | When the entry was expired. |
| FortiSIEM.WatchlistEntry.ageOut | String | Expiration date of the entry. |
Command Example
!fortisiem-watchlist-entry-update entry_id=1488255 value=5.5.5.7 count=5
Context Example
{
"FortiSIEM": {
"WatchlistEntry": {
"ageOut": "Never",
"count": 5,
"custId": 1,
"dataCreationType": "USER",
"description": null,
"entryValue": "5.5.5.7",
"expiredTime": 0,
"firstSeen": null,
"id": 1488255,
"lastSeen": null,
"naturalId": "5.5.5.7_1644916470062",
"state": "Enabled",
"triggeringRules": null
}
}
}
Human Readable Output
Successfully Updated Entry: 1488255
Id State Entry Value Triggering Rules Count First Seen Last Seen 1488255 Enabled 5.5.5.7 5
fortisiem-watchlist-entry-delete
Delete entry of watchlist.
Base Command
fortisiem-watchlist-entry-delete
Input
| Argument Name | Description | Required |
|---|---|---|
| entry_ids | Comma-separated list of entry IDs to delete. | Required |
Context Output
There is no context output for this command.
Command Example
!fortisiem-watchlist-entry-delete entry_ids=1488255
Human Readable Output
The entry 1488255 were deleted successfully.
fortisiem-watchlist-delete
Delete watchlist.
Base Command
fortisiem-watchlist-delete
Input
| Argument Name | Description | Required |
|---|---|---|
| watchlist_id | Comma-separated list of watchlist IDs to delete. | Required |
Context Output
There is no context output for this command.
Command Example
!fortisiem-watchlist-delete watchlist_id=1244273
Human Readable Output
The watchlist 1244273 was deleted successfully.
fortisiem-watchlist-entry-get
Get entry by the specified entry ID.
Base Command
fortisiem-watchlist-entry-get
Input
| Argument Name | Description | Required |
|---|---|---|
| entry_ids | Comma-separated list of entry IDs. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| FortiSIEM.WatchlistEntry.lastSeen | Date | The last time the entry was seen. |
| FortiSIEM.WatchlistEntry.naturalId | String | Entry unique ID. |
| FortiSIEM.WatchlistEntry.dataCreationType | String | Entry data creation type. |
| FortiSIEM.WatchlistEntry.firstSeen | Date | The first time the entry was seen. |
| FortiSIEM.WatchlistEntry.count | Number | The number of times the entry was seen. |
| FortiSIEM.WatchlistEntry.triggeringRules | String | The triggering rules associated with the entry. |
| FortiSIEM.WatchlistEntry.description | String | Entry description. |
| FortiSIEM.WatchlistEntry.id | Number | Entry ID. |
| FortiSIEM.WatchlistEntry.state | String | Entry state. |
| FortiSIEM.WatchlistEntry.entryValue | String | Entry value. |
| FortiSIEM.WatchlistEntry.expiredTime | Date | When the entry was expired. |
| FortiSIEM.WatchlistEntry.ageOut | String | Expiration date of the entry. |
Command Example
!fortisiem-watchlist-entry-get entry_ids=1576423
Context Example
{
"FortiSIEM": {
"WatchlistEntry": {
"ageOut": "1w",
"count": 1,
"custId": 1,
"dataCreationType": null,
"description": null,
"entryValue": "192.168.91.3",
"expiredTime": "2022-02-20T10:42:30",
"firstSeen": "2022-01-04T12:43:00",
"id": 1576423,
"lastSeen": "2022-02-13T10:42:30",
"naturalId": "192.168.91.3_1644748950000",
"state": "Enabled",
"triggeringRules": "Sudden Increase in ICMP Requests From A Host"
}
}
}
Human Readable Output
Get Watchlist Entry: 1576423
Id State Entry Value Triggering Rules Count First Seen Last Seen 1576423 Enabled 192.168.91.3 Sudden Increase in ICMP Requests From A Host 1 2022-01-04T12:43:00 2022-02-13T10:42:30
fortisiem-event-search-results
The results of the specified search ID.
Base Command
fortisiem-event-search-results
Input
| Argument Name | Description | Required |
|---|---|---|
| search_id | The ID of the search query to retrieve its results. | Required |
| limit | Maximum number of results to return. Default is 50. | Optional |
| page | The page number to retrieve. Default is 1. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| FortiSIEM.Event.custId | Number | The customer ID the event is related to. |
| FortiSIEM.Event.index | Number | The position number of the event in the results. |
| FortiSIEM.Event.id | String | Event ID. |
| FortiSIEM.Event.eventType | String | The event type. |
| FortiSIEM.Event.receiveTime | Date | When the event was received in UTC time. |
| FortiSIEM.Event.nid | String | The event ID. |
| FortiSIEM.Event.attributes | Unknown | Additional attributes of the event. |
Breaking changes from the previous version of this integration - FortiSIEM v2
The following sections list the changes in this version.
Commands
The following commands were removed in this version
fortisiem-get-events-by-incident - this command was replaced by fortisiem-event-list-by-incident.
fortisiem-clear-incident - this command was replaced by fortisiem-incident-update.
fortisiem-get-events-by-filter - this command was replaced by fortisiem-event-search-status.
fortisiem-get-cmdb-devices - this command was replaced by fortisiem-cmdb-devices-list.
fortisiem-get-events-by-query - this command was replaced by fortisiem-event-search-status.
fortisiem-get-lists .
fortisiem-add-item-to-resource-list.
fortisiem-remove-item-from-resource-list.
fortisiem-get-resource-list.
Additional Considerations for this version
The following commands were added in this version
fortisiem-watchlist-list
fortisiem-watchlist-get
fortisiem-watchlist-add
fortisiem-watchlist-entry-add
fortisiem-watchlist-entry-update
fortisiem-watchlist-delete
fortisiem-watchlist-entry-delete
fortisiem-watchlist-entry-get
The fetch incidents command can also fetch triggered events
Configuration parameters
url— Server URL (required)credentials— Username (required)max_fetch— Maximum incidents per fetch.first_fetch— First fetch timestamp (<number> <time unit>, e.g., 12 hours, 7 days).status— Filter incidents by status.fetch_mode— Fetch Modemax_events_fetch— Maximum events to fetch per incident.proxy— Use system proxy settingsinsecure— Trust any certificate (not secure)incidentType— Incident typeincidentFetchInterval— Incidents Fetch IntervalisFetch— Fetch incidents
Commands (16)
-
fortisiem-cmdb-device-getRetrieve full information of the specified devices.
-
fortisiem-cmdb-devices-listList CMDB (Centralized Management Database) devices with short information for each device. If you provide one of the exclude arguments, their values are excluded from the provided include arguments. For example, to list all devices in the range 192.168.20.1-192.168.20.100, but exclude 192.168.20.20, 192.168.20.25, use include_ip_range='192.168.20.1-192.168.20.100' and exclude_ip='192.168.20.20, 192.168.20.25'. If no argument is provided, the command retrieves all devices.
-
fortisiem-event-list-by-incidentLists events by the specified incident ID. Available for FortiSiem version 6.6 and earlier.
-
fortisiem-event-searchInitiate search process on events. The events are retrieved according to a constraint determined either by the query argument or by the filtering arguments. When using filtering arguments, an 'AND' operator is used between them. If the query argument is filled, it overrides the values in the filtering arguments.
-
fortisiem-event-search-resultsThe results of the specified search ID.
-
fortisiem-event-search-statusThe status of the specified search ID.
-
fortisiem-incident-updateUpdate attributes of the specified incident. Only the provided attributes are overwritten.
-
fortisiem-monitored-organizations-listList of monitored organizations in service provider deployments.
-
fortisiem-watchlist-addAdd a watchlist group. You can also add an entry to the watchlist.
-
fortisiem-watchlist-deleteDelete watchlist.
-
fortisiem-watchlist-entry-addAdd watchlist entry to one or more watchlist groups.
-
fortisiem-watchlist-entry-deleteDelete entry of watchlist.
-
fortisiem-watchlist-entry-getGet entry by the specified entry ID.
-
fortisiem-watchlist-entry-updateUpdate watchlist entry. This command overrides all existing values in the entry's attribute. Fill in all relevant arguments to avoid deletion of data.
-
fortisiem-watchlist-getGet watchlist by the specified watchlist or entry ID.
-
fortisiem-watchlist-listList all watchlists from FortiSIEM database.
import json from unittest.mock import patch import defusedxml.ElementTree as defused_ET import demistomock as demisto # noqa: F401 import pytest from CommonServerPython import * # noqa: F401 from freezegun import freeze_time """MOCK PARAMETERS """ SERVER_URL = "https://192.168.30.124" BASE_URL = f"{SERVER_URL}/phoenix/rest" USERNAME = "TEST_USERNAME" PASSWORD = "XXXX" QUERY = "eventId=123" IP_ADDRESS_1 = "1.1.1.1" IP_ADDRESS_2 = "2.2.2.2" def load_json_mock_response(file_name: str) -> dict: """ Load mock file that simulates an API response. Args: file_name (str): Name of the mock response JSON file to return. Returns: str: Mock file content. """ with open(f"test_data/{file_name}", encoding="utf-8") as mock_file: return json.loads(mock_file.read()) def load_xml_mock_response(file_name: str) -> str: """ Load mock file in XML format that simulates an API response. Args: file_name (str): Name of the mock response XML file to return. Returns: str: Mock file content. """ file_path = f"test_data/{file_name}" top = defused_ET.parse(file_path) return ET.tostring(top.getroot(), encoding="utf8").decode("utf-8") def mock_client(): from FortiSIEMV2 import FortiSIEMClient return FortiSIEMClient(SERVER_URL, True, False, {}, (USERNAME, PASSWORD)) @pytest.mark.parametrize( "mock_response_file,command_arguments,expected_devices_number,expected_device_name", [ ("list_devices_1.xml", {"include_ip_list": f"{IP_ADDRESS_1},{IP_ADDRESS_2}"}, 2, "DEVICE_1"), ("list_devices_1.xml", {}, 2, "DEVICE_1"), ( "list_devices_2.xml", {"include_ip_list": f"{IP_ADDRESS_1}-{IP_ADDRESS_2}", "exclude_ip_list": f"{IP_ADDRESS_1}"}, 1, "DEVICE_2", ), ], ) def test_cmdb_devices_list(mock_response_file, command_arguments, expected_devices_number, expected_device_name, requests_mock): """ Scenario: List CMDB devices. Given: - User has provided valid credentials. - User may provided list/range of include/exclude IP addresses. When: - fortisiem-cmdb-device-list command called. Then: - Ensure number of items is correct. - Ensure outputs prefix is correct. - Validate outputs' fields. """ from FortiSIEMV2 import cmdb_devices_list_command client = mock_client() mock_response = load_xml_mock_response(mock_response_file) requests_mock.get(f"{client._base_url}cmdbDeviceInfo/devices", text=mock_response) result = cmdb_devices_list_command(client, command_arguments) outputs = result.outputs assert len(outputs) == expected_devices_number assert outputs[0]["name"] == expected_device_name def test_cmdb_device_get(requests_mock): """ Scenario: Get CMDB device. Given: - User has provided valid credentials. - User has provided valid IP address. When: - fortisiem-cmdb-device-get command called. Then: - Ensure outputs prefix is correct. - Validate outputs' fields. """ from FortiSIEMV2 import FortiSIEMClient, cmdb_device_get_command client: FortiSIEMClient = mock_client() mock_response = load_xml_mock_response("get_device.xml") requests_mock.get(f"{client._base_url}cmdbDeviceInfo/device", text=mock_response) results = cmdb_device_get_command(client, {"ips": IP_ADDRESS_1}) outputs = results[0].outputs assert outputs[0]["name"] == "DEVICE_1" assert outputs[0]["accessIp"] == IP_ADDRESS_1 def test_monitored_organizations_list(requests_mock): """ Scenario: List Organizations. Given: - User has provided valid credentials. When: - fortisiem-monitored-organizations-list command called. Then: - Ensure number of items is correct. - Ensure outputs prefix is correct. - Validate outputs' fields. """ from FortiSIEMV2 import FortiSIEMClient, monitored_organizations_list_command client: FortiSIEMClient = mock_client() mock_response = load_xml_mock_response("list_organizations.xml") requests_mock.get(f"{client._base_url}config/Domain", text=mock_response) result = monitored_organizations_list_command(client, {"limit": 2}) outputs = result.outputs assert len(outputs) == 1 assert outputs[0]["custId"] == "0" assert outputs[0]["id"] == "500003" @pytest.mark.parametrize( "command_arguments,expected_response,expected_msg", [({"incident_id": "123", "comment": "test-success"}, "OK", "successfully updated")], ) def test_update_incident(command_arguments, expected_response, expected_msg, requests_mock): """ Scenario: Update incident. Given: - User has provided valid credentials. - User provided incident ID. - User provided comment. When: - fortisiem-incident-update command called. Then: - Validate outputs' fields. """ from FortiSIEMV2 import FortiSIEMClient, incident_update_command client: FortiSIEMClient = mock_client() requests_mock.post(f"{client._base_url}incident/external", json=expected_response) result = incident_update_command(client, command_arguments) outputs = result.readable_output assert expected_msg in outputs def test_list_events_by_incident(requests_mock): """ Scenario: List triggered events. Given: - User has provided valid credentials. - User has provided incident ID. When: - fortisiem-event-list-by-incident command called. Then: - Ensure number of items is correct. - Ensure outputs prefix is correct. - Validate outputs' fields. """ from FortiSIEMV2 import FortiSIEMClient, events_list_command client: FortiSIEMClient = mock_client() mock_response = load_json_mock_response("list_events_by_incident.json") requests_mock.get(f"{client._base_url}pub/incident/triggeringEvents", json=mock_response) result = events_list_command(client, {"limit": 2, "incident_id": 123}) outputs = result.outputs assert len(outputs) == 2 assert outputs[0]["id"] == "1111" assert outputs[0]["attributes"]["Reporting IP"] == "192.168.1.1" assert outputs[1]["id"] == "9071234812007542512" assert outputs[1]["attributes"]["Reporting IP"] == "192.168.1.2" @pytest.mark.parametrize( "command_arguments,response_file,suffix_url,watchlist_number,watchlist_id,watchlist_display_name", [ ({"limit": "2"}, "list_watchlist.json", "watchlist/all", 2, 111, "Accounts Locked"), ({"entry_value": "192.168.1.1"}, "list_watchlist2.json", "watchlist/value", 1, 112, "Port Scanners"), ], ) def test_list_watchlist( command_arguments, response_file, suffix_url, watchlist_number, watchlist_id, watchlist_display_name, requests_mock ): """ Scenario: List Watchlist groups. Given: - User has provided valid credentials. When: - fortisiem-watchlist-list command called. Then: - Ensure number of items is correct. - Ensure outputs prefix is correct. - Validate outputs' fields. """ from FortiSIEMV2 import FortiSIEMClient, watchlist_list_command client: FortiSIEMClient = mock_client() mock_response = load_json_mock_response(response_file) requests_mock.get(f"{client._base_url}{suffix_url}", json=mock_response) result = watchlist_list_command(client, command_arguments) outputs = result.outputs assert len(outputs) == watchlist_number assert result.outputs_prefix == "FortiSIEM.Watchlist" assert outputs[0]["id"] == watchlist_id assert outputs[0]["displayName"] == watchlist_display_name @pytest.mark.parametrize( "command_arguments,response_file,suffix_url,watchlist_id,watchlist_display_name", [ ({"watchlist_ids": "111"}, "get_watchlist.json", "watchlist/111", 111, "Accounts Locked"), ({"entry_id": "55555"}, "list_watchlist2.json", "watchlist/byEntry/55555", 112, "Port Scanners"), ], ) def test_get_watchlist(command_arguments, response_file, suffix_url, watchlist_id, watchlist_display_name, requests_mock): """ Scenario: Get Watchlist group. Given: - User has provided valid credentials. When: - fortisiem-watchlist-get command called. Then: - Ensure number of items is correct. - Ensure outputs prefix is correct. - Validate outputs' fields. """ from FortiSIEMV2 import FortiSIEMClient, watchlist_get_command client: FortiSIEMClient = mock_client() mock_response = load_json_mock_response(response_file) requests_mock.get(f"{client._base_url}{suffix_url}", json=mock_response) results = watchlist_get_command(client, command_arguments) outputs = results[0].outputs assert results[0].outputs_prefix == "FortiSIEM.Watchlist" assert outputs[0]["id"] == watchlist_id assert outputs[0]["displayName"] == watchlist_display_name def test_add_watchlist(requests_mock): """ Scenario: Add Watchlist group. Given: - User has provided valid credentials. When: - fortisiem-watchlist-add command called. Then: - Ensure outputs prefix is correct. - Validate outputs' fields. """ from FortiSIEMV2 import FortiSIEMClient, watchlist_add_command client: FortiSIEMClient = mock_client() mock_response = load_json_mock_response("add_watchlist.json") requests_mock.post(f"{client._base_url}watchlist/save", json=mock_response) result = watchlist_add_command( client, { "description": "Servers, network or storage devices", "display_name": "Resource Issues Test4", "is_case_sensitive": False, "data_creation_type": "USER", "value_type": "STRING", "entry_inclusive": "true", }, ) outputs = result.outputs assert result.outputs_prefix == "FortiSIEM.Watchlist" assert outputs[0]["id"] == 111 assert outputs[0]["displayName"] == "Resource Issues Test4" assert outputs[0]["description"] == "Servers, network or storage devices" assert outputs[0]["valueType"] == "STRING" def test_add_entry(requests_mock): """ Scenario: Add Entry. Given: - User has provided valid credentials. When: - fortisiem-watchlist-entry-add command called. Then: - Ensure outputs prefix is correct. - Validate outputs' fields. """ from FortiSIEMV2 import FortiSIEMClient, watchlist_entry_add_command client: FortiSIEMClient = mock_client() mock_response = load_json_mock_response("add_entry.json") requests_mock.post(f"{client._base_url}watchlist/addTo", json=mock_response) result = watchlist_entry_add_command( client, { "watchlist_id": 500496, "value": "192.168.1.1", "inclusive": True, "count": 2, }, ) readable_output = result.readable_output assert readable_output.startswith("Successfully added Entry") def test_update_entry(requests_mock): """ Scenario: Update Entry. Given: - User has provided valid credentials. When: - fortisiem-watchlist-entry-update command called. Then: - Ensure outputs prefix is correct. - Validate outputs' fields. """ from FortiSIEMV2 import FortiSIEMClient, watchlist_entry_update_command client: FortiSIEMClient = mock_client() mock_response = load_json_mock_response("update_entry.json") requests_mock.post(f"{client._base_url}watchlist/entry/save", json=mock_response) result = watchlist_entry_update_command( client, { "data_creation_type": "USER", "count": 100, "triggering_rules": "Datastore Space Warning", "description": "Testing again", "entry_id": 889400, "inclusive": True, "value": "PVVol_A001_A000356_POWER23", }, ) outputs = result.outputs assert result.outputs_prefix == "FortiSIEM.WatchlistEntry" assert outputs[0]["id"] == 889400 assert outputs[0]["state"] == "Enabled" assert outputs[0]["triggeringRules"] == "Datastore Space Warning" assert outputs[0]["dataCreationType"] == "USER" assert outputs[0]["description"] == "Testing again" def test_delete_watchlist(requests_mock): """ Scenario: Delete Watchlist. Given: - User has provided valid credentials. When: - fortisiem-watchlist-delete command called. Then: - Validate outputs' fields. """ from FortiSIEMV2 import FortiSIEMClient, watchlist_delete_command client: FortiSIEMClient = mock_client() mock_response = load_json_mock_response("delete_watchlist.json") requests_mock.post(f"{client._base_url}watchlist/delete", json=mock_response) results = watchlist_delete_command(client, {"watchlist_id": 111}) readable_output = results[0].readable_output assert readable_output == "The watchlist 111 was deleted successfully." def test_delete_entry(requests_mock): """ Scenario: Delete entry. Given: - User has provided valid credentials. When: - fortisiem-watchlist-entry-delete command called. Then: - Validate outputs' fields. """ from FortiSIEMV2 import FortiSIEMClient, watchlist_entry_delete_command client: FortiSIEMClient = mock_client() mock_response = load_json_mock_response("delete_entry.json") requests_mock.post(f"{client._base_url}watchlist/entry/delete", json=mock_response) results = watchlist_entry_delete_command(client, {"entry_ids": 11111}) readable_output = results[0].readable_output assert readable_output == "The entry 11111 were deleted successfully." def test_get_entry(requests_mock): """ Scenario: Get entry. Given: - User has provided valid credentials. - User has provided entry ID. When: - fortisiem-watchlist-entry-get command called. Then: - Ensure number of items is correct. - Ensure outputs prefix is correct. - Validate outputs' fields. """ from FortiSIEMV2 import FortiSIEMClient, watchlist_entry_get_command client: FortiSIEMClient = mock_client() mock_response = load_json_mock_response("get_entry.json") entry_id = 55555 requests_mock.get(f"{client._base_url}watchlist/entry/{entry_id}", json=mock_response) results = watchlist_entry_get_command(client, {"entry_ids": entry_id}) outputs = results[0].outputs assert results[0].outputs_prefix == "FortiSIEM.WatchlistEntry" assert len(outputs) == 1 assert outputs[0]["id"] == entry_id def test_events_search_init(requests_mock): """ Scenario: Initiate events search query.. Given: - User has provided valid credentials. - User has provided valid query. When: - fortisiem-event-search command called. Then: - Ensure number of items is correct. - Ensure outputs prefix is correct. - Validate outputs' fields. """ from FortiSIEMV2 import FortiSIEMClient, events_search_init_command client: FortiSIEMClient = mock_client() query_id = "10931,1638796483313" query = "eventId='9071234812007542512'" from_time = "2021-10-12" to_time = "2022=-02-10" requests_mock.post(f"{client._base_url}query/eventQuery", text=query_id) results = events_search_init_command( client, { "query": query, "from_time": from_time, "to_time": to_time, }, ) outputs = results.outputs assert results.outputs_prefix == "FortiSIEM.EventsSearchInit" assert len(outputs) == 1 assert outputs["search_id"] == query_id def test_events_search_status(requests_mock): """ Scenario: Get events search query status. Given: - User has provided valid credentials. - User has provided valid search ID. When: - fortisiem-event-search-status command called. Then: - Ensure number of items is correct. - Ensure outputs prefix is correct. - Validate outputs' fields. """ from FortiSIEMV2 import FortiSIEMClient, events_search_status_command client: FortiSIEMClient = mock_client() search_id = "10931,1638796483313" mock_response = "100" requests_mock.get(f"{client._base_url}query/progress/{search_id}", text=mock_response) results = events_search_status_command(client, {"search_id": search_id}) outputs = results.outputs assert results.outputs_prefix == "FortiSIEM.EventsSearchStatus" assert len(outputs) == 2 assert outputs["percentage_status"] == mock_response def test_events_search_results(requests_mock): """ Scenario: Retrieve the events that returned from the specified search query ID. Given: - User has provided valid credentials. - User has provided search ID. When: - 'fortisiem-event-search-results' command called. Then: - Ensure number of items is correct. - Ensure outputs prefix is correct. - Validate outputs' fields. """ from FortiSIEMV2 import FortiSIEMClient, events_search_results_command client: FortiSIEMClient = mock_client() mock_response = load_xml_mock_response("list_events_via_search_query.xml") start_index = 0 limit = 1 search_id = "47189,1638796483313" requests_mock.get(f"{client._base_url}query/events/{search_id}/{start_index}/{limit}", text=mock_response) result = events_search_results_command(client, {"search_id": search_id, "limit": limit, "page": 1}) outputs = result.outputs assert len(outputs) == 1 assert result.outputs_prefix == "FortiSIEM.Event" assert outputs[0]["id"] == "9071234812100595667" assert outputs[0]["attributes"]["reptDevIpAddr"] == "192.168.1.1" @pytest.mark.parametrize( "incident_attrib,original_key,formatted_key", [("incidentTarget", "hostName", "target_hostName"), ("incidentSrc", "hostIpAddr", "source_ipAddr")], ) def test_build_readable_attribute_key(incident_attrib, original_key, formatted_key): """ Scenario: Formatting nested attribute name to be more readable, and convenient to display in fetch incident command. For the input of "srcIpAddr", "incidentSrc" the formatted key will be: "source_ipAddr". Given: - Incident attribute name. - The nested key of the value the resides in the incident attribute. When: - During fetch incidents command is invoked. Then: - Validate method's output. """ from FortiSIEMV2 import build_readable_attribute_key result = build_readable_attribute_key(original_key, incident_attrib) assert result == formatted_key @pytest.mark.parametrize( "args,expected_output", [ ( {"extended_data": True, "eventId": "111", "eventType": "ASA-Built-Conn"}, 'eventId = "111" AND eventType = "ASA-Built-Conn"', ), ({"query": "eventId!=111", "extended_data": False}, ""), ], ) def test_build_constraint_from_args(args, expected_output): """ Building a constraint for the search query. Given: - 'fortisiem-event-search' arguments. When: - 'fortisiem-event-search' command called. Then: - Validate method's output. """ from FortiSIEMV2 import build_constraint_from_args result = build_constraint_from_args(args) assert result == expected_output @pytest.mark.commands @freeze_time(time.ctime(1646205070)) @pytest.mark.parametrize( "last_run,incidents_file,fetch_with_events,expected_output", [ ( {}, "fetch_incidents.json", False, { "incidents_number": 10, "events_number": 0, "last_run": {"create_time": 1646105070000, "last_incidents": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], "start_index": 0}, }, ), ( {"create_time": 1646094600000, "last_incidents": [1, 2, 3, 4, 5], "start_index": 0}, "fetch_incidents.json", False, { "incidents_number": 5, "events_number": 0, "last_run": {"create_time": 1646105070000, "last_incidents": [6, 7, 8, 9, 10], "start_index": 0}, }, ), ( {"create_time": 1646105070000, "last_incidents": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], "start_index": 0}, "fetch_incidents_empty.json", False, { "incidents_number": 0, "events_number": 0, "last_run": {"create_time": 1646105070000, "last_incidents": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], "start_index": 0}, }, ), ( {}, "fetch_incidents.json", True, { "incidents_number": 10, "events_number": 5, "last_run": {"create_time": 1646105070000, "last_incidents": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], "start_index": 0}, }, ), ( {"last_incidents": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], "start_index": 0, "create_time": 1646092830000}, "fetch_incidents_same_time.json", False, { "incidents_number": 5, "events_number": 0, "last_run": { "last_incidents": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], "start_index": 5, "create_time": 1646092830000, }, }, ), ], ) def test_fetch_incidents(last_run, incidents_file, fetch_with_events, expected_output, requests_mock): """ Fetching incidents. Given: - 'fetch-incidents' arguments. Scenarios: - Last run do not exist. - Last run exists - No incidents to fetch. - Incidents to fetch with events. - New incidents came in the same time like prev last incidents. Then: - Validate incidents & updated last run obj. """ from FortiSIEMV2 import FortiSIEMClient, fetch_incidents client: FortiSIEMClient = mock_client() status_list = ["Active"] max_fetch = 10 max_events_fetch = 5 first_fetch = "1 week" mock_response = load_json_mock_response(incidents_file) requests_mock.post(f"{client._base_url}pub/incident", json=mock_response) if fetch_with_events: events_mock_response = load_json_mock_response("triggered_events.json") requests_mock.get(f"{client._base_url}pub/incident/triggeringEvents", json=events_mock_response) incidents, updated_last_run = fetch_incidents( client, max_fetch, first_fetch, status_list, fetch_with_events, max_events_fetch, last_run ) expected_incidents_number = expected_output.get("incidents_number") expected_events_number = expected_output.get("events_number") expected_last_run = expected_output.get("last_run") incident_raw_json = json.loads(incidents[0]["rawJSON"]) if incidents else {} events = incident_raw_json.get("events") events_number = len(events) if events else 0 assert len(incidents) == expected_incidents_number assert updated_last_run == expected_last_run assert events_number == expected_events_number @pytest.mark.commands @freeze_time(time.ctime(1646240070)) @patch("FortiSIEMV2.FortiSIEMClient.fetch_incidents_request") def test_fetch_incidents_with_pagination(post_mock): """ Fetching incidents in pagination use case. Given: - 'fetch-incidents' arguments. Scenarios: - Fetch incidents which retrieved from different pages. Then: - Validate incidents & updated last run obj. """ from FortiSIEMV2 import FortiSIEMClient, fetch_incidents client: FortiSIEMClient = mock_client() status_list = ["Active"] max_fetch = 5 max_events_fetch = 5 first_fetch = "3 hours" mocked_responses = [ load_json_mock_response("fetch_incidents_paging_1.json"), load_json_mock_response("fetch_incidents_paging_2.json"), ] post_mock.side_effect = mocked_responses incidents, updated_last_run = fetch_incidents(client, max_fetch, first_fetch, status_list, False, max_events_fetch, {}) assert len(incidents) == 5 assert updated_last_run["create_time"] == 1646237070000 assert updated_last_run["last_incidents"] == [9, 10, 11, 12, 13] @pytest.mark.parametrize( "nested_attr, expected_result", [ ("key:value", ("key", "value")), ("key:value:extra", ("key", "value")), ("", (None, None)), ("key", (None, None)), ], ) def test_format_nested_incident_attribute(nested_attr, expected_result): """ Formatting incident attributes. Given: - Some incident attributes. When: - format_nested_incident_attribute is running. Then: - Check that the formatted incident attribute is as expected. """ from FortiSIEMV2 import format_nested_incident_attribute assert format_nested_incident_attribute(nested_attr) == expected_result @pytest.mark.parametrize( "events_mock_response, expected_result", [ ({"result": {"description": "The incident detail was not found for incident 123465"}}, 0), (load_json_mock_response("triggered_events.json"), 5), (load_json_mock_response("triggered_events_dict.json"), 5), ], ) def test_get_related_events_for_fetch_command(events_mock_response, expected_result, requests_mock): """ Fetching events per incident. Given: - Incident ID with/without events. When: - get_related_events_for_fetch_command is running. Then: - Check that the sum of the events is as expected. """ from FortiSIEMV2 import FortiSIEMClient, get_related_events_for_fetch_command client: FortiSIEMClient = mock_client() requests_mock.get(f"{client._base_url}pub/incident/triggeringEvents", json=events_mock_response) assert len(get_related_events_for_fetch_command("123456", 20, client)) == expected_result @pytest.mark.commands @freeze_time(time.ctime(1646205070)) def test_fetch_incidents_without_incident_title(requests_mock): """ Fetching incidents. Given: - 'fetch-incidents' arguments. Scenarios: - Last run do not exist. - Last run exists - No incidents to fetch. - Incidents to fetch with events. - New incidents came in the same time like prev last incidents. Then: - Validate incidents & updated last run obj. """ incidents_file = "fetch_incidents_without_incidentTitle.json" expected_output = { "incidents_number": 1, "events_number": 0, "last_run": {"create_time": 1646092830000, "last_incidents": [1], "start_index": 0}, } from FortiSIEMV2 import FortiSIEMClient, fetch_incidents client: FortiSIEMClient = mock_client() status_list = ["Active"] max_fetch = 1 max_events_fetch = 5 first_fetch = "1 week" mock_response = load_json_mock_response(incidents_file) requests_mock.post(f"{client._base_url}pub/incident", json=mock_response) incidents, updated_last_run = fetch_incidents(client, max_fetch, first_fetch, status_list, False, max_events_fetch, {}) expected_incidents_number = expected_output.get("incidents_number") expected_events_number = expected_output.get("events_number") expected_last_run = expected_output.get("last_run") incident_raw_json = json.loads(incidents[0]["rawJSON"]) if incidents else {} events = incident_raw_json.get("events") events_number = len(events) if events else 0 assert len(incidents) == expected_incidents_number assert updated_last_run == expected_last_run assert incidents[0].get("name") == "FortiSIEM incident: 1" assert events_number == expected_events_number def test_events_list_request_with_time_params_success(requests_mock): """ Testing events_list_request with timeFrom/timeTo parameters - success path. Given: - An incident ID and time_from/time_to parameters are provided. When: - events_list_request is called with time_from and time_to. Then: - The request is made with timeFrom and timeTo query params. - The response is returned correctly. """ from FortiSIEMV2 import FortiSIEMClient client: FortiSIEMClient = mock_client() mock_response = load_json_mock_response("list_events_by_incident.json") incident_id = "123" time_from = 1646092830000 time_to = 1646147610000 adapter = requests_mock.get(f"{client._base_url}pub/incident/triggeringEvents", json=mock_response) result = client.events_list_request(size=10, incident_id=incident_id, time_from=time_from, time_to=time_to) assert result == mock_response assert adapter.called_once # Verify the request was made with time params assert adapter.last_request.qs["timefrom"] == [str(time_from)] assert adapter.last_request.qs["timeto"] == [str(time_to)] assert adapter.last_request.qs["incidentid"] == [incident_id] def test_events_list_request_with_time_params_fallback(requests_mock): """ Testing events_list_request with timeFrom/timeTo parameters - fallback path. Given: - An incident ID and time_from/time_to parameters are provided. - The first request (with time params) raises an exception. When: - events_list_request is called with time_from and time_to. Then: - The first request with time params fails. - A fallback request without time params is made and succeeds. - The fallback response is returned correctly. """ from FortiSIEMV2 import FortiSIEMClient client: FortiSIEMClient = mock_client() mock_response = load_json_mock_response("list_events_by_incident.json") incident_id = "123" time_from = 1646092830000 time_to = 1646147610000 # First call raises an exception (with time params), second call succeeds (without time params) responses = [ {"status_code": 500, "json": {"error": "timeFrom/timeTo not supported"}}, {"status_code": 200, "json": mock_response}, ] adapter = requests_mock.get(f"{client._base_url}pub/incident/triggeringEvents", responses) result = client.events_list_request(size=10, incident_id=incident_id, time_from=time_from, time_to=time_to) assert result == mock_response # Verify both requests were attempted assert adapter.call_count == 2 # First request should have time params first_request = adapter.request_history[0] assert "timefrom" in first_request.qs assert "timeto" in first_request.qs # Second request (fallback) should NOT have time params second_request = adapter.request_history[1] assert "timefrom" not in second_request.qs assert "timeto" not in second_request.qs def test_events_list_request_without_time_params(requests_mock): """ Testing events_list_request without timeFrom/timeTo parameters - original behavior. Given: - An incident ID is provided but no time_from/time_to parameters. When: - events_list_request is called without time_from and time_to. Then: - The request is made without timeFrom and timeTo query params. - The original behavior is preserved. """ from FortiSIEMV2 import FortiSIEMClient client: FortiSIEMClient = mock_client() mock_response = load_json_mock_response("list_events_by_incident.json") incident_id = "123" adapter = requests_mock.get(f"{client._base_url}pub/incident/triggeringEvents", json=mock_response) result = client.events_list_request(size=10, incident_id=incident_id) assert result == mock_response assert adapter.called_once # Verify the request was made WITHOUT time params assert "timefrom" not in adapter.last_request.qs assert "timeto" not in adapter.last_request.qs assert adapter.last_request.qs["incidentid"] == [incident_id] @patch("FortiSIEMV2.FortiSIEMClient.events_list_request") def test_get_related_events_for_fetch_command_with_time_params(mock_events_list_request): """ Testing get_related_events_for_fetch_command with time_from and time_to parameters. Given: - An incident ID, max_events_fetch, and time_from/time_to parameters are provided. When: - get_related_events_for_fetch_command is called with time_from and time_to. Then: - events_list_request is called with the time_from and time_to parameters forwarded. """ from FortiSIEMV2 import FortiSIEMClient, get_related_events_for_fetch_command client: FortiSIEMClient = mock_client() mock_events_list_request.return_value = load_json_mock_response("triggered_events.json") time_from = 1646092830000 time_to = 1646147610000 result = get_related_events_for_fetch_command("123456", 20, client, time_from=time_from, time_to=time_to) assert len(result) == 5 mock_events_list_request.assert_called_once_with(20, "123456", time_from=time_from, time_to=time_to) @pytest.mark.commands @freeze_time(time.ctime(1646205070)) @patch("FortiSIEMV2.FortiSIEMClient.events_list_request") def test_fetch_incidents_passes_time_params_to_events(mock_events_list_request, requests_mock): """ Testing that fetch_incidents passes incidentFirstSeen/incidentLastSeen (with 5-minute buffer) as time_from/time_to to events_list_request when fetching with events. Given: - fetch_with_events is True. - Incidents have incidentFirstSeen and incidentLastSeen fields. When: - fetch_incidents is called. Then: - events_list_request is called with time_from=incidentFirstSeen - EVENTS_TIME_BUFFER_MS and time_to=incidentLastSeen + EVENTS_TIME_BUFFER_MS for each incident. """ from FortiSIEMV2 import EVENTS_TIME_BUFFER_MS, FortiSIEMClient, fetch_incidents client: FortiSIEMClient = mock_client() status_list = ["Active"] max_fetch = 2 max_events_fetch = 5 first_fetch = "1 week" mock_response = load_json_mock_response("fetch_incidents.json") # Limit to 2 incidents for simplicity mock_response["data"] = mock_response["data"][:2] mock_response["total"] = 2 mock_response["size"] = 2 requests_mock.post(f"{client._base_url}pub/incident", json=mock_response) events_data = load_json_mock_response("triggered_events.json") mock_events_list_request.return_value = events_data incidents, _ = fetch_incidents(client, max_fetch, first_fetch, status_list, True, max_events_fetch, {}) assert len(incidents) == 2 # Verify events_list_request was called with time params from each incident assert mock_events_list_request.call_count == 2 # First incident: incidentFirstSeen=1646092830000, incidentLastSeen=1646147610000 # Buffer of EVENTS_TIME_BUFFER_MS (300000ms = 5 minutes) is subtracted from time_from and added to time_to first_call_kwargs = mock_events_list_request.call_args_list[0] assert first_call_kwargs[1]["time_from"] == 1646092830000 - EVENTS_TIME_BUFFER_MS assert first_call_kwargs[1]["time_to"] == 1646147610000 + EVENTS_TIME_BUFFER_MS # Second incident: incidentFirstSeen=1646093040000, incidentLastSeen=1646697840000 second_call_kwargs = mock_events_list_request.call_args_list[1] assert second_call_kwargs[1]["time_from"] == 1646093040000 - EVENTS_TIME_BUFFER_MS assert second_call_kwargs[1]["time_to"] == 1646697840000 + EVENTS_TIME_BUFFER_MS @patch("FortiSIEMV2.get_related_events_for_fetch_command") def test_fetch_events_concurrently_success(mock_get_events): """ Testing fetch_events_concurrently with multiple incidents - all succeed. Given: - 3 formatted incidents with incidentId, incidentFirstSeen, and incidentLastSeen. - get_related_events_for_fetch_command returns sample events for each incident. When: - fetch_events_concurrently is called. Then: - The returned events_map has the correct keys and values. - success_count == 3 and fail_count == 0. - get_related_events_for_fetch_command was called with the correct time_from (with 5-minute buffer subtracted) and time_to (with 5-minute buffer added). """ from FortiSIEMV2 import EVENTS_TIME_BUFFER_MS, fetch_events_concurrently client = mock_client() sample_incidents = [ {"incidentId": 101, "incidentFirstSeen": 1646092830000, "incidentLastSeen": 1646147610000}, {"incidentId": 102, "incidentFirstSeen": 1646093040000, "incidentLastSeen": 1646697840000}, {"incidentId": 103, "incidentFirstSeen": 1646094000000, "incidentLastSeen": 1646700000000}, ] def side_effect(incident_id, max_events, cli, time_from=None, time_to=None): return [{"Event ID": f"event_{incident_id}_1"}, {"Event ID": f"event_{incident_id}_2"}] mock_get_events.side_effect = side_effect events_map, total_time, success_count, fail_count = fetch_events_concurrently(sample_incidents, 20, client) assert success_count == 3 assert fail_count == 0 assert len(events_map) == 3 assert 101 in events_map assert 102 in events_map assert 103 in events_map assert len(events_map[101]) == 2 assert len(events_map[102]) == 2 assert len(events_map[103]) == 2 # Verify get_related_events_for_fetch_command was called with correct time_from/time_to assert mock_get_events.call_count == 3 for call_args in mock_get_events.call_args_list: inc_id = call_args[0][0] called_time_from = call_args[1]["time_from"] called_time_to = call_args[1]["time_to"] matching_incident = next(i for i in sample_incidents if i["incidentId"] == inc_id) assert called_time_from == matching_incident["incidentFirstSeen"] - EVENTS_TIME_BUFFER_MS assert called_time_to == matching_incident["incidentLastSeen"] + EVENTS_TIME_BUFFER_MS @patch("FortiSIEMV2.get_related_events_for_fetch_command") def test_fetch_events_concurrently_partial_failure(mock_get_events): """ Testing fetch_events_concurrently with partial failure - some incidents fail. Given: - 3 formatted incidents. - get_related_events_for_fetch_command succeeds for incidents 201 and 203 but raises an exception for 202. When: - fetch_events_concurrently is called. Then: - Successful incidents have events in the map. - Failed incident has an empty list in the map. - success_count == 2 and fail_count == 1. """ from FortiSIEMV2 import fetch_events_concurrently client = mock_client() sample_incidents = [ {"incidentId": 201, "incidentFirstSeen": 1000000, "incidentLastSeen": 2000000}, {"incidentId": 202, "incidentFirstSeen": 3000000, "incidentLastSeen": 4000000}, {"incidentId": 203, "incidentFirstSeen": 5000000, "incidentLastSeen": 6000000}, ] def side_effect(incident_id, max_events, cli, time_from=None, time_to=None): if incident_id == 202: raise Exception("API error for incident 202") return [{"Event ID": f"event_{incident_id}_1"}] mock_get_events.side_effect = side_effect events_map, total_time, success_count, fail_count = fetch_events_concurrently(sample_incidents, 20, client) assert success_count == 2 assert fail_count == 1 assert len(events_map) == 3 assert len(events_map[201]) == 1 assert events_map[202] == [] assert len(events_map[203]) == 1 def test_fetch_events_concurrently_empty_list(): """ Testing fetch_events_concurrently with an empty incidents list. Given: - An empty formatted_incidents list. When: - fetch_events_concurrently is called. Then: - A ValueError is raised because ThreadPoolExecutor requires max_workers > 0. Note: - In production, fetch_incidents() guards against this by checking `if fetch_with_events and formatted_incidents` before calling fetch_events_concurrently. """ from FortiSIEMV2 import fetch_events_concurrently client = mock_client() with pytest.raises(ValueError, match="max_workers must be greater than 0"): fetch_events_concurrently([], 20, client) @patch("FortiSIEMV2.get_related_events_for_fetch_command") def test_fetch_events_concurrently_time_buffer(mock_get_events): """ Testing that the 5-minute buffer is correctly applied to time_from and time_to. Given: - An incident with incidentFirstSeen=1000000 and incidentLastSeen=2000000. When: - fetch_events_concurrently is called. Then: - get_related_events_for_fetch_command is called with time_from=1000000 - 300000 = 700000 and time_to=2000000 + 300000 = 2300000. """ from FortiSIEMV2 import EVENTS_TIME_BUFFER_MS, fetch_events_concurrently client = mock_client() sample_incidents = [ {"incidentId": 301, "incidentFirstSeen": 1000000, "incidentLastSeen": 2000000}, ] mock_get_events.return_value = [{"Event ID": "event_301_1"}] events_map, total_time, success_count, fail_count = fetch_events_concurrently(sample_incidents, 20, client) assert success_count == 1 assert fail_count == 0 assert EVENTS_TIME_BUFFER_MS == 300000 mock_get_events.assert_called_once() call_kwargs = mock_get_events.call_args[1] assert call_kwargs["time_from"] == 700000 # 1000000 - 300000 assert call_kwargs["time_to"] == 2300000 # 2000000 + 300000 @pytest.mark.commands @freeze_time(time.ctime(1646205070)) @patch("FortiSIEMV2.get_related_events_for_fetch_command") def test_fetch_incidents_with_concurrent_events(mock_get_events, requests_mock): """ Integration-style test that verifies fetch_incidents uses concurrent fetching. Given: - fetch_with_events is True. - fetch_relevant_incidents returns sample incidents. - get_related_events_for_fetch_command returns sample events. When: - fetch_incidents is called with fetch_with_events=True. Then: - The returned incidents contain the expected events. - get_related_events_for_fetch_command is called for each incident. """ from FortiSIEMV2 import FortiSIEMClient, fetch_incidents client: FortiSIEMClient = mock_client() status_list = ["Active"] max_fetch = 2 max_events_fetch = 5 first_fetch = "1 week" mock_response = load_json_mock_response("fetch_incidents.json") mock_response["data"] = mock_response["data"][:2] mock_response["total"] = 2 mock_response["size"] = 2 requests_mock.post(f"{client._base_url}pub/incident", json=mock_response) def side_effect(incident_id, max_events, cli, time_from=None, time_to=None): return [{"Event ID": f"event_{incident_id}_1"}, {"Event ID": f"event_{incident_id}_2"}] mock_get_events.side_effect = side_effect incidents, _ = fetch_incidents(client, max_fetch, first_fetch, status_list, True, max_events_fetch, {}) assert len(incidents) == 2 assert mock_get_events.call_count == 2 # Verify each incident has events in rawJSON for incident in incidents: raw_json = json.loads(incident["rawJSON"]) events = raw_json.get("events", []) assert len(events) == 2