FraudWatch

Manage incidents via the Fraudwatch API. FraudWatch International provides a fully managed Enterprise Digital Brand Protection Suite, including online brand management & monitoring, as well as providing other brand protection solutions that protect organizations and their customers around the world against online brand-related abuse.

Data Enrichment & Threat Intelligence · FraudWatch PhishPortal

Details

IDFraudWatch
ProviderNetcraft
CategoryData Enrichment & Threat Intelligence
From Version5.0.0
Docker Imagedemisto/python3:3.12.13.10116658
Supported ModulesAgentix XSIAM

README

Manage incidents via the Fraudwatch API. FraudWatch International provides a fully managed Enterprise Digital Brand Protection Suite, including online brand management & monitoring as well as providing other brand protection solutions that protect organizations and their customers around the world against online brand-related abuse.
This integration was integrated and tested with version v1 of FraudWatch Phishportal

Retrieve FraudWatch API token

  1. Navigate to the Phishportal site.
  2. Enter your Username and Password.
  3. On the left sidebar, click on ‘Portal API’.
  4. On the top of the page, you should see your API Token.
  5. If the API token is out of date, click on the Regenerate Token button adjacent to the API token, to generate a new token.
  6. Set the API token in the Password parameter.
    NOTE: no need to set the Username parameter it can be left blank (visible only on 6.0.2 and lower versions).

FraudWatch Retrieve API token

Configure FraudWatch on Cortex XSOAR

  1. Navigate to Settings > Integrations > Servers & Services.
  2. Search for FraudWatch.
  3. Click Add instance to create and configure a new integration instance.

    Parameter Description Required
    Fetch incidents   False
    API Token (leave empty. Fill in the API token in the password field.)   False
    Server URL URL of the server. True
    Brand Fetch incidents that match the specified brand. An error is encountered if the brand does not exist. A list of the existing brands can be retrieved by the ‘fraudwatch-brands-list’ command. False
    Status Fetch incidents that have the specified status. False
    Incident type   False
    Incidents Fetch Interval   False
    First fetch timestamp format: (<number> <time unit>, e.g., 12 hours, 1 day) False
    Maximum Incidents To Fetch   False
    Trust any certificate (not secure)   False
    Use system proxy settings   False
  4. Click Test to validate the URLs, token, and connection.

Fetch Incidents

Due to limitations in FraudWatch API, first fetch timestamp can be at most 1 day.

Commands

You can execute these commands from the Cortex XSOAR CLI, as part of an automation, or in a playbook.
After you successfully execute a command, a DBot message appears in the War Room with the command details.

fraudwatch-incidents-list


Get a list of incidents from FraudWatch service.

Base Command

fraudwatch-incidents-list

Input

Argument Name Description Required
brand Retrieve incidents that match the specified brand. An error is encountered if the brand does not exist. A list of the existing brands can be retrieved by the ‘fraudwatch-brands-list’ command. Optional
status Retrieve incidents that have the specified status. Possible values are: active, new, monitor, reactive, onhold, closed, closedmonitor, rejected, duplicate. Optional
limit Number of incidents to retrieve. Optional
page_size The number of incidents in a page. Maximum value is 200. Optional
page Retrieve incidents by the given page number. Optional
from Retrieve alerts for which ‘date opened’ is higher or equal to the ‘from’ value. Supports ISO and time range (<number> <time unit>, e.g., 12 hours, 7 days) formats. If not specified, the default value of ‘to’ is the current time. Optional
to Retrieve alerts for which ‘date opened’ is lower or equal to the ‘to’ value. Supports ISO and time range (<number> <time unit>, e.g., 12 hours, 7 days) formats. If not specified, the default value of ‘from’ is 12 months before ‘to’. Optional

Context Output

Path Type Description
FraudWatch.Incident.identifier String Incident identifier.
FraudWatch.Incident.reference_id String Incident reference ID.
FraudWatch.Incident.url String Main URL associated with the incident.
FraudWatch.Incident.status String Incident status.
FraudWatch.Incident.type String Incident type.
FraudWatch.Incident.brand String Incident brand.
FraudWatch.Incident.client String Incident client.
FraudWatch.Incident.content_ip String Incident content IP.
FraudWatch.Incident.host String Incident host.
FraudWatch.Incident.host_country String Incident host country.
FraudWatch.Incident.host_timezone String Incident host timezone.
FraudWatch.Incident.created_by String Who created the incident.
FraudWatch.Incident.discovered_by String Who discovered the incident.
FraudWatch.Incident.current_duration String Current duration of the incident.
FraudWatch.Incident.active_duration Unknown Current active duration of the incident.
FraudWatch.Incident.date_opened Date The date in which the incident was opened.
FraudWatch.Incident.date_closed Date The date in which the incident was closed.
FraudWatch.Incident.additional_urls String Additional URLs associated with the incident.
FraudWatch.Incident.link String Link to the incident page in the FraudWatch user interface.

Command Example

!fraudwatch-incidents-list brand="Testing Brand 2" from="2020-12-12" limit=3 status=monitor

Context Example

{
    "FraudWatch": {
        "Incident": [
            {
                "active_duration": null,
                "additional_urls": [
                    "http://www.malicious2.com",
                    "http://www.malicious3.com",
                    "http://www.malicious4.com",
                    "http://www.malicious5.com"
                ],
                "brand": "Testing Brand 2",
                "client": "Palo Alto",
                "content_ip": null,
                "created_by": "Client",
                "current_duration": "85882",
                "date_closed": null,
                "date_opened": "2021-02-07T15:37:12.000Z",
                "discovered_by": "client",
                "host": null,
                "host_country": null,
                "host_timezone": null,
                "identifier": "JJJ-595483",
                "link": "https://www.phishportal.com/client/incident/JJJ-595483",
                "reference_id": "abc1234",
                "status": "monitor",
                "type": "Vishing",
                "url": "http://www.malicious.com"
            },
            {
                "active_duration": null,
                "additional_urls": [
                    "http://www.malicious2.com",
                    "http://www.malicious3.com",
                    "http://www.malicious4.com",
                    "http://www.malicious5.com"
                ],
                "brand": "Testing Brand 2",
                "client": "Palo Alto",
                "content_ip": null,
                "created_by": "Client",
                "current_duration": "86649",
                "date_closed": null,
                "date_opened": "2021-02-07T15:24:25.000Z",
                "discovered_by": "client",
                "host": null,
                "host_country": null,
                "host_timezone": null,
                "identifier": "JJJ-992295",
                "link": "https://www.phishportal.com/client/incident/JJJ-992295",
                "reference_id": "abc1234",
                "status": "monitor",
                "type": "Vishing",
                "url": "http://www.malicious.com"
            },
            {
                "active_duration": null,
                "additional_urls": [
                    "abuse.com"
                ],
                "brand": "Testing Brand 2",
                "client": "Palo Alto",
                "content_ip": "192.168.0.1",
                "created_by": "Client",
                "current_duration": "340758",
                "date_closed": null,
                "date_opened": "2021-02-04T16:49:16.000Z",
                "discovered_by": "client",
                "host": null,
                "host_country": null,
                "host_timezone": null,
                "identifier": "JJJ-302171",
                "link": "https://www.phishportal.com/client/incident/JJJ-302171",
                "reference_id": "malicious1",
                "status": "monitor",
                "type": "Brand Abuse",
                "url": "http://malicious.com"
            }
        ]
    }
}

Human Readable Output

FraudWatch Incidents

