Cisco Umbrella Cloud Security v2

Cisco Umbrella is a cloud security platform providing the first line of defense against internet threats. It uses DNS-layer security to block malicious requests before a connection is established, offering protection against malware, ransomware, phishing, and more. It offers real-time reporting, integrates with other Cisco solutions for layered security, and uses machine learning to uncover and predict threats.

Network Security · Cisco Umbrella cloud security

Details

IDCisco Umbrella Cloud Security v2
ProviderCisco Systems
CategoryNetwork Security
From Version6.9.0
Docker Imagedemisto/python3:3.12.13.10116658
Supported ModulesAgentix XSIAM

README

Cisco Umbrella is a cloud security platform providing the first line of defense against internet threats. It uses DNS-layer security to block malicious requests before a connection is established, offering protection against malware, ransomware, phishing, and more. It offers real-time reporting, integrates with other Cisco solutions for layered security, and uses machine learning to uncover and predict threats.
This integration was tested with version 2 of Cisco Umbrella Cloud Security

Configure Cisco Umbrella Cloud Security v2 in Cortex

Parameter Required
API Key True
API Secret True
Use system proxy settings False
Trust any certificate (not secure) False
Base URL True

Commands

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

umbrella-destinations-list


Get destinations within a destination list. A destination is a URL, IP or fully qualified domain name.

Base Command

umbrella-destinations-list

Input

Argument Name Description Required
destination_list_id The ID of the destination list. Destination lists can be fetched with the umbrella-destination-lists-list command. Required
destination_ids Comma-separated list of destination IDs to be retrieved from a list of destinations. Optional
destinations Comma-separated list of destinations to retrieve, a destination may be a domain, URL, or IP address. Optional
page Page number of paginated results. Minimum 1; Default 1. Optional
page_size The number of items per page. Minimum 1; Maximum 100; Default 50. Optional
limit The number of items per page. Minimum 1. Default is 50. Optional

Context Output

Path Type Description
Umbrella.Destinations.id String The unique ID of the destination.
Umbrella.Destinations.destination String A destination may be a domain, URL, or IP address.
Umbrella.Destinations.type String The type of destination within the destination list.
Umbrella.Destinations.comment String A comment about the destination.
Umbrella.Destinations.createdAt Date The date and time when the destination list was created.

Command example

!umbrella-destinations-list destination_list_id=17425859 limit=3

Context Example

{
    "Umbrella": {
        "Destinations": [
            {
                "comment": "Added from XSOAR",
                "createdAt": "2023-07-19 18:21:11",
                "destination": "www.facebook.com",
                "id": "154",
                "type": "domain"
            },
            {
                "comment": "Lior",
                "createdAt": "2023-07-06 04:42:55",
                "destination": "cisco.com",
                "id": "30058",
                "type": "domain"
            },
            {
                "comment": "Sabri",
                "createdAt": "2023-07-06 04:42:55",
                "destination": "www.pokemon.com",
                "id": "138036",
                "type": "domain"
            }
        ]
    }
}

Human Readable Output

Destination(s)

Id Destination Type Comment Created At
154 www.facebook.com domain Added from XSOAR 2023-07-19 18:21:11
30058 cisco.com domain Pikachu 2023-07-06 04:42:55
138036 www.pokemon.com domain Choose 2023-07-06 04:42:55

umbrella-destination-add


Add a destination to a destination list. A destination is a URL, IPv4, CIDR or fully qualified domain name. Accepted types for destination list with the access “allow” are: DOMAIN, IPv4 and CIDR. Accepted types for destination list with the access “block” are: URL and DOMAIN.

Base Command

umbrella-destination-add

Input

Argument Name Description Required
destination_list_id The ID of the destination list. Destination lists can be fetched with the umbrella-destination-lists-list command. Required
destinations Comma-separated list of destinations. A destination may be a URL, IPv4, CIDR or fully qualified domain name. Required
comment A comment about all the inserted destinations. Default is Added from XSOAR. Optional

Context Output

Path Type Description
Umbrella.DestinationLists.id Number The unique ID of the destination list.
Umbrella.DestinationLists.organizationId Number The organization ID.
Umbrella.DestinationLists.access String The type of access for the destination list. Valid values are: allow or block.
Umbrella.DestinationLists.isGlobal Boolean Specifies whether the destination list is a global destination list. There is only one default destination list of type ‘allow’ or ‘block’ for an organization.
Umbrella.DestinationLists.name String The name of the destination list.
Umbrella.DestinationLists.thirdpartyCategoryId Number The third-party category ID of the destination list.
Umbrella.DestinationLists.createdAt Number The date and time when the destination list was created.
Umbrella.DestinationLists.modifiedAt Number The date and time when the destination list was modified.
Umbrella.DestinationLists.isMspDefault Boolean Specifies whether MSP is the default.
Umbrella.DestinationLists.markedForDeletion Boolean Specifies whether the destination list is marked for deletion.
Umbrella.DestinationLists.bundleTypeId Number The number that represents the type of the Umbrella policy associated with the destination list. Umbrella returns ‘1’ for the DNS policy or ‘2’ for the Web policy.
Umbrella.DestinationLists.meta.destinationCount Number The total number of destinations in a destination list.

Base Command

umbrella-destination-add

Input

Argument Name Description Required
destination_list_id The ID of the destination list. Destination Lists can be fetched with umbrella-destination-lists-list. Required
destinations Comma separated list of destinations. A destination may be a URL, IPv4, CIDR or fully qualified domain name. Required
comment A comment about all the inserted destinations. Default value: “Added from XSOAR”. Default is Added from XSOAR. Optional

Context Output

Path Type Description
Umbrella.DestinationLists.id Number The unique ID of the destination list.
Umbrella.DestinationLists.organizationId Number The organization ID.
Umbrella.DestinationLists.access String The type of access for the destination list. Valid values are: allow or block.
Umbrella.DestinationLists.isGlobal Boolean Specifies whether the destination list is a global destination list. There is only one default destination list of type ‘allow’ or ‘block’ for an organization.
Umbrella.DestinationLists.name String The name of the destination list.
Umbrella.DestinationLists.thirdpartyCategoryId Number The third-party category ID of the destination list.
Umbrella.DestinationLists.createdAt Number The date and time when the destination list was created.
Umbrella.DestinationLists.modifiedAt Number The date and time when the destination list was modified.
Umbrella.DestinationLists.isMspDefault Boolean Specifies whether MSP is the default.
Umbrella.DestinationLists.markedForDeletion Boolean Specifies whether the destination list is marked for deletion.
Umbrella.DestinationLists.bundleTypeId Number The number that represents the type of the Umbrella policy associated with the destination list. Umbrella returns ‘1’ for the DNS policy or ‘2’ for the Web policy.
Umbrella.DestinationLists.meta.destinationCount Number The total number of destinations in a destination list.

