Recorded Future Identity
Fetch & triage | Search & Lookup | Access Recorded Future Identity data and Playbook Alerts.
Data Enrichment & Threat Intelligence · Recorded Future Identity
Details
| ID | Recorded Future Identity |
|---|---|
| Provider | Mastercard |
| Category | Data Enrichment & Threat Intelligence |
| From Version | 6.0.0 |
| Docker Image | demisto/python3:3.12.13.10116658 |
| Supported Modules | Agentix XSIAM |
README
Recorded Future Identity Integration
Overview
This integration allows you to leverage Recorded Future’s identity-related threat intelligence within Cortex XSOAR. It
provides commands to search and lookup identity data, fetch playbook alerts, and manage incident statuses, enhancing
your security operations with actionable intelligence.
Setup
A valid API Token for Recorded Future Identity Intelligence needed to fetch information.
Get help with Recorded Future for Cortex XSOAR.
Configuration
- Navigate to Integrations:
- Go to Settings > Integrations > Servers & Services.
- Search for Recorded Future Identity:
- In the search bar, type Recorded Future Identity.
- Add a New Instance:
- Click Add instance to create and configure a new integration instance.
- Enter Configuration Parameters:
- Fill in the required parameters such as Server URL and API Token.
- Adjust optional settings like proxy usage and incident fetching as needed.
- Test the Configuration:
- Click Test to ensure the settings are correct and that the connection to Recorded Future is successful.
Configuration Parameters
| Parameter | Description | Required |
|---|---|---|
| Server URL | The URL to the Recorded Future ConnectAPI. | True |
| API Token | Valid API Token from Recorded Future. | True |
| Trust any certificate | Trust any certificate (not secure). | False |
| Use system proxy settings | Use system proxy settings. | False |
| Fetch incidents | Enable fetching incidents. | False |
| First Incident Fetch: Time Range | Limit incidents to include in the first fetch by time range. Input format: “NN hours” or “NN days”. | False |
| Maximum number of incidents per fetch | Limit the number of incidents returned per single fetch. Default value: 50. | False |
| Incident type | The type of incidents to fetch. | False |
| Password properties | Password properties that are used as a filter. | False |
| Limit Identities | Limit of identities to get, minimum is 0 and maximum is 10,000. | False |
| Domains | List of domains to use in search and lookup commands (e.g., mycompany.com, nextcompany.com). | True |
| Playbook Alerts: Fetched Statuses | Choose what statuses are included in the fetch (New, In Progress, Dismissed, Resolved). | False |
| Playbook Alerts: Fetched Priorities Threshold | Choose the priority threshold to fetch alerts of the selected priority and higher (Informational < Moderate < High). | False |
- Click Test to validate the URLs, token, and connection.
Pre-Process Rule
The integration pulls in Playbook alerts from Recorded Future based on its updates, creating the need for a
preprocessing rule that updates existing incidents instead of creating duplicates. Follow the guidelines below to
configure the preprocessing rule.
- Navigate to Settings > Objects Setup > Pre-Process Rules.
- Click New Rule.
- Name the rule appropriately.
- In the Conditions for Incoming Incident section, enter:
- Name - Includes - Recorded Future Playbook Alert
- In the Action section, select Drop and update.
- In the Update section, enter
- Link to - Oldest incident - Created within the last - Your desired timeframe
- DbotMirrorId - Is identical (Incoming Incident) - to incoming incident
The configuration of the preprocessing rule is optional, but highly recommended.