identifier reference_id url status type brand client content_ip created_by discovered_by current_duration date_opened additional_urls link
JJJ-595483 abc1234 http://www.malicious.com monitor Vishing Testing Brand 2 Palo Alto   Client client 85882 2021-02-07T15:37:12.000Z http://www.malicious2.com,
http://www.malicious3.com,
http://www.malicious4.com,
http://www.malicious5.com
https://www.phishportal.com/client/incident/JJJ-595483
JJJ-992295 abc1234 http://www.malicious.com monitor Vishing Testing Brand 2 Palo Alto   Client client 86649 2021-02-07T15:24:25.000Z http://www.malicious2.com,
http://www.malicious3.com,
http://www.malicious4.com,
http://www.malicious5.com
https://www.phishportal.com/client/incident/JJJ-992295
JJJ-302171 malicious1 http://malicious.com monitor Brand Abuse Testing Brand 2 Palo Alto 192.168.0.1 Client client 340758 2021-02-04T16:49:16.000Z abuse.com https://www.phishportal.com/client/incident/JJJ-302171

fraudwatch-incident-report


Report an incident to FraudWatch service.

Base Command

fraudwatch-incident-report

Input

Argument Name Description Required
brand The brand associated with the reported incident. An error is encountered if the brand does not exist. A list of the existing brands can be retrieved by the ‘fraudwatch-brands-list’ command. Required
type The incident type to be associated with the reported incident. Possible values are: phishing, vishing, brand_abuse, malware, social_media_brand_abuse, mobile_app_unauthorized, pac_file, pharming, messaging, dmarc_email_server. Required
reference_id Reference ID to be associated with the reported incident. Should be unique. Reference ID can be used later on to retrieve a specific incident by its reference ID. Optional
primary_url Primary URL of the reported incident. Required
urls A Comma-separated list of additional URLs to be associated with the reported incident. Optional
evidence Evidence to be added (such as logs, etc…) to the reported incident. Optional
instructions Additional instructions to be added for the FraudWatch security team. Optional

Context Output

Path Type Description
FraudWatch.Incident.identifier String Incident identifier.
FraudWatch.Incident.reference_id String Incident reference ID.
FraudWatch.Incident.url String Main URL associated with the incident.
FraudWatch.Incident.status String Incident status.
FraudWatch.Incident.type String Incident type.
FraudWatch.Incident.brand String Incident brand.
FraudWatch.Incident.client String Incident client.
FraudWatch.Incident.content_ip String Incident content IP.
FraudWatch.Incident.host String Incident host.
FraudWatch.Incident.host_country String Incident host contry.
FraudWatch.Incident.host_timezone String Incident host timezone.
FraudWatch.Incident.created_by String Who created the incident.
FraudWatch.Incident.discovered_by String Who discovered the incident.
FraudWatch.Incident.current_duration String Current duration of the incident.
FraudWatch.Incident.active_duration Unknown Current active duration of the incident.
FraudWatch.Incident.date_opened Date The date in which the incident was opened.
FraudWatch.Incident.date_closed Date The date in which the incident was closed. This field is expected to be empty because the incident was just created.
FraudWatch.Incident.additional_urls String Additional URLs associated with the incident.
FraudWatch.Incident.link String Link to the incident page in the FraudWatch user interface.

Command Example

!fraudwatch-incident-report brand="Testing Brand 1" primary_url="http://www.maliciousaddress.com" type="vishing" reference_id="abc123" urls="http://abuse.com"

Context Example

{
    "FraudWatch": {
        "Incident": {
            "active_duration": null,
            "additional_urls": [
                "http://abuse.com"
            ],
            "brand": "Testing Brand 1",
            "client": "Palo Alto",
            "content_ip": null,
            "created_by": "FraudWatch",
            "current_duration": "0",
            "date_closed": null,
            "date_opened": "2021-02-08T15:28:37.000Z",
            "discovered_by": "client",
            "host": null,
            "host_country": null,
            "host_timezone": null,
            "identifier": "JJJ-358877",
            "link": "https://www.phishportal.com/client/incident/JJJ-358877",
            "reference_id": "abc123",
            "status": "monitor",
            "type": "Vishing",
            "url": "http://www.maliciousaddress.com"
        }
    }
}

Human Readable Output

Created FraudWatch Incident

additional_urls brand client created_by current_duration date_opened discovered_by identifier link reference_id status type url
http://abuse.com Testing Brand 1 Palo Alto FraudWatch 0 2021-02-08T15:28:37.000Z client JJJ-358877 https://www.phishportal.com/client/incident/JJJ-358877 abc123 monitor Vishing http://www.maliciousaddress.com

fraudwatch-incident-update


Updates the incident associated with the ‘incident_id’ with the specified argument values.

Base Command

fraudwatch-incident-update

Input

Argument Name Description Required
incident_id The ID of the incident to be updated. The incident ID is the ‘identifier’ field returned by the ‘fraudwatch-incidents-list’ command. Required
brand Updates the incident associated with the ‘incident_id’ with the brand specified. An error is encountered if the brand does not exist. A list of the existing brands can be retrieved by the ‘fraudwatch-brands-list’ command. Optional
reference_id Updates the incident associated with the ‘incident_id’ with the reference ID specified. Reference ID should be unique, and can be used by the ‘fraudwatch-incident-get-by-identifier’ command to retrieve a specific incident by its reference id. Optional
evidence Evidence to be added (such as logs, etc…) to the incident. Optional
instructions Updates the incident associated with the ‘incident_id’ with additional instructions for the FraudWatch security team. Optional

Context Output

There is no context output for this command.

Command Example

!fraudwatch-incident-update incident_id=JJJ-504830 brand="Testing Brand 2" reference_id="reference123"

Human Readable Output

Incident with ID JJJ-504830 was updated successfully

fraudwatch-incident-get-by-identifier


Gets an incident from FraudWatch service by its reference ID or incident ID. Single values of ‘reference_id’ and ‘incident_id’ should be specified.

Base Command

fraudwatch-incident-get-by-identifier

Input

Argument Name Description Required
incident_id The ID of the incident to retrieve. Incident ID is the ‘identifier’ field returned by the ‘fraudwatch-incidents-list’ command. Optional
reference_id Reference id of the incident to retrieve. If more than one incident is associated with ‘reference_id’ specified, returns the details of the incident with the latest ‘date opened’. Reference ID is the ‘reference_id’ field returned by the ‘fraudwatch-incidents-list’ command. Optional

Context Output

Path Type Description
FraudWatch.Incident.identifier String Incident identifier.
FraudWatch.Incident.reference_id String Incident reference ID.
FraudWatch.Incident.url String Main URL associated with the incident.
FraudWatch.Incident.status String Incident status.
FraudWatch.Incident.type String Incident type.
FraudWatch.Incident.brand String Incident brand.
FraudWatch.Incident.client String Incident client.
FraudWatch.Incident.content_ip String Incident content IP.
FraudWatch.Incident.host String Incident host.
FraudWatch.Incident.host_country String Incident host country.
FraudWatch.Incident.host_timezone String Incident host timezone.
FraudWatch.Incident.created_by String Who created the incident.
FraudWatch.Incident.discovered_by String Who discovered the incident.
FraudWatch.Incident.current_duration String Current duration of the incident.
FraudWatch.Incident.active_duration Unknown Current active duration of the incident.
FraudWatch.Incident.date_opened Date The date in which the incident was opened.
FraudWatch.Incident.date_closed Date The date in which the incident was closed.
FraudWatch.Incident.additional_urls String Additional URLs associated with the incident.
FraudWatch.Incident.link String Link to the incident page in the FraudWatch user interface.