Command example

!umbrella-destination-add destination_list_id=17463731 destinations="www.LiorSabri.com,1.1.1.1"

Human Readable Output

The destination(s) “[‘www.LiorSabri.com’, ‘1.1.1.1’]” were successfully added to the destination list “17463731”

umbrella-destination-delete


Remove a destination from a destination list. A destination is a URL, IP or fully qualified domain name.

Base Command

umbrella-destination-delete

Input

Argument Name Description Required
destination_list_id The ID of the destination list. Destination lists can be fetched with the umbrella-destination-lists-list command. Required
destination_ids Comma-separated list of destination IDs. Destinations can be fetched with the umbrella-destination-list command. Required

Context Output

Path Type Description
Umbrella.DestinationLists.id Number The unique ID of the destination list.
Umbrella.DestinationLists.organizationId Number The organization ID.
Umbrella.DestinationLists.access String The type of access for the destination list. Valid values are: allow or block.
Umbrella.DestinationLists.isGlobal Boolean Specifies whether the destination list is a global destination list. There is only one default destination list of type ‘allow’ or ‘block’ for an organization.
Umbrella.DestinationLists.name String The name of the destination list.
Umbrella.DestinationLists.thirdpartyCategoryId Number The third-party category ID of the destination list.
Umbrella.DestinationLists.createdAt Number The date and time when the destination list was created.
Umbrella.DestinationLists.modifiedAt Number The date and time when the destination list was modified.
Umbrella.DestinationLists.isMspDefault Boolean Specifies whether MSP is the default.
Umbrella.DestinationLists.markedForDeletion Boolean Specifies whether the destination list is marked for deletion.
Umbrella.DestinationLists.bundleTypeId Number The number that represents the type of the Umbrella policy associated with the destination list. Umbrella returns ‘1’ for the DNS policy or ‘2’ for the Web policy.
Umbrella.DestinationLists.meta.destinationCount Number The total number of destinations in a destination list.

Command example

!umbrella-destination-delete destination_list_id=17463733 destination_ids=25826

Human Readable Output

The destination(s) “[25826]” were successfully removed from the destination list “17463733”

umbrella-destination-lists-list


Get destination lists. A list of destinations (for example, domain name or URL) to which you can block or allow access.

Base Command

umbrella-destination-lists-list

Input

Argument Name Description Required
destination_list_id The ID of the destination list to retrieve. Optional
page Page number of paginated results. Minimum 1; Default 1. Optional
page_size The number of items per page. Minimum 1; Maximum 100; Default 50. Optional
limit The maximum number of records to retrieve. Minimum 1. Default is 50. Optional

Context Output

Path Type Description
Umbrella.DestinationLists.id Number The unique ID of the destination list.
Umbrella.DestinationLists.organizationId Number The organization ID.
Umbrella.DestinationLists.access String The type of access for the destination list. Valid values are: allow or block.
Umbrella.DestinationLists.isGlobal Boolean Specifies whether the destination list is a global destination list. There is only one default destination list of type ‘allow’ or ‘block’ for an organization.
Umbrella.DestinationLists.name String The name of the destination list.
Umbrella.DestinationLists.thirdpartyCategoryId Number The third-party category ID of the destination list.
Umbrella.DestinationLists.createdAt Number The date and time when the destination list was created.
Umbrella.DestinationLists.modifiedAt Number The date and time when the destination list was modified.
Umbrella.DestinationLists.isMspDefault Boolean Specifies whether MSP is the default.
Umbrella.DestinationLists.markedForDeletion Boolean Specifies whether the destination list is marked for deletion.
Umbrella.DestinationLists.bundleTypeId Number The number that represents the type of the Umbrella policy associated with the destination list. Umbrella returns ‘1’ for the DNS policy or ‘2’ for the Web policy.
Umbrella.DestinationLists.meta.destinationCount Number The total number of destinations in a destination list.
Umbrella.DestinationLists.meta.domainCount Number The total number of domains in a destination list. Domains are part of the total number of destinations in a destination list.
Umbrella.DestinationLists.meta.ipv4Count Number The total number of IP addresses in a destination list. IP addresses are part of the total number of destinations in a destination list.
Umbrella.DestinationLists.meta.urlCount Number The total number of URLs in a destination list. URLs are part of the total number of destinations in a destination list.
Umbrella.DestinationLists.meta.applicationCount Number The total number or applications in a destination list. Applications are part of the total number of destinations in a destination list.

Command example

!umbrella-destination-lists-list limit=3

Context Example

{
    "Umbrella": {
        "DestinationLists": [
            {
                "access": "allow",
                "bundleTypeId": 1,
                "createdAt": 1690184121,
                "id": 17463749,
                "isGlobal": false,
                "isMspDefault": false,
                "markedForDeletion": false,
                "meta": {
                    "applicationCount": 0,
                    "destinationCount": 1,
                    "domainCount": 0,
                    "ipv4Count": 1,
                    "urlCount": 0
                },
                "modifiedAt": 1690184121,
                "name": "Lior",
                "organizationId": 123456,
                "thirdpartyCategoryId": null
            },
            {
                "access": "allow",
                "bundleTypeId": 1,
                "createdAt": 1690184234,
                "id": 17463756,
                "isGlobal": false,
                "isMspDefault": false,
                "markedForDeletion": false,
                "meta": {
                    "applicationCount": 0,
                    "destinationCount": 8,
                    "domainCount": 4,
                    "ipv4Count": 4,
                    "urlCount": 0
                },
                "modifiedAt": 1690184435,
                "name": "LiorSB",
                "organizationId": 123456,
                "thirdpartyCategoryId": null
            },
            {
                "access": "allow",
                "bundleTypeId": 1,
                "createdAt": 1638798710,
                "id": 15609454,
                "isGlobal": true,
                "isMspDefault": false,
                "markedForDeletion": false,
                "meta": {
                    "applicationCount": 0,
                    "destinationCount": 14,
                    "domainCount": 10,
                    "ipv4Count": 4,
                    "urlCount": 0
                },
                "modifiedAt": 1690183660,
                "name": "Global Allow List",
                "organizationId": 123456,
                "thirdpartyCategoryId": null
            }
        ]
    }
}

