IBM Resilient Systems
Case management that enables visibility across your tools for continual IR improvement.
Case Management · IBM Security QRadar SOAR
Details
| ID | IBM Resilient Systems |
|---|---|
| Provider | IBM |
| Category | Case Management |
| From Version | 5.0.0 |
| Docker Image | demisto/resilient:2.0.0.10133006 |
| Supported Modules | Agentix XSIAM |
README
Case management that enables visibility across your tools for continual IR improvement.
Configure IBM Resilient Systems in Cortex
| Parameter | Description | Required |
|---|---|---|
| Server URL (e.g. 192.168.0.1) | True | |
| Organization name | True | |
| API key ID | False | |
| API key secret | False | |
| Trust any certificate (not secure) | False | |
| Use system proxy settings | False | |
| Incident type | False | |
| Fetch incidents | False | |
| Fetch closed incidents | False | |
| Fetch notes | ||
| Fetch tasks | ||
| First fetch timestamp (YYYY-MM-DDTHH:MM:SSZ). For example: 2020-02-02T19:00:00Z | False | |
| Maximum incidents to fetch. | Maximum number of incidents per fetch. The maximum is 1000. | False |
| Incident Mirroring Direction | Cortex XSOAR only parameter. | False |
| Close Mirrored XSOAR Incidents | When selected, closing the IBM Resilient incident with a ‘Closed’ status, will close the Cortex XSOAR incident. | False |
| Close Mirrored IBM Resilient Incidents | When selected, closing the Cortex XSOAR incident will close the incident in IBM Resilient. | False |
| Tag from IBM Qradar SOAR | Add this tag to an entry to mirror it from IBM Qradar SOAR. | False |
| Tag to IBM QRadar SOAR | Add this tag to an entry to mirror it to IBM Qradar SOAR. | 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.
rs-search-incidents
Query for incidents
Base Command
rs-search-incidents
Input
| Argument Name | Description | Required |
|---|---|---|
| page | Indicates the starting point for the first record in the dataset. Starting from 1. | Optional |
| page_size | The maximum number of records to return in the response. Null or less than 1 to return all records, up to the server-configured maximum limit. When greater than 0, an error will be thrown if the length is greater than the server-configured maximum limit. | Optional |
| limit | Maximum number of incidents to retrieve. Default is 1000. | Optional |
| severity | Comma-separated list of incident severity, e.g., Low,Medium,High. | Optional |
| date-created-before | Created date of the incident before the given date in the format YYYY-MM-DDTHH:MM:SSZ, e.g., 2018-05-07T10:59:07Z. | Optional |
| date-created-after | Created date of the incident after the given date in the format YYYY-MM-DDTHH:MM:SSZ, e.g., 2018-05-07T10:59:07Z. | Optional |
| date-created-within-the-last | Created date of the incident within the last timeframe (days/hours/minutes). Should be given a number, along with the timeframe argument. | Optional |
| timeframe | Timeframe to search within for incident. Should be given with within-the-last/due-in argument. Possible values: “days”, “hours”, “minutes”. Possible values are: days, hours, minutes. | Optional |
| date-occurred-within-the-last | Occurred date of the incident within the last timeframe (days/hours/minutes). Should be given a number, along with the timeframe argument. | Optional |
| date-occurred-before | Occurred date of the incident before the given date in the format YYYY-MM-DDTHH:MM:SSZ, e.g., 2018-05-07T10:59:07Z. | Optional |
| date-occurred-after | Occurred date of the incident after the given date in the format YYYY-MM-DDTHH:MM:SSZ, e.g., 2018-05-07T10:59:07Z. | Optional |
| incident-type | Incident type. Possible values are: CommunicationError, DenialOfService, ImproperDisposal:DigitalAsset, ImproperDisposal:documents/files, LostDocuments/files/records, LostPC/laptop/tablet, LostPDA/smartphone, LostStorageDevice/media, Malware, NotAnIssue, Other, Phishing, StolenDocuments/files/records, StolenPC/laptop/tablet, StolenPDA/Smartphone, StolenStorageDevice/media, SystemIntrusion, TBD/Unknown, Vendor/3rdPartyError. | Optional |
| nist | NIST Attack Vectors. Possible values: “Attrition”, “E-mail”, “External/RemovableMedia”, “Impersonation”, “ImproperUsage”, “Loss/TheftOfEquipment”, “Other”, “Web”. Possible values are: Attrition, E-mail, External/RemovableMedia, Impersonation, ImproperUsage, Loss/TheftOfEquipment, Other, Web. | Optional |
| status | Incident status. Possible values: “Active” and “Closed”. Possible values are: Active, Closed. | Optional |
| due-in | Due date of the incident in given timeframe (days/hours/minutes). Should be given a number, along with the timeframe argument. | Optional |
| return_level | The incident data structure returned (“partial”, “normal”, “full”).’. Possible values are: partial, normal, full. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| Resilient.Incidents.CreateDate | string | Created date of the incident. |
| Resilient.Incidents.Name | string | Incident name. |
| Resilient.Incidents.Resolution | string | Incident resolution. |
| Resilient.Incidents.DiscoveredDate | string | Discovered date of the incident. |
| Resilient.Incidents.ResolutionSummary | string | Incident resolution summary. |
| Resilient.Incidents.Id | string | Incident ID. |
| Resilient.Incidents.Phase | string | Incident phase. |
| Resilient.Incidents.PlanStatus | string | Incident status. |
| Resilient.Incidents.Severity | string | Incident severity. |
| Resilient.Incidents.Description | string | Incident description. |
| Resilient.Incidents.Confirmed | boolean | Incident confirmation. |
| Resilient.Incidents.NegativePr | boolean | Whether negative PR is likely. |
| Resilient.Incidents.DateOccurred | string | Date incident occurred. |
| Resilient.Incidents.Reporter | string | Name of reporting individual. |
| Resilient.Incidents.NistAttackVectors | Unknown | Incident NIST attack vectors. |
| Resilient.Incidents.ExposureType | string | Incident exposure type. |
| Resilient.Incidents.ResolutionSummary | string | Incident resolution summary. |
rs-update-incident
Updates incidents.
Base Command
rs-update-incident
Input
| Argument Name | Description | Required |
|---|---|---|
| incident-id | Incident ID to update. | Required |
| severity | Severity to update. Possible value: “Low”, “Medium”, and “High”. Possible values are: Low, Medium, High. | Optional |
| owner | User full name to set as incident owner, e.g., Steve Jobs. | Optional |
| incident-type | Incident type (added to the current incident types list). Possible values are: CommunicationError, DenialOfService, ImproperDisposal:DigitalAsset, ImproperDisposal:documents/files, LostDocuments/files/records, LostPC/laptop/tablet, LostPDA/smartphone, LostStorageDevice/media, Malware, NotAnIssue, Other, Phishing, StolenDocuments/files/records, StolenPC/laptop/tablet, StolenPDA/Smartphone, StolenStorageDevice/media, SystemIntrusion, TBD/Unknown, Vendor/3rdPartyError. | Optional |
| resolution | Incident resolution. Possible value: “Unresolved”, “Duplicate”, “NotAnIssue”, and “Resolved”. Possible values are: Unresolved, Duplicate, NotAnIssue, Resolved. | Optional |
| resolution-summary | Incident resolution summary. | Optional |
| description | Incident description. | Optional |
| name | Incident name. | Optional |
| nist | NIST Attack Vectors (added to the current list of NIST attack vectors). Possible values: “Attrition”, “E-mail”, “External/RemovableMedia”, “Impersonation”, “ImproperUsage”, “Loss/TheftOfEquipment”, “Other”, “Web”. Possible values are: Attrition, E-mail, External/RemovableMedia, Impersonation, ImproperUsage, Loss/TheftOfEquipment, Other, Web. | Optional |
| other-fields | A JSON object of the form: {field_name: new_field_value}. For example: {"description": {"textarea": {"format": "html", "content": "The new description"}}, "name": {"text": "The new name"}}. The name should be the path to it in the incident separated by “.” For example: `{“properties.incident_summary”: {“text”: “The new name”}}”. Because of API limitations we currently support only fields of the following types: ID, list of IDS, Number, Boolean, Text, Data, Textarea. For more information, refer to https://xsoar.pan.dev/docs/reference/integrations/ibm-resilient-systems. In case of conflicts between the other-fields argument and the regular fields arguments, the other-fields value will be used. |
Optional |
Context Output
There is no context output for this command.
Command Example
!rs-update-incident incident-id=1234 severity=High incident-type=Malware
Human Readable Output
Incident 1234 was updated successfully.
rs-incidents-get-members
Gets members of the incident.
Base Command
rs-incidents-get-members
Input
| Argument Name | Description | Required |
|---|---|---|
| incident-id | Incident ID to get members of. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| Resilient.Incidents.ID | string | Incident ID. |
| Resilient.Incidents.Members.FirstName | string | Member’s first name. |
| Resilient.Incidents.Members.LastName | string | Member’s last name. |
| Resilient.Incidents.Members.ID | number | Member’s ID. |
| Resilient.Incidents.Members.Email | string | Member’s email address. |
Command Example
!rs-incidents-get-members incident-id=1234
Context Example
{
"Resilient": {
"Incidents": {
"Id": "1234",
"Members": [
{
"Email": "example@example.com",
"FirstName": "example",
"ID": 1,
"LastName": "example"
}
]
}
}
}
Human Readable Output
Members of incident 1234
ID LastName FirstName 1 example example example@example.com
rs-get-incident
Gets an individual incident by ID.
Base Command
rs-get-incident
Input
| Argument Name | Description | Required |
|---|---|---|
| incident-id | ID of incident to get. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| Resilient.Incidents.CreateDate | string | Created date of the incident. |
| Resilient.Incidents.Name | string | Incident name. |
| Resilient.Incidents.Resolution | string | Incident resolution. |
| Resilient.Incidents.DiscoveredDate | string | Discovered date of the incident. |
| Resilient.Incidents.ResolutionSummary | string | Incident resolution summary. |
| Resilient.Incidents.Id | string | Incident ID. |
| Resilient.Incidents.Phase | string | Incident phase. |
| Resilient.Incidents.PlanStatus | string | Incident status. |
| Resilient.Incidents.Severity | string | Incident severity. |
| Resilient.Incidents.Description | string | Incident description. |
| Resilient.Incidents.Confirmed | boolean | Incident confirmation. |
| Resilient.Incidents.NegativePr | boolean | Whether negative PR is likely. |
| Resilient.Incidents.DateOccurred | string | Date incident occurred. |
| Resilient.Incidents.Reporter | string | Name of reporting individual. |
| Resilient.Incidents.NistAttackVectors | Unknown | Incident NIST attack vectors. |
| Resilient.Incidents.ExposureType | string | Incident exposure type. |
rs-get-incident
Gets an individual incident by ID.
Base Command
rs-get-incident
Input
| Argument Name | Description | Required |
|---|---|---|
| incident-id | ID of incident to get. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| Resilient.Incidents.CreateDate | string | Created date of the incident. |
| Resilient.Incidents.Name | string | Incident name. |
| Resilient.Incidents.Resolution | string | Incident resolution. |
| Resilient.Incidents.DiscoveredDate | string | Discovered date of the incident. |
| Resilient.Incidents.ResolutionSummary | string | Incident resolution summary. |
| Resilient.Incidents.Id | string | Incident ID. |
| Resilient.Incidents.Phase | string | Incident phase. |
| Resilient.Incidents.PlanStatus | string | Incident status. |
| Resilient.Incidents.Severity | string | Incident severity. |
| Resilient.Incidents.Description | string | Incident description. |
| Resilient.Incidents.Confirmed | boolean | Incident confirmation. |
| Resilient.Incidents.NegativePr | boolean | Whether negative PR is likely. |
| Resilient.Incidents.DateOccurred | string | Date incident occurred. |
| Resilient.Incidents.Reporter | string | Name of reporting individual. |
| Resilient.Incidents.NistAttackVectors | Unknown | Incident NIST attack vectors. |
| Resilient.Incidents.ExposureType | string | Incident exposure type. |
| Resilient.Incidents.ResolutionSummary | string | Incident resolution summary. |
"Incidents": {
"Id": "1234",
"Members": {
"Email": "example@exampe.com",
"FirstName": "example",
"ID": 2,
"LastName": "example",
"members": [],
"vers": 10
}
}
} }
#### Human Readable Output
>### Members of incident 1234
>|Email|FirstName|ID|LastName|members|vers|
>|---|---|---|---|---|---|
>| example@example.com | example | 2 | example | | 10 |
### rs-get-users
***
Gets a list of all users in the system.
#### Base Command
`rs-get-users`
#### Input
There are no input arguments for this command.
#### Context Output
There is no context output for this command.
#### Command Example
```!rs-get-users```
#### Human Readable Output
>### IBM Resilient Systems Users
>|ID|LastName|FirstName|Email|
>|---|---|---|---|
>| 1 | example | example | example@example.com |
>| 2 | example1 | example1 | example1@example.com |
### rs-close-incident
***
Closes an incident.
#### Base Command
`rs-close-incident`
#### Input
| **Argument Name** | **Description** | **Required** |
| --- | --- | --- |
| incident-id | ID of the incident to close. | Required |
#### Context Output
There is no context output for this command.
#### Command Example
```!rs-close-incident incident-id=1234```
#### Human Readable Output
>Incident 1234 was closed.
### rs-create-incident
***
Creates an incident.
#### Base Command
`rs-create-incident`
#### Input
| **Argument Name** | **Description** | **Required** |
| --- | --- | --- |
| name | Incident name. | Required |
#### Context Output
| **Path** | **Type** | **Description** |
| --- | --- | --- |
| Resilient.Incidents.Id | string | Incident ID. |
| Resilient.Incidents.Name | string | Incident name. |
#### Command Example
```!rs-create-incident name=IncidentName```
#### Context Example
```json
{
"Resilient": {
"Incidents": {
"Id": "1235",
"Name": "IncidentName"
}
}
}
Human Readable Output
Incident IncidentName was created
ID Name 1235 IncidentName
rs-incident-artifacts
Gets incident artifacts.
Base Command
rs-incident-artifacts
Input
| Argument Name | Description | Required |
|---|---|---|
| incident-id | Incident ID to get artifacts of. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| Resilient.Incidents.Id | string | Incident ID. |
| Resilient.Incidents.Name | string | Incident name. |
| Resilient.Incidents.Artifacts.CreatedDate | string | Artifact created date. |
| Resilient.Incidents.Artifacts.Creator | string | Artifact creator. |
| Resilient.Incidents.Artifacts.Description | string | Artifact description. |
| Resilient.Incidents.Artifacts.ID | number | Artifact ID. |
| Resilient.Incidents.Artifacts.Type | string | Artifact type. |
| Resilient.Incidents.Artifacts.Value | string | Artifact value. |
| Resilient.Incidents.Artifacts.Attachments.ContentType | string | Attachment content type. |
| Resilient.Incidents.Artifacts.Attachments.CreatedDate | string | Attachment created date. |
| Resilient.Incidents.Artifacts.Attachments.Creator | string | Attachment creator. |
| Resilient.Incidents.Artifacts.Attachments.ID | number | Attachment ID. |
| Resilient.Incidents.Artifacts.Attachments.Name | string | Attachment name. |
| Resilient.Incidents.Artifacts.Attachments.Size | number | Attachment size. |
Command Example
!rs-incident-artifacts incident-id=1234
Context Example
{
"Resilient": {
"Incidents": {
"Artifacts": [
{
"CreatedDate": "2000-00-00T00:00:00Z",
"Creator": "example example",
"Description": "example",
"ID": 1,
"Type": "IP Address",
"Value": "1.1.1.1"
},
{
"CreatedDate": "2000-00-00T00:00:00Z",
"Creator": "example example",
"Description": "example",
"ID": 2,
"Type": "IP Address",
"Value": "2.2.2.2"
}
],
"Id": "1234",
"Name": "example"
}
}
}
Human Readable Output
Incident 1234 artifacts
ID Value Description CreatedDate Creator 1 1.1.1.1 example 2000-00-00T00:00:00Z example example 2 2.2.2.2 example 2000-00-00T00:00:00Z example example
rs-incident-attachments
Gets incident attachments.
Base Command
rs-incident-attachments
Input
| Argument Name | Description | Required |
|---|---|---|
| incident-id | Incident ID to get attachments from. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| Resilient.Incidents.Id | string | Incident ID. |
| Resilient.Incidents.Name | string | Incident name. |
| Resilient.Incidents.Owner | string | Incident owner. |
| Resilient.Incidents.Attachments.ContentType | string | Attachment content type. |
| Resilient.Incidents.Attachments.CreatedDate | string | Attachment created date. |
| Resilient.Incidents.Attachments.Creator | string | Attachment creator. |
| Resilient.Incidents.Attachments.ID | number | Attachment ID. |
| Resilient.Incidents.Attachments.Name | string | Attachment name. |
| Resilient.Incidents.Attachments.Size | number | Attachment size. |
Command Example
!rs-incident-attachments incident-id=1234
Context Example
{
"Resilient": {
"Incidents": {
"Attachments": [
{
"ContentType": "example",
"CreatedDate": "2000-00-00T00:00:00Z",
"Creator": "example example",
"ID": 1,
"Name": "example",
"Size": 10
}
],
"Id": "1234",
"Name": "example",
"Owner": "example example"
}
}
}
Human Readable Output
Incident 1234 attachments
ContentType CreatedDate Creator ID Name Size example 2000-00-00T00:00:00Z example example 1 example 10
rs-related-incidents
Gets related incidents.
Base Command
rs-related-incidents
Input
| Argument Name | Description | Required |
|---|---|---|
| incident-id | Incident ID to get related incidents of. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| Resilient.Incidents.Id | string | Incident ID. |
| Resilient.Incidents.Related.CreatedDate | string | Created date of the related incident. |
| Resilient.Incidents.Related.Name | string | Name of the related incident. |
| Resilient.Incidents.Related.ID | number | ID of the related incident. |
| Resilient.Incidents.Related.Status | string | Status (Active/Closed) of the related incident. |
| Resilient.Incidents.Related.Artifacts.CreatedDate | string | Created date of the artifact. |
| Resilient.Incidents.Related.Artifacts.ID | number | ID of the artifact. |
| Resilient.Incidents.Related.Artifacts.Creator | string | Creator of the artifact. |
Command Example
!rs-related-incidents incident-id=1234
Context Example
{
"Resilient": {
"Incidents": {
"Id": "1234",
"Related": [
{
"Artifacts": [
{
"CreatedDate": "2000-00-00T00:00:00Z",
"Creator": "example example",
"ID": 1
},
{
"CreatedDate": "2000-00-00T00:00:00Z",
"Creator": "example example",
"Description": "example",
"ID": 2
}
],
"CreatedDate": "2000-00-00T00:00:00Z",
"ID": 1235,
"Name": "example",
"Status": "Closed"
}
]
}
}
}
Human Readable Output
Incident 1234 related incidents
Artifacts CreatedDate ID Name Status ID: 1
Created Date: 2000-00-00T00:00:00Z
Creator: example example
ID: 2
Created Date: 2000-00-00T00:00:00Z
Description: example
Creator: example example2000-00-00T00:00:00Z 1234 example Closed
rs-incidents-get-tasks
Gets tasks of incidents.
Base Command
rs-incidents-get-tasks
Input
| Argument Name | Description | Required |
|---|---|---|
| incident-id | Incident ID to get tasks of. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| Resilient.Incidents.Id | string | Incident ID. |
| Resilient.Incidents.Name | string | Incident name. |
| Resilient.Incidents.Tasks.Category | string | Task category. |
| Resilient.Incidents.Tasks.Creator | string | Task creator. |
| Resilient.Incidents.Tasks.DueDate | string | Task due date. |
| Resilient.Incidents.Tasks.Form | string | Task form. |
| Resilient.Incidents.Tasks.ID | string | Task ID. |
| Resilient.Incidents.Tasks.Name | string | Task name. |
| Resilient.Incidents.Tasks.Required | boolean | Whether the task is required. |
| Resilient.Incidents.Tasks.Status | string | Task status (Open/Closed). |
Command Example
!rs-incidents-get-tasks incident-id=1234
Context Example
{
"Resilient": {
"Incidents": {
"Id": "1234",
"Name": "example",
"Tasks": [
{
"Category": "Respond",
"Creator": "example example",
"Form": "data_compromised, determined_date",
"ID": 1,
"Name": "example",
"Required": true,
"Status": "Open"
}
]
}
}
}
Human Readable Output
Incident 1234 tasks
ID Name Category Form Status DueDate Instructions UserNotes Required Creator 1 example Respond data_compromised, determined_date Open true example example
rs-add-note
Add a note to an incident.
Base Command
rs-add-note
Input
| Argument Name | Description | Required |
|---|---|---|
| incident-id | The ID of the incident. | Required |
| note | The text of the note. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| Resilient.IncidentNote.type | String | The type of the note (incident or task). |
| Resilient.IncidentNote.id | Number | The note’s ID. |
| Resilient.IncidentNote.parent_id | Number | The ID of the parent note (null for top-level note).. |
| Resilient.IncidentNote.user_id | Number | The ID of the user who created the note. |
| Resilient.IncidentNote.user_fname | String | The user’s first name. |
| Resilient.IncidentNote.user_lname | String | The user’s last name. |
| Resilient.IncidentNote.text | String | The note text. |
| Resilient.IncidentNote.create_date | Date | The date the note was created. |
| Resilient.IncidentNote.modify_date | Date | The date the note was modified. |
| Resilient.IncidentNote.is_deleted | Boolean | The flag indicating if the note is deleted. Generally, note objects are removed from the database when the user deletes them. However, if the user deletes a parent note, the parent is just marked as deleted (and its text is cleared). |
| Resilient.IncidentNote.modify_user.id | Number | The ID of the user who last modified the note. |
| Resilient.IncidentNote.modify_user.first_name | String | The first name of the user who last modified the note. |
| Resilient.IncidentNote.modify_user.last_name | String | The last name of the user who last modified the note. |
| Resilient.IncidentNote.inc_id | Number | The ID of the incident to which this note belongs. |
| Resilient.IncidentNote.inc_name | String | The name of the incident to which this note belongs. |
| Resilient.IncidentNote.task_id | Number | The ID of the task to which this note belongs. Will be null on incident notes. |
| Resilient.IncidentNote.task_name | String | The name of the task to which this note belongs. Will be null on incident notes. |
| Resilient.IncidentNote.task_custom | Booolean | For a task note, whether that task is a custom task. Null for incident notes. |
| Resilient.IncidentNote.task_members | Unknown | For a task note, the list of that task’s members, if any. Null for incident notes. |
| Resilient.IncidentNote.task_at_id | Unknown | For a task note, whether that task is an automatic task. Null for incident notes and task notes that are not automatically generated. |
| Resilient.IncidentNote.inc_owner | Number | The owner of the incident to which this note belongs. |
| Resilient.IncidentNote.user_name | String | The name of the owner of the incident to which this note belongs. |
| Resilient.IncidentNote.modify_principal.id | Number | The ID of the principal. |
| Resilient.IncidentNote.modify_principal.type | String | The type of the principal. Currently only user or group. |
| Resilient.IncidentNote.modify_principal.name | String | The name of the principal. |
| Resilient.IncidentNote.modify_principal.display_name | String | The display name of the principal. |
| Resilient.IncidentNote.comment_perms.update | Boolean | Whether the current user has permission to update this note. |
| Resilient.IncidentNote.comment_perms.delete | Boolean | Whether the current user has permission to delete this note. |
Command Example
!rs-add-note incident-id=1234 note="This is a note"
Context Example
{
"Resilient": {
"incidentNote": {
"actions": [],
"children": [],
"comment_perms": {
"delete": true,
"update": true
},
"create_date": 1600000000000,
"id": 10,
"inc_id": 1234,
"inc_name": "example",
"inc_owner": 1,
"is_deleted": false,
"mentioned_users": [],
"modify_date": 1600000000000,
"modify_principal": {
"display_name": "example example",
"id": 1,
"name": "example@example.com",
"type": "user"
},
"modify_user": {
"first_name": "example",
"id": 1,
"last_name": "example"
},
"parent_id": null,
"task_at_id": null,
"task_custom": null,
"task_id": null,
"task_members": null,
"task_name": null,
"text": "<div>This is a note</div>",
"type": "incident",
"user_fname": "example",
"user_id": 1,
"user_lname": "example",
"user_name": "example example"
}
}
}
Human Readable Output
The note was added successfully to incident 1234
rs-add-artifact
Add an artifact to an incident.
Base Command
rs-add-artifact
Input
| Argument Name | Description | Required |
|---|---|---|
| incident-id | The ID of the incident. | Required |
| artifact-type | The type of the artifact. Possible values are: DNS Name, Email Attachment, Email Attachment Name, Email Body, Email Recipient, Email Sender, Email Sender Name, Email Subject, File Name, File Path, HTTP Request Header, HTTP Response Header, IP Address, Log File, MAC Address, Malware Family/Variant, Malware MD5 Hash, Malware Sample, Malware Sample Fuzzy Hash, Malware SHA-1 Hash, Malware SHA-256 Hash, Mutex, Network CIDR Range, Observed Data, Other File, Password, Port, Process Name, Registry Key, RFC 822 Email Message File, Service, String, System Name, Threat CVE ID, URI Path, URL, URL Referer, User Account, User Agent, X509 Certificate File. | Required |
| artifact-value | The value of the artifact. | Required |
| artifact-description | The description of the artifact. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| Resilient.IncidentArtifact.id | Number | The ID of the artifact. |
| Resilient.IncidentArtifact.type | Number | The type of the artifact. |
| Resilient.IncidentArtifact.value | String | The value of the artifact. For example, the IP address for an IP address artifact. |
| Resilient.IncidentArtifact.description | String | The description of the artifact. |
| Resilient.IncidentArtifact.attachment | Unknown | The files attached to the artifact. |
| Resilient.IncidentArtifact.parent_id | Number | The parent artifact ID. |
| Resilient.IncidentArtifact.creator.id | Number | The ID of the artifact creator. |
| Resilient.IncidentArtifact.creator.fname | String | The first name of the artifact creator. |
| Resilient.IncidentArtifact.creator.lname | String | The last name of the artifact creator. |
| Resilient.IncidentArtifact.creator.display_name | String | The display name of the artifact creator. |
| Resilient.IncidentArtifact.creator.status | String | The status of the artifact creator. |
| Resilient.IncidentArtifact.creator.email | String | The email of the artifact creator. |
| Resilient.IncidentArtifact.creator.phone | String | The phone number of the artifact creator. |
| Resilient.IncidentArtifact.creator.cell | String | The cellphone number of the artifact creator. |
| Resilient.IncidentArtifact.creator.title | String | The user’s job title (e.g., Incident Response Manager). |
| Resilient.IncidentArtifact.creator.locked | Boolean | The status of the creator’s account. (True if locked. false otherwise). |
| Resilient.IncidentArtifact.creator.password_changed | Boolean | Whether the user’s password has changed. (True if changed, false otherwise). |
| Resilient.IncidentArtifact.creator.is_external | Boolean | Whether the user’s account is authenticated externally. |
| Resilient.IncidentArtifact.creator.ui_theme | String | The UI theme the user has selected. The Resilient UI recognizes the following values (darkmode, lightmode, verydarkmode). |
| Resilient.IncidentArtifact.inc_id | Number | The incident ID. |
| Resilient.IncidentArtifact.inc_name | String | The incident name. |
| Resilient.IncidentArtifact.inc_owner | Number | The incident owner. |
| Resilient.IncidentArtifact.created | Date | The date when the artifact is created. |
| Resilient.IncidentArtifact.last_modified_time | Date | The last date on which the artifact changed. |
| Resilient.IncidentArtifact.last_modified_by.id | Number | The ID of the user who last changed the artifact. |
| Resilient.IncidentArtifact.last_modified_by.type | String | The type of user who last changed the artifact. |
| Resilient.IncidentArtifact.last_modified_by.name | String | The name of the user who last changed the artifact. |
| Resilient.IncidentArtifact.last_modified_by.display_name | String | The display name of the user who last changed the artifact. |
| Resilient.IncidentArtifact.perms.read | Boolean | Whether the current user has permission to read this artifact. |
| Resilient.IncidentArtifact.perms.write | Boolean | Whether the current user has permission to write to this artifact. |
| Resilient.IncidentArtifact.perms.delete | Boolean | Whether the current user has permission to delete this artifact. |
| Resilient.IncidentArtifact.properties | Unknown | The additional artifact properties. |
| Resilient.IncidentArtifact.hash | String | The hash of the incident. |
| Resilient.IncidentArtifact.relating | Boolean | Whether this artifact should be used for relating to other incidents. Null means use the default specified by the type. True means to always relate. False means to never relate. |
| Resilient.IncidentArtifact.creator_principal.id | Number | The ID of the principal. |
| Resilient.IncidentArtifact.creator_principal.type | String | The type of the principal. Currently only user or group. |
| Resilient.IncidentArtifact.creator_principal.name | String | The API name of the principal. |
| Resilient.IncidentArtifact.creator_principal.display_name | String | The display name of the principal. |
| Resilient.IncidentArtifact.ip.source | Boolean | Whether the IP address is a source. |
| Resilient.IncidentArtifact.ip.destination | Boolean | Whether the IP address is a destination. |
Command Example
!rs-add-artifact artifact-type="IP Address" artifact-value=1.1.1.1 incident-id=1234 artifact-description="This is a description"
Context Example
{
"Resilient": {
"incidentArtifact": {
"actions": [],
"attachment": null,
"created": 1600000000000,
"creator": {
"cell": "",
"display_name": "example example",
"email": "example@example.com",
"fname": "example",
"id": 9,
"is_external": false,
"lname": "example",
"locked": false,
"password_changed": false,
"phone": "",
"status": "A",
"title": "",
"ui_theme": "darkmode"
},
"creator_principal": {
"display_name": "example example",
"id": 1,
"name": "example@example.com",
"type": "user"
},
"description": "example",
"hash": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"hits": [],
"id": 1,
"inc_id": 1234,
"inc_name": "example",
"inc_owner": 1,
"ip": {
"destination": null,
"source": null
},
"last_modified_by": {
"display_name": "example example",
"id": 1,
"name": "example@example.com",
"type": "user"
},
"last_modified_time": 1600000000000,
"parent_id": null,
"pending_sources": [],
"perms": {
"delete": true,
"read": true,
"write": true
},
"properties": null,
"relating": null,
"type": 1,
"value": "1.1.1.1"
}
}
}
Human Readable Output
The artifact was added successfully to incident 1234
rs-delete-incidents
Delete multiple incidents.
Base Command
rs-delete-incidents
Input
| Argument Name | Description | Required |
|---|---|---|
| incident_ids | A comma-separated list of incident IDs to be deleted. | Required |
Context Output
There is no context output for this command.
rs-list-incident-notes
Gets all of the top-level comments for an incident.
Base Command
rs-list-incident-notes
Input
| Argument Name | Description | Required |
|---|---|---|
| incident_id | Incident ID to update. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| Resilient.IncidentNote | Dictionary | Top-level comments for incident. |
rs-update-task
Update an incident’s task fields.
Base Command
rs-update-task
Input
| Argument Name | Description | Required |
|---|---|---|
| task_id | ID of task to update. | Required |
| name | Task name. Technically required, copy original task name if no changes are desired. | Required |
| owner_id | User ID of the new owner. | Optional |
| due_date | Task due date in ISO format e.g,. “2020-02-02T19:00:00Z. Empty date indicates that the task has no assigned due date. | Optional |
| phase | The phase to which this task belongs. Possible values are: Initial, Engage, Detect/Analyze, Respond, Post-Incident, Custom, Complete. | Optional |
| status | Changing the status field, completes or re-openes the task. Possible values are: Open, Completed. | Optional |
Context Output
There is no context output for this command.
rs-add-custom-task
Adds a custom task to the specified incident.
Base Command
rs-add-custom-task
Input
| Argument Name | Description | Required |
|---|---|---|
| incident_id | ID of incident to add a task to. | Required |
| name | Task name. | Required |
| description | Task description. | Required |
| instructions | Textual instructions for the task. This will override the default instructions for the task. | Required |
| due_date | Task due date in ISO format e.g., “2020-02-02T19:00:00Z. Empty date indicates that the task has no assigned due date. | Required |
| owner_id | The owner of the task (ID or name as appears in IBM QRadar SOAR). Leave empty if the task has no owner. | Optional |
| phase | Task to be added to the IBM QRadar incident. Possible values are: Initial, Engage, Detect/Analyze, Respond, Post-Incident, Custom, Complete. Default is task. | Required |
Context Output
There is no context output for this command.
rs-delete-task-members
Delete a task’s member. This effectively changes the task from a “private” task to a non-private task (to one where any incident member can operate on it).
Base Command
rs-delete-task-members
Input
| Argument Name | Description | Required |
|---|---|---|
| task_id | ID of the task to delete its members. | Required |
Context Output
There is no context output for this command.
rs-list-tasks
Gets an array of open tasks to which the current user is assigned.
Base Command
rs-list-tasks
Input
There are no input arguments for this command.
Context Output
| Path | Type | Description |
|---|---|---|
| Resilient.Tasks | Dictionary | List of open tasks. |
rs-list-scripts
Retrieves the specified script’s information or a list of all organization’s scripts.
Base Command
rs-list-scripts
Input
| Argument Name | Description | Required |
|---|---|---|
| script_id | Internal ID/name of the script. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| Resilient.Scripts | Dictionary | Retrieved script or list of scripts with metadata. |
rs-delete-tasks
Deletes a specified list of tasks. Note that only custom tasks can be deleted.
Base Command
rs-delete-tasks
Input
| Argument Name | Description | Required |
|---|---|---|
| task_ids | A comma-separated list of task IDs to be deleted. | Required |
Context Output
There is no context output for this command.
rs-list-task-instructions
Lists the task’s instructions.
Base Command
rs-list-task-instructions
Input
| Argument Name | Description | Required |
|---|---|---|
| task_id | ID of the task to list its instructions. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| Resilient.Task | Dictionary | Task instructions. |
rs-update-incident-note
Updates an incident’s note.
Base Command
rs-update-incident-note
Input
| Argument Name | Description | Required |
|---|---|---|
| incident_id | Incident ID to update its note. | Required |
| note_id | Note ID to update. | Required |
| note | Text of the note. | Required |
Context Output
There is no context output for this command.
rs-upload-incident-attachment
Upload an attachment for an incident.
Base Command
rs-upload-incident-attachment
Input
| Argument Name | Description | Required |
|---|---|---|
| incident_id | Incident ID to update. | Required |
| entry_id | EntryID of the file to upload. | Required |
Context Output
There is no context output for this command.
rs-get-task-members
Get the members of a task. Private tasks will have the returned “members” property set. Non-private tasks will have a null “members” property.
Base Command
rs-get-task-members
Input
| Argument Name | Description | Required |
|---|---|---|
| task_id | ID of task to get its members. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| Resilient.Task | Dictionary | Task members. |
rs-get-attachment
Gets incident attachment’s name and contents as a file by its ID.
Base Command
rs-get-attachment
Input
| Argument Name | Description | Required |
|---|---|---|
| incident_id | Incident ID to get attachments from. | Required |
| attachment_id | Attachment ID to get. | Required |
Context Output
There is no context output for this command.
rs-incidents-update-member
Updates incident’s members.
Base Command
rs-incidents-update-member
Input
| Argument Name | Description | Required |
|---|---|---|
| incident-id | ID of the incident for which to update its members. | Required |
| members | A comma-separated list of members to add, e.g., 1,2,3. | Required |
Context Output
There is no context output for this command.
Configuration parameters
server— Server URL (e.g. 192.168.0.1) (required)org— Organization name (required)api_key_id— API key IDapi_key_secret— API key secretcredentials_api_key— API key IDcredentials— Credentials (Deprecated - use API Key authentication instead)insecure— Trust any certificate (not secure)proxy— Use system proxy settingsincidentType— Incident typeincidentFetchInterval— Incidents Fetch IntervalisFetch— Fetch incidentsfetch_closed— Fetch closed incidentsfetch_notes— Fetch notesfetch_tasks— Fetch tasksfetch_time— First fetch timestamp (YYYY-MM-DDTHH:MM:SSZ). For example: 2020-02-02T19:00:00Zmax_fetch— Maximum incidents to fetch.mirror_direction— Incident Mirroring Directionclose_xsoar_incident— Close Mirrored XSOAR Incidentsclose_ibm_incident— Close Mirrored IBM Resilient Incidentstag_from_ibm— Tag from IBM Qradar SOARtag_to_ibm— Tag to IBM QRadar SOAR
Commands (31)
-
get-mapping-fieldsReturns the list of fields to map in outgoing mirroring. This command is only used for debugging purposes.
-
get-modified-remote-dataReturns the list of incidents IDs that were modified since the last update time. Note that this method is for debugging purposes. The get-modified-remote-data command is used as part of the mirroring feature, which is available from version 6.1.
-
get-remote-dataGet remote data from a remote incident. This method does not update the current incident, and should be used for debugging purposes only.
-
rs-add-artifactAdd an artifact to an incident.
-
rs-add-custom-taskAdds a custom task to the specified incident.
-
rs-add-noteAdd a note to an incident.
-
rs-close-incidentCloses an incident.
-
rs-create-incidentCreates an incident.
-
rs-delete-incidentsDelete multiple incidents.
-
rs-delete-task-membersDelete a task's member. This effectively changes the task from a "private" task to a non-private task (to one where any incident member can operate on it).
-
rs-delete-tasksDeletes a specified list of tasks. Note that only custom tasks can be deleted.
-
rs-get-attachmentGets incident attachment's name and contents as a file by its ID.
-
rs-get-incidentGets an individual incident by ID.
-
rs-get-task-membersGet the members of a task. Private tasks will have the returned "members" property set. Non-private tasks will have a null "members" property.
-
rs-get-usersGets a list of all users in the system.
-
rs-incident-artifactsGets incident artifacts.
-
rs-incident-attachmentsGets incident attachments.
-
rs-incidents-get-membersGets members of the incident.
-
rs-incidents-get-tasksGets tasks of incidents.
-
rs-incidents-update-memberUpdates the incident's members.
-
rs-list-incident-notesGets all of the top-level comments for an incident.
-
rs-list-scriptsRetrieves the specified script's information or a list of all organization's scripts.
-
rs-list-task-instructionsGet the task's instructions.
-
rs-list-tasksGets an array of open tasks to which the current user is assigned.
-
rs-related-incidentsGets related incidents.
-
rs-search-incidentsQuery for incidents.
-
rs-update-incidentUpdates incidents.
-
rs-update-incident-noteUpdates an incident's note.
-
rs-update-taskUpdate an incident's task fields.
-
rs-upload-incident-attachmentUpload an attachment for an incident.
-
update-remote-systemUpdates the remote incident with local incident changes. This method is only used for debugging purposes and will not update the current incident.
import json from io import BytesIO import demistomock as demisto import pytest import requests from CommonServerPython import DemistoException from requests import Session DEFAULT_MAX_FETCH = 1000 TAG_TO_IBM = "FROM XSOAR" def dict_to_response(data, status=200): response = requests.Response() response.status_code = status # Convert dictionary to bytes and set as content response.raw = BytesIO(json.dumps(data).encode("utf-8")) response.headers["Content-Type"] = "application/json" return response def load_test_data(json_path): with open(json_path) as f: return json.load(f) class MockClient: @staticmethod def get(incident_id): return { "name": "The old name", "description": {"format": "html", "content": "The old description"}, "owner_id": 1, "discovered_date": 1624782898000, "confirmed": "true", } @staticmethod def post(url, body): return url, body @staticmethod def patch(url, body): return url, body @pytest.fixture def _mocker(mocker): mocker.patch.object( demisto, "params", return_value={"server": "example.com:80", "org": "example", "proxy": True, "close_ibm_incident": True} ) return mocker def test_update_incident_command_with_invalid_json(_mocker): """ Given: - An incident should be updated. When: - Running update_incident_command function with other-fields argument, the other-field is an invalid json. Then: - Ensure the parsing before the request fails and returns a JSONDecodeError. """ args = {"incident-id": "1234", "other-fields": "Invalid json"} from IBMResilientSystems import update_incident_command with pytest.raises(Exception) as exception: update_incident_command(MockClient, args) assert "The other_fields argument is not a valid json." in exception.value.args[0] def test_add_note(_mocker): """ Given: - An incident that should be updated with a note. When: - Running add_note_command function. Then: - Ensure the function runs as expected. """ mock_result = _mocker.patch.object(MockClient, "post") expected_result = ("/incidents/1234/comments", {"text": {"format": "text", "content": f"This is a new note\n{TAG_TO_IBM}"}}) from IBMResilientSystems import add_note_command output = add_note_command(MockClient, "1234", "This is a new note", tag_to_ibm=TAG_TO_IBM) assert mock_result.call_args.args == expected_result assert "1234" in output.readable_output def test_add_incident_artifact(_mocker): """ Given: - An incident should be updated with an artifact. When: - Running add_artifact_command function. Then: - Ensure the function runs as expected. """ mock_result = _mocker.patch.object(MockClient, "post") expected_result = ( "/incidents/1234/artifacts", { "type": "IP Address", "value": "1.1.1.1", "description": {"format": "text", "content": "This is the artifact description"}, }, ) from IBMResilientSystems import add_artifact_command output = add_artifact_command(MockClient, "1234", "IP Address", "1.1.1.1", "This is the artifact description") assert mock_result.call_args.args == expected_result assert "1234" in output.get("HumanReadable") def test_test_module(_mocker): """ Tests whether the test module returns expected result for default http response. """ from IBMResilientSystems import SimpleClient, test_module client = SimpleClient() _mocker.patch.object(client, "get", return_value={}) assert test_module(client, "2024-01-01T00:00:00Z") == "ok" @pytest.mark.parametrize( "fetch_time, expected_result", [ ("2024-01-01T00:00:00Z", "ok"), ("2024-01-01T00:00:00", "ok"), ("", "ok"), ("2024/01/01 00:00:00", "fail"), ("invalid-date", "fail"), ], ) def test_test_module_fetch_time(fetch_time, expected_result, _mocker): """ Tests whether the test module returns expected result for valid and invalid responses. """ from IBMResilientSystems import SimpleClient, test_module, validate_iso_time_format client = SimpleClient() _mocker.patch.object(client, "get", return_value={}) fetch_time = validate_iso_time_format(fetch_time) if expected_result == "fail": with pytest.raises(DemistoException): test_module(client, fetch_time) else: assert test_module(client, fetch_time) == expected_result @pytest.mark.parametrize( "args, expected", [ ( {}, { "filters": [{"conditions": []}], "sorts": [{"field_name": "create_date", "type": "asc"}], "length": DEFAULT_MAX_FETCH, }, ), # Test without any filters or pagination params ( {"severity": "Low"}, { "filters": [{"conditions": [{"field_name": "severity_code", "method": "in", "value": [50]}]}], "sorts": [{"field_name": "create_date", "type": "asc"}], "length": DEFAULT_MAX_FETCH, }, ), ( {"date-created-before": "2022-01-01T10:00:00Z"}, { "filters": [{"conditions": [{"field_name": "create_date", "method": "lte", "value": 1641031200000}]}], "sorts": [{"field_name": "create_date", "type": "asc"}], "length": DEFAULT_MAX_FETCH, }, ), ( {"page": 1, "page_size": 10, "last-modified-after": "2022-01-01T10:00:00Z"}, { "filters": [{"conditions": [{"field_name": "inc_last_modified_date", "method": "gte", "value": 1641031200000}]}], "sorts": [{"field_name": "create_date", "type": "asc"}], "start": 0, "length": 10, }, ), ], ids=["no-filters-query", "args-1-query", "args-2-query", "pagination-params-query"], ) def test_prepare_search_query_data(_mocker, args, expected): from IBMResilientSystems import prepare_search_query_data assert prepare_search_query_data(args) == expected @pytest.mark.parametrize( "input_notes, expected_output", [ ( [ { "type": "incident", "id": 0, "parent_id": None, "user_id": 0, "user_fname": "Demisto", "user_lname": "Resilient", "text": "insecure?", "create_date": 1722424268280, "modify_date": 1722424268280, "children": [], "mentioned_users": [], "is_deleted": False, "modify_user": {"id": 0, "first_name": "Demisto", "last_name": "Resilient"}, "actions": [], "inc_id": 2222, "inc_name": "inci-11", "task_id": None, "task_name": None, "task_custom": None, "task_members": None, "task_at_id": None, "inc_owner": 0, "user_name": "Demisto Resilient", "modify_principal": {"id": 0, "type": "user", "name": "demist", "display_name": "Demisto Resilient"}, "comment_perms": {"update": True, "delete": True}, } ], [ { "create_date": "2024-07-31T11:11:08Z", "created_by": "Demisto Resilient", "id": 0, "modify_date": 1722424268280, "text": "insecure?", } ], ), ( [{"id": 2, "text": " ", "create_date": 1722424253387}], [{"create_date": "2024-07-31T11:10:53Z", "created_by": " ", "id": 2, "modify_date": None, "text": " "}], ), ], ) def test_prettify_incident_notes(_mocker, input_notes, expected_output): from IBMResilientSystems import prettify_incident_notes assert prettify_incident_notes(input_notes) == expected_output @pytest.mark.parametrize( "incidents, expected_output", [ ([], "No results found."), ], ) def test_search_incidents_command(_mocker, incidents, expected_output): from IBMResilientSystems import SimpleClient, search_incidents_command client = SimpleClient() _mocker.patch("IBMResilientSystems.search_incidents", return_value=incidents) assert search_incidents_command(client=client, args={}) == expected_output @pytest.mark.parametrize( "args", [ ({"date-created-after": 1577865600000, "limit": "1000", "page": "1", "page_size": "10"}), ], ) def test_search_incidents(_mocker, args): from IBMResilientSystems import DEFAULT_RETURN_LEVEL, SimpleClient, search_incidents test_dict_response = load_test_data("./test_data/test_search_incidents_response.json") test_response = dict_to_response(test_dict_response) request = _mocker.patch.object(Session, "post", return_value=test_response) client = SimpleClient() client.org_id = 0 search_incidents(client=client, args=args) request_url = request.call_args.args[0] request_headers = request.call_args.kwargs["headers"] request_data = request.call_args.kwargs["data"] assert request_url.endswith( f"/rest/orgs/0/incidents/query_paged?text_content_output_format=objects_convert_text&return_level=" f"{args.get('return_level', DEFAULT_RETURN_LEVEL)}" ) assert request_headers["content-type"] == "application/json" assert request_data == ( '{"filters": [{"conditions": [{"field_name": "create_date", "method": "gte", "value": 1577865600000}]}],' ' "sorts": [{"field_name": "create_date", "type": "asc"}], "length": 10, "start": 0}' ) @pytest.mark.parametrize( "args, processed_payload", [ ( { "incident-id": 0000, "severity": "Low", "incident-type": "Malware", "nist": "Attrition", "resolution": "NotAnIssue", "resolution-summary": "This is a test incident.", "description": "Test incident", "name": "incident-0000", }, { "changes": [ {"field": "severity_code", "old_value": {"id": 6}, "new_value": {"id": 4}}, { "field": "incident_type_ids", "old_value": {"ids": [21, 19, 17, 6]}, "new_value": {"ids": [21, 19, 17, 6, 19]}, }, {"field": "nist_attack_vectors", "old_value": {"ids": [4, 2]}, "new_value": {"ids": [4, 2, 2]}}, {"field": "resolution_id", "old_value": {"id": 9}, "new_value": {"id": 9}}, { "field": "resolution_summary", "old_value": {"textarea": {"format": "html", "content": "This is a test incident."}}, "new_value": {"textarea": {"format": "html", "content": "This is a test incident."}}, }, { "field": "description", "old_value": {"textarea": {"format": "html", "content": "1111 2222 3333"}}, "new_value": {"textarea": {"format": "html", "content": "Test incident"}}, }, {"field": "name", "old_value": {"text": "incident_name"}, "new_value": {"text": "incident-0000"}}, ] }, ), ], ) def test_update_incident_command(_mocker, args, processed_payload): from IBMResilientSystems import SimpleClient, update_incident_command client = SimpleClient() client.org_id = 0 _mocker.patch.object( Session, "get", return_value=dict_to_response(load_test_data("./test_data/test_get_incident_response.json")) ) request = _mocker.patch.object( Session, "patch", return_value=dict_to_response({"success": True, "title": None, "message": None, "hints": []}) ) update_incident_command(client, args) assert request.call_args.args[0].endswith(f"/rest/orgs/{client.org_id}/incidents/{args['incident-id']}") assert json.loads(request.call_args[1]["data"]) == processed_payload def test_update_incident(_mocker): from IBMResilientSystems import SimpleClient, update_incident request = _mocker.patch.object( Session, "patch", return_value=dict_to_response({"success": True, "title": None, "message": None, "hints": []}) ) client = SimpleClient() client.org_id = 0 update_incident(client, incident_id="0000", data={}) assert request.call_args.args[0].endswith("/rest/orgs/0/incidents/0000") assert request.call_args[1]["data"] == "{}" @pytest.mark.parametrize( "incident_id, expected_human_readable", [ ( "1000", "### IBM QRadar SOAR incident ID 1000\n|Id|Name|Description|NistAttackVectors|Phase|Resolution|ResolutionSummary|Owner" # noqa: E501 "|CreatedDate|DateOccurred|DiscoveredDate|DueDate|NegativePr|Confirmed|ExposureType|Severity|Reporter|\n" "|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|\n| 1000 | incident_name | 1111 2222 3333 | " "E-mail<br>Attrition<br> | | Not an Issue | This is a test incident. | | 2024-07-29T11:32:36Z | | 2024-07-29T11:31:57Z " # noqa: E501 "| | true | true | ExternalParty | 6 | |", ), ], ) def test_get_incident_command(_mocker, incident_id, expected_human_readable): from IBMResilientSystems import SimpleClient, get_incident_command client = SimpleClient() client.org_id = 0 _mocker.patch("IBMResilientSystems.get_users", return_value=[]) _mocker.patch("IBMResilientSystems.get_phases", return_value={}) _mocker.patch.object( Session, "get", return_value=dict_to_response(load_test_data("./test_data/test_get_incident_response.json")) ) context_entry = get_incident_command(client, incident_id) assert context_entry["HumanReadable"].strip() == expected_human_readable @pytest.mark.parametrize( "script_id, expected_outputs, expected_readable_output", [ ( "1", [ { "id": 1, "name": "Sample script: process inbound email (v35)", "description": "This script processes inbound emails.", "language": "python", "object_type": 13, "uuid": "0000-0000-0000-0000-0000", "actions": [], "tags": [], "script_text": "...", "creator_id": 0, "last_modified_by": 0, "last_modified_time": 1600698818789, } ], """### example Scripts |id|name|description|language| |---|---|---|---| | 1 | Sample script: process inbound email (v35) | This script processes inbound emails. | python | """, ), ( "", [ { "id": 1, "name": "Sample script: process inbound email (v35)", "description": "This script processes inbound emails.", "language": "python", "object_type": 13, "uuid": "0000-0000-0000-0000-0000", "actions": [], "tags": [], }, { "id": 3, "name": "test-script", "description": "Testing", "language": "python", "object_type": 0, "uuid": "0000-0000-0000-0000-0000", "actions": [], "tags": [], }, { "id": 4, "name": "test-script-2", "description": "Testing 2", "language": "python", "object_type": 13, "uuid": "0000-0000-0000-0000-0000", "actions": [], "tags": [], }, ], """### example Scripts |id|name|description|language| |---|---|---|---| | 1 | Sample script: process inbound email (v35) | This script processes inbound emails. | python | | 3 | test-script | Testing | python | | 4 | test-script-2 | Testing 2 | python | """, ), ( "2", [ { "error_code": "generic", "hints": [], "message": "Unable to find object with ID 2", "success": False, "title": None, } ], """### example Scripts |id|name|description|language| |---|---|---|---| | | | | | """, ), ], ) def test_list_scripts_command(_mocker, script_id: str, expected_outputs: list, expected_readable_output: str): from os import path from IBMResilientSystems import SimpleClient, list_scripts_command def side_effect(url: str): _script_id = url.split("/")[-1] if _script_id: # Return enriched script data for a specific script ID response_path = f"./test_data/test_get_script_{_script_id}_response.json" if not path.exists(response_path): response_path = "./test_data/test_get_script_fail_response.json" else: response_path = "./test_data/test_get_all_scripts_response.json" return load_test_data(response_path) client = SimpleClient() client.org_id = 0 args = {"script_id": script_id} _mocker.patch.object(SimpleClient, "get", side_effect=side_effect) command_result = list_scripts_command(client, args) assert command_result.readable_output == expected_readable_output assert command_result.outputs == expected_outputs @pytest.mark.parametrize("file_entry_id", ["ENTRY_ID"]) def test_upload_incident_attachment(_mocker, file_entry_id: str): from IBMResilientSystems import SimpleClient, upload_incident_attachment_command client = SimpleClient() client.org_id = 0 response = {"status_code": 200} expected_output = "File was uploaded successfully to 1000." def mock_get_file_path(entry_id): # noqa: F811 if entry_id == "ENTRY_ID": return {"path": "/path/to/file", "name": "filename.txt"} return None _mocker.patch.object(demisto, "getFilePath", side_effect=mock_get_file_path) post_attachment_request = _mocker.patch.object(SimpleClient, "post_attachment", return_value=response) args = {"entry_id": file_entry_id, "incident_id": 1000} result = upload_incident_attachment_command(SimpleClient(), args, tag_to_ibm="FROM XSOAR") assert result.readable_output == expected_output post_attachment_request.assert_called_once_with( uri=f"/incidents/{args['incident_id']}/attachments", filepath="/path/to/file", filename=f"filename_{TAG_TO_IBM}.txt" ) def test_delete_incidents_command(_mocker): from IBMResilientSystems import SimpleClient, delete_incidents_command client = SimpleClient() client.org_id = 0 delete_incident_request = _mocker.patch.object( SimpleClient, "put", return_value={"success": True, "title": None, "message": None, "hints": []} ) incident_ids = ["1001", "1002"] delete_incidents_command(client, args={"incident_ids": ",".join(incident_ids)}) delete_incident_request.assert_called_once_with("/incidents/delete", payload=incident_ids) def test_list_incident_notes_command(_mocker): from IBMResilientSystems import SimpleClient, list_incident_notes_command client = SimpleClient() client.org_id = 0 get_incident_notes_request = _mocker.patch.object( SimpleClient, "get", return_value=load_test_data("./test_data/test_get_incident_notes_reponse.json") ) list_incident_notes_command(client, {"incident_id": "2000"}) get_incident_notes_request.assert_called_once_with("/incidents/2000/comments?text_content_output_format=objects_convert_text") def test_update_incident_note(_mocker): from IBMResilientSystems import SimpleClient, update_incident_note_command client = SimpleClient() client.org_id = 0 update_incident_note_request = _mocker.patch.object(SimpleClient, "put", return_value={}) update_incident_note_command(client, args={"incident_id": 2000, "note_id": 1, "note": "NOTE_BODY"}) update_incident_note_request.assert_called_once_with( "/incidents/2000/comments/1", payload={"text": {"format": "text", "content": "NOTE_BODY"}} ) @pytest.mark.parametrize( "args, expected_task_dto", [ ( { "incident_id": "2000", "name": "TASK-1", "owner_id": "0", "description": "TASK", "instructions": "INSTRUCTIONS", "phase": "Initial", "due_date": "2023-04-01T12:00:00.000Z", }, { "name": "TASK-1", "phase_id": {"name": "Initial"}, "description": "TASK", "due_date": 1680350400000, "instructions": "INSTRUCTIONS", "owner_id": 0, }, ), ( # Task without Instructions { "incident_id": "2001", "name": "TASK-2", "owner_id": "1", "description": "TASK 2", "instructions": "", "phase": "Custom", "due_date": "2023-05-01T12:00:00.000Z", }, {"name": "TASK-2", "phase_id": {"name": "Custom"}, "description": "TASK 2", "due_date": 1682942400000, "owner_id": 1}, ), ( # Invalid Owner ID { "incident_id": "2003", "name": "TASK-3", "owner_id": "abcd", "description": "TASK 3", "instructions": "TASK 3 Instructions", "phase": "Initial", "due_date": "2023-07-01T12:00:00.000Z", }, DemistoException("Owner ID must be an integer number."), ), ( # Task without Due Date { "incident_id": "2004", "name": "TASK-4", "owner_id": "3", "description": "TASK 4", "instructions": "TASK 4 Instructions", "phase": "Initial", "due_date": "", }, { "name": "TASK-4", "phase_id": {"name": "Initial"}, "description": "TASK 4", "instructions": "TASK 4 Instructions", "owner_id": 3, }, ), ], ) def test_add_custom_task_command(_mocker, args, expected_task_dto): from IBMResilientSystems import SimpleClient, add_custom_task_command client = SimpleClient() client.org_id = 0 def post_side_effect(uri, payload): if isinstance(expected_task_dto, Exception): raise expected_task_dto assert uri == f"/incidents/{args['incident_id']}/tasks" assert payload == expected_task_dto return {"id": "1234"} add_custom_task_request = _mocker.patch.object(SimpleClient, "post", side_effect=post_side_effect) if isinstance(expected_task_dto, Exception): with pytest.raises(DemistoException, match="Owner ID must be an integer number."): add_custom_task_command(client, args=args) else: result = add_custom_task_command(client, args=args) add_custom_task_request.assert_called_once_with(uri=f"/incidents/{args['incident_id']}/tasks", payload=expected_task_dto) assert ( result.readable_output == f"Successfully created new task for incident with ID {args['incident_id']}. Task ID: 1234" ) def test_list_tasks_command(_mocker): from IBMResilientSystems import SimpleClient, list_tasks_command client = SimpleClient() client.org_id = 0 get_tasks_request = _mocker.patch.object(SimpleClient, "get", return_value={}) list_tasks_command(client) get_tasks_request.assert_called_with("/tasks") def test_get_task_members_command(_mocker): from IBMResilientSystems import SimpleClient, get_task_members_command client = SimpleClient() client.org_id = 0 task_id = "1234" get_task_members_request = _mocker.patch.object(SimpleClient, "get", return_value={}) get_task_members_command(client, args={"task_id": task_id}) get_task_members_request.assert_called_with(f"/tasks/{task_id}/members") @pytest.mark.parametrize("task_ids, should_raise_exception", [("1000", False), ("2000,3000", False), ("", True)]) def test_delete_tasks_command(_mocker, task_ids, should_raise_exception): from IBMResilientSystems import SimpleClient, delete_tasks_command client = SimpleClient() client.org_id = 0 delete_tasks_request = _mocker.patch.object( SimpleClient, "put", return_value={"success": True, "title": None, "message": None, "hints": []} ) if should_raise_exception: with pytest.raises(DemistoException): delete_tasks_command(client, args={"task_ids": task_ids}) else: result = delete_tasks_command(client, args={"task_ids": task_ids}) task_id_list = task_ids.split(",") delete_tasks_request.assert_called_once_with("/tasks/delete", payload=task_id_list) assert result.readable_output == f"Tasks with IDs {task_id_list} were deleted successfully." def test_delete_task_members_command(_mocker): from IBMResilientSystems import SimpleClient, delete_task_members_command client = SimpleClient() client.org_id = 0 task_id = "1234" mock_response = {"content": "Members deleted successfully"} delete_task_members_request = _mocker.patch.object(SimpleClient, "delete", return_value=mock_response) delete_task_members_command(client, args={"task_id": task_id}) delete_task_members_request.assert_called_once_with(f"/tasks/{task_id}/members") def test_list_task_instructions_command(_mocker): from IBMResilientSystems import SimpleClient, list_task_instructions_command client = SimpleClient() client.org_id = 0 task_id = "5678" mock_response = {"text": {"content": "These are the instructions for the task.", "format": "text"}} get_task_instructions_request = _mocker.patch.object(SimpleClient, "get", return_value=mock_response) list_task_instructions_command(client, args={"task_id": task_id}) get_task_instructions_request.assert_called_once_with( f"/tasks/{task_id}/instructions_ex?text_content_output_format=objects_convert_text" ) def test_get_attachment_command(_mocker): from IBMResilientSystems import SimpleClient, get_attachment_command from requests import Response client = SimpleClient() client.org_id = 0 def side_effect(url: str, get_response_object=False): if url.endswith("/contents"): # File content request response = Response() response.__setattr__("_content", b"test file content") # Note: Bytes for content response.status_code = 200 return response # File metadata response else: return { "type": "incident", "id": 1, "uuid": "0000-0000-0000-0000", "name": "test-test-test.txt", "content_type": "text/plain", "created": 1725880565507, "creator_id": 1, "size": 6, "actions": [], "playbooks": [], "task_id": None, "task_name": None, "task_custom": None, "task_members": None, "task_at_id": None, "reconciliation_status": "matched", "vers": 8, "inc_id": 2000, "inc_name": "INCIDENT-1", "inc_owner": 0, } get_attachment_request = _mocker.patch.object(SimpleClient, "get", side_effect=side_effect) args = {"incident_id": "1000", "attachment_id": "1"} get_attachment_command(client, args) get_attachment_endpoint = f'/incidents/{args.get("incident_id")}/attachments/{args.get("attachment_id")}' get_attachment_contents_endpoint = get_attachment_endpoint + "/contents" # Check the calls made to the mock get_attachment_request.assert_has_calls( [_mocker.call(get_attachment_endpoint), _mocker.call(get_attachment_contents_endpoint, get_response_object=True)] ) def test_get_modified_remote_data_command(_mocker): from CommonServerPython import GetModifiedRemoteDataResponse from IBMResilientSystems import SimpleClient, get_modified_remote_data_command client = SimpleClient() client.org_id = 0 mock_search_incidents = _mocker.patch( "IBMResilientSystems.search_incidents", return_value=[ {"id": 1000, "last_modified_time": "2023-09-01T12:01:00Z"}, {"id": 1001, "last_modified_time": "2023-09-01T12:02:00Z"}, ], ) expected_output = GetModifiedRemoteDataResponse(["1000", "1001"]) last_update = "2023-09-01T12:00:00Z" args = {"lastUpdate": last_update} result = get_modified_remote_data_command(client, args) mock_search_incidents.assert_called_once_with(client, args={"last-modified-after": last_update}) assert result.modified_incident_ids == expected_output.modified_incident_ids def test_get_remote_data_command(_mocker): from IBMResilientSystems import SimpleClient, get_remote_data_command # Mock client and its methods client = SimpleClient() client.org_id = 0 incident_id = "1000" last_update = "2024-01-01T00:00:00Z" # Mock incoming arguments args = {"id": incident_id, "lastUpdate": last_update} # Mock the get_incident and process_raw_incident function behavior mock_incident_data = { "plan_status": "A", # 'A' stands for Active "end_date": None, "notes": [{"modify_date": 1725880565507, "text": {"content": "Note content"}, "created_by": "User 1"}], "attachments": [{"ID": "1", "Create Time": 1725880565507, "Name": "Attachment1"}], } _mocker.patch("IBMResilientSystems.get_incident", return_value=mock_incident_data) _mocker.patch("IBMResilientSystems.process_raw_incident", return_value=mock_incident_data) # Mock get_attachment and handle_incoming_incident_resolution _mocker.patch("IBMResilientSystems.get_attachment", return_value=("filename.txt", b"file content")) _mocker.patch("IBMResilientSystems.handle_incoming_incident_resolution", return_value={"Contents": "Incident resolved"}) # Call the command and capture the result result = get_remote_data_command(client, args, tag_to_ibm="FROM ", tag_from_ibm="TO ") # Check if the result contains the expected mirrored data and entries assert len(result.entries) == 3 # A note, a file, and a reopen entry. assert "Note content" in result.entries[0].get("Contents") assert "filename.txt" in result.entries[1].get("File") assert result.mirrored_object def test_update_remote_system_command_no_changes(_mocker): from IBMResilientSystems import SimpleClient, update_remote_system_command client = SimpleClient() args = {"remoteId": "1000", "incidentChanged": False, "entries": [], "delta": None, "data": {}, "incStatus": "Active"} debug_mock = _mocker.patch.object(demisto, "debug") result = update_remote_system_command(client, args, tag_to_ibm="FROM XSOAR") assert result == "1000" debug_mock.assert_called_with("Skipping updating remote incident fields [1000] as it is not new nor changed") def test_update_remote_system_command_with_changes(_mocker): from IBMResilientSystems import SimpleClient, update_remote_system_command client = SimpleClient() args = { "remoteId": "1001", "incidentChanged": True, "entries": [], "delta": {"name": "Updated Incident Name"}, "data": {}, "incStatus": "Active", } prepare_mock = _mocker.patch( "IBMResilientSystems.prepare_incident_update_dto_for_mirror", return_value={"name": "Updated Incident Name"} ) update_mock = _mocker.patch("IBMResilientSystems.update_incident") result = update_remote_system_command(client, args, tag_to_ibm="FROM XSOAR") assert result == "1001" prepare_mock.assert_called_once_with(client, "1001", {"name": "Updated Incident Name"}) update_mock.assert_called_once_with(client, "1001", {"name": "Updated Incident Name"}) def test_update_remote_system_command_with_note(_mocker): from CommonServerPython import EntryType from IBMResilientSystems import SimpleClient, update_remote_system_command client = SimpleClient() args = { "remoteId": "1002", "incidentChanged": False, "entries": [{"id": "1", "type": EntryType.NOTE, "tags": ["FROM XSOAR"], "Contents": "Test note"}], "delta": None, "data": {}, "incStatus": "Active", } add_note_mock = _mocker.patch("IBMResilientSystems.add_note") result = update_remote_system_command(client, args, tag_to_ibm="FROM XSOAR") assert result == "1002" add_note_mock.assert_called_once_with(client, "1002", "Test note") def test_update_remote_system_command_with_file(_mocker): from CommonServerPython import EntryType from IBMResilientSystems import SimpleClient, update_remote_system_command client = SimpleClient() args = { "remoteId": "1003", "incidentChanged": False, "entries": [{"id": "2", "type": EntryType.FILE, "tags": ["FROM XSOAR"], "Contents": "file content"}], "delta": None, "data": {}, "incStatus": "Active", } upload_mock = _mocker.patch("IBMResilientSystems.upload_incident_attachment") result = update_remote_system_command(client, args, tag_to_ibm="FROM XSOAR") assert result == "1003" upload_mock.assert_called_once_with(client, "1003", "2", "FROM XSOAR") def test_update_remote_system_command_with_multiple_entries(_mocker): from CommonServerPython import EntryType from IBMResilientSystems import SimpleClient, update_remote_system_command client = SimpleClient() args = { "remoteId": "1004", "incidentChanged": True, "entries": [ {"id": "3", "type": EntryType.NOTE, "tags": ["FROM XSOAR"], "Contents": "Test note 1"}, {"id": "4", "type": EntryType.FILE, "tags": ["FROM XSOAR"], "Contents": "file content"}, {"id": "5", "type": EntryType.NOTE, "tags": ["FROM XSOAR"], "Contents": "Test note 2"}, ], "delta": {"description": "Updated description"}, "data": {}, "incStatus": "Active", } prepare_mock = _mocker.patch( "IBMResilientSystems.prepare_incident_update_dto_for_mirror", return_value={"description": "Updated description"} ) update_mock = _mocker.patch("IBMResilientSystems.update_incident") add_note_mock = _mocker.patch("IBMResilientSystems.add_note") upload_mock = _mocker.patch("IBMResilientSystems.upload_incident_attachment") result = update_remote_system_command(client, args, tag_to_ibm="FROM XSOAR") assert result == "1004" prepare_mock.assert_called_once_with(client, "1004", {"description": "Updated description"}) update_mock.assert_called_once_with(client, "1004", {"description": "Updated description"}) assert add_note_mock.call_count == 2 add_note_mock.assert_any_call(client, "1004", "Test note 1") add_note_mock.assert_any_call(client, "1004", "Test note 2") upload_mock.assert_called_once_with(client, "1004", "4", "FROM XSOAR") def test_update_remote_system_command_with_untagged_entries(_mocker): from CommonServerPython import EntryType from IBMResilientSystems import SimpleClient, update_remote_system_command client = SimpleClient() args = { "remoteId": "1005", "incidentChanged": False, "entries": [ {"id": "6", "type": EntryType.NOTE, "tags": [], "Contents": "Untagged note"}, {"id": "7", "type": EntryType.FILE, "tags": [], "Contents": "Untagged file"}, ], "delta": None, "data": {}, "incStatus": "Active", } add_note_mock = _mocker.patch("IBMResilientSystems.add_note") upload_mock = _mocker.patch("IBMResilientSystems.upload_incident_attachment") result = update_remote_system_command(client, args, tag_to_ibm="FROM XSOAR") assert result == "1005" add_note_mock.assert_not_called() upload_mock.assert_not_called() def test_get_mapping_fields_command(_mocker): from CommonServerPython import GetMappingFieldsResponse from IBMResilientSystems import IBM_QRADAR_INCIDENT_FIELDS, IBM_QRADAR_SOAR_INCIDENT_SCHEMA_NAME, get_mapping_fields_command response = get_mapping_fields_command() assert isinstance(response, GetMappingFieldsResponse) # Get the mapping scheme from the response scheme = response.scheme_types_mappings[0] # Assert that the scheme has the correct incident schema name assert scheme.type_name == IBM_QRADAR_SOAR_INCIDENT_SCHEMA_NAME # Assert that the scheme contains the correct fields for field_name, _field_data in IBM_QRADAR_INCIDENT_FIELDS.items(): assert field_name in scheme.fields @pytest.mark.parametrize( "last_run, first_fetch_time, expected_args, expected_last_run", [ (None, "2023-01-01T00:00:00Z", {"date-created-after": 1672531200000}, 1672531200001), ({"time": 1672531200000}, "2023-01-01T00:00:00Z", {"date-created-after": 1672531200000}, 1672531200001), ], ) def test_fetch_incidents(_mocker, last_run, first_fetch_time, expected_args, expected_last_run): from IBMResilientSystems import SimpleClient, fetch_incidents mock_search_incidents = _mocker.patch("IBMResilientSystems.search_incidents", return_value=[]) mock_set_last_run = _mocker.patch.object(demisto, "setLastRun", return_value=None) client = SimpleClient() client.org_id = 0 fetch_incidents(client, first_fetch_time, fetch_closed=True) mock_search_incidents.assert_called_once_with(client, {"date-created-after": expected_args["date-created-after"]}) mock_set_last_run.assert_called_once_with({"time": expected_last_run}) def test_to_timestamp_with_integer(_mocker): from IBMResilientSystems import to_timestamp assert to_timestamp(1641024000000) == 1641024000000 def test_to_timestamp_with_string_timestamp(_mocker): from IBMResilientSystems import to_timestamp assert to_timestamp("1641024000000") == 1641024000000 def test_to_timestamp_with_string_date(_mocker): from IBMResilientSystems import to_timestamp assert to_timestamp("2022-01-01T12:00:00Z") == 1641038400000 def test_to_timestamp_with_invalid_string(_mocker): import pytest from IBMResilientSystems import to_timestamp with pytest.raises(ValueError): to_timestamp("INVALID_DATE_STRING") def test_validate_iso_time_format_with_milliseconds(_mocker): from IBMResilientSystems import validate_iso_time_format input_time = "2023-01-01T12:30:45.123456Z" expected_output = "2023-01-01T12:30:45Z" assert validate_iso_time_format(input_time) == expected_output def test_validate_iso_time_format_without_z(_mocker): from IBMResilientSystems import validate_iso_time_format input_time = "2023-01-01T12:30:45" expected_output = "2023-01-01T12:30:45Z" assert validate_iso_time_format(input_time) == expected_output def test_validate_iso_time_format_with_z(_mocker): from IBMResilientSystems import validate_iso_time_format input_time = "2023-01-01T12:30:45Z" expected_output = "2023-01-01T12:30:45Z" assert validate_iso_time_format(input_time) == expected_output def test_update_task_command_multiple_fields(_mocker): from IBMResilientSystems import SimpleClient, update_task_command client = SimpleClient() args = { "task_id": "5678", "name": "Complex Task", "owner_id": "10", "due_date": "2023-12-31T23:59:59Z", "phase": "Engage", "instructions": "Investigate thoroughly", "status": "Open", } update_task_mock = _mocker.patch("IBMResilientSystems.update_task") _mocker.patch("IBMResilientSystems.to_timestamp", return_value=1704067199000) result = update_task_command(client, args) expected_dto = { "name": "Complex Task", "inc_owner_id": 10, "due_date": 1704067199000, "phase_id": "Engage", "instructions": "Investigate thoroughly", "status": "O", } update_task_mock.assert_called_once_with(client, "5678", expected_dto) assert result.readable_output == "Task 5678 updated successfully." def test_update_task_command_completed_status(_mocker): from IBMResilientSystems import SimpleClient, update_task_command client = SimpleClient() args = {"task_id": "9012", "status": "Completed"} update_task_mock = _mocker.patch("IBMResilientSystems.update_task") result = update_task_command(client, args) update_task_mock.assert_called_once_with(client, "9012", {"status": "C"}) assert result.readable_output == "Task 9012 updated successfully." def test_update_task_command_invalid_status(_mocker): from IBMResilientSystems import SimpleClient, update_task_command client = SimpleClient() args = {"task_id": "3456", "status": "Invalid"} update_task_mock = _mocker.patch("IBMResilientSystems.update_task") result = update_task_command(client, args) update_task_mock.assert_called_once_with(client, "3456", {}) assert result.readable_output == "Task 3456 updated successfully." def test_update_task_command_empty_args(_mocker): from IBMResilientSystems import SimpleClient, update_task_command client = SimpleClient() args = {"task_id": "7890"} update_task_mock = _mocker.patch("IBMResilientSystems.update_task") result = update_task_command(client, args) update_task_mock.assert_called_once_with(client, "7890", {}) assert result.readable_output == "Task 7890 updated successfully." def test_process_raw_incident(_mocker): _mocker.patch.object( demisto, "params", return_value={"server": "example.com:80", "org": "example", "proxy": True, "fetch_tasks": True, "fetch_notes": True}, ) from IBMResilientSystems import SimpleClient, process_raw_incident client = SimpleClient() _mocker.patch("IBMResilientSystems.get_tasks", return_value=[]) _mocker.patch("IBMResilientSystems.get_incident_notes", return_value=[]) _mocker.patch("IBMResilientSystems.incident_attachments", return_value=[]) _mocker.patch("IBMResilientSystems.incident_artifacts", return_value=[]) _mocker.patch("IBMResilientSystems.get_phase_name", return_value="Detect/Analyze") result = process_raw_incident(client, load_test_data("./test_data/test_get_incident_response.json")) assert result["description"] == "1111 2222 3333" assert result["discovered_date"] == "2024-07-29T11:31:57Z" assert result["create_date"] == "2024-07-29T11:32:36Z" @pytest.mark.parametrize( "incident_id, delta, expected_dto", [ ( "1000", {"ibmsecurityqradarsoarname": "Updated Incident Name", "description": "New description"}, { "changes": [ {"field": "name", "new_value": {"text": "Updated Incident Name"}, "old_value": {"text": "incident_name"}}, { "field": "description", "new_value": {"textarea": {"content": "New description", "format": "html"}}, "old_value": {"textarea": {"content": "1111 2222 3333", "format": "html"}}, }, ] }, ), ( "1001", {"resolution_id": ""}, { "changes": [ {"field": "plan_status", "new_value": {"text": "C"}, "old_value": {"text": "A"}}, {"field": "resolution_id", "new_value": {"textarea": None}, "old_value": {"id": 9}}, ] }, ), ], ) def test_prepare_incident_update_dto_for_mirror(_mocker, incident_id, delta, expected_dto): from IBMResilientSystems import SimpleClient, prepare_incident_update_dto_for_mirror client = SimpleClient() client.org_id = 0 mock_get_incident = _mocker.patch( "IBMResilientSystems.get_incident", return_value=load_test_data("./test_data/test_get_incident_response.json") ) _mocker.patch.object(demisto, "params", return_value={"close_ibm_incident": True}) result = prepare_incident_update_dto_for_mirror(client, incident_id, delta) mock_get_incident.assert_called_once_with(client, incident_id) assert result == expected_dto def test_prettify_incident_tasks_multiple_tasks(_mocker): from IBMResilientSystems import SimpleClient, prettify_incident_tasks client = SimpleClient() tasks = [ { "id": 1, "name": "Task 1", "description": "Description 1", "due_date": 1641024000000, "status": "O", "required": True, "owner_fname": "John", "owner_lname": "Doe", "phase_id": 1, "creator_principal": {"display_name": "Admin User"}, "instructions": {"content": "Instructions 1"}, }, { "id": 2, "name": "Task 2", "description": "Description 2", "due_date": None, "status": "C", "required": False, "owner_fname": "Jane", "owner_lname": "Smith", "phase_id": 2, "creator_principal": None, "instructions": None, }, ] _mocker.patch("IBMResilientSystems.get_phase_name", side_effect=["Initial", "Analysis"]) _mocker.patch("IBMResilientSystems.normalize_timestamp", return_value="2022-01-01T12:00:00Z") result = prettify_incident_tasks(client, tasks) assert len(result) == 2 assert result[0]["ID"] == 1 assert result[0]["Name"] == "Task 1" assert result[0]["Status"] == "Open" assert result[0]["DueDate"] == "2022-01-01T12:00:00Z" assert result[0]["Phase"] == "Initial" assert result[0]["Creator"] == "Admin User" assert result[0]["Instructions"] == "Instructions 1" assert result[1]["ID"] == 2 assert result[1]["Name"] == "Task 2" assert result[1]["Status"] == "Closed" assert result[1]["DueDate"] == "No due date" assert result[1]["Phase"] == "Analysis" assert result[1]["Creator"] == "" assert result[1]["Instructions"] == "" def test_prettify_incident_tasks_missing_fields(_mocker): from IBMResilientSystems import SimpleClient, prettify_incident_tasks client = SimpleClient() tasks = [ {"id": 1, "name": "Minimal Task", "description": "", "due_date": None, "status": "O", "required": False, "phase_id": 1} ] _mocker.patch("IBMResilientSystems.get_phase_name", return_value="Initial") result = prettify_incident_tasks(client, tasks) assert len(result) == 1 assert result[0]["ID"] == 1 assert result[0]["Name"] == "Minimal Task" assert result[0]["Description"] == "" assert result[0]["DueDate"] == "No due date" assert result[0]["Status"] == "Open" assert result[0]["Required"] is False assert result[0]["Owner"] == " " assert result[0]["Phase"] == "Initial" assert result[0]["Creator"] == "" assert result[0]["Instructions"] == "" def test_list_open_incidents(_mocker): from IBMResilientSystems import SimpleClient, list_open_incidents client = SimpleClient() get_incidents_request = _mocker.patch.object(SimpleClient, "get", return_value=[]) list_open_incidents(client) get_incidents_request.assert_called_once_with("/incidents/open") def test_get_users(_mocker): from IBMResilientSystems import SimpleClient, get_users client = SimpleClient() _mocker.patch.object(SimpleClient, "get", return_value=[]) get_users(client) client.get.assert_called_once_with("/users") def test_get_phase_name(_mocker): from IBMResilientSystems import SimpleClient, get_phase_name client = SimpleClient() _mocker.patch.object( SimpleClient, "get", return_value={ "id": 1004, "name": "Engage", "enabled": True, "perms": {"deleteable": True, "reorderable": True}, "uuid": "0000-0000-0000-00000", "order": 1, "tags": [], }, ) get_phase_name(client, "1004") client.get.assert_called_once_with("/phases/1004") def test_get_phases(_mocker): from IBMResilientSystems import SimpleClient, get_phases client = SimpleClient() _mocker.patch.object( SimpleClient, "get", return_value={ "entities": [ { "id": 1003, "name": "Initial", "enabled": True, "perms": {"deleteable": False, "reorderable": False}, "uuid": "0000-0000-0000-0000", "order": 0, "tags": [], } ] }, ) get_phases(client) client.get.assert_called_once_with("/phases") def test_get_tasks(_mocker): from IBMResilientSystems import SimpleClient, get_tasks client = SimpleClient() _mocker.patch.object(SimpleClient, "get", return_value=[]) get_tasks(client, "1000") client.get.assert_called_once_with("/incidents/1000/tasks?text_content_output_format=objects_convert_text") @pytest.mark.parametrize( "resolution_id, resolution_summary, expected_close_reason, expected_close_notes", [ (8, "Duplicate issue", "Duplicate", "Duplicate issue"), (0, "Hardware failure", "Resolved", "Hardware failure"), (None, "User error", "Resolved", "User error"), ], ) def test_handle_incoming_incident_resolution( _mocker, resolution_id, resolution_summary, expected_close_reason, expected_close_notes ): from IBMResilientSystems import EntryFormat, EntryType, handle_incoming_incident_resolution incident_id = "1234" result = handle_incoming_incident_resolution(incident_id, resolution_id, resolution_summary) assert result["Type"] == EntryType.NOTE assert result["ContentsFormat"] == EntryFormat.JSON assert result["Contents"]["dbotIncidentClose"] is True assert result["Contents"]["closeReason"] == expected_close_reason assert result["Contents"]["closeNotes"] == f"{expected_close_notes}\nClosed on IBM QRadar SOAR" def test_handle_incoming_incident_resolution_unknown_resolution(_mocker): from IBMResilientSystems import EntryFormat, EntryType, handle_incoming_incident_resolution incident_id = "5678" resolution_id = 999 # Unknown resolution ID resolution_summary = "Unknown resolution" result = handle_incoming_incident_resolution(incident_id, resolution_id, resolution_summary) assert result["Type"] == EntryType.NOTE assert result["ContentsFormat"] == EntryFormat.JSON assert result["Contents"]["dbotIncidentClose"] is True assert result["Contents"]["closeReason"] == "Resolved" assert result["Contents"]["closeNotes"] == "Unknown resolution\nClosed on IBM QRadar SOAR"