Commands
You can execute these commands from the Cortex XSOAR CLI, as part of an automation, or in a playbook. After executing a
command, a DBot message appears in the War Room with the command details.
recordedfuture-identity-search
Search for identities in the Recorded Future dataset.
Base Command
recordedfuture-identity-search
Input
| Argument Name | Description | Required |
|---|---|---|
| first-downloaded | Time period for “first_downloaded_gte” filter (e.g., “7 days ago”, “Three months ago”, “Last Year”, “All time”). Default value: “All time”. | Optional |
| latest-downloaded | Time period for “last_downloaded_gte” filter (e.g., “7 days ago”, “Three months ago”, “Last Year”, “All time”). Default value: “All time”. | Optional |
| exfiltration-date | Time period for “exfiltration_date_gte” filter (e.g., “7 days ago”, “Three months ago”, “Last Year”, “All time”). Default value: “All time”. | Optional |
| domain-type | Type of the domain (Email, Authorization, All). | Optional |
| domains | Domains separated by comma (if not specified, domains from app instance will be used). | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| RecordedFuture.Credentials.SearchIdentities | List | List of Identities that were found in search command |
Command Example
!recordedfuture-identity-search latest-downloaded="All time" domain-type=Authorization
Context Example
{
"RecordedFuture": {
"Credentials": {
"SearchIdentities": [
{
"login": "30fake",
"domain": "fakeyahoo.com"
},
{
"login": "3072882fake",
"domain": "fakeyahoo.com"
},
"fake3@fake.com",
"test@fakeyahoo.com"
]
}
}
}
Human Readable Output
This is search results for fakeyahoo.com, fake.com
- 30fake in domain fakeyahoo.com
- 3072882fake in domain fakeyahoo.com
- fake3@fake.com
- test@fakeyahoo.com
recordedfuture-identity-lookup
Get detailed information regarding identities.
Base Command
recordedfuture-identity-lookup
Input
| Argument Name | Description | Required |
|---|---|---|
| identities | String of identities separated by comma. | Required |
| first-downloaded | Time period for “first_downloaded_gte” filter (e.g., “7 days ago”, “Three months ago”, “Last Year”, “All time”). Default value: “All time”. | Optional |
| latest-downloaded | Time period for “last_downloaded_gte” filter (e.g., “7 days ago”, “Three months ago”, “Last Year”, “All time”). Default value: “All time”. | Optional |
| exfiltration-date | Time period for “exfiltration_date_gte” filter (e.g., “7 days ago”, “Three months ago”, “Last Year”, “All time”). Default value: “All time”. | Optional |
| domains | Domains separated by comma. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| RecordedFuture.Credentials.Identities.identity.subjects | string | Identity value |
| RecordedFuture.Credentials.Identities.count | number | Leaked credentials count number |
| RecordedFuture.Credentials.Identities.credentials.subject | string | Identity value |
| RecordedFuture.Credentials.Identities.credentials.dumps.name | string | Dump name |
| RecordedFuture.Credentials.Identities.credentials.dumps.description | string | Dump description |
| RecordedFuture.Credentials.Identities.credentials.dumps.downloaded | string | Datetime string that show the day when dump was downloaded |
| RecordedFuture.Credentials.Identities.credentials.type | string | Dump type |
| RecordedFuture.Credentials.Identities.credentials.breaches.name | string | Breach name |
| RecordedFuture.Credentials.Identities.credentials.breaches.domain | string | Breach domain |
| RecordedFuture.Credentials.Identities.credentials.breaches.type | string | Breach type |
| RecordedFuture.Credentials.Identities.credentials.breaches.breached | string | Datetime string that show the day when breach happened |
| RecordedFuture.Credentials.Identities.credentials.breaches.description | string | Breach description |
| RecordedFuture.Credentials.Identities.credentials.breaches.site_description | string | Breach site description |
| RecordedFuture.Credentials.Identities.credentials.first_downloaded | string | Datetime string representing firs time downloaded |
| RecordedFuture.Credentials.Identities.credentials.latest_downloaded | string | Datetime string representing last time downloaded |
| RecordedFuture.Credentials.Identities.credentials.exposed_secret.type | string | Exposed secret type |
| RecordedFuture.Credentials.Identities.credentials.exposed_secret.hashes.algorithm | string | Exposed secret hash algorithm |
| RecordedFuture.Credentials.Identities.credentials.exposed_secret.hashes.hash | string | Exposed secret hash value |
| RecordedFuture.Credentials.Identities.credentials.exposed_secret.effectively_clear | boolean | Exposed secret clear or not |
| RecordedFuture.Credentials.Identities.credentials.exposed_secret.details.properties | string | Exposed secret properties |
| RecordedFuture.Credentials.Identities.credentials.exposed_secret.details.clear_text_hint | string | Exposed secret text hint |
| RecordedFuture.Credentials.Identities.credentials.exposed_secret.details.rank | string | Rank for the exposed password |
Command Example
!recordedfuture-identity-lookup identities="fake@fakeyahoo.com,real@notfake.com" first-downloaded="3 Months ago"
Context Example
{
"RecordedFuture": {
"Credentials": {
"Identities": [
{
"identity":{
"subjects":[
"fake@yahoo.com"
]
},
"count":4,
"credentials":[
{
"subject":"fake@yahoo.com",
"dumps":[
{
"name":"FAKE Dump November 2020",
"description":"This SQL Dump linked to the 2020",
"downloaded":"2020-11-05T00:00:00.000Z",
"type":"SQL Dump",
"breaches":[
{
"name":"FAKE",
"domain":"fake.com",
"type":"breach",
"breached":"2020-10-01T00:00:00.000Z",
"description":"In October 2020, fake suffered a breach that exposed a portion of their backup data from November 2017. The exposed data included",
"site_description":"Fale provides backup and related services for mobile devices."
}
]
}
],
"first_downloaded":"2020-11-05T00:00:00.000Z",
"latest_downloaded":"2020-11-05T00:00:00.000Z",
"exposed_secret":{
"type":"pbkdf2_sha256",
"hashes":[
{
"algorithm":"PBKDF2_SHA256",
"hash":"12000$IWIs9x2tM7U3afasdasd3d23nRfo0sFRjpkMlim2GA2+p/2Y7RQLpODP4S0="
}
],
"effectively_clear":false
}
},
{
"subject":"fake@yahoo.com",
"dumps":[
{
"name":"Dark Web Dump October 2019",
"description":"This combo list of email addresses and clear passwords is not associated with any specific breach",
"downloaded":"2019-10-31T00:00:00.000Z",
"type":"Combo List"
},
{
"name":"Dark Web Dump September 2019",
"description":"This combo list of email addresses and clear passwords is not associated with any specific breach..",
"downloaded":"2019-09-02T00:00:00.000Z",
"type":"Combo List"
}
],
"first_downloaded":"2019-09-02T00:00:00.000Z",
"latest_downloaded":"2019-10-31T00:00:00.000Z",
"exposed_secret":{
"type":"clear",
"hashes":[
{
"algorithm":"SHA1",
"hash":"bbebf4e24e6631570cd8f60e1b0f77c"
},
{
"algorithm":"SHA256",
"hash":"7bf5626ca4944595aa89bf5fdsdfsdf9c9a1e9b014356d9b10ef31c9e9aadc7835e4"
},
{
"algorithm":"NTLM",
"hash":"1b408e981311312e21eefc0f5e37a20808"
},
{
"algorithm":"MD5",
"hash":"a6b9607b296pfk941dd390d9e690ade8e"
}
],
"details":{
"properties":[
"Letter",
"Number",
"UpperCase",
"LowerCase",
"AtLeast8Characters"
],
"clear_text_hint":"oo"
},
"effectively_clear":true
}
}
]
},
{
"identity":{
"subjects":[
"007"
]
},
"count":1,
"credentials":[
{
"subject":"007",
"dumps":[
{
"name":"Dark Web Password Dump 2020-11-07"
},
{
"name":"Dark Web Password Dump 2020-09-28"
}
],
"first_downloaded":"2020-09-28T15:52:05.000Z",
"latest_downloaded":"2020-11-07T08:38:56.000Z",
"exposed_secret":{
"type":"clear",
"hashes":[
{
"algorithm":"SHA1",
"hash":"6204f1659eb8dasdas871be7f4b3ee560207bda2c5"
},
{
"algorithm":"SHA256",
"hash":"2fff03500b9335t4g5427fa105aa0bb0d5b3e770d314352aef010b6ae0dff7ab9a"
},
{
"algorithm":"NTLM",
"hash":"a2abf21c7669d58137kffsa83381d55cc"
},
{
"algorithm":"MD5",
"hash":"c399d3ec33214df310b11dcb30b1b5e03"
}
],
"details":{
"properties":[
"Letter",
"LowerCase"
],
"clear_text_hint":"lo"
},
"effectively_clear":true
},
"compromise":{
"exfiltration_date":"2020-11-07T08:38:56.000Z"
},
"authorization_service":{
"url":"https://login.fakeyahoo.com/",
"domain":"fakeyahoo.com"
}
}
]
}
]
}
}
}
Human Readable Output
Credentials Lookup
Results for fake1@fake.com
We found 1 passwords that were leaked for this identity
Password 1
Rank: TopMillionCommonPasswords
Properties: Letter, Number, LowerCase, AtLeast8Characters
Type: clear
Effectively Clear: True
Clear Text Hint: wa
Algorithm: SHA1 Hash:21b1ee2d6764b61b038605378f361599a8b503ed
Algorithm: SHA256 Hash:99dbda619dfd82cf9dae074b5c3168e75961b642f3245fe7f400ad03940a0bd8
Algorithm: NTLM Hash:da89071afe87527dc0e89a09d35cb9c0
Algorithm: MD5 Hash:a0b1c21221b29780fc5e3373e626ab9b
Authorization service url: https://signup.norsegods.online/signup
Authorization service url: https://signup.norsegods.online/signup
Authorization service url: https://signup.norsegods.online/signup
Authorization service url: https://signup.norsegods.online/signup
Authorization service url: https://signup.norsegods.online/signupDomain: norsegods.online
First Downloaded: Nov 2022
Last Downloaded: Nov 2022
Exfiltration date: N/A
Malware Family: RedLine Stealer
Information about dumps where we found for Password 1
Stealer Malware Logs 2022-11-03, Nov 2022
Description: This credential data was derived from stealer malware logs.
Dump type: N/ACompromised Host
Operating System: Windows 10 Enterprise x64OS User Name: gabi2
File Path Location: C:\Windows\Microsoft.NET\Framework\v4.0.30319\AppLaunch.exe
Time Zone: UTC+01:00
Name of the Machine: N/A
User Account Control Setting: AllowAll
Antivirus: Windows Defender
IP Address: 138.255.250.246
Country: DO
Postal Code: 11403
recordedfuture-password-lookup
Lookup for password in Recorded Future Dataset.
Base Command
recordedfuture-password-lookup
Input
| Argument Name | Description | Required |
|---|---|---|
| password-hash | Password hash value. | Required |
| hash-algorithm | Password hash algorithm (e.g., SHA1, SHA256, NTLM, MD5). Default value: “SHA256”. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| RecordedFuture.Credentials.Password.Hash | String | Recorded Future password hash value. |
| RecordedFuture.Credentials.Password.Algorithm | String | Recorded Future password hash algorithm. |
| RecordedFuture.Credentials.Password.ExposureStatus | String | Recorded Future password exposure status. One of Common, UnCommon, NeverExposed |
Command Example
!recordedfuture-password-lookup password-hash="0e44ce7308af2b3de5232e4616403ce7d49ba2aec83f79c196409556422a4927" hash-algorithm="SHA256"
Context Example
{
"RecordedFuture": {
"Credentials": {
"Password": [
{
"password": {
"algorithm": "SHA256"
"hash": "da6a0f1c706df7e864f9d6f9431de9950450880e"
}
"exposure_status": NeverExposed
},
{
"password": {
"algorithm": "SHA256"
"hash": "0e44ce7308af2b3de5232e4616403ce7d49ba2aec83f79c196409556422a"
}
"exposure_status": Common
},
]
}
}
}
Human Readable Output
This is search results for password you provided
Password hash: 0e44ce7308af2b3de5232e4616403ce7d49ba2aec83f79c196409556422a4927
Password hash algorithm: SHA256
Password status: Common
recordedfuture-identity-playbook-alerts-search
Search Playbook alerts based on filters.
Base Command
recordedfuture-identity-playbook-alerts-search
Input
| Argument Name | Description | Required |
|---|---|---|
| limit | The maximum number of alerts to fetch. Default: 50. | Optional |
| time_since_update | The amount of time since the last update. E.g., “2 hours” or “7 days” ago. Default: “24 hours”. | Optional |
| playbook_alert_status | The statuses to retrieve. Defaults to only new status if not specified. Possible values are: new, in-progress, dismissed, resolved. | Optional |
| priority | Actions priority assigned in Recorded Future. Possible values are: high, moderate, informational. | Optional |
| order_search_by | The order by which to search for playbook alerts. Possible values are: updated, created. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| IdentityRecordedFuture.PlaybookAlerts.playbook_alert_id | String | Unique ID of the playbook alert |
| IdentityRecordedFuture.PlaybookAlerts.category | String | Playbook alert category |
| IdentityRecordedFuture.PlaybookAlerts.priority | String | Recommended Priority of the alert |
| IdentityRecordedFuture.PlaybookAlerts.status | String | Current alert status in Recorded Future |
| IdentityRecordedFuture.PlaybookAlerts.title | String | Title of the alert |
| IdentityRecordedFuture.PlaybookAlerts.updated | date | Date of last update |
| IdentityRecordedFuture.PlaybookAlerts.created | date | Date of creation |
| IdentityRecordedFuture.PlaybookAlerts.organization_id | String | Organization uhash |
| IdentityRecordedFuture.PlaybookAlerts.organization_name | String | Plaintext Organization name |
| IdentityRecordedFuture.PlaybookAlerts.assignee_id | String | uhash of the assigned user |
| IdentityRecordedFuture.PlaybookAlerts.assignee_name | unknown | name of the assigned user |
| IdentityRecordedFuture.PlaybookAlerts.owner_id | String | uhash of the enterprise that owns the alert |
| IdentityRecordedFuture.PlaybookAlerts.owner_name | String | Name of the enterprise that owns the alert |
Command Example
!recordedfuture-identity-playbook-alerts-search
!recordedfuture-identity-playbook-alerts-search
!recordedfuture-identity-playbook-alerts-search limit=10
!recordedfuture-identity-playbook-alerts-search playbook_alert_status=in-progress
!recordedfuture-identity-playbook-alerts-search priority=high
!recordedfuture-identity-playbook-alerts-search order_search_by=updated
recordedfuture-identity-playbook-alerts-details
Get Playbook alert details by ID.
Base Command
recordedfuture-identity-playbook-alerts-details
Input
| Argument Name | Description | Required |
|---|---|---|
| alert_ids | IDs of the playbook alert that should be fetched. | Required |
| detail_sections | What evidence sections to include in the fetch, fetches all available if not specified. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| IdentityRecordedFuture.PlaybookAlerts.playbook_alert_id | String | Unique ID of the playbook alert. |
| IdentityRecordedFuture.PlaybookAlerts.category | String | Playbook alert category. |
| IdentityRecordedFuture.PlaybookAlerts.priority | String | Recommended Priority of the alert. |
| IdentityRecordedFuture.PlaybookAlerts.status | String | Current alert status in Recorded Future. |
| IdentityRecordedFuture.PlaybookAlerts.title | String | Title of the alert. |
| IdentityRecordedFuture.PlaybookAlerts.updated | date | Date of last update. |
| IdentityRecordedFuture.PlaybookAlerts.created | date | Date of creation. |
| IdentityRecordedFuture.PlaybookAlerts.organization_id | String | Organization uhash. |
| IdentityRecordedFuture.PlaybookAlerts.organization_name | String | Plaintext Organization name. |
| IdentityRecordedFuture.PlaybookAlerts.assignee_id | String | uhash of the assigned user. |
| IdentityRecordedFuture.PlaybookAlerts.assignee_name | String | name of the assigned user. |
| IdentityRecordedFuture.PlaybookAlerts.owner_id | String | uhash of the enterprise that owns the alert. |
| IdentityRecordedFuture.PlaybookAlerts.owner_name | String | Name of the enterprise that owns the alert. |
| IdentityRecordedFuture.PlaybookAlerts.panel_status.playbook_alert_id | String | Unique ID of the playbook alert. |
| IdentityRecordedFuture.PlaybookAlerts.panel_status.category | String | Playbook alert category. |
| IdentityRecordedFuture.PlaybookAlerts.panel_status.priority | String | Recommended Priority of the alert. |
| IdentityRecordedFuture.PlaybookAlerts.panel_status.status | String | Current alert status in Recorded Future. |
| IdentityRecordedFuture.PlaybookAlerts.panel_status.title | String | Title of the alert. |
| IdentityRecordedFuture.PlaybookAlerts.panel_status.updated | date | Date of last update. |
| IdentityRecordedFuture.PlaybookAlerts.panel_status.created | date | Date of creation. |
| IdentityRecordedFuture.PlaybookAlerts.panel_status.organization_id | String | Organization uhash. |
| IdentityRecordedFuture.PlaybookAlerts.panel_status.organization_name | String | Plaintext Organization name. |
| IdentityRecordedFuture.PlaybookAlerts.panel_status.assignee_id | String | uhash of the assigned user. |
| IdentityRecordedFuture.PlaybookAlerts.panel_status.assignee_name | unknown | name of the assigned user. |
| IdentityRecordedFuture.PlaybookAlerts.panel_status.owner_id | String | uhash of the enterprise that owns the alert. |
| IdentityRecordedFuture.PlaybookAlerts.panel_status.owner_name | String | Name of the enterprise that owns the alert. |
| IdentityRecordedFuture.PlaybookAlerts.panel_status.case_rule_id | String | ID of the playbook alert category. |
| IdentityRecordedFuture.PlaybookAlerts.panel_status.case_rule_label | String | Name of the playbook alert category. |
| IdentityRecordedFuture.PlaybookAlerts.panel_status.context_list.context | Array | Context of entity connected to the Playbook alert. |
| IdentityRecordedFuture.PlaybookAlerts.panel_status.created | String | Date marking the creation of the Playbook alert in Recorded Future. |
| IdentityRecordedFuture.PlaybookAlerts.panel_status.entity_criticality | String | Criticality of the Playbook alert. |
| IdentityRecordedFuture.PlaybookAlerts.panel_status.entity_id | String | ID of the entity in Recorded Future. |
| IdentityRecordedFuture.PlaybookAlerts.panel_status.entity_name | String | Name of the entity. |
| IdentityRecordedFuture.PlaybookAlerts.panel_status.risk_score | String | Risk score of the entity in Recorded Future. |
| IdentityRecordedFuture.PlaybookAlerts.panel_status.targets | Array | List of targets of the Playbook alert. |
| IdentityRecordedFuture.PlaybookAlerts.panel_status.lifecycle_stage | String | Indicates what lifecycle the vulerability is in. |
| IdentityRecordedFuture.PlaybookAlerts.panel_summary.explanation | String | Entails the explanation to the triggering of the Playbook alert. |
| IdentityRecordedFuture.PlaybookAlerts.panel_summary.resolved_record_list.context_list.context | String | Context of entity connected to the Playbook alert. |
| IdentityRecordedFuture.PlaybookAlerts.panel_summary.resolved_record_list.criticality | String | Level of criticality. |
| IdentityRecordedFuture.PlaybookAlerts.panel_summary.resolved_record_list.entity | String | ID of the entitiy in Recorded Future. |
| IdentityRecordedFuture.PlaybookAlerts.panel_summary.resolved_record_list.record_type | String | Type of record A, CNAME or MX. |
| IdentityRecordedFuture.PlaybookAlerts.panel_summary.resolved_record_list.risk_score | String | Risk score of the entity in Recorded Future. |
| IdentityRecordedFuture.PlaybookAlerts.panel_summary.screenshots.description | String | Description of the image. |
| IdentityRecordedFuture.PlaybookAlerts.panel_summary.screenshots.image_id | String | ID of the screenshot in recorded future. |
| IdentityRecordedFuture.PlaybookAlerts.panel_summary.screenshots.tag | String | Image Analisys tag. |
| IdentityRecordedFuture.PlaybookAlerts.panel_summary.screenshots.created | String | When the image was created. |
| IdentityRecordedFuture.PlaybookAlerts.panel_summary.screenshots.base64 | String | The image binary encoded as a base64 string. |
| IdentityRecordedFuture.PlaybookAlerts.panel_summary.summary.targets.name | String | Target affected by the vulnerability. |
| IdentityRecordedFuture.PlaybookAlerts.panel_summary.summary.lifecycle_stage | String | The current lifecycle stage of the Playbook Alert. |
| IdentityRecordedFuture.PlaybookAlerts.panel_summary.summary.riskrules.rule | String | Name of the rule that triggered. |
| IdentityRecordedFuture.PlaybookAlerts.panel_summary.summary.riskrules.description | String | Short description of the trigger (E.g 13 sightings on 1 source..). |
| IdentityRecordedFuture.PlaybookAlerts.panel_summary.affected_products.name | String | Name of of affected product. |
| IdentityRecordedFuture.PlaybookAlerts.panel_summary.insikt_notes.id | String | The ID of the Insikt note. |
| IdentityRecordedFuture.PlaybookAlerts.panel_summary.insikt_notes.title | String | The title of the Insikt note. |
| IdentityRecordedFuture.PlaybookAlerts.panel_summary.insikt_notes.topic | String | The topic of the Insikt note. |
| IdentityRecordedFuture.PlaybookAlerts.panel_summary.insikt_notes.published | String | The time at which the Insikt note was published. |
| IdentityRecordedFuture.PlaybookAlerts.panel_summary.insikt_notes.fragment | String | A fragment of the Insikt note text. |
| IdentityRecordedFuture.PlaybookAlerts.panel_log.id | String | Log ID in Recorded Future. |
| IdentityRecordedFuture.PlaybookAlerts.panel_log.actor_id | String | ID of the actor. |
| IdentityRecordedFuture.PlaybookAlerts.panel_log.created | String | When was the log created. |
| IdentityRecordedFuture.PlaybookAlerts.panel_log.modified | String | When was the log last modified. |
| IdentityRecordedFuture.PlaybookAlerts.panel_log.action_priority | String | The priority of the Playbook alert. |
| IdentityRecordedFuture.PlaybookAlerts.panel_log.message | String | Log message. |
| IdentityRecordedFuture.PlaybookAlerts.panel_log.changes.assigne_change.old | String | Previous assignee. |
| IdentityRecordedFuture.PlaybookAlerts.panel_log.changes.assigne_change.new | String | New assignee. |
| IdentityRecordedFuture.PlaybookAlerts.panel_log.changes.assigne_change.type | String | Type of change. |
| IdentityRecordedFuture.PlaybookAlerts.panel_log.changes.status_change.old | String | Previous status. |
| IdentityRecordedFuture.PlaybookAlerts.panel_log.changes.status_change.new | String | New status. |
| IdentityRecordedFuture.PlaybookAlerts.panel_log.changes.status_change.type | String | Type of change. |
| IdentityRecordedFuture.PlaybookAlerts.panel_log.changes.title_change.old | String | Previous title. |
| IdentityRecordedFuture.PlaybookAlerts.panel_log.changes.title_change.new | String | New title. |
| IdentityRecordedFuture.PlaybookAlerts.panel_log.changes.title_change.type | String | Type of change. |
| IdentityRecordedFuture.PlaybookAlerts.panel_log.changes.priority_change.old | String | Previous priority. |
| IdentityRecordedFuture.PlaybookAlerts.panel_log.changes.priority_change.new | String | New priority. |
| IdentityRecordedFuture.PlaybookAlerts.panel_log.changes.priority_change.type | String | Type of change. |
| IdentityRecordedFuture.PlaybookAlerts.panel_log.changes.reopen_strategy_change.old | String | Previous reopen strategy. |
| IdentityRecordedFuture.PlaybookAlerts.panel_log.changes.reopen_strategy_change.new | String | New reopen strategy. |
| IdentityRecordedFuture.PlaybookAlerts.panel_log.changes.reopen_strategy_change.type | String | Type of change. |
| IdentityRecordedFuture.PlaybookAlerts.panel_log.changes.entities_change.removed | String | Removed entity. |
| IdentityRecordedFuture.PlaybookAlerts.panel_log.changes.entities_change.added | String | Added entity. |
| IdentityRecordedFuture.PlaybookAlerts.panel_log.changes.entities_change.type | String | Type of change. |
| IdentityRecordedFuture.PlaybookAlerts.panel_log.changes.related_entities_change.removed | String | Removed related entity. |
| IdentityRecordedFuture.PlaybookAlerts.panel_log.changes.related_entities_change.added | String | Added related entity. |
| IdentityRecordedFuture.PlaybookAlerts.panel_log.changes.related_entities_changetype | String | Type of change. |
| IdentityRecordedFuture.PlaybookAlerts.panel_log.changes.description_change.old | String | Previous description. |
| IdentityRecordedFuture.PlaybookAlerts.panel_log.changes.description_change.new | String | New description. |
| IdentityRecordedFuture.PlaybookAlerts.panel_log.changes.description_change.type | String | Type of change. |
| IdentityRecordedFuture.PlaybookAlerts.panel_log.changes.external_id_change.old | String | Previous external ID. |
| IdentityRecordedFuture.PlaybookAlerts.panel_log.changes.external_id_change.new | String | New external ID. |
| IdentityRecordedFuture.PlaybookAlerts.panel_log.changes.external_id_change.type | String | Type of change. |
| IdentityRecordedFuture.PlaybookAlerts.panel_action.action | String | The name of the action. |
| IdentityRecordedFuture.PlaybookAlerts.panel_action.updated | String | When was the action last updated. |
| IdentityRecordedFuture.PlaybookAlerts.panel_action.assignee_name | String | Full name of the assignee. |
| IdentityRecordedFuture.PlaybookAlerts.panel_action.assignee_id | String | ID of the assignee. |
| IdentityRecordedFuture.PlaybookAlerts.panel_action.status | String | The status of the action. |
| IdentityRecordedFuture.PlaybookAlerts.panel_action.description | String | A short description of the action. |
| IdentityRecordedFuture.PlaybookAlerts.panel_action.link | String | A link associated with the action. |
| IdentityRecordedFuture.PlaybookAlerts.panel_dns.ip_list.record | String | The DNS record. |
| IdentityRecordedFuture.PlaybookAlerts.panel_dns.ip_list.risk_score | String | Risk score associated with the record. |
| IdentityRecordedFuture.PlaybookAlerts.panel_dns.ip_list.criticality | String | The level of criticality. |
| IdentityRecordedFuture.PlaybookAlerts.panel_dns.ip_list.record_type | String | Type of record A, CNAME or MX. |
| IdentityRecordedFuture.PlaybookAlerts.panel_dns.ip_list.context_list.context | String | Labels of malicious behavior types that can be associated with an entity. |
| IdentityRecordedFuture.PlaybookAlerts.panel_dns.mx_list.record | String | The DNS record. |
| IdentityRecordedFuture.PlaybookAlerts.panel_dns.mx_list.risk_score | String | Risk score associated with the record. |
| IdentityRecordedFuture.PlaybookAlerts.panel_dns.mx_list.criticality | String | The level of criticality. |
| IdentityRecordedFuture.PlaybookAlerts.panel_dns.mx_list.record_type | String | Type of record A, CNAME or MX. |
| IdentityRecordedFuture.PlaybookAlerts.panel_dns.mx_list.context_list.context | String | Labels of malicious behavior types that can be associated with an entity. |
| IdentityRecordedFuture.PlaybookAlerts.panel_dns.ns_list.record | String | The DNS record. |
| IdentityRecordedFuture.PlaybookAlerts.panel_dns.ns_list.risk_score | String | Risk score associated with the record. |
| IdentityRecordedFuture.PlaybookAlerts.panel_dns.ns_list.criticality | String | The level of criticality. |
| IdentityRecordedFuture.PlaybookAlerts.panel_dns.ns_list.record_type | String | Type of record A, CNAME or MX. |
| IdentityRecordedFuture.PlaybookAlerts.panel_dns.ns_list.context_list.context | String | Labels of malicious behavior types that can be associated with an entity. |
| IdentityRecordedFuture.PlaybookAlerts.panel_whois.body.added | String | When the whois information was added. |
| IdentityRecordedFuture.PlaybookAlerts.panel_whois.body.attribute | String | Attribute, either whois or whoisContancts. |
| IdentityRecordedFuture.PlaybookAlerts.panel_whois.body.entity | String | ID of whois entity. |
| IdentityRecordedFuture.PlaybookAlerts.panel_whois.body.provider | String | Name of provider. |
| IdentityRecordedFuture.PlaybookAlerts.panel_whois.body.value.createdDate | String | When was it created. |
| IdentityRecordedFuture.PlaybookAlerts.panel_whois.body.value.nameServers | Array | List of name server IDs. |
| IdentityRecordedFuture.PlaybookAlerts.panel_whois.body.value.privateRegistration | Bool | Boolean indicating private registration. |
| IdentityRecordedFuture.PlaybookAlerts.panel_whois.body.value.registrarName | String | Name of the registrar. |
| IdentityRecordedFuture.PlaybookAlerts.panel_whois.body.value.status | String | Status of registrar. |
| IdentityRecordedFuture.PlaybookAlerts.panel_whois.body.value.city | String | Contact located in this city. |
| IdentityRecordedFuture.PlaybookAlerts.panel_whois.body.value.country | String | Contact located in this city. |
| IdentityRecordedFuture.PlaybookAlerts.panel_whois.body.value.name | String | Name of contact. |
| IdentityRecordedFuture.PlaybookAlerts.panel_whois.body.value.organization | String | Name of contact organization. |
| IdentityRecordedFuture.PlaybookAlerts.panel_whois.body.value.postalCode | String | Postal code of contact organization. |
| IdentityRecordedFuture.PlaybookAlerts.panel_whois.body.value.state | String | Contact located in state. |
| IdentityRecordedFuture.PlaybookAlerts.panel_whois.body.value.street1 | String | Street name of contact. |
| IdentityRecordedFuture.PlaybookAlerts.panel_whois.body.value.telephone | String | Phone number of contact. |
| IdentityRecordedFuture.PlaybookAlerts.panel_whois.body.value.type | String | Type of contact. |
Command Example
!recordedfuture-identity-playbook-alerts-details alert_ids="12312312-1231-1231-1231-123123123123" detail_sections="status,log"
recordedfuture-identity-playbook-alerts-update
Update the status of one or multiple Playbook alerts.
Base Command
recordedfuture-identity-playbook-alerts-update
Input
| Argument Name | Description | Required |
|---|---|---|
| alert_ids | IDs of the playbook alerts that will be updated. | Required |
| new_status | New status to set for all alerts in alert_ids (e.g., new, in-progress, dismissed, resolved). | Required |
| add_actions_taken | Add ‘actions taken’ to all alerts in alert_ids. Can only be used with ‘new_status=resolved’ or ‘new_status=dismissed’. Possible values: enforced_password_reset, placed_in_risky_group, reviewed_incident_report, account_disabled_or_terminated, account_remediated, other. | Optional |
| remove_actions_taken | Remove ‘actions taken’ from all alerts in alert_ids. You can specify multiple values by providing a quoted string with values separated by commas. Possible values: enforced_password_reset, placed_in_risky_group, reviewed_incident_report, account_disabled_or_terminated, account_remediated, other. | Optional |
| comment | Add a comment to all alerts in alert_ids. | Optional |
| reopen | Set the reopen strategy for the alert. Reopen on significant updates or keep the alert Resolved. Default: reopen on significant updates. Can only be used with new_status=resolved. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| IdentityRecordedFuture.PlaybookAlerts.playbook_alert_id | string | Unique ID of the playbook alert in Recorded Future. |
| IdentityRecordedFuture.PlaybookAlerts.current_status | string | Current status of playbook alert in Recorded Future. |
| IdentityRecordedFuture.PlaybookAlerts.title | string | Title of the playbook alert in Recorded Future. |
| IdentityRecordedFuture.PlaybookAlerts.status_message | string | Message describing the outcome of the update. |
Command Example
!recordedfuture-identity-playbook-alerts-update alert_ids="12312312-1231-1231-1231-123123123123" new_status="in-progress"
!recordedfuture-identity-playbook-alerts-update alert_ids="12312312-1231-1231-1231-123123123123" new_status="resolved" comment="Resolved in XSOAR" add_actions_taken=""
Configuration parameters
server_url— Server URL (e.g., https://api.recordedfuture.com/gw/xsoar-identity) (required)token— API Tokencredential—unsecure— Trust any certificate (not secure)proxy— Use system proxy settingsisFetch— Fetch incidentsincidentType— Incident typeincidentFetchInterval— Incidents Fetch Intervalfirst_fetch— First Incident Fetch: Time Rangemax_fetch— Maximum number of incidents per fetchpa_statuses— Playbook Alerts: Fetched Statusespa_priorities— Playbook Alerts: Fetched Priorities Thresholdpassword_properties— Password propertieslimit_identities— Limit of identities to get (min is 0 and max is 10 000)domains— Domains list separated by comma (e.g. norsegods.online, norsegods.online ). This will be used for identity search command as a default value. (required)
Commands (6)
-
recordedfuture-identity-lookupLookup for identities in Recorded Future Identity Dataset.
-
recordedfuture-identity-playbook-alerts-detailsGet Playbook alert details by id.
-
recordedfuture-identity-playbook-alerts-searchSearch playbook alerts based on filters.
-
recordedfuture-identity-playbook-alerts-updateUpdate the status of one or multiple Playbook alerts.
-
recordedfuture-identity-searchSearch for identities in Recorded Future Identity Dataset.
-
recordedfuture-password-lookupLookup for password in Recorded Future Dataset.
from pathlib import Path from unittest.mock import Mock, patch import pytest import vcr as vcrpy from IdentityRecordedFuture import Client CASSETTES = Path(__file__).parent / "test_data" DATETIME_STR_VALUE = "2021-12-08T12:10:21.837Z" def filter_out_whoami(response): import json body = response["body"]["string"] try: body.decode("utf-8") json_blob = json.loads(body) json_blob.pop("api_key", None) response["body"]["string"] = json.dumps(json_blob).encode("utf-8") except UnicodeDecodeError: pass # It's not a json string return response vcr = vcrpy.VCR( serializer="yaml", cassette_library_dir=str(CASSETTES), record_mode="once", filter_headers=[("X-RFToken", "XXXXXX")], before_record_response=filter_out_whoami, ) def util_load_json(path): import json with open(path, encoding="utf-8") as f: return json.loads(f.read()) def create_client() -> Client: import os base_url = "https://api.recordedfuture.com/gw/xsoar-identity/" verify_ssl = True token = os.environ.get("RF_TOKEN") headers = { "X-RFToken": token, "X-RF-User-Agent": "Cortex_XSOAR/2.0 Cortex_XSOAR_unittest_0.1", } return Client(base_url=base_url, verify=verify_ssl, headers=headers, proxy=None) @patch("IdentityRecordedFuture.BaseClient._http_request") def test_client_whoami(mock_http_request: Mock) -> None: """Test whoami().""" client = create_client() response = {1: 1} mock_http_request.return_value = response result = client.whoami() assert response == result mock_http_request.assert_called_once_with( method="get", url_suffix="info/whoami", timeout=60, ) @patch("IdentityRecordedFuture.Client._call") def test_credentials_search(mock_call: Mock) -> None: """Test credentials_search().""" client = create_client() client.credentials_search() mock_call.assert_called_once_with(url_suffix="/v2/identity/credentials/search") @patch("IdentityRecordedFuture.Actions._process_result_actions") def test_credentials_search_process_result(process_result_mock: Mock) -> None: """Test search identities code.""" from IdentityRecordedFuture import Actions client = Mock() actions = Actions(client) some_random_data = "data" client.credentials_search.return_value = some_random_data process_result_mock.return_value = some_random_data action_result = actions.identity_search_command() assert some_random_data == action_result client.credentials_search.assert_called_once() process_result_mock.assert_called_once_with(response=some_random_data) @patch("IdentityRecordedFuture.Client._call") def test_credentials_lookup(mock_call: Mock) -> None: """Test credentials_lookup().""" client = create_client() client.credentials_lookup() mock_call.assert_called_once_with(url_suffix="/v2/identity/credentials/lookup") @patch("IdentityRecordedFuture.Actions._process_result_actions") def test_credentials_lookup_process_result(process_result_mock: Mock) -> None: """Test lookup identities code.""" from IdentityRecordedFuture import Actions client = Mock() actions = Actions(client) some_random_data = "data" client.credentials_lookup.return_value = some_random_data process_result_mock.return_value = some_random_data action_result = actions.identity_lookup_command() assert some_random_data == action_result client.credentials_lookup.assert_called_once() process_result_mock.assert_called_once_with(response=some_random_data) @patch("IdentityRecordedFuture.Client._call") def test_password_lookup(mock_call: Mock) -> None: """Test password_lookup().""" client = create_client() client.password_lookup() mock_call.assert_called_once_with(url_suffix="/v2/identity/password/lookup") @patch("IdentityRecordedFuture.Actions._process_result_actions") def test_password_lookup_process_result(process_result_mock: Mock) -> None: """Test password lookup code.""" from IdentityRecordedFuture import Actions client = Mock() actions = Actions(client) some_random_data = "data" client.password_lookup.return_value = some_random_data process_result_mock.return_value = some_random_data action_result = actions.password_lookup_command() assert some_random_data == action_result client.password_lookup.assert_called_once() process_result_mock.assert_called_once_with(response=some_random_data) @patch("IdentityRecordedFuture.demisto") @patch("IdentityRecordedFuture.BaseClient._http_request") def test_call(mock_http_request: Mock, mocked_demisto: Mock): """Test _call().""" client = create_client() STATUS_TO_RETRY = [500, 501, 502, 503, 504] mock_demisto_last_run = {"last_run": "mock"} mock_demisto_command = "command" mock_demisto_command_args = {"arg1": "arg1_value", "arg2": "arg2_value"} mock_demisto_params = {"someparam": "some param value"} mocked_demisto.command.return_value = mock_demisto_command mocked_demisto.getLastRun.return_value = mock_demisto_last_run mocked_demisto.args.return_value = mock_demisto_command_args mocked_demisto.params.return_value = mock_demisto_params http_response = {"some": "http response"} mock_http_request.return_value = http_response mock_url_suffix = "mock_url_suffix" result = client._call(url_suffix=mock_url_suffix) json_data = { "demisto_args": mock_demisto_command_args, "demisto_params": mock_demisto_params, "demisto_command": mock_demisto_command, "demisto_last_run": mock_demisto_last_run, } mock_http_request.assert_called_once_with( method="post", url_suffix=mock_url_suffix, json_data=json_data, timeout=90, retries=3, status_list_to_retry=STATUS_TO_RETRY, ) assert result == http_response @patch("IdentityRecordedFuture.return_error") @patch("IdentityRecordedFuture.BaseClient._http_request") def test_call_return_error(mock_http_request: Mock, return_error_mock: Mock): """Test _call() when error message was returned.""" from CommonServerPython import DemistoException client = create_client() mock_url_suffix = "mock_url_suffix" error_message_res = {"message": "error"} mock_error_response = Mock() mock_error_response.json.return_value = error_message_res mock_http_request.side_effect = DemistoException(message="", res=mock_error_response) client._call(url_suffix=mock_url_suffix) return_error_mock.assert_called_once_with(**error_message_res) @patch("IdentityRecordedFuture.BaseClient._http_request") def test_call_return_error_not_json(mock_http_request: Mock): """Test _call() when error message was returned and it is not json serializable.""" import json from CommonServerPython import DemistoException client = create_client() mock_url_suffix = "mock_url_suffix" mock_error_response = Mock() mock_error_response.json.side_effect = json.JSONDecodeError("some not json error", "some", 3) mock_http_request.side_effect = DemistoException(message="", res=mock_error_response) with pytest.raises(DemistoException): client._call(url_suffix=mock_url_suffix) @patch("IdentityRecordedFuture.CommandResults") @patch("IdentityRecordedFuture.BaseClient._http_request") def test_call_return_http_404_error(mock_http_request: Mock, command_results_mock: Mock): """Test _call() when error message was returned.""" from CommonServerPython import DemistoException client = create_client() mock_url_suffix = "mock_url_suffix" mock_http_request.side_effect = DemistoException("There is HTTP 404 error") client._call(url_suffix=mock_url_suffix) command_results_mock.assert_called_once_with( outputs_prefix="", outputs={}, raw_response={}, readable_output="No results found.", outputs_key_field="", ) @patch("IdentityRecordedFuture.BaseClient._http_request") def test_call_return_http_error(mock_http_request: Mock): """Test _call() when error message was returned.""" from CommonServerPython import DemistoException client = create_client() mock_url_suffix = "mock_url_suffix" mock_http_request.side_effect = DemistoException("Some error from the Server") with pytest.raises(DemistoException): client._call(url_suffix=mock_url_suffix) def test_call_with_kwargs(mocker): """ Test the `_call` method to ensure it correctly processes additional keyword arguments and sends a request with the expected parameters. """ import os import demistomock as demisto client = create_client() STATUS_TO_RETRY = [500, 501, 502, 503, 504] # This is needed for CommonServerPython module to not add demisto.params() into callingContext. os.environ["COMMON_SERVER_NO_AUTO_PARAMS_REMOVE_NULLS"] = "True" # Mock demisto command and args. mock_command_name = "command_name" mock_command_args = {"arg1": "arg1_value", "arg2": "arg2_value"} mocker.patch.object(demisto, "command", return_value=mock_command_name) mocker.patch.object(demisto, "args", return_value=mock_command_args) mock_http_request = mocker.patch.object(client, "_http_request") mock_url_suffix = "mock_url_suffix" client._call(url_suffix=mock_url_suffix, timeout=120, any_other_kwarg=True) json_data = { "demisto_command": mock_command_name, "demisto_args": mock_command_args, "demisto_params": {}, "demisto_last_run": {"lastRun": "2018-10-24T14:13:20+00:00"}, } mock_http_request.assert_called_once_with( method="post", url_suffix=mock_url_suffix, json_data=json_data, timeout=120, retries=3, status_list_to_retry=STATUS_TO_RETRY, any_other_kwarg=True, ) def test_call_returns_response(mocker): """ Test that the `_call` method correctly returns the response from the HTTP request. """ import os import demistomock as demisto client = create_client() # This is needed for CommonServerPython module to not add demisto.params() into callingContext. os.environ["COMMON_SERVER_NO_AUTO_PARAMS_REMOVE_NULLS"] = "True" # Mock demisto command and args. mock_command_name = "command_name" mock_command_args = {"arg1": "arg1_value", "arg2": "arg2_value"} mocker.patch.object(demisto, "command", return_value=mock_command_name) mocker.patch.object(demisto, "args", return_value=mock_command_args) mock_response = {"response": {"data": "mock data"}} mocker.patch.object(client, "_http_request", return_value=mock_response) mock_url_suffix = "mock_url_suffix" response = client._call(url_suffix=mock_url_suffix) assert response == mock_response def test_call_response_processing_return_error(mocker): """ Test that the `_call` method correctly processes a return_error response. """ import os import demistomock as demisto client = create_client() STATUS_TO_RETRY = [500, 501, 502, 503, 504] # This is needed for CommonServerPython module to not add demisto.params() into callingContext. os.environ["COMMON_SERVER_NO_AUTO_PARAMS_REMOVE_NULLS"] = "True" # Mock demisto command and args. mock_command_name = "command_name" mock_command_args = {"arg1": "arg1_value", "arg2": "arg2_value"} mocker.patch.object(demisto, "command", return_value=mock_command_name) mocker.patch.object(demisto, "args", return_value=mock_command_args) mock_return_error = mocker.patch("IdentityRecordedFuture.return_error") mock_http_request = mocker.patch.object( client, "_http_request", return_value={"return_error": {"message": "mock error"}}, ) mock_url_suffix = "mock_url_suffix" client._call(url_suffix=mock_url_suffix) json_data = { "demisto_command": mock_command_name, "demisto_args": mock_command_args, "demisto_params": {}, "demisto_last_run": {"lastRun": "2018-10-24T14:13:20+00:00"}, } mock_http_request.assert_called_once_with( method="post", url_suffix=mock_url_suffix, json_data=json_data, timeout=90, retries=3, status_list_to_retry=STATUS_TO_RETRY, ) mock_return_error.assert_called_once_with(message="mock error") def test_call_response_processing_404(mocker): """ Test that the `_call` method correctly handles a 404 error response and returns the appropriate CommandResults. """ import os import demistomock as demisto from CommonServerPython import CommandResults, DemistoException client = create_client() STATUS_TO_RETRY = [500, 501, 502, 503, 504] # This is needed for CommonServerPython module to not add demisto.params() into callingContext. os.environ["COMMON_SERVER_NO_AUTO_PARAMS_REMOVE_NULLS"] = "True" # Mock demisto command and args. mock_command_name = "command_name" mock_command_args = {"arg1": "arg1_value", "arg2": "arg2_value"} mocker.patch.object(demisto, "command", return_value=mock_command_name) mocker.patch.object(demisto, "args", return_value=mock_command_args) mocker.patch("IdentityRecordedFuture.return_error") def mock_http_request_method(*args, **kwargs): # Imitate how CommonServerPython handles bad responses (when status code not in ok_codes, # or if ok_codes=None - it uses requests.Response.ok to check whether response is good). raise DemistoException("404") mocker.patch.object(client, "_http_request", mock_http_request_method) spy_http_request = mocker.spy(client, "_http_request") mock_url_suffix = "mock_url_suffix" result = client._call(url_suffix=mock_url_suffix) json_data = { "demisto_command": mock_command_name, "demisto_args": mock_command_args, "demisto_params": {}, "demisto_last_run": {"lastRun": "2018-10-24T14:13:20+00:00"}, } spy_http_request.assert_called_once_with( method="post", url_suffix=mock_url_suffix, json_data=json_data, timeout=90, retries=3, status_list_to_retry=STATUS_TO_RETRY, ) assert isinstance(result, CommandResults) assert result.outputs_prefix == "" assert result.outputs_key_field == "" assert result.outputs == {} assert result.raw_response == {} assert result.readable_output == "No results found." def test_call_DemistoException_res_json_error(mocker): """Test _call when err.res.json() raises an exception (e.g., JSONDecodeError).""" import json import demistomock as demisto from CommonServerPython import DemistoException client = create_client() mocker.patch.object(demisto, "command", return_value="command_name") mocker.patch.object(demisto, "args", return_value={}) mocker.patch.object(demisto, "params", return_value={}) mocker.patch.object(demisto, "getLastRun", return_value={}) class MockResponse: def json(self): raise json.JSONDecodeError("Expecting value", "doc", 0) def mock_http_request(*args, **kwargs): err = DemistoException("Error with response") err.res = MockResponse() raise err mocker.patch.object(client, "_http_request", side_effect=mock_http_request) with pytest.raises(DemistoException): client._call(url_suffix="mock_url_suffix") def test_call_DemistoException_res_None(mocker): """Test _call when DemistoException has no response (err.res is None).""" import demistomock as demisto from CommonServerPython import DemistoException client = create_client() mocker.patch.object(demisto, "command", return_value="command_name") mocker.patch.object(demisto, "args", return_value={}) mocker.patch.object(demisto, "params", return_value={}) mocker.patch.object(demisto, "getLastRun", return_value={}) def mock_http_request(*args, **kwargs): err = DemistoException("Some error without response") err.res = None raise err mocker.patch.object(client, "_http_request", side_effect=mock_http_request) with pytest.raises(DemistoException) as excinfo: client._call(url_suffix="mock_url_suffix") assert str(excinfo.value) == "Some error without response" def test_fetch_incidents(mocker): """ Test the `fetch_incidents` method to ensure it sends the correct request and processes the response as expected. """ import os import demistomock as demisto client = create_client() # This is needed for CommonServerPython module to not add demisto.params() into callingContext. os.environ["COMMON_SERVER_NO_AUTO_PARAMS_REMOVE_NULLS"] = "True" # Mock demisto command and args. mock_command_name = "command_name" mock_command_args = {"arg1": "arg1_value", "arg2": "arg2_value"} mock_params = {"param1": "param1 value"} mocker.patch.object(demisto, "command", return_value=mock_command_name) mocker.patch.object(demisto, "args", return_value=mock_command_args) mocker.patch.object(demisto, "params", return_value=mock_params) mock_last_run_dict = {"lastRun": "2022-08-31T12:12:20+00:00"} mocker.patch.object(demisto, "getLastRun", return_value=mock_last_run_dict) mock_call_response = {"response": {"data": "mock response"}} mock_call = mocker.patch.object(client, "_call", return_value=mock_call_response) response = client.fetch_incidents() mock_call.assert_called_once_with( timeout=120, url_suffix="/playbook_alert/fetch", ) assert response == mock_call_response def test_playbook_alert_search(mocker): """ Test the `search_playbook_alerts` method to ensure it sends the correct request and processes the response as expected. """ import os import demistomock as demisto client = create_client() # This is needed for CommonServerPython module to not add demisto.params() into callingContext. os.environ["COMMON_SERVER_NO_AUTO_PARAMS_REMOVE_NULLS"] = "True" # Mock demisto command and args. mock_command_name = "command_name" mock_command_args = {"arg1": "arg1_value", "arg2": "arg2_value"} mocker.patch.object(demisto, "command", return_value=mock_command_name) mocker.patch.object(demisto, "args", return_value=mock_command_args) mock_call_response = {"response": {"data": "mock response"}} mock_call = mocker.patch.object(client, "_call", return_value=mock_call_response) response = client.search_playbook_alerts() mock_call.assert_called_once_with(url_suffix="/playbook_alert/search") assert response == mock_call_response def test_playbook_alert_details_multi_input(mocker): """ Test the `details_playbook_alerts` method to ensure it correctly processes multiple input arguments. """ import os import demistomock as demisto client = create_client() os.environ["COMMON_SERVER_NO_AUTO_PARAMS_REMOVE_NULLS"] = "True" mock_command_name = "command_name" mock_alert_ids = "input1,mock_value" mock_detail_sections = "input1,mock_value" mock_command_args = { "alert_ids": mock_alert_ids, "detail_sections": mock_detail_sections, } mocker.patch.object(demisto, "command", return_value=mock_command_name) mocker.patch.object(demisto, "args", return_value=mock_command_args) mock_call_response = {"response": {"data": "mock response"}} mock_call = mocker.patch.object(client, "_call", return_value=mock_call_response) response = client.details_playbook_alerts() mock_call.assert_called_once_with(url_suffix="/playbook_alert/lookup") assert response == mock_call_response def test_playbook_alert_update_multi_input(mocker): """ Test the `update_playbook_alerts` method to ensure it correctly processes multiple input arguments. """ import os import demistomock as demisto client = create_client() os.environ["COMMON_SERVER_NO_AUTO_PARAMS_REMOVE_NULLS"] = "True" mock_command_name = "command_name" mock_alert_ids = "input1,input2" mock_command_args = {"alert_ids": mock_alert_ids} mocker.patch.object(demisto, "command", return_value=mock_command_name) mocker.patch.object(demisto, "args", return_value=mock_command_args) mock_call_response = {"response": {"data": "mock response"}} mock_call = mocker.patch.object(client, "_call", return_value=mock_call_response) response = client.update_playbook_alerts() mock_call.assert_called_once_with(url_suffix="/playbook_alert/update") assert response == mock_call_response def test_playbook_alert_search_multi_input(mocker): """ Test the `search_playbook_alerts` method to ensure it correctly processes multiple input arguments. """ import os import demistomock as demisto client = create_client() os.environ["COMMON_SERVER_NO_AUTO_PARAMS_REMOVE_NULLS"] = "True" mock_command_name = "command_name" mock_priority = "high" mock_detail_sections = "sdadwa,adinhw0ijd" mock_command_args = { "priority": mock_priority, "playbook_alert_status": mock_detail_sections, } mocker.patch.object(demisto, "command", return_value=mock_command_name) mocker.patch.object(demisto, "args", return_value=mock_command_args) mock_call_response = {"response": {"data": "mock response"}} mock_call = mocker.patch.object(client, "_call", return_value=mock_call_response) response = client.search_playbook_alerts() mock_call.assert_called_once_with(url_suffix="/playbook_alert/search") assert response == mock_call_response def test_playbook_alert_details(mocker): """ Test the `details_playbook_alerts` method to ensure it sends the correct request and processes the response as expected. """ import os import demistomock as demisto client = create_client() # This is needed for CommonServerPython module to not add demisto.params() into callingContext. os.environ["COMMON_SERVER_NO_AUTO_PARAMS_REMOVE_NULLS"] = "True" # Mock demisto command and args. mock_command_name = "command_name" mock_command_args = {"arg1": "arg1_value", "arg2": "arg2_value"} mocker.patch.object(demisto, "command", return_value=mock_command_name) mocker.patch.object(demisto, "args", return_value=mock_command_args) mock_call_response = {"response": {"data": "mock response"}} mock_call = mocker.patch.object(client, "_call", return_value=mock_call_response) response = client.details_playbook_alerts() mock_call.assert_called_once_with(url_suffix="/playbook_alert/lookup") assert response == mock_call_response def test_playbook_alert_update(mocker): """ Test the `update_playbook_alerts` method to ensure it sends the correct request and processes the response as expected. """ import os import demistomock as demisto client = create_client() # This is needed for CommonServerPython module to not add demisto.params() into callingContext. os.environ["COMMON_SERVER_NO_AUTO_PARAMS_REMOVE_NULLS"] = "True" # Mock demisto command and args. mock_command_name = "command_name" mock_command_args = {"arg1": "arg1_value", "arg2": "arg2_value"} mocker.patch.object(demisto, "command", return_value=mock_command_name) mocker.patch.object(demisto, "args", return_value=mock_command_args) mock_call_response = {"response": {"data": "mock response"}} mock_call = mocker.patch.object(client, "_call", return_value=mock_call_response) response = client.update_playbook_alerts() mock_call.assert_called_once_with(url_suffix="/playbook_alert/update") assert response == mock_call_response def test_actions_init(mocker): """ Test the initialization of the `Actions` class to ensure the client is correctly assigned. """ from IdentityRecordedFuture import Actions mock_client = mocker.Mock() actions = Actions(mock_client) assert actions.client == mock_client def test_process_result_actions_returns_list() -> None: """Test result processing function with the case when we received 404 error.""" from CommonServerPython import CommandResults from IdentityRecordedFuture import Actions client = Mock() actions = Actions(client) response = CommandResults() result = actions._process_result_actions(response) assert isinstance(result, list) def test_process_result_actions_returns_none() -> None: from IdentityRecordedFuture import Actions client = Mock() actions = Actions(client) response = {} result = actions._process_result_actions(response) assert result is None def test_process_result_actions_404_error() -> None: """Test result processing function with the case when we received 404 error.""" from CommonServerPython import CommandResults from IdentityRecordedFuture import Actions client = Mock() actions = Actions(client) response = CommandResults() result = actions._process_result_actions(response) assert [response] == result def test_process_result_actions_wrong_type() -> None: """Test result processing function with the case when we received string data.""" from IdentityRecordedFuture import Actions client = Mock() actions = Actions(client) response = "Some bad response from API" result = actions._process_result_actions(response) assert result is None def test_process_result_actions_no_key_value() -> None: """Test result processing function with the case when we received date without action_result key.""" from IdentityRecordedFuture import Actions client = Mock() actions = Actions(client) response = {} result = actions._process_result_actions(response) assert result is None def test_process_result_actions() -> None: """Test result processing function with the case when we received good data.""" from CommonServerPython import CommandResults from IdentityRecordedFuture import Actions client = Mock() actions = Actions(client) response = {"action_result": {"readable_output": "data"}} results = actions._process_result_actions(response) for x in results: assert isinstance(x, CommandResults) def test_process_result_actions_404(mocker): """ Test the `_process_result_actions` method to ensure it handles a CommandResults response correctly. """ from CommonServerPython import CommandResults from IdentityRecordedFuture import Actions client = Mock() actions = Actions(client) # Test if response is CommandResults # (case when we got 404 on response, and it was processed in client._call() method). response = CommandResults(readable_output="Mock") result_actions = actions._process_result_actions(response=response) assert result_actions == [response] def test_process_result_actions_response_is_not_dict(mocker): """ Test the `_process_result_actions` method to ensure it returns None when the response is not a dictionary. """ from IdentityRecordedFuture import Actions client = Mock() actions = Actions(client) # Test if response is not CommandResults and not Dict. response = "Mock string - not CommandResults and not dict" result_actions = actions._process_result_actions(response=response) # type: ignore assert result_actions is None def test_process_result_actions_no_or_empty_result_actions_in_response(mocker): """ Test the `_process_result_actions` method to ensure it returns None when there are no result actions in the response. """ from IdentityRecordedFuture import Actions client = Mock() actions = Actions(client) # Test no results_actions in response. response = {"data": "mock"} result_actions = actions._process_result_actions(response=response) assert result_actions is None # Test case when bool(results_actions) in response is False. response = {"data": "mock", "result_actions": None} result_actions = actions._process_result_actions(response=response) assert result_actions is None response = {"data": "mock", "result_actions": []} result_actions = actions._process_result_actions(response=response) assert result_actions is None response = {"data": "mock", "result_actions": {}} result_actions = actions._process_result_actions(response=response) assert result_actions is None def test_process_result_actions_command_results_only(mocker): """ Test the `_process_result_actions` method to ensure it processes a response containing CommandResults correctly. """ from IdentityRecordedFuture import Actions, CommandResults client = Mock() actions = Actions(client) response = { "data": "mock", "result_actions": [ { "CommandResults": { "outputs_prefix": "mock_outputs_prefix", "outputs": "mock_outputs", "raw_response": "mock_raw_response", "readable_output": "mock_readable_output", "outputs_key_field": "mock_outputs_key_field", }, } ], } result_actions = actions._process_result_actions(response=response) assert len(result_actions) == 1 r_a = result_actions[0] assert isinstance(r_a, CommandResults) assert r_a.outputs_prefix == "mock_outputs_prefix" assert r_a.outputs == "mock_outputs" assert r_a.raw_response == "mock_raw_response" assert r_a.readable_output == "mock_readable_output" assert r_a.outputs_key_field == "mock_outputs_key_field" def test_process_result_actions_with_invalid_actions(mocker): """Test processing result actions with invalid keys.""" from CommonServerPython import CommandResults from IdentityRecordedFuture import Actions actions = Actions(rf_client=None) response = { "result_actions": [ {"InvalidKey": {}}, { "CommandResults": { "outputs_prefix": "mock_prefix", "outputs": "mock_outputs", } }, { "CommandResults": { "outputs_prefix": "another_prefix", "outputs": "another_outputs", } }, ] } result = actions._process_result_actions(response) assert len(result) == 2 assert isinstance(result[0], CommandResults) assert result[0].outputs_prefix == "mock_prefix" assert result[0].outputs == "mock_outputs" assert isinstance(result[1], CommandResults) assert result[1].outputs_prefix == "another_prefix" assert result[1].outputs == "another_outputs" def test_fetch_incidents_with_attachment(mocker): """ Test the `fetch_incidents` method to ensure it correctly processes incidents with attachments. """ import json import CommonServerPython as csp import demistomock as demisto from IdentityRecordedFuture import Actions client = create_client() screenshot_dict = { "panel_evidence_summary": { "screenshots": [ { "image_id": "an_id", "base64": "YWJhc2U2NHN0cmluZw==", "description": "vivid description of image", } ] } } mock_incidents_value = { "name": "incident_name", "rawJSON": json.dumps(screenshot_dict), } mock_demisto_last_run_value = "mock_demisto_last_run" mock_client_fetch_incidents_response = { "incidents": [mock_incidents_value], "demisto_last_run": mock_demisto_last_run_value, } mock_client_fetch_incidents = mocker.patch.object( client, "fetch_incidents", return_value=mock_client_fetch_incidents_response ) mock_demisto_incidents = mocker.patch.object(demisto, "incidents") mock_demisto_set_last_run = mocker.patch.object(demisto, "setLastRun") mock_file_result = mocker.patch.object( csp, "fileResult", return_value={"File": "mockfilepath", "FileID": "mock_file_id"}, ) mock_incidents_value.update( { "attachment": { "description": "vivid description of image", "showMediaFile": True, }.update(mock_file_result) } ) actions = Actions(client) actions.fetch_incidents() mock_client_fetch_incidents.assert_called_once_with() mock_demisto_incidents.assert_called_once_with([mock_incidents_value]) mock_demisto_set_last_run.assert_called_once_with(mock_demisto_last_run_value) def test_transform_incidents_attachments_without_screenshots(mocker): """Test transforming incidents without screenshots.""" import json from IdentityRecordedFuture import Actions incidents = [{"rawJSON": json.dumps({"panel_evidence_summary": {}})}] mock_fileResult = mocker.patch("IdentityRecordedFuture.fileResult") Actions._transform_incidents_attachments(incidents) assert "attachment" not in incidents[0] mock_fileResult.assert_not_called() def test_fetch_incidents_with_incidents_present(mocker): """ Test the `fetch_incidents` method to ensure it correctly processes incidents when incidents are present in the response. """ import demistomock as demisto from IdentityRecordedFuture import Actions client = create_client() mock_incidents_value = [ {"mock_incident_key1": "mock_incident_value1"}, {"mock_incident_key2": "mock_incident_value2"}, ] mock_demisto_last_run_value = "mock_demisto_last_run" mock_alerts_update_data_value = "mock_alerts_update_data_value" mock_client_fetch_incidents_response = { "incidents": mock_incidents_value, "demisto_last_run": mock_demisto_last_run_value, "data": "mock", "alerts_update_data": mock_alerts_update_data_value, } mock_client_fetch_incidents = mocker.patch.object( client, "fetch_incidents", return_value=mock_client_fetch_incidents_response ) mock_demisto_incidents = mocker.patch.object(demisto, "incidents") mock_demisto_set_last_run = mocker.patch.object(demisto, "setLastRun") actions = Actions(client) actions.fetch_incidents() mock_client_fetch_incidents.assert_called_once_with() mock_demisto_incidents.assert_called_once_with(mock_incidents_value) mock_demisto_set_last_run.assert_called_once_with(mock_demisto_last_run_value) def test_playbook_alert_details_command_with_result_actions(mocker): """ Test the `playbook_alert_details_command` method to ensure it correctly processes result actions. """ from IdentityRecordedFuture import Actions client = create_client() mock_response = "mock_response" mock_client_playbook_alert_details = mocker.patch.object(client, "details_playbook_alerts", return_value=mock_response) actions = Actions(client) mock_process_result_actions_return_value = "mock_process_result_actions_return_value" mock_process_result_actions = mocker.patch.object( actions, "_process_result_actions", return_value=mock_process_result_actions_return_value, ) result = actions.playbook_alert_details_command() mock_client_playbook_alert_details.assert_called_once_with() mock_process_result_actions.assert_called_once_with(response=mock_response) # As there are some result actions - return those result actions. assert result == mock_process_result_actions_return_value def test_playbook_alert_details_command_without_result_actions(mocker): """ Test the `playbook_alert_details_command` method to ensure it handles the case when there are no result actions. """ from IdentityRecordedFuture import Actions client = create_client() mock_response = "mock_response" mock_client_playbook_alert_details = mocker.patch.object(client, "details_playbook_alerts", return_value=mock_response) actions = Actions(client) mock_process_result_actions_return_value = None mock_process_result_actions = mocker.patch.object( actions, "_process_result_actions", return_value=mock_process_result_actions_return_value, ) actions.playbook_alert_details_command() mock_client_playbook_alert_details.assert_called_once_with() mock_process_result_actions.assert_called_once_with(response=mock_response) def test_playbook_alert_search_command_without_result_actions(mocker): """ Test the `playbook_alert_search_command` method to ensure it handles the case when there are no result actions. """ from IdentityRecordedFuture import Actions client = create_client() mock_response = "mock_response" mock_client_playbook_alert_search = mocker.patch.object(client, "search_playbook_alerts", return_value=mock_response) actions = Actions(client) mock_process_result_actions_return_value = None mock_process_result_actions = mocker.patch.object( actions, "_process_result_actions", return_value=mock_process_result_actions_return_value, ) actions.playbook_alert_search_command() mock_client_playbook_alert_search.assert_called_once_with() mock_process_result_actions.assert_called_once_with(response=mock_response) def test_playbook_alert_update_command(mocker): """ Test the `playbook_alert_update_command` method to ensure it correctly processes result actions. """ from IdentityRecordedFuture import Actions client = create_client() mock_response = "mock_response" mock_client_alert_set_status = mocker.patch.object(client, "update_playbook_alerts", return_value=mock_response) actions = Actions(client) mock_process_result_actions_return_value = "mock_process_result_actions_return_value" mock_process_result_actions = mocker.patch.object( actions, "_process_result_actions", return_value=mock_process_result_actions_return_value, ) result = actions.playbook_alert_update_command() mock_client_alert_set_status.assert_called_once_with() mock_process_result_actions.assert_called_once_with(response=mock_response) assert result == mock_process_result_actions_return_value def test_test_module(mocker): """ Test the `test-module` command to ensure it verifies the integration setup correctly. """ import platform import demistomock as demisto import IdentityRecordedFuture mocker.patch.object(demisto, "command", return_value="test-module") mocker.patch.object(demisto, "demistoVersion", return_value={"version": "mock_version"}) mocker.patch.object(demisto, "params", return_value={"credential": {"password": "example"}}) mocker.patch.object(platform, "platform", return_value="mock_platform") mocker.patch.object(IdentityRecordedFuture.Client, "whoami") mocked_return_res = mocker.patch.object(IdentityRecordedFuture, "return_results") IdentityRecordedFuture.main() mocked_return_res.assert_called_with("ok") def test_test_module_with_boom(mocker): """ Test the `test-module` command to ensure it handles exceptions and returns the appropriate error message. """ import platform import demistomock as demisto import IdentityRecordedFuture mocker.patch.object(demisto, "command", return_value="test-module") mocker.patch.object(demisto, "demistoVersion", return_value={"version": "mock_version"}) mocker.patch.object(demisto, "params", return_value={"credential": {"password": "example"}}) mocker.patch.object(platform, "platform", return_value="mock_platform") mock_whoami = mocker.patch.object(IdentityRecordedFuture.Client, "whoami") mock_whoami.side_effect = Exception("Side effect triggered") mocked_return_err = mocker.patch.object(IdentityRecordedFuture, "return_error") IdentityRecordedFuture.main() mocked_return_err.assert_called_with( message=( f"Failed to execute {demisto.command()} command. Error: Failed due to - " "Unknown error. Please verify that the API URL and Token are correctly configured. " "RAW Error: Side effect triggered" ), error=mocker.ANY, ) @patch("IdentityRecordedFuture.demisto") @patch("IdentityRecordedFuture.Client") @patch("IdentityRecordedFuture.Actions") def test_main_general( actions_mock: Mock, client_mock: Mock, mocked_demisto: Mock, ): """Test main function if it runs correctly and calls general functions""" import IdentityRecordedFuture # Mocking a known command to ensure that the 'else' block is not triggered mocked_demisto.command.return_value = "test-module" mocked_demisto.params.return_value = { "server_url": "https://example.com", "credential": {"password": "example"}, } IdentityRecordedFuture.main() client_mock.assert_called_once() mocked_demisto.params.assert_called_once_with() mocked_demisto.command.assert_called_once_with() actions_mock.assert_called_once_with(client_mock.return_value) def test_main_with_unknown_command(mocker): """Test main function with an unknown command.""" import demistomock as demisto import IdentityRecordedFuture mocker.patch.object(demisto, "command", return_value="unknown-command") mock_return_error = mocker.patch("IdentityRecordedFuture.return_error") mock_get_client = mocker.patch("IdentityRecordedFuture.get_client") IdentityRecordedFuture.main() mock_get_client.assert_called_once() mock_return_error.assert_called_once_with(message="Unknown command: unknown-command") def test_main_exception_handling(mocker): """Test main function's exception handling.""" import demistomock as demisto import IdentityRecordedFuture mocker.patch.object(demisto, "command", return_value="test-module") mock_get_client = mocker.patch("IdentityRecordedFuture.get_client") mock_get_client.return_value.whoami.side_effect = Exception("Test exception") mock_return_error = mocker.patch("IdentityRecordedFuture.return_error") IdentityRecordedFuture.main() mock_get_client.assert_called_once() mock_return_error.assert_called_once_with( message=( "Failed to execute test-module command. Error: Failed due to - Unknown error. " "Please verify that the API URL and Token are correctly configured. RAW Error: Test exception" ), error=mocker.ANY, ) def test_get_client_no_api_token(mocker): """Test get_client when no API token is provided.""" import demistomock as demisto import IdentityRecordedFuture mock_params = { "server_url": "https://api.recordedfuture.com/gw/xsoar-identity/", "unsecure": False, "credential": {"password": None}, "token": None, } mocker.patch.object(demisto, "params", return_value=mock_params) mock_return_error = mocker.patch("IdentityRecordedFuture.return_error") proxies = {} IdentityRecordedFuture.get_client(proxies=proxies) mock_return_error.assert_called_once_with(message="Please provide a valid API token") @patch("IdentityRecordedFuture.Client") def test_get_client_with_proxy(client_mock, mocker): """Test get_client when proxy is used.""" import demistomock as demisto import IdentityRecordedFuture server_url = "https://api.recordedfuture.com/gw/xsoar-identity/" unsecure = False verify_ssl = not unsecure mock_params = { "server_url": server_url, "unsecure": unsecure, "credential": {"password": "example"}, "proxy": True, } mocker.patch.object(demisto, "params", return_value=mock_params) proxies = {"http": "example.com", "https": "example.com"} IdentityRecordedFuture.get_client(proxies=proxies) client_mock.assert_called_once_with( base_url=server_url.rstrip("/"), verify=verify_ssl, headers=mocker.ANY, proxy=bool(proxies), ) @patch("IdentityRecordedFuture.Client") def test_get_client_without_proxy(client_mock, mocker): """Test get_client when proxy is not used.""" import demistomock as demisto import IdentityRecordedFuture server_url = "https://api.recordedfuture.com/gw/xsoar-identity/" unsecure = False verify_ssl = not unsecure mock_params = { "server_url": server_url, "unsecure": unsecure, "credential": {"password": "example"}, "proxy": False, } mocker.patch.object(demisto, "params", return_value=mock_params) proxies = {} IdentityRecordedFuture.get_client(proxies=proxies) client_mock.assert_called_once_with( base_url=server_url.rstrip("/"), verify=verify_ssl, headers=mocker.ANY, proxy=bool(proxies), ) def test_main_calls_handle_proxy(mocker): """ Test main function to ensure it calls handle_proxy() and provides proxies to the get_client. """ import demistomock as demisto import IdentityRecordedFuture mock_proxies = {"http": "example.com", "https": "example.com"} mock_handle_proxy = mocker.patch("IdentityRecordedFuture.handle_proxy", return_value=mock_proxies) mocker.patch.object(demisto, "command", return_value="test-module") mocker.patch.object(demisto, "params", return_value={"credential": {"password": "example"}}) mock_get_client = mocker.patch("IdentityRecordedFuture.get_client") mocker.patch("IdentityRecordedFuture.Client.whoami") mock_return_results = mocker.patch("IdentityRecordedFuture.return_results") IdentityRecordedFuture.main() mock_handle_proxy.assert_called_once() mock_get_client.assert_called_once_with(proxies=mock_proxies) mock_return_results.assert_called_once_with("ok")