Human Readable Output

Destination List

Id Name Access Is Global Destination Count
17463749 Lior allow false 1
17463756 LiorSB allow false 8
15609454 Global Allow List allow true 14

umbrella-destination-list-create


Create a destination list. A list of destinations (for example, domain name or URL) to which you can block or allow access. Accepted types for destination list with the access “allow” are: DOMAIN, IPv4 and CIDR. Accepted types for destination list with the access “block” are: URL and DOMAIN.

Base Command

umbrella-destination-list-create

Input

Argument Name Description Required
bundle_type The type of the Umbrella policy associated with the destination list. If the field is not specified, the default value is ‘DNS’. Possible values are: DNS, WEB. Optional
access The type of access for the destination list. Valid values are “allow” or “block”. Accepted types for destination list with the access “allow” are: DOMAIN, IPv4 and CIDR. Accepted types for destination list with the access “block” are: URL and DOMAIN. Possible values are: allow, block. Required
is_global Specifies whether the destination list is a global destination list. There is only one default destination list of type ‘allow’ or ‘block’ for an organization. Possible values are: True, False. Required
name The name of the destination list. Required
destinations Comma-separated list of destinations. A destination may be a URL, IPv4, CIDR or fully qualified domain name. Optional
destinations_comment A comment about all the inserted destinations. Default is Added from XSOAR. Optional

Context Output

Path Type Description
Umbrella.DestinationLists.id Number The unique ID of the destination list.
Umbrella.DestinationLists.organizationId Number The organization ID.
Umbrella.DestinationLists.access String The type of access for the destination list. Valid values are: allow or block.
Umbrella.DestinationLists.isGlobal Boolean Specifies whether the destination list is a global destination list. There is only one default destination list of type ‘allow’ or ‘block’ for an organization.
Umbrella.DestinationLists.name String The name of the destination list.
Umbrella.DestinationLists.thirdpartyCategoryId Number The third-party category ID of the destination list.
Umbrella.DestinationLists.createdAt Number The date and time when the destination list was created.
Umbrella.DestinationLists.modifiedAt Number The date and time when the destination list was modified.
Umbrella.DestinationLists.isMspDefault Boolean Specifies whether MSP is the default.
Umbrella.DestinationLists.markedForDeletion Boolean Specifies whether the destination list is marked for deletion.
Umbrella.DestinationLists.bundleTypeId Number The number that represents the type of the Umbrella policy associated with the destination list. Umbrella returns ‘1’ for the DNS policy or ‘2’ for the Web policy.
Umbrella.DestinationLists.meta.destinationCount Number The total number of destinations in a destination list.

Command example

!umbrella-destination-list-create access=allow is_global=False name=LiorSBList bundle_type=WEB destinations="https://pokemon.com"

Context Example

{
    "Umbrella": {
        "DestinationLists": {
            "access": "allow",
            "bundleTypeId": 1,
            "createdAt": 1690208665,
            "id": 17464621,
            "isGlobal": false,
            "isMspDefault": false,
            "markedForDeletion": false,
            "meta": {
                "destinationCount": 1
            },
            "modifiedAt": 1690208665,
            "name": "LiorSBList",
            "organizationId": 123456,
            "thirdpartyCategoryId": null
        }
    }
}

Human Readable Output

Destination List

Id Name Access Is Global Destination Count
17464621 LiorSBList allow false 1

umbrella-destination-list-update


Edit a destination list. A list of destinations (for example, domain name or URL) to which you can block or allow access.

Base Command

umbrella-destination-list-update

Input

Argument Name Description Required
destination_list_id The ID of the destination list. Destination lists can be fetched with the umbrella-destination-lists-list command. Required
name The name of the destination list. Required

Context Output

Path Type Description
Umbrella.DestinationLists.id Number The unique ID of the destination list.
Umbrella.DestinationLists.organizationId Number The organization ID.
Umbrella.DestinationLists.access String The type of access for the destination list. Valid values are: allow or block.
Umbrella.DestinationLists.isGlobal Boolean Specifies whether the destination list is a global destination list. There is only one default destination list of type ‘allow’ or ‘block’ for an organization.
Umbrella.DestinationLists.name String The name of the destination list.
Umbrella.DestinationLists.thirdpartyCategoryId Number The third-party category ID of the destination list.
Umbrella.DestinationLists.createdAt Number The date and time when the destination list was created.
Umbrella.DestinationLists.modifiedAt Number The date and time when the destination list was modified.
Umbrella.DestinationLists.isMspDefault Boolean Specifies whether MSP is the default.
Umbrella.DestinationLists.markedForDeletion Boolean Specifies whether the destination list is marked for deletion.
Umbrella.DestinationLists.bundleTypeId Number The number that represents the type of the Umbrella policy associated with the destination list. Umbrella returns ‘1’ for the DNS policy or ‘2’ for the Web policy.
Umbrella.DestinationLists.meta.destinationCount Number The total number of destinations in a destination list.

Command example

!umbrella-destination-list-update destination_list_id=17463733 name=LiorUpdated

Context Example

{
    "Umbrella": {
        "DestinationLists": {
            "access": "allow",
            "bundleTypeId": 1,
            "createdAt": 1690183414,
            "id": 17463733,
            "isGlobal": false,
            "isMspDefault": false,
            "markedForDeletion": false,
            "meta": {
                "destinationCount": 0
            },
            "modifiedAt": 1690208670,
            "name": "LiorUpdated",
            "organizationId": 123456,
            "thirdpartyCategoryId": null
        }
    }
}

Human Readable Output

Destination List

Id Name Access Is Global Destination Count
17463733 LiorUpdated allow false 0

umbrella-destination-list-delete


Delete a destination list. A list of destinations (for example, domain name or URL) to which you can block or allow access.

Base Command

umbrella-destination-list-delete

Input

Argument Name Description Required
destination_list_id The ID of the destination list. Destination lists can be fetched with the umbrella-destination-lists-list command. Required

Context Output

There is no context output for this command.

Command example

!umbrella-destination-list-delete destination_list_id=17463733

Human Readable Output

The destination list “17463733” was successfully deleted

Breaking changes from the previous version of this integration - Cisco Umbrella Cloud Security v2

The following sections list the changes in this version.

Commands