Command Example

!fraudwatch-incident-get-by-identifier incident_id=JJJ-168840

Context Example

{
    "FraudWatch": {
        "Incident": {
            "active_duration": null,
            "additional_urls": [
                "http://malicious1.com",
                "http://malicious2.com"
            ],
            "brand": "Testing Brand 1",
            "client": "Palo Alto",
            "content_ip": null,
            "created_by": "Client",
            "current_duration": "514313",
            "date_closed": null,
            "date_opened": "2021-02-02T16:36:50.000Z",
            "discovered_by": "client",
            "host": null,
            "host_country": null,
            "host_timezone": null,
            "identifier": "JJJ-168840",
            "link": "https://www.phishportal.com/client/incident/JJJ-168840",
            "reference_id": null,
            "status": "monitor",
            "type": "Vishing",
            "url": "test.com"
        }
    }
}

Human Readable Output

FraudWatch Incident

additional_urls brand client created_by current_duration date_opened discovered_by identifier link status type url
http://malicious1.com,
http://malicious2.com
Testing Brand 1 Palo Alto Client 514313 2021-02-02T16:36:50.000Z client JJJ-168840 https://www.phishportal.com/client/incident/JJJ-168840 monitor Vishing test.com

fraudwatch-incident-forensic-get


Gets the forensic data of the incident associated with the specified incident ID.

Base Command

fraudwatch-incident-forensic-get

Input

Argument Name Description Required
incident_id The ID of the incident for which to retrieve the forensic data. Incident ID is the ‘identifier’ field returned by the ‘fraudwatch-incidents-list’ command. Required

Context Output

Path Type Description
FraudWatch.IncidentForensicData.host_provider.name String Name of the host provider.
FraudWatch.IncidentForensicData.host_provider.country String Country of the host provider.
FraudWatch.IncidentForensicData.host_nameservers String Names of the host servers.
FraudWatch.IncidentForensicData.host_domain_registrar.name String Host domain registrar name.
FraudWatch.IncidentForensicData.host_domain_registrar.email String Host domain registrar email.
FraudWatch.IncidentForensicData.host_domain_registrar.country String Host domain registrar country.
FraudWatch.IncidentForensicData.host_site_owner Unknown Host site owner.
FraudWatch.IncidentForensicData.host_site_admin Unknown Host site admin.
FraudWatch.IncidentForensicData.host_domain_admin Unknown Host domain admin.
FraudWatch.IncidentForensicData.host_ip_providier Unknown Host IP provider.
FraudWatch.IncidentForensicData.identifier String Identifier of the incident.

Command Example

!fraudwatch-incident-forensic-get incident_id=JJJ-397266

Context Example

{
    "FraudWatch": {
        "IncidentForensicData": {
            "host_domain_registrar": {
                "country": "abuse@address.com",
                "email": "http://www.abuseaddress.com",
                "name": "Moniker Online Services LLC"
            },
            "host_nameservers": [
                "NS1.IRAN.COM",
                "NS2.IRAN.COM"
            ],
            "identifier": "JJJ-397266"
        }
    }
}

Human Readable Output

FraudWatch Incident Forensic Data

host_domain_registrar host_nameservers identifier
name: Moniker Online Services LLC
email: http://www.abuseaddress.com
country: abuse@address.com
NS1.IRAN.COM,
NS2.IRAN.COM
JJJ-397266

fraudwatch-incident-contact-emails-list


Provides a list of the messages related to the incident associated with the specified incident ID.

Base Command

fraudwatch-incident-contact-emails-list

Input

Argument Name Description Required
incident_id The ID of the incident for which to retrieve the related messages. Incident ID is the ‘identifier’ field returned by the ‘fraudwatch-incidents-list’ command. Required
limit Number of related messages to retrieve. Optional
page_size Maximum number of related messages in a page. Maximum value is 200. Optional
page Retrieve related messages by the specified page number. Optional

Context Output

Path Type Description
FraudWatch.IncidentContacts.noteId String Note ID of the related message.
FraudWatch.IncidentContacts.subject String Subject of the related message.
FraudWatch.IncidentContacts.creator String The creator of the related message.
FraudWatch.IncidentContacts.content String The content of the related message.
FraudWatch.IncidentContacts.date Date The date of the related message.

Command Example

!fraudwatch-incident-contact-emails-list incident_id=JJJ-898410 limit=2

Context Example

{
    "FraudWatch": {
        "IncidentContacts": [
            {
                "content": "This incident is very malicious, please monitor it\r\n",
                "creator": "Client",
                "date": "2021-02-08T15:26:19.000Z",
                "noteId": "11081853",
                "subject": "Client Reply"
            },
            {
                "content": "This incident is very malicious, please monitor it\r\n",
                "creator": "Client",
                "date": "2021-02-08T15:21:58.000Z",
                "noteId": "11081828",
                "subject": "Client Reply"
            }
        ]
    }
}

Human Readable Output

FraudWatch Incident Contacts Data

noteId subject creator content date
11081853 Client Reply Client This incident is very malicious, please monitor it
2021-02-08T15:26:19.000Z
11081828 Client Reply Client This incident is very malicious, please monitor it
2021-02-08T15:21:58.000Z

fraudwatch-incident-messages-add


Add a new related message to the incident associated with the specified incident ID.

Base Command

fraudwatch-incident-messages-add

Input

Argument Name Description Required
incident_id Add a related message to the incident with the specified incident ID. Incident ID is the ‘identifier’ field returned by the ‘fraudwatch-incidents-list’ command. Required
message_content Content of the related message. Required

Context Output

There is no context output for this command.

Command Example

!fraudwatch-incident-messages-add incident_id=JJJ-898410 message_content="This incident is very malicious, please monitor it"

Human Readable Output

Message for incident id JJJ-898410 was added successfully

fraudwatch-incident-urls-add


Adds additional URLs to the incident associated with the specified incident ID. Fails if one of the new urls already exists.

Base Command

fraudwatch-incident-urls-add

Input

Argument Name Description Required
incident_id The ID of the incident to add additional urls to. Incident ID is the ‘identifier’ field returned by the ‘fraudwatch-incidents-list’ command. Required
urls A comma-separated list of additional URLs to be added to the incident associated with the ‘incident_id’. Required

Context Output

Path Type Description
FraudWatch.IncidentUrls.success String Whether the URLs were added successfully.
FraudWatch.IncidentUrls.new_urls String The new URLs that were added.

Command Example

!fraudwatch-incident-urls-add incident_id=JJJ-162968 urls=http://www.malicious1.com,http://www.malicious2.com

Context Example

{
    "FraudWatch": {
        "IncidentUrls": {
            "new_urls": [
                "http://www.malicious1.com",
                "http://www.malicious2.com"
            ],
            "success": "Add additional urls successfully"
        }
    }
}

Human Readable Output

FraudWatch Incident Urls

new_urls success
http://www.malicious1.com,
http://www.malicious2.com
Add additional urls successfully

fraudwatch-incident-attachment-upload


Adds a new file attachment to the incident associated with the specified incident ID.

Base Command

fraudwatch-incident-attachment-upload

Input

Argument Name Description Required
incident_id The ID of the incident to add a file attachment to. Incident ID is the ‘identifier’ field returned by the ‘fraudwatch-incidents-list’ command. Required
entry_id The entry id in Cortex XSOAR of the attachment to be added to the incident. Required

