NetskopeAPIv1

Get alerts and events, manage quarantine files as well as URL and hash lists using Netskope API v1.

Network Security · Netskope

Details

IDNetskopeAPIv1
ProviderNetskope
CategoryNetwork Security
From Version6.2.0
Docker Imagedemisto/python3:3.12.13.10116658
Supported ModulesAgentix XSIAM

README

Netskope (API v1)

Get alerts and events, manage quarantine files as well as URL and hash lists using Netskope API v1.
This integration was integrated and tested with version 93.0.7.625 of Netskope.

Configure Netskope in Cortex

Parameter Description Required
Server URL   True
API token   True
Trust any certificate (not secure)   False
Use system proxy settings   False
Fetch incidents   False
Maximum incidents per fetch   False
First fetch timestamp (<number> <time unit>, like 12 hours, 7 days)   False
Fetch Events Fetch events as incidents, in addition to the alerts. False
Event types to fetch   False
Maximum events as incidents per fetch   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.

netskope-event-list


Get events extracted from SaaS traffic and or logs.

Base Command

netskope-event-list

Input

Argument Name Description Required
query Free query to filter the events. For example, “app eq Dropbox”. For more information, please visit Netskope documentation: https://docs.netskope.com/en/get-events-data.html’ Optional
event_type Select events by their type. Possible values are: page, application, audit, infrastructure, network. Required
timeperiod Get all events from a certain time period. Possible values are: Last 60 mins, Last 24 Hrs, Last 7 Days, Last 30 Days. Optional
start_time Restrict events to those that have timestamps greater than the provided timestamp. Optional
end_time Restrict events to those that have timestamps less than or equal to the provided timestamp. Optional
insertion_start_time Restrict events to those that were inserted to the system after the provided timestamp. Optional
insertion_end_time Restrict events to those that were inserted to the system before the provided timestamp. Optional
limit The maximum amount of events to retrieve. Default is 50. Optional
page The page number of the events to retrieve (minimum is 1). Default is 1. Optional
unsorted If true, the returned data will not be sorted (useful for improved performance). Possible values are: true, false. Optional

Context Output

Path Type Description
Netskope.Event.event_id String The unique identifier of the event.
Netskope.Event.timestamp Number Unix epoch timestamp when the event happened in.
Netskope.Event.type String Shows if it is an application event or a connection event.
Netskope.Event.access_method String Cloud app traffic can be steered to the Netskope cloud using different deployment methods such as Client (Netskope Client), Secure Forwarder etc.
Netskope.Event.traffic_type String Type of the traffic: CloudApp or Web.
Netskope.Event.count Number Number of raw log lines/events sessionized or suppressed during the suppressed interval.
Netskope.Event.app String Specific cloud application used by the user (e.g. app = Dropbox).
Netskope.Event.appcategory String Application Category as designated by Netskope.
Netskope.Event.url String URL of the application that the user visited as provided by the log or data plane traffic.
Netskope.Event.page String The URL of the originating page.
Netskope.Event.domain String Domain value.
Netskope.Event.object String Name of the object which is being acted on.
Netskope.Event.object_id String Unique ID associated with an object.
Netskope.Event.activity String Description of the user performed activity.
Netskope.Event.device String Device type from where the user accessed the cloud app.
Netskope.Event.category String The event category.

Command example

!netskope-event-list event_type=application limit=1 start_time=2021-03-21T18:48:02.358736 end_time=2022-03-21T18:48:02.358736

Context Example

{
    "Netskope": {
        "Event": {
            "_insertion_epoch_timestamp": 1647890592,
            "access_method": "API Connector",
            "activity": "Create",
            "alert": "no",
            "app": "Google Workspace",
            "appcategory": "Application Suite",
            "audit_category": null,
            "audit_type": "authorize",
            "browser": "unknown",
            "category": "Application Suite",
            "cci": 91,
            "ccl": "excellent",
            "count": 1,
            "device": "Other",
            "event_id": "a3f6cb3f22c4431defbf371b",
            "from_user": "test@goxsoar.com",
            "from_user_category": "Internal",
            "instance_id": "goxsoar.com",
            "netskope_activity": "False",
            "object": "BetterCloud",
            "object_id": "800521135851.apps.googleusercontent.com",
            "object_type": "Token",
            "organization_unit": "",
            "os": "unknown",
            "other_categories": [],
            "scopes": [
                "https://apps-apis.google.com/a/feeds/calendar/resource/",
                "https://apps-apis.google.com/a/feeds/compliance/audit/",
                "https://apps-apis.google.com/a/feeds/domain/",
                "https://apps-apis.google.com/a/feeds/emailsettings/2.0/",
                "https://docs.google.com/feeds/",
                "https://sites.google.com/feeds/",
                "https://spreadsheets.google.com/feeds/",
                "https://www.google.com/m8/feeds/",
                "https://www.googleapis.com/auth/contacts",
                "https://www.googleapis.com/auth/activity",
                "https://www.googleapis.com/auth/admin.datatransfer",
                "https://www.googleapis.com/auth/admin.directory.group",
                "https://www.googleapis.com/auth/admin.directory.group.member",
                "https://www.googleapis.com/auth/admin.directory.orgunit",
                "https://www.googleapis.com/auth/admin.directory.user",
                "https://www.googleapis.com/auth/admin.directory.user.alias",
                "https://www.googleapis.com/auth/admin.directory.user.security",
                "https://www.googleapis.com/auth/admin.directory.userschema",
                "https://www.googleapis.com/auth/admin.reports.audit.readonly",
                "https://www.googleapis.com/auth/admin.reports.usage.readonly",
                "https://www.googleapis.com/auth/apps.groups.settings",
                "https://www.googleapis.com/auth/apps.licensing",
                "https://www.googleapis.com/auth/calendar",
                "https://www.googleapis.com/auth/drive",
                "https://www.googleapis.com/auth/userinfo.email",
                "https://www.googleapis.com/auth/userinfo.profile",
                "https://www.googleapis.com/auth/gmail.settings.basic",
                "https://www.googleapis.com/auth/gmail.settings.sharing"
            ],
            "site": "Google App Suite",
            "srcip": "fda3:e722:ac3:10:15:8d06:a37:f8d0",
            "timestamp": 1647888482,
            "traffic_type": "CloudApp",
            "type": "nspolicy",
            "ur_normalized": "test@goxsoar.com",
            "user": "test@goxsoar.com",
            "user_category": "Internal",
            "userip": "fda3:e722:ac3:10:15:8d06:a37:f8d0",
            "userkey": "test@goxsoar.com"
        }
    }
}

Human Readable Output

Events List

Current page size: 1
Showing page 1 out of others that may exist.

Event Id Timestamp Type Access Method App Traffic Type
a3f6cb3f22c4431defbf371b 1647888482 nspolicy API Connector Google Workspace CloudApp

netskope-alert-list


Get alerts generated by Netskope, including policy, DLP, and watch list alerts.

Base Command

netskope-alert-list

Input