The following commands were removed in this version

  • umbrella-get-destination-lists - this command was replaced by umbrella-destination-lists-list.
  • umbrella-add-domain - this command was replaced by umbrella-destination-add.
  • umbrella-remove-domain - this command was replaced by umbrella-destination-delete.
  • umbrella-get-destination-domain - this command was replaced by umbrella-destinations-list.
  • umbrella-get-destination-domains - this command was replaced by umbrella-destinations-list.
  • umbrella-search-destination-domains - this command was replaced by umbrella-destinations-list.

Arguments

The following arguments were removed in this version

In the umbrella-get-destination-lists command:

  • orgId - this argument was removed.
    In the umbrella-add-domain command:
  • orgId - this argument was removed.
    In the umbrella-remove-domain command:
  • orgId - this argument was removed.
    In the umbrella-get-destination-domain command:
  • orgId - this argument was removed.
    In the umbrella-get-destination-domains command:
  • orgId - this argument was removed.
    In the umbrella-search-destination-domains command:
  • orgId - this argument was removed.

The behavior of the following arguments was changed

In the umbrella-add-domain command:

  • destId - this argument was replaced by destination_list_id.
  • domains - this argument was replaced by destinations.
    In the umbrella-remove-domain command:
  • destId - this argument was replaced by destination_list_id.
  • domainIds - this argument was replaced by destination_ids.
    In the umbrella-get-destination-domain command:
  • destId - this argument was replaced by destination_list_id.
    In the umbrella-get-destination-domains command:
  • destId - this argument was replaced by destination_list_id.
    In the umbrella-search-destination-domains command:
  • destId - this argument was replaced by destination_list_id.
  • domains - this argument was replaced by destinations.

Configuration parameters

  • credentials — API Key (required)
  • proxy — Use system proxy settings
  • insecure — Trust any certificate (not secure)
  • baseURL — Base URL (required)

Commands (13)

  • umbrella-add-domain Deprecated

    Deprecated. Use the `umbrella-destination-add` instead.

  • umbrella-destination-add

    Add a destination to a destination list. A destination is a URL, IPv4, CIDR or fully qualified domain name. Accepted types for destination list with the access "allow" are: DOMAIN, IPv4 and CIDR. Accepted types for destination list with the access "block" are: URL and DOMAIN.

  • umbrella-destination-delete

    Remove a destination from a destination list. A destination is a URL, IP or fully qualified domain name.

  • umbrella-destination-list-create

    Create a destination list. A list of destinations (for example, domain name or URL) to which you can block or allow access. Accepted types for destination list with the access "allow" are: DOMAIN, IPv4 and CIDR. Accepted types for destination list with the access "block" are: URL and DOMAIN.

  • umbrella-destination-list-delete

    Delete a destination list. A list of destinations (for example, domain name or URL) to which you can block or allow access.

  • umbrella-destination-list-update

    Edit a destination list. A list of destinations (for example, domain name or URL) to which you can block or allow access.

  • umbrella-destination-lists-list

    Get destination lists. A list of destinations (for example, domain name or URL) to which you can block or allow access.

  • umbrella-destinations-list

    Get destinations within a destination list. A destination is a URL, IP or fully qualified domain name.

  • umbrella-get-destination-domain Deprecated

    Deprecated. Use the `umbrella-destinations-list` instead.

  • umbrella-get-destination-domains Deprecated

    Deprecated. Use the `umbrella-destinations-list` instead.

  • umbrella-get-destination-lists Deprecated

    Deprecated. Use the `umbrella-destination-lists-list` instead.

  • umbrella-remove-domain Deprecated

    Deprecated. Use the `umbrella-destination-delete` instead.

  • umbrella-search-destination-domains Deprecated

    Deprecated. Use the `umbrella-destinations-list` instead.

import demistomock as demisto  # noqa: F401
from CommonServerPython import *  # noqa: F401

import enum
import http
from collections.abc import Callable
from typing import Any, TypeVar


INTEGRATION_COMMAND_PREFIX = "umbrella"
DESTINATION = "destination"
DESTINATION_LIST = "destination-list"
DOMAIN = "domain"

INTEGRATION_OUTPUT_PREFIX = "Umbrella"
DESTINATION_OUTPUT_PREFIX = "Destinations"
DESTINATION_LIST_OUTPUT_PREFIX = "DestinationLists"

ID_OUTPUTS_KEY_FIELD = "id"

MAX_LIMIT = 100
DEFAULT_LIMIT = 50
DEFAULT_URL = "https://api.umbrella.com"

DESTINATION_LIST_HEADERS = ["id", "name", "access", "isGlobal", "destinationCount"]
DESTINATION_LIST_JSON_TRANSFORMER = JsonTransformer(keys=DESTINATION_LIST_HEADERS, is_nested=True)

OptionalDictOrList = TypeVar("OptionalDictOrList", None, dict[str, Any], list[dict[str, Any]])


class BundleType(int, enum.Enum):
    DNS = 1
    WEB = 2


class Access(str, enum.Enum):
    ALLOW = "allow"
    BLOCK = "block"


""" Client """