Context Output

There is no context output for this command.

Command Example

!fraudwatch-incident-attachment-upload entry_id=fmSNZSY2fSCA2WptU8rddf@d382f488-92db-400c-87ff-fdd71f3b7408 incident_id=JJJ-604206

Human Readable Output

File fraud_test.txt was uploaded successfully to incident with an incident id JJJ-604206

fraudwatch-brands-list


Gets a list of brands from FraudWatch service.

Base Command

fraudwatch-brands-list

Input

Argument Name Description Required
limit Number of brands to retrieve. Optional
page_size Total number of brands in a page. Values range: 20-100. Optional
page Retrieve brands by the specified page number. Optional

Context Output

Path Type Description
FraudWatch.Brand.client String Brand client.
FraudWatch.Brand.alternate business name String Brand alternative business name.
FraudWatch.Brand.name String Brand name.
FraudWatch.Brand.active Boolean Wether the brand is active or not.
FraudWatch.Brand.services.name String Brand service name.
FraudWatch.Brand.services.action String Brand service action.

Command Example


#### Context Example

```json
{
    "FraudWatch": {
        "Brand": [
            {
                "active": true,
                "alternate business name": "",
                "client": "Palo Alto",
                "name": "Testing Brand 1",
                "services": [
                    {
                        "action": "takedown",
                        "name": "Phishing"
                    },
                    {
                        "action": "monitor",
                        "name": "Vishing"
                    },
                    {
                        "action": "monitor",
                        "name": "Brand Abuse"
                    },
                    {
                        "action": "monitor",
                        "name": "Malware"
                    },
                    {
                        "action": "monitor",
                        "name": "Social Media Brand Abuse"
                    },
                    {
                        "action": "monitor",
                        "name": "Mobile App (Unauthorized)"
                    },
                    {
                        "action": "monitor",
                        "name": "PAC File"
                    },
                    {
                        "action": "monitor",
                        "name": "Messaging"
                    },
                    {
                        "action": "monitor",
                        "name": "DMARC Email Server"
                    }
                ]
            },
            {
                "active": true,
                "alternate business name": "",
                "client": "Palo Alto",
                "name": "Testing Brand 2",
                "services": [
                    {
                        "action": "takedown",
                        "name": "Phishing"
                    },
                    {
                        "action": "monitor",
                        "name": "Vishing"
                    },
                    {
                        "action": "monitor",
                        "name": "Brand Abuse"
                    },
                    {
                        "action": "monitor",
                        "name": "Malware"
                    },
                    {
                        "action": "monitor",
                        "name": "Social Media Brand Abuse"
                    },
                    {
                        "action": "monitor",
                        "name": "Mobile App (Unauthorized)"
                    },
                    {
                        "action": "monitor",
                        "name": "PAC File"
                    },
                    {
                        "action": "monitor",
                        "name": "Messaging"
                    },
                    {
                        "action": "monitor",
                        "name": "DMARC Email Server"
                    }
                ]
            }
        ]
    }
}

Human Readable Output

FraudWatch Brands

name active client
Testing Brand 1 true Palo Alto
Testing Brand 2 true Palo Alto

Configuration parameters

  • isFetch — Fetch incidents
  • credentials — API Token (leave empty. Fill in the API token in the password field.)
  • base_url — Server URL (required)
  • brand — Brand
  • status — Status
  • incidentType — Incident type
  • incidentFetchInterval — Incidents Fetch Interval
  • first_fetch — First fetch timestamp
  • max_fetch — Maximum Incidents To Fetch
  • insecure — Trust any certificate (not secure)
  • proxy — Use system proxy settings

Commands (10)

  • fraudwatch-brands-list

    Gets a list of brands from FraudWatch service.

  • fraudwatch-incident-attachment-upload

    Adds a new file attachment to the incident associated with the specified incident ID.

  • fraudwatch-incident-contact-emails-list

    Provides a list of the messages related to the incident associated with the specified incident ID.

  • fraudwatch-incident-forensic-get

    Gets the forensic data of the incident associated with the specified incident ID.

  • fraudwatch-incident-get-by-identifier

    Gets an incident from FraudWatch service by its reference ID or incident ID. Single values of 'reference_id' and 'incident_id' should be specified.

  • fraudwatch-incident-messages-add

    Add a new related message to the incident associated with the specified incident ID.

  • fraudwatch-incident-report

    Report an incident to FraudWatch service.

  • fraudwatch-incident-update

    Updates the incident associated with the 'incident_id' with the specified argument values.

  • fraudwatch-incident-urls-add

    Adds additional URLs to the incident associated with the specified incident ID. Fails if one of the new urls already exists.

  • fraudwatch-incidents-list

    Get a list of incidents from FraudWatch service.

import demistomock as demisto  # noqa: F401
import pytz
import urllib3
from CommonServerPython import *  # noqa: F401
from requests import Response

# disable insecure warnings
urllib3.disable_warnings()

""" CONSTANTS """
MINIMUM_POSITIVE_VALUE = 1
MAX_PAGE_SIZE_VALUE = 200
DEFAULT_PAGE_SIZE_VALUE = 50
MAX_PAGE_SIZE_BRANDS = 100
MINIMUM_PAGE_SIZE_BRANDS = 20
DEFAULT_URL = "https://www.phishportal.com/"

FRAUD_WATCH_DATE_FORMAT = "%Y-%m-%d"

INCIDENT_LIST_MARKDOWN_HEADERS = [
    "identifier",
    "reference_id",
    "url",
    "status",
    "type",
    "brand",
    "client",
    "content_ip",
    "host",
    "host_country",
    "host_timezone",
    "created_by",
    "discovered_by",
    "current_duration",
    "active_duration",
    "date_opened",
    "date_closed",
    "additional_urls",
    "link",
]

UTC_TIMEZONE = pytz.timezone("utc")
""" CLIENT CLASS """


class Client(BaseClient):
    URL_ENCODED_HEADER = {"Content-Type": "application/x-www-form-urlencoded"}
    JSON_CONTENT_HEADER = {"Content-Type": "application/json"}

    def __init__(self, api_key: str, base_url: str, verify: bool, proxy: bool):
        self.api_key = api_key
        self.base_headers = {"Authorization": f"Bearer {api_key}", "Accept": "application/json"}
        super().__init__(base_url=base_url, verify=verify, proxy=proxy, headers=self.base_headers)

    def http_request(
        self,
        method: str,
        url_suffix: str,
        params: Optional[Dict] = None,
        data: Optional[Dict] = None,
        headers: Optional[Dict] = None,
        files: Optional[Dict] = None,
    ):
        """
        Wrapper for Base Client http request. Uses error handler to catch errors returned by FraudWatch and returning
        an exception with more precise description for the exception
        Args:
            method (str): The HTTP method to perform ('POST', 'GET', 'PUT').
            url_suffix (str): The url suffix of the API call.
            params (Optional[Dict]): Additional query params to be sent.
            data (Optional[Dict]): Additional data to be sent.
            headers (Optional[Dict]): Additional headers to be sent.
            files (Optional[Dict]): Additional files to be sent

        Returns:
            The HTTP response, or exception if exception occurred.
        """
        return self._http_request(
            method=method,
            url_suffix=url_suffix,
            params=params,
            data=data,
            headers=headers,
            files=files,
            error_handler=fraud_watch_error_handler,
        )

    def incidents_list(
        self,
        brand: Optional[str] = None,
        status: Optional[str] = None,
        page: Optional[int] = None,
        limit: Optional[int] = None,
        from_date: Optional[str] = None,
        to_date: Optional[str] = None,
    ):
        params = assign_params(brand=brand, status=status, page=page, limit=limit, to=to_date)

        # This is because 'from' is reserved word so it can't be used as key argument in assign_params.
        if from_date:
            params["from"] = from_date

        return self.http_request(method="GET", url_suffix="incidents", params=params)

    def incident_report(
        self,
        brand: str,
        incident_type: str,
        primary_url: str,
        reference_id: Optional[str] = None,
        urls: Optional[List[str]] = None,
        evidence: Optional[str] = None,
        instructions: Optional[str] = None,
    ):
        return self.http_request(
            method="POST",
            url_suffix="incidents",
            data=assign_params(
                brand=brand,
                type=incident_type,
                reference_id=reference_id,
                primary_url=primary_url,
                urls=urls,
                evidence=evidence,
                instructions=instructions,
            ),
            headers={**self.base_headers, **self.URL_ENCODED_HEADER},
        )

    def incident_update(
        self,
        incident_id: str,
        brand: Optional[str] = None,
        reference_id: Optional[str] = None,
        evidence: Optional[str] = None,
        instructions: Optional[str] = None,
    ):
        return self.http_request(
            method="PUT",
            url_suffix=f"incident/{incident_id}",
            data=assign_params(brand=brand, reference_id=reference_id, evidence=evidence, instructions=instructions),
            headers={**self.base_headers, **self.URL_ENCODED_HEADER},
        )

    def incident_list_by_id(self, incident_id: str):
        return self.http_request(method="GET", url_suffix=f"incident/{incident_id}")

    def incident_get_by_reference(self, reference_id: str):
        return self.http_request(method="GET", url_suffix=f"incident/reference/{reference_id}")

    def incident_forensic_get(self, incident_id: str):
        return self.http_request(method="GET", url_suffix=f"incident/{incident_id}/forensic")

    def incident_contact_emails_list(self, incident_id: str, page: Optional[int] = None, limit: Optional[int] = None):
        return self.http_request(
            method="GET", url_suffix=f"incident/{incident_id}/message", params=assign_params(page=page, limit=limit)
        )

    def incident_messages_add(self, incident_id: str, message_content: Any):
        return self.http_request(
            method="POST",
            url_suffix=f"incident/{incident_id}/message/add",
            data=message_content,
            headers={**self.base_headers, **self.JSON_CONTENT_HEADER},
        )

    def incident_urls_add(self, incident_id: str, urls: Dict[str, List[str]]):
        return self.http_request(
            method="POST",
            url_suffix=f"incident/{incident_id}/urls/add",
            data=urls,
            headers={**self.base_headers, **self.URL_ENCODED_HEADER},
        )

    def attachment_upload_command(self, incident_id: str, file: Any):
        return self.http_request(method="POST", url_suffix=f"incident/{incident_id}/upload", files=file)

    def brands_list(self, page: Optional[int] = None, limit: Optional[int] = None):
        return self.http_request(method="GET", url_suffix="account/brands", params=assign_params(page=page, limit=limit))


""" HELPER FUNCTIONS """


def fraud_watch_error_handler(res: Response):
    """
    FraudWatch error handler for any error occurred during the API request.
    This function job is to translate the known exceptions returned by FraudWatch
    to human readable exception to help the user understand why the request have failed.
    If error returned is not in the expected error format, raises the exception as is.
    Args:
        res (Any): The error response returned by FraudWatch.

    Returns:
        - raises DemistoException.
    """
    err_msg = f"Error in API call [{res.status_code}] - {res.reason}"
    try:
        # Try to parse json error response
        error_entry = res.json()
        message = error_entry.get("message")
        errors = error_entry.get("errors")
        if res.status_code == 403:
            err_msg += "\nMake sure your API token is valid and up-to-date"
        elif not message and not errors:
            err_msg += f"\n{json.dumps(error_entry)}"
        else:
            if message and errors:
                fraud_watch_error_reason = f"{message}: {errors}"
            else:
                fraud_watch_error_reason = message if message else errors
            err_msg += f"\n{fraud_watch_error_reason}"
        raise DemistoException(err_msg, res=res)
    except ValueError:
        err_msg += f"\n{res.text}"
        raise DemistoException(err_msg, res=res)


def get_and_validate_positive_int_argument(
    args: Dict, argument_name: str, lower_bound: int = MINIMUM_POSITIVE_VALUE, upper_bound: Optional[int] = None
) -> Optional[int]:
    """
    Extracts int argument from Demisto arguments.
    If argument exists, validates that:
    - lower_bound <= argument's value.
    - argument's value <= maximum_bound if maximum_bound is not None.

    Args:
        args (Dict): Demisto arguments.
        argument_name (str): The name of the argument to extract.
        lower_bound (int): Lower number bound of the argument value.
        upper_bound (Optional[int]): Maximum number bound of the argument value, if given.

    Returns:
        - (int): If argument exists and is between 'lower_bound' and 'maximum_bound', returns argument.
        - (None): If argument does not exist, returns None.
        - (Exception): If argument exists and is lower than 'lower_bound' or
                       higher than 'maximum_bound' (if 'maximum_bound' exists), raises DemistoException.
    """
    argument_value = arg_to_number(args.get(argument_name), arg_name=argument_name)
    if not argument_value:
        return None
    if argument_value < lower_bound:
        raise DemistoException(f"{argument_name} should be equal or higher than {lower_bound}")
    if upper_bound and upper_bound < argument_value:
        raise DemistoException(f"{argument_name} should be equal or lower than {upper_bound}")

    return argument_value


def get_time_parameter(arg: Optional[str]):
    """
    parses arg into date time object with aware time zone if 'arg' exists.
    If no time zone is given, sets timezone to UTC.
    Returns the date time object created.
    Args:
        arg (str): The argument to turn into aware date time.

    Returns:
        - (None) If 'arg' is None, returns None.
        - (datetime): If 'arg' is exists, returns date time.
    """
    maybe_unaware_date = arg_to_datetime(arg, is_utc=True)
    if not maybe_unaware_date:
        return None

    aware_time_date = maybe_unaware_date if maybe_unaware_date.tzinfo else UTC_TIMEZONE.localize(maybe_unaware_date)

    return aware_time_date


""" COMMAND FUNCTIONS """


def get_page_and_limit_args(
    args: Dict, minimum_page_size: int = MINIMUM_POSITIVE_VALUE, maximum_page_size: int = MAX_PAGE_SIZE_VALUE
) -> tuple[int, int]:
    """
    Receives demisto argument, and extract the relevant arguments for limits and paging:
    'page', 'page_size', 'limit'.
    Follows the logic:
    - 'limit' argument cannot be specified with 'page' or 'page_size' argument.
    - 'page_size' argument is within its expected lower/upper bounds.
    - If 'limit' is not given, and 'page_size' is, sets 'limit' value to 'page_size' value.
    - If 'limit' is not given, and 'page_size' is not given, sets 'limit' value to 'DEFAULT_PAGE_SIZE_VALUE' value.
    Args:
        args (Dict): Demisto argument.
        minimum_page_size (int): lower_bound for page size, default is 'MINIMUM_POSITIVE_VALUE' (1).
        maximum_page_size (int): upper_bound for page size, default is 'MAX_PAGE_SIZE_VALUE' (200).

    Returns:
        - (int, int): 'page', 'limit' extracted, or their default values used.
        - (DemistoException): If arguments don't follow the expected logic mentioned.
    """
    page = get_and_validate_positive_int_argument(args, "page")
    page_size = get_and_validate_positive_int_argument(
        args, "page_size", lower_bound=minimum_page_size, upper_bound=maximum_page_size
    )
    limit = get_and_validate_positive_int_argument(args, "limit")
    if limit and (page_size or page):
        raise DemistoException("""Limit argument cannot be given with 'page_size' or 'page' argument.""")
    if not limit and page_size:
        limit = page_size
    if not limit:
        limit = DEFAULT_PAGE_SIZE_VALUE
    if not page:
        page = MINIMUM_POSITIVE_VALUE

    return page, limit


def fetch_incidents_command(client: Client, params: Dict, last_run: Dict):
    """
    Fetches new incidents from FraudWatch.
    Because of FraudWatch limitation, such as 'from_date' being accurate only by day, and incidents returned
    are not ordered by their date opened.
    1) First_fetch time is limited to 1 day.
    2) Every fetch_incidents call, we will fetch all the pages from the day of the time stamp saved in last run.
    3) Filter all incidents that their date opened was earlier than last saved time stamp.
    4) Sort the remained incidents by their date opened.
    5) Return the first 'max_fetch' incidents in the sorted list.
    Limiting first_fetch is needed because we must traverse all incidents that might be relevant, therefore the
    list cannot be very large.
    Uses Demisto last run parameters in the following way:
    - 'last_fetch_time' - The latest time fetching was done. Keeps track of incidents to be fetched by their date.

    Args:
        client (Client): FraudWatch client to perform API call to fetch incidents.
        params (Dict): Demisto params.
        last_run (Dict): Demisto last run.

    Returns:
        (List[Dict], Dict): Incidents and new run parameters.
    """
    brand = params.get("brand")
    status = params.get("status")
    limit = arg_to_number(params.get("max_fetch", DEFAULT_PAGE_SIZE_VALUE))

    yesterday = get_time_parameter("1 day")
    first_fetch_time_string = params.get("first_fetch", "1 days").strip()
    minimum_date_opened_str = last_run.get("last_fetch_time", first_fetch_time_string)
    # Between yesterday date and last fetch date we choose the latest
    minimum_date_opened = max(get_time_parameter(minimum_date_opened_str), yesterday)

    from_date = minimum_date_opened.strftime(FRAUD_WATCH_DATE_FORMAT)
    incidents = []

    # Need to fetch all incidents because FraudWatch does not sort incidents based on dates.
    current_page = 1
    while True:
        raw_response = client.incidents_list(
            brand=brand, status=status, page=current_page, limit=MAX_PAGE_SIZE_VALUE, from_date=from_date
        )
        if raw_response.get("error"):
            raise DemistoException(f"""Error occurred while pulling incidents from FraudWatch: {raw_response.get('error')}""")
        temp_incidents = raw_response.get("incidents", [])
        if not temp_incidents:
            break
        incidents += temp_incidents
        current_page += 1

    incidents = [incident for incident in incidents if get_time_parameter(incident.get("date_opened")) > minimum_date_opened]
    incidents.sort(key=lambda incident: incident.get("date_opened"))
    incidents = incidents[:limit]

    incidents_obj_list: List[Dict[str, Any]] = [
        {
            "name": f"""{incident.get('brand')}:{incident.get('identifier')}""",
            "type": "FraudWatch Incident",
            "occurred": incident.get("date_opened"),
            "rawJSON": json.dumps(incident),
        }
        for incident in incidents
    ]

    next_run = {"last_fetch_time": incidents[-1].get("date_opened") if incidents else minimum_date_opened_str}
    return incidents_obj_list, next_run


def test_module(client: Client, params: Dict) -> str:
    """
    Tests API connectivity and authentication.
    Returning 'ok' indicates that the integration works like it is supposed to.
    Connection to the service is successful.
    Raises exceptions if something goes wrong.

    Args:
        client (Client): FraudWatch client to perform the API call.
        params (Dict): Demisto params.

    Returns:
        (str): 'ok' if test passed, anything else will fail the test.
    """
    try:
        fetch_incidents_command(client, params, {})
        return "ok"
    except DemistoException as e:
        if "Forbidden" in str(e) or "Authorization" in str(e):
            message = "Authorization Error: make sure API Key is correctly set"
            e = DemistoException(message)
        raise e


def fraud_watch_incidents_list_command(client: Client, args: Dict) -> CommandResults:
    """
    Gets a list of incidents from FraudWatch service with the possible filters:
    - Brand: Retrieve incidents which corresponds to the given brand. throws Exception if brand does not exist.
    - Status: Retrieve incidents which corresponds to the given status. Unknown status will return empty incident list.
    - Limit: Total number of Incidents in a page. The default limit is 20 and the maximum number is 200.
    - Page: Retrieve incidents by the given page number.
    - From Date: Retrieve alerts that their date opened is higher or equal to 'from' value.
                 Supports ISO and time range (<number> <time unit>, e.g., 12 hours, 7 days) formats.
                 If 'to' argument is not given, default value for 'to' is current day.
    - To Date: Retrieve alerts that their date opened is lower or equal to 'to' value.
               Supports ISO and time range (<number> <time unit>, e.g., 12 hours, 7 days) formats.
               If 'from' argument is not given, default value for 'from' is 12 months before 'to'.

    Known errors that causes error to be returned by FraudWatch service:
    - Unknown brand.
    - Invalid 'to_date' or 'from_date' values.

    Args:
        client (Client): FraudWatch client to perform the API calls.
        args (Dict): Demisto arguments.

    Returns:
        CommandResults.
    """
    brand = args.get("brand")
    status = args.get("status")
    page, limit = get_page_and_limit_args(args)

    if from_date := get_time_parameter(args.get("from")):
        from_date = from_date.strftime(FRAUD_WATCH_DATE_FORMAT)

    if to_date := get_time_parameter(args.get("to")):
        to_date = to_date.strftime(FRAUD_WATCH_DATE_FORMAT)

    if from_date and not to_date:
        to_date = get_time_parameter("tomorrow").strftime(FRAUD_WATCH_DATE_FORMAT)

    raw_responses = []
    outputs = []
    count = limit
    while limit > 0:
        raw_response = client.incidents_list(brand, status, page, MAX_PAGE_SIZE_VALUE, from_date, to_date)
        incidents = raw_response.get("incidents", [])
        if not incidents:
            break
        if raw_response.get("error"):
            raise DemistoException(f"""Error occurred during the call to FraudWatch: {raw_response.get('error')}""")
        raw_responses.append(raw_response)
        outputs.extend(incidents)
        limit -= MAX_PAGE_SIZE_VALUE
        page += 1

    final_raw_responses = raw_responses[:count]
    final_outputs = outputs[:count]

    return CommandResults(
        outputs_prefix="FraudWatch.Incident",
        outputs=final_outputs,
        outputs_key_field="identifier",
        raw_response=final_raw_responses,
        readable_output=tableToMarkdown("FraudWatch Incidents", final_outputs, INCIDENT_LIST_MARKDOWN_HEADERS, removeNull=True),
    )


def fraud_watch_incident_report_command(client: Client, args: Dict) -> CommandResults:
    """
    Report an incident to FraudWatch service:
    - Brand(Required): The brand associated to the reported incident.
    - Incident Type(Required): The type of the incident to be associated to the reported incident.
         - possible values: ['phishing' => Phishing,
                           'vishing' => Vishing,
                           'brand_abuse' => Brand Abuse,
                           'malware' => Malware,
                           'social_media_brand_abuse' => Social Media,
                           'mobile_app_unauthorized' => Mobile App,
                           'pac_file' => PAC File,
                           'pharming' => Pharming,
                           'messaging' => Messaging,
                           'dmarc_email_server' => DMARC]
           left side is the parameter to be sent, right side is the way it will be shown in FraudWatch User Interface.
    - Reference ID: Reference ID to be associated to the reported incident. Should be unique.
                    Reference ID can be used later to retrieve specific incident by its reference id.
    - Primary URL(Required): Primary URL of the reported incident.
    - Urls: Additional urls in addition to primary URL to be associated to the reported incident.
    - Evidence: Evidence to be added (such as logs, etc...) to the reported incident.
    - Instructions: Additional instructions to be added for FraudWatch Security Team.

    Known errors that causes error to be returned by FraudWatch service:
    - Not given or unknown brand.
    - Not given or unknown incident type.
    - Not given primary url.
    - No data given.
    Args:
        client (Client): FraudWatch client to perform the API calls.
        args (Dict): Demisto arguments.

    Returns:
        CommandResults.
    """
    brand: str = args.get("brand")  # type: ignore
    incident_type: str = args.get("type")  # type: ignore
    primary_url: str = args.get("primary_url")  # type: ignore
    reference_id = args.get("reference_id")
    urls = args.get("urls")
    evidence = args.get("evidence")
    instructions = args.get("instructions")

    raw_response = client.incident_report(brand, incident_type, primary_url, reference_id, urls, evidence, instructions)

    return CommandResults(
        outputs_prefix="FraudWatch.Incident",
        outputs=raw_response,
        outputs_key_field="identifier",
        raw_response=raw_response,
        readable_output=tableToMarkdown("Created FraudWatch Incident", raw_response, removeNull=True),
    )


def fraud_watch_incident_update_command(client: Client, args: Dict) -> CommandResults:
    """
    Updates the incident ID corresponding to the given incident_id with given arguments values:
    - Incident ID(Required): The ID of the incident to be updated.
    - Brand: Updates this to be the brand associated to the incident which corresponds to given incident id.
    - Reference ID: Reference ID to be associated to the incident which corresponds to given incident id.
                    Should be unique. Reference ID can be used later to retrieve specific incident by its reference id.
    - Evidence: Evidence to be added (such as logs, etc...) to the incident.
    - Instructions: Add Additional instructions to be added for FraudWatch Security
                    Team to the incident which corresponds to given incident id.

    At least one of 'Brand', 'Reference ID', 'Evidence', 'Instructions' must be given.

    Known errors that causes error to be returned by FraudWatch service:
    - Unknown incident id.
    - Not given or unknown brand.
    - No data given.

    Args:
        client (Client): FraudWatch client to perform the API calls.
        args (Dict): Demisto arguments.

    Returns:
        CommandResults.
    """
    incident_id: str = args.get("incident_id")  # type: ignore
    brand = args.get("brand")
    reference_id = args.get("reference_id")
    evidence = args.get("evidence")
    instructions = args.get("instructions")

    if all(argument is None for argument in [brand, reference_id, evidence, instructions]):
        return CommandResults(readable_output=f"### Could not update incident: {incident_id} - No data was given.")

    raw_response = client.incident_update(incident_id, brand, reference_id, evidence, instructions)

    return CommandResults(
        raw_response=raw_response, readable_output=f"### Incident with ID {incident_id} was updated successfully"
    )


def fraud_watch_incident_get_by_identifier_command(client: Client, args: Dict) -> CommandResults:
    """
    Gets an incident from FraudWatch service by its reference ID or incident ID:
    - Incident ID: The ID of the incident to be retrieved.
    - Reference ID: Reference id of the incident to be retrieved.
                    In case more than one incident has the corresponding reference id, FraudWatch service
                    returns the incident with the latest 'date_opened' field.

    Exactly one of 'Incident ID', 'Reference ID' should be given, else DemistoException will be raised.

    Known errors that causes error to be returned by FraudWatch service:
    - Unknown incident id.
    - Unknown reference_id id.

    Args:
        client (Client): FraudWatch client to perform the API calls.
        args (Dict): Demisto arguments.

    Returns:
        CommandResults.
    """
    incident_id: str = args.get("incident_id")  # type: ignore
    reference_id: str = args.get("reference_id")  # type: ignore

    if (incident_id and reference_id) or (not incident_id and not reference_id):
        raise DemistoException("Exactly one of reference id or incident id must be given.")

    if incident_id:
        raw_response = client.incident_list_by_id(incident_id)
    else:
        raw_response = client.incident_get_by_reference(reference_id)

    return CommandResults(
        outputs_prefix="FraudWatch.Incident",
        outputs=raw_response,
        outputs_key_field="identifier",
        raw_response=raw_response,
        readable_output=tableToMarkdown("FraudWatch Incident", raw_response, removeNull=True),
    )


def fraud_watch_incident_forensic_get_command(client: Client, args: Dict) -> CommandResults:
    """
    Gets forensic data of an incident which corresponds to the given incident ID:
    - Incident ID (Required): The ID of the incident to have its forensic data retrieved.

    Known errors that causes error to be returned by FraudWatch service:
    - Unknown incident id.

    Args:
        client (Client): FraudWatch client to perform the API calls.
        args (Dict): Demisto arguments.

    Returns:
        CommandResults.
    """
    incident_id: str = args.get("incident_id")  # type: ignore

    raw_response = client.incident_forensic_get(incident_id)
    outputs = remove_empty_elements(raw_response)
    if outputs:
        outputs["identifier"] = incident_id

    return CommandResults(
        outputs_prefix="FraudWatch.IncidentForensicData",
        outputs=outputs,
        outputs_key_field="identifier",
        raw_response=raw_response,
        readable_output=tableToMarkdown("FraudWatch Incident Forensic Data", outputs, removeNull=True),
    )


def fraud_watch_incident_contact_emails_list_command(client: Client, args: Dict) -> CommandResults:
    """
    Provides contact emails for the incident which corresponds to the given incident ID:
    - Incident ID (Required): The ID of the incident to have its email contacts data retrieved.
    - Limit: Total number of contact emails in a page. The default limit is 20 and the maximum number is 200.
    - Page: Retrieve contact emails by the given page number.

    Known errors that causes error to be returned by FraudWatch service:
    - Unknown incident id.
    - Page index out of bounds.

    Args:
        client (Client): FraudWatch client to perform the API calls.
        args (Dict): Demisto arguments.

    Returns:
        CommandResults.
    """
    incident_id: str = args.get("incident_id")  # type: ignore
    page, limit = get_page_and_limit_args(args)

    raw_responses = []
    count = limit

    try:
        while limit > 0:
            raw_response = client.incident_contact_emails_list(incident_id, page, MAX_PAGE_SIZE_VALUE)
            if not raw_response:
                break
            raw_responses.extend(raw_response)
            limit -= MAX_PAGE_SIZE_VALUE
            page += 1
    except DemistoException as e:
        if "Contact email not found" in str(e):
            page_error_msg = f"""Make sure page index: {page} is within bounds.""" if page else ""
            unknown_incident_msg = f"""Make sure incident id: {incident_id} is correct."""
            raise DemistoException(f"Error occurred. {page_error_msg} {unknown_incident_msg}")
        raise e

    outputs = [dict(output, identifier=incident_id) for output in raw_responses]

    final_raw_responses = raw_responses[:count]
    final_outputs = outputs[:count]

    return CommandResults(
        outputs_prefix="FraudWatch.IncidentContacts",
        outputs=final_outputs,
        outputs_key_field="noteId",
        raw_response=final_raw_responses,
        readable_output=tableToMarkdown(
            "FraudWatch Incident Contacts Data",
            final_outputs,
            ["noteId", "incident_id", "subject", "creator", "content", "date"],
            removeNull=True,
        ),
    )


def fraud_watch_incident_messages_add_command(client: Client, args: Dict):
    """
    Add a new message to be associated to the incident which corresponds to the given incident ID:
    - Incident ID (Required): The ID of the incident to add a message to its email contacts.
    - Message Content (Required): Content of the message.

    Known errors that causes error to be returned by FraudWatch service:
    - Unknown incident id.

    Args:
        client (Client): FraudWatch client to perform the API calls.
        args (Dict): Demisto arguments.

    Returns:
        CommandResults.
    """
    incident_id: str = args.get("incident_id")  # type: ignore
    message_content = args.get("message_content")

    raw_response = client.incident_messages_add(incident_id, message_content)

    human_readable = f"### Message for incident id {incident_id} was added successfully."

    return CommandResults(raw_response=raw_response, readable_output=human_readable)


def fraud_watch_incident_urls_add_command(client: Client, args: Dict) -> CommandResults:
    """
    Adds additional urls to the incident which corresponds to the given incident ID:
    - Incident ID (Required): The ID of the incident to add additional urls to.
    - Urls: Additional urls to be added to the incident that matches 'Incident ID'.

    Known errors that causes error to be returned by FraudWatch service:
    - Unknown incident id.

    Args:
        client (Client): FraudWatch client to perform the API calls.
        args (Dict): Demisto arguments.

    Returns:
        CommandResults.
    """
    incident_id: str = args.get("incident_id")  # type: ignore
    urls: Dict[str, List[str]] = {"urls[]": argToList(args.get("urls"))}

    raw_response = client.incident_urls_add(incident_id, urls)

    return CommandResults(
        outputs_prefix="FraudWatch.IncidentUrls",
        outputs=raw_response,
        raw_response=raw_response,
        readable_output=tableToMarkdown("FraudWatch Incident Urls", raw_response, removeNull=True),
    )


def fraud_watch_attachment_upload_command(client: Client, args: Dict):
    """
    Adds a new file attachment to the incident which corresponds to the given incident ID.
    - Incident ID (Required): The ID of the incident to add additional urls to.
    - File Attachment: Entry id of the attachment to be added to the incident which corresponds to Incident ID.

    Known errors that causes error to be returned by FraudWatch service:
    - Unknown incident id.

    Args:
        client (Client): FraudWatch client to perform the API calls.
        args (Dict): Demisto arguments.

    Returns:
        CommandResults.
    """
    incident_id: str = args.get("incident_id")  # type: ignore
    entry_id = args.get("entry_id")

    try:
        # entry id of uploaded file to war room
        file_info = demisto.getFilePath(entry_id)
        file = open(file_info["path"], "rb")
    except Exception:
        raise DemistoException(f"Entry {entry_id} does not contain a file.")

    files = [("incident_attachment", (file_info["name"], file))]
    raw_response = client.attachment_upload_command(incident_id, files)

    return CommandResults(
        raw_response=raw_response,
        # change to name and not entry ID
        readable_output=f"""### File {file_info['name']} was uploaded successfully to incident: {incident_id}""",
    )


def fraud_watch_brands_list_command(client: Client, args: Dict) -> CommandResults:
    """
    Gets a list of brands from FraudWatch service:
    - Limit: Total number of brands in a page. The default limit is 20, minimum number is also 20
             and the maximum number is 100.
    - Page: Retrieve brands by the given page number.

    Args:
        client (Client): FraudWatch client to perform the API calls.
        args (Dict): Demisto arguments.

    Returns:
        CommandResults.
    """
    page, limit = get_page_and_limit_args(
        args, minimum_page_size=MINIMUM_PAGE_SIZE_BRANDS, maximum_page_size=MAX_PAGE_SIZE_BRANDS
    )
    raw_responses = []
    outputs = []
    count = limit
    while limit > 0:
        raw_response = client.brands_list(page, MAX_PAGE_SIZE_BRANDS)
        brands_list = raw_response.get("brands", [])
        if not brands_list:
            break
        raw_responses.append(raw_response)
        outputs.extend(brands_list)
        limit -= MAX_PAGE_SIZE_BRANDS
        page += 1

    final_raw_responses = raw_responses[:count]
    final_outputs = outputs[:count]

    return CommandResults(
        outputs_prefix="FraudWatch.Brand",
        outputs=final_outputs,
        outputs_key_field="name",
        raw_response=final_raw_responses,
        readable_output=tableToMarkdown("FraudWatch Brands", final_outputs, ["name", "active", "client"], removeNull=True),
    )


""" MAIN FUNCTION """


def main() -> None:
    """
    Main function, parses params and runs command functions.
    """
    command = demisto.command()
    params = demisto.params()
    args = demisto.args()

    verify_certificate = not params.get("insecure", False)
    proxy = params.get("proxy", False)

    credentials = params.get("credentials")
    api_key = credentials.get("password")
    if not api_key:
        raise DemistoException("API token was not given. Please insert your API token. See documentation for further info")

    base_url = urljoin(params.get("base_url", DEFAULT_URL), "v1/")

    demisto.debug(f"Command being called is {command}")
    try:
        client = Client(api_key=api_key, base_url=base_url, verify=verify_certificate, proxy=proxy)

        if command == "test-module":
            return_results(test_module(client, params))

        elif command == "fetch-incidents":
            incidents, next_run = fetch_incidents_command(client, params, demisto.getLastRun())
            demisto.setLastRun(next_run)
            demisto.incidents(incidents)

        elif command == "fraudwatch-incidents-list":
            return_results(fraud_watch_incidents_list_command(client, args))

        elif command == "fraudwatch-incident-report":
            return_results(fraud_watch_incident_report_command(client, args))

        elif command == "fraudwatch-incident-update":
            return_results(fraud_watch_incident_update_command(client, args))

        elif command == "fraudwatch-incident-get-by-identifier":
            return_results(fraud_watch_incident_get_by_identifier_command(client, args))

        elif command == "fraudwatch-incident-forensic-get":
            return_results(fraud_watch_incident_forensic_get_command(client, args))

        elif command == "fraudwatch-incident-contact-emails-list":
            return_results(fraud_watch_incident_contact_emails_list_command(client, args))

        elif command == "fraudwatch-incident-messages-add":
            return_results(fraud_watch_incident_messages_add_command(client, args))

        elif command == "fraudwatch-incident-urls-add":
            return_results(fraud_watch_incident_urls_add_command(client, args))

        elif command == "fraudwatch-incident-attachment-upload":
            return_results(fraud_watch_attachment_upload_command(client, args))

        elif command == "fraudwatch-brands-list":
            return_results(fraud_watch_brands_list_command(client, args))

        else:
            raise NotImplementedError(f"Command {command} is not implemented.")

    # Log exceptions and return errors
    except Exception as e:
        return_error(f"Failed to execute {demisto.command()} command.\nError:\n{e!s}")


""" ENTRY POINT """

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