Symantec Endpoint Protection V2

Query the Symantec Endpoint Protection Manager using the official REST API.

Endpoint · Symantec Endpoint Protection

Details

IDSymantec Endpoint Protection V2
ProviderBroadcom
CategoryEndpoint
From Version5.0.0
Docker Imagedemisto/python3:3.12.13.10116658
Supported ModulesAgentix XSIAM

README

Query the Symantec Endpoint Protection Manager using the official REST API.

Use Cases

  • Scan/Quarantine/content-update an endpoint.
  • Assign policy to an endpoint.
  • Move client to different group.

Unsupported use cases in the API:

  • Get scan results
  • Get reports/logs
  • Receive system alerts

Required Permissions

The following role is required to use the Symantec Endpoint Protection API:

  • sysadmin
    Note: An Administrator role does not have enough permissions for this integration. A System Administrator (sysadmin) role is required.

Configure Symantec Endpoint Protection V2 in Cortex

Parameter Required
Server (e.g., https://1.2.3.4:8446) True
Authentication True
Password True
SEPM domain for the user False
Trust any certificate (not secure) False
Use system proxy settings False
Local time zone (e.g., +02:30,-06:00) 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.

sep-endpoints-info


Returns information about endpoints.

Base Command

sep-endpoints-info

Input

Argument Name Description Required
columns A CSV list of the displayed columns. Optional
computerName Filters by the host name of the computer. A wild card search can be done using ‘*’ at the end of the query. Optional
lastUpdate Indicates when a computer’s status was last updated. The default is “0”, which returns all results. Default is 0. Optional
os The operating system by which to filter. Possible values are: CentOs, Debian, Fedora, MacOSX, Oracle, OSX, RedHat, SUSE, Ubuntu, Win10, Win2K, Win7, Win8, WinEmb7, WinEmb8, WinEmb81, WinFundamental, WinNT, Win2K3, Win2K8, Win2K8R2, WinVista, WinXP, WinXPEmb, WinXPProf64. Optional
pageSize The number of results to include on each page. The default is 20. Optional
groupName The name of the group to which the endpoint belongs. A wild card search can be done using ‘*’ at the end of the query. Optional

Context Output

Path Type Description
SEPM.Endpoint.Hostname String The hostname of the endpoint.
SEPM.Endpoint.Domain String The domain of the endpoint.
SEPM.Endpoint.IPAddresses String The IP addresses of the endpoint.
SEPM.Endpoint.OS String The OS information of the endpoint.
SEPM.Endpoint.Description String The description of the endpoint.
SEPM.Endpoint.MACAddresses String The MAC address of the endpoint.
SEPM.Endpoint.BIOSVersion String The BIOS version of the endpoint.
SEPM.Endpoint.DHCPServer String The DHCP server address of the endpoint.
SEPM.Endpoint.HardwareKey String The hardware key of the client to be moved.
SEPM.Endpoint.LastScanTime String The last scan time of the endpoint.
SEPM.Endpoint.RunningVersion String The running version of the endpoint.
SEPM.Endpoint.TargetVersion String The target version of the endpoint.
IP.Address String The IP address of the endpoint.
IP.Host String The IP host of the endpoint.
Endpoint.Hostname Unknown The hostname of the endpoint.
Endpoint.MACAddress Unknown The MAC address of the endpoint.
Endpoint.Domain Unknown The domain of the endpoint.
Endpoint.IPAddress Unknown The IP address of the endpoint.
Endpoint.DHCPServer Unknown The DHCP server of the endpoint.
Endpoint.OS String The OS of the endpoint.
Endpoint.OSVersion String The OS version of the endpoint.
Endpoint.BIOSVersion String The BIOS version of the endpoint.
Endpoint.Memory String The memory of the endpoint.
Endpoint.Processors String The processors that the endpoint uses.
IP.Hostname String The hostname that is mapped to this IP address.
SEPM.Endpoint.Group String The group of the endpoint.
SEPM.Endpoint.PatternIdx String The PatternIdx of the endpoint.
SEPM.Endpoint.OnlineStatus String The online status of the endpoint.
SEPM.Endpoint.UpdateTime String The update time of the endpoint.

Command Example

!sep-endpoints-info

Human Readable Output

Human_Readable_Output_1

sep-groups-info


Returns information about groups.

Base Command

sep-groups-info

Input

Argument Name Description Required
columns The column by which the results are sorted. Optional

Context Output

Path Type Description
SEPM.Groups Unknown The list of groups.
SEPM.Groups.created number The time of creation time (in Epoch).
SEPM.Groups.fullPathName string The name of the group.
SEPM.Groups.id string The ID of the group.
SEPM.Groups.numberOfPhysicalComputers number The number of physical computers in the group.
SEPM.Groups.numberOfRegisteredUsers number The number of registered users in the group.
SEPM.Groups.policyDate number The date of the policy (in Epoch).
SEPM.Groups.policySerialNumber number The serial number of the policy.

Command Example

!sep-groups-info

Human Readable Output

Human_Readable_Output_2

sep-system-info


Returns information about the system, such as version or AV definition.

Base Command

sep-system-info

Input

There are no input arguments for this command.

Context Output

Path Type Description
SEPM.ServerAVDefVersion string The version of the AV definition.

Command Example

!sep-system-info

Human Readable Output

Human_Readable_Output_3

sep-command-status


Retrieves the status of a command.

Base Command

sep-command-status

Input

Argument Name Description Required
commandId The ID of the command. Required

Context Output

Path Type Description
SEPM.LastCommand.CommandDetails string The details of the command.
SEPM.LastCommand.CommandId string The ID of the command.

Command Example

!sep-command-status commandId=04A68CA5952B4726AAFEB421E0EB436C

Human Readable Output

Human_Readable_Output_4

sep-client-content


Retrieves the content of the client.

Base Command

sep-client-content

Input

There are no input arguments for this command.

Context Output

Path Type Description
SEPM.ClientContentVersions string Displays the versions for each client.
SEPM.LastUpdated string The last update of a date.

Command Example

!sep-client-content

Human Readable Output

Human_Readable_Output_5

sep-list-policies


Retrieves a list of existing policies.

Base Command

sep-list-policies

Input

There are no input arguments for this command.

Context Output

Path Type Description
SEPM.PoliciesList.PolicyName string The name of the policy.
SEPM.PoliciesList.Type string The type of the policy.
SEPM.PoliciesList.ID string The ID of the policy.
SEPM.PoliciesList.Description string The description of the policy.
SEPM.PoliciesList.Enabled boolean Whether the list of polices is enabled. Enabled if “True”.
SEPM.PoliciesList.AssignedLocations.GroupID string The ID of the group of the locations assigned to this policy.
SEPM.PoliciesList.AssignedLocations.Locations string The list of location IDs assigned to this policy.
SEPM.PoliciesList.AssignedCloudGroups.GroupID string The ID of the cloud group of the locations assigned to this policy.
SEPM.PoliciesList.AssignedCloudGroups.Locations string The list of location IDs belonging to a cloud group assigned to this policy.

Command Example

!sep-list-policies

Human Readable Output

Human_Readable_Output_6

sep-assign-policy


Assigns an existing policy to a specified location.

Base Command

sep-assign-policy

Input

Argument Name Description Required
groupID The ID of the group to which the endpoint belongs. Required
locationID The ID of the location of the endpoint. Required
policyType The type of policy to be assigned. Required
policyID The ID of the policy to be assigned. Required

Context Output

There is no context output for this command.

Command Example

!sep-assign-policy groupID=44BE96AFC0A8010B0CFACB30929326C2 locationID=50FEEA3FC0A8010B739E49CB0C321A7E policyID=A00ADE188AA148D7AD319CBCA1FA2F23 policyType=hi

Human Readable Output

Human_Readable_Output_7

sep-list-locations


Retrieves a list of location IDs for a specified group.

Base Command

sep-list-locations

Input

Argument Name Description Required
groupID The group ID for which to list locations. Required

Context Output

Path Type Description
SEPM.Locations.ID Unknown The ID of the location.

Command Example

!sep-list-locations groupID=44BE96AFC0A8010B0CFACB30929326C2

Human Readable Output

Human_Readable_Output_8

sep-endpoint-quarantine


Quarantines an endpoint according to its policy.

Base Command

sep-endpoint-quarantine

Input

Argument Name Description Required
endpoint The IP or hostname of the endpoint. Required
actionType Adds or removes an endpoint from quarantine. Possible values are: Add, Remove. Required

Context Output

Path Type Description
SEPM.Quarantine.CommandID string The ID of the command that was run.
SEPM.Quarantine.Action string The type of the action type. Can be “Add” or “Remove”.
SEPM.Quarantine.Endpoint string The IP or hostname of the identifier of the endpoint.

Command Example

!sep-endpoint-quarantine actionType=add endpoint=demisto-PC

Human Readable Output

Human_Readable_Output_9

sep-scan-endpoint


Scans an endpoint.

Base Command

sep-scan-endpoint

Input

Argument Name Description Required
endpoint The IP address or hostname of the endpoint. Required
scanType The scan type of the endpoint. Can be “ScanNow_Quick”, “ScanNow_Full”, or “ScanNow_Custom”. Possible values are: ScanNow_Quick, ScanNow_Full, ScanNow_Custom. Required

Context Output

Path Type Description
SEPM.Scan.CommandID string The ID of the command that was run.
SEPM.Scan.Type string The type of the scan. Can be “ScanNow_Quick”, “ScanNow_Full”, or “ScanNow_Custom”.
SEPM.Scan.Endpoint Unknown The IP or hostname of the identifier of the endpoint.

Command Example

!sep-scan-endpoint endpoint=demisto-PC scanType=ScanNow_Quick

Human Readable Output

Human_Readable_Output_10

sep-update-endpoint-content


Updates the content of a specified client.

Base Command

sep-update-endpoint-content

Input

Argument Name Description Required
endpoint The IP address or hostname of the endpoint. Required

Context Output

Path Type Description
SEPM.Update.Endpoint String The endpoint that is being updated.
SEPM.Update.CommandID String The ID of the command for which to check the status.

Command Example

!sep-update-endpoint-content endpoint=demisto-PC

Human Readable Output

Human_Readable_Output_11

sep-move-client-to-group


Moves a client to a group.

Base Command

sep-move-client-to-group

Input

Argument Name Description Required
groupID The ID of the group to which to move the client. Required
hardwareKey The hardware key of the client to be moved. Required

Context Output

There is no context output for this command.

Command Example

!sep-move-client-to-group groupID=AA51516BC0A8010B3BFBBE37F7B71214 hardwareKey=269CE816FDB1BA25A2505D0A5A59294C

Human Readable Output

Human_Readable_Output_12

sep-identify-old-clients


Get endpoints for a running version that is different than the target version or the desired version (if specified).

Base Command

sep-identify-old-clients

Input

Argument Name Description Required
columns Sets which columns will be displayed. Optional
computerName Filters by the host name of the computer. A wild card search can be done using ‘*’ at the end of the query. Optional
lastUpdate Indicates when a computer’s status was last updated. The default is “0”, which returns all results. Optional
os The operating system by which to filter. Optional
pageSize The number of results to include on each page. The default is 20. Optional
groupName The name of the group to which the endpoint belongs. A wild card search can be done using ‘*‘at the end of the query. Optional
desiredVersion desiredVersion. Optional

Context Output

There is no context output for this command.

Command Example

!sep-identify-old-clients desiredVersion=10

Human Readable Output

Human_Readable_Output_13

Known Limitations

  • SEPM REST- API currently exposes statistics, but does not expose extended information about Risks, Application and Device control, and Network logs.
  • SEPM REST- API currently does not support an operation to get Host Names or IP addresses of clients who don’t have an update content version.
  • SEPM REST- API currently does not support an operation to create or download reports.

Configuration parameters

  • server — Server (e.g., https://1.2.3.4:8446) (required)
  • authentication — Authentication (required)
  • domain — SEPM domain for the user
  • insecure — Trust any certificate (not secure)
  • proxy — Use system proxy settings
  • timeZone — Local time zone (e.g., +02:30,-06:00)

Commands (13)

  • sep-assign-policy

    Assigns an existing policy to a specified location.

  • sep-client-content

    Retrieves the content of the client.

  • sep-command-status

    Retrieves the status of a command.

  • sep-endpoint-quarantine

    Quarantines an endpoint according to its policy.

  • sep-endpoints-info

    Returns information about endpoints.

  • sep-groups-info

    Returns information about groups.

  • sep-identify-old-clients

    Get endpoints for a running version that is different than the target version or the desired version (if specified).

  • sep-list-locations

    Retrieves a list of location IDs for a specified group.

  • sep-list-policies

    Retrieves a list of existing policies.

  • sep-move-client-to-group

    Moves a client to a group.

  • sep-scan-endpoint

    Scans an endpoint.

  • sep-system-info

    Returns information about the system, such as version or AV definition.

  • sep-update-endpoint-content

    Updates the content of a specified client.

import demistomock as demisto
from CommonServerPython import *
import requests
import json
import re
import urllib.request
import urllib.parse
import urllib.error
import urllib3

urllib3.disable_warnings()

handle_proxy()

ENDPOINTS_INFO_DEFAULT_COLUMNS = [
    "computerName",
    "ipAddresses",
    "operatingSystem",
    "osBitness",
    "cidsDefsetVersion",
    "lastScanTime",
    "description",
    "quarantineDesc",
    "domainOrWorkgroup",
    "macAddresses",
    "group",
    "dhcpServer",
    "biosVersion",
    "virtualizationPlatform",
    "computerTimeStamp",
    "creationTime",
    "agentTimestamp",
    "hardwareKey",
]
GROUPS_INFO_DEFAULT_COLUMNS = [
    "fullPathName",
    "numberOfPhysicalComputers",
    "numberOfRegisteredUsers",
    "policySerialNumber",
    "policyDate",
    "description",
    "created",
    "id",
]

"""LITERALS"""

EPOCH_MINUTE = 60 * 1000
EPOCH_HOUR = 60 * EPOCH_MINUTE

"""HELPER FUNCTIONS"""


def fix_url(base):
    return base if base.endswith("/") else (base + "/")


def endpoint_ip_extract(raw_json):
    ips_array = []
    for content in raw_json:
        ip = {"Address": content.get("ipAddresses", [""])[0], "Mac": content.get("computerName")}
        ip = createContext(ip, removeNull=True)
        if ip:
            ips_array.append(ip)
    return ips_array


def endpoint_endpoint_extract(raw_json):
    endpoints_arr = []
    for content in raw_json:
        endpoint = {
            "Hostname": content.get("computerName"),
            "MACAddress": content.get("macAddresses", [""])[0],
            "Domain": content.get("domainOrWorkgroup"),
            "IPAddress": content.get("ipAddresses", [""])[0],
            "DHCPServer": content.get("dhcpServer"),
            "OS": content.get("operatingSystem"),
            "OSVersion": content.get("osVersion"),
            "BIOSVersion": content.get("biosVersion"),
            "Memory": content.get("memory"),
            "Processors": content.get("processorType"),
        }
        endpoint = createContext(endpoint, removeNull=True)
        if endpoint:
            endpoints_arr.append(endpoint)
    return endpoints_arr


def build_query_params(params):
    list_params = [key + "=" + str(params[key]) for key in params]
    query_params = "&".join(list_params)
    return "?" + query_params if query_params else ""


def do_auth(server, crads, insecure, domain):
    url = fix_url(str(server)) + "sepm/api/v1/identity/authenticate"
    body = {
        "username": crads.get("identifier") if crads.get("identifier") else "",
        "password": urllib.parse.quote(crads.get("password")) if crads.get("password") else "",
        "domain": domain if domain else "",
    }
    res = requests.post(url, headers={"Content-Type": "application/json"}, data=json.dumps(body), verify=not insecure)
    res.raise_for_status()
    return parse_response(res)


def do_get(token, raw, suffix):
    insecure = demisto.getParam("insecure")
    server = demisto.getParam("server")
    url = fix_url(server) + suffix
    res = requests.get(url, headers={"Authorization": "Bearer " + token}, verify=not insecure)
    res.raise_for_status()
    if raw:
        return res
    else:
        return parse_response(res)


def do_post(token, is_xml, suffix, body):
    insecure = demisto.getParam("insecure")
    server = demisto.getParam("server")
    url = fix_url(server) + suffix
    res = requests.post(url, headers={"Authorization": "Bearer " + token}, data=body, verify=not insecure)
    res.raise_for_status()
    parsed_response = {}
    if is_xml:
        if res.content:
            parsed_response = xml2json(res.content)
        else:
            return_error(f"Unable to parse the following response: {res}")
    else:
        parsed_response = parse_response(res)
    return parsed_response


def do_put(token, suffix, body):
    insecure = demisto.getParam("insecure")
    server = demisto.getParam("server")
    url = fix_url(server) + suffix
    res = requests.put(
        url,
        headers={"Authorization": "Bearer " + token, "Content-Type": "application/json"},
        data=json.dumps(body),
        verify=not insecure,
    )
    parsed_response = parse_response(res)
    return parsed_response


def do_patch(token, suffix, body):
    insecure = demisto.getParam("insecure")
    server = demisto.getParam("server")
    url = fix_url(server) + suffix
    res = requests.patch(
        url,
        headers={"Authorization": "Bearer " + token, "Content-Type": "application/json"},
        data=json.dumps(body),
        verify=not insecure,
    )
    res.raise_for_status()
    parsed_response = parse_response(res)
    return parsed_response


def parse_response(resp):
    if resp.status_code == 200 or resp.status_code == 207:
        if resp.text == "":
            return resp
        try:
            return resp.json()
        except Exception as ex:
            return_error(f"Unable to parse response: {ex}")
    else:
        try:
            message = resp.json().get("errorMessage")
            return_error(f"Error: {message}")
        except Exception:
            return_error(f"Error: {resp}")


def get_token_from_response(resp):
    if resp.get("token"):
        return resp.get("token")
    else:
        return_error(f"No token: {resp}")  # noqa: RET503


def choose_columns(column_arg, default_list):
    if not column_arg:
        columns_list = default_list
        columns_list.sort()
    elif column_arg == "all" or column_arg == "*":
        columns_list = []
    else:
        columns_list = argToList(column_arg)
    return columns_list


def build_command_xml(data):
    return (
        '<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"'
        f' xmlns:com="http://command.client.webservice.sepm.symantec.com/"> \
            <soapenv:Header/><soapenv:Body>{data}</soapenv:Body></soapenv:Envelope>'
    )


def build_client_xml(data):
    return (
        '<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" '
        f'xmlns:cli="http://client.webservice.sepm.symantec.com/"> \
            <soapenv:Header/><soapenv:Body>{data}</soapenv:Body></soapenv:Envelope>'
    )


def get_command_status_details(token, command_id):
    xml = build_command_xml(f"<com:getCommandStatusDetails><commandID>{command_id}</commandID></com:getCommandStatusDetails>")
    res_json = do_post(token, True, "sepm/ws/v1/CommandService", xml)
    return res_json


def build_command_response_output(title, command_id, message, response):
    cmd_status_details = response.get("cmdStatusDetail")
    cmd_status_details.pop("hardwareKey", None)
    md = tableToMarkdown(title, cmd_status_details) + "\n"
    md += f"### Command ID: {command_id}\n"
    md += "### " + message
    demisto.results(
        {
            "Type": entryTypes["note"],
            "ContentsFormat": formats["json"],
            "Contents": {"cmdStatusDetail": cmd_status_details, "commandId": command_id},
            "HumanReadable": md,
            "EntryContext": {
                "SEPM.LastCommand": createContext(
                    {"CommandDetails": cmd_status_details, "CommandId": command_id}, removeNull=True
                )
            },
        }
    )


def get_computer_id_by_ip(token, ip):
    xml = build_client_xml(f"<cli:getComputersByIP><ipAddresses>{ip}</ipAddresses></cli:getComputersByIP>")
    res_json = do_post(token, True, "sepm/ws/v1/ClientService", xml)
    return demisto.get(json.loads(res_json), "Envelope.Body.getComputersByIPResponse.ComputerResult.computers.computerId")


def get_computer_id_by_hostname(token, hostname):
    xml = build_client_xml(
        f"<cli:getComputersByHostName><computerHostNames>{hostname}</computerHostNames></cli:getComputersByHostName>"
    )
    res_json = do_post(token, True, "sepm/ws/v1/ClientService", xml)
    return demisto.get(json.loads(res_json), "Envelope.Body.getComputersByHostNameResponse.ComputerResult.computers.computerId")


def get_computer_id(token, endpoint_ip, endpoint_host_name):
    computer_id = ""
    if endpoint_ip:
        try:
            computer_id = get_computer_id_by_ip(token, endpoint_ip)
        except Exception:
            return_error("Failed to locate the endpoint by its IP address.")
    elif endpoint_host_name:
        try:
            computer_id = get_computer_id_by_hostname(token, endpoint_host_name)
        except Exception:
            return_error("Failed to locat the endpoint by its hostname.")
    else:
        return_error("Please provide the IP address or the hostname of endpoint.")
    return computer_id


def update_content(token, computer_id):
    xml = build_command_xml(
        f"<com:runClientCommandUpdateContent><computerGUIDList>{computer_id}</computerGUIDList>"
        "</com:runClientCommandUpdateContent>"
    )
    res_json = do_post(token, True, "sepm/ws/v1/CommandService", xml)
    command_id = demisto.get(
        json.loads(res_json), "Envelope.Body.runClientCommandUpdateContentResponse.CommandClientResult.commandId"
    )
    if not command_id:
        error_code = demisto.get(
            res_json, "Envelope.Body.runClientCommandUpdateContentResponse.CommandClientResult.inputErrors.errorCode"
        )
        error_message = demisto.get(
            res_json, "Envelope.Body.runClientCommandUpdateContentResponse.CommandClientResult.inputErrors.errorMessage"
        )
        if error_code or error_message:
            return_error(f"An error response has returned from server: {error_message} with code: {error_code}")
        else:
            return_error("Could not retrieve command ID, no error was returned from server")
    return command_id


def scan(token, computer_id, scan_type):
    xml = build_command_xml(
        f"<com:runClientCommandScan><computerGUIDList>{computer_id}</computerGUIDList>"
        f"<scanType>{scan_type}</scanType></com:runClientCommandScan>"
    )
    res_json = do_post(token, True, "sepm/ws/v1/CommandService", xml)
    command_id = demisto.get(json.loads(res_json), "Envelope.Body.runClientCommandScanResponse.CommandClientResult.commandId")
    if not command_id:
        error_code = demisto.get(
            json.loads(res_json), "Envelope.Body.runClientCommandScanResponse.CommandClientResult.inputErrors.errorCode"
        )
        error_message = demisto.get(
            json.loads(res_json), "Envelope.Body.runClientCommandScanResponse.CommandClientResult.inputErrors.errorMessage"
        )
        if error_code or error_message:
            return_error(f"An error response has returned from server: {error_message} with code: {error_code}")
        else:
            return_error("Could not retrieve command ID, no error was returned from server")
    return command_id


def quarantine(token, computer_id, action_type):
    xml = build_command_xml(
        f"<com:runClientCommandQuarantine><command><commandType>{action_type}</commandType><targetObjectIds>{computer_id}"
        "</targetObjectIds><targetObjectType>COMPUTER</targetObjectType></command>"
        "</com:runClientCommandQuarantine>"
    )
    res_json = do_post(token, True, "sepm/ws/v1/CommandService", xml)
    command_id = demisto.get(
        json.loads(res_json), "Envelope.Body.runClientCommandQuarantineResponse.CommandClientResult.commandId"
    )
    if not command_id:
        error_code = demisto.get(
            json.loads(res_json), "Envelope.Body.runClientCommandQuarantineResponse.CommandClientResult.inputErrors.errorCode"
        )
        error_message = demisto.get(
            json.loads(res_json), "Envelope.Body.runClientCommandQuarantineResponse.CommandClientResult.inputErrors.errorMessage"
        )
        if error_code or error_message:
            return_error(f"An error response has returned from server: {error_message} with code: {error_code}")
        else:
            return_error("Could not retrieve command ID, no error was returned from server")
    return command_id


def validate_time_zone(time_zone):
    pattern = re.compile("^[+-][0-9][0-9]:[0-9][0-9]")
    return bool(pattern.match(time_zone))


def parse_epoch_to_local(epoch, time_zone):
    if not validate_time_zone(time_zone):
        return_error("timeZone param should be in the format of [+/-][h][h]:[m][m]. For exmaple +04:30")
    operator = time_zone[0]
    hour = int(time_zone[1:3])
    minutes = int(time_zone[4:6])
    time_zone_epoch = hour * EPOCH_HOUR + minutes * EPOCH_MINUTE
    local = int(epoch) + time_zone_epoch if operator == "+" else int(epoch) - time_zone_epoch
    return local


def change_assigined(policy):
    new_format = {
        "Policy Name": policy.get("PolicyName"),
        "Type": policy.get("Type"),
        "ID": policy.get("ID"),
        "Assigned": bool(policy.get("AssignedLocations")) or bool(policy.get("AssignedCloudGroups")),
        "Discription": policy.get("Discription"),
        "Enabled": policy.get("Enabled"),
    }
    return new_format


def sanitize_policies_list_for_md(policies_list):
    return list(map(change_assigined, policies_list))


def sanitize_policies_list(policies_list):
    return list(  # noqa: C417
        map(
            lambda policy: {
                "PolicyName": policy["name"],
                "Type": policy["policytype"],
                "ID": policy["id"],
                "Description": policy["desc"],
                "Enabled": policy["enabled"],
                "AssignedLocations": list(  # noqa: C417
                    map(
                        lambda location: {"GroupID": location.get("groupId"), "Locations": location.get("locationIds")},
                        policy.get("assignedtolocations") if policy.get("assignedtolocations") else [],
                    )
                ),
                "AssignedCloudGroups": list(  # noqa: C417
                    map(
                        lambda location: {"GroupID": location.get("groupId"), "Locations": location.get("locationIds")},
                        policy.get("assignedtocloudgroups") if policy.get("assignedtocloudgroups") else [],
                    )
                ),
            },
            policies_list,
        )
    )


def validate_ip(ip):
    pattern = re.compile(r"^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$")
    return bool(pattern.match(ip))


def get_client_content(token, time_zone):
    client_content_json = do_get(token, False, "sepm/api/v1/stats/client/content")
    epoch_time = client_content_json.get("lastUpdated")
    if time_zone:
        epoch_time = parse_epoch_to_local(epoch_time, time_zone)
    last_update_date = timestamp_to_datestring(epoch_time, "%a %b %d %y %H:%M:%S %z")
    client_version = client_content_json.get("clientDefStatusList")
    return client_content_json, client_version, last_update_date


def get_endpoints_info(token, computer_name, last_update, os, page_size, columns, group_name=None):
    params = {"computerName": computer_name, "lastUpdate": last_update, "os": os, "pageSize": page_size, "columns": columns}
    params = createContext(params, removeNull=True)
    json_response = do_get(token, False, "sepm/api/v1/computers" + build_query_params(params))
    filtered_json_response = json_response.get("content")
    final_json = []
    entry_context = []
    for content in filtered_json_response:
        group = content.get("group", {"name": ""})
        bool_start = group.get("name").startswith(group_name[:-1]) if group_name and group_name[-1] == "*" else False
        if (not group_name) or group.get("name") == group_name or bool_start:  # No group name filter
            # used `set` on the mac address list as it sometimes contained duplicated values
            content["macAddresses"] = list(set(content.get("macAddresses")))
            entry_context.append(
                {
                    "Hostname": content.get("computerName"),
                    "Domain": content.get("domainOrWorkgroup"),
                    "IPAddresses": content.get("ipAddresses"),
                    "OS": content.get("operatingSystem", "") + " | " + content.get("osBitness", ""),
                    "Description": content.get("content.description"),
                    "MACAddresses": content.get("macAddresses"),
                    "BIOSVesrsion": content.get("biosVersion"),
                    "DHCPServer": content.get("dhcpServer"),
                    "HardwareKey": content.get("hardwareKey"),
                    "LastScanTime": epochToTimestamp(content.get("lastScanTime")),
                    "RunningVersion": content.get("deploymentRunningVersion"),
                    "TargetVersion": content.get("deploymentTargetVersion"),
                    "Group": group.get("name"),
                    "PatternIdx": content.get("patternIdx"),
                    "OnlineStatus": content.get("onlineStatus"),
                    "UpdateTime": epochToTimestamp(content.get("lastUpdateTime")),
                }
            )
            final_json.append(content)

    return final_json, entry_context


def create_endpints_filter_string(computer_name, last_update, os, page_size, group_name=None):
    md = "## Endpoints Information"
    if last_update != "0":
        md += f", filtered for last updated status: {last_update}" if last_update else ""
    md += f", filtered for hostname: {computer_name}" if computer_name else ""
    md += f", filtered for os: {os}" if os else ""
    md += f", filtered for group name: {group_name}" if group_name else ""
    md += f", page size: {page_size}" if page_size else ""
    md += "\n"
    return md


def get_groups_info(token, columns):
    json_res = do_get(token, False, "sepm/api/v1/groups" + build_query_params({"columns": columns}))
    sepm_groups = []
    filtered_json_response = json_res.get("content")
    for entry in filtered_json_response:
        group = {}
        for header in GROUPS_INFO_DEFAULT_COLUMNS:
            group[header] = entry[header]
            sepm_groups.append(group)
    return filtered_json_response, json_res, sepm_groups


def get_command_status(token, command_id):
    command_status_json = get_command_status_details(token, command_id)
    cmd_status_detail = demisto.get(
        json.loads(command_status_json),
        "Envelope.Body.getCommandStatusDetailsResponse.CommandStatusDetailResult.cmdStatusDetail",
    )
    cmd_status_detail.pop("hardwareKey", None)
    state_id = cmd_status_detail.get("stateId")
    is_done = False
    if state_id == "2" or state_id == "3":
        is_done = True
    message = "Command is done." if is_done else "Command is in progress. Run !sep-command-status to check again."
    return cmd_status_detail, message


def get_list_of_policies(token):
    policies_list = do_get(token, False, "sepm/api/v1/policies/summary").get("content")
    fixed_policy_list = sanitize_policies_list(policies_list)
    md_list = sanitize_policies_list_for_md(fixed_policy_list)
    return md_list, policies_list, fixed_policy_list


def endpoint_quarantine(token, endpoint, action):
    action_type = "Quarantine" if action == "Add" else "Undo"
    computer_id = get_id_by_endpoint(token, endpoint)
    command_id = quarantine(token, computer_id, action_type)
    return command_id


def get_location_list(token, group_id):
    url = f"sepm/api/v1/groups/{group_id}/locations"
    url_resp = do_get(token, False, url)
    location_ids = list(map(lambda location_string: {"ID": location_string.split("/")[-1]}, url_resp))  # noqa: C417
    return url_resp, location_ids


def get_id_by_endpoint(token, endpoint):
    computer_id = ""
    if not endpoint:
        return_error("Please provide the IP address or the hostname of endpoint.")
    elif validate_ip(endpoint):
        computer_id = get_computer_id(token, endpoint, None)
    else:
        computer_id = get_computer_id(token, None, endpoint)
    return computer_id


def scan_endpoint(token, endpoint, scan_type):
    computer_id = get_id_by_endpoint(token, endpoint)
    command_id = scan(token, computer_id, scan_type)
    return command_id


def update_endpoint_content(token, endpoint):
    computer_id = get_id_by_endpoint(token, endpoint)
    command_id = update_content(token, computer_id)
    return command_id


def filter_only_old_clients(filtered_json_response, desired_version):
    filtered = []
    for content in filtered_json_response:
        RunningVersion = content.get("deploymentRunningVersion")
        TargetVersion = content.get("deploymentTargetVersion")

        if (desired_version and RunningVersion != desired_version) or (not desired_version and RunningVersion != TargetVersion):
            filtered.append(content)
    return filtered


"""COMMANDS"""


def system_info_command(token):
    version_json = do_get(token, False, "sepm/api/v1/version")
    avdef_json = do_get(token, False, "sepm/api/v1/content/avdef/latest")
    system_info_json = {"version": version_json, "avdef": avdef_json}
    md = "## System Information\n"
    md += tableToMarkdown("Version", version_json)
    md += tableToMarkdown("AV Definitions", avdef_json)
    context = avdef_json.get("publishedBySymantec")
    if type(context) is dict:
        context = createContext(context, removeNull=True)
    demisto.results(
        {
            "Type": entryTypes["note"],
            "ContentsFormat": formats["json"],
            "Contents": system_info_json,
            "HumanReadable": md,
            "EntryContext": {"SEPM.ServerAVDefVersion": context},
        }
    )


def old_clients_command(token):
    computer_name = demisto.getArg("computerName")
    last_update = demisto.getArg("lastUpdate")
    os = demisto.getArg("os")
    page_size = demisto.getArg("pageSize")
    columns = demisto.getArg("columns")
    group_name = demisto.getArg("groupName")
    desired_version = demisto.getArg("desiredVersion")
    filtered_json_response, entry_context = get_endpoints_info(
        token, computer_name, last_update, os, page_size, columns, group_name
    )
    columns_list = choose_columns(columns, ENDPOINTS_INFO_DEFAULT_COLUMNS)
    filtered_json_response = filter_only_old_clients(filtered_json_response, desired_version)
    md = create_endpints_filter_string(computer_name, last_update, os, page_size, group_name)
    md += tableToMarkdown("Old Endpoints", filtered_json_response, columns_list)

    demisto.results(
        {"Type": entryTypes["note"], "ContentsFormat": formats["json"], "Contents": filtered_json_response, "HumanReadable": md}
    )


def client_content_command(token):
    time_zone = demisto.getParam("timeZone")
    client_content_json, client_version, last_update_date = get_client_content(token, time_zone)
    md = f"## Client Content, last updated on {last_update_date}\n"
    md += tableToMarkdown("Client Content Versions", client_version)
    demisto.results(
        {
            "Type": entryTypes["note"],
            "ContentsFormat": formats["json"],
            "Contents": client_content_json,
            "HumanReadable": md,
            "EntryContext": {"SEPM.ClientContentVersions": client_version, "SEPM.LastUpdated": last_update_date},
        }
    )


def endpoints_info_command(token):
    computer_name = demisto.getArg("computerName")
    last_update = demisto.getArg("lastUpdate")
    os = demisto.getArg("os")
    page_size = demisto.getArg("pageSize")
    columns = demisto.getArg("columns")
    group_name = demisto.getArg("groupName")
    filtered_json_response, entry_context = get_endpoints_info(
        token, computer_name, last_update, os, page_size, columns, group_name
    )
    columns_list = choose_columns(columns, ENDPOINTS_INFO_DEFAULT_COLUMNS)
    md = create_endpints_filter_string(computer_name, last_update, os, page_size, group_name)
    md += tableToMarkdown("Endpoints", filtered_json_response, columns_list)
    demisto.results(
        {
            "Type": entryTypes["note"],
            "ContentsFormat": formats["json"],
            "Contents": filtered_json_response,
            "HumanReadable": md,
            "IgnoreAutoExtract": True,
            "EntryContext": {
                "SEPM.Endpoint(val.Hostname == obj.Hostname)": createContext(entry_context, removeNull=True),
                "IP(val.Address === obj.Address)": endpoint_ip_extract(filtered_json_response),
                "Endpoint(val.Hostname == obj.Hostname)": endpoint_endpoint_extract(filtered_json_response),
            },
        }
    )


def groups_info_command(token):
    columns = demisto.getArg("columns")
    filtered_json_response, json_res, sepm_groups = get_groups_info(token, columns)
    columns_list = choose_columns(columns, GROUPS_INFO_DEFAULT_COLUMNS)
    md = tableToMarkdown("Groups Information", filtered_json_response, columns_list)
    demisto.results(
        {
            "Type": entryTypes["note"],
            "ContentsFormat": formats["json"],
            "Contents": json_res,
            "HumanReadable": md,
            "IgnoreAutoExtract": True,
            "EntryContext": {"SEPM.Groups": sepm_groups},
        }
    )


def command_status(token):
    command_id = demisto.getArg("commandId")
    cmd_status_detail, message = get_command_status(token, command_id)
    md = f"### Command ID: {command_id}\n"
    md += f"### State ID: {cmd_status_detail.get('stateId')}\n"
    md += "### " + message
    demisto.results(
        {
            "Type": entryTypes["note"],
            "ContentsFormat": formats["json"],
            "Contents": {"cmdStatusDetail": cmd_status_detail, "commandId": command_id},
            "HumanReadable": md,
            "IgnoreAutoExtract": True,
            "EntryContext": {
                "SEPM.LastCommand(val.CommandID && val.CommandID == obj.CommandID)": createContext(
                    {"CommandDetails": cmd_status_detail, "CommandID": command_id}, removeNull=True
                )
            },
        }
    )


def list_policies_command(token):
    md_list, policies_list, fixed_policy_list = get_list_of_policies(token)
    md = tableToMarkdown(
        "List of existing policies", md_list, ["Policy Name", "Type", "ID", "Enabled", "Assigned", "Description"]
    )
    demisto.results(
        {
            "Type": entryTypes["note"],
            "ContentsFormat": formats["json"],
            "Contents": policies_list,
            "HumanReadable": md,
            "IgnoreAutoExtract": True,
            "EntryContext": {"SEPM.PoliciesList": createContext(fixed_policy_list, removeNull=True)},
        }
    )


def assign_policie_command(token):
    group_id = demisto.getArg("groupID")
    locatoion_id = demisto.getArg("locationID")
    policy_type = demisto.getArg("policyType").lower()
    policy_id = demisto.getArg("policyID")
    do_put(token, f"sepm/api/v1/groups/{group_id}/locations/{locatoion_id}/policies/{policy_type}", {"id": policy_id})
    md = f"### Policy: {policy_id}, of type: {policy_type}, was assigned to location: {locatoion_id}, in group: {group_id}"
    demisto.results(
        {"Type": entryTypes["note"], "ContentsFormat": formats["json"], "Contents": "", "HumanReadable": md, "EntryContext": {}}
    )


def list_locations_command(token):
    group_id = demisto.getArg("groupID")
    url_resp, location_ids = get_location_list(token, group_id)
    demisto.results(
        {
            "Type": entryTypes["note"],
            "ContentsFormat": formats["json"],
            "Contents": url_resp,
            "HumanReadable": tableToMarkdown(
                "Locations",
                list(map(lambda location: {"Location ID": location.get("ID")}, location_ids)),  # noqa: C417
            ),
            "IgnoreAutoExtract": True,
            "EntryContext": {"SEPM.Locations": location_ids},
        }
    )


def endpoint_quarantine_command(token):
    endpoint = demisto.getArg("endpoint")
    action = demisto.getArg("actionType")
    command_id = endpoint_quarantine(token, endpoint, action)
    message = (
        f"### Initiated quarantine for endpoint {endpoint}. Command ID: {command_id}."
        if action == "Add"
        else f"### Removing endpoint: {endpoint} from quarantine. Command ID: {command_id}."
    )
    context = {"CommandID": command_id, "Action": action, "Endpoint": endpoint}
    demisto.results(
        {
            "Type": entryTypes["note"],
            "ContentsFormat": formats["text"],
            "Contents": command_id,
            "HumanReadable": message,
            "IgnoreAutoExtract": True,
            "EntryContext": {"SEPM.Quarantine": context},
        }
    )


def scan_endpoint_command(token):
    endpoint = demisto.getArg("endpoint")
    scan_type = demisto.getArg("scanType")
    command_id = scan_endpoint(token, endpoint, scan_type)
    message = f"### Initiated scan on endpoint: {endpoint} with type: {scan_type}. Command ID: {command_id}."
    context = {"CommandID": command_id, "Type": scan_type, "Endpoint": endpoint}
    demisto.results(
        {
            "Type": entryTypes["note"],
            "ContentsFormat": formats["text"],
            "Contents": command_id,
            "HumanReadable": message,
            "IgnoreAutoExtract": True,
            "EntryContext": {"SEPM.Scan": context},
        }
    )


def update_endpoint_content_command(token):
    endpoint = demisto.getArg("endpoint")
    command_id = update_endpoint_content(token, endpoint)
    message = f"### Updating endpoint: {endpoint}. Command ID: {command_id}."
    context = {"CommandID": command_id, "Endpoint": endpoint}
    demisto.results(
        {
            "Type": entryTypes["note"],
            "ContentsFormat": formats["text"],
            "Contents": command_id,
            "HumanReadable": message,
            "IgnoreAutoExtract": True,
            "EntryContext": {"SEPM.Update": context},
        }
    )


def move_client_to_group(token, group_id, hardware_key):
    body = [{"group": {"id": group_id}, "hardwareKey": hardware_key}]
    response = do_patch(token, "sepm/api/v1/computers", body)
    message = (
        "### Moved client to requested group successfully"
        if response[0].get("responseCode") == "200"
        else "### Error moving client"
    )
    return response, message


def move_client_to_group_command(token):
    group_id = demisto.getArg("groupID")
    hardware_key = demisto.getArg("hardwareKey")
    response, message = move_client_to_group(token, group_id, hardware_key)
    demisto.results(
        {
            "Type": entryTypes["note"],
            "ContentsFormat": formats["text"],
            "Contents": response,
            "HumanReadable": message,
            "IgnoreAutoExtract": True,
        }
    )


def main():
    current_command = demisto.command()
    try:
        """
        Before EVERY command the following tow lines are performed (do_auth and get_token_from_response)
        """
        resp = do_auth(
            server=demisto.getParam("server"),
            crads=demisto.getParam("authentication"),
            insecure=demisto.getParam("insecure"),
            domain=demisto.getParam("domain"),
        )
        token = get_token_from_response(resp)
        if current_command == "test-module" and token:
            # This is the call made when pressing the integration test button.
            demisto.results("ok")
        if current_command == "sep-system-info":
            system_info_command(token)
        if current_command == "sep-client-content":
            client_content_command(token)
        if current_command == "sep-endpoints-info":
            endpoints_info_command(token)
        if current_command == "sep-groups-info":
            groups_info_command(token)
        if current_command == "sep-command-status":
            command_status(token)
        if current_command == "sep-list-policies":
            list_policies_command(token)
        if current_command == "sep-assign-policy":
            assign_policie_command(token)
        if current_command == "sep-list-locations":
            list_locations_command(token)
        if current_command == "sep-endpoint-quarantine":
            endpoint_quarantine_command(token)
        if current_command == "sep-scan-endpoint":
            scan_endpoint_command(token)
        if current_command == "sep-update-endpoint-content":
            update_endpoint_content_command(token)
        if current_command == "sep-move-client-to-group":
            move_client_to_group_command(token)
        if current_command == "sep-identify-old-clients":
            old_clients_command(token)
    except Exception as ex:
        return_error(f"Cannot perform the command: {current_command}. Error: {ex}")


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