class Client(BaseClient):
    """Client class to interact with the Umbrella API."""

    AUTH_SUFFIX = "auth/v2/token"
    POLICIES_SUFFIX = "policies/v2"

    DESTINATION_LIST_ENDPOINT = urljoin(POLICIES_SUFFIX, "destinationlists")

    def __init__(
        self,
        base_url: str,
        api_key: str,
        api_secret: str,
        verify: bool = False,
        proxy: bool = False,
    ) -> None:
        """Initialize the Client class.

        Args:
            base_url (str): The base URL of the API.
            api_key (str): The API key to use.
            api_secret (str): The API secret to use.
            verify (bool, optional): Whether to verify the SSL certificate.
                Defaults to False.
            proxy (bool, optional): Whether to use a proxy.
                Defaults to False.
        """
        self.api_key = api_key
        self.api_secret = api_secret

        super().__init__(
            base_url=base_url,
            verify=verify,
            proxy=proxy,
            headers={},
        )

    def login(self) -> None:
        """
        Log in to the API using the API key and API secret.
        The access token is stored in the headers of the request.
        """
        access_token = self.get_access_token()
        self._headers["Authorization"] = f"Bearer {access_token}"

    def get_access_token(self):
        """
        Get an access token that was previously created if it is still valid, else, generate a new access token from
        the API key and secret.
        """
        # Check if there is an existing valid access token
        integration_context = get_integration_context()
        if integration_context.get("access_token") and integration_context.get("expiry_time") > date_to_timestamp(datetime.now()):
            return integration_context.get("access_token")
        else:
            try:
                res = self._http_request(
                    method="POST",
                    url_suffix=Client.AUTH_SUFFIX,
                    headers={"Content-Type": "application/x-www-form-urlencoded"},
                    auth=(self.api_key, self.api_secret),
                    data={"grant_type": "client_credentials"},
                )
                if res.get("access_token"):
                    expiry_time = date_to_timestamp(datetime.now(), date_format="%Y-%m-%dT%H:%M:%S")
                    expiry_time += res.get("expires_in", 0) * 1000 - 10
                    context = {"access_token": res.get("access_token"), "expiry_time": expiry_time}
                    set_integration_context(context)
                    return res.get("access_token")
            except Exception as e:
                return_error(
                    f"Error occurred while creating an access token. Please check the instance configuration.\n\n{e.args[0]}"
                )

    def _get_destination_payload(
        self,
        destinations: list[str] | None = None,
        destinations_comment: str | None = None,
    ) -> list[dict[str, Any]] | None:
        """Get the destination payload.

        Args:
            destinations (list[str] | None, optional): The list of destinations.
                Defaults to None.
            destinations_comment (str | None, optional): The comment of the destinations.
                Defaults to None.

        Returns:
            list[dict[str, Any]] | None: The destination payload or None incase destinations weren't given.
        """
        if not destinations:
            return None

        return [
            {
                "destination": destination,
                "comment": destinations_comment,
            }
            for destination in destinations
        ]

    def list_destinations(
        self,
        destination_list_id: str,
        page: int | None = None,
        limit: int = DEFAULT_LIMIT,
    ) -> dict[str, Any]:
        """Retrieve the list of destinations for a given destination list ID.

        Args:
            destination_list_id (str): The ID of the destination list.
            page (int, optional): The page number.
                Defaults to None.
            limit (int, optional): The number of items per page.
                Defaults to DEFAULT_LIMIT.

        Returns:
            dict[str, Any]: The list of destinations.
        """
        params = assign_params(
            page=page,
            limit=limit,
        )
        url_suffix = urljoin(Client.DESTINATION_LIST_ENDPOINT, f"{destination_list_id}/destinations")

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

    def add_destination(
        self,
        destination_list_id: str,
        destinations: list[str],
        destinations_comment: str | None = None,
    ) -> dict[str, Any]:
        """Add a list of destinations to a given destination list ID.

        Args:
            destination_list_id (str): The ID of the destination list.
            destinations (list[str]): The list of destinations.
            destinations_comment (str, optional): The comment of the destination.
                Defaults to None.

        Returns:
            dict[str, Any]: The destination list that holds the added destinations.
        """
        payload = self._get_destination_payload(
            destinations=destinations,
            destinations_comment=destinations_comment,
        )
        url_suffix = urljoin(Client.DESTINATION_LIST_ENDPOINT, f"{destination_list_id}/destinations")

        return self._http_request(
            method="POST",
            url_suffix=url_suffix,
            json_data=payload,
        )

    def delete_destination(self, destination_list_id: str, destination_ids: list[int]) -> dict[str, Any]:
        """Delete a list of destinations from a given destination list ID.

        Args:
            destination_list_id (str): The ID of the destination list.
            destination_ids (list[int]): The list of destination IDs.

        Returns:
            dict[str, Any]: The destination list that holds the deleted destinations.
        """
        url_suffix = urljoin(Client.DESTINATION_LIST_ENDPOINT, f"{destination_list_id}/destinations/remove")
        return self._http_request(
            method="DELETE",
            url_suffix=url_suffix,
            json_data=destination_ids,
        )

    def list_destination_lists(
        self,
        page: int | None = None,
        limit: int = DEFAULT_LIMIT,
    ) -> dict[str, Any]:
        """Retrieve the list of destination lists.

        Args:
            destination_list_id (str): The ID of the destination list.

        Returns:
            dict[str, Any]: The destination list.
        """
        params = assign_params(
            page=page,
            limit=limit,
        )
        return self._http_request(
            method="GET",
            url_suffix=Client.DESTINATION_LIST_ENDPOINT,
            params=params,
        )

    def get_destination_list(self, destination_list_id: str) -> dict[str, Any]:
        """Retrieve a destination list.

        Args:
            destination_list_id (str): The ID of the destination list.

        Returns:
            dict[str, Any]: The destination list.
        """
        url_suffix = urljoin(Client.DESTINATION_LIST_ENDPOINT, destination_list_id)
        return self._http_request(method="GET", url_suffix=url_suffix)

    def create_destination_list(
        self,
        name: str,
        access: Access,
        is_global: bool,
        bundle_type: str | None = None,
        destinations: list[str] | None = None,
        destinations_comment: str | None = None,
    ) -> dict[str, Any]:
        """Create a destination list.

        Args:
            name (str): The name of the destination list.
            access (Access): The access of the destination list.
            is_global (bool): Whether the destination list is global.
            bundle_type (str | None, optional): The bundle type of the destination list.
                Defaults to None.
            destinations (list[str] | None, optional): The list of destinations.
                Defaults to None.
            destinations_comment (str | None, optional): The comment of the destinations.
                Defaults to None.

        Returns:
            dict[str, Any]: The destination list.
        """
        payload = remove_empty_elements(
            {
                "name": name,
                "access": access,
                "isGlobal": is_global,
                "bundleType": bundle_type and BundleType[bundle_type].value,
                "destinations": self._get_destination_payload(
                    destinations=destinations,
                    destinations_comment=destinations_comment,
                ),
            }
        )
        return self._http_request(
            method="POST",
            url_suffix=Client.DESTINATION_LIST_ENDPOINT,
            json_data=payload,
        )

    def update_destination_list(self, destination_list_id: str, name: str) -> dict[str, Any]:
        """Update a destination list.

        Args:
            destination_list_id (str): The ID of the destination list.
            name (str): The name of the destination list.

        Returns:
            dict[str, Any]: The destination list.
        """
        url_suffix = urljoin(Client.DESTINATION_LIST_ENDPOINT, destination_list_id)
        return self._http_request(
            method="PATCH",
            url_suffix=url_suffix,
            json_data={"name": name},
        )

    def delete_destination_list(self, destination_list_id: str) -> dict[str, Any]:
        """Delete a destination list.

        Args:
            destination_list_id (str): The ID of the destination list.

        Returns:
            dict[str, Any]: The destination list.
        """
        url_suffix = urljoin(Client.DESTINATION_LIST_ENDPOINT, destination_list_id)
        return self._http_request(
            method="DELETE",
            url_suffix=url_suffix,
        )


""" HELPER COMMANDS """


