Details
| ID | HelloWorld |
|---|---|
| Provider | Open Source |
| Category | Utilities |
| From Version | 6.8.0 |
| Docker Image | demisto/python3:3.12.13.10116658 |
| Supported Modules | Agentix XSIAM Exposure Management |
README
This is the Hello World integration for getting started.
Configure HelloWorld on Cortex XSOAR
- Navigate to Settings > Integrations > Servers & Services.
- Search for HelloWorld.
-
Click Add instance to create and configure a new integration instance.
Parameter Description Required Source Reliability Reliability of the source providing the intelligence data. False Server URL (e.g., https://api.xsoar-example.com) True Fetch alerts False Alert type False Maximum number of alerts per fetch False API Key True Score threshold for IP reputation command Set this to determine the HelloWorld score that will determine if an IP is malicious (0-100) False Severity of alerts to fetch True First fetch time False Trust any certificate (not secure) False Use system proxy settings False - Click Test to validate the URLs, token, and connection.
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.
helloworld-say-hello
Hello command - prints hello to anyone.
Base Command
helloworld-say-hello
Input
| Argument Name | Description | Required |
|---|---|---|
| name | The name of whom you want to say hello to. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| hello | String | Should be Hello **something** here. |
Command example
!helloworld-say-hello name="Hello Dbot"
Context Example
{
"hello": "Hello Hello Dbot"
}
Human Readable Output
Hello Hello Dbot
helloworld-alert-list
Lists the example alerts as it would be fetched from the API.
Base Command
helloworld-alert-list
Input
| Argument Name | Description | Required |
|---|---|---|
| alert_id | Filter by alert item ID. If not provided, all IDs will be retrieved. | Optional |
| limit | How many alerts to fetch. Default is 10. | Optional |
| severity | The severity by which to filter the alerts. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| HelloWorld.alert.id | Number | The ID of the alert. |
| HelloWorld.alert.name | String | The name of the alert. |
| HelloWorld.alert.severity | String | The severity of the alert. |
| HelloWorld.alert.date | Date | The date of the alert occurrence. |
| HelloWorld.alert.status | String | The status of the alert. |
Command example
!helloworld-alert-list limit="3" severity="low"
Context Example
{
"HelloWorld": {
"Alert": [
{
"date": "2023-09-14T11:30:39.882955",
"id": 1,
"name": "XSOAR Test Alert #1",
"severity": "low",
"status": "Testing"
},
{
"date": "2023-09-14T11:30:39.882955",
"id": 2,
"name": "XSOAR Test Alert #2",
"severity": "low",
"status": "Testing"
},
{
"date": "2023-09-14T11:30:39.882955",
"id": 3,
"name": "XSOAR Test Alert #3",
"severity": "low",
"status": "Testing"
}
]
}
}
Human Readable Output
Items List (Sample Data)
date id name severity status 2023-09-14T11:30:39.882955 1 XSOAR Test Alert #1 low Testing 2023-09-14T11:30:39.882955 2 XSOAR Test Alert #2 low Testing 2023-09-14T11:30:39.882955 3 XSOAR Test Alert #3 low Testing
Command example
!helloworld-alert-list alert_id=2
Context Example
{
"HelloWorld": {
"Alert": {
"date": "2023-09-14T11:30:39.882955",
"id": 2,
"name": "XSOAR Test Alert #2",
"severity": "low",
"status": "Testing"
}
}
}
Human Readable Output
Items List (Sample Data)
date id name severity status 2023-09-14T11:30:39.882955 2 XSOAR Test Alert #2 low Testing
helloworld-alert-note-create
Example of creating a new item in the API.
Base Command
helloworld-alert-note-create
Input
| Argument Name | Description | Required |
|---|---|---|
| alert_id | The alert’s ID to add the note to. | Required |
| note_text | The comment to add to the note. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| HelloWorld.alert.id | Number | The ID of the alert. |
| HelloWorld.alert.name | String | The name of the alert. |
| HelloWorld.alert.severity | String | The severity of the alert. |
| HelloWorld.alert.date | Date | The date of the alert occurrence. |
| HelloWorld.alert.status | String | The status of the alert. |
Command example
!helloworld-alert-note-create alert_id=2 note_text=test
Context Example
{
"HelloWorld": {
"Note": {
"msg": "Note was created for alert #2 successfully with comment='test'",
"status": "success"
}
}
}
Human Readable Output
Note was created successfully.
ip
Return IP information and reputation.
Base Command
ip
Input
| Argument Name | Description | Required |
|---|---|---|
| ip | List of IPs. | Optional |
| threshold | If the IP has a reputation above the threshold, then the IP is defined as malicious. If threshold is not set, then the threshold from the instance configuration is used. Default is 65. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| DBotScore.Indicator | String | The indicator that was tested. |
| DBotScore.Score | Number | The actual score. |
| DBotScore.Type | String | The indicator type. |
| DBotScore.Vendor | String | The vendor used to calculate the score. |
| HelloWorld.IP.asn | String | The autonomous system name for the IP address. |
| HelloWorld.IP.asn_cidr | String | The ASN CIDR. |
| HelloWorld.IP.asn_country_code | String | The ASN country code. |
| HelloWorld.IP.asn_date | Date | The date on which the ASN was assigned. |
| HelloWorld.IP.asn_description | String | The ASN description. |
| HelloWorld.IP.asn_registry | String | The registry the ASN belongs to. |
| HelloWorld.IP.entities | String | Entities associated to the IP. |
| HelloWorld.IP.ip | String | The actual IP address. |
| HelloWorld.IP.network.cidr | String | Network CIDR for the IP address. |
| HelloWorld.IP.network.country | Unknown | The country of the IP address. |
| HelloWorld.IP.network.end_address | String | The last IP address of the CIDR. |
| HelloWorld.IP.network.events.action | String | The action that happened on the event. |
| HelloWorld.IP.network.events.actor | Unknown | The actor that performed the action on the event. |
| HelloWorld.IP.network.events.timestamp | String | The timestamp when the event occurred. |
| HelloWorld.IP.network.handle | String | The handle of the network. |
| HelloWorld.IP.network.ip_version | String | The IP address version. |
| HelloWorld.IP.network.links | String | Links associated to the IP address. |
| HelloWorld.IP.network.name | String | The name of the network. |
| HelloWorld.IP.network.notices.description | String | The description of the notice. |
| HelloWorld.IP.network.notices.links | Unknown | Links associated with the notice. |
| HelloWorld.IP.network.notices.title | String | Title of the notice. |
| HelloWorld.IP.network.parent_handle | String | Handle of the parent network. |
| HelloWorld.IP.network.raw | Unknown | Additional raw data for the network. |
| HelloWorld.IP.network.remarks | Unknown | Additional remarks for the network. |
| HelloWorld.IP.network.start_address | String | The first IP address of the CIDR. |
| HelloWorld.IP.network.status | String | Status of the network. |
| HelloWorld.IP.network.type | String | The type of the network. |
| HelloWorld.IP.query | String | IP address that was queried. |
| HelloWorld.IP.raw | Unknown | Additional raw data for the IP address. |
| HelloWorld.IP.score | Number | Reputation score from HelloWorld for this IP (0 to 100, where higher is worse). |
| IP.Address | String | IP address. |
| IP.Malicious.Vendor | String | The vendor reporting the IP address as malicious. |
| IP.Malicious.Description | String | A description explaining why the IP address was reported as malicious. |
| IP.ASN | String | The autonomous system name for the IP address. |
| IP.Relationships.EntityA | string | The source of the relationship. |
| IP.Relationships.EntityB | string | The destination of the relationship. |
| IP.Relationships.Relationship | string | The name of the relationship. |
| IP.Relationships.EntityAType | string | The type of the source of the relationship. |
| IP.Relationships.EntityBType | string | The type of the destination of the relationship. |
Command example
!ip ip="8.8.8.8"
Context Example
{
"DBotScore": {
"Indicator": "8.8.8.8",
"Reliability": "C - Fairly reliable",
"Score": 3,
"Type": "ip",
"Vendor": "HelloWorld Dev"
},
"HelloWorld": {
"IP": {
"id": "x.x.x.x",
"ip": "8.8.8.8",
"links": {
"self": "https://www.virustotal.com/api/v3/ip_addresses/x.x.x.x"
},
"type": "ip_address"
}
},
"IP": {
"Address": "8.8.8.8",
"Malicious": {
"Description": "Hello World returned reputation -4",
"Vendor": "HelloWorld Dev"
},
"Relationships": [
{
"EntityA": "8.8.8.8",
"EntityAType": "IP",
"EntityB": "h",
"EntityBType": "URL",
"Relationship": "related-to"
},
{
"EntityA": "8.8.8.8",
"EntityAType": "IP",
"EntityB": "t",
"EntityBType": "URL",
"Relationship": "related-to"
},
{
"EntityA": "8.8.8.8",
"EntityAType": "IP",
"EntityB": "t",
"EntityBType": "URL",
"Relationship": "related-to"
},
{
"EntityA": "8.8.8.8",
"EntityAType": "IP",
"EntityB": "p",
"EntityBType": "URL",
"Relationship": "related-to"
},
{
"EntityA": "8.8.8.8",
"EntityAType": "IP",
"EntityB": "s",
"EntityBType": "URL",
"Relationship": "related-to"
},
{
"EntityA": "8.8.8.8",
"EntityAType": "IP",
"EntityB": ":",
"EntityBType": "URL",
"Relationship": "related-to"
},
{
"EntityA": "8.8.8.8",
"EntityAType": "IP",
"EntityB": "/",
"EntityBType": "URL",
"Relationship": "related-to"
},
{
"EntityA": "8.8.8.8",
"EntityAType": "IP",
"EntityB": "/",
"EntityBType": "URL",
"Relationship": "related-to"
},
{
"EntityA": "8.8.8.8",
"EntityAType": "IP",
"EntityB": "w",
"EntityBType": "URL",
"Relationship": "related-to"
},
{
"EntityA": "8.8.8.8",
"EntityAType": "IP",
"EntityB": "w",
"EntityBType": "URL",
"Relationship": "related-to"
},
{
"EntityA": "8.8.8.8",
"EntityAType": "IP",
"EntityB": "w",
"EntityBType": "URL",
"Relationship": "related-to"
},
{
"EntityA": "8.8.8.8",
"EntityAType": "IP",
"EntityB": ".",
"EntityBType": "URL",
"Relationship": "related-to"
},
{
"EntityA": "8.8.8.8",
"EntityAType": "IP",
"EntityB": "v",
"EntityBType": "URL",
"Relationship": "related-to"
},
{
"EntityA": "8.8.8.8",
"EntityAType": "IP",
"EntityB": "i",
"EntityBType": "URL",
"Relationship": "related-to"
},
{
"EntityA": "8.8.8.8",
"EntityAType": "IP",
"EntityB": "r",
"EntityBType": "URL",
"Relationship": "related-to"
},
{
"EntityA": "8.8.8.8",
"EntityAType": "IP",
"EntityB": "u",
"EntityBType": "URL",
"Relationship": "related-to"
},
{
"EntityA": "8.8.8.8",
"EntityAType": "IP",
"EntityB": "s",
"EntityBType": "URL",
"Relationship": "related-to"
},
{
"EntityA": "8.8.8.8",
"EntityAType": "IP",
"EntityB": "t",
"EntityBType": "URL",
"Relationship": "related-to"
},
{
"EntityA": "8.8.8.8",
"EntityAType": "IP",
"EntityB": "o",
"EntityBType": "URL",
"Relationship": "related-to"
},
{
"EntityA": "8.8.8.8",
"EntityAType": "IP",
"EntityB": "t",
"EntityBType": "URL",
"Relationship": "related-to"
},
{
"EntityA": "8.8.8.8",
"EntityAType": "IP",
"EntityB": "a",
"EntityBType": "URL",
"Relationship": "related-to"
},
{
"EntityA": "8.8.8.8",
"EntityAType": "IP",
"EntityB": "l",
"EntityBType": "URL",
"Relationship": "related-to"
},
{
"EntityA": "8.8.8.8",
"EntityAType": "IP",
"EntityB": ".",
"EntityBType": "URL",
"Relationship": "related-to"
},
{
"EntityA": "8.8.8.8",
"EntityAType": "IP",
"EntityB": "c",
"EntityBType": "URL",
"Relationship": "related-to"
},
{
"EntityA": "8.8.8.8",
"EntityAType": "IP",
"EntityB": "o",
"EntityBType": "URL",
"Relationship": "related-to"
},
{
"EntityA": "8.8.8.8",
"EntityAType": "IP",
"EntityB": "m",
"EntityBType": "URL",
"Relationship": "related-to"
},
{
"EntityA": "8.8.8.8",
"EntityAType": "IP",
"EntityB": "/",
"EntityBType": "URL",
"Relationship": "related-to"
},
{
"EntityA": "8.8.8.8",
"EntityAType": "IP",
"EntityB": "a",
"EntityBType": "URL",
"Relationship": "related-to"
},
{
"EntityA": "8.8.8.8",
"EntityAType": "IP",
"EntityB": "p",
"EntityBType": "URL",
"Relationship": "related-to"
},
{
"EntityA": "8.8.8.8",
"EntityAType": "IP",
"EntityB": "i",
"EntityBType": "URL",
"Relationship": "related-to"
},
{
"EntityA": "8.8.8.8",
"EntityAType": "IP",
"EntityB": "/",
"EntityBType": "URL",
"Relationship": "related-to"
},
{
"EntityA": "8.8.8.8",
"EntityAType": "IP",
"EntityB": "v",
"EntityBType": "URL",
"Relationship": "related-to"
},
{
"EntityA": "8.8.8.8",
"EntityAType": "IP",
"EntityB": "3",
"EntityBType": "URL",
"Relationship": "related-to"
},
{
"EntityA": "8.8.8.8",
"EntityAType": "IP",
"EntityB": "/",
"EntityBType": "URL",
"Relationship": "related-to"
},
{
"EntityA": "8.8.8.8",
"EntityAType": "IP",
"EntityB": "i",
"EntityBType": "URL",
"Relationship": "related-to"
},
{
"EntityA": "8.8.8.8",
"EntityAType": "IP",
"EntityB": "p",
"EntityBType": "URL",
"Relationship": "related-to"
},
{
"EntityA": "8.8.8.8",
"EntityAType": "IP",
"EntityB": "_",
"EntityBType": "URL",
"Relationship": "related-to"
},
{
"EntityA": "8.8.8.8",
"EntityAType": "IP",
"EntityB": "a",
"EntityBType": "URL",
"Relationship": "related-to"
},
{
"EntityA": "8.8.8.8",
"EntityAType": "IP",
"EntityB": "d",
"EntityBType": "URL",
"Relationship": "related-to"
},
{
"EntityA": "8.8.8.8",
"EntityAType": "IP",
"EntityB": "d",
"EntityBType": "URL",
"Relationship": "related-to"
},
{
"EntityA": "8.8.8.8",
"EntityAType": "IP",
"EntityB": "r",
"EntityBType": "URL",
"Relationship": "related-to"
},
{
"EntityA": "8.8.8.8",
"EntityAType": "IP",
"EntityB": "e",
"EntityBType": "URL",
"Relationship": "related-to"
},
{
"EntityA": "8.8.8.8",
"EntityAType": "IP",
"EntityB": "s",
"EntityBType": "URL",
"Relationship": "related-to"
},
{
"EntityA": "8.8.8.8",
"EntityAType": "IP",
"EntityB": "s",
"EntityBType": "URL",
"Relationship": "related-to"
},
{
"EntityA": "8.8.8.8",
"EntityAType": "IP",
"EntityB": "e",
"EntityBType": "URL",
"Relationship": "related-to"
},
{
"EntityA": "8.8.8.8",
"EntityAType": "IP",
"EntityB": "s",
"EntityBType": "URL",
"Relationship": "related-to"
},
{
"EntityA": "8.8.8.8",
"EntityAType": "IP",
"EntityB": "/",
"EntityBType": "URL",
"Relationship": "related-to"
},
{
"EntityA": "8.8.8.8",
"EntityAType": "IP",
"EntityB": "x",
"EntityBType": "URL",
"Relationship": "related-to"
},
{
"EntityA": "8.8.8.8",
"EntityAType": "IP",
"EntityB": ".",
"EntityBType": "URL",
"Relationship": "related-to"
},
{
"EntityA": "8.8.8.8",
"EntityAType": "IP",
"EntityB": "x",
"EntityBType": "URL",
"Relationship": "related-to"
},
{
"EntityA": "8.8.8.8",
"EntityAType": "IP",
"EntityB": ".",
"EntityBType": "URL",
"Relationship": "related-to"
},
{
"EntityA": "8.8.8.8",
"EntityAType": "IP",
"EntityB": "x",
"EntityBType": "URL",
"Relationship": "related-to"
},
{
"EntityA": "8.8.8.8",
"EntityAType": "IP",
"EntityB": ".",
"EntityBType": "URL",
"Relationship": "related-to"
},
{
"EntityA": "8.8.8.8",
"EntityAType": "IP",
"EntityB": "x",
"EntityBType": "URL",
"Relationship": "related-to"
}
]
}
}
Human Readable Output
IP (Sample Data)
id ip links type x.x.x.x 8.8.8.8 self: https://www.virustotal.com/api/v3/ip_addresses/x.x.x.x ip_address Attributes
as_owner asn continent country jarm last_analysis_stats last_modification_date network regional_internet_registry reputation tags total_votes whois_date EMERALD-ONION NA US :jarm: harmless: 72
malicious: 5
suspicious: 2
timeout: 0
undetected: 8:cidr: ARIN values: harmless: 0
malicious: 1
Configuration parameters
integrationReliability— Source Reliabilityurl— Server URL (e.g., https://api.xsoar-example.com) (required)isFetch— Fetch incidentsincidentType— Incident typeincidentFetchInterval— Incidents Fetch Intervalmax_fetch— Maximum number of alerts per fetchcredentials— (required)threshold_ip— Score threshold for IP reputation commandseverity— Severity of alerts to fetch (required)first_fetch— First fetch timeinsecure— Trust any certificate (not secure)proxy— Use system proxy settings
Commands (4)
-
helloworld-alert-listLists the example alerts as it would be fetched from the API.
-
helloworld-alert-note-createExample of creating a new item in the API.
-
helloworld-say-helloHello command - prints hello to anyone.
-
ipReturn IP information and reputation.
import demistomock as demisto # noqa: F401 from CommonServerPython import * # noqa: F401 """HelloWorld Integration for Cortex XSOAR (aka Demisto) This integration is a good example on you can build a Cortex XSOAR Integration using Python 3. Please follow the documentation links below and make sure that your integration follows the Code Conventions and passes the Linting phase. Developer Documentation: https://xsoar.pan.dev/docs/welcome Code Conventions: https://xsoar.pan.dev/docs/integrations/code-conventions Linting: https://xsoar.pan.dev/docs/integrations/linting When building a Cortex XSOAR integration that is reusable, a lot of effort must be placed in the design. We recommend to fill a Design Document template, that allows you to capture Use Cases, Requirements and Inputs/Outputs. Example Design document for the this Integration (HelloWorld): https://docs.google.com/document/d/1wETtBEKg37PHNU8tYeB56M1LE314ux086z3HFeF_cX0 HelloWorld API -------------- The HelloWorld API is a simple API that shows a realistic use case for an XSOAR integration. This API has a few basic functions: - Alerts: the endpoint returns mocked alerts and allows you to search based on a number of parameters, such as severity. It can also return a single alert by ID. This is used to create new alerts in XSOAR by using the ``fetch-incidents`` command, which is by default invoked every minute. - Reputation (ip): this endpoint return a WHOIS lookup of the ip given as well as a reputation score (from 0 to 100) that is used to determine whether the entity is malicious. This endpoint is called by XSOAR reputation command ``ip`` that is run automatically every time an indicator is extracted in XSOAR. As a best practice of design, it is important to map and document the mapping between a score in the original API format (0 to 100 in this case) to a score in XSOAR format (0 to 3). This score is called ``DBotScore``, and is returned in the context to allow automated handling of indicators based on their reputation. More information: https://xsoar.pan.dev/docs/integrations/dbot - Create Note: to demonstrate how to run commands that are not returning instant data, the API provides a command simulates creating a new entity in the API. This can be used for endpoints that take longer than a few seconds to complete with the GenericPolling mechanism to implement the job polling loop. The results can be returned in JSON or attachment file format. Info on GenericPolling: https://xsoar.pan.dev/docs/playbooks/generic-polling This integration also has a ``say-hello`` command for backward compatibility, that doesn't connect to an API and just returns a ``Hello {name}`` string, where name is the input value provided. Integration File Structure -------------------------- An integration usually consists of the following parts: - Imports - Constants - Client Class - Helper Functions - Command Functions - Main Function - Entry Point Imports ------- Here you can import Python module you need for your integration. If you need a module that is not part of the default XSOAR Docker images, you can add a custom one. More details: https://xsoar.pan.dev/docs/integrations/docker There are also internal imports that are used by XSOAR: - demistomock (imported as demisto): allows your code to work offline for testing. The actual ``demisto`` module is provided at runtime when the code runs in XSOAR. - CommonServerPython.py: contains a set of helper functions, base classes and other useful components that will make your integration code easier to maintain. - CommonServerUserPython.py: includes a set of user defined commands that are specific to an XSOAR installation. Do not use it for integrations that are meant to be shared externally. These imports are automatically loaded at runtime within the XSOAR script runner, so you shouldn't modify them Constants --------- Usually some constants that do not require user parameters or inputs, such as the default API entry point for your service, or the maximum numbers of alerts to fetch every time. Client Class ------------ We recommend to use a Client class to wrap all the code that needs to interact with your API. Moreover, we recommend, when possible, to inherit from the BaseClient class, defined in CommonServerPython.py. This class already handles a lot of the work, such as system proxy settings, SSL certificate verification and exception handling for HTTP errors. Note that the Client class should NOT contain any Cortex XSOAR specific code, i.e. it shouldn't use anything in the ``demisto`` class (functions such as ``demisto.args()`` or ``demisto.results()`` or even ``return_results`` and ``return_error``. You will use the Command Functions to handle XSOAR inputs and outputs. When calling an API, you should use the ``_http.request()`` method and you can return the raw data to the calling function (usually a Command function). You should usually have one function for each API endpoint. Look at the code and the commends of this specific class to better understand the implementation details. Helper Functions ---------------- Helper functions are usually used as utility functions that are used by several command functions throughout your code. For example they map arguments to types or convert severity formats from integration-specific to XSOAR. Many helper functions are already defined in ``CommonServerPython.py`` and are often very handy. Command Functions ----------------- Command functions perform the mapping between XSOAR inputs and outputs to the Client class functions inputs and outputs. As a best practice, they shouldn't contain calls to ``demisto.args()``, ``demisto.results()``, ``return_error`` and ``demisto.command()`` as those should be handled through the ``main()`` function. However, in command functions, use ``demisto`` or ``CommonServerPython.py`` artifacts, such as ``demisto.debug()`` or the ``CommandResults`` class and the ``Common.*`` classes. Usually you will have one command function for every specific XSOAR command you want to implement in your integration, plus ``test-module``, ``fetch-incidents`` and ``fetch-indicators``(if the latter two are supported by your integration). Each command function should invoke one specific function of the Client class. Command functions, when invoked through an XSOAR command usually return data using the ``CommandResults`` class, that is then passed to ``return_results()`` in the ``main()`` function. ``return_results()`` is defined in ``CommonServerPython.py`` to return the data to XSOAR. ``return_results()`` actually wraps ``demisto.results()``. You should never use ``demisto.results()`` directly. Sometimes you will need to return values in a format that is not compatible with ``CommandResults`` (for example files): in that case you must return a data structure that is then pass passed to ``return.results()``. In any case you should never call ``return_results()`` directly from the command functions. When you use create the CommandResults object in command functions, you usually pass some types of data: - Human Readable: usually in Markdown format. This is what is presented to the analyst in the War Room. You can use ``tableToMarkdown()``, defined in ``CommonServerPython.py``, to convert lists and dicts in Markdown and pass it to ``return_results()`` using the ``readable_output`` argument, or the ``return_results()`` function will call ``tableToMarkdown()`` automatically for you. - Context Output: this is the machine readable data, JSON based, that XSOAR can parse and manage in the Playbooks or Incident's War Room. The Context Output fields should be defined in your integration YML file and is important during the design phase. Make sure you define the format and follow best practices. You can use ``demisto-sdk json-to-outputs`` to autogenerate the YML file outputs section. Context output is passed as the ``outputs`` argument in ``demisto_results()``, and the prefix (i.e. ``HelloWorld.Alert``) is passed via the ``outputs_prefix`` argument. More information on Context Outputs, Standards, DBotScore and demisto-sdk: https://xsoar.pan.dev/docs/integrations/code-conventions#outputs https://xsoar.pan.dev/docs/integrations/context-and-outputs https://xsoar.pan.dev/docs/integrations/context-standards https://xsoar.pan.dev/docs/integrations/dbot https://github.com/demisto/demisto-sdk/blob/master/demisto_sdk/commands/json_to_outputs/README.md Also, when you write data in the Context, you want to make sure that if you return updated information for an entity, to update it and not append to the list of entities (i.e. in HelloWorld you want to update the status of an existing ``HelloWorld.Alert`` in the context when you retrieve it, rather than adding a new one if you already retrieved it). To update data in the Context, you can define which is the key attribute to use, such as (using the example): ``outputs_key_field='alert_id'``. This means that you are using the ``alert_id`` key to determine whether adding a new entry in the context or updating an existing one that has the same ID. You can look at the examples to understand how it works. More information here: https://xsoar.pan.dev/docs/integrations/context-and-outputs https://xsoar.pan.dev/docs/integrations/code-conventions#outputs https://xsoar.pan.dev/docs/integrations/dt - Raw Output: this is usually the raw result from your API and is used for troubleshooting purposes or for invoking your command from Automation Scripts. If not specified, ``return_results()`` will use the same data as ``outputs``. Main Function ------------- The ``main()`` function takes care of reading the integration parameters via the ``demisto.params()`` function, initializes the Client class and checks the different options provided to ``demisto.commands()``, to invoke the correct command function passing to it ``demisto.args()`` and returning the data to ``return_results()``. If implemented, ``main()`` also invokes the function ``fetch_incidents()``with the right parameters and passes the outputs to the ``demisto.incidents()`` function. ``main()`` also catches exceptions and returns an error message via ``return_error()``. Entry Point ----------- This is the integration code entry point. It checks whether the ``__name__`` variable is ``__main__`` , ``__builtin__`` (for Python 2) or ``builtins`` (for Python 3) and then calls the ``main()`` function. Just keep this convention. """ import json from typing import Any import dateparser from CommonServerUserPython import * """ CONSTANTS """ LOG_LINE = "HelloWorldDebugLog: " # Make sure to use a line easily to search and read in logs. DATE_FORMAT = "%Y-%m-%dT%H:%M:%SZ" MAX_ALERTS_TO_FETCH = 50 DEFAULT_INDICATORS_THRESHOLD = 65 HELLOWORLD_SEVERITIES = ["Low", "Medium", "High", "Critical"] LIMIT = 10 DEFAULT_PAGE_SIZE = 5 DUMMY_API_KEY = "dummy-key" ITEM_TEMPLATE = '"id": {id}, "name": "XSOAR Test Alert #{id}", "severity": "{severity}", "date": "{date}", "status": "{status}"' """ CLIENT CLASS """ class Client(BaseClient): """Client class to interact with the service API This Client implements API calls, and does not contain any Demisto logic. Should only do requests and return data. It inherits from BaseClient defined in CommonServer Python. Most calls use _http_request() that handles proxy, SSL verification, etc. For this HelloWorld implementation, no special attributes defined """ def specific_api_endpoint_call_example(self, param1, param2): """Example of calling a real specific API endpoint""" # INTEGRATION DEVELOPER TIP # 1. The assign_params function (Found in CommonServerPython) can easily create a None-free dictionary. # Use it to send the ``json_data`` argument to ``self._http_request`` method (request body). # When the request body is complex, best practice is to build the dictionary outside (pass as argument). # 2. It's possible to get the whole response and not just the data part, using the ``resp_type`` argument. # 3. It's possible to get responses for statuses other than 200 by using the ``ok_codes`` argument- # otherwise, the ``self._http_request method`` will fail when parsing it. # 4. If the URL parameters are complex (filters, etc.) - # it is possible to pass them using the ``params`` argument. url_suffix = f"/api/endpoint/{param1}/{param2}" response = self._http_request(method="GET", url_suffix=url_suffix) return response def get_ip_reputation(self, ip: str) -> dict[str, Any]: """For developing walkthrough purposes, this is a dummy response. For real API calls, see the specific_api_endpoint_call_example method. Args: ip (str): IP address to get the reputation for. Returns: dict: dict containing the dummy IP reputation for an example ip as it should be returned from the API. """ mocked_response = { "attributes": { "as_owner": "EMERALD-ONION", "asn": 396507, "continent": "NA", "country": "US", "jarm": ":jarm:", "last_analysis_stats": {"harmless": 72, "malicious": 5, "suspicious": 2, "timeout": 0, "undetected": 8}, "last_modification_date": 1613300914, "network": ":cidr:", "regional_internet_registry": "ARIN", "reputation": -4, "tags": [], "total_votes": {"harmless": 0, "malicious": 1}, "whois_date": 1611870274, }, "id": "x.x.x.x", "links": {"self": "https://www.virustotal.com/api/v3/ip_addresses/x.x.x.x"}, "type": "ip_address", } return mocked_response def say_hello(self, name: str) -> str: """ Returns a string: 'Hello {name}' Args: name (str): name to append to the 'Hello' string. Returns: str: string containing 'Hello {name}' """ return f"Hello {name}" def get_alert_list(self, limit: int, severity: str = None, last_id: int = 0) -> list[dict]: """For developing walkthrough purposes, this is a dummy response. For real API calls, see the specific_api_endpoint_call_example method. Args: limit (int): The number of item to generate. severity (str) : The severity value of the items returned. Returns: list[dict]: Dummy data of items as it would return from API. """ mock_response: list[dict] = [] for i in range(limit): item = ITEM_TEMPLATE.format( id=last_id + i + 1, severity=severity if severity else "", date=datetime(2023, 9, 14, 11, 30, 39, 882955).isoformat(), status="Testing", ) dict_item = json.loads("{" + item + "}") mock_response.append(dict_item) return mock_response def get_alert(self, alert_id: int) -> list[dict]: """For developing walkthrough purposes, this is a dummy response. For real API calls, see the specific_api_endpoint_call_example method. Args: alert_id (int) : An alert to retrieve. Returns: dict: Dummy data of alert as it would return from API. """ item = ITEM_TEMPLATE.format( id=alert_id, severity="low", date=datetime(2023, 9, 14, 11, 30, 39, 882955).isoformat(), status="Testing" ) return json.loads("{" + item + "}") def create_note(self, alert_id: int, comment: str) -> dict: """ This function calls the API to create a new note in an alert. For real API calls, see the specific_api_endpoint_call_example method. Args: alert_id (int): a number represent an alert. comment (str): A text comment to add to the alert as a note. Returns: dict: The summary of the newly created note from the API response. """ return {"status": "success", "msg": f"Note was created for alert #{alert_id} successfully with {comment=}"} def get_alert_list_for_fetch(self, limit, start_time: datetime, last_id: int = 0, severity: str = "low") -> list[dict]: """This function return dummy events for fetch. Args: limit (int): The number of alert to fetch. start_time (str, optional): The time to start fetch alerts from. Defaults to None. severity (str, optional): The severity of the alerts fetched. Defaults to None. """ def mock_time(item): item["id"] = last_id + 1 item["date"] = datetime.strftime(start_time + timedelta(minutes=1), DATE_FORMAT) incidents = self.get_alert_list(limit=limit, severity=severity, last_id=last_id) demisto.debug("Setting alerts time to now.") for item in incidents: mock_time(item) last_id += 1 return incidents """ HELPER FUNCTIONS """ def validate_api_key(api_key: str) -> None: """ This is a validation that the api-key is valid. It is not needed when dealing with a real API. But we wanted to give you a full experience. Some APIs handle invalid credentials with an invalid status code with an unclear message, which users will not understand. It can be handled in the commands or in the main function when the status code implies incorrect credentials. Args: api_key (str): api to connect to the API. Raises: DemistoException: Exception with a nicer error when credential are invalid. """ if api_key != DUMMY_API_KEY: raise DemistoException("Invalid Credentials. Please Verify your Connection parameters.") def convert_to_demisto_severity(severity: str) -> int: """ Maps HelloWorld severity to Cortex XSOAR severity. Converts the HelloWorld alert severity level ('Low', 'Medium', 'High', 'Critical') to Cortex XSOAR alert severity (1 to 4). Args: severity (str): severity as returned from the HelloWorld API. first_fetch_time (int): The first fetch time as configured in the integration params. Returns: int: Cortex XSOAR Severity (1 to 4) """ # In this case the mapping is straightforward, but more complex mappings # might be required in your integration, so a dedicated function is # recommended. This mapping should also be documented. return { "low": IncidentSeverity.LOW, "medium": IncidentSeverity.MEDIUM, "high": IncidentSeverity.HIGH, "critical": IncidentSeverity.CRITICAL, "unknown": IncidentSeverity.UNKNOWN, }[severity] def dedup_by_ids(alerts: list[dict], ids_to_compare: list[int]) -> tuple[list[dict], int]: """Gets a list of new IDs and a list of existing IDs, and returns a list with only alerts with id not found in ids_to_compare. For example, if alerts=[{'a':2},{'b': 3}] and ids_to_compare=[1,2], [3] is returned. Args: new_ids (list[dict]): A list of alerts to compare. Assuming the existence of "id" key. ids_to_compare (list[str]): A list of existing strings Returns: list[dict]: A list of only new unique alerts. int: The number of duplicates found. """ dups = [] dedup = [] for alert in alerts: if id := alert["id"] in ids_to_compare: dups.append(id) else: dedup.append(alert) return dedup, len(dups) """ COMMAND FUNCTIONS """ def test_module(client: Client, params: dict[str, Any]) -> str: """ Tests API connectivity and authentication' When 'ok' is returned it indicates the integration works like it is supposed to and connection to the service is successful. Raises exceptions if something goes wrong. Args: client (Client): HelloWorld client to use. params (Dict): Integration parameters. first_fetch_time (int): The first fetch time as configured in the integration params. Returns: str: 'ok' if test passed, anything else will raise an exception and will fail the test. """ # INTEGRATION DEVELOPER TIP # Client class should raise the exceptions, but if the test fails # the exception text is printed to the Cortex XSOAR UI. # If you have some specific errors you want to capture (i.e. auth failure) # you should catch the exception here and return a string with a more # readable output (for example return 'Authentication Error, API Key # invalid'). # Cortex XSOAR will print everything you return different than 'ok' as # an error try: time = dateparser.parse("1 minute") assert time severity = params.get("severity", None) if params.get("isFetch"): # Tests fetch alert: fetch_incidents(client=client, max_results=1, last_run={}, first_fetch_time=time.isoformat(), severity=severity) else: client.get_alert_list(limit=1, severity=params.get("severity")) except DemistoException as e: if "Forbidden" in str(e): return "Authorization Error: make sure API Key is correctly set" else: raise e return "ok" def say_hello_command(client: Client, args: dict[str, Any]) -> CommandResults: """ helloworld-say-hello command: Returns Hello {somename} Args: client (Client): HelloWorld client to use. args (dict): all command arguments, usually passed from ``demisto.args()``. ``args['name']`` is used as input name. Returns: CommandResults: A ``CommandResults`` object that is then passed to ``return_results``, that hello world message. """ # INTEGRATION DEVELOPER TIP # In this case 'name' is an argument set in the HelloWorld.yml file as mandatory, # so the null check here as XSOAR will always check it before your code is called. # Although it's not mandatory to check, you are welcome to do so. name = args.get("name", None) if not name: raise ValueError("name not specified") # Call the Client function and get the raw response result = client.say_hello(name) # Create the human readable output. # It will be in markdown format - https://www.markdownguide.org/basic-syntax/ # More complex output can be formatted using ``tableToMarkDown()`` defined # in ``CommonServerPython.py`` readable_output = f"## {result}" # More information about Context: # https://xsoar.pan.dev/docs/integrations/context-and-outputs # We return a ``CommandResults`` object, and we want to pass a custom # markdown here, so the argument ``readable_output`` is explicit. If not # passed, ``CommandResults``` will do a ``tableToMarkdown()`` do the data # to generate the readable output. return CommandResults(readable_output=readable_output, outputs_prefix="hello", outputs_key_field="", outputs=result) def fetch_incidents( client: Client, max_results: int, last_run: dict, first_fetch_time: str, severity: str = "low", _page_size: int = DEFAULT_PAGE_SIZE, ) -> tuple[dict, list[dict]]: """ This function retrieves new alerts every interval (default is 1 minute). It has to implement the logic of making sure that alerts are fetched only once and no alerts are missed. By default it's invoked by Cortex XSOAR every minute. It will use last_run to save the timestamp of the last alert it processed. If last_run is not provided, it should use the integration parameter first_fetch_time to determine when to start fetching the first time. Args: client (Client): HelloWorld client to use. max_results (int): Maximum numbers of alerts per fetch. last_run (dict): A dict with a key containing the latest alert created time we got from last fetch. first_fetch_time(int): If last_run is None (first time we are fetching), it contains the timestamp in milliseconds on when to start fetching alerts. severity (str): severity of the alert to search for. page_size (int): number of alerts to retrieve per page from the API. This should be standard when dealing with pagination. It contains `_` here since we are not using it in an API call. Returns: dict: Next run dictionary containing the timestamp that will be used in ``last_run`` on the next fetch. list: List of alerts that will be created in Cortex XSOAR. """ # INTEGRATION DEVELOPER TIP # You can use the last_run to save important information between fetches (For example, the last fetched alert's IDs). # Note that the last_run can store only small amounts of data, abusing it might cause unexpected behavior. # INTEGRATION DEVELOPER TIP # The fetch-incident function is usually *very* hard to debug in a client's environment. # Logging the steps correctly can save a lot of time and effort. # Make sure to use demisto.debug() to avoid flooding the general log. # Get the last fetch time, if exists last_fetch = last_run.get("last_fetch", None) last_ids: list[int] = last_run.get("last_ids", []) or [] # Handle first fetch time if last_fetch is None: # if missing, use what provided via first_fetch_time last_fetch = first_fetch_time else: # otherwise use the stored last fetch last_fetch = last_fetch assert last_fetch # Initialize an empty list of alerts to return. Incidents are processed alerts. incidents: list[dict[str, Any]] = [] last_dummy_id = max(last_ids) if last_ids else 0 demisto.debug(f"Running API query with {last_fetch=}, {severity=}") # Calling the relevant client method. Note that sometimes pagination is in order. # For pagination related information, see: # https://xsoar.pan.dev/docs/integrations/code-conventions#pagination-in-integration-commands. alerts = client.get_alert_list_for_fetch( limit=max_results, start_time=dateparser.parse(last_fetch), # type: ignore severity=severity, last_id=last_dummy_id, ) demisto.debug(f"Received {len(alerts)} alerts from server.") # INTEGRATION DEVELOPER TIP # alerts might be duplicated in some cases: # 1. Pagination done without next page's token - # The exact time of the last alert is queried again so the same alert will be fetched again. # 2. Limit is exceeded but there are more alert in the same time to fetch in the next run- # (Mostly happens when API does not support milliseconds). alerts, number_of_dups = dedup_by_ids(alerts, last_ids) demisto.debug(f"recieved {number_of_dups} duplicates alerts to skip.") # Get the last alert time. # We assume asc order so we can get all the alerts fetched with the exact same time and avoid it in the next run. # If no results returned from API, we use the last alert fetched from last_run. last_fetched_time = alerts[-1]["date"] if alerts else last_fetch last_ids = [] demisto.debug(f"{alerts=}") for alert in alerts: # To prevent duplicates, we are only adding alerts with creation_time > last_fetched. # When we cannot assume alerts order in the response, we can use this code: # if last_fetch: # if alert_created_time <= last_fetch: # continue # Update last run and add alert if the alert is newer than last fetch # if alert_created_time > latest_created_time: # latest_created_time = alert_created_time # Otherwise, we might need to add the alert ID to the last_ids so it will be avoided in the next run. if alert["date"] == last_fetched_time: last_ids.append(alert["id"]) # Formatting the alerts as needed (Adding fields, Removing sensitive ones, etc.) alert["name"] = alert.get("name") or "Hello World Alert" # INTEGRATION DEVELOPER TIP # The incident dict is initialized with a few mandatory fields: # name: the incident name # occurred: the time on when the incident occurred, in ISO8601 format, which matches the API response in this case. # we can use timestamp_to_datestring() from CommonServerPython.py to handle the conversion when dealing with timestamps. # rawJSON: everything else is packed in a string via json.dumps() and is included in rawJSON. # It will be used later for classification and mapping inside Cortex XSOAR. # severity: it's not mandatory, but is recommended. It must be # converted to XSOAR specific severity (int 1 to 4) # Note that there are other fields commented out here. You can do some # mapping of fields (either out of the box fields, like "details" and # "type") or custom fields (like "helloworldid") directly here in the # code, or they can be handled in the classification and mapping phase (Most Recommended). # In either case customers can override them. We leave the values commented out here, but you can use them if you want. incident = { "name": alert["name"], # 'details': alert['name'], "occurred": alert["date"], "rawJSON": json.dumps(alert), # 'type': 'Hello World Alert', # Map to a specific XSOAR alert Type "severity": convert_to_demisto_severity(alert.get("severity", "low")), # 'CustomFields': { # Map specific XSOAR Custom Fields # 'helloworldid': alert.get('id'), # 'helloworldstatus': alert.get('status'), # 'helloworldvalidity': alert.get('validity') # } } incidents.append(incident) # Save the next_run as a dict with the last_fetch key to be stored. # When we reached the limit but there are still alerts to get from this run, # the leftovers will be returned in the next run by time. demisto.debug(f"setting next run- {last_fetched_time=}") next_run = {"last_fetch": last_fetched_time, "last_ids": last_ids} return next_run, incidents def ip_reputation_command( client: Client, args: dict[str, Any], default_threshold: int, reliability: DBotScoreReliability | str ) -> list[CommandResults]: """ ip command: Returns IP reputation for a list of IPs Args: client (Client): HelloWorld client to use. args (dict): all command arguments, usually passed from ``demisto.args()``. ``args['ip']`` is a list of IPs or a single IP. We are providing a dummy response for the ip 8.8.8.8. ``args['threshold']`` threshold to determine whether an IP is malicious. default_threshold (int): default threshold to determine whether an IP is malicious if threshold is not specified in the XSOAR arguments. reliability (DBotScoreReliability): reliability of the source providing the intelligence data. Returns: CommandResults: A ``CommandResults`` object that is then passed to ``return_results``, that contains IPs. """ # INTEGRATION DEVELOPER TIP # Reputation commands usually support multiple inputs (i.e. arrays), so # they can be invoked once in Cortex XSOAR. In case the API supports a single # IP at a time, we will cycle this for all the members of the array. # We use argToList(), implemented in CommonServerPython.py to automatically # return a list of a single element even if the provided input is a scalar. ips = argToList(args.get("ip")) if not ips: raise ValueError("IP(s) not specified") # It's a good practice to document the threshold you use to determine # if a score is malicious in your integration documentation. # Thresholds should also be possible to override, as in this case, # where threshold is an actual argument of the command. threshold = int(args.get("threshold", default_threshold)) # Initialize an empty list of CommandResults to return # each CommandResult will contain context standard for IP command_results: list[CommandResults] = [] for ip in ips: if not is_ip_valid(ip, accept_v6_ips=True): # check IP's validity raise ValueError(f'IP "{ip}" is not valid') ip_data = client.get_ip_reputation(ip) ip_data["ip"] = ip # This is an example of creating relationships in reputation commands. # We will create relationships between indicators only in case that the API returns information about # the relationship between two indicators. # See https://xsoar.pan.dev/docs/integrations/generic-commands-reputation#relationships relationships_list = [] links = ip_data.get("links", {}).get("self", "") for link in links: relationships_list.append( EntityRelationship( entity_a=ip, entity_a_type=FeedIndicatorType.IP, name="related-to", entity_b=link, entity_b_type=FeedIndicatorType.URL, brand="HelloWorld", ) ) # We can use demisto.get to get nested values from dict. reputation = int(demisto.get(ip_data, "attributes.reputation", defaultParam=0)) # HelloWorld score to XSOAR reputation mapping # See: https://xsoar.pan.dev/docs/integrations/dbot # We are using Common.DBotScore as macros to simplify # the mapping. if reputation == 0: score = Common.DBotScore.NONE # unknown elif reputation < threshold / 2: score = Common.DBotScore.BAD # bad elif reputation < threshold: score = Common.DBotScore.SUSPICIOUS # suspicious else: score = Common.DBotScore.GOOD # good # The context is bigger here than other commands, as it consists in 3 # parts: the vendor-specific context (HelloWorld), the standard-context # (IP) and the DBotScore. # More information: # https://xsoar.pan.dev/docs/integrations/context-and-outputs # https://xsoar.pan.dev/docs/integrations/context-standards # https://xsoar.pan.dev/docs/integrations/dbot # Also check the HelloWorld Design Document # Create the DBotScore structure first using the Common.DBotScore class. dbot_score = Common.DBotScore( indicator=ip, indicator_type=DBotScoreType.IP, integration_name="HelloWorld", score=score, malicious_description=f"Hello World returned reputation {reputation}", reliability=reliability, ) # Create the IP Standard Context structure using Common.IP and add # dbot_score to it. ip_standard_context = Common.IP(ip=ip, asn=ip_data.get("asn"), dbot_score=dbot_score, relationships=relationships_list) # INTEGRATION DEVELOPER TIP # In the integration specific Context output (HelloWorld.IP) in this # example you want to provide a lot of information as it can be used # programmatically from within Cortex XSOAR in playbooks and commands. # On the other hand, this API is way to verbose, so we want to select # only certain keys to be returned in order not to clog the context # with useless information. What to actually return in the context and # to define as a command output is subject to design considerations. # INTEGRATION DEVELOPER TIP # To generate the Context Outputs on the YML use ``demisto-sdk``'s # ``json-to-outputs`` option. # Define which fields we want to exclude from the context output as they are too verbose. # We will use attributes key separately. Just make sure to keep the whole response somewhere. ip_context_excluded_fields = ["whois", "attributes"] ip_data_outputs = {k: ip_data[k] for k in ip_data if k not in ip_context_excluded_fields} # In this case we want to use an custom markdown to specify the table title, # but otherwise ``CommandResults()`` will call ``tableToMarkdown()`` # automatically. readable_attributes = tableToMarkdown("Attributes", ip_data["attributes"], is_auto_json_transform=True) readable_output = tableToMarkdown("IP (Sample Data)", ip_data_outputs) readable_output += readable_attributes # INTEGRATION DEVELOPER TIP # The output key will be ``HelloWorld.IP``, using ``ip`` as the key field. # ``indicator`` is used to provide the context standard (IP) command_results.append( CommandResults( readable_output=readable_output, raw_response=ip_data, outputs_prefix="HelloWorld.IP", outputs_key_field="ip", outputs=ip_data_outputs, indicator=ip_standard_context, relationships=relationships_list, ) ) return command_results def alert_list_command(client: Client, args: dict[str, Any]) -> CommandResults: alert_id = arg_to_number(args.get("alert_id")) severity = args.get("severity") if not severity and not alert_id: raise DemistoException("Severity is a required parameter.") # Pagination params. See https://xsoar.pan.dev/docs/integrations/code-conventions#pagination-in-integration-commands limit = arg_to_number(args.get("limit")) or LIMIT if alert_id: # If alert_id is provided, we only need one call to API and pagination is not needed. full_res = client.get_alert(alert_id) if isinstance(full_res, dict): full_res = [full_res] else: full_res = client.get_alert_list(limit=limit, severity=severity) readable_output = tableToMarkdown("Items List (Sample Data)", full_res) return CommandResults( readable_output=readable_output, outputs_prefix="HelloWorld.Alert", outputs_key_field="id", outputs=full_res ) def alert_note_create_command(client: Client, args: dict[str, Any]) -> CommandResults: alert_id = arg_to_number(args["alert_id"], required=True) note = args["note_text"] if not alert_id: raise DemistoException("Please provide alert id.") res_data = client.create_note(alert_id=alert_id, comment=note) return CommandResults( readable_output="Note was created successfully.", outputs_prefix="HelloWorld.Note", outputs_key_field="id", outputs=res_data, ) """ MAIN FUNCTION """ def main() -> None: # pragma: no cover """ main function, parses params and runs command functions """ params = demisto.params() args = demisto.args() command = demisto.command() api_key = params.get("credentials", {}).get("password") validate_api_key(api_key) # get the service API url base_url = params.get("url") # If your Client class inherits from BaseClient, SSL verification is handled out-of-the-box by it. # Just pass ``verify_certificate`` to the Client constructor verify_certificate = not params.get("insecure", False) # How much time before the first fetch to retrieve alerts first_fetch_time = arg_to_datetime(arg=params.get("first_fetch", "3 days"), arg_name="First fetch time", required=True) assert first_fetch_time # if your Client class inherits from BaseClient, system proxy is handled # out of the box by it, just pass ``proxy`` to the Client constructor proxy = params.get("proxy", False) # Integration that implements reputation commands (e.g. url, ip, domain,..., etc) must have # a reliability score of the source providing the intelligence data. reliability = params.get("integrationReliability") or DBotScoreReliability.C # INTEGRATION DEVELOPER TIP # You can use functions such as ``demisto.debug()``, ``demisto.info()``, # etc. to print information in the XSOAR server log. You can set the log # level on the server configuration # See: https://xsoar.pan.dev/docs/integrations/code-conventions#logging demisto.debug(f"Command being called is {command}") try: headers = {"Authorization": f"Token {api_key}"} client = Client(base_url=base_url, verify=verify_certificate, headers=headers, proxy=proxy) if command == "test-module": # This is the call made when pressing the integration Test button. result = test_module(client, params) return_results(result) elif command == "ip": default_threshold_ip = arg_to_number(params.get("threshold_ip")) or DEFAULT_INDICATORS_THRESHOLD return_results(ip_reputation_command(client, args, default_threshold_ip, reliability)) elif command == "fetch-incidents": # Set and define the fetch incidents command to run after activated via integration settings. severity = params.get("severity", "low") # Convert the argument to an int using helper function or set to MAX_ALERTS_TO_FETCH max_results = arg_to_number(arg=params.get("max_fetch"), arg_name="max_fetch", required=False) if not max_results or max_results > MAX_ALERTS_TO_FETCH: max_results = MAX_ALERTS_TO_FETCH next_run, incidents = fetch_incidents( client=client, max_results=max_results, last_run=demisto.getLastRun(), # getLastRun() gets the last run dict first_fetch_time=datetime.strftime(first_fetch_time, DATE_FORMAT), severity=severity, ) # saves next_run for the time fetch-incidents is invoked demisto.setLastRun(next_run) # fetch-incidents calls ``demisto.incidents()`` to provide the list # of incidents to create demisto.incidents(incidents) elif command == "helloworld-alert-list": return_results(alert_list_command(client, args)) elif command == "helloworld-alert-note-create": return_results(alert_note_create_command(client, args)) elif command == "helloworld-say-hello": return_results(say_hello_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 {command} command.\nError:\n{str(e)}") """ ENTRY POINT """ if __name__ in ("__main__", "__builtin__", "builtins"): main()