EclecticIQ Intelligence Center v3

Threat Intelligence Platform that connects and interprets intelligence data from open sources, commercial suppliers and industry partnerships .

Data Enrichment & Threat Intelligence · EclecticIQ Platform · Feed

Details

IDEclecticIQ Intelligence Center v3
ProviderEclecticIQ
CategoryData Enrichment & Threat Intelligence
From Version6.6.0
Docker Imagedemisto/python3:3.12.13.10116658
Supported ModulesAgentix XSIAM

README

Threat Intelligence Platform that connects and interprets intelligence data from open sources, commercial suppliers and industry partnerships .
This integration was integrated and tested with version 2.14 and 3.0 of EclecticIQ Intelligence Center v3.

Some changes have been made that might affect your existing content.
If you are upgrading from a previous version of this integration, see Breaking Changes.

Configure EclecticIQ Intelligence Center v3 in Cortex

Parameter Description Required
EclecticIQ Intelligence Center URL (e.g. https://eclecticiq-platform.local)   True
API user token to authenticate in EclecticIQ Intelligence Center   True
EclecticIQ Intelligence Center public API version   True
IP threshold. Minimum maliciousness confidence level to consider the IP address malicious: High, Medium, Low, Safe, Unknown   False
URL threshold. Minimum maliciousness confidence level to consider the URL malicious: High, Medium, Low, Safe, Unknown   False
File threshold. Minimum maliciousness confidence level to consider the file malicious: High, Medium, Low, Safe, Unknown   False
Email threshold. Minimum maliciousness confidence level to consider the email address malicious: High, Medium, Low, Safe, Unknown   False
Domain threshold. Minimum maliciousness confidence level to consider the domain malicious: High, Medium, Low, Safe, Unknown   False
Group name in EclecticIQ Intelligence Center to use as entities source   False
Create sightings automatically in EclecticIQ Intelligence Center when reputation check command executed.   False
Fetch indicators    
Indicator Reputation Indicators from this integration instance will be marked with this reputation False
Source Reliability Reliability of the source providing the intelligence data True
    False
    False
Feed Fetch Interval   False
Bypass exclusion list When selected, the exclusion list is ignored for indicators from this feed. This means that if an indicator from this feed is on the exclusion list, the indicator might still be added to the system. False
Feed IDs to fetch e.g. 12,14,22 False
    False
Traffic Light Protocol Color The Traffic Light Protocol (TLP) designation to apply to indicators fetched from the feed  
Trust any certificate (not secure)   False
Use system proxy settings   False

Commands

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

ip


Get reputation of IP address observable.

Base Command

ip

Input

Argument Name Description Required
ip IPv4 to get the reputation of. Required

Context Output

Path Type Description
EclecticIQ.IP.Created Date Observable creation time.
EclecticIQ.IP.LastUpdated Date Observable last update time.
EclecticIQ.IP.Maliciousness String Observable maliciousness.
EclecticIQ.IP.Observable String Observable value.
EclecticIQ.IP.SourceName String Observable source name.
DBotScore.Indicator String The indicator that was tested.
DBotScore.Score Number The actual score.
DBotScore.Reliability String How reliable the score is (for example, “C - fairly reliable”).
DBotScore.Type String The indicator type.
DBotScore.Vendor String The vendor used to calculate the score.
IP.Address String IP address.

Command example

!ip ip="8.8.8.8"

Context Example

{
    "DBotScore": {
        "Indicator": "8.8.8.8",
        "Score": 0,
        "Type": "ip",
        "Vendor": "EclecticIQ Intelligence Center v3"
    },
    "EclecticIQ": {
        "IP": {
            "Created": "2023-10-09T18:00",
            "LastUpdated": "2023-11-13T15:35",
            "Maliciousness": "unknown",
            "Observable": "8.8.8.8",
            "SourceName": "enricher_task: VirusTotal APIv3 File Hash (Contacted Infrastructure) Enricher; enricher_task: Recorded Future Enricher; group: Testing Group; "
        }
    },
    "IP": {
        "Address": "8.8.8.8"
    }
}

Human Readable Output

EclecticIQ IP reputation - 8.8.8.8

created id last_updated maliciousness platform_link source_name type value
2023-10-09T18:00 466127 2023-11-13T15:35 unknown https://ic-playground.eclecticiq.com/main/intel/all/browse/observable?tab=overview&id=466127 enricher_task: VirusTotal APIv3 File Hash (Contacted Infrastructure) Enricher; enricher_task: Recorded Future Enricher; group: Testing Group; ipv4 8.8.8.8

url


Gets the reputation of a URL observable.

Base Command

url

Input

Argument Name Description Required
url URL observable to get the reputation of. Required

Context Output

Path Type Description
EclecticIQ.URL.Created Date Observable creation time.
EclecticIQ.URL.LastUpdated Date Observable last update time.
EclecticIQ.URL.Maliciousness String Observable maliciousness.
EclecticIQ.URL.Observable String Observable value.
EclecticIQ.URL.SourceName String Observable source name.
DBotScore.Indicator String The indicator that was tested.
DBotScore.Score Number The actual score.
DBotScore.Reliability String How reliable the score is (for example, “C - fairly reliable”).
DBotScore.Type String The indicator type.
DBotScore.Vendor String The vendor used to calculate the score.
URL.Data String URL requested.

Command example

!url url="https://www.ultimatewindowssecurity.com/securitylog/encyclopedia/"

Context Example

{
    "DBotScore": {
        "Indicator": "https://www.ultimatewindowssecurity.com/securitylog/encyclopedia/",
        "Score": 0,
        "Type": "url",
        "Vendor": "EclecticIQ Intelligence Center v3"
    },
    "EclecticIQ": {
        "URL": {
            "Created": "2023-06-02T08:14",
            "LastUpdated": "2023-06-02T08:14",
            "Maliciousness": "unknown",
            "Observable": "https://www.ultimatewindowssecurity.com/securitylog/encyclopedia/",
            "SourceName": "incoming_feed: Elemendar; "
        }
    },
    "URL": {
        "Data": "https://www.ultimatewindowssecurity.com/securitylog/encyclopedia/"
    }
}

Human Readable Output

EclecticIQ URL reputation - https://www.ultimatewindowssecurity.com/securitylog/encyclopedia/

created id last_updated maliciousness platform_link source_name type value
2023-06-02T08:14 119519 2023-06-02T08:14 unknown https://ic-playground.eclecticiq.com/main/intel/all/browse/observable?tab=overview&id=119519 incoming_feed: Elemendar; uri https://www.ultimatewindowssecurity.com/securitylog/encyclopedia/

file


Gets the reputation of a file hash observable.

Base Command

file

Input

Argument Name Description Required
file File hash observable to get the reputation of. Required

Context Output

Path Type Description
EclecticIQ.File.Created Date Observable creation time.
EclecticIQ.File.LastUpdated Date Observable last update time.
EclecticIQ.File.Maliciousness String Observable maliciousness.
EclecticIQ.File.Observable String Observable value.
EclecticIQ.File.SourceName String Observable source name.
DBotScore.Indicator String The indicator that was tested.
DBotScore.Score Number The actual score.
DBotScore.Reliability String How reliable the score is (for example, “C - fairly reliable”).
DBotScore.Type String The indicator type.
DBotScore.Vendor String The vendor used to calculate the score.
File.MD5 String Bad MD5 hash.
File.SHA1 String Bad SHA1 hash.
File.SHA256 String Bad SHA256 hash.

Command example

!file file=ae5f156a6f5052494a295c597389dbee

Context Example

{
    "DBotScore": {
        "Indicator": "ae5f156a6f5052494a295c597389dbee",
        "Score": 0,
        "Type": "file",
        "Vendor": "EclecticIQ Intelligence Center v3"
    },
    "EclecticIQ": {
        "File": {
            "Created": "2023-05-26T09:20",
            "LastUpdated": "2023-05-26T09:20",
            "Maliciousness": "unknown",
            "Observable": "ae5f156a6f5052494a295c597389dbee",
            "SourceName": "enricher_task: Threatcrowd API V2; "
        }
    },
    "File": {
        "Hashes": [
            {
                "type": "MD5",
                "value": "ae5f156a6f5052494a295c597389dbee"
            }
        ],
        "MD5": "ae5f156a6f5052494a295c597389dbee"
    }
}

Human Readable Output

EclecticIQ File reputation - ae5f156a6f5052494a295c597389dbee

created id last_updated maliciousness platform_link source_name type value
2023-05-26T09:20 13 2023-05-26T09:20 unknown https://ic-playground.eclecticiq.com/main/intel/all/browse/observable?tab=overview&id=13 enricher_task: Threatcrowd API V2; hash-md5 ae5f156a6f5052494a295c597389dbee

eclecticiq-get-entity


Query EIC for entity.

Base Command

eclecticiq-get-entity

Input

Argument Name Description Required
observable_value Observable value to query related entities. Optional
entity_title Text to search inside entity title. Optional
entity_type Type of entity to limit query. Possible values are: all, campaign, course-of-action, exploit-target, incident, indicator, sighting, threat-actor, ttp. Default is all. Optional

Context Output

Path Type Description
EclecticIQ.Entity.confidence String Entity confidence.
EclecticIQ.Entity.created_at Date Entity creation time.
EclecticIQ.Entity.description String Entity description.
EclecticIQ.Entity.entity_title String Entity title.
EclecticIQ.Entity.entity_type String Entity type.
EclecticIQ.Entity.impact.type String Entity impact type.
EclecticIQ.Entity.impact.value String Entity impact value.
EclecticIQ.Entity.impact.value_vocab String Entity impact STIX vocabulary.
EclecticIQ.Entity.observables_list.maliciousness String Related observable maliciousness.
EclecticIQ.Entity.observables_list.type String Related observable type.
EclecticIQ.Entity.observables_list.value String Related observable value.
EclecticIQ.Entity.observables_output String Related observables string.
EclecticIQ.Entity.relationships_list Unknown Entity relationships list.
EclecticIQ.Entity.relationships_output String Entity relationships string.
EclecticIQ.Entity.source_name String Entity source.
EclecticIQ.Entity.tags_list Unknown Entity tags and taxonomies.

email


Gets the reputation of an email address observable.

Base Command

email

Input

Argument Name Description Required
email Email address observable to get the reputation of. Required

Context Output

Path Type Description
EclecticIQ.Email.Created Date Observable creation time.
EclecticIQ.Email.LastUpdated Date Observable last update time.
EclecticIQ.Email.Maliciousness String Observable maliciousness.
EclecticIQ.Email.Observable String Observable value.
EclecticIQ.Email.SourceName String Observable source name.
DBotScore.Indicator String The indicator that was tested.
DBotScore.Score Number The actual score.
DBotScore.Reliability String How reliable the score is (for example, “C - fairly reliable”).
DBotScore.Type String The indicator type.
DBotScore.Vendor String The vendor used to calculate the score.

Command example

!email email=domains@twitter.com

Context Example

{
    "DBotScore": {
        "Indicator": "domains@twitter.com",
        "Score": 0,
        "Type": "email",
        "Vendor": "EclecticIQ Intelligence Center v3"
    },
    "EclecticIQ": {
        "Email": {
            "Created": "2023-05-26T09:22",
            "LastUpdated": "2023-05-26T09:22",
            "Maliciousness": "unknown",
            "Observable": "domains@twitter.com",
            "SourceName": "enricher_task: Threatcrowd API V2; "
        }
    },
    "Email": {
        "Address": "domains@twitter.com"
    }
}

Human Readable Output

EclecticIQ Email reputation - domains@twitter.com

created id last_updated maliciousness platform_link source_name type value
2023-05-26T09:22 1028 2023-05-26T09:22 unknown https://ic-playground.eclecticiq.com/main/intel/all/browse/observable?tab=overview&id=1028 enricher_task: Threatcrowd API V2; email domains@twitter.com

domain


Gets the reputation of a domain observable.

Base Command

domain

Input

Argument Name Description Required
domain Domain observable to get the reputation of. Required

Context Output

Path Type Description
EclecticIQ.Domain.Created Date Observable creation time.
EclecticIQ.Domain.LastUpdated Date Observable last update time.
EclecticIQ.Domain.Maliciousness String Observable maliciousness.
EclecticIQ.Domain.Observable String Observable value.
EclecticIQ.Domain.SourceName String Observable source name.
DBotScore.Indicator String The indicator that was tested.
DBotScore.Score Number The actual score.
DBotScore.Reliability String How reliable the score is (for example, “C - fairly reliable”).
DBotScore.Type String The indicator type.
DBotScore.Vendor String The vendor used to calculate the score.
Domain.Name String Requested Domain.

Command example

!domain domain=urlz.fr

Context Example

{
    "DBotScore": {
        "Indicator": "urlz.fr",
        "Score": 3,
        "Type": "domain",
        "Vendor": "EclecticIQ Intelligence Center v3"
    },
    "Domain": {
        "Malicious": {
            "Description": "EclecticIQ maliciousness confidence level: low",
            "Vendor": "EclecticIQ Intelligence Center v3"
        },
        "Name": "urlz.fr"
    },
    "EclecticIQ": {
        "Domain": {
            "Created": "2023-05-26T09:20",
            "LastUpdated": "2023-05-26T09:20",
            "Maliciousness": "low",
            "Observable": "urlz.fr",
            "SourceName": ""
        }
    }
}

Human Readable Output

EclecticIQ Domain reputation - urlz.fr

created id last_updated maliciousness platform_link source_name type value
2023-05-26T09:20 43 2023-05-26T09:20 low https://ic-playground.eclecticiq.com/main/intel/all/browse/observable?tab=overview&id=43   domain urlz.fr

eclecticiq-create-sighting


Create a sighting entity on EIC.
Must contain at least one observable.

Base Command

eclecticiq-create-sighting

Input

Argument Name Description Required
observable_value Observable value to connect to Sighting. Required
observable_type Observable type. Possible values are: domain, email, email-subject, file, hash, hash-md5, hash-sha1, hash-sha256, hash-sha512, host, ipv4, ipv6, mutex, port, process, uri, winregistry. Required
observable_maliciousness Observable maliciousness. Possible values are: Malicious (High confidence), Malicious (Medium confidence), Malicious (Low confidence), Safe, Unknown. Required
sighting_title Sighting title. Required
sighting_description Sighting description. Optional
sighting_confidence Sighting confidence. Possible values are: None, Unknown, Low, Medium, High. Required
sighting_impact Sighting impact. Possible values are: None, Unknown, Low, Medium, High. Required
sighting_tag Sighting tags, use comma (“,”) as delimeter between tags. Optional

Context Output

Path Type Description
EclecticIQ.Sightings.SightingDetails.ObservableMaliciousness String Sighting related observable maliciousness.
EclecticIQ.Sightings.SightingDetails.ObservableType String Sighting related observable type.
EclecticIQ.Sightings.SightingDetails.ObservableValue String Sighting related observable value.
EclecticIQ.Sightings.SightingDetails.SightingTitle String Sighting title.
EclecticIQ.Sightings.SightingId String Sighting entity ID.

eclecticiq-create-indicator


Create an indicator entity on EIC.
Must contain at least one observable.

Base Command

eclecticiq-create-indicator

Input

Argument Name Description Required
indicator_title Indicator title. Required
indicator_description Indicator description. Optional
indicator_confidence Indicator confidence. Possible values are: None, Unknown, Low, Medium, High. Required
indicator_impact Indicator impact. Possible values are: None, Unknown, Low, Medium, High. Required
indicator_tag Indicator tags, use comma (“,”) as delimeter between tags. Optional
observable_value Observable value to connect to Indicator. Required
observable_type Observable type. Possible values are: domain, email, email-subject, file, hash, hash-md5, hash-sha1, hash-sha256, hash-sha512, host, ipv4, ipv6, mutex, port, process, uri, winregistry. Required
observable_maliciousness Observable maliciousness. Possible values are: Malicious (High confidence), Malicious (Medium confidence), Malicious (Low confidence), Safe, Unknown. Required
observable_dictionary Any amount observables in format: [{“value”:”192.168.0.192”, “type”:”ipv4”, “maliciousness”:”medium”}]. Observable types use as in EclecticIQ. Observable maliciousness could be: high, medium, low, safe, unknown. Optional

Context Output

Path Type Description
EclecticIQ.Indicators.IndicatorId String Indicator entity ID.
EclecticIQ.Indicators.IndicatorTitle String Indicator entity title.
EclecticIQ.Indicators.ObservablesList.observable_classification String Indicator related observable classification.
EclecticIQ.Indicators.ObservablesList.observable_maliciousness String Indicator related observable maliciousness.
EclecticIQ.Indicators.ObservablesList.observable_type String Indicator related observable type.
EclecticIQ.Indicators.ObservablesList.observable_value String Indicator related observable value.

eclecticiq-get-entity-by-id


Query EclecticIQ Intelligence Center for entity by its ID.

Base Command

eclecticiq-get-entity-by-id

Input

Argument Name Description Required
entity_id Entity ID in EclecticIQ format, for example: a86f8393-eff6-4b31-b203-f63152be5a43. Required

Context Output

Path Type Description
EclecticIQ.EntityById.confidence String Entity confidence.
EclecticIQ.EntityById.created_at Date Entity creation time.
EclecticIQ.EntityById.description String Entity description.
EclecticIQ.EntityById.entity_title String Entity title.
EclecticIQ.EntityById.entity_type String Entity type.
EclecticIQ.EntityById.impact String Entity impact.
EclecticIQ.EntityById.observables_list.maliciousness String Related observable maliciousness.
EclecticIQ.EntityById.observables_list.type String Related observable type.
EclecticIQ.EntityById.observables_list.value String Related observable value.
EclecticIQ.EntityById.relationships_list Unknown Entity relationships list.
EclecticIQ.EntityById.source_name String Entity source.
EclecticIQ.EntityById.tags_list Unknown Entity tags and taxonomies.

eclecticiq-request-get


Make HTTP GET request to EclecticIQ Intelligence Center.

Base Command

eclecticiq-request-get

Input

Argument Name Description Required
uri EclecticIQ URI excluding Intelligence Cetner address but including API version and params if needed. e.g. /private/status. Required

Context Output

Path Type Description
EclecticIQ.GET.ReplyBody Unknown GET reply body.
EclecticIQ.GET.ReplyStatus String GET reply status code.
EclecticIQ.GET.URI String GET reply requested URI.

Command example

!eclecticiq-request-get uri=/api/v2/datasets

Context Example

{
    "EclecticIQ": {
        "GET": {
            "ReplyBody": {
                "count": 100,
                "data": [...],
                "limit": 100,
                "offset": 0,
                "total_count": 113
            },
            "ReplyStatus": "200",
            "URI": "/api/v2/datasets"
        }
    }
}

Human Readable Output

EclecticIQ GET action to endpoint /api/v2/datasets exectued. Reply status: 200

eclecticiq-request-post


Make HTTP POST request to EclecticIQ Intelligence Center.

Base Command

eclecticiq-request-post

Input

Argument Name Description Required
uri EclecticIQ URI excluding Intelligence Cetner address but including API version and params if needed. Required
body JSON payload. Optional

Context Output

Path Type Description
EclecticIQ.POST.ReplyBody String POST reply body.
EclecticIQ.POST.ReplyStatus String POST reply status code.
EclecticIQ.POST.URI String POST reply requested URI.

Command example

!eclecticiq-request-post uri=/api/v2/datasets body=`{"data": {"workspaces": "1", "name": "test11112"}}

Context Example

{
    "EclecticIQ": {
        "POST": {
            "ReplyBody": {
                "data": {...}
            },
            "ReplyStatus": "201",
            "URI": "/api/v2/datasets"
        }
    }
}

Human Readable Output

EclecticIQ POST action to endpoint /api/v2/datasets exectued. Reply status: 201

eclecticiq-request-put


Make HTTP PUT request to EclecticIQ Intelligence Center.

Base Command

eclecticiq-request-put

Input

Argument Name Description Required
uri EclecticIQ URI excluding Intelligence Cetner address but including API version and params if needed. Required
body JSON payload. Optional

Context Output

Path Type Description
EclecticIQ.PUT.ReplyBody String PUT reply body.
EclecticIQ.PUT.ReplyStatus String PUT reply status code.
EclecticIQ.PUT.URI String PUT reply requested URI.

Command example

!eclecticiq-request-put uri=/api/v2/datasets body=`{"data": {"workspaces": "1", "name": "test11112"}}

Context Example

{
    "EclecticIQ": {
        "PUT": {
            "ReplyBody": {
                "data": {...}
            },
            "ReplyStatus": "200",
            "URI": "/api/v2/datasets"
        }
    }
}

Human Readable Output

EclecticIQ PUT action to endpoint /api/v2/datasets exectued. Reply status: 200

eclecticiq-request-patch


Make HTTP PATCH request to EclecticIQ Intelligence Center.

Base Command

eclecticiq-request-patch

Input

Argument Name Description Required
uri EclecticIQ URI excluding Intelligence Cetner address but including API version and params if needed. Required
body JSON payload. Optional

Context Output

Path Type Description
EclecticIQ.PATCH.ReplyBody String PATCH reply body.
EclecticIQ.PATCH.ReplyStatus String PATCH reply status code.
EclecticIQ.PATCH.URI String PATCH reply requested URI.

Command example

!eclecticiq-request-patch uri=/api/v2/datasets/1 body=`{"data": {"workspaces": "1", "name": "test11112"}}

Context Example

{
    "EclecticIQ": {
        "PATCH": {
            "ReplyBody": {
                "data": {...}
            },
            "ReplyStatus": "200",
            "URI": "/api/v2/datasets/1"
        }
    }
}

Human Readable Output

EclecticIQ PATCH action to endpoint /api/v2/datasets/1 exectued. Reply status: 200

eclecticiq-request-delete


Make HTTP DELETE request to EclecticIQ Intelligence Center.

Base Command

eclecticiq-request-delete

Input

Argument Name Description Required
uri EclecticIQ URI excluding Intelligence Cetner address but including API version and params if needed. Required

Context Output

Path Type Description
EclecticIQ.DELETE.ReplyStatus String DELETE reply status code.
EclecticIQ.DELETE.URI String DELETE reply requested URI.

eclecticiq-get-indicators


Get last block of Indicators from configured to fetch Outgoing feed.

Base Command

eclecticiq-get-indicators

Input

| Argument Name | Description | Required |
| — | — | — |

Context Output

There is no context output for this command.

Command example

!eclecticiq-get-indicators

Human Readable Output

Indicators collected from first block of feed:[{‘id’: ‘11’, ‘created_at’: ‘2023-06-26T15:23:19.737166+00:00’, ‘update_strategy’: ‘REPLACE’, ‘packaging_status’: ‘SUCCESS’, ‘name’: ‘splunk-test’}]

No entries.

Breaking changes from the previous version of this integration - EclecticIQ Intelligence Center v3

Integration rebuild with added functionality to fetch indicators from outgoing feeds and with many new commands. Integration is not compatible with EclecticIQ integration v2.

Configuration parameters

  • url — EclecticIQ Intelligence Center URL (e.g. https://eclecticiq-platform.local) (required)
  • eiq_token — API user token to authenticate in EclecticIQ Intelligence Center (required)
  • eiq_api_version — EclecticIQ Intelligence Center public API version (required)
  • ip_threshold — IP threshold. Minimum maliciousness confidence level to consider the IP address malicious: High, Medium, Low, Safe, Unknown
  • url_threshold — URL threshold. Minimum maliciousness confidence level to consider the URL malicious: High, Medium, Low, Safe, Unknown
  • file_threshold — File threshold. Minimum maliciousness confidence level to consider the file malicious: High, Medium, Low, Safe, Unknown
  • email_threshold — Email threshold. Minimum maliciousness confidence level to consider the email address malicious: High, Medium, Low, Safe, Unknown
  • domain_threshold — Domain threshold. Minimum maliciousness confidence level to consider the domain malicious: High, Medium, Low, Safe, Unknown
  • group_name — Group name in EclecticIQ Intelligence Center to use as entities source
  • sightings_auto_creation — Create sightings automatically in EclecticIQ Intelligence Center when reputation check command executed.
  • feed — Fetch indicators
  • feedReputation — Indicator Reputation
  • feedReliability — Source Reliability (required)
  • feedExpirationPolicy
  • feedExpirationInterval
  • feedFetchInterval — Feed Fetch Interval
  • feedBypassExclusionList — Bypass exclusion list
  • feedId — Feed IDs to fetch
  • feedIncremental
  • feedTags — Tags
  • tlp_color — Traffic Light Protocol Color
  • insecure — Trust any certificate (not secure)
  • proxy — Use system proxy settings

Commands (15)

  • domain

    Gets the reputation of a domain observable.

  • eclecticiq-create-indicator

    Creates Indicator Entity in EclecticIQ IC.

  • eclecticiq-create-sighting

    Creates Sighting in EclecticIQ IC.

  • eclecticiq-get-entity

    Query EclecticIQ Intelligence Center for entities.

  • eclecticiq-get-entity-by-id

    Query EclecticIQ Intelligence Center for entity by its ID.

  • eclecticiq-get-indicators

    Get last block of Indicators from configured to fetch Outgoing feed.

  • eclecticiq-request-delete

    Make HTTP DELETE request to EclecticIQ Intelligence Center.

  • eclecticiq-request-get

    Make HTTP GET request to EclecticIQ Intelligence Center.

  • eclecticiq-request-patch

    Make HTTP PATCH request to EclecticIQ Intelligence Center.

  • eclecticiq-request-post

    Make HTTP POST request to EclecticIQ Intelligence Center.

  • eclecticiq-request-put

    Make HTTP PUT request to EclecticIQ Intelligence Center.

  • email

    Gets the reputation of an email address observable.

  • file

    Gets the reputation of a file hash observable.

  • ip

    Get reputation of IP address observable.

  • url

    Gets the reputation of a URL observable.

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

""" IMPORTS """

import csv
import io
import json
import logging
import re

import requests
import urllib3

urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)

""" GLOBALS/PARAMS """

PASSWORD = demisto.params().get("eiq_token")
URL = demisto.params().get("url")
SERVER = URL[:-1] if (URL and URL.endswith("/")) else URL
API_VERSION = demisto.params().get("eiq_api_version")
USE_SSL = not demisto.params().get("insecure", False)
HEADERS = {}  # type: Dict[str, str]
IP_THRESHOLD = demisto.params().get("ip_threshold", "").lower()
URL_THRESHOLD = demisto.params().get("url_threshold", "").lower()
FILE_THRESHOLD = demisto.params().get("file_threshold", "").lower()
EMAIL_THRESHOLD = demisto.params().get("email_threshold", "").lower()
DOMAIN_THRESHOLD = demisto.params().get("domain_threshold", "").lower()
GROUP_NAME = demisto.params().get("group_name")
SIGHTINGS_AUTO_CREATE = demisto.params().get("sightings_auto_creation")

""" EclecticIQ lib """

API_PATHS = {
    "v1": {
        "group_id_search": "/api/v1/sources/",
        "feeds_list": "/private/outgoing-feed-download/",
        "outgoing_feeds": "/private/outgoing-feeds/",
        "feed_content_blocks": "/private/outgoing-feed-download/",
        "groups": "/private/groups/",
        "entities": "/private/entities/",
        "observable_search": "/api/v1/observables",
        "observable_search_raw": "/private/search-history/search-observable",
        "entity_search": "/api/v1/entities",
        "taxonomy_get": "/api/v1/taxonomies",
        "observables": "/private/search-all",
        "tasks": "/private/tasks/",
        "dataset": "/private/intel-sets/",
        "relationships": "/api/v1/relationships",
        "task_status": "/private/task-runs/",
        "incoming_feeds": "/private/incoming-feeds/",
        "observables_batch_delete": "/private/extracts/batch-delete/",
        "status": "/private/status",
        "enrichers": "/private/enricher-tasks/",
        "enrichers-run": "/private/enricher-tasks/batch-run",
    },
    "v2": {
        "group_id_search": "/api/v2/sources/",
        "feeds_list": "/private/outgoing-feed-download/",
        "outgoing_feeds": "/api/v2/outgoing-feeds/",
        "feed_content_blocks": "/private/outgoing-feed-download/",
        "groups": "/private/groups/",
        "entities": "/private/entities/",
        "observable_search": "/api/v2/observables",
        "observable_search_raw": "/private/search-history/search-observable",
        "entity_search": "/api/v2/entities",
        "taxonomy_get": "/api/v2/taxonomies",
        "observables": "/private/search-all",
        "tasks": "/private/tasks/",
        "dataset": "/private/intel-sets/",
        "relationships": "/api/v2/relationships",
        "task_status": "/private/task-runs/",
        "incoming_feeds": "/private/incoming-feeds/",
        "observables_batch_delete": "/private/extracts/batch-delete/",
        "status": "/private/status",
        "enrichers": "/private/enricher-tasks/",
        "enrichers-run": "/private/enricher-tasks/batch-run",
    },
}

USER_AGENT = "script"


def extract_uuid_from_url(url):
    match = re.search(r"[\da-z\-]{36}", url)

    if match:
        return match.group()
    else:
        return None


def observable_id_from_url(url):
    match = re.search(r"(observables\/)([\d]+)", url)

    if match:
        return match.group(2)
    else:
        return None


def taxonomie_id_from_url(url):
    match = re.search(r"(taxonomies\/)([\d]+)", url)

    if match:
        return match.group(2)
    else:
        return None


def format_ts(dt):
    return dt.replace(microsecond=0).isoformat() + "Z"


def format_ts_human(dt):
    return dt.replace(microsecond=0).isoformat() + "Z"


class xsoar_logger:
    def info(self, message):
        demisto.info(message)

    def error(self, message):
        demisto.error(message)

    def exception(self, message):
        demisto.error(message)

    def debug(self, message):
        demisto.debug(message)

    def warning(self, message):
        demisto.error(message)


class EclecticIQ_api:
    def __init__(
        self,
        baseurl,
        eiq_api_version,
        username,
        password,
        verify_ssl=True,
        proxy_ip=None,
        proxy_username=None,
        proxy_password=None,
        logger=None,
        init_cred_test=True,
    ):
        self.eiq_logging = xsoar_logger()
        self.eiq_username = username
        self.eiq_password = password
        self.baseurl = baseurl
        self.verify_ssl = self.set_verify_ssl(verify_ssl)
        self.proxy_ip = proxy_ip
        self.proxy_username = proxy_username
        self.proxy_password = proxy_password
        self.proxies = self.set_eiq_proxy()
        self.eiq_api_version = eiq_api_version
        self.taxonomie_dict = {}
        self.headers = {
            "user-agent": USER_AGENT,
            "Content-Type": "application/json",
            "Accept": "application/json",
        }
        self.get_outh_token(test_credentials=init_cred_test)

    def set_logger(self, logger):
        if logger is None:
            logging.basicConfig(level=logging.INFO)
            logger_output = logging.getLogger()
            return logger_output
        else:
            return logger

    def set_verify_ssl(self, ssl_status):
        if ssl_status in ["1", "True", "true", True]:
            return True
        return ssl_status not in ["0", "False", "false", False]

    def sanitize_eiq_url(self, eiq_url):
        # TD
        return eiq_url

    def set_eiq_proxy(self):
        if self.proxy_ip and self.proxy_username and self.proxy_password:
            return {
                "http": "http://" + self.proxy_username + ":" + self.proxy_password + "@" + self.proxy_ip + "/",
                "https": "http://" + self.proxy_username + ":" + self.proxy_password + "@" + self.proxy_ip + "/",
            }
        elif self.proxy_ip:
            return {
                "http": "http://" + self.proxy_ip + "/",
                "https": "http://" + self.proxy_ip + "/",
            }
        else:
            return None

    def get_outh_token(self, test_credentials=True):
        self.eiq_logging.info("Authenticating using username: " + str(self.eiq_username))

        try:
            self.headers["Authorization"] = "Bearer " + str(self.eiq_password)

            if test_credentials:
                r = requests.get(
                    str(self.baseurl) + "/private",
                    headers=self.headers,
                    verify=self.verify_ssl,
                    proxies=self.proxies,
                    timeout=30,
                )

                if r and r.status_code in [100, 200, 201, 202]:
                    self.eiq_logging.info("Authentication successful")
                else:
                    if not r:
                        msg = "Could not perform auth request to EclecticIQ"
                        self.eiq_logging.exception(msg)
                        raise Exception(msg)
                    try:
                        err = r.json()
                        detail = err["errors"][0]["detail"]
                        msg = (
                            f"EclecticIQ VA returned an error, "
                            f"code:[{r.status_code}], reason:[{r.reason}], URL: [{r.url}], details:[{detail}]"
                        )
                    except Exception:
                        msg = f"EclecticIQ VA returned an error, code:[{r.status_code}], reason:[{r.reason}], URL: [{r.url}]"
                    raise Exception(msg)

        except Exception:
            self.eiq_logging.error("Authentication failed")
            raise

    def send_api_request(self, method, path, params=None, data=None):
        url = self.baseurl + path

        r = None
        try:
            if hasattr(requests, method):
                request_method = getattr(requests, method)
                r = request_method(
                    url,
                    headers=self.headers,
                    params=params,
                    data=json.dumps(data),
                    verify=self.verify_ssl,
                    proxies=self.proxies,
                    timeout=30,
                )
            else:
                self.eiq_logging.error("Unknown method: " + str(method))
                raise Exception(f"Unsupported HTTP method: {method}")

        except Exception as e:
            self.eiq_logging.exception(f"Could not perform request to EclecticIQ VA: {method}: {url}. Exception: {e}")

        if r and r.status_code in [100, 200, 201, 202, 204]:
            return r
        else:
            if r is None:
                msg = f"Could not perform request to EclecticIQ VA: {method}: {url}. Check network connectivity."
                self.eiq_logging.exception(msg)
                raise Exception(msg)
            elif r.status_code in [401]:
                msg = f"Wrong credentials. Status code:{r.status_code}"
                self.eiq_logging.exception(msg)
                raise Exception(msg)
            elif not r:
                msg = f"Could not perform request to EclecticIQ VA: {method}: {url}."
                self.eiq_logging.exception(msg)
                raise Exception(msg)

            try:
                err = r.json()
                detail = err["errors"][0]["detail"]
                msg = (
                    f"EclecticIQ VA returned an error, code:{r.status_code},"
                    f" reason:[{r.reason}], URL: [{r.url}], details:[{detail}]"
                )
            except Exception:
                msg = f"EclecticIQ VA returned an error, code:{r.status_code}, reason:[{r.reason}], URL: [{r.url}]"
            raise Exception(msg)

    def get_source_group_uid(self, group_name):
        # get source group UID.
        self.eiq_logging.debug("Requesting source id for specified group, name=[" + str(group_name) + "]")
        r = self.send_api_request(
            "get",
            path=API_PATHS[self.eiq_api_version]["groups"],
            params="filter[name]=" + str(group_name),
        )

        if not r.json()["data"]:
            self.eiq_logging.error(
                "Something went wrong fetching the group id. "
                "Please note the source group name is case sensitive! "
                "Received response:" + str(r.json())
            )
            return "error_in_fetching_group_id"
        else:
            self.eiq_logging.debug("Source group id received")
            self.eiq_logging.debug("Source group id is: " + str(r.json()["data"][0]["source"]))
            return r.json()["data"][0]["source"]

    def get_source_group_order_id(self, group_name):
        # get source group UID.
        self.eiq_logging.debug("Requesting source id for specified group, name=[" + str(group_name) + "]")
        r = self.send_api_request(
            "get",
            path=API_PATHS[self.eiq_api_version]["groups"],
            params="filter[name]=" + str(group_name),
        )

        return r.json()["data"][0]["id"]

    def get_enrichers_list(self):
        # get enrichers list
        self.eiq_logging.debug("Requesting availble Enrichers list from platform.")
        r = self.send_api_request("get", path=API_PATHS[self.eiq_api_version]["enrichers"])

        return r.json()["data"]

    def get_active_enrichers_list(self):
        enrichers_list = []
        enrichers_list = self.get_enrichers_list()
        active_list = []

        for enricher in enrichers_list:
            if enricher["is_active"]:
                active_list.append(enricher)

        return active_list

    def enrich_observable(self, enricher_id, observable_id):
        # To enrich Observable
        self.eiq_logging.debug(f"Enriching observable.{observable_id}")
        run_dict = {"data": {"enricher_tasks": [enricher_id], "extracts": [observable_id]}}

        r = self.send_api_request("post", path=API_PATHS[self.eiq_api_version]["enrichers-run"], data=run_dict)

        return r.json()["data"]

    def get_status(self):
        # get platform status
        self.eiq_logging.info("Requesting Platform status")

        r = self.send_api_request("get", path=API_PATHS[self.eiq_api_version]["status"])

        return r.json()["data"]

    def get_status_red_component(self):
        status = self.get_status()

        result = {"health": status["health"], "components": []}

        for celery_component in status["celery_states"]:
            if celery_component["health"] == "RED":
                result["components"].append(celery_component)

        for service_component in status["service_states"]:
            if service_component["health"] == "RED":
                result["components"].append(service_component)

        return result

    def create_incoming_feed(
        self,
        feed_title,
        content_type,
        password,
        username,
        collection_name="null",
        polling_service_url="null",
        taxii_version="null",
        transport_type="null",
        basic_auth="false",
    ):
        self.eiq_logging.info(f"Creating Incoming Feed {feed_title}")
        # To create Incoming feed

        # TD "null" doesnt work, if lines are not commented it leads to 500 error
        create_feed_dict = {
            "data": {
                "content_type": content_type,
                "half_life": {},
                "is_public": "false",
                "name": feed_title,
                "require_link_types": "false",
                "require_valid_signature": "false",
                "transport_configuration": {
                    "basic_authentication_mode": basic_auth,
                    "password": password,
                    "username": username,
                    "collection_name": collection_name,
                    "polling_service_url": polling_service_url,
                    "ssl_authentication_mode": "false",
                    "taxii_version": taxii_version,
                    "verify_ssl": "false",
                },
                "transport_type": transport_type,
            }
        }

        r = self.send_api_request(
            "post",
            path=API_PATHS[self.eiq_api_version]["incoming_feeds"],
            data=create_feed_dict,
        )

        result = (json.loads(r.text))["data"]

        return result

    def download_incoming_feed(self, feed_id, feed_provider_task):
        # To download incmoing feed
        self.eiq_logging.info(f"Downloading Incoming Feed {feed_id}")

        run_task_download_feed = {
            "data": {
                "id": feed_provider_task,
                "is_active": True,
                # "parameters": {
                #     "basic_authentication_mode": False,
                #     "collection_name": "multi-binding-fixed",
                #     "polling_service_url": "https://test.taxiistand.com/read-only/services/poll",
                #     "ssl_authentication_mode": False,
                #     "taxii_version": "1.1",
                #     "verify_ssl": False
                # },
                # "task_name": "eiq.incoming-transports.taxii",
                "task_type": "provider_task",
                "trigger": "null",
            }
        }

        r = self.send_api_request(
            "post",
            path=API_PATHS[self.eiq_api_version]["tasks"] + str(feed_provider_task) + "/run",
            data=run_task_download_feed,
        )

        result = (json.loads(r.text))["data"]
        return result

    def create_outgoing_feed(
        self,
        content_type,
        intel_set_id,
        feed_title,
        transport_type,
        update_strategy,
        access_group_name,
    ):
        # create Outgoing feed

        self.eiq_logging.info(f"Creating Outgoing Feed {feed_title}")

        group_id = self.get_source_group_order_id(access_group_name)

        authorized_group_order_id = str(group_id)

        create_outgoing_feed = {
            "data": {
                "allowed_extract_states": [
                    {"classification": "bad", "confidence": "high"},
                    {"classification": "bad", "confidence": "medium"},
                    {"classification": "bad", "confidence": "low"},
                    {"classification": "good"},
                    {"classification": "unknown"},
                ],
                "allowed_link_types": [
                    "parameter",
                    "affected",
                    "configuration",
                    "vulnerability",
                    "weakness",
                    "affected-asset",
                    "related",
                    "observed",
                    "sighted",
                    "test-mechanism",
                    "identity",
                    "malicious-infrastructure",
                    "targeted-victim",
                ],
                "anonymize_replace_actions": [],
                "anonymize_skip_paths": [],
                "content_configuration": {"producer_override_enabled": False},
                "content_type": content_type,
                "deselected_enrichers": [],
                "do_sign_content": False,
                "enrichment_extract_types": [
                    "company",
                    "geo-lat",
                    "registrar",
                    "city",
                    "forum-name",
                    "file",
                    "netname",
                    "street",
                    "host",
                    "person",
                    "uri-hash-sha256",
                    "product",
                    "postcode",
                    "domain",
                    "cce",
                    "name",
                    "card",
                    "actor-id",
                    "winregistry",
                    "geo",
                    "fox-it-portal-uri",
                    "mac-48",
                    "email",
                    "inetnum",
                    "eui-64",
                    "forum-thread",
                    "address",
                    "card-owner",
                    "email-subject",
                    "uri",
                    "country-code",
                    "ipv6",
                    "telephone",
                    "rule",
                    "nationality",
                    "forum-room",
                    "mutex",
                    "asn",
                    "hash-md5",
                    "ipv4",
                    "organization",
                    "country",
                    "bank-account",
                    "snort",
                    "handle",
                    "hash-sha256",
                    "industry",
                    "port",
                    "cve",
                    "geo-long",
                    "hash-sha1",
                    "yara",
                    "malware",
                    "hash-sha512",
                    "cwe",
                    "process",
                ],
                # "execution_schedule": "null",
                "extract_types": [
                    "actor-id",
                    "address",
                    "asn",
                    "bank-account",
                    "card",
                    "card-owner",
                    "cce",
                    "city",
                    "company",
                    "country",
                    "country-code",
                    "cve",
                    "cwe",
                    "domain",
                    "email",
                    "email-subject",
                    "eui-64",
                    "file",
                    "forum-name",
                    "forum-room",
                    "forum-thread",
                    "fox-it-portal-uri",
                    "geo",
                    "geo-lat",
                    "geo-long",
                    "handle",
                    "hash-md5",
                    "hash-sha1",
                    "hash-sha256",
                    "hash-sha512",
                    "host",
                    "industry",
                    "inetnum",
                    "ipv4",
                    "ipv6",
                    "mac-48",
                    "malware",
                    "mutex",
                    "name",
                    "nationality",
                    "netname",
                    "organization",
                    "person",
                    "port",
                    "postcode",
                    "process",
                    "product",
                    "registrar",
                    "rule",
                    "snort",
                    "street",
                    "telephone",
                    "uri",
                    "uri-hash-sha256",
                    "winregistry",
                    "yara",
                ],
                # "half_life_filter": "null",
                "include_without_link_type": True,
                "intel_sets": [str(intel_set_id)],
                "is_active": False,
                "name": feed_title,
                "require_valid_data": False,
                # "source_reliability_filter": "null",
                # "tlp_color_filter": "null",
                # "tlp_color_override": "null",
                "transport_configuration": {
                    "authorized_groups": [authorized_group_order_id],
                    "is_public": False,
                },
                "transport_type": transport_type,
                "update_strategy": update_strategy,
                "whitelist_sources": [],
                "whitelist_tags": [],
                "whitelist_taxonomy_nodes": [],
            }
        }

        r = self.send_api_request(
            "post",
            path=API_PATHS[self.eiq_api_version]["outgoing_feeds"],
            data=create_outgoing_feed,
        )

        result = r.json()["data"]

        return result

    def get_incoming_feed_blobs_pending(self, feed_id):
        # to get incmoing feed blobs
        self.eiq_logging.info(f"Requesting Incoming feed run status: {feed_id}")

        r = self.send_api_request("get", path=API_PATHS[self.eiq_api_version]["incoming_feeds"])

        result = None

        for feed in (json.loads(r.text))["data"]:
            if feed["id"] == feed_id:
                result = feed["n_blobs_pending"]

        return result

    def get_full_feed_info(self, feed_id):
        # to get outgoing feed full info

        self.eiq_logging.info(f"Requesting full feed info for feed id={feed_id}")

        if feed_id == "*":
            feed_id = ""

        r = self.send_api_request("get", path=API_PATHS[self.eiq_api_version]["outgoing_feeds"] + str(feed_id))

        result = (json.loads(r.text))["data"]

        return result

    def get_incoming_feed_full_info(self, feed_id):
        # to get incmoing feed full info

        self.eiq_logging.info(f"Requesting full feed info for incoming feed id={feed_id}")

        if feed_id == "*":
            feed_id = ""

        r = self.send_api_request("get", path=API_PATHS[self.eiq_api_version]["incoming_feeds"] + str(feed_id))

        result = (json.loads(r.text))["data"]

        return result

    def get_feed_info(self, feed_ids):
        self.eiq_logging.info(f"Requesting feed info for feed id={feed_ids}")
        feed_ids = (feed_ids.replace(" ", "")).split(",")
        result = []

        if self.eiq_api_version == "FC":
            for k in feed_ids:
                feed_result = {
                    "id": k,
                    "created_at": "",
                    "update_strategy": "REPLACE",
                    "packaging_status": "SUCCESS",
                }
                result.append(feed_result)
            self.feeds_info = result
            return result

        for k in feed_ids:
            feed_result = {}
            try:
                r = self.send_api_request("get", path=API_PATHS[self.eiq_api_version]["outgoing_feeds"] + k)
            except Exception:
                self.eiq_logging.error(f"Feed id={k} information cannot be requested.")
                continue

            if not r.json()["data"]:
                self.eiq_logging.error("Feed id={0} information cannot be requested. Received response:" + str(r.json())).format(
                    k
                )
                return "error_in_fetching_feed_info"
            else:
                self.eiq_logging.debug(f"Feed id={k} information requested")
                feed_result["id"] = r.json()["data"]["id"]
                feed_result["created_at"] = r.json()["data"]["created_at"]
                feed_result["update_strategy"] = r.json()["data"]["update_strategy"]
                feed_result["packaging_status"] = r.json()["data"]["packaging_status"]
                feed_result["name"] = r.json()["data"]["name"]
                result.append(feed_result)
                self.eiq_logging.debug(
                    f"Feed id={k} information retrieved successfully. Received response:" + str(json.dumps(feed_result))
                )

        return result

    def download_block_list(self, block):
        self.eiq_logging.debug(f"Downloading block url{block}")

        if self.eiq_api_version == "FC":
            block = (str(block)).replace(self.baseurl, "")

        r = self.send_api_request("get", path=str(block))
        data = r.text

        return data

    def get_feed_content_blocks(self, feed, feed_last_run=None):
        self.eiq_logging.debug("Requesting block list for feed id={}".format(feed["id"]))

        if feed_last_run is None:
            feed_last_run = {}
            feed_last_run["last_ingested"] = None
            feed_last_run["created_at"] = None

        if feed["packaging_status"] == "SUCCESS" and feed["update_strategy"] == "REPLACE":
            self.eiq_logging.debug("Requesting block list for REPLACE feed.")

            r = self.send_api_request(
                "get",
                path=API_PATHS[self.eiq_api_version]["feed_content_blocks"] + "{}/runs/latest".format(feed["id"]),
            )

            data = r.json()["data"]["content_blocks"]
            if feed_last_run["last_ingested"] == data[-1]:
                self.eiq_logging.info("Received list contains {} blocks for feed id={}.".format(len(data), feed["id"]))
                return []
            self.eiq_logging.info("Received list contains {} blocks for feed id={}.".format(len(data), feed["id"]))
            return data

        elif feed["packaging_status"] == "SUCCESS" and (feed["update_strategy"] in ["APPEND", "DIFF"]):
            self.eiq_logging.debug("Requesting block list for {} feed.".format(feed["update_strategy"]))

            r = self.send_api_request(
                "get",
                path=API_PATHS[self.eiq_api_version]["feed_content_blocks"] + "{}".format(feed["id"]) + "/",
            )

            data = r.json()["data"]["content_blocks"]

            if (feed["created_at"] != feed_last_run["created_at"]) or feed_last_run["last_ingested"] is None:
                self.eiq_logging.info(
                    f"Received list contains {len(data)} blocks for {feed['update_strategy']} feed:{feed['id']}."
                    " Feed created time changed or first run, "
                    "reingestion of all the feed content."
                )
                return data
            else:
                try:
                    last_ingested_index = data.index(feed_last_run["last_ingested"])
                    diff_data = data[last_ingested_index + 1 :]
                    self.eiq_logging.info(
                        "Received list contains {} blocks for {} feed:{}.".format(
                            len(diff_data), feed["update_strategy"], feed["id"]
                        )
                    )
                    return diff_data
                except ValueError:
                    self.eiq_logging.error("Value of last ingested block not available in Feed {}.".format(feed["id"]))
                    return None

        elif feed["packaging_status"] == "RUNNING":
            self.eiq_logging.info("Feed id={} is running now. Collecting data is not possible.".format(feed["id"]))
            return None
        else:
            self.eiq_logging.info("Feed id={} update strategy is not supported. Use Replace or Diff".format(feed["id"]))
            return None

    def get_group_name(self, group_id):
        self.eiq_logging.info(f"Getting group name by id:{group_id}")
        r = self.send_api_request(
            "get",
            path=API_PATHS[self.eiq_api_version]["group_id_search"] + str(group_id),
        )

        response = json.loads(r.text)
        result = {}

        result["name"] = response["data"].get("name", "N/A")
        result["type"] = response["data"].get("source_type", "N/A")

        return result

    def get_observable_by_id(self, id):
        self.eiq_logging.info(f"Searching Observable with ID:{id}")

        r = self.send_api_request(
            "get",
            path=API_PATHS[self.eiq_api_version]["observable_search"] + "/" + str(id),
        )

        observable_response = json.loads(r.text)

        return observable_response

    def lookup_observable(self, value, type=None):
        """Method lookups specific observable by value and type.

        Args:
            value: value of Observable
            type: type of observable, e.g. ipv4, hash-md5 etc

        Returns:
            Return dictionary with Observable details:
             {created: date and time of creation,
             last_updated: last update time,
             maliciousness: value of maliciousness,
             type: type of Observable from args ,
             value: value of Observable from args,
             source_name: who produced Observable,
             platform_link: direct link o the platform
             }

            Otherwise returns None.

        """
        self.eiq_logging.info(f"Searching Observable:{value}, type:{type}")

        params = {}
        params["filter[value]"] = value

        if type is not None:
            params["filter[type]"] = type

        r = self.send_api_request(
            "get",
            path=API_PATHS[self.eiq_api_version]["observable_search"],
            params=params,
        )

        observable_response = json.loads(r.text)

        if observable_response["count"] == 1:
            result = {}
            result["created"] = str(observable_response["data"][0]["created_at"])[:16]
            result["last_updated"] = str(observable_response["data"][0]["last_updated_at"])[:16]
            result["maliciousness"] = observable_response["data"][0]["meta"]["maliciousness"]
            result["type"] = observable_response["data"][0]["type"]
            result["value"] = observable_response["data"][0]["value"]
            result["id"] = str(observable_response["data"][0]["id"])
            result["source_name"] = ""

            for k in observable_response["data"][0]["sources"]:
                source_lookup_data = self.get_group_name(extract_uuid_from_url(k))
                result["source_name"] += str(source_lookup_data["type"]) + ": " + str(source_lookup_data["name"]) + "; "

            result["platform_link"] = self.baseurl + "/main/intel/all/browse/observable?tab=overview&id=" + result["id"]

            return result

        elif observable_response["count"] > 1:
            self.eiq_logging.info(f"Finding duplicates for observable:{value}, type:{type}, return first one")
            result = {}
            result["created"] = str(observable_response["data"][0]["created_at"])[:16]
            result["last_updated"] = str(observable_response["data"][0]["last_updated_at"])[:16]
            result["maliciousness"] = observable_response["data"][0]["meta"]["maliciousness"]
            result["type"] = observable_response["data"][0]["type"]
            result["value"] = observable_response["data"][0]["value"]
            result["id"] = str(observable_response["data"][0]["id"])
            result["source_name"] = ""

            for k in observable_response["data"][0]["sources"]:
                source_lookup_data = self.get_group_name(extract_uuid_from_url(k))
                result["source_name"] += str(source_lookup_data["type"]) + ": " + str(source_lookup_data["name"]) + "; "

            result["platform_link"] = self.baseurl + "/main/intel/all/browse/observable?tab=overview&id=" + result["id"]

            return result

        else:
            return None

    def get_all_observables(self):
        self.eiq_logging.info("Searching all Observable.")

        r = self.send_api_request("get", path=API_PATHS[self.eiq_api_version]["observable_search"])

        return json.loads(r.text)

    def get_taxonomy_dict(self):
        """Method returns dictionary with all the available taxonomy in Platform.

        Returns:
            Return dictionary with {taxonomy ids:taxonomy title}. Otherwise returns False.

        """
        self.eiq_logging.info("Get all the taxonomy titles from Platform.")

        r = self.send_api_request(
            "get",
            path=API_PATHS[self.eiq_api_version]["taxonomy_get"],
            params={"limit": 500},
        )

        taxonomy = json.loads(r.text)
        taxonomy_dict = {}

        for i in taxonomy["data"]:
            try:
                id = str(i["id"])
                name = i["name"]

                taxonomy_dict[id] = name
            except KeyError:
                continue

        if len(taxonomy_dict) > 0:
            return taxonomy_dict
        else:
            return False

    def get_entity_realtionships(self, source_id=None, target_id=None):
        self.eiq_logging.info(f"Get realtionshsip for entity. Source id: {source_id}, Destination id: {target_id}")
        params = {}

        if source_id:
            params["filter[data.source]"] = source_id
            direction = "source"
        elif target_id:
            params["filter[data.target]"] = target_id
            direction = "target"
        else:
            direction = ""
            demisto.debug(f"No source_id or target_id. {direction=}")

        r = self.send_api_request("get", path=API_PATHS[self.eiq_api_version]["relationships"], params=params)

        parsed_response = json.loads(r.text)
        result = []

        if len(parsed_response["data"]) > 0:
            for i in parsed_response["data"]:
                relation = {}
                if direction == "source":
                    r = self.send_api_request(
                        "get",
                        path=(
                            API_PATHS[self.eiq_api_version]["entity_search"]
                            + "/"
                            + str(extract_uuid_from_url(i["data"]["target"]))
                        ),
                    )
                elif direction == "target":
                    r = self.send_api_request(
                        "get",
                        path=(
                            API_PATHS[self.eiq_api_version]["entity_search"]
                            + "/"
                            + str(extract_uuid_from_url(i["data"]["source"]))
                        ),
                    )

                related_entity_parsed_response = json.loads(r.text)

                if self.eiq_api_version == "v1":
                    relation["relation_title"] = i["meta"].get("title")
                    relation["entity_type"] = related_entity_parsed_response["data"]["type"]
                elif self.eiq_api_version == "v2":
                    relation["relation_title"] = i["data"].get("key")
                    relation["entity_type"] = related_entity_parsed_response["data"]["data"]["type"]

                relation["entity_title"] = related_entity_parsed_response["data"]["data"]["title"]
                relation["entity_id"] = related_entity_parsed_response["data"]["id"]
                relation["observables_count"] = len(related_entity_parsed_response["data"]["observables"])
                result.append(relation)
        return result

    def get_entity_by_id(self, entity_id, observables_lookup=True, relationships_lookup=True):
        """Method lookups specific entity by Id.

        Args:
            entity_id: Requested entity Id.

        Returns:
            Return dictionary with entity details:
             {entity_title: value,
             entity_type: value,
             created_at: value,
             source_name: value,
             tags_list: [
                tag and taxonomy list ...
                ],
             relationships_list: [
                    {relationship_type: incoming/outgoing,
                    connected_node: id,
                    connected_node_type: value,
                    connected_node_type: value
                    }
                relationship list ...
                ],
             observables_list: [
                    {value: obs_value,
                    type: obs_type
                    },
                    ...
                ]
             }

            Otherwise returns False.

        """
        self.eiq_logging.info(f"Looking up Entity {entity_id}.")

        try:
            r = self.send_api_request(
                "get",
                path=API_PATHS[self.eiq_api_version]["entity_search"] + "/" + str(entity_id),
            )
            parsed_response = json.loads(r.text)

            if len(self.taxonomie_dict) == 0:
                self.taxonomie_dict = self.get_taxonomy_dict()

            result = {}

            result["entity_title"] = parsed_response["data"]["data"].get("title", "N/A")
            result["created_at"] = str(parsed_response["data"].get("created_at", "N/A"))[:16]
            source = self.get_group_name(extract_uuid_from_url(parsed_response["data"]["sources"][0]))
            result["source_name"] = source["type"] + ": " + source["name"]
            result["tags_list"] = []
            result["confidence"] = parsed_response["data"]["data"].get("confidence", "N/A")
            result["description"] = parsed_response["data"]["data"].get("description", "N/A")
            result["impact"] = parsed_response["data"]["data"].get("impact", "N/A")

            if self.eiq_api_version == "v1":
                result["entity_type"] = parsed_response["data"].get("type", "N/A")
            elif self.eiq_api_version == "v2":
                result["entity_type"] = parsed_response["data"]["data"].get("type", "N/A")

            try:
                for i in parsed_response["data"]["meta"]["tags"]:
                    result["tags_list"].append(i)
            except KeyError:
                pass

            try:
                for i in parsed_response["data"]["meta"]["taxonomies"]:
                    result["tags_list"].append(self.taxonomie_dict.get(taxonomie_id_from_url(i)))
            except KeyError:
                pass

            if observables_lookup:
                result["observables_list"] = []
                try:
                    for i in parsed_response["data"]["observables"]:
                        observable_data = self.get_observable_by_id(observable_id_from_url(i))
                        result["observables_list"].append(
                            {
                                "value": observable_data["data"]["value"],
                                "type": observable_data["data"]["type"],
                                "maliciousness": observable_data["data"]["meta"]["maliciousness"],
                            }
                        )
                except (KeyError, TypeError):
                    pass

            if relationships_lookup:
                entity_is_source_relationships = self.get_entity_realtionships(source_id=entity_id)
                entity_is_target_relationships = self.get_entity_realtionships(target_id=entity_id)
                result["relationships_list"] = []

                for i in entity_is_source_relationships:
                    result["relationships_list"].append(
                        {
                            "source_entity_title": result["entity_title"],
                            "source_entity_type": result["entity_type"],
                            "target_entity_title": i["entity_title"],
                            "target_entity_type": i["entity_type"],
                            "target_entity_id": i["entity_id"],
                            "target_entity_observables_count": i["observables_count"],
                            "relationship_title": i["relation_title"],
                        }
                    )

                for i in entity_is_target_relationships:
                    result["relationships_list"].append(
                        {
                            "source_entity_title": i["entity_title"],
                            "source_entity_type": i["entity_type"],
                            "target_entity_title": result["entity_title"],
                            "target_entity_type": result["entity_type"],
                            "source_entity_id": i["entity_id"],
                            "source_entity_observables_count": i["observables_count"],
                            "relationship_title": i["relation_title"],
                        }
                    )

        except Exception as e:
            if "Status code: 404" in str(e):
                return False
            else:
                return e

        return result

    def search_entity(self, entity_value=None, entity_type=None, observable_value=None):
        """Method search specific entity by specific search conditions.

        Note: search works with wildcards for entity value and with strict conditions for everything else.
            Also, it's recommended to use this method to lookup entity name based on the entity ID, because it doesnt
            return all the relationships.

            if you need to find specific entity - search by entity id
            if you need to find all the entities with specific observables extracted - search with observable values

        Args:
            entity_value: entity value to search. add " or * to make search wildcard or strict
            entity_type: value to search
            entity_id: entity id to search
            observable_value: observable value to search inside entity

        Returns:
            Return dictionary with all the entity details.
            Otherwise returns False.

        """
        self.eiq_logging.info(f"Searching Entity:{entity_value} with extracted observable:{observable_value}, type:{entity_type}")

        params = {}

        if entity_value is not None:
            if entity_value[0] == '"' and entity_value[-1] == '"':
                entity_value = entity_value[1:-1]
                entity_value = entity_value.replace('"', '\\"')
                entity_value = '"' + entity_value + '"'
            else:
                entity_value = entity_value.replace('"', '\\"')

            params["filter[data.title]"] = entity_value

        if observable_value is not None:
            observable_data = self.lookup_observable(value=observable_value)

            try:
                params["filter[observables]"] = observable_data["id"]
            except Exception:
                return False

        if entity_type is not None:
            params["filter[type]"] = entity_type

        r = self.send_api_request("get", path=API_PATHS[self.eiq_api_version]["entity_search"], params=params)

        search_response = json.loads(r.text)

        if len(search_response["data"]) > 0:
            parsed_result = []
            for entity in search_response["data"]:
                parsed_entity = self.get_entity_by_id(entity["id"])
                parsed_result.append(parsed_entity)

            return parsed_result
        else:
            return False

    def elastic_search(self, search_payload=None, latency_check=False, page_size=10):
        self.eiq_logging.info("Searching in elastic")

        r = self.send_api_request(
            "post",
            path=API_PATHS[self.eiq_api_version]["entity_search"] + "?size=" + str(page_size),
            data=search_payload,
        )

        search_response = json.loads(r.text)

        if len(search_response["hits"]["hits"]) > 0:
            if latency_check:
                search_response["latency"] = r.elapsed.total_seconds()
                return search_response

            else:
                return search_response["hits"]["hits"]
        else:
            return False

    def create_entity(
        self,
        observable_dict,
        source_group_name,
        entity_title,
        entity_description,
        entity_confidence="Medium",
        entity_tags=[],
        entity_type="eclecticiq-sighting",
        entity_impact_value="None",
    ):
        """Method creates entity in Platform.

        Args:
            observable_dict: list of dictionaries with observables to create. Format:
                [{
                observable_type: "value",
                observable_value: value,
                observable_maliciousness: high/medium/low,
                observable_classification: good/bad
                }]
            source_group_name: group name in Platform for Source. Case sensitive.
            entity_title: value
            entity_description: value
            entity_confidence: Low/Medium/High
            entity_tags: list of strings
            entity_type: type of entity. e.g. indicator, ttp, eclecticiq-sighting etc
            entity_impact_value: "None", "Unknown", "Low", "Medium", "High"

        Returns:
            Return created entity id if successful otherwise returns False.

        """
        self.eiq_logging.info(f"Creating Entity in EclecticIQ Platform. Type:{entity_type}, title:{entity_title}")

        group_id = self.get_source_group_uid(source_group_name)

        today = datetime.utcnow().date()

        today_begin = format_ts(datetime(today.year, today.month, today.day, 0, 0, 0))
        threat_start = format_ts(datetime.utcnow())

        observable_dict_to_add = []
        record: dict[str, Any] = {}

        for i in observable_dict:
            record = {}

            if entity_type == "eclecticiq-sighting":
                record["link_type"] = "sighted"
            else:
                record["link_type"] = "observed"

            if i.get("observable_maliciousness", "") in ["low", "medium", "high"]:
                record["confidence"] = i["observable_maliciousness"]

            if i.get("observable_classification", "") in ["bad", "good", "unknown"]:
                record["classification"] = i["observable_classification"]

            if i.get("observable_value", ""):
                record["value"] = i["observable_value"]
            else:
                continue

            if i.get("observable_type", "") in [
                "asn",
                "country",
                "cve",
                "domain",
                "email",
                "email-subject",
                "file",
                "handle",
                "hash-md5",
                "hash-sha1",
                "hash-sha256",
                "hash-sha512",
                "industry",
                "ipv4",
                "ipv6",
                "malware",
                "name",
                "organization",
                "port",
                "snort",
                "uri",
                "yara",
            ]:
                record["kind"] = i["observable_type"]
            else:
                continue

            observable_dict_to_add.append(record)

        entity = {
            "data": {
                "data": {
                    "confidence": {"type": "confidence", "value": entity_confidence},
                    "description": entity_description,
                    "description_structuring_format": "html",
                    "impact": {
                        "type": "statement",
                        "value": entity_impact_value,
                        "value_vocab": "{http://stix.mitre.org/default_vocabularies-1}HighMediumLowVocab-1.0",
                    },
                    "type": entity_type,
                    "title": entity_title,
                    "security_control": {
                        "type": "information-source",
                        "time": {
                            "type": "time",
                            "start_time": today_begin,
                            "start_time_precision": "second",
                        },
                    },
                },
                "meta": {
                    "manual_extracts": observable_dict_to_add,
                    "taxonomy": [],
                    "estimated_threat_start_time": threat_start,
                    "tags": entity_tags,
                    "ingest_time": threat_start,
                },
                "sources": [{"source_id": group_id}],
            }
        }

        r = self.send_api_request("post", path=API_PATHS[self.eiq_api_version]["entities"], data=entity)

        entity_response = json.loads(r.text)

        try:
            return entity_response["data"]["id"]
        except KeyError:
            return False

    def get_observable(self, observable):
        self.eiq_logging.info(f"EclecticIQ_api: Searching for Observable: {observable}")
        path = API_PATHS[self.eiq_api_version]["observables"] + "?q=extracts.value:" + observable
        r = self.send_api_request("get", path=path)
        return r.json()


""" HELPER FUNCTIONS """


def maliciousness_to_dbotscore(maliciousness, threshold):
    """

    Translates EclecticIQ obversable maliciousness confidence level to DBotScore based on given threshold

    Parameters
    ----------
    maliciousness : str
        EclecticIQ obversable maliciousness confidence level.
    threshold : str
        Minimum maliciousness confidence level to consider the IOC malicious.

    Returns
    -------
    number
        Translated DBot Score

    """
    maliciousness_list = ["unknown", "safe", "low", "medium", "high"]

    maliciousness_dictionary = {
        "unknown": 0,
        "safe": 1,
        "low": 2,
        "medium": 2,
        "high": 3,
    }

    for i in maliciousness_list[maliciousness_list.index(threshold) :]:
        maliciousness_dictionary[i] = 3

    return maliciousness_dictionary[maliciousness]


""" COMMANDS + REQUESTS FUNCTIONS """


def test_module(eiq_api):
    """
    The function which runs when clicking on Test in integration settings
    Returns
    -------
    str
        ok if getting observable successfully

    """
    try:
        eiq_api.lookup_observable("123.123.123.123", "ipv4")
    except Exception as exception:
        if "Unauthorized" in str(exception) or "authentication" in str(exception):
            return "Authorization Error: make sure API Credentials are correctly set"

        if "connection" in str(exception):
            return "Connection Error: make sure Server URL is correctly set"
        raise exception

    return "ok"


def ip_command(eiq_api):
    """
    Gets reputation of an EclecticIQ IPv4 observable
    Parameters
    ----------
    ip : str
        IPv4 to get reputation of
    Returns
    -------
    entry
        Reputation of given IPv4
    """
    observable_value = demisto.args()["ip"]
    response_eiq = eiq_api.lookup_observable(observable_value, "ipv4")
    ip_result = parse_reputation_results(response_eiq, observable_value, "ip", IP_THRESHOLD, "IP")

    if SIGHTINGS_AUTO_CREATE:
        observable_dict = [
            {
                "observable_type": response_eiq["type"],
                "observable_value": observable_value,
                "observable_maliciousness": "medium",
                "observable_classification": "bad",
            }
        ]

        eiq_api.create_entity(
            observable_dict=observable_dict,
            source_group_name=GROUP_NAME,
            entity_title="XSOAR automatic Sighting for " + observable_value,
            entity_description="",
        )

    return ip_result


def parse_reputation_results(
    response_eiq,
    observable_value,
    demisto_observable_type,
    observable_threshold,
    demisto_observable_alias,
):
    command_results = []
    indicator: Any = None
    if response_eiq is not None:
        score = 0
        maliciousness = response_eiq.get("maliciousness")
        score = maliciousness_to_dbotscore(maliciousness, observable_threshold)

        if demisto_observable_type == "ip":
            indicator = Common.IP(
                ip=observable_value,
                dbot_score=Common.DBotScore(
                    observable_value,
                    DBotScoreType.IP,
                    "EclecticIQ",
                    score,
                    "EclecticIQ maliciousness confidence level: " + maliciousness,
                ),
            )
            prefix = "EclecticIQ.IP"

        elif demisto_observable_type == "url":
            indicator = Common.URL(
                url=observable_value,
                dbot_score=Common.DBotScore(
                    observable_value,
                    DBotScoreType.URL,
                    "EclecticIQ",
                    score,
                    "EclecticIQ maliciousness confidence level: " + maliciousness,
                ),
            )
            prefix = "EclecticIQ.URL"

        elif demisto_observable_type == "domain":
            indicator = Common.Domain(
                domain=observable_value,
                dbot_score=Common.DBotScore(
                    observable_value,
                    DBotScoreType.DOMAIN,
                    "EclecticIQ",
                    score,
                    "EclecticIQ maliciousness confidence level: " + maliciousness,
                ),
            )
            prefix = "EclecticIQ.Domain"

        elif demisto_observable_type == "file" and bool(re.search(r"\w{40}", observable_value)):  # SHA1
            indicator = Common.File(
                sha1=observable_value,
                dbot_score=Common.DBotScore(
                    observable_value,
                    DBotScoreType.FILE,
                    "EclecticIQ",
                    score,
                    "EclecticIQ maliciousness confidence level: " + maliciousness,
                ),
            )
            prefix = "EclecticIQ.File"

        elif demisto_observable_type == "file" and bool(re.search(r"\w{64}", observable_value)):  # SHA256
            indicator = Common.File(
                sha256=observable_value,
                dbot_score=Common.DBotScore(
                    observable_value,
                    DBotScoreType.FILE,
                    "EclecticIQ",
                    score,
                    "EclecticIQ maliciousness confidence level: " + maliciousness,
                ),
            )
            prefix = "EclecticIQ.File"

        elif demisto_observable_type == "file" and bool(re.search(r"\w{32}", observable_value)):  # MD5
            indicator = Common.File(
                md5=observable_value,
                dbot_score=Common.DBotScore(
                    observable_value,
                    DBotScoreType.FILE,
                    "EclecticIQ",
                    score,
                    "EclecticIQ maliciousness confidence level: " + maliciousness,
                ),
            )
            prefix = "EclecticIQ.File"

        elif demisto_observable_type == "file" and bool(re.search(r"\w{128}", observable_value)):  # SHA512
            indicator = Common.File(
                sha512=observable_value,
                dbot_score=Common.DBotScore(
                    observable_value,
                    DBotScoreType.FILE,
                    "EclecticIQ",
                    score,
                    "EclecticIQ maliciousness confidence level: " + maliciousness,
                ),
            )
            prefix = "EclecticIQ.File"

        elif demisto_observable_type == "email":
            indicator = Common.EMAIL(
                address=observable_value,
                dbot_score=Common.DBotScore(
                    observable_value,
                    DBotScoreType.EMAIL,
                    "EclecticIQ",
                    score,
                    "EclecticIQ maliciousness confidence level: " + maliciousness,
                ),
            )
            prefix = "EclecticIQ.Email"
        else:
            prefix = ""
            demisto.debug(f"{demisto_observable_type=} -> {prefix=}")

        raw_result = response_eiq

        entry_context = {
            "Observable": observable_value,
            "Created": response_eiq.get("created"),
            "LastUpdated": response_eiq.get("last_updated"),
            "SourceName": response_eiq.get("source_name"),
            "Maliciousness": maliciousness,
        }

        outputs_key_field = "Observable"

        human_readable_title = "EclecticIQ " + demisto_observable_alias + f" reputation - {observable_value}"
        human_readable = tableToMarkdown(human_readable_title, response_eiq)

        command_results.append(
            CommandResults(
                readable_output=human_readable,
                raw_response=raw_result,
                outputs_prefix=prefix,
                outputs=entry_context,
                outputs_key_field=outputs_key_field,
                indicator=indicator,
            )
        )

    else:
        human_readable = "### Observable: " + str(observable_value) + " not found in EclecticIQ IC."
        raw_result = {
            "result": "Observable not found in EclecticIQ IC.",
            "observable": str(observable_value),
        }

        if demisto_observable_type == "ip":
            prefix = "EclecticIQ.IP"
        elif demisto_observable_type == "url":
            prefix = "EclecticIQ.URL"
        elif demisto_observable_type == "domain":
            prefix = "EclecticIQ.Domain"
        elif demisto_observable_type == "file" and bool(re.search(r"\w{40}", observable_value)):  # SHA1
            prefix = "EclecticIQ.File"
        elif demisto_observable_type == "file" and bool(re.search(r"\w{64}", observable_value)):  # SHA256
            prefix = "EclecticIQ.File"
        elif demisto_observable_type == "file" and bool(re.search(r"\w{32}", observable_value)):  # MD5
            prefix = "EclecticIQ.File"
        elif demisto_observable_type == "file" and bool(re.search(r"\w{128}", observable_value)):  # SHA512
            prefix = "EclecticIQ.File"
        elif demisto_observable_type == "email":
            prefix = "EclecticIQ.Email"

        command_results.append(CommandResults(readable_output=human_readable, raw_response=raw_result))

    return command_results


def url_command(eiq_api):
    """
    Gets reputation of an EclecticIQ URI observable
    Parameters
    ----------
    url : str
        URL to get reputation of
    Returns
    -------
    entry
        Reputation of given URL
    """
    observable_value = demisto.args()["url"]
    response_eiq = eiq_api.lookup_observable(observable_value, "uri")
    url_result = parse_reputation_results(response_eiq, observable_value, "url", URL_THRESHOLD, "URL")

    if SIGHTINGS_AUTO_CREATE:
        observable_dict = [
            {
                "observable_type": response_eiq["type"],
                "observable_value": observable_value,
                "observable_maliciousness": "medium",
                "observable_classification": "bad",
            }
        ]

        eiq_api.create_entity(
            observable_dict=observable_dict,
            source_group_name=GROUP_NAME,
            entity_title="XSOAR automatic Sighting for " + observable_value,
            entity_description="",
        )

    return url_result


def file_command(eiq_api):
    """
    Gets reputation of an EclecticIQ hash observable
    Parameters
    ----------
    file : str
        File hash to get reputation of
    Returns
    -------
    entry
        Reputation of given file hash
    """

    observable_value = demisto.args()["file"]
    response_eiq = eiq_api.lookup_observable(observable_value, ["hash-md5", "hash-sha1", "hash-sha256", "hash-sha512"])
    file_result = parse_reputation_results(response_eiq, observable_value, "file", FILE_THRESHOLD, "File")

    if SIGHTINGS_AUTO_CREATE:
        observable_dict = [
            {
                "observable_type": response_eiq["type"],
                "observable_value": observable_value,
                "observable_maliciousness": "medium",
                "observable_classification": "bad",
            }
        ]

        eiq_api.create_entity(
            observable_dict=observable_dict,
            source_group_name=GROUP_NAME,
            entity_title="XSOAR automatic Sighting for " + observable_value,
            entity_description="",
        )

    return file_result


def email_command(eiq_api):
    """
    Gets reputation of an EclecticIQ email address observable
    Parameters
    ----------
    email : str
        Email address to get reputation of
    Returns
    -------
    entry
        Reputation of given email address
    """

    observable_value = demisto.args()["email"]
    response_eiq = eiq_api.lookup_observable(observable_value, "email")
    email_result = parse_reputation_results(response_eiq, observable_value, "email", EMAIL_THRESHOLD, "Email")

    if SIGHTINGS_AUTO_CREATE:
        observable_dict = [
            {
                "observable_type": response_eiq["type"],
                "observable_value": observable_value,
                "observable_maliciousness": "medium",
                "observable_classification": "bad",
            }
        ]

        eiq_api.create_entity(
            observable_dict=observable_dict,
            source_group_name=GROUP_NAME,
            entity_title="XSOAR automatic Sighting for " + observable_value,
            entity_description="",
        )

    return email_result


def domain_command(eiq_api):
    """
    Gets reputation of an EclecticIQ domain observable
    Parameters
    ----------
    domain : str
        Domain address to get reputation of
    Returns
    -------
    entry
        Reputation of given domain address
    """
    observable_value = demisto.args()["domain"]
    response_eiq = eiq_api.lookup_observable(observable_value, "domain")
    domain_result = parse_reputation_results(response_eiq, observable_value, "domain", DOMAIN_THRESHOLD, "Domain")

    if SIGHTINGS_AUTO_CREATE:
        observable_dict = [
            {
                "observable_type": response_eiq["type"],
                "observable_value": observable_value,
                "observable_maliciousness": "medium",
                "observable_classification": "bad",
            }
        ]

        eiq_api.create_entity(
            observable_dict=observable_dict,
            source_group_name=GROUP_NAME,
            entity_title="XSOAR automatic Sighting for " + observable_value,
            entity_description="",
        )

    return domain_result


def get_entity(eiq_api):
    observable_value = demisto.args().get("observable_value", None)
    entity_value = demisto.args().get("entity_title", None)
    entity_type = demisto.args()["entity_type"]

    if entity_type == "all":
        entity_type = None

    query_result = eiq_api.search_entity(
        entity_value=entity_value,
        entity_type=entity_type,
        observable_value=observable_value,
    )

    if (type(query_result) is dict) or (type(query_result) is list):
        output_result = []
        for entity in query_result:
            record = entity

            record["observables_output"] = str(entity.get("observables_list"))
            record["relationships_output"] = str(entity.get("relationships_list"))
            output_result.append(record)

        total_count = len(query_result)
        human_readable_title = "Total " + str(total_count) + " entities found in EclecticIQ Intelligence Center."

        human_readable = tableToMarkdown(human_readable_title, output_result)

        return CommandResults(
            readable_output=human_readable,
            raw_response=query_result,
            outputs_prefix="EclecticIQ.Entity",
            outputs=output_result,
            outputs_key_field="entity_title",
        )

    return "No entities found in EclecticIQ Intelligence Center."


def get_entity_by_id(eiq_api):
    entity_id = demisto.args().get("entity_id", None)
    query_result = eiq_api.get_entity_by_id(entity_id)

    if type(query_result).__name__ == "Exception" and "Status code:404" in str(query_result):
        return "No entities found in EclecticIQ Platform."

    elif (type(query_result) is dict) or (type(query_result) is list):
        human_readable_title = "Entities found in EclecticIQ Intelligence Center."
        human_readable = tableToMarkdown(human_readable_title, query_result)

        return CommandResults(
            readable_output=human_readable,
            raw_response=query_result,
            outputs_prefix="EclecticIQ.EntityById",
            outputs=query_result,
            outputs_key_field="entity_title",
        )

    return "No entities found in EclecticIQ Platform."


def create_sighting(eiq_api):
    args = demisto.args()
    observable_value = args.get("observable_value", "")
    observable_type = args.get("observable_type", "")
    observable_maliciousness = args.get("observable_maliciousness", "")
    sighting_title = args.get("sighting_title", "")
    sighting_description = args.get("sighting_description", "")
    sighting_confidence = args.get("sighting_confidence", "")
    sighting_impact = args.get("sighting_impact", "")
    sighting_tag = argToList(args.get("sighting_tag"))

    if sighting_tag == [""]:
        del sighting_tag[:]

    observable_record = {}

    observable_record = convert_maliciousness(observable_maliciousness)
    observable_record["observable_type"] = observable_type
    observable_record["observable_value"] = observable_value

    observable_list = []
    observable_list.append(observable_record)

    sighting_id = eiq_api.create_entity(
        observable_dict=observable_list,
        source_group_name=GROUP_NAME,
        entity_title=sighting_title,
        entity_description=sighting_description,
        entity_confidence=sighting_confidence,
        entity_tags=sighting_tag,
        entity_impact_value=sighting_impact,
    )

    raw_result = {
        "entity_id": sighting_id,
        "sighting_details": {
            "sighting_title": sighting_title,
            "observable_value": observable_value,
            "observable_type": observable_type,
            "observable_maliciousness": observable_maliciousness,
        },
    }

    entry_context = {
        "SightingId": sighting_id,
        "SightingDetails": {
            "SightingTitle": sighting_title,
            "ObservableValue": observable_value,
            "ObservableType": observable_type,
            "ObservableMaliciousness": observable_maliciousness,
        },
    }

    human_readable_title = f'EclecticIQ Sighting Created, Entity ID - {raw_result["entity_id"]}'
    human_readable = tableToMarkdown(human_readable_title, raw_result)

    return CommandResults(
        readable_output=human_readable,
        raw_response=raw_result,
        outputs_prefix="EclecticIQ.Sightings",
        outputs=entry_context,
        outputs_key_field="SightingId",
    )


def convert_maliciousness(observable_dict):
    maliciousness_to_meta = {
        "Malicious (High confidence)": {
            "classification": "bad",
            "confidence": "high",
        },
        "Malicious (Medium confidence)": {
            "classification": "bad",
            "confidence": "medium",
        },
        "Malicious (Low confidence)": {
            "classification": "bad",
            "confidence": "low",
        },
        "Safe": {
            "classification": "good",
        },
        "Unknown": {},
    }

    record = {}
    meta_data = maliciousness_to_meta[observable_dict]
    record["observable_maliciousness"] = meta_data.get("confidence", "")  # type: ignore[attr-defined]
    record["observable_classification"] = meta_data.get("classification", "")  # type: ignore[attr-defined]

    return record


def prepare_entity_observables(observable1value, observable1type, observable1malicousness, observable_dict):
    """Method duplicate _prepare_observables method with difference in params names.
    Been added for backward compatibility.

    """

    maliciousness_to_meta = {
        "Malicious (High confidence)": {
            "classification": "bad",
            "confidence": "high",
        },
        "Malicious (Medium confidence)": {
            "classification": "bad",
            "confidence": "medium",
        },
        "Malicious (Low confidence)": {
            "classification": "bad",
            "confidence": "low",
        },
        "Safe": {
            "classification": "good",
        },
        "Unknown": {},
    }

    maliciousness_to_meta_dict = {
        "high": {
            "classification": "bad",
            "confidence": "high",
        },
        "medium": {
            "classification": "bad",
            "confidence": "medium",
        },
        "low": {
            "classification": "bad",
            "confidence": "low",
        },
        "safe": {
            "classification": "good",
        },
        "unknown": {},
    }

    result = []

    record = {"observable_type": observable1type, "observable_value": observable1value}

    meta_data = maliciousness_to_meta[observable1malicousness]
    record["observable_maliciousness"] = meta_data.get("confidence", "")  # type: ignore[attr-defined]
    record["observable_classification"] = meta_data.get("classification", "")  # type: ignore[attr-defined]

    result.append(record)

    if observable_dict:
        observable_dict = json.loads(observable_dict)

        for observable in observable_dict:
            record = {
                "observable_type": observable["type"],
                "observable_value": observable["value"],
            }
            meta_data = maliciousness_to_meta_dict[observable["maliciousness"]]
            record["observable_maliciousness"] = meta_data.get("confidence", "")  # type: ignore[attr-defined]
            record["observable_classification"] = meta_data.get("classification", "")  # type: ignore[attr-defined]

            result.append(record)

    return result


def create_indicator(eiq_api):
    args = demisto.args()
    observable_value = args.get("observable_value", "")
    observable_type = args.get("observable_type", "")
    observable_maliciousness = args.get("observable_maliciousness", "")
    indicator_title = args.get("indicator_title", "")
    indicator_description = args.get("indicator_description", "")
    indicator_confidence = args.get("indicator_confidence", "")
    indicator_impact = args.get("indicator_impact", "")
    indicator_tag = argToList(args.get("indicator_tag"))
    observable_dictionary = args.get("observable_dictionary", {})

    if indicator_tag == [""]:
        del indicator_tag[:]

    if observable_dictionary == "":
        observable_dictionary = {}

    observable_list = []
    observable_list = prepare_entity_observables(
        observable_value,
        observable_type,
        observable_maliciousness,
        observable_dictionary,
    )

    indicator_id = eiq_api.create_entity(
        observable_dict=observable_list,
        source_group_name=GROUP_NAME,
        entity_title=indicator_title,
        entity_description=indicator_description,
        entity_confidence=indicator_confidence,
        entity_tags=indicator_tag,
        entity_impact_value=indicator_impact,
        entity_type="indicator",
    )

    raw_result = {
        "entity_id": indicator_id,
        "indicator_title": indicator_title,
        "observables_list": observable_list,
    }

    entry_context = {
        "IndicatorId": indicator_id,
        "IndicatorTitle": indicator_title,
        "ObservablesList": observable_list,
    }

    human_readable_title = "EclecticIQ Indicator Created, Entity ID - {}".format(raw_result["entity_id"])
    human_readable = tableToMarkdown(human_readable_title, raw_result)

    return CommandResults(
        readable_output=human_readable,
        raw_response=raw_result,
        outputs_prefix="EclecticIQ.Indicators",
        outputs=entry_context,
        outputs_key_field="IndicatorId",
    )


def get_indicators(eiq_api):
    feed_ids = demisto.params().get("feedId", "")

    if len(feed_ids) > 0:
        feeds_info = eiq_api.get_feed_info(feed_ids)
        indicators_to_add: list[dict] = []

        for item in feeds_info:
            item["id"] = str(item["id"])

            demisto.debug("Feed id {} is starting to fetch.".format(item["id"]))
            blocks = eiq_api.get_feed_content_blocks(item)

            for block in blocks:
                demisto.debug("Feed id={} preparing data to ingest block {}.".format(str(item["id"]), block))
                data_from_block = eiq_api.download_block_list(block)
                indicators_to_add = indicators_to_add + export_csv_to_indicators_get(item["id"], data_from_block)
                break

        human_readable = tableToMarkdown(
            "Indicators collected from first block of feed:" + str(feeds_info),
            indicators_to_add,
        )

        return CommandResults(readable_output=human_readable)
    else:
        human_readable = tableToMarkdown("Feed ID to fetch is not configured in the Integreation settings.", {})
        return CommandResults(readable_output=human_readable)


def export_csv_to_indicators_get(feed_id, text, flag=False):
    demisto.info(f"Getting Indicators from feed #{feed_id!s}")
    text = io.StringIO(text)
    csvreader = csv.DictReader(text, delimiter=",")

    indicators_to_add = []

    indicator_types_mapper = {
        "cve": "CVE",
        "domain": "Domain",
        "email": "Email",
        "hash-md5": "File MD5",
        "hash-sha1": "File SHA-1",
        "hash-sha256": "File SHA-256",
        "hash-sha512": "File SHA-512",
        "ipv4": "IP",
        "ipv4-cidr": "CIDR",
        "ipv6": "IPv6",
        "uri": "URL",
    }

    maliciousness_dictionary = {
        "": 0,
        "unknown": 0,
        "safe": 1,
        "low": 2,
        "medium": 2,
        "high": 3,
    }

    if "diff" not in csvreader.fieldnames:  # type: ignore[operator]
        demisto.info("Update method is 'replace' or 'append'.")
        # If there is no "diff" column in the CSV
        # So the update method is set to "replace", this means we
        # delete everything from this feed and then recreate it.
        for row in csvreader:
            if row["type"] in indicator_types_mapper:
                indicator_obj = {
                    "value": row["value"],
                    "type": indicator_types_mapper[row["type"]],
                    "eclecticiqentityid": row["entity.id"],
                    "eclecticiqentitytitle": row["entity.title"],
                    "eclecticiqentitytype": row["entity.type"],
                    "eclecticiqfeedid": str(feed_id),
                    "eclecticiqsource": row["source.names"],
                    "eclecticiqentitydescription": row.get("entity.description", ""),
                    "score": maliciousness_dictionary[row["meta.confidence"]],
                }

                indicators_to_add.append(indicator_obj)

    return indicators_to_add


def fetch_indicators(eiq_api):
    feed_ids = demisto.params().get("feedId", "")

    if len(feed_ids) > 0:
        feeds_info = eiq_api.get_feed_info(feed_ids)
        context = demisto.getLastRun()

        for item in feeds_info:
            item["id"] = str(item["id"])

            demisto.debug("Feed id {} is starting to fetch.".format(item["id"]))

            flag = False
            state = context.get(
                item["id"],
                {
                    "created_at": None,
                    "feed_name": None,
                    "update_strategy": None,
                    "last_ingested": None,
                },
            )

            if not state["created_at"] or item["created_at"] != state["created_at"]:
                # check created_at field between platform and IBM
                # if they are different
                # delete all from this feed and ingest again
                flag = True

            if item["update_strategy"] == "REPLACE":
                flag = True

            if flag is True:
                context = demisto.getLastRun()
                context[item["id"]] = {
                    "created_at": item["created_at"],
                    "feed_name": item["name"],
                    "update_strategy": item["update_strategy"],
                    "last_ingested": None,
                }
                demisto.setLastRun(context)
                blocks = eiq_api.get_feed_content_blocks(item, state)
            else:
                state = context[item["id"]]
                blocks = eiq_api.get_feed_content_blocks(item, state)

            demisto.info("Starting Ingestion of feed #{}".format(item["id"]))

            indicators_to_add: list[dict] = []

            for block in blocks:
                demisto.debug("Feed id={} preparing data to ingest block {}.".format(str(item["id"]), block))
                data_from_block = eiq_api.download_block_list(block)
                indicators_to_add = indicators_to_add + export_csv_to_indicators(item["id"], data_from_block, flag)

                flag = False
                context[item["id"]]["last_ingested"] = block
                demisto.setLastRun(context)

            demisto.info("Feed id={} was fully ingested/updated.".format(str(item["id"])))

            return indicators_to_add
        return []
    else:
        demisto.error("Fetching enabled but Feed IDs not configured.")
        return []


def export_csv_to_indicators(feed_id, text, flag=False):
    demisto.info(f"Exporting to Indicators feed #{feed_id!s}")
    text = io.StringIO(text)
    csvreader = csv.DictReader(text, delimiter=",")

    indicators_to_add = []

    indicator_types_mapper = {
        "cve": "CVE",
        "domain": "Domain",
        "email": "Email",
        "hash-md5": "File MD5",
        "hash-sha1": "File SHA-1",
        "hash-sha256": "File SHA-256",
        "hash-sha512": "File SHA-512",
        "ipv4": "IP",
        "ipv4-cidr": "CIDR",
        "ipv6": "IPv6",
        "uri": "URL",
    }

    maliciousness_dictionary = {
        "": 0,
        "unknown": 0,
        "safe": 1,
        "low": 2,
        "medium": 2,
        "high": 3,
    }

    if "diff" not in csvreader.fieldnames:  # type: ignore[operator]
        demisto.info("Update method is 'replace' or 'append' so check for changes and update.")
        # If there is no "diff" column in the CSV
        # So the update method is set to "replace", this means we
        # delete everything from this feed and then recreate it.
        for row in csvreader:
            if row["type"] in indicator_types_mapper:
                raw_json = {}
                raw_json = row

                indicator_obj = {
                    "value": row["value"],
                    "type": indicator_types_mapper[row["type"]],
                    "rawJSON": raw_json,
                    "fields": {
                        "eclecticiqentityid": row["entity.id"],
                        "eclecticiqentitytitle": row["entity.title"],
                        "eclecticiqentitytype": row["entity.type"],
                        "eclecticiqfeedid": str(feed_id),
                        "eclecticiqsource": row["source.names"],
                        "eclecticiqentitydescription": row.get("entity.description", ""),
                    },
                    "score": maliciousness_dictionary[row["meta.confidence"]],
                }

                indicators_to_add.append(indicator_obj)

    return indicators_to_add


def request_get(eiq_api):
    uri = demisto.args().get("uri", "")

    raw_response = eiq_api.send_api_request("get", uri)
    entry_context = {}
    entry_context["URI"] = uri
    entry_context["ReplyStatus"] = str(raw_response.status_code)
    entry_context["ReplyBody"] = raw_response.json()

    human_readable_title = (
        "### EclecticIQ GET action to endpoint " + uri + " exectued. Reply status: " + str(raw_response.status_code)
    )

    return CommandResults(
        readable_output=human_readable_title,
        raw_response=raw_response.json(),
        outputs_prefix="EclecticIQ.GET",
        outputs=entry_context,
        outputs_key_field="URI",
    )


def request_post(eiq_api):
    uri = demisto.args().get("uri", "")
    body = json.loads(demisto.args().get("body", "{}"))

    raw_response = eiq_api.send_api_request("post", uri, data=body)
    entry_context = {}
    entry_context["URI"] = uri
    entry_context["ReplyStatus"] = str(raw_response.status_code)
    entry_context["ReplyBody"] = raw_response.json()

    human_readable_title = f"### EclecticIQ POST action to endpoint {uri} exectued. Reply status: {raw_response.status_code}"

    return CommandResults(
        readable_output=human_readable_title,
        raw_response=raw_response.json(),
        outputs_prefix="EclecticIQ.POST",
        outputs=entry_context,
        outputs_key_field="URI",
    )


def request_put(eiq_api):
    uri = demisto.args().get("uri", "")
    body = json.loads(demisto.args().get("body", "{}"))

    raw_response = eiq_api.send_api_request("put", uri, data=body)
    entry_context = {
        "URI": uri,
        "ReplyStatus": str(raw_response.status_code),
        "ReplyBody": raw_response.json(),
    }

    human_readable_title = f"### EclecticIQ PUT action to endpoint {uri} exectued. Reply status: {raw_response.status_code}"

    return CommandResults(
        readable_output=human_readable_title,
        raw_response=raw_response.json(),
        outputs_prefix="EclecticIQ.PUT",
        outputs=entry_context,
        outputs_key_field="URI",
    )


def request_patch(eiq_api):
    uri = demisto.args().get("uri", "")
    body = json.loads(demisto.args().get("body", "{}"))

    raw_response = eiq_api.send_api_request("patch", uri, data=body)
    entry_context = {
        "URI": uri,
        "ReplyStatus": str(raw_response.status_code),
        "ReplyBody": raw_response.json(),
    }

    human_readable_title = f"### EclecticIQ PATCH action to endpoint {uri} exectued. Reply status: {raw_response.status_code}"

    return CommandResults(
        readable_output=human_readable_title,
        raw_response=raw_response.json(),
        outputs_prefix="EclecticIQ.PATCH",
        outputs=entry_context,
        outputs_key_field="URI",
    )


def request_delete(eiq_api):
    uri = demisto.args().get("uri", "")

    raw_response = eiq_api.send_api_request("delete", uri)
    entry_context = {}
    entry_context["URI"] = uri
    entry_context["ReplyStatus"] = str(raw_response.status_code)

    human_readable_title = f"### EclecticIQ DELETE action to endpoint {uri} exectued. Reply status: {raw_response.status_code}"

    return CommandResults(
        readable_output=human_readable_title,
        raw_response=entry_context,
        outputs_prefix="EclecticIQ.DELETE",
        outputs=entry_context,
        outputs_key_field="URI",
    )


""" COMMANDS MANAGER / SWITCH PANEL """


def main():
    COMMANDS = {
        "test-module": test_module,
        "url": url_command,
        "ip": ip_command,
        "email": email_command,
        "file": file_command,
        "domain": domain_command,
        "fetch-indicators": fetch_indicators,
        "eclecticiq-get-entity": get_entity,
        "eclecticiq-get-entity-by-id": get_entity_by_id,
        "eclecticiq-create-sighting": create_sighting,
        "eclecticiq-create-indicator": create_indicator,
        "eclecticiq-get-indicators": get_indicators,
        "eclecticiq-request-get": request_get,
        "eclecticiq-request-post": request_post,
        "eclecticiq-request-delete": request_delete,
        "eclecticiq-request-put": request_put,
        "eclecticiq-request-patch": request_patch,
    }

    if not demisto.params().get("proxy", False):
        skip_proxy()

    try:
        eiq = EclecticIQ_api(  # noqa: F841
            baseurl=SERVER,
            eiq_api_version=API_VERSION,
            username="",
            password=PASSWORD,
            verify_ssl=USE_SSL,
        )

        LOG(f"Command being called is {demisto.command()}")
        command = demisto.command()
        command_func = COMMANDS.get(command)

        if command == "fetch-indicators":
            indicators_to_add = fetch_indicators(eiq)
            for b in batch(indicators_to_add, batch_size=500):
                demisto.createIndicators(b)
        elif command is not None:
            return_results(command_func(eiq))  # type: ignore[misc]

    except Exception as e:
        return_error(f"Error has occurred in EclecticIQ integration: {e!s}.")


if __name__ == "__builtin__" or __name__ == "builtins":
    main()