@logger
def bridge_v1_to_v2(
    v2_command: Callable[[Client, dict[str, Any]], CommandResults],
    client: Client,
    args: dict[str, Any],
) -> CommandResults:
    """
    Bridge commands from the v1 pack (not API) to v2 commands.

    Args:
        v2_command (Callable[[Client, dict[str, Any]], CommandResults]): The v2 command.
        client (Client): Session to Cisco Umbrella to run API requests.
        args (dict[str, Any]): Arguments passed down by the CLI to configure the request.

    Returns:
        CommandResults: The result of v2 command.
    """
    args["destination_list_id"] = args.pop("destId", None)
    args["destinations"] = args.pop("domain", None) or args.pop("domains", None)
    args["destination_ids"] = args.pop("domainIds", None)
    args["limit"] = DEFAULT_LIMIT

    return v2_command(client, args)


@logger
def is_get_request_type(get_args: list, list_args: list) -> bool:
    """
    Determine whether the request arguments are for a GET or LIST request.

    Args:
        get_args (list): GET request arguments.
        list_args (list): LIST request arguments.

    Raises:
        ValueError: In case the user has entered both GET and LIST arguments, raise an error.

    Returns:
        bool: True if the arguments are for a GET request, False otherwise.
    """
    is_get_request = any(get_args)
    is_list_request = any(list_args)

    if is_get_request and is_list_request:
        raise ValueError("GET and LIST arguments can not be supported simultaneously.")

    return is_get_request


@logger
def get_json_table(obj: OptionalDictOrList, json_transformer: JsonTransformer) -> OptionalDictOrList:
    """Convert a dict or list to a table.

    Args:
        obj (OptionalDictOrList): The dict or list to convert.
        json_transformer (JsonTransformer): The json transformer to use.

    Returns:
        OptionalDictOrList: The converted dict or list, otherwise returns obj if nothing was inserted.
    """
    if not obj:
        return obj

    def transform_json_to_dict(data: dict[str, Any]) -> dict[str, Any]:
        """Use a JsonTransformer to extract the given keys from a dict and reconstruct it.

        Args:
            data (dict[str, Any]): The dict to transform.

        Returns:
            dict[str, Any]: The transformed dict.
        """
        return {transformed_data[1]: transformed_data[2] for transformed_data in json_transformer.json_to_path_generator(data)}

    if isinstance(obj, list):
        return [transform_json_to_dict(o) for o in obj]

    return transform_json_to_dict(obj)


def get_single_or_full_list(items: list) -> list | dict:
    """Get a single item or a full list of items.

    If the list contains only one item, return the item.
    If the list contains more than one item, return the full list.

    Args:
        items (list): The list of items.

    Returns:
        list | dict: The single item or the full list of items.
    """
    return items[0] if len(items) == 1 else items


@logger
def handle_pagination(
    list_command: Callable[..., dict[str, Any]],
    limit: int,
    *args,
    page_size: int | None = None,
    page: int | None = None,
    **kwargs,
) -> tuple[list[dict[str, Any]] | dict[str, Any], dict[str, Any]]:
    """Handles pagination for the given list_command.

    Args:
        list_command (Callable[..., dict[str, Any]]): Callable that returns data containing destinations.
        limit (int): The maximum number of items to return.
        page (int | None, optional): The page number to start from.
            Defaults to None.
        page_size (int | None, optional): The number of items to return per page.
            Defaults to None.
        *args: Arguments passed down by the CLI to configure the request.
        **kwargs: Keyword arguments passed down by the CLI to configure the request.

    Returns:
        tuple[list[dict[str, Any]] | dict[str, Any], dict[str, Any]]:
            A tuple containing the list of items and raw responses, or a single item and raw response if page is None.
    """
    if page:
        page_size = min(page_size or DEFAULT_LIMIT, MAX_LIMIT)
        demisto.debug(f"Calling list command with {args=}, {page=}, limit={page_size}")
        raw_response = list_command(*args, page=page, limit=page_size, **kwargs)
        return raw_response.get("data", []), raw_response

    page = 1
    outputs: list[dict[str, Any]] = []

    # Keep calling the API until the required amount of items have been met.
    while limit > 0:
        demisto.debug(f"Calling list command with {args=}, {page=}, limit={MAX_LIMIT}")
        raw_response = list_command(*args, page=page, limit=MAX_LIMIT, **kwargs)

        # If the API returned no items, we're done.
        if not (output := raw_response.get("data")):
            demisto.debug(f"The API returned no items for {page=}, stopping")
            break

        received_items = len(output)

        # If the API returned more than the required amount of items, we need to trim the output.
        if limit < received_items:
            output = output[:limit]

        outputs += output

        # If the API returned less than the required amount of items, we're done.
        if received_items < MAX_LIMIT:
            demisto.debug(f"These are the last items in the API {page=}, stopping")
            break

        limit -= received_items
        page += 1

    raw_response["meta"]["total"] = len(outputs)
    outputs_result = get_single_or_full_list(outputs)
    raw_response["data"] = outputs_result

    return outputs_result, raw_response