Argument Name Description Required
query Free query to filter the alerts. For example, “alert_name like ‘test’”. For more information, please visit Netskope documentation: https://docs.netskope.com/en/get-alerts-data.html’ Optional
alert_type Select alerts by their type. Possible values are: anomaly, Compromised Credential, policy, Legal Hold, malsite, Malware, DLP, Security Assessment, watchlist, quarantine, Remediation, uba. Optional
acked Whether to retrieve acknowledged alerts or not. Possible values are: true, false. Optional
timeperiod Get alerts from certain time period. Possible values are: Last 60 mins, Last 24 Hrs, Last 7 Days, Last 30 days, Last 60 days, Last 90 days. Optional
start_time Restrict alerts to those that have timestamps greater than the provided timestamp. Optional
end_time Restrict alerts to those that have timestamps less than or equal to the provided timestamp. Optional
insertion_start_time Restrict alerts which have been inserted into the system after the provided timestamp. Optional
insertion_end_time Restrict alerts which have been inserted into the system before the provided timestamp. Optional
limit The maximum number of alerts to return. Default is 50. Optional
page The page number of the alerts to retrieve (minimum is 1). Default is 1. Optional
unsorted If true, the returned data will not be sorted (useful for improved performance). Possible values are: true, false. Optional

Context Output