@logger
def find_destinations(
    list_command: Callable[..., dict[str, Any]],
    destination_list_id: str,
    destinations: set[str] | None = None,
    destination_ids: set[str] | None = None,
) -> tuple[list[dict[str, Any]] | dict[str, Any], dict[str, Any]]:
    """Fetches and returns destinations and their associated data from a given list_command callable.

    Args:
        list_command (Callable[..., dict[str, Any]]): Callable that returns data containing destinations.
        destination_list_id (str): The destination list to search through.
        destinations (list[str] | None, optional): A set of destination names to look for.
            Defaults to None.
        destination_ids (list[str] | None, optional): A set of destination ids to look for.
            Defaults to None.

    Raises:
        ValueError: If both destinations and destination_ids weren't provided.

    Returns:
        tuple[list[dict[str, Any]] | dict[str, Any], dict[str, Any]]:
            A tuple containing two lists: one with the fetched data,
            and one with the raw responses from the list command.
    """
    if not any((destinations, destination_ids)):
        raise ValueError('At least one of the arguments "destinations" or "destination_ids" must be provided.')

    def filter_out_non_target_items(
        item: str,
        target_items: set[str] | None,
        other_item: str,
        other_target_items: set[str] | None,
    ) -> bool:
        """
        Checks if an item is in target_items.
        If so, removes the item and associated other_item from their respective sets.

        Args:
            item (str): Item to check for in target_items.
            target_items (set[str]): Set of items to search in.
            other_item (str): Associated item to remove from other_target_items if item is found in target_items.
            other_target_items (set[str]): Set of associated items.

        Returns:
            bool: True if the item was found and removed, False otherwise.
        """
        if not target_items:
            return False

        if item in (target_items or ()):
            target_items.discard(item)

            if other_target_items:
                other_target_items.discard(other_item)

            return True

        return False

    destinations = set(destinations) if destinations else None
    destination_ids = set(destination_ids) if destination_ids else None

    page = 0
    outputs: list[dict[str, Any]] = []

    while destinations or destination_ids:
        page += 1
        demisto.debug(f"Calling list command with {destination_list_id=}, {page=}, limit={MAX_LIMIT}")
        raw_response = list_command(destination_list_id=destination_list_id, page=page, limit=MAX_LIMIT)
        items = raw_response.get("data", [])

        if not items:
            demisto.debug(f"The API returned no items for {page=}, stopping")
            break

        for item in items:
            # Called twice to avoid duplicates if an item was given in ID and destination.
            if filter_out_non_target_items(
                item["id"],
                destination_ids,
                item["destination"],
                destinations,
            ) or filter_out_non_target_items(
                item["destination"],
                destinations,
                item["id"],
                destination_ids,
            ):
                outputs.append(item)

        if len(items) < MAX_LIMIT:
            demisto.debug(f"These are the last items in the API {page=}, stopping")
            break

    raw_response["meta"]["total"] = len(outputs)
    outputs_result = get_single_or_full_list(outputs)
    raw_response["data"] = outputs_result

    return outputs_result, raw_response


@logger
def get_destination_list_command_results(raw_response: list | dict, outputs: list | dict) -> CommandResults:
    """Get the destination list command results.

    Args:
        raw_response (list | dict): The raw response from the API.
        outputs (list | dict): The destination list data.

    Returns:
        CommandResults: The destination list command results.
    """
    readable_output = tableToMarkdown(
        name="Destination List:",
        t=get_json_table(outputs, DESTINATION_LIST_JSON_TRANSFORMER),
        headers=DESTINATION_LIST_HEADERS,
        headerTransform=pascalToSpace,
    )
    return CommandResults(
        outputs_prefix=".".join([INTEGRATION_OUTPUT_PREFIX, DESTINATION_LIST_OUTPUT_PREFIX]),
        outputs_key_field=ID_OUTPUTS_KEY_FIELD,
        outputs=outputs,
        readable_output=readable_output,
        raw_response=raw_response,
    )


""" COMMANDS """


@logger
def list_destinations_command(client: Client, args: dict[str, Any]) -> CommandResults:
    destination_list_id = args["destination_list_id"]
    # GET arguments
    destinations = argToList(args.get("destinations"))
    destination_ids = argToList(args.get("destination_ids"))
    # LIST arguments
    limit = arg_to_number(args.get("limit")) or DEFAULT_LIMIT
    page = arg_to_number(args.get("page"))
    page_size = arg_to_number(args.get("page_size"))

    if is_get_request_type(
        get_args=[destinations, destination_ids],
        list_args=[page, page_size],
    ):
        outputs, raw_response = find_destinations(
            list_command=client.list_destinations,
            destination_list_id=destination_list_id,
            destinations=destinations,
            destination_ids=destination_ids,
        )
    else:  # is_list_request
        outputs, raw_response = handle_pagination(
            list_command=client.list_destinations,
            limit=limit,
            page=page,
            page_size=page_size,
            destination_list_id=destination_list_id,
        )

    readable_output = tableToMarkdown(
        name="Destination(s):",
        t=outputs,
        headers=["id", "destination", "type", "comment", "createdAt"],
        headerTransform=pascalToSpace,
    )

    return CommandResults(
        outputs_prefix=".".join([INTEGRATION_OUTPUT_PREFIX, DESTINATION_OUTPUT_PREFIX]),
        outputs_key_field=ID_OUTPUTS_KEY_FIELD,
        outputs=outputs,
        readable_output=readable_output,
        raw_response=raw_response,
    )


@logger
def add_destination_command(client: Client, args: dict[str, Any]) -> CommandResults:
    """Add destinations to a destination list.

    Args:
        client (Client): Session to Cisco Umbrella to run API requests.
        args (dict[str, Any]): Arguments passed down by the CLI to configure the request.
            destination_list_id (str): The ID of the destination list.
            destinations (list[str]): The list of destinations to add.
            comment (str, optional): The comment for the destinations.

    Returns:
        CommandResults: The result of the destination addition.
    """
    destination_list_id = args["destination_list_id"]
    destinations = argToList(args["destinations"])
    destinations_comment = args.get("comment")

    raw_response = client.add_destination(
        destination_list_id=destination_list_id,
        destinations=destinations,
        destinations_comment=destinations_comment,
    )

    return CommandResults(
        readable_output=(
            f'The destination(s) "{destinations}" were successfully added to the destination list "{destination_list_id}"'
        ),
        raw_response=raw_response,
    )


@logger
def delete_destination_command(client: Client, args: dict[str, Any]) -> CommandResults:
    """Delete destinations from a destination list.

    Args:
        client (Client): Session to Cisco Umbrella to run API requests.
        args (dict[str, Any]): Arguments passed down by the CLI to configure the request.
            destination_list_id (str): The ID of the destination list.
            destination_ids (list[int]): The IDs of the destinations to delete.

    Returns:
        CommandResults: The result of the destination deletion.
    """
    destination_list_id = args["destination_list_id"]
    destination_ids = [number for arg in argToList(args["destination_ids"]) if (number := arg_to_number(arg)) is not None]

    raw_response = client.delete_destination(
        destination_list_id=destination_list_id,
        destination_ids=destination_ids,
    )

    return CommandResults(
        readable_output=(
            f'The destination(s) "{destination_ids}" '
            f'were successfully removed from the destination list "{destination_list_id}"'
        ),
        raw_response=raw_response,
    )


@logger
def list_destination_lists_command(client: Client, args: dict[str, Any]) -> CommandResults:
    """List destination lists.

    Args:
        client (Client): Session to Cisco Umbrella to run API requests.
        args (dict[str, Any]): Arguments passed down by the CLI to configure the request.
            destination_list_id (str, optional): The ID of a specific destination list.
            limit (int, optional): The maximum number of records to retrieve.
            page (int, optional): Page number of paginated results.
            page_size (int, optional): The number of items per page.

    Returns:
        CommandResults: The requested destination lists.
    """
    # GET arguments
    destination_list_id = args.get("destination_list_id", "")
    # LIST arguments
    limit = arg_to_number(args.get("limit")) or DEFAULT_LIMIT
    page = arg_to_number(args.get("page"))
    page_size = arg_to_number(args.get("page_size"))

    if is_get_request_type(
        get_args=[destination_list_id],
        list_args=[page, page_size],
    ):
        outputs, raw_response = None, client.get_destination_list(destination_list_id)
    else:  # is_list_request
        outputs, raw_response = handle_pagination(
            list_command=client.list_destination_lists,
            limit=limit,
            page=page,
            page_size=page_size,
        )

    return get_destination_list_command_results(
        outputs=outputs or raw_response.get("data"),
        raw_response=raw_response,
    )


@logger
def create_destination_list_command(client: Client, args: dict[str, Any]) -> CommandResults:
    """Create a destination list.

    Args:
        client (Client): Session to Cisco Umbrella to run API requests.
        args (dict[str, Any]): Arguments passed down by the CLI to configure the request.
            name (str): The name of the destination list.
            access (str): The access level of the destination list.
            is_global (bool): Whether the destination list is global.
            bundle_type (str, optional): The type of bundle.
            destinations (list[str], optional): The list of destinations.
            destinations_comment (str, optional): The comment for the destinations.

    Returns:
        CommandResults: The created destination list.
    """
    raw_response = client.create_destination_list(
        name=args["name"],
        access=args["access"],
        is_global=argToBoolean(args["is_global"]),
        bundle_type=args.get("bundle_type"),
        destinations=argToList(args.get("destinations")),
        destinations_comment=args.get("destinations_comment"),
    )
    return get_destination_list_command_results(
        outputs=raw_response,
        raw_response=raw_response,
    )


@logger
def update_destination_list_command(client: Client, args: dict[str, Any]) -> CommandResults:
    """Update a destination list.

    Args:
        client (Client): Session to Cisco Umbrella to run API requests.
        args (dict[str, Any]): Arguments passed down by the CLI to configure the request.
            destination_list_id (str): The ID of the destination list.
            name (str): The name of the destination list.

    Returns:
        CommandResults: The updated destination list.
    """
    raw_response = client.update_destination_list(
        destination_list_id=args["destination_list_id"],
        name=args["name"],
    )
    return get_destination_list_command_results(
        outputs=raw_response["data"],
        raw_response=raw_response,
    )


@logger
def delete_destination_list_command(client: Client, args: dict[str, Any]) -> CommandResults:
    """Delete a destination list.

    Args:
        client (Client): Session to Cisco Umbrella to run API requests.
        args (dict[str, Any]): Arguments passed down by the CLI to configure the request.
            destination_list_id (str): The ID of the destination list.

    Returns:
        CommandResults: The result of the destination list deletion.
    """
    destination_list_id = args["destination_list_id"]
    raw_response = client.delete_destination_list(destination_list_id)

    return CommandResults(
        readable_output=f'The destination list "{destination_list_id}" was successfully deleted',
        raw_response=raw_response,
    )


@logger
def test_module(client: Client) -> str:
    """Tests API connectivity and authentication.

    Returning 'ok' indicates that the integration works like it is supposed to.
    Raises exceptions if something goes wrong.

    Args:
        client (Client): Session to Cisco Umbrella to run API requests.

    Raises:
        DemistoException: Incase there is an unknown error.

    Returns:
        str: : 'ok' if test passed, or an error message if the credentials are incorrect.
    """
    try:
        client.login()

    except DemistoException as exc:
        if exc.res is not None and exc.res.status_code == http.HTTPStatus.UNAUTHORIZED:
            return "Authorization Error: invalid API key or secret"

        raise exc

    return "ok"


def main() -> None:
    params: dict[str, Any] = demisto.params()
    args: dict[str, Any] = demisto.args()
    command: str = demisto.command()
    base_url = params.get("baseURL") or DEFAULT_URL

    api_key: str = params["credentials"]["identifier"]
    api_secret: str = params["credentials"]["password"]
    verify_certificate: bool = not params.get("insecure", False)
    proxy: bool = params.get("proxy", False)

    demisto.debug(f"Command being called is {command}")

    commands_v2 = {
        f"{INTEGRATION_COMMAND_PREFIX}-{DESTINATION}s-list": list_destinations_command,
        f"{INTEGRATION_COMMAND_PREFIX}-{DESTINATION}-add": add_destination_command,
        f"{INTEGRATION_COMMAND_PREFIX}-{DESTINATION}-delete": delete_destination_command,
        f"{INTEGRATION_COMMAND_PREFIX}-{DESTINATION_LIST}s-list": list_destination_lists_command,
        f"{INTEGRATION_COMMAND_PREFIX}-{DESTINATION_LIST}-create": create_destination_list_command,
        f"{INTEGRATION_COMMAND_PREFIX}-{DESTINATION_LIST}-update": update_destination_list_command,
        f"{INTEGRATION_COMMAND_PREFIX}-{DESTINATION_LIST}-delete": delete_destination_list_command,
    }

    commands_v1 = {
        f"{INTEGRATION_COMMAND_PREFIX}-get-{DESTINATION}-{DOMAIN}": list_destinations_command,
        f"{INTEGRATION_COMMAND_PREFIX}-get-{DESTINATION}-{DOMAIN}s": list_destinations_command,
        f"{INTEGRATION_COMMAND_PREFIX}-search-{DESTINATION}-{DOMAIN}s": list_destinations_command,
        f"{INTEGRATION_COMMAND_PREFIX}-add-{DOMAIN}": add_destination_command,
        f"{INTEGRATION_COMMAND_PREFIX}-remove-{DOMAIN}": delete_destination_command,
        f"{INTEGRATION_COMMAND_PREFIX}-get-{DESTINATION}-lists": list_destination_lists_command,
    }

    try:
        client = Client(
            base_url=base_url,
            api_key=api_key,
            api_secret=api_secret,
            verify=verify_certificate,
            proxy=proxy,
        )

        if command == "test-module":
            results = test_module(client)
        elif command in commands_v2:
            client.login()
            results = commands_v2[command](client, args)
        elif command in commands_v1:
            client.login()
            results = bridge_v1_to_v2(v2_command=commands_v1[command], client=client, args=args)
        else:
            raise NotImplementedError(f"{command} command is not implemented.")

        return_results(results)

    except Exception as e:
        return_error(str(e))


if __name__ in ["__main__", "builtin", "builtins"]:
    main()