Path Type Description
Netskope.Alert.alert_id String The unique identifier of the alert.
Netskope.Alert.timestamp Number Timestamp when the event/alert happened.
Netskope.Alert.type String Shows if it is an application event or a connection event.
Netskope.Alert.access_method String Cloud app traffic can be steered to the Netskope cloud using different deployment methods such as Client (Netskope Client), Secure Forwarder etc.
Netskope.Alert.traffic_type String Type of the traffic: CloudApp or Web.
Netskope.Alert.action String Action taken on the event for the policy.
Netskope.Alert.count Number Number of raw log lines/events sessionized or suppressed during the suppressed interval.
Netskope.Alert.alert_name String Name of the alert.
Netskope.Alert.alert_type String Type of the alert.
Netskope.Alert.acked Boolean Whether user acknowledged the alert or not.
Netskope.Alert.policy String Name of the policy configured by an admin.
Netskope.Alert.app String Specific cloud application used by the user (e.
Netskope.Alert.appcategory String Application Category as designated by Netskope.
Netskope.Alert.dlp_file String File/Object name extracted from the file/object.
Netskope.Alert.dlp_profile String DLP profile name.
Netskope.Alert.dlp_rule String DLP rule that triggered.
Netskope.Alert.category String The alert category.
Netskope.Alert.cci Number The cloud confidence index.

Command example

!netskope-alert-list limit=1 start_time=2021-03-21T18:48:02.358736 end_time=2022-03-21T18:48:02.358736

Context Example

{
    "Netskope": {
        "Alert": {
            "_insertion_epoch_timestamp": 1647888457,
            "access_method": "API Connector",
            "acked": "false",
            "action": "alert",
            "activity": "Introspection Scan",
            "alert": "yes",
            "alert_id": "0d7fa7e3cb3034bcc0ff94a5",
            "alert_name": "Gdrive - Alert on PII",
            "alert_type": "DLP",
            "app": "Google Drive",
            "appcategory": "Cloud Storage",
            "browser": "unknown",
            "category": "Cloud Storage",
            "cci": 91,
            "ccl": "excellent",
            "count": 1,
            "device": "Other",
            "dlp_file": "CS Owned PC Data",
            "dlp_incident_id": 1407319677213026800,
            "dlp_is_unique_count": "true",
            "dlp_parent_id": 1407319677213026800,
            "dlp_profile": "Best Practice PII DLP Profile",
            "dlp_rule": "FullName-Near-SSN-Unique",
            "dlp_rule_count": 64,
            "dlp_rule_severity": "High",
            "dlp_unique_count": 63,
            "dst_country": "US",
            "dst_latitude": 37.4059906006,
            "dst_location": "Mountain View",
            "dst_longitude": -122.0785140991,
            "dst_region": "California",
            "dst_timezone": "America/Los_Angeles",
            "dst_zipcode": "N/A",
            "dstip": "142.250.191.78",
            "exposure": "internal",
            "file_lang": "ENGLISH",
            "file_path": "/My Drive/CS Owned PC Data",
            "file_size": 805733,
            "file_type": "application/vnd.google-apps.spreadsheet",
            "from_user": "test@goxsoar.com",
            "instance": "goxsoar.com",
            "instance_id": "goxsoar.com",
            "internal_collaborator_count": 20,
            "md5": "81e5926346f19f158688ccf40d88436e",
            "mime_type": "application/vnd.google-apps.spreadsheet",
            "modified": 1647888385,
            "netskope_pop": "US-SJC1",
            "object": "CS Owned PC Data",
            "object_id": "1wxnfr3SWylRdPx8R3WHo2ywml7LsYPSt_6wqUX2KuDU",
            "object_type": "File",
            "organization_unit": "",
            "os": "unknown",
            "other_categories": [],
            "outer_doc_type": 361,
            "owner": "test@goxsoar.com",
            "policy": "Gdrive - Alert on PII",
            "request_id": 6525954495577788000,
            "scan_type": "Ongoing",
            "sha256": "3f53dac4dbce5fbc982ba3180b1b5d5fbed2a9c1cacf906471ce6a9aeef08a05",
            "shared_with": "support@goxsoar.com",
            "site": "Google Drive",
            "suppression_key": "CS Owned PC Data",
            "timestamp": 1647888450,
            "title": "CS Owned PC Data",
            "total_collaborator_count": 20,
            "traffic_type": "CloudApp",
            "true_obj_category": "Spreadsheet",
            "true_obj_type": "Microsoft Excel 2007 XML",
            "type": "nspolicy",
            "ur_normalized": "test@goxsoar.com",
            "url": "https://drive.google.com/open?id=4wxnfr3SWylRdPx8R3WHo2ywml7LsYPSt_6wqUX2KuDU",
            "user": "test@goxsoar.com",
            "user_id": "test@goxsoar.com",
            "userkey": "test@goxsoar.com"
        }
    }
}

Human Readable Output

Alerts List

Current page size: 1
Showing page 1 out of others that may exist.

Alert Id Alert Name Alert Type Timestamp Action
0d7fa7e3cb3034bcc0ff94a5 Gdrive - Alert on PII DLP 1647888450 alert

netskope-quarantined-file-list


List all quarantined files.

Base Command

netskope-quarantined-file-list

Input

Argument Name Description Required
start_time Get files last modified after the provided date string. Optional
end_time Get files last modified before the provided date string. Optional
limit The maximum amount of clients to retrieve. Default is 50. Optional
page The page number of the clients to retrieve (minimum is 1). Default is 1. Optional

Context Output

Path Type Description
Netskope.Quarantine.quarantine_profile_id String The ID of quarantine profile.
Netskope.Quarantine.quarantine_profile_name String The name of quarantine profile.
Netskope.Quarantine.file_id String The ID of the quarantined file.
Netskope.Quarantine.original_file_name String The original filename before quarantining.
Netskope.Quarantine.policy String The policy name caused quarantine the file.
Netskope.Quarantine.quarantined_file_name String The filename after quarantining.
Netskope.Quarantine.user_id String The ID of the user related to the quarantined file.

Command example

!netskope-quarantined-file-list limit=1

Context Example

{
    "Netskope": {
        "Quarantine": {
            "file_id": "1M_RU4jLPUwclKOhqZ7sPSqkMNS-S6Vyr",
            "original_file_name": "PII SSN Large v2.xlsx",
            "policy": "[Data Protection] - Quarantine PII Uploads to Box",
            "quarantine_profile_id": "1",
            "quarantine_profile_name": "Qmasters Testing Google Drive",
            "quarantined_file_name": "inline_884993759783_1_F2DD7F63_PII SSN Large v2.xlsx",
            "user_id": "test@goxsoar.com"
        }
    }
}

Human Readable Output

Quarantined Files List

Current page size: 1
Showing page 1 out of others that may exist.

quarantine_profile_id quarantine_profile_name file_id original_file_name policy
1 Qmasters Testing Google Drive 1M_RU4jLPUwclKOhqZ7sPSqkMNS-S6Vyr PII SSN Large v2.xlsx [Data Protection] - Quarantine PII Uploads to Box

netskope-quarantined-file-get


Download a quarantined file.

Base Command

netskope-quarantined-file-get

Input

Argument Name Description Required
quarantine_profile_id The ID of quarantine profile. Required
file_id The ID of the quarantined file. Required

Context Output

Path Type Description
File.Size Number The size of the file.
File.Name String The name of the file.
File.EntryID String The entry ID of the file.
File.Info String File information.
File.Type String The file type.
File.Extension String The file extension.

Command example

!netskope-quarantined-file-get file_id=1M_RU4jLPUwclKOhqZ7sPSqkMNS-S6Vyr quarantine_profile_id=1

Context Example

{
    "File": {
        "EntryID": "4447@8479e914-8493-4968-8f32-78852375d17b",
        "Extension": "zip",
        "Info": "application/zip",
        "MD5": "8ac692ef2cc78adfc523188e54d52933",
        "Name": "1M_RU4jLPUwclKOhqZ7sPSqkMNS-S6Vyr.zip",
        "SHA1": "19cf128ad8fd41108d0a601e8e9c8cf123c11c5a",
        "SHA256": "9ea98de9c4f852665f678338767280f64b20ed11943c49709440643ed04df122",
        "SHA512": "5a6bce0fd15e4184d29ee8d38627ec47150665a6ccb67d39b02a8898ff0c987c4e7be580c9243cc3d4a7f3f2b637f226216434bf01bc2babfe35a4b7ff8cc2cc",
        "SSDeep": "384:wc313ff+IizBt/oPervm6X61XWRcjDQJB9k3Wl:wMGIct/PjtX61EVB9mWl",
        "Size": 15774,
        "Type": "Microsoft Excel 2007+"
    }
}

Human Readable Output

netskope-quarantined-file-update


Take an action on a quarantined file.

Base Command

netskope-quarantined-file-update

Input

Argument Name Description Required
quarantine_profile_id The profile ID of the quarantined file. Required
file_id The ID of the quarantined file. Required
action Action to be performed on a quarantined. Possible values are: block, allow. Required

Context Output

There is no context output for this command.

Command example

!netskope-quarantined-file-update file_id=1M_RR4jLPUwclKOhqZ7sPSqkMNS-S6Vyr quarantine_profile_id=1 action=block

Human Readable Output

The file 1M_RR4jLPUwclKOhqZ7sPSqkMNS-S6Vyr was successfully blocked!

netskope-url-list-update


Update the URL List with the values provided.
The command will override the whole list content, rather than appending the new values.

Base Command

netskope-url-list-update

Input

Argument Name Description Required
name Name of an existing URL List shown in the Netskope UI on the URL List page. Required
urls The content of the URL list. Required

Context Output

Path Type Description
Netskope.URLList.name String The name of the URL list.
Netskope.URLList.URL String The content the URL list.

Command example

!netskope-url-list-update name="Allowed URLs" urls="allow.me,allow2.me"

Context Example

{
    "Netskope": {
        "URLList": {
            "URL": [
                "allow.me",
                "allow2.me"
            ],
            "name": "Allowed URLs"
        }
    }
}

Human Readable Output

URL List Allowed URLs:
allow.me, allow2.me

netskope-file-hash-list-update


Update file hash list with the values provided.
The command will override the whole list content, rather than appending the new values.

Base Command

netskope-file-hash-list-update

Input

Argument Name Description Required
name Name of an existing file hash list shown in the Netskope UI on the file hash list page. Required
hash List of file hashes (md5 or sha256). Required

Context Output

Path Type Description
FileHashList.name String The name of the hash list.
FileHashList.hash String The content of the hash list.

Command example

!netskope-file-hash-list-update name="Test SHA256" hash="00db7cf5cc13df9ae88615af999582608361c14fc915d1dd76fa619d1c341597"

Context Example

{
    "Netskope": {
        "FileHashList": {
            "hash": [
                "00db7cf5cc13df9ae88615af999582608361c14fc915d1dd76fa619d1c341597"
            ],
            "name": "Test SHA256"
        }
    }
}

Human Readable Output

Hash List Test SHA256:
00db7cf5cc13df9ae88615af999582608361c14fc915d1dd76fa619d1c341597

netskope-client-list


Get information about the Netskope clients.

Base Command

netskope-client-list

Input

Argument Name Description Required
query Free query on the clients, based on the client fields. For example, “host_info.hostname eq xxx”. For more information, please visit Netskope documentation: https://docs.netskope.com/en/get-client-data.html’. Optional
limit The maximum amount of clients to retrieve. Default is 50. Optional
page The page number of the clients to retrieve (minimum is 1). Default is 1. Optional

Context Output

Path Type Description
Netskope.Client.client_id String The ID of the Netskope client.
Netskope.Client.client_version String The client version.
Netskope.Client.device_id String The ID of the client’s device.
Netskope.Client.host_info String Information about the client’s host.
Netskope.Client.last_event String Information about the last event related to the client.
Netskope.Client.user_added_time String The last time a client’s user was added to Netskope.
Netskope.Client.users String List of all users of the provided client.

Command example

!netskope-client-list limit=1

Context Example

{
    "Netskope": {
        "Client": {
            "client_id": "TEST82A5",
            "client_version": "91.0.6.812",
            "device_id": "TEST82A5",
            "host_info": {
                "device_make": "Parallels Software International Inc.",
                "device_model": "Parallels Virtual Platform",
                "hostname": "TEST82A5",
                "managementID": "",
                "nsdeviceuid": "725DAC1A-6654-3F3A-971E-C984FBE9FE5E",
                "os": "Windows",
                "os_version": "10.0 (2009)"
            },
            "last_event": {
                "actor": "System",
                "event": "Tunnel Up",
                "npa_status": "Steering Disabled",
                "status": "Enabled",
                "timestamp": 1642475967
            },
            "user_added_time": 1638994653,
            "users": [
                {
                    "_id": "0c6f3f867882c2d243a83310",
                    "device_classification_status": "Not Configured",
                    "last_event": {
                        "actor": "System",
                        "event": "Tunnel Up",
                        "npa_status": "Steering Disabled",
                        "status": "Enabled",
                        "timestamp": 1642475967
                    },
                    "user_source": "Manual",
                    "user_state": 0,
                    "userkey": "W1Acv9LP05u2654lwRaD",
                    "username": "test@goxsoar.com"
                },
                {
                    "_id": "0c6f3f867882c2d243a83310",
                    "device_classification_status": "Unknown",
                    "last_event": {
                        "actor": "System",
                        "event": "Tunnel Down",
                        "npa_status": "Disabled",
                        "status": "Disabled",
                        "timestamp": 1638995110
                    },
                    "userkey": "W1Acv9LP05u2654lwRaD"
                }
            ]
        }
    }
}

Human Readable Output

Clients List

Current page size: 1
Showing page 1 out of others that may exist.

Client Id Client Version Device Id User Added Time
TEST82A5 91.0.6.812 TEST82A5 1638994653

netskope-host-associated-user-list


List all users of certain host by its hostname.

Base Command

netskope-host-associated-user-list

Input

Argument Name Description Required
hostname The hostname to view its users. Required
limit The maximum amount of users to retrieve. Default is 50. Optional
page The page number of the users to retrieve (minimum is 1). Default is 1. Optional

Context Output

Path Type Description
Netskope.User.user_id String The ID of the Netskope user.
Netskope.User.device_classification_status String The device classification status.
Netskope.User.last_event Unknown Information about the last event related to the user.
Netskope.User.user_source String The source of the user.
Netskope.User.userkey String The user key.
Netskope.User.username String The name/email of the user.

Command example

!netskope-host-associated-user-list hostname=TEST82A5 limit=1

Context Example

{
    "Netskope": {
        "User": {
            "device_classification_status": "Unknown",
            "last_event": {
                "actor": "System",
                "event": "Tunnel Down",
                "npa_status": "Disabled",
                "status": "Disabled",
                "timestamp": 1638995110
            },
            "user_id": "0c6f3f867882c2d243a83310",
            "user_source": "Manual",
            "user_state": 0,
            "userkey": "W1Acv9LP05u2654lwRaD",
            "username": "test@goxsoar.com"
        }
    }
}

Human Readable Output

Users Associated With TEST82A5

Current page size: 1
Showing page 1 out of others that may exist.

user_id username user_source
0c6f3f867882c2d243a83310 test@goxsoar.com Manual
0c6f3f867882c2d243a83310    

netskope-user-associated-host-list


List all hosts related to a certain username.

Base Command

netskope-user-associated-host-list

Input

Argument Name Description Required
username The username to view its hosts. Required
limit The maximum amount of hosts to retrieve. Default is 50. Optional
page The page number of the hosts to retrieve (minimum is 1). Default is 1. Optional

Context Output

Path Type Description
Netskope.Host.nsdeviceuid String Netskope device UID.
Netskope.Host.os String The device operating system.
Netskope.Host.os_version String The device operating system version.
Netskope.Host.device_model String The device model.
Netskope.Host.hostname String The hostname of the device.
Netskope.Host.agent_status String The status of the agent on the device.

Command example

!netskope-user-associated-host-list username=test@goxsoar.com

Context Example

{
    "Netskope": {
        "Host": {
            "agent_status": "Enabled",
            "device_make": "Parallels Software International Inc.",
            "device_model": "Parallels Virtual Platform",
            "hostname": "TEST82A5",
            "managementID": "",
            "nsdeviceuid": "725DAC1A-6654-3F3A-971E-C984FBE9FE5E",
            "os": "Windows",
            "os_version": "10.0 (2009)"
        }
    }
}

Human Readable Output

Hosts Associated With test@goxsoar.com

Current page size: 50
Showing page 1 out of others that may exist.

hostname os_version agent_status
TEST82A5 10.0 (2009) Enabled

Configuration parameters

  • url — Server URL (required)
  • credentials — (required)
  • max_fetch — Maximum incidents per fetch. Max value is 200. (required)
  • first_fetch — First fetch timestamp (<number> <time unit>, like 12 hours, 7 days)
  • alerts_query — Alerts Query
  • fetch_events — Fetch Events
  • event_types — Event types to fetch.
  • max_events_fetch — Maximum events as incidents per fetch. Max value is 200.
  • events_query — Events Query
  • incidentType — Incident type
  • incidentFetchInterval — Incidents Fetch Interval
  • isFetch — Fetch incidents
  • insecure — Trust any certificate (not secure)
  • proxy — Use system proxy settings

Commands (10)

  • netskope-alert-list

    Get alerts generated by Netskope, including policy, DLP, and watch list alerts. You must provide start_time and end_time, or insertion_start_time and insertion_end_time, or timeperiod. Also, you cannot provide a combination of the options mentioned above.

  • netskope-client-list

    Get information about the Netskope clients.

  • netskope-event-list

    Get events extracted from SaaS traffic and or logs. You must provide start_time and end_time, or insertion_start_time and insertion_end_time, or timeperiod. Also, you cannot provide a combination of the options mentioned above.

  • netskope-file-hash-list-update

    Update file hash list with the values provided. The command will override the whole list content, rather than appending the new values.

  • netskope-host-associated-user-list

    List all users of certain host by its hostname.

  • netskope-quarantined-file-get

    Download a quarantined file.

  • netskope-quarantined-file-list

    List all quarantined files.

  • netskope-quarantined-file-update

    Take an action on a quarantined file.

  • netskope-url-list-update

    Update the URL List with the values provided. The command will override the whole list content, rather than appending the new values.

  • netskope-user-associated-host-list

    List all hosts related to a certain username.

from copy import deepcopy
from typing import Any

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

from CommonServerUserPython import *

# disable insecure warnings
urllib3.disable_warnings()

DEFAULT_PAGE = 1
DEFAULT_LIMIT = 50
DEFAULT_MAX_FETCH = DEFAULT_LIMIT
DEFAULT_EVENTS_FETCH = DEFAULT_LIMIT
DEFAULT_EVENT_TYPE = "application"
DEFAULT_FIRST_FETCH = "7 days"
MAX_LIMIT = 100
MAX_FETCH = 200
MAX_EVENTS_FETCH = 200

TIME_PERIOD_MAPPING = {
    "Last 60 Minutes": 3600,
    "Last 24 Hours": 86400,
    "Last 7 Days": 604800,
    "Last 30 Days": 2592000,
    "Last 60 Days": 5184000,
    "Last 90 Days": 7776000,
}


class Client(BaseClient):
    """
    Client for Netskope RESTful API.

    Args:
        base_url (str): The base URL of Netskope.
        token (str): The token to authenticate against Netskope API.
        use_ssl (bool): Specifies whether to verify the SSL certificate or not.
        use_proxy (bool): Specifies if to use XSOAR proxy settings.
    """

    def __init__(self, base_url: str, token: str, use_ssl: bool, use_proxy: bool):
        super().__init__(urljoin(base_url, "/api/v1/"), verify=use_ssl, proxy=use_proxy)
        self._session.params["token"] = token  # type: ignore

    def list_events_request(
        self,
        query: str | None = None,
        event_type: str | None = None,
        timeperiod: int | None = None,
        start_time: int | None = None,
        end_time: int | None = None,
        insertion_start_time: int | None = None,
        insertion_end_time: int | None = None,
        limit: int | None = None,
        skip: int | None = None,
        unsorted: bool | None = None,
    ) -> dict[str, Any]:
        """
        Get events extracted from SaaS traffic and or logs.

        Args:
            query (Optional[str]): Free query to filter the events.
            event_type (Optional[str]): Select events by their type.
            timeperiod (Optional[int]): Get all events from a certain time period.
            start_time (Optional[int]): Restrict events to those that have timestamps greater than the provided timestamp.
            end_time (Optional[int]): Restrict events to those that have timestamps less than or equal to the provided timestamp.
            insertion_start_time (Optional[int]): Restrict events to those that were inserted to the system
                after the provided timestamp.
            insertion_end_time (Optional[int]): Restrict events to those that were inserted to the system
                before the provided timestamp.
            limit (Optional[int]): The maximum amount of events to retrieve (up to 10000 events).
            skip (Optional[int]): The skip number of the events to retrieve (minimum is 1).
            unsorted (Optional[bool]): If true, the returned data will not be sorted (useful for improved performance).

        Returns:
            Dict[str, Any]: Netskope events.
        """

        body = remove_empty_elements(
            {
                "query": query,
                "type": event_type,
                "timeperiod": timeperiod,
                "starttime": start_time,
                "endtime": end_time,
                "insertionstarttime": insertion_start_time,
                "insertionendtime": insertion_end_time,
                "limit": limit,
                "skip": skip,
                "unsorted": unsorted,
            }
        )

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

    def list_alerts_request(
        self,
        query: str | None = None,
        alert_type: str | None = None,
        acked: bool | None = None,
        timeperiod: int | None = None,
        start_time: int | None = None,
        end_time: int | None = None,
        insertion_start_time: int | None = None,
        insertion_end_time: int | None = None,
        limit: int | None = None,
        skip: int | None = None,
        unsorted: bool | None = None,
    ) -> dict[str, Any]:
        """
        Get alerts generated by Netskope, including policy, DLP, and watch list alerts.

        Args:
            query (Optional[str]): Free query to filter the alerts.
            alert_type (Optional[str]): Select alerts by their type.
            acked (Optional[bool]): Whether to retrieve acknowledged alerts or not.
            timeperiod (Optional[int]): Get alerts from certain time period.
            start_time (Optional[int]): Restrict alerts to those that have timestamps greater than the provided timestamp.
            end_time (Optional[int]): Restrict alerts to those that have timestamps less than or equal to the provided timestamp.
            insertion_start_time (Optional[int]): Restrict alerts which have been inserted into the system
                after the provided timestamp.
            insertion_end_time (Optional[int]): Restrict alerts which have been inserted into the system
                before the provided timestamp.
            limit (Optional[int]): The maximum number of alerts to return (up to 10000).
            skip (Optional[int]): The skip number of the alerts to retrieve (minimum is 1).
            unsorted (Optional[bool]): If true, the returned data will not be sorted (useful for improved performance).

        Returns:
            Dict[str, Any]: Netskope alerts.
        """

        body = remove_empty_elements(
            {
                "query": query,
                "alert_type": alert_type,
                "acked": acked,
                "timeperiod": timeperiod,
                "starttime": start_time,
                "endtime": end_time,
                "insertionstarttime": insertion_start_time,
                "insertionendtime": insertion_end_time,
                "limit": limit,
                "skip": skip,
                "unsorted": unsorted,
            }
        )

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

    def list_quarantined_files_request(
        self, start_time: int | None = None, end_time: int | None = None, limit: int | None = None, skip: int | None = None
    ) -> dict[str, Any]:
        """
        List all quarantined files.

        Args:
            start_time (Optional[int]): Get files last modified within a certain time period.
            end_time (Optional[int]): Get files last modified within a certain time period.
            limit (Optional[int]): The maximum amount of clients to retrieve (up to 10000).
            skip (Optional[int]): The skip number of the clients to retrieve (minimum is 1).

        Returns:
            Dict[str, Any]: Netskope quarantine files.
        """

        body = remove_empty_elements(
            {"starttime": start_time, "endtime": end_time, "limit": limit, "skip": skip, "op": "get-files"}
        )

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

    def get_quarantined_file_request(self, quarantine_profile_id: str, file_id: str) -> bytes:
        """
        Download a quarantined file.

        Args:
            quarantine_profile_id (str): The ID of quarantine profile.
            file_id (str): The ID of the quarantined file.

        Returns:
            bytes: The quarantined file content.
        """

        body = {"quarantine_profile_id": quarantine_profile_id, "file_id": file_id, "op": "download-url"}

        return self._http_request(method="POST", url_suffix="quarantine", json_data=body, resp_type="content")

    def update_quarantined_file_request(self, quarantine_profile_id: str, file_id: str, action: str) -> None:
        """
        Take an action on a quarantined file.

        Args:
            quarantine_profile_id (str): The profile id of the quarantined file.
            file_id (str): The id of the quarantined file.
            action (str): Action to be performed on a quarantined.
        """

        body = {"quarantine_profile_id": quarantine_profile_id, "file_id": file_id, "action": action, "op": "take-action"}

        self._http_request(method="POST", url_suffix="quarantine", json_data=body, resp_type="text")

    def update_url_list_request(self, name: str, urls: list[str]) -> None:
        """
        Update the URL List with the values provided.

        Args:
            name (str): Name of an existing URL List shown in the Netskope UI on the URL List skip.
            urls (List[str]): The content of the URL list.
        """

        body = {"name": name, "list": ",".join(urls)}
        self._http_request(method="POST", url_suffix="updateUrlList", json_data=body)

    def update_file_hash_list_request(self, name: str, hashes: list[str]) -> None:
        """
        Update file hash list with the values provided.

        Args:
            name (str): Name of an existing file hash list shown in the Netskope UI on the file hash list skip.
            hashes (str): List of file hashes (md5 or sha256).
        """

        body = {"name": name, "list": ",".join(hashes)}
        return self._http_request(method="POST", url_suffix="updateFileHashList", json_data=body)

    def list_clients_request(self, query: str | None = None, limit: int | None = None, skip: int | None = None) -> dict[str, Any]:
        """
        Get information about the Netskope clients.

        Args:
            query (Optional[str]): Free query on the clients, based on the client fields.
            limit (Optional[int]): The maximum amount of clients to retrieve (up to 10000).
            skip (Optional[int]): The skip number of the clients to retrieve (minimum is 1).

        Returns:
            Dict[str, Any]: The clients information.
        """

        body = remove_empty_elements({"query": query, "limit": limit, "skip": skip})
        return self._http_request(method="POST", url_suffix="clients", params=body)

    def _http_request(self, *args, **kwargs):
        response = super()._http_request(*args, **kwargs)
        if isinstance(response, dict) and "errors" in response:
            errors = "\n".join(response["errors"])
            raise DemistoException(f"Invalid API call: {errors}", res=response)

        return response


def arg_to_boolean(arg: str | None) -> bool | None:
    """
    Converts an XSOAR argument to a Python boolean or None.

    Args:
        arg (Optional[str]): The argument to convert.

    Returns:
        Optional[bool]: A boolean if arg can be converted,
        or None if arg is None.
    """

    if arg is None:
        return None

    return argToBoolean(arg)


def arg_to_seconds_timestamp(arg: str | None):
    """
    Converts an XSOAR date string argument to a timestamp in seconds.

    Args:
        arg (Optional[str]): The argument to convert.

    Returns:
        Optional[int]: A timestamp if arg can be converted,
        or None if arg is None.
    """

    if arg is None:
        return None

    return date_to_seconds_timestamp(arg_to_datetime(arg))  # type: ignore


def date_to_seconds_timestamp(date_str_or_dt: Union[str, datetime]) -> int:
    """
    Converts date string or datetime object to a timestamp in seconds.

    Args:
        date_str_or_dt (Union[str, datetime]): The datestring or datetime.

    Returns:
        int: The timestamp in seconds.
    """

    return date_to_timestamp(date_str_or_dt) // 1000


def validate_time_arguments(
    start_time: int | None = None,
    end_time: int | None = None,
    insertion_start_time: int | None = None,
    insertion_end_time: int | None = None,
    timeperiod: int | None = None,
) -> None:
    """
    Validates time arguments from the user.
    The user must provide one of the following:
        - start_time and end_time.
        - insertion_start_time and insertion_end_time.
        - timeperiod.

    Args:
        start_time (Optional[int], optional): The start time to fetch from the API.
        end_time (Optional[int], optional): The end time to fetch from the API.
        insertion_start_time (Optional[int], optional): The insertion start time to fetch from the API.
        insertion_end_time (Optional[int], optional): The insertion end time to fetch from the API.
        timeperiod (Optional[str], optional): The timeperiod to fetch from the API.

    Raises:
        DemistoException: The user did not provide valid timestamp.
    """

    combination = (all((start_time, end_time)), all((insertion_start_time, insertion_end_time)), bool(timeperiod))

    if not any(combination):
        raise DemistoException(
            "Missing time arguments. Please provide start_time and end_time, "
            "or insertion_start_time and or insertion_end_time or timeperiod."
        )
    if combination.count(True) > 1:
        raise DemistoException(
            "Invalid time arguments. Please provide only start_time and end_time, "
            "or insertion_start_time and or insertion_end_time or timeperiod. "
            "You must not combine between the mentioned options."
        )


def validate_fetch_params(
    max_fetch: int, max_events_fetch: int, fetch_events: bool, first_fetch: str, event_types: list[str]
) -> None:
    """
    Validates the parameters for fetch incident command.

    Args:
        max_fetch: (int): The maximum number of incidents for one fetch.
        max_events_fetch (int) The maximum number of events per incident for one fetch.
        fetch_events (bool): Whether or not fetch events when fetching incident.
        first_fetch: (str): First fetch time in words.
    """

    if first_fetch:
        arg_to_datetime(first_fetch)  # verify that it is a date.

    if max_fetch > MAX_FETCH:
        return_error(f"The Maximum number of incidents per fetch should not exceed {MAX_FETCH}.")
    if fetch_events and max_events_fetch > MAX_EVENTS_FETCH:
        return_error(f"The Maximum number of events for each incident per fetch should not exceed {MAX_EVENTS_FETCH}.")

    if not isinstance(event_types, list):
        return_error("The fetched event types must be a list.")


def get_pagination_readable_message(header: str, page: int, limit: int) -> str:
    return f"{header}\n Current page size: {limit}\n Showing page {page} out of others that may exist."


def get_pagination_arguments(args: dict[str, Any]) -> tuple[int, int, int]:
    """
    Gets and validates pagination arguments for client (skip and limit).

    Args:
        args (Dict[str, Any]): The command arguments (page and limit).

    Returns:
        Tuple[int, int]: The page, calculated skip and limit after validation.
    """

    page = arg_to_number(args.get("page")) or DEFAULT_PAGE
    limit = arg_to_number(args.get("limit")) or DEFAULT_LIMIT

    if page < 1:
        raise DemistoException("Page argument must be greater than 1")
    if not 1 <= limit <= MAX_LIMIT:
        raise DemistoException(f"Limit argument must be between 1 to {MAX_LIMIT}")

    return page, (page - 1) * limit, limit


def list_events_command(client: Client, args: dict[str, str]) -> CommandResults:
    """
    Get events extracted from SaaS traffic and or logs.

    Args:
        client (client): The Netskope client.
        args (Dict[str, Any]): Command arguments from XSOAR.

    Returns:
        CommandResults: Command results with raw response, outputs and readable outputs.
    """

    query = args.get("query")
    event_type = args["event_type"]
    timeperiod = TIME_PERIOD_MAPPING.get(args.get("timeperiod", ""))
    start_time = arg_to_seconds_timestamp(args.get("start_time"))
    end_time = arg_to_seconds_timestamp(args.get("end_time"))
    insertion_start_time = arg_to_seconds_timestamp(args.get("insertion_start_time"))
    insertion_end_time = arg_to_seconds_timestamp(args.get("insertion_end_time"))
    page, skip, limit = get_pagination_arguments(args)
    unsorted = arg_to_boolean(args.get("unsorted"))

    validate_time_arguments(
        start_time=start_time,
        end_time=end_time,
        timeperiod=timeperiod,
        insertion_start_time=insertion_start_time,
        insertion_end_time=insertion_end_time,
    )

    response = client.list_events_request(
        query=query,
        event_type=event_type,
        timeperiod=timeperiod,
        start_time=start_time,
        end_time=end_time,
        insertion_start_time=insertion_start_time,
        insertion_end_time=insertion_end_time,
        limit=limit,
        skip=skip,
        unsorted=unsorted,
    )
    outputs = deepcopy(response["data"])
    for event in outputs:
        event["event_id"] = event["_id"]
        event["timestamp"] = timestamp_to_datestring(event["timestamp"] * 1000)

    readable_output = tableToMarkdown(
        get_pagination_readable_message("Events List:", page=page, limit=limit),
        outputs,
        removeNull=True,
        headers=["event_id", "timestamp", "type", "access_method", "app", "traffic_type"],
        headerTransform=string_to_table_header,
    )

    return CommandResults(
        outputs_prefix="Netskope.Event",
        outputs_key_field="event_id",
        outputs=outputs,
        readable_output=readable_output,
        raw_response=response,
    )


def list_alerts_command(client: Client, args: dict[str, str]) -> CommandResults:
    """
    Get alerts generated by Netskope, including policy, DLP, and watch list alerts.

    Args:
        client (client): The Netskope client.
        args (Dict[str, Any]): Command arguments from XSOAR.

    Returns:
        CommandResults: Command results with raw response, outputs and readable outputs.
    """

    query = args.get("query")
    alert_type = args.get("alert_type")
    acked = arg_to_boolean(args.get("acked"))
    timeperiod = TIME_PERIOD_MAPPING.get(args.get("timeperiod", ""))
    start_time = arg_to_seconds_timestamp(args.get("start_time"))
    end_time = arg_to_seconds_timestamp(args.get("end_time"))
    insertion_start_time = arg_to_seconds_timestamp(args.get("insertion_start_time"))
    insertion_end_time = arg_to_seconds_timestamp(args.get("insertion_end_time"))
    page, skip, limit = get_pagination_arguments(args)
    unsorted = arg_to_boolean(args.get("unsorted"))

    validate_time_arguments(
        start_time=start_time,
        end_time=end_time,
        timeperiod=timeperiod,
        insertion_start_time=insertion_start_time,
        insertion_end_time=insertion_end_time,
    )

    response = client.list_alerts_request(
        query=query,
        alert_type=alert_type,
        acked=acked,
        timeperiod=timeperiod,
        start_time=start_time,
        end_time=end_time,
        insertion_start_time=insertion_start_time,
        insertion_end_time=insertion_end_time,
        limit=limit,
        skip=skip,
        unsorted=unsorted,
    )
    outputs = deepcopy(response["data"])
    for alert in outputs:
        alert["alert_id"] = alert["_id"]
        alert["timestamp"] = timestamp_to_datestring(alert["timestamp"] * 1000)

    readable_output = tableToMarkdown(
        get_pagination_readable_message("Alerts List:", page=page, limit=limit),
        outputs,
        removeNull=True,
        headers=["alert_id", "alert_name", "alert_type", "timestamp", "action"],
        headerTransform=string_to_table_header,
    )

    return CommandResults(
        outputs_prefix="Netskope.Alert",
        outputs_key_field="alert_id",
        outputs=outputs,
        readable_output=readable_output,
        raw_response=response,
    )


def list_quarantined_files_command(client: Client, args: dict[str, str]) -> CommandResults:
    """
    List all quarantined files.

    Args:
        client (client): The Netskope client.
        args (Dict[str, Any]): Command arguments from XSOAR.

    Returns:
        CommandResults: Command results with raw response, outputs and readable outputs.
    """

    start_time = arg_to_seconds_timestamp(args.get("start_time"))
    end_time = arg_to_seconds_timestamp(args.get("end_time"))
    page, skip, limit = get_pagination_arguments(args)

    response = client.list_quarantined_files_request(start_time=start_time, end_time=end_time, limit=limit, skip=skip)

    outputs = dict_safe_get(response, ["data", "quarantined"])  # type: ignore
    for output in outputs:  # type: ignore
        for file_output in output["files"]:
            file_output["quarantine_profile_id"] = output["quarantine_profile_id"]
            file_output["quarantine_profile_name"] = output["quarantine_profile_name"]
    outputs: list = sum((output["files"] for output in outputs), [])

    readable_header = get_pagination_readable_message("Quarantined Files List:", page=page, limit=limit)
    readable_output = tableToMarkdown(
        readable_header,
        outputs,
        removeNull=True,
        headers=["quarantine_profile_id", "quarantine_profile_name", "file_id", "original_file_name", "policy"],
        headerTransform=string_to_table_header,
    )

    return CommandResults(
        outputs_prefix="Netskope.Quarantine",
        outputs_key_field="file_id",
        outputs=outputs,
        readable_output=readable_output,
        raw_response=response,
    )


def get_quarantined_file_command(client: Client, args: dict[str, str]) -> CommandResults:
    """
    Download a quarantined file.

    Args:
        client (client): The Netskope client.
        args (Dict[str, Any]): Command arguments from XSOAR.

    Returns:
        CommandResults: Command results with raw response, outputs and readable outputs.
    """

    quarantine_profile_id = args["quarantine_profile_id"]
    file_id = args["file_id"]
    file_name = args.get("file_name", file_id)
    response = client.get_quarantined_file_request(quarantine_profile_id=quarantine_profile_id, file_id=file_id)

    return fileResult(filename=file_name, data=response, file_type=EntryType.FILE)


def update_quarantined_file_command(client: Client, args: dict[str, str]) -> CommandResults:
    """
    Take an action on a quarantined file.

    Args:
        client (client): The Netskope client.
        args (Dict[str, Any]): Command arguments from XSOAR.

    Returns:
        CommandResults: Command results with raw response, outputs and readable outputs.
    """

    quarantine_profile_id = args["quarantine_profile_id"]
    file_id = args["file_id"]
    action = args["action"]

    client.update_quarantined_file_request(quarantine_profile_id=quarantine_profile_id, file_id=file_id, action=action)
    readable_output = f"## The file {file_id} was successfully {action}ed!"

    return CommandResults(readable_output=readable_output)


def update_url_list_command(client: Client, args: dict[str, str]) -> CommandResults:
    """
    Update the URL List with the values provided.

    Args:
        client (client): The Netskope client.
        args (Dict[str, Any]): Command arguments from XSOAR.

    Returns:
        CommandResults: Command results with raw response, outputs and readable outputs.
    """

    name = args["name"]
    urls = argToList(args["urls"])

    client.update_url_list_request(name=name, urls=urls)

    outputs = {"name": name, "URL": urls}
    readable_output = f'URL List {name}:\n{", ".join(urls)}'

    return CommandResults(
        outputs_prefix="Netskope.URLList", outputs_key_field="name", outputs=outputs, readable_output=readable_output
    )


def update_file_hash_list_command(client: Client, args: dict[str, str]) -> CommandResults:
    """
    Update file hash list with the values provided.

    Args:
        client (client): The Netskope client.
        args (Dict[str, Any]): Command arguments from XSOAR.

    Returns:
        CommandResults: Command results with raw response, outputs and readable outputs.
    """

    name = args.get("name", "")
    hashes = argToList(args.get("hash"))

    client.update_file_hash_list_request(name=name, hashes=hashes)

    outputs = {"name": name, "hash": hashes}
    readable_output = f'Hash List {name}:\n{", ".join(hashes)}'

    return CommandResults(
        outputs_prefix="Netskope.FileHashList", outputs_key_field="name", outputs=outputs, readable_output=readable_output
    )


def list_clients_command(client: Client, args: dict[str, str]) -> CommandResults:
    """
    Get information about the Netskope clients.

    Args:
        client (client): The Netskope client.
        args (Dict[str, Any]): Command arguments from XSOAR.

    Returns:
        CommandResults: Command results with raw response, outputs and readable outputs.
    """

    query = args.get("query")
    page, skip, limit = get_pagination_arguments(args)

    response = client.list_clients_request(query=query, limit=limit, skip=skip)
    outputs = [client["attributes"] for client in response["data"]]
    for output in outputs:
        output["client_id"] = output["_id"]

    readable_header = get_pagination_readable_message("Clients List:", page=page, limit=limit)
    readable_output = tableToMarkdown(
        readable_header,
        outputs,
        removeNull=True,
        headers=["client_id", "client_version", "device_id", "user_added_time"],
        headerTransform=string_to_table_header,
    )
    return CommandResults(
        outputs_prefix="Netskope.Client",
        outputs_key_field="client_id",
        outputs=outputs,
        readable_output=readable_output,
        raw_response=response,
    )


def list_host_associated_user_command(client: Client, args: dict[str, str]) -> CommandResults:
    """
    List all users of certain host by its hostname.

    Args:
        client (client): The Netskope client.
        args (Dict[str, Any]): Command arguments from XSOAR.

    Returns:
        CommandResults: Command results with raw response, outputs and readable outputs.
    """

    hostname = args["hostname"]
    page, skip, limit = get_pagination_arguments(args)

    response = client.list_clients_request(query=f"host_info.hostname eq {hostname}", limit=limit, skip=skip)

    outputs: list = sum((client["attributes"].get("users") for client in response["data"]), [])
    for output in outputs:
        output["user_id"] = output["_id"]

    readable_header = get_pagination_readable_message(f"Users Associated With {hostname}:", page=page, limit=limit)
    readable_output = tableToMarkdown(
        readable_header,
        outputs,
        removeNull=True,
        headers=["user_id", "username", "user_source"],
        headerTransform=string_to_table_header,
    )

    return CommandResults(
        outputs_prefix="Netskope.User",
        outputs_key_field="user_id",
        outputs=outputs,
        readable_output=readable_output,
        raw_response=response,
    )


def list_user_associated_host_command(client: Client, args: dict[str, str]) -> CommandResults:
    """
    List all hosts related to a certain username.

    Args:
        client (client): The Netskope client.
        args (Dict[str, Any]): Command arguments from XSOAR.

    Returns:
        CommandResults: Command results with raw response, outputs and readable outputs.
    """

    username = args["username"]
    page, skip, limit = get_pagination_arguments(args)

    response = client.list_clients_request(query=f"username eq {username}", limit=limit, skip=skip)

    outputs = []
    for client in response["data"]:
        attributes = client["attributes"]
        agent_status = dict_safe_get(attributes, ["last_event", "status"])
        outputs.append({"agent_status": agent_status, **attributes["host_info"]})

    readable_header = get_pagination_readable_message(f"Hosts Associated With {username}:", page=page, limit=limit)
    readable_output = tableToMarkdown(
        readable_header,
        outputs,
        removeNull=True,
        headers=["hostname", "os_version", "agent_status"],
        headerTransform=string_to_table_header,
    )

    return CommandResults(
        outputs_prefix="Netskope.Host",
        outputs_key_field="nsdeviceuid",
        outputs=outputs,
        readable_output=readable_output,
        raw_response=response,
    )


def test_module(
    client: Client, max_fetch: int, first_fetch: str, fetch_events: bool, max_events_fetch: int, event_types: list[str]
) -> str:
    """
    Validates all integration parameters, and tests connection to Netskope instance.
    """

    validate_fetch_params(max_fetch, max_events_fetch, fetch_events, first_fetch, event_types)
    client.list_alerts_request(
        limit=1, skip=0, start_time=date_to_seconds_timestamp(datetime.now()), end_time=date_to_seconds_timestamp(datetime.now())
    )
    return "ok"


def fetch_multiple_type_events(
    client: Client, max_fetch: int, start_time: int, event_types: list[str], query: str | None
) -> list[dict[str, Any]]:
    """
    Fetches events from multiple types.
    The function makes an API call for each type, since the API requires
    specifying the event type.

    Args:
        client (Client): The Netskope client.
        max_fetch (int): The maximum amount of events to fetch for each type.
        start_time (int): The time to fetch the events from.
        event_types (List[str]): The event types to fetch as incidents.
        query (Optional[str]): Query for filtering the events.

    Returns:
        List[Dict[str, Any]]: The fetched events.
    """

    events = []
    if event_types:
        max_fetch = max_fetch // len(event_types)

    for event_type in event_types:
        new_events = client.list_events_request(
            start_time=start_time,
            end_time=date_to_seconds_timestamp(datetime.now()),
            limit=max_fetch,
            unsorted=False,
            event_type=event_type,
            query=query,
        )["data"]
        for event in new_events:
            event["event_id"] = event["_id"]
            event["incident_type"] = event_type

        events.extend(new_events)

    return events


def fetch_incidents(
    client: Client,
    max_fetch: int,
    first_fetch: str,
    fetch_events: bool,
    max_events_fetch: int,
    event_types: list[str],
    alerts_query: str | None,
    events_query: str | None,
) -> None:
    """
    Fetches alerts and events as incidents.

    Args:
        client (Client): The Netskope client.
        max_fetch (int): Maximum number of incidents to fetch.
        first_fetch (str): The timestamp to fetch the incidents from.
        max_events_fetch (int): Maximum number of events to fetch.
        event_types (List[str]): The type of events to fetch.
        alerts_query (Optional[str]): Query for filtering the fetched alerts.
        events_query (Optional[str]): Query for filtering the fetched events.
    """

    validate_fetch_params(max_fetch, max_events_fetch, fetch_events, first_fetch, event_types)

    last_run = demisto.getLastRun() or {}
    first_fetch: int = arg_to_seconds_timestamp(first_fetch)

    last_alert_time = last_run.get("last_alert_time") or first_fetch
    alerts = client.list_alerts_request(
        start_time=last_alert_time,
        end_time=date_to_seconds_timestamp(datetime.now()),
        limit=max_fetch,
        query=alerts_query,
        unsorted=False,
    )["data"]

    last_event_time = last_run.get("last_event_time") or first_fetch
    if fetch_events:
        events = fetch_multiple_type_events(
            client, max_fetch=max_events_fetch, start_time=last_event_time, event_types=event_types, query=events_query
        )
    else:
        events = []

    incidents = []
    for alert in alerts:
        alert["incident_type"] = alert["alert_type"]

        incidents.append(
            {"name": alert["alert_name"], "occurred": timestamp_to_datestring(alert["timestamp"]), "rawJSON": json.dumps(alert)}
        )

    for event in events:
        incidents.append(
            {"name": event["event_id"], "occurred": timestamp_to_datestring(event["timestamp"]), "rawJSON": json.dumps(event)}
        )

    # The alerts and events are sorted in descending order.
    # Also, we increment the timestamp in one second to avoid duplicates.
    demisto.setLastRun(
        {
            "last_alert_time": alerts[0]["timestamp"] + 1 if alerts else last_alert_time,
            "last_event_time": events[0]["timestamp"] + 1 if events else last_event_time,
        }
    )
    demisto.incidents(incidents)


def main():
    params = demisto.params()

    url = params["url"]

    token = params["credentials"]["password"]
    use_ssl = not params.get("insecure", False)
    use_proxy = params.get("proxy", False)
    max_fetch = arg_to_number(params.get("max_fetch")) or DEFAULT_MAX_FETCH
    first_fetch = params.get("first_fetch", DEFAULT_FIRST_FETCH)
    fetch_events = argToBoolean(params.get("fetch_events", False))
    event_types = argToList(params.get("fetch_event_types", DEFAULT_EVENT_TYPE))
    max_events_fetch = arg_to_number(params.get("max_events_fetch")) or DEFAULT_EVENTS_FETCH

    client = Client(url, token, use_ssl, use_proxy)

    commands = {
        "netskope-event-list": list_events_command,
        "netskope-alert-list": list_alerts_command,
        "netskope-quarantined-file-list": list_quarantined_files_command,
        "netskope-quarantined-file-get": get_quarantined_file_command,
        "netskope-quarantined-file-update": update_quarantined_file_command,
        "netskope-url-list-update": update_url_list_command,
        "netskope-file-hash-list-update": update_file_hash_list_command,
        "netskope-client-list": list_clients_command,
        "netskope-host-associated-user-list": list_host_associated_user_command,
        "netskope-user-associated-host-list": list_user_associated_host_command,
    }

    try:
        command = demisto.command()

        if command == "test-module":
            return_results(
                test_module(
                    client,
                    max_fetch=max_fetch,
                    first_fetch=first_fetch,
                    fetch_events=fetch_events,
                    max_events_fetch=max_events_fetch,
                    event_types=event_types,
                )
            )
        elif command == "fetch-incidents":
            fetch_incidents(
                client,
                max_fetch=max_fetch,
                first_fetch=first_fetch,
                fetch_events=fetch_events,
                max_events_fetch=max_events_fetch,
                event_types=event_types,
                alerts_query=demisto.params().get("alerts_query"),
                events_query=demisto.params().get("events_query"),
            )
        elif command in commands:
            return_results(commands[command](client, demisto.args()))
        else:
            raise NotImplementedError(f"The command {command} does not exist!")
    except Exception as e:
        return_error(f"Failed to execute {demisto.command()} command.\nError:\n{e}")


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