Signal Sciences WAF

Protect your web application using Signal Sciences.

Network Security · Signal Sciences WAF

Details

IDSignal Sciences WAF
ProviderFastly
CategoryNetwork Security
From Version5.0.0
Docker Imagedemisto/python3:3.12.13.10116658
Supported ModulesAgentix XSIAM EDR Cortex Cloud Cloud Runtime Security

README

Use the Signal Sciences next-gen web application firewall to increase security and maintain reliability.

Configure Signal Sciences WAF on Cortex XSOAR

  1. Navigate to Settings > Integrations > Servers & Services.
  2. Search for Signal Sciences WAF.
  3. Click Add instance to create and configure a new integration instance.
    • Name: a textual name for the integration instance.
    • Email
    • Token
    • Corporation Name (should match the pattern [0-9a-z_.-]+)
    • Fetch incidents
    • Incident type
    • Fetch Interval (in minutes)
    • CSV list of sites to fetch from. If no sites are specified, events from all corporation’s sites will be fetched.
  4. Click Test to validate the URLs, token, and connection.

Commands

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

1. Get a site’s allow list


Fetches a site’s allow list, which resides on the Signal Sciences’ platform.

Base Command

sigsci-get-whitelist

Input
Argument Name Description Required
siteName The site that holds the allow list you want to get. Required

 

Context Output
Path Type Description
SigSciences.Corp.Site.Whitelist.ID string ID for this particular entry.
SigSciences.Corp.Site.Whitelist.Source string IP Address present in the allow list.
SigSciences.Corp.Site.Whitelist.ExpiryDate date Expiration Timestamp.
SigSciences.Corp.Site.Whitelist.Note string Note associated with the tag.
SigSciences.Corp.Site.Whitelist.CreatedDate date The created date timestamp
SigSciences.Corp.Site.Whitelist.CreatedBy string The user who added this source.

 

Command Example
!sigsci-get-whitelist siteName=demisto
Context Example
{
    "SigSciences.Corp.Site.Whitelist": [
        {
            "ExpiryDate": "",
            "Note": "docdoc",
            "Source": "9.7.9.7",
            "CreatedBy": "user@demisto.com",
            "CreatedDate": "2019-03-28T09:52:47Z",
            "ID": "5c9c996fc7213901971d5679"
        }
    ]
}
Human Readable Output

Signal Sciences - Whitelist

Number of IPs in the allow list 1

Source Note Created Date Created By
9.7.9.7 docdoc 2019-03-28T09:52:47Z user@demisto.com

 

2. Get a site’s blacklist: sigsci-get-blacklist


Fetches a site’s block list, which resides on the Signal Sciences’ platform.

Base Command

sigsci-get-blacklist

Input
Argument Name Description Required
siteName The site that holds the block list you wish to get Required

 

Context Output
Path Type Description
SigSciences.Corp.Site.Blacklist.ID string The ID for this entry.
SigSciences.Corp.Site.Blacklist.Source string The IP address on the blacklist.
SigSciences.Corp.Site.Blacklist.ExpiryDate date The expiration timestamp.
SigSciences.Corp.Site.Blacklist.Note string The note associated with the tag.
SigSciences.Corp.Site.Blacklist.CreatedDate date The created date timestamp.
SigSciences.Corp.Site.Blacklist.CreatedBy string The user who added this source.

 

Command Example
!sigsci-get-blacklist siteName=demisto
Context Example
{
    "SigSciences.Corp.Site.Blacklist": [
        {
            "ExpiryDate": "",
            "Note": "docdoc",
            "Source": "5.7.5.7",
            "CreatedBy": "user@demisto.com",
            "CreatedDate": "2019-03-28T09:52:49Z",
            "ID": "5c9c9971719dcc0198a38a5b"
        }
    ]
}
Human Readable Output

Signal Sciences - Blacklist

Number of IPs in the block list 1

Source Note Created Date Created By
5.7.5.7 docdoc 2019-03-28T09:52:49Z user@demisto.com

 

3. Add an IP address to an allow list: sigsci-whitelist-add-ip


Adds an IP address to a site’s allow list, which resides the Signal Sciences’ platform.

Base Command

sigsci-whitelist-add-ip

Input
Argument Name Description Required
siteName The site that holds the allow list you want to add an IP address to. Required
ip The IP address to add to the site’s allow list in CSV format. Required
note The note associated with the tag. Required
expires The datetime for the IP address to removed from the site’s allow list (in RFC3339 format). To keep the IP address on the site’s allow list indefinitely, do not specify this argument. Optional

 

Context Output
Path Type Description
SigSciences.Corp.Site.Whitelist.Source string The IP address on the allow list.
SigSciences.Corp.Site.Whitelist.Note string The note associated with the tag.
SigSciences.Corp.Site.Whitelist.ID string The ID for this entry.
SigSciences.Corp.Site.Whitelist.CreatedBy string The user who added this source.
SigSciences.Corp.Site.Whitelist.CreatedDate date The created date timestamp
SigSciences.Corp.Site.Whitelist.ExpiryDate date The expiration timestamp.

 

Command Example
!sigsci-whitelist-add-ip ip=9.7.9.7 note=docdoc siteName=demisto
Context Example
{
    "SigSciences.Corp.Site.Whitelist": {
        "ExpiryDate": "",
        "Note": "docdoc",
        "Source": "9.7.9.7",
        "CreatedBy": "user@demisto.com",
        "CreatedDate": "2019-03-28T13:30:34Z",
        "ID": "5c9ccc7a342a24019743d265"
    }
}
Human Readable Output

Signal Sciences - Adding an IP to allow list

The IP 9.7.9.7 has been successfully added to allow list.

Source Note Expiration date
9.7.9.7 docdoc Not Set

 

4. Add an IP address to a block list: sigsci-blacklist-add-ip


Adds an IP to a site’s block list, which resides on the Signal Sciences’ platform.

Base Command

sigsci-blacklist-add-ip

Input
Argument Name Description Required
siteName The site that holds the block list you wish to add to Required
ip The IP address to add to the site’s block list in CSV format. Required
note The note associated with the tag. Required
expires The datetime for the IP address to removed from the site’s block list (in RFC3339 format). To keep the IP address on the site’s block list indefinitely, do not specify this argument. Optional

 

Context Output
Path Type Description
SigSciences.Corp.Site.Blacklist.Source string The IP address on the blacklist.
SigSciences.Corp.Site.Blacklist.Note string The note associated with the tag.
SigSciences.Corp.Site.Blacklist.ID string The ID for this entry.
SigSciences.Corp.Site.Blacklist.CreatedBy string The user who added this source.
SigSciences.Corp.Site.Blacklist.CreatedDate date The created date timestamp.
SigSciences.Corp.Site.Blacklist.ExpiryDate date The expiration timestamp.

 

Command Example
!sigsci-blacklist-add-ip ip=5.7.5.7 note=docdoc siteName=demisto
Context Example
{
    "SigSciences.Corp.Site.Blacklist": {
        "ExpiryDate": "",
        "Note": "docdoc",
        "Source": "5.7.5.7",
        "CreatedBy": "user@demisto.com",
        "CreatedDate": "2019-03-28T13:30:35Z",
        "ID": "5c9ccc7bf7c34301986b1d82"
    }
}
Human Readable Output

Signal Sciences - Adding an IP to block list

The IP 5.7.5.7 has been successfully added to block list.

Source Note Expiration date
5.7.5.7 docdoc Not Set

 

5. Remove an IP address from an allow list: sigsci-whitelist-remove-ip


Removes an IP address from a site’s allow list, which resides on the Signal Sciences’ platform.

Base Command

sigsci-whitelist-remove-ip

Input
Argument Name Description Required
siteName The site that contains the allow list you want to remove an IP address from. Required
IP The IP address entry to remove. Required

 

Context Output

There is no context output for this command.

Command Example
!sigsci-whitelist-remove-ip IP=4.7.4.7 siteName=demisto

6. Remove an IP address from a block list


Removes an IP from a site’s block list, which resides on the Signal Sciences’ platform.

Base Command

sigsci-blacklist-remove-ip

Input
Argument Name Description Required
siteName The site that contains the block list you want to remove an IP address from. Required
IP The IP address entry to remove. Required

 

Context Output

There is no context output for this command.

Command Example
!sigsci-blacklist-remove-ip IP=6.8.6.8 siteName=demisto

7. Get all site names


Returns all site names from the Signal Sciences platform.

Base Command

sigsci-get-sites

Input

There are no input arguments for this command.

Context Output
Path Type Description
SigSciences.Site.Name string The site name.
SigSciences.Site.CreatedDate date The site creation date.

 

8. Create a list for a site


Creates a new list for a given site on the Signal Sciences platform.

Base Command

sigsci-create-site-list

Input
Argument Name Description Required
siteName The name of the site in Signal Sciences you want to add a list to. Required
list_name The name of the list to create on Signal Sciences. Required
list_type The type for the list you wish to create on Signal Sciences. Legal types are IP, String, Country or Wildcard Required
entries_list A CSV list of values, consistent with the list’s type. Required
description Short text that describes the new list. Optional

 

Context Output
Path Type Description
SigSciences.Corp.Site.List.Name string The name of the list.
SigSciences.Corp.Site.List.Type string The type of the list.
SigSciences.Corp.Site.List.Entries unknown The entry list of the list.
SigSciences.Corp.Site.List.ID string The ID of the list.
SigSciences.Corp.Site.List.Description string The description of the list.
SigSciences.Corp.Site.List.CreatedBy string The creator of the list.
SigSciences.Corp.Site.List.CreatedDate string The creation date of the list.
SigSciences.Corp.Site.List.UpdatedDate string The last update date of the list.

 

Command Example
!sigsci-create-site-list entries_list=56.1.1.1 list_name=sitelistdoc list_type=IP siteName=demisto

9. Get information for a site list


Returns all the data about a site list.

Base Command

sigsci-get-site-list

Input
Argument Name Description Required
siteName The name of the site in Signal Sciences that the list you’re searching for belongs to. Required
list_id The ID of the list. Required

 

Context Output
Path Type Description
SigSciences.Corp.Site.List.Name string The name of the list.
SigSciences.Corp.Site.List.Type string The type of the list.
SigSciences.Corp.Site.List.Entries unknown The entry list of the list.
SigSciences.Corp.Site.List.ID unknown The ID of the list.
SigSciences.Corp.Site.List.Description unknown The description of the list.
SigSciences.Corp.Site.List.CreatedBy unknown The creator of the list.
SigSciences.Corp.Site.List.CreatedDate unknown The creation date of the list.
SigSciences.Corp.Site.List.UpdatedDate unknown The last update date of the list.

 

Command Example
!sigsci-get-site-list list_id=site.sitelistdoc3 siteName=demisto
Context Example
{
    "SigSciences.Corp.Site.List": {
        "Name": "sitelistdoc3",
        "CreatedDate": "2019-03-28T09:20:34Z",
        "UpdatedDate": "2019-03-28T09:53:01Z",
        "CreatedBy": "user@demisto.com",
        "Entries": [
            "6.1.7.1",
            "77.8.77.8"
        ],
        "Type": "ip",
        "ID": "site.sitelistdoc3",
        "Description": ""
    }
}
Human Readable Output

Found data about list with ID: site.sitelistdoc3

Name ID Type Entries Created By Created Date Updated Date
sitelistdoc3 site.sitelistdoc3 ip 6.1.7.1,
77.8.77.8
user@demisto.com 2019-03-28T09:20:34Z 2019-03-28T09:53:01Z

 

10. Delete a site list


Deletes a site list in Signal Sciences.

Base Command

sigsci-delete-site-list

Input
Argument Name Description Required
siteName The name of the site in Signal Sciences you want to delete a list from. Required
list_id The ID of the list in Signal Sciences to delete. Required

 

Context Output

There is no context output for this command.

Command Example
!sigsci-delete-site-list list_id=site.sitelistdoc2 siteName=demisto

11. Update a site list


Updates a site list in Signal Sciences.

Base Command

sigsci-update-site-list

Input
Argument Name Description Required
siteName The name of the site in Signal Sciences that the list to update belongs to. Required
list_id The ID of the list to update. Required
method The method to use, must be “Add” or “Remove”. The method will determine whether the given entries are added to or removed from the list. Required
entries_list A CSV list of values, consistent with the list’s type. Required
description A description for the updated list. Optional

 

Context Output
Path Type Description
SigSciences.Corp.Site.List.Name string The name of the list in Signal Sciences.
SigSciences.Corp.Site.List.Type string The type of the list in Signal Sciences.
SigSciences.Corp.Site.List.Entries unknown The entry list of the list in Signal Sciences.
SigSciences.Corp.Site.List.ID string The ID of the list in Signal Sciences.
SigSciences.Corp.Site.List.Description string The description of the list in Signal Sciences. Maximum is 140 characters.
SigSciences.Corp.Site.List.CreatedBy string The creator of the list in Signal Sciences.
SigSciences.Corp.Site.List.CreatedDate string The creation date of the list in Signal Sciences.
SigSciences.Corp.Site.List.UpdatedDate string The last update date of the list in Signal Sciences.

 

Command Example
!sigsci-update-site-list entries_list=77.8.77.8 list_id=site.sitelistdoc3 method=Add siteName=demisto
Context Example
{
    "SigSciences.Corp.Site.List": {
        "Name": "sitelistdoc3",
        "CreatedDate": "2019-03-28T09:20:34Z",
        "UpdatedDate": "2019-03-28T09:53:01Z",
        "CreatedBy": "user@demisto.com",
        "Entries": [
            "6.1.7.1",
            "77.8.77.8"
        ],
        "Type": "ip",
        "ID": "site.sitelistdoc3",
        "Description": ""
    }
}
Human Readable Output

Signal Sciences - Updating a list

Name ID Type Entries Created By Created Date Updated Date
sitelistdoc3 site.sitelistdoc3 ip 6.1.7.1,
77.8.77.8
user@demisto.com 2019-03-28T09:20:34Z 2019-03-28T09:53:01Z

 

12. Add an alert to a site


Adds a custom alert to a site in Signal Sciences.

Base Command

sigsci-add-alert

Input
Argument Name Description Required
siteName The name of the site to add an alert to. Required
long_name A human readable description of the alert. Must be between 3 and 25 characters. Required
interval The number of minutes of past traffic to examine. Must be 1, 10 or 60. Required
tag_name The name of the tag whose occurrences the alert is watching. Must match an existing tag. Required
threshold The number of occurrences of the tag in the specified interval that are required to trigger the alert. Required
enabled A flag to toggle this alert (“True” or “False”). Required
action A flag that describes what happens when the alert is triggered. “info” creates an incident in the dashboard. “flagged” creates an incident and blocks traffic for 24 hours. Required

 

Context Output
Path Type Description
SigSciences.Corp.Site.Alert.ID string The unique ID of the alert.
SigSciences.Corp.Site.Alert.SiteID string The ID of the site.
SigSciences.Corp.Site.Alert.TagName string The name of the tag whose occurrences the alert is watching.
SigSciences.Corp.Site.Alert.LongName string A short description of the alert.
SigSciences.Corp.Site.Alert.Interval number The number of minutes of past traffic to examine.
SigSciences.Corp.Site.Alert.Threshold number The number of occurrences of the tag in the specified interval that are required to trigger the alert.
SigSciences.Corp.Site.Alert.BlockDurationSeconds number The number of seconds that this alert is active.
SigSciences.Corp.Site.Alert.SkipNotifications boolean A flag to disable external notifications - Slack, webhooks, emails, and so on.
SigSciences.Corp.Site.Alert.Enabled boolean A flag to toggle this alert.
SigSciences.Corp.Site.Alert.Action string A flag that describes what happens when the alert is triggered.
SigSciences.Corp.Site.Alert.CreatedDate date The timestamp of event (RFC3339 format).

 

Command Example
!sigsci-add-alert action='info' enabled=False interval=60 long_name=tttt siteName=demisto tag_name=USERAGENT threshold=100

13. Get information for an alert


Retrieves data for an alert.

Base Command

sigsci-get-alert

Input
Argument Name Description Required
siteName The name of the site the alert you want to get data for belongs to. Required
alert_id The ID of the alert to retrieve. Required

 

Context Output
Path Type Description
SigSciences.Corp.Site.Alert.ID string The unique ID of the alert.
SigSciences.Corp.Site.Alert.SiteID string The ID of the site.
SigSciences.Corp.Site.Alert.TagName string The name of the tag whose occurrences the alert is watching.
SigSciences.Corp.Site.Alert.LongName string A short description of the alert.
SigSciences.Corp.Site.Alert.Interval number The number of minutes of past traffic to examine.
SigSciences.Corp.Site.Alert.Threshold number The number of occurrences of the tag in the specified interval that are required to trigger the alert.
SigSciences.Corp.Site.Alert.BlockDurationSeconds number The number of seconds this alert is active.
SigSciences.Corp.Site.Alert.SkipNotifications boolean A flag to disable external notifications - Slack, webhooks, emails, and so on.
SigSciences.Corp.Site.Alert.Enabled boolean A flag to toggle this alert.
SigSciences.Corp.Site.Alert.Action string A flag that describes what happens when the alert is triggered.
SigSciences.Corp.Site.Alert.CreatedDate date The timestamp of the event (RFC3339 format).

 

Command Example
!sigsci-get-alert alert_id=5b8ba7fd7a54b34f0c0f12cc siteName=demisto
Human Readable Output

14. Delete an alert from a site


Deletes an alert from a given site in Signal Sciences.

Base Command

sigsci-delete-alert

Input
Argument Name Description Required
siteName The name of the site to delete an alert from. Required
alert_id The ID of the alert to delete. Required

 

Context Output

There is no context output for this command.

Command Example
!sigsci-delete-alert alert_id=5b8ba7fd7a54b34f0c0f12cc siteName=demisto

15. Update attributes for an alert


Updates the attributes of a given alert.

Base Command

sigsci-update-alert

Input
Argument Name Description Required
siteName The site of the alert to update. Required
alert_id The ID of the alert to update. Required
tag_name The name of the tag whose occurrences the alert is watching. Required
long_name A human readable description of the alert. Must be between 3 and 25 characters. Required
interval The number of minutes of past traffic to examine. Required
threshold The number of occurrences of the tag in the specified interval that are required to trigger the alert. Required
enabled A flag to toggle this alert. Required
action A flag that describes what happens when the alert is triggered. “info” creates an incident in the dashboard. “flagged” creates an incident and blocks traffic for 24 hours. Required

 

Context Output
Path Type Description
SigSciences.Corp.Site.Alert.CreatedDate string The unique ID of the alert.
SigSciences.Corp.Site.Alert.SiteID string The ID of the site.
SigSciences.Corp.Site.Alert.TagName string The name of the tag whose occurrences the alert is watching.
SigSciences.Corp.Site.Alert.LongName string A short description of the alert.
SigSciences.Corp.Site.Alert.Interval number The number of minutes of past traffic to examine.
SigSciences.Corp.Site.Alert.Threshold number The number of occurrences of the tag in the specified interval that are required to trigger the alert.
SigSciences.Corp.Site.Alert.BlockDurationSeconds number The number of seconds that this alert is active.
SigSciences.Corp.Site.Alert.SkipNotifications boolean A flag to disable external notifications - Slack, webhooks, emails, and so on.
SigSciences.Corp.Site.Alert.Enabled boolean A flag to toggle this alert.
SigSciences.Corp.Site.Alert.Action string A flag that describes what happens when the alert is triggered.
SigSciences.Corp.Site.Alert.CreatedDate date The timestamp of event (RFC3339 format).

 

Command Example
!sigsci-update-alert action=flagged alert_id=5c9c8f35c7213901971d2e5a enabled=False interval=10 long_name=ggggg siteName=demisto tag_name=SQLI threshold=40
Context Example
{
    "SigSciences.Corp.Site.Alert": {
        "LongName": "ggggg",
        "SkipNotifications": "",
        "Interval": 10,
        "Enabled": true,
        "Threshold": 40,
        "SiteID": "",
        "TagName": "SQLI",
        "CreatedDate": "2019-03-28T09:09:09Z",
        "Action": "flagged",
        "ID": "5c9c8f35c7213901971d2e5a",
        "BlockDurationSeconds": ""
    }
}
Human Readable Output

Updated alert 5c9c8f35c7213901971d2e5a. new values:

ID Created Date Tag Name Action Long Name Interval (In Minutes) Threshold Enabled
5c9c8f35c7213901971d2e5a 2019-03-28T09:09:09Z SQLI flagged ggggg 10 40 true

 

16. Get all alerts for a site


Retrieves all alerts for given a site.

Base Command

sigsci-get-all-alerts

Input
Argument Name Description Required
siteName The name of site to get alerts for. Required

 

Context Output
Path Type Description
SigSciences.Corp.Site.Alert.ID string The unique ID of the alert.
SigSciences.Corp.Site.Alert.SiteID string The ID of the site.
SigSciences.Corp.Site.Alert.TagName string The name of the tag whose occurrences the alert is watching.
SigSciences.Corp.Site.Alert.LongName string A short description of the alert.
SigSciences.Corp.Site.Alert.Interval number The number of minutes of past traffic to examine.
SigSciences.Corp.Site.Alert.Threshold number The number of occurrences of the tag in the specified interval that are required to trigger the alert.
SigSciences.Corp.Site.Alert.BlockDurationSeconds number The number of seconds this alert is active.
SigSciences.Corp.Site.Alert.SkipNotification boolean A flag to disable external notifications - Slack, webhooks, emails, and so on.
SigSciences.Corp.Site.Alert.Enabled boolean A flag to toggle this alert.
SigSciences.Corp.Site.Alert.Action string A flag that describes what happens when the alert is triggered.
SigSciences.Corp.Site.Alert.CreatedDate date The timestamp of event (RFC3339 format).

 

Command Example
!sigsci-get-all-alerts siteName=demisto
Context Example
{
    "SigSciences.Corp.Site.Alert": [
        {
            "LongName": "ggggg",
            "SkipNotifications": "",
            "Interval": 10,
            "Enabled": true,
            "Threshold": 40,
            "SiteID": "",
            "TagName": "SQLI",
            "CreatedDate": "2019-03-28T09:09:09Z",
            "Action": "flagged",
            "ID": "5c9c8f35c7213901971d2e5a",
            "BlockDurationSeconds": ""
        },
        {
            "LongName": "The site's Online Agent count is zero",
            "SkipNotifications": "",
            "Interval": 5,
            "Enabled": false,
            "Threshold": 0,
            "SiteID": "",
            "TagName": "agent_count",
            "CreatedDate": "2018-08-30T21:00:03Z",
            "Action": "siteMetricInfo",
            "ID": "5b885ad33be3360a3f80237c",
            "BlockDurationSeconds": 21600
        },
        {
            "LongName": "The average RPS across all agents is less than 10",
            "SkipNotifications": "",
            "Interval": 5,
            "Enabled": false,
            "Threshold": 3000,
            "SiteID": "",
            "TagName": "requests_total",
            "CreatedDate": "2018-08-30T21:00:03Z",
            "Action": "siteMetricInfo",
            "ID": "5b885ad33be3360a3f80237b",
            "BlockDurationSeconds": 21600
        }
    ]
}
Human Readable Output

Signal Sciences - Alert list

Number of alerts in site: 3

ID Created Date Tag Name Action Long Name Interval (In Minutes) Threshold Block Duration Seconds Enabled
5c9c8f35c7213901971d2e5a 2019-03-28T09:09:09Z SQLI flagged ggggg 10 40   true
5b885ad33be3360a3f80237c 2018-08-30T21:00:03Z agent_count siteMetricInfo The site’s Online Agent count is zero 5 0 21600 false
5b885ad33be3360a3f80237b 2018-08-30T21:00:03Z requests_total siteMetricInfo The average RPS across all agents is less than 10 5 3000 21600 false

 

17. Get all lists for a site


Retrieves all site lists for a given site.

Base Command

sigsci-get-all-site-lists

Input
Argument Name Description Required
siteName The name of the site to retrieve lists for. Required

 

Context Output
Path Type Description
SigSciences.Corp.Site.List.Name string The name of the list in Signal Sciences.
SigSciences.Corp.Site.List.Type string The type of the list in Signal Sciences.
SigSciences.Corp.Site.List.Entries unknown The entry list of the list in Signal Sciences.
SigSciences.Corp.Site.List.ID string The ID of the list in Signal Sciences.
SigSciences.Corp.Site.List.Description string The description of the list in Signal Sciences. Maximum is 140 characters.
SigSciences.Corp.Site.List.CreatedBy string The creator of the list in Signal Sciences.
SigSciences.Corp.Site.List.CreatedDate string The creation date of the list in Signal Sciences.
SigSciences.Corp.Site.List.UpdatedDate string The last update date of the list in Signal Sciences.

 

Command Example
!sigsci-get-all-site-lists siteName=demisto
Context Example
{
    "SigSciences.Corp.Site.List": [
        {
            "Name": "sitelistdoc",
            "CreatedDate": "2019-03-28T09:52:55Z",
            "UpdatedDate": "2019-03-28T09:52:55Z",
            "CreatedBy": "user@demisto.com",
            "Entries": [
                "56.1.1.1"
            ],
            "Type": "ip",
            "ID": "site.sitelistdoc",
            "Description": ""
        },
        {
            "Name": "sitelistdoc3",
            "CreatedDate": "2019-03-28T09:20:34Z",
            "UpdatedDate": "2019-03-28T13:30:41Z",
            "CreatedBy": "user@demisto.com",
            "Entries": [
                "6.1.7.1",
                "77.8.77.8"
            ],
            "Type": "ip",
            "ID": "site.sitelistdoc3",
            "Description": ""
        }
    ]
}
Human Readable Output

Signal Sciences - list of site lists

Number of site lists in site: 2

Name ID Type Entries Created By Created Date Updated Date
sitelistdoc site.sitelistdoc ip 56.1.1.1 user@demisto.com 2019-03-28T09:52:55Z 2019-03-28T09:52:55Z
sitelistdoc3 site.sitelistdoc3 ip 6.1.7.1,
77.8.77.8
user@demisto.com 2019-03-28T09:20:34Z 2019-03-28T13:30:41Z

 

18. Create a corp list


Creates a new corp list.

Base Command

sigsci-create-corp-list

Input
Argument Name Description Required
list_name A name for the new list. Required
list_type The type for the new list (“IP”, “String”, “Country”, or “Wildcard”). Required
entries_list A CSV list of values, consistent with the list’s type. Required
description Short text that describes the new list. Optional

 

Context Output
Path Type Description
SigSciences.Corp.List.Name string The name of the list.
SigSciences.Corp.List.Type string The type of the list.
SigSciences.Corp.List.Entries unknown The entry list of the list.
SigSciences.Corp.List.ID string The ID of the list.
SigSciences.Corp.List.Description string The description of the list.
SigSciences.Corp.List.CreatedBy string The creator of the list.
SigSciences.Corp.List.CreatedDate string The creation date of the list.
SigSciences.Corp.List.UpdatedDate string The last update date of the list.

 

Command Example
!sigsci-create-corp-list entries_list=3.5.3.5 list_name=fordoc list_type=IP

19. Get information for a corp list


Retrieves all data about a given corp list.

Base Command

sigsci-get-corp-list

Input
Argument Name Description Required
list_id The ID of the list to get data for. Required

 

Context Output
Path Type Description
SigSciences.Corp.List.Name unknown The name of the list.
SigSciences.Corp.List.Type unknown The type of the list.
SigSciences.Corp.List.Entries unknown The entry list of the list.
SigSciences.Corp.List.ID unknown The ID of the list.
SigSciences.Corp.List.Description unknown The description of the list.
SigSciences.Corp.List.CreatedBy unknown The creator of the list.
SigSciences.Corp.List.CreatedDate unknown The creation date of the list.
SigSciences.Corp.List.UpdatedDate unknown The last update date of the list.

 

Command Example
!sigsci-get-corp-list list_id=corp.fordoc2
Context Example
{
    "SigSciences.Corp.List": {
        "Name": "fordoc2",
        "CreatedDate": "2019-03-28T09:14:43Z",
        "UpdatedDate": "2019-03-28T09:53:14Z",
        "CreatedBy": "user@demisto.com",
        "Entries": [
            "44.2.44.2",
            "55.7.55.7"
        ],
        "Type": "ip",
        "ID": "corp.fordoc2",
        "Description": ""
    }
}
Human Readable Output

Found data about list with ID: corp.fordoc2

Name ID Type Entries Created By Created Date Updated Date
fordoc2 corp.fordoc2 ip 44.2.44.2,
55.7.55.7
user@demisto.com 2019-03-28T09:14:43Z 2019-03-28T09:53:14Z

 

20. Delete a corp list


Deletes a given corp list.

Base Command

sigsci-delete-corp-list

Input
Argument Name Description Required
list_id The ID of the list to delete. Required

 

Context Output

There is no context output for this command.

Command Example
!sigsci-delete-corp-list list_id=corp.maya
Human Readable Output

21. Update a corp list


Updates (add or delete) entries for a given corp list.

Base Command

sigsci-update-corp-list

Input
Argument Name Description Required
list_id The ID of the list you wish to update Required
method The method to use - must be “Add” or “Remove”. The method will determine whether the entries you provide are added to the list or removed from it Required
entries_list A list of values, consistent with the list’s type, separated by commas Required
description A description for the updated list. Optional

 

Context Output
Path Type Description
SigSciences.Corp.List.Name unknown The name of the list.
SigSciences.Corp.List.Type unknown The type of the list.
SigSciences.Corp.List.Entries unknown The entry list of the list.
SigSciences.Corp.List.ID unknown The ID of the list.
SigSciences.Corp.List.Description unknown The description of the list.
SigSciences.Corp.List.CreatedBy unknown The creator of the list.
SigSciences.Corp.List.CreatedDate unknown The creation date of the list.
SigSciences.Corp.List.UpdatedDate unknown The last update date of the list.

 

Command Example
!sigsci-update-corp-list entries_list=55.7.55.7 list_id=corp.fordoc2 method=Add
Context Example
{
    "SigSciences.Corp.List": {
        "Name": "fordoc2",
        "CreatedDate": "2019-03-28T09:14:43Z",
        "UpdatedDate": "2019-03-28T09:53:14Z",
        "CreatedBy": "user@demisto.com",
        "Entries": [
            "44.2.44.2",
            "55.7.55.7"
        ],
        "Type": "ip",
        "ID": "corp.fordoc2",
        "Description": ""
    }
}
Human Readable Output

Signal Sciences - Updating a list

Name ID Type Entries Created By Created Date Updated Date
fordoc2 corp.fordoc2 ip 44.2.44.2,
55.7.55.7
user@demisto.com 2019-03-28T09:14:43Z 2019-03-28T09:53:14Z

 

22. Get information for all lists of a corp


Retrieves data about all lists for the given corp.

Base Command

sigsci-get-all-corp-lists

Input

There are no input arguments for this command.

Context Output
Path Type Description
SigSciences.Corp.List.Name unknown The name of the list.
SigSciences.Corp.List.Type unknown The type of the list.
SigSciences.Corp.List.Entries unknown The entry list of the list.
SigSciences.Corp.List.ID unknown The ID of the list.
SigSciences.Corp.List.Description unknown The description of the list.
SigSciences.Corp.List.CreatedBy unknown The creator of the list.
SigSciences.Corp.List.CreatedDate unknown The creation date of the list.
SigSciences.Corp.List.UpdatedDate unknown The last update date of the list.

 

23. Get a list of events


Fetches events from Signal Sciences.

Base Command

sigsci-fetch-events

Input
Argument Name Description Required
siteName The name of the site to fetch events from. Required
from_time The POSIX Unix time to start. Optional
until_time The POSIX Unix time to end. Optional
sort The sort order (“asc” or “desc”). Optional
since_id The ID of the first object in the set. Optional
max_id The ID of the last object in the set. Optional
limit The maximum number of entries to return. Optional
page The page of the results. Optional
action The action to filter by (‘flagged’ or ‘info’). Optional
tag The tag to filter by. Must be a valid tag name. Optional
ip The ID to filter by. Optional
status The status to filter by (“active” or “expired”). Optional

 

Context Output
Path Type Description
SigSciences.Corp.Site.Event.ID string The unique ID of the event.
SigSciences.Corp.Site.Event.Timestamp date The timestamp of the event (RFC3339 format).
SigSciences.Corp.Site.Event.Source string The source information, for example, “IP”.
SigSciences.Corp.Site.Event.RemoteCountryCode string The country code.
SigSciences.Corp.Site.Event.RemoteHostname string The remote hostname.
SigSciences.Corp.Site.Event.UserAgents unknown An array of user agents.
SigSciences.Corp.Site.Event.Action unknown If “flagged”, the IP address is flagged and subsequent malicious requests will be blocked. If “info”, the IP address is flagged and subsequent requests will be logged.
SigSciences.Corp.Site.Event.Reasons unknown The reason the event was triggered.
SigSciences.Corp.Site.Event.RequestCount number The total number of requests.
SigSciences.Corp.Site.Event.TagCount number The total number of tags.
SigSciences.Corp.Site.Event.Window number The time window (in seconds) when the items were detected.
SigSciences.Corp.Site.Event.DateExpires string The date the event expires (RFC3339 format).
SigSciences.Corp.Site.Event.ExpiredBy string The email address of the user that expired the event (if the event is expired manually).

 

24. Get an event by event ID


Retrieves an event by the event ID.

Base Command

sigsci-get-event-by-id

Input
Argument Name Description Required
siteName Name of the site to get the event from Required
event_id The ID of the event. Required

 

Context Output
Path Type Description
SigSciences.Corp.Site.Event.ID unknown The unique ID of the event.
SigSciences.Corp.Site.Event.Timestamp unknown The timestamp of the event (RFC3339 format).
SigSciences.Corp.Site.Event.Source unknown Source information, for example, “IP”.
SigSciences.Corp.Site.Event.RemoteCountryCode unknown The country code.
SigSciences.Corp.Site.Event.RemoteHostname unknown The remote hostname.
SigSciences.Corp.Site.Event.UserAgents unknown An array of user agents.
SigSciences.Corp.Site.Event.Action unknown If “flagged”, the IP address is flagged and subsequent malicious requests will be blocked. If “info”, the IP address is flagged and subsequent requests will be logged.
SigSciences.Corp.Site.Event.Reasons unknown The reason the event was triggered.
SigSciences.Corp.Site.Event.RequestCount unknown The total number of requests.
SigSciences.Corp.Site.Event.TagCount unknown The total number of tags.
SigSciences.Corp.Site.Event.Window unknown The time window (in seconds) when the items were detected.
SigSciences.Corp.Site.Event.DateExpires unknown The date the event expires (RFC3339 format).
SigSciences.Corp.Site.Event.ExpiredBy unknown The email address of the user that expired the event (if the event is expired manually).

 

25. Expire an event


Expires a given event.

Base Command

sigsci-expire-event

Input
Argument Name Description Required
siteName The name of the site to expire an event from. Required
event_id The ID of the event to expire. Required

 

26. Get requests for a site


Retrieves requests from a given site according to a search query.

Base Command

sigsci-get-requests

Input
Argument Name Description Required
siteName The name of the site to get requests from. Required
page The page of the results. Maximum is 1000 requests. Optional
limit The number of entries to return. Optional
q The search query, in Signal Sciences syntax. If a query is not supplied, no requests will be returned. Optional

 

Context Output
Path Type Description
SigSciences.Corp.Site.Request.ID unknown The unique ID of the request.
SigSciences.Corp.Site.Request.ServerHostName unknown The server hostname.
SigSciences.Corp.Site.Request.RemoteIP unknown The remote IP address.
SigSciences.Corp.Site.Request.RemoteHostName unknown The remote hostname.
SigSciences.Corp.Site.Request.RemoteCountryCode unknown The remote country code.
SigSciences.Corp.Site.Request.UserAgent unknown The user agent of the request.
SigSciences.Corp.Site.Request.Timestamp unknown The timestamp (RFC3339 format).
SigSciences.Corp.Site.Request.Method unknown The HTTP method, for example, “PUT”.
SigSciences.Corp.Site.Request.ServerName unknown The server name.
SigSciences.Corp.Site.Request.Protocol unknown The HTTP protocol, for example, “HTTP/1.1” .
SigSciences.Corp.Site.Request.Path unknown The path.
SigSciences.Corp.Site.Request.URI unknown The URI.
SigSciences.Corp.Site.Request.ResponseCode unknown The HTTP response code.
SigSciences.Corp.Site.Request.ResponseSize unknown The HTTP response size.
SigSciences.Corp.Site.Request.ResponseMillis unknown The response time in millis.
SigSciences.Corp.Site.Request.AgentResponseCode unknown The agent response code.
SigSciences.Corp.Site.Request.Tags unknown The array of relevant tags.

 

27. Get a request by request ID


Retrieves a request by request ID.

Base Command

sigsci-get-request-by-id

Input
Argument Name Description Required
siteName The name of the site to get from. Required
request_id The ID of the request to get. Required

 

Context Output
Path Type Description
SigSciences.Corp.Site.Request.ID unknown The unique ID of the request.
SigSciences.Corp.Site.Request.ServerHostName unknown Server hostname.
SigSciences.Corp.Site.Request.RemoteIP unknown The remote IP address.
SigSciences.Corp.Site.Request.RemoteHostName unknown The remote hostname.
SigSciences.Corp.Site.Request.RemoteCountryCode unknown The remote country code.
SigSciences.Corp.Site.Request.UserAgent unknown The user agent of the request.
SigSciences.Corp.Site.Request.Timestamp unknown The timestamp RFC3339 date time serverHostname string.
SigSciences.Corp.Site.Request.Method unknown The HTTP method, for example, “PUT”.
SigSciences.Corp.Site.Request.ServerName unknown The server name.
SigSciences.Corp.Site.Request.Protocol unknown The HTTP protocol, for example “HTTP/1.1”.
SigSciences.Corp.Site.Request.Path unknown The path.
SigSciences.Corp.Site.Request.URI unknown The URI.
SigSciences.Corp.Site.Request.ResponseCode unknown The HTTP response code.
SigSciences.Corp.Site.Request.ResponseSize unknown The HTTP response size.
SigSciences.Corp.Site.Request.ResponseMillis unknown The response time in milliseconds.
SigSciences.Corp.Site.Request.AgentResponseCode unknown The agent response code.
SigSciences.Corp.Site.Request.Tags unknown An array of relevant tags.

sigsci-get-events


Fetches events from Signal Sciences.

Base Command

sigsci-get-events

Input

Argument Name Description Required
siteName The name of the site to fetch events from. Required
from_time The POSIX Unix time to start. Optional
until_time The POSIX Unix time to end. . Optional
sort The sort order (“asc” or “desc”). Possible values are: asc, desc. Optional
since_id The ID of the first object in the set. Optional
max_id The ID of the last object in the set. . Optional
limit The maximum number of entries to return. Optional
page The page of the results. Optional
action The action to filter by (‘flagged’ or ‘info’). . Possible values are: flagged, info. Optional
tag The tag to filter by. Must be a valid tag name. Optional
ip The ID to filter by. Optional
status The status to filter by (“active” or “expired”). Possible values are: active, expired. Optional

Context Output

Path Type Description
SigSciences.Corp.Site.Event.ID string The unique ID of the event.
SigSciences.Corp.Site.Event.Timestamp date The timestamp of the event (RFC3339 format).
SigSciences.Corp.Site.Event.Source string The source information, for example, “IP”.
SigSciences.Corp.Site.Event.RemoteCountryCode string The country code.
SigSciences.Corp.Site.Event.RemoteHostname string The remote hostname.
SigSciences.Corp.Site.Event.UserAgents unknown An array of user agents.
SigSciences.Corp.Site.Event.Action unknown If “flagged”, the IP address is flagged and subsequent malicious requests will be blocked. If “info”, the IP address is flagged and subsequent requests will be logged.
SigSciences.Corp.Site.Event.Reasons unknown The reason the event was triggered.
SigSciences.Corp.Site.Event.RequestCount number The total number of requests.
SigSciences.Corp.Site.Event.TagCount number The total number of tags.
SigSciences.Corp.Site.Event.Window number The time window (in seconds) when the items were detected.
SigSciences.Corp.Site.Event.DateExpires string The date the event expires (RFC3339 format).
SigSciences.Corp.Site.Event.ExpiredBy string The email address of the user that expired the event (if the event is expired manually).

Configuration parameters

  • Email — Email (required)
  • Token — Token
  • cred_token
  • corpName — Corporation Name (should match the pattern [0-9a-z_.-]+) (required)
  • isFetch — Fetch incidents
  • incidentType — Incident type
  • incidentFetchInterval — Incidents Fetch Interval
  • fetch_interval — Fetch Interval (in minutes)
  • sites_to_fetch — CSV list of sites to fetch from. If no sites are specified, events from all corporation's sites will be fetched.
  • insecure — Trust any certificate (not secure)
  • proxy — Use system proxy settings

Commands (27)

  • sigsci-add-alert

    Adds a custom alert to a site in Signal Sciences.

  • sigsci-blacklist-add-ip

    Adds an IP to a site's block list, which resides on the Signal Sciences' platform.

  • sigsci-blacklist-remove-ip

    Removes an IP from a site's block list, which resides on the Signal Sciences' platform.

  • sigsci-create-corp-list

    Creates a new corp list.

  • sigsci-create-site-list

    Creates a new list for a given site on the Signal Sciences platform.

  • sigsci-delete-alert

    Deletes an alert from a given site in Signal Sciences.

  • sigsci-delete-corp-list

    Deletes a given corp list.

  • sigsci-delete-site-list

    Deletes a site list in Signal Sciences.

  • sigsci-expire-event

    Expires a given event.

  • sigsci-get-alert

    Retrieves data for an alert.

  • sigsci-get-all-alerts

    Retrieves all alerts for given a site.

  • sigsci-get-all-corp-lists

    Retrieves data about all lists for the given corp.

  • sigsci-get-all-site-lists

    Retrieves all site lists for a given site.

  • sigsci-get-blacklist

    Fetches a site's block list, which resides on the Signal Sciences' platform.

  • sigsci-get-corp-list

    Retrieves all data about a given corp list.

  • sigsci-get-event-by-id

    Retrieves an event by the event ID.

  • sigsci-get-events

    Fetches events from Signal Sciences.

  • sigsci-get-request-by-id

    Retrieves a request by request ID.

  • sigsci-get-requests

    Retrieves requests from a given site according to a search query.

  • sigsci-get-site-list

    Returns all the data about a site list.

  • sigsci-get-sites

    Returns all site names from the Signal Sciences platform.

  • sigsci-get-whitelist

    Fetches a site's allow list, which resides on the Signal Sciences' platform.

  • sigsci-update-alert

    Updates the attributes of a given alert.

  • sigsci-update-corp-list

    Updates (add or delete) entries for a given corp list.

  • sigsci-update-site-list

    Updates a site list in Signal Sciences.

  • sigsci-whitelist-add-ip

    Adds an IP address to a site's allow list, which resides on the Signal Sciences' platform.

  • sigsci-whitelist-remove-ip

    Removes an IP address from a site's allow list, which resides on the Signal Sciences' platform.

import demistomock as demisto
from CommonServerPython import *

from CommonServerUserPython import *

""" IMPORTS """
import json

import requests

""" GLOBAL VARS """
""" GLOBAL VARS """
USE_SSL = not demisto.params().get("insecure", False)
handle_proxy()

EMAIL = demisto.params()["Email"]
TOKEN = secret_key = demisto.params().get("cred_token", {}).get("password") or demisto.params()["Token"]
CORPNAME = demisto.params()["corpName"]
FETCH_INTERVAL = demisto.params()["fetch_interval"]
SITES_TO_FETCH = demisto.params().get("sites_to_fetch", None)

SERVER_URL = "https://dashboard.signalsciences.net/api/v0/"

"""SUFFIX ENDPOINTS"""
GET_SITES_SUFFIX = "corps/{0}/sites"
WHITELIST_SUFFIX = "corps/{0}/sites/{1}/whitelist"
BLACKLIST_SUFFIX = "corps/{0}/sites/{1}/blacklist"
DELETE_WHITELIST_IP_SUFFIX = "corps/{0}/sites/{1}/whitelist/{2}"
DELETE_BLACKLIST_IP_SUFFIX = "corps/{0}/sites/{1}/blacklist/{2}"
SITE_CREATE_LIST_SUFFIX = "corps/{0}/sites/{1}/lists"
SITE_ACCESS_LIST_SUFFIX = "corps/{0}/sites/{1}/lists/{2}"
SITE_CREATE_ALERT_SUFFIX = "corps/{0}/sites/{1}/alerts"
SITE_ACCESS_ALERT_SUFFIX = "corps/{0}/sites/{1}/alerts/{2}"
CREATE_CORP_LIST_SUFFIX = "corps/{0}/lists"
ACCESS_CORP_LIST_SUFFIX = "corps/{0}/lists/{1}"
GET_EVENTS_SUFFIX = "/corps/{0}/sites/{1}/events"
ACCESS_EVENT_SUFFIX = "/corps/{0}/sites/{1}/events/{2}"
EXPIRE_EVENT_SUFFIX = "/corps/{0}/sites/{1}/events/{2}/expire"
GET_REQUESTS_SUFFIX = "/corps/{0}/sites/{1}/requests"
ACCESS_REQUEST_SUFFIX = "/corps/{0}/sites/{1}/requests/{2}"


"""TABLE TITLES"""
WHITELIST_TITLE = "Signal Sciences - Whitelist"
BLACKLIST_TITLE = "Signal Sciences - Blacklist"
SITES_LIST_TITLE = "Sites list"
ADD_IP_TO_WHITELIST_TITLE = "Signal Sciences - Adding an IP to Whitelist"
ADD_IP_TO_BLACKLIST_TITLE = "Signal Sciences - Adding an IP to Blacklist"
ADD_ALERT_TITLE = "Signal Sciences - Adding a new custom alert"
UPDATE_LIST_TITLE = "Signal Sciences - Updating a list"
ALERT_LIST_TITLE = "Signal Sciences - Alert list"
LIST_OF_SITE_LISTS_TITLE = "Signal Sciences - list of site lists"
LIST_OF_CORP_LISTS_TITLE = "Signal Sciences - list of corp lists"
LIST_OF_EVENTS_TITLE = "Signal Sciences - list of events"
LIST_OF_REQUESTS_TITLE = "Signal Sciences - list of requests"
CREATE_SITE_LIST_TITLE = "Signal Sciences - creating a new site list \n\n List {0} has been successfully created"
CREATE_CORP_LIST_TITLE = "Signal Sciences - creating a new corp list \n\n List {0} has been successfully created"
DELETE_CORP_LIST_TITLE = "### Signal Sciences - deleting corp list \n\n List {0} has been successfully removed"
EXPIRE_EVENT_TITLE = "### Signal Sciences - expiring event \n\n Event {0} has been successfully expired"
WHITELIST_REMOVE_IP_TITLE = (
    "### Signal Sciences - Removing an IP from Whitelist \n\n The IP {0} has been successfully removed from Whitelist."
)
DELETE_SITE_LIST_TITLE = "### Signal Sciences - deleting site list \n\n The list has been succesfully removed"
BLACKLIST_REMOVE_IP_TITLE = (
    "### Signal Sciences - Removing an IP from Blacklist \n\n The IP {0} has been successfully removed from Blacklist."
)
IP_ADDED_TO_WHITELIST_TITLE = "The IP {0} has been successfully added to whitelist."
IP_ADDED_TO_BLACKLIST_TITLE = "The IP {0} has been successfully added to blacklist."


"""TABLE HEADERS"""
ADD_IP_HEADERS = ["Source", "Note", "Expiration date"]
WHITELIST_OR_BLACKLIST_HEADERS = ["ID", "Source", "Expiry Date", "Note", "Created Date", "Created By"]
LIST_HEADERS = ["Name", "ID", "Type", "Entries", "Description", "Created By", "Created Date", "Updated Date"]
GET_SITE_HEADERS = ["Name", "Created Date"]
EVENT_HEADERS = [
    "ID",
    "Timestamp",
    "Source",
    "Remote Country Code",
    "Action",
    "Reasons",
    "Remote Hostname",
    "User Agents",
    "Request Count",
    "Tag Count",
    "Window",
    "Date Expires",
    "Expired By",
]
REQUEST_HEADER = [
    "ID",
    "Timestamp",
    "Remote Country Code",
    "Remote Hostname",
    "Remote IP",
    "User Agent",
    "Method",
    "Server Name",
    "Protocol",
    "Path",
    "URI",
    "Response Code",
    "Response Size",
    "Response Millis",
    "Agent Response Code",
    "Tags",
]
ALERT_HEADERS = [
    "ID",
    "Site ID",
    "Created Date",
    "Tag Name",
    "Action",
    "Long Name",
    "Interval (In Minutes)",
    "Threshold",
    "Block Duration Seconds",
    "Skip Notifications",
    "Enabled",
]


"""List Types dict"""

LEGAL_SIGSCI_LIST_TYPES = {"ip", "country", "string", "wildcard"}


""" HELPER FUNCTIONS """


def camel_case_to_spaces(string_in_camel_case):
    """Given a string in camelcase, will turn it into spaces

    Args:
        string_in_camel_case(String): the string in camel case

    Returns:
        A new string, separated by spaces and every word starts with a capital letter
    """
    string_with_underscores = camel_case_to_underscore(string_in_camel_case)
    new_string_with_spaces = string_with_underscores.replace("_", " ")
    return new_string_with_spaces.title()


def dict_keys_from_camelcase_to_spaces(dict_with_camelcase_keys):
    """Given a dict with keys in camelcase, returns a copy of it with keys in spaces (helloWorld becomes Hello World)

    Args:
        dict_with_camelcase_keys(Dictionary): the original dictionary, with keys in camelcase

    Returns:
        A new dictionary, with keys separated by spaces
    """
    dict_with_spaces_in_keys = {}
    for key in dict_with_camelcase_keys:
        key_with_spaces = camel_case_to_spaces(key)
        dict_with_spaces_in_keys[key_with_spaces] = dict_with_camelcase_keys[key]
    return dict_with_spaces_in_keys


def return_list_of_dicts_with_spaces(list_of_camelcase_dicts):
    """Given a list of dicts, iterates over it and for each dict makes all the keys with spaces instead of camelcase

    Args:
        list_of_camelcase_dicts(List): array of dictionaries

    Returns:
        A new array of dictionaries, with keys including spaces instead of camelcase
    """
    dicts_with_spaces = []
    for dict_camelcase in list_of_camelcase_dicts:
        dict_with_spaces = dict_keys_from_camelcase_to_spaces(dict_camelcase)
        dicts_with_spaces.append(dict_with_spaces)

    return dicts_with_spaces


def has_api_call_failed(res):
    """
    Note: In SigSci, if an API call fails it returns a json with only 'message' in it.
    """
    return "message" in res


def is_error_status(status):
    return int(status) >= 400


def return_error_message(results_json):
    error_message = results_json.get("message", None)
    if error_message is None:
        return_error("Error: An error occured")
    return_error(f"Error: {error_message}")


def http_request(method, url, params_dict=None, data=None, use_format_instead_of_raw=False):
    LOG(f"running {method} request with url={url}\nparams={json.dumps(params_dict)}")
    if not TOKEN:
        raise DemistoException("Token must be provided.")
    headers = {"Content-Type": "application/json", "x-api-user": EMAIL, "x-api-token": TOKEN}

    try:
        # Some commands in Signal Sciences require sending the data in raw, and some in format
        # To send in format, we use the 'data' argument in requests. for raw, we use the 'json' argument.
        if use_format_instead_of_raw:
            res = requests.request(method, url, verify=USE_SSL, params=params_dict, headers=headers, data=json.dumps(data))
        else:
            res = requests.request(method, url, verify=USE_SSL, params=params_dict, headers=headers, json=data)

        if is_error_status(res.status_code):
            return_error_message(res.json())

        # references to delete from whitelist/blacklist only
        if "whitelist/" in url or "blacklist/" in url:
            return {}
        if res.status_code == 204:
            return {}
        res_json = res.json()
        if has_api_call_failed(res_json):
            return {}
        return res_json

    except Exception as e:
        LOG(e)
        raise (e)


def is_legal_list_type(list_type):
    return list_type.lower() in LEGAL_SIGSCI_LIST_TYPES


def represents_int(string_var):
    if "." in string_var:
        return False
    if string_var[0] in ("-", "+"):
        return string_var[1:].isdigit()
    return string_var.isdigit()


def is_legal_interval_for_alert(interval):
    """
    Note: legal values for the interval on an alert are only 1, 10 or 60.
    This function verifies the value given is compatible with this demand.
    """
    if not represents_int(interval):
        return False
    interval_int = int(interval)
    return interval_int == 1 or interval_int == 10 or interval_int == 60


def validate_list_description_length(description):
    if description is not None and len(description) > 140:
        return_error("Error: Description given is too long. Description must be 140 characters or shorter")


def validate_update_list_args(method, description):
    if not (method == "Add" or method == "Remove"):
        return_error("Error: Method given is illegal. Method must be 'Add' or 'Remove'")
    validate_list_description_length(description)


def validate_create_list_args(list_type, description):
    if not is_legal_list_type(list_type):
        return_error(f"Error: {list_type} is not a legal type for a list. Legal types are IP, String, Country or Wildcard")
    validate_list_description_length(description)


def validate_alert_args(siteName, long_name, tag_name, interval, threshold, enabled, action):
    if not represents_int(threshold):
        return_error(f"Error: {threshold} is not a valid threshold value. Threshold must be an integer")
    if not is_legal_interval_for_alert(interval):
        return_error(f"Error: {interval} is not a valid interval value. Interval value must be 1, 10 or 60")
    if len(long_name) < 3 or len(long_name) > 25:
        return_error("Error: Illegal value for long_name argument - long_name must be between 3 and 25 characters long")
    if not (enabled.lower() == "true" or enabled.lower() == "false"):
        return_error("Error: Illegal value for 'enabled' argument - value must be 'True' or 'False'")
    if not (action == "info" or action == "flagged"):
        return_error("Error: Illegal value for 'action' argument - value must be 'info' or 'flagged'")


def validate_get_events_args(from_time, until_time, sort, limit, page, action, ip, status):
    if from_time is not None and not represents_int(str(from_time)):
        return_error("Error: from_time must be an integer.")
    if until_time is not None and not represents_int(str(until_time)):
        return_error("Error: until_time must be an integer.")
    if sort is not None and not (sort == "asc" or sort == "desc"):
        return_error("Error: sort value must be 'asc' or 'desc'.")
    if limit is not None and (not represents_int(str(limit)) or int(limit) < 0 or int(limit) > 1000):
        return_error("Error: limit must be an integer, larger than 0 and at most 1000")
    if action is not None and not (action == "flagged" or action == "info"):
        return_error("Error: action value must be 'flagged' or 'info'")
    if ip is not None and not is_ip_valid(str(ip)):
        return_error("Error: illegal value for 'ip' argument. Must be a valid ip address")
    if status is not None and not (status == "active" or status == "expired"):
        return_error("Error: status value must be 'active' or 'expired'")
    if page is not None and not represents_int(str(page)):
        return_error("Error: page must be an integer.")


def create_get_event_data_from_args(from_time, until_time, sort, since_id, max_id, limit, page, action, tag, ip, status):
    get_events_request_data = {}
    if from_time is not None:
        get_events_request_data["from"] = int(from_time)
    if until_time is not None:
        get_events_request_data["until"] = int(until_time)
    if sort is not None:
        get_events_request_data["sort"] = sort
    if since_id is not None:
        get_events_request_data["since_id"] = since_id
    if max_id is not None:
        get_events_request_data["max_id"] = max_id
    if limit is not None:
        get_events_request_data["limit"] = int(limit)
    if page is not None:
        get_events_request_data["page"] = int(page)
    if action is not None:
        get_events_request_data["action"] = action
    if tag is not None:
        get_events_request_data["tag"] = tag
    if ip is not None:
        get_events_request_data["ip"] = ip
    if status is not None:
        get_events_request_data["status"] = status
    return get_events_request_data


def event_entry_context_from_response(response_data):
    entry_context = {
        "ID": response_data.get("id", ""),
        "Timestamp": response_data.get("timestamp", ""),
        "Source": response_data.get("source", ""),
        "Action": response_data.get("action", ""),
        "Reasons": response_data.get("reasons", ""),
        "RemoteCountryCode": response_data.get("remoteCountryCode", ""),
        "RemoteHostname": response_data.get("RemoteHostname", ""),
        "UserAgents": response_data.get("userAgents", ""),
        "RequestCount": response_data.get("requestCount", ""),
        "TagCount": response_data.get("tagCount", ""),
        "Window": response_data.get("window", ""),
        "DateExpires": response_data.get("expires", ""),
        "ExpiredBy": response_data.get("expiredBy", ""),
    }
    return entry_context


def adjust_event_human_readable(entry_context_with_spaces, entry_context):
    """Change keys in human readable data to match the headers."""
    entry_context_with_spaces["ID"] = entry_context.get("ID", "")


def validate_fetch_requests_args(page, limit):
    if limit is not None and (not represents_int(limit) or int(limit) < 0 or int(limit) > 1000):
        return_error("Error: limit must be an integer, larger than 0 and at most 1000")
    if page is not None and not represents_int(page):
        return_error("Error: page must be an integer")


def request_entry_context_from_response(response_data):
    entry_context = {
        "ID": response_data.get("id", ""),
        "ServerHostName": response_data.get("serverHostName", ""),
        "RemoteIP": response_data.get("remoteIP", ""),
        "RemoteHostname": response_data.get("RemoteHostname", ""),
        "RemoteCountryCode": response_data.get("remoteCountryCode", ""),
        "UserAgent": response_data.get("userAgent", ""),
        "Timestamp": response_data.get("timestamp", ""),
        "Method": response_data.get("method", ""),
        "ServerName": response_data.get("serverName", ""),
        "Protocol": response_data.get("protocol", ""),
        "Path": response_data.get("path", ""),
        "URI": response_data.get("uri", ""),
        "ResponseCode": response_data.get("responseCode", ""),
        "ResponseSize": response_data.get("responseSize", ""),
        "ResponseMillis": response_data.get("responseMillis", ""),
        "AgentResponseCode": response_data.get("agentResponseCode", ""),
        "Tags": response_data.get("tags", ""),
    }
    return entry_context


def adjust_request_human_readable(entry_context_with_spaces, entry_context):
    """Change keys in human readable data to match the headers."""
    entry_context_with_spaces["ID"] = entry_context.get("ID", "")
    entry_context_with_spaces["URI"] = entry_context.get("URI", "")
    entry_context_with_spaces["Remote IP"] = entry_context.get("RemoteIP", "")


def list_entry_context_from_response(response_data):
    entry_context = {
        "ID": response_data.get("id", ""),
        "Name": response_data.get("name", ""),
        "Type": response_data.get("type", ""),
        "Entries": response_data.get("entries", ""),
        "Description": response_data.get("description", ""),
        "CreatedBy": response_data.get("createdBy", ""),
        "CreatedDate": response_data.get("created", ""),
        "UpdatedDate": response_data.get("updated", ""),
    }
    return entry_context


def adjust_list_human_readable(entry_context_with_spaces, entry_context):
    """Change keys in human readable data to match the headers."""
    entry_context_with_spaces["ID"] = entry_context.get("ID", "")


def alert_entry_context_from_response(response_data):
    entry_context = {
        "ID": response_data.get("id", ""),
        "LongName": response_data.get("longName", ""),
        "SiteID": response_data.get("siteId", ""),
        "TagName": response_data.get("tagName", ""),
        "Interval": response_data.get("interval", ""),
        "Threshold": response_data.get("threshold", ""),
        "BlockDurationSeconds": response_data.get("blockDurationSeconds", ""),
        "SkipNotifications": response_data.get("skipNotifications", ""),
        "Enabled": response_data.get("enabled", ""),
        "Action": response_data.get("action", ""),
        "CreatedDate": response_data.get("created", ""),
    }
    return entry_context


def adjust_alert_human_readable(entry_context_with_spaces, entry_context):
    """Change keys in human readable data to match the headers."""
    entry_context_with_spaces["Interval (In Minutes)"] = entry_context_with_spaces.get("Interval", "")
    entry_context_with_spaces["ID"] = entry_context.get("ID", "")
    entry_context_with_spaces["Site ID"] = entry_context.get("siteID", "")


def check_ip_is_valid(ip):
    if not is_ip_valid(ip):
        return_error(f"Error: {ip} is invalid IP. Please enter a valid IP address")


def gen_entries_data_for_update_list_request(entries_list, method):
    """Using the recieved args, generates the data object required by the API
    in order to update a list (site or corp alike).
    Args:
        entries_list (list): a list containing IP addresses
        method (string): The method we want to apply on the entries, either 'Add' or 'Remove'.
            States if the IPs should be added or removed to the site/corp list.

    Returns:
        dict. Contains additions and deletions list with the entries we want to act on.
    """
    entries = {"additions": [], "deletions": []}  # type: Dict
    entries_list_in_list_format = entries_list.split(",")
    if method == "Add":
        entries["additions"] = entries_list_in_list_format
    else:
        entries["deletions"] = entries_list_in_list_format
    return entries


def gen_context_for_add_to_whitelist_or_blacklist(response_data):
    full_data = []
    for data in response_data:
        full_data.append(
            {
                "ID": data.get("id", ""),
                "Note": data.get("note", ""),
                "Source": data.get("source", ""),
                "CreatedBy": data.get("createdBy", ""),
                "CreatedDate": data.get("created", ""),
                "ExpiryDate": data.get("expires", ""),
            }
        )
    return full_data


def gen_human_readable_for_add_to_whitelist_or_blacklist(ip_context):
    human_readable = []
    for context in ip_context:
        human_readable.append(
            {
                "Note": context["Note"],
                "Source": context["Source"],
                "Expiration date": context["ExpiryDate"] if context["ExpiryDate"] else "Not Set",
            }
        )
    return human_readable


def add_ip_to_whitelist_or_blacklist(url, ip, note, expires=None):
    res_list = []
    error_list = []
    for single_ip in argToList(ip):
        try:
            check_ip_is_valid(single_ip)
            data = {"source": single_ip, "note": note}
            if expires is not None:
                data["expires"] = expires
            res_list.append(http_request("PUT", url, data=data))
        except SystemExit:
            # handle exceptions in return_error
            pass
        except Exception as e:
            error_list.append(f"failed adding ip: {single_ip} to balcklist error: {e}")
            demisto.error(f"failed adding ip: {single_ip} to balcklist\n{traceback.format_exc()}")
    return res_list, error_list


def get_all_sites_in_corp():
    get_sites_request_response = get_sites()
    data_of_sites_in_corp = get_sites_request_response.get("data", [])
    return data_of_sites_in_corp


def get_list_of_all_site_names_in_corp():
    data_of_sites_in_corp = get_all_sites_in_corp()
    list_of_all_sites_names_in_corp = []
    for site_data in data_of_sites_in_corp:
        site_name = site_data["name"]
        list_of_all_sites_names_in_corp.append(site_name)
    return list_of_all_sites_names_in_corp


def get_list_of_site_names_to_fetch():
    list_of_site_names_to_fetch = None
    if SITES_TO_FETCH:
        list_of_site_names_to_fetch = SITES_TO_FETCH.split(",")
    else:
        list_of_site_names_to_fetch = get_list_of_all_site_names_in_corp()
    return list_of_site_names_to_fetch


def remove_milliseconds_from_iso(date_in_iso_format):
    date_parts_arr = date_in_iso_format.split(".")
    date_in_iso_without_milliseconds = date_parts_arr[0]
    return date_in_iso_without_milliseconds


def get_events_from_given_sites(list_of_site_names_to_fetch, desired_from_time_in_posix):
    events_from_given_sites = []  # type: List[Any]
    for site_name in list_of_site_names_to_fetch:
        fetch_from_site_response_json = get_events(siteName=site_name, from_time=desired_from_time_in_posix)

        events_fetched_from_site = fetch_from_site_response_json.get("data", [])
        events_from_given_sites.extend(events_fetched_from_site)
    return events_from_given_sites


def datetime_to_posix_without_milliseconds(datetime_object):
    timestamp_in_unix_millisecond = date_to_timestamp(datetime_object, "datetime.datetime")
    posix_with_ms = timestamp_in_unix_millisecond
    posix_without_ms = str(posix_with_ms).split(",")[0]
    return posix_without_ms


"""COMMANDS"""


def test_module():
    try:
        url = SERVER_URL + "corps"
        http_request("GET", url)
    except Exception as e:
        raise Exception(e)
    demisto.results("ok")


def create_corp_list(list_name, list_type, entries_list, description=None):
    """This method sends a request to the Signal Sciences API to create a new corp list.
    Note:
        Illegal entries (not compatible with the type) will result in a 404.
        They will be handled by the http_request function.

    Args:
        list_name (string): A name for the newly created list.
        list_type (string): The desired type for the newly created list.
        entries_list (list): A list of entries, consistent with the given type.
        description (string): A description for the newly created list.

    Returns:
        dict. The data returned from the Signal Sciences API in response to the request, loaded into a json.
    """
    validate_create_list_args(list_type, description)

    url = SERVER_URL + CREATE_CORP_LIST_SUFFIX.format(CORPNAME)
    entries_list_in_list_format = entries_list.split(",")
    data_for_request = {"name": list_name.lower(), "type": list_type.lower(), "entries": entries_list_in_list_format}
    if description is not None:
        data_for_request["description"] = description
    new_list_data = http_request("POST", url, data=data_for_request)
    return new_list_data


def create_corp_list_command():
    args = demisto.args()
    response_data = create_corp_list(args["list_name"], args["list_type"], args["entries_list"], args.get("description", None))
    entry_context = list_entry_context_from_response(response_data)
    entry_context_with_spaces = dict_keys_from_camelcase_to_spaces(entry_context)
    human_readable = tableToMarkdown(
        CREATE_CORP_LIST_TITLE.format(args["list_name"]), entry_context_with_spaces, headers=LIST_HEADERS, removeNull=True
    )

    adjust_list_human_readable(entry_context_with_spaces, entry_context)

    return_outputs(
        raw_response=response_data,
        readable_output=human_readable,
        outputs={
            "SigSciences.Corp.List(val.ID==obj.ID)": entry_context,
        },
    )


def get_corp_list(list_id):
    url = SERVER_URL + ACCESS_CORP_LIST_SUFFIX.format(CORPNAME, list_id)
    list_data = http_request("GET", url)
    return list_data


def get_corp_list_command():
    args = demisto.args()
    response_data = get_corp_list(args["list_id"])
    entry_context = list_entry_context_from_response(response_data)
    title = f"Found data about list with ID: {args['list_id']}"
    entry_context_with_spaces = dict_keys_from_camelcase_to_spaces(entry_context)
    adjust_list_human_readable(entry_context_with_spaces, entry_context)
    human_readable = tableToMarkdown(title, entry_context_with_spaces, headers=LIST_HEADERS, removeNull=True)
    return_outputs(
        raw_response=response_data,
        readable_output=human_readable,
        outputs={
            "SigSciences.Corp.List(val.ID==obj.ID)": entry_context,
        },
    )


def delete_corp_list(list_id):
    url = SERVER_URL + ACCESS_CORP_LIST_SUFFIX.format(CORPNAME, list_id)
    list_data = http_request("DELETE", url)
    return list_data


def delete_corp_list_command():
    args = demisto.args()
    response_data = delete_corp_list(args["list_id"])
    demisto.results(
        {
            "Type": entryTypes["note"],
            "ContentsFormat": formats["markdown"],
            "Contents": response_data,
            "HumanReadable": DELETE_CORP_LIST_TITLE.format(args["list_id"]),
        }
    )


def update_corp_list(list_id, method, entries_list, description=None):
    validate_update_list_args(method, description)
    entries_in_update_format = gen_entries_data_for_update_list_request(entries_list, method)
    url = SERVER_URL + ACCESS_CORP_LIST_SUFFIX.format(CORPNAME, list_id)
    data_for_request = {"entries": entries_in_update_format}
    if description is not None:
        data_for_request["description"] = description
    response_data = http_request("PATCH", url, data=data_for_request)
    return response_data


def update_corp_list_command():
    args = demisto.args()
    response_data = update_corp_list(args["list_id"], args["method"], args["entries_list"], args.get("description", None))
    entry_context = list_entry_context_from_response(response_data)
    entry_context_with_spaces = dict_keys_from_camelcase_to_spaces(entry_context)
    adjust_list_human_readable(entry_context_with_spaces, entry_context)
    human_readable = tableToMarkdown(UPDATE_LIST_TITLE, entry_context_with_spaces, headers=LIST_HEADERS, removeNull=True)
    return_outputs(
        raw_response=response_data,
        readable_output=human_readable,
        outputs={
            "SigSciences.Corp.List(val.ID==obj.ID)": entry_context,
        },
    )


def get_all_corp_lists():
    url = SERVER_URL + CREATE_CORP_LIST_SUFFIX.format(CORPNAME)
    response_data = http_request("GET", url)
    return response_data


def get_all_corp_lists_command():
    response_data = get_all_corp_lists()
    list_of_corp_lists = response_data.get("data", [])

    corp_lists_contexts = []
    for corp_list_data in list_of_corp_lists:
        cur_corp_list_context = list_entry_context_from_response(corp_list_data)
        corp_lists_contexts.append(cur_corp_list_context)

    sidedata = f"Number of corp lists in corp: {len(list_of_corp_lists)}"
    corp_lists_contexts_with_spaces = return_list_of_dicts_with_spaces(corp_lists_contexts)

    for i in range(len(corp_lists_contexts)):
        adjust_list_human_readable(corp_lists_contexts_with_spaces[i], corp_lists_contexts[i])

    human_readable = tableToMarkdown(
        LIST_OF_CORP_LISTS_TITLE, corp_lists_contexts_with_spaces, headers=LIST_HEADERS, removeNull=True, metadata=sidedata
    )
    return_outputs(
        raw_response=response_data,
        readable_output=human_readable,
        outputs={
            "SigSciences.Corp.List(val.ID==obj.ID)": corp_lists_contexts,
        },
    )


def get_events(
    siteName,
    from_time=None,
    until_time=None,
    sort=None,
    since_id=None,
    max_id=None,
    limit=None,
    page=None,
    action=None,
    tag=None,
    ip=None,
    status=None,
):
    validate_get_events_args(from_time, until_time, sort, limit, page, action, ip, status)
    url = SERVER_URL + GET_EVENTS_SUFFIX.format(CORPNAME, siteName)
    data_for_request = create_get_event_data_from_args(
        from_time, until_time, sort, since_id, max_id, limit, page, action, tag, ip, status
    )
    events_data_response = http_request("GET", url, params_dict=data_for_request)

    return events_data_response


def get_events_command():
    args = demisto.args()
    response_data = get_events(
        args["siteName"],
        args.get("from_time", None),
        args.get("until_time", None),
        args.get("sort", None),
        args.get("since_id", None),
        args.get("max_id", None),
        args.get("limit", None),
        args.get("page", None),
        args.get("action", None),
        args.get("tag", None),
        args.get("ip", None),
        args.get("status", None),
    )

    list_of_events = response_data.get("data", [])
    events_contexts = []
    for event_data in list_of_events:
        cur_event_context = event_entry_context_from_response(event_data)
        events_contexts.append(cur_event_context)

    events_contexts_with_spaces = return_list_of_dicts_with_spaces(events_contexts)

    for i in range(len(events_contexts)):
        adjust_list_human_readable(events_contexts_with_spaces[i], events_contexts[i])

    sidedata = f"Number of events in site: {len(list_of_events)}"
    human_readable = tableToMarkdown(
        LIST_OF_EVENTS_TITLE, events_contexts_with_spaces, removeNull=True, headers=EVENT_HEADERS, metadata=sidedata
    )
    return_outputs(
        raw_response=response_data,
        readable_output=human_readable,
        outputs={
            "SigSciences.Corp.Site.Event(val.ID==obj.ID)": events_contexts,
        },
    )


def get_event_by_id(siteName, event_id):
    url = SERVER_URL + ACCESS_EVENT_SUFFIX.format(CORPNAME, siteName, event_id)
    event_data_response = http_request("GET", url)
    return event_data_response


def get_event_by_id_command():
    args = demisto.args()
    response_data = get_event_by_id(args["siteName"], args["event_id"])
    entry_context = event_entry_context_from_response(response_data)
    title = f"Found data about event with ID: {args['event_id']}"

    entry_context_with_spaces = dict_keys_from_camelcase_to_spaces(entry_context)
    adjust_event_human_readable(entry_context_with_spaces, entry_context)

    human_readable = tableToMarkdown(title, entry_context_with_spaces, headers=EVENT_HEADERS, removeNull=True)
    return_outputs(
        raw_response=response_data,
        readable_output=human_readable,
        outputs={
            "SigSciences.Corp.Site.Event(val.ID==obj.ID)": entry_context,
        },
    )


def expire_event(siteName, event_id):
    url = SERVER_URL + EXPIRE_EVENT_SUFFIX.format(CORPNAME, siteName, event_id)
    event_data_response = http_request("POST", url)
    return event_data_response


def expire_event_command():
    args = demisto.args()
    response_data = expire_event(args["siteName"], args["event_id"])
    demisto.results(
        {
            "Type": entryTypes["note"],
            "ContentsFormat": formats["markdown"],
            "Contents": response_data,
            "HumanReadable": EXPIRE_EVENT_TITLE.format(args["event_id"]),
        }
    )


def get_requests(siteName, page, limit, query):
    url = SERVER_URL + GET_REQUESTS_SUFFIX.format(CORPNAME, siteName)
    validate_fetch_requests_args(page, limit)
    data_for_request = {}
    if page is not None:
        data_for_request["page"] = page
    if limit is not None:
        data_for_request["limit"] = limit
    if query is not None:
        data_for_request["q"] = query

    requests_data_response = http_request("GET", url, data=data_for_request)
    return requests_data_response


def get_requests_command():
    args = demisto.args()
    response_data = get_requests(args["siteName"], args.get("page", None), args.get("limit", None), args.get("query", None))
    list_of_requests = response_data.get("data", [])
    requests_contexts = []
    for request_data in list_of_requests:
        cur_request_context = request_entry_context_from_response(request_data)
        requests_contexts.append(cur_request_context)

    requests_contexts_with_spaces = return_list_of_dicts_with_spaces(requests_contexts)

    for i in range(len(requests_contexts)):
        adjust_list_human_readable(requests_contexts_with_spaces[i], requests_contexts[i])

    sidedata = f"Number of requests in site: {len(list_of_requests)}"
    human_readable = tableToMarkdown(
        LIST_OF_REQUESTS_TITLE, requests_contexts_with_spaces, headers=REQUEST_HEADER, removeNull=True, metadata=sidedata
    )
    return_outputs(
        raw_response=response_data,
        readable_output=human_readable,
        outputs={
            "SigSciences.Corp.Site.Request(val.ID==obj.ID)": requests_contexts,
        },
    )


def get_request_by_id(siteName, request_id):
    url = SERVER_URL + ACCESS_REQUEST_SUFFIX.format(CORPNAME, siteName, request_id)
    request_data_response = http_request("GET", url)
    return request_data_response


def get_request_by_id_command():
    args = demisto.args()
    response_data = get_request_by_id(args["siteName"], args["request_id"])
    entry_context = request_entry_context_from_response(response_data)
    title = f"Found data about request with ID: {args['request_id']}"

    entry_context_with_spaces = dict_keys_from_camelcase_to_spaces(entry_context)
    adjust_request_human_readable(entry_context_with_spaces, entry_context)

    human_readable = tableToMarkdown(title, entry_context_with_spaces, headers=REQUEST_HEADER, removeNull=True)
    return_outputs(
        raw_response=response_data,
        readable_output=human_readable,
        outputs={
            "SigSciences.Corp.Site.Request(val.ID==obj.ID)": entry_context,
        },
    )


def create_site_list(siteName, list_name, list_type, entries_list, description=None):
    validate_create_list_args(list_type, description)
    url = SERVER_URL + SITE_CREATE_LIST_SUFFIX.format(CORPNAME, siteName)
    entries_list_in_list_format = entries_list.split(",")
    data_for_request = {"name": list_name.lower(), "type": list_type.lower(), "entries": entries_list_in_list_format}
    if description is not None:
        data_for_request["description"] = description

    new_list_data = http_request("POST", url, data=data_for_request)
    return new_list_data


def create_site_list_command():
    args = demisto.args()
    response_data = create_site_list(
        args["siteName"], args["list_name"], args["list_type"], args["entries_list"], args.get("description", None)
    )
    entry_context = list_entry_context_from_response(response_data)
    entry_context_with_spaces = dict_keys_from_camelcase_to_spaces(entry_context)
    adjust_list_human_readable(entry_context_with_spaces, entry_context)

    human_readable = tableToMarkdown(
        CREATE_SITE_LIST_TITLE.format(args["list_name"]), entry_context_with_spaces, headers=LIST_HEADERS, removeNull=True
    )
    return_outputs(
        raw_response=response_data,
        readable_output=human_readable,
        outputs={
            "SigSciences.Corp.Site.List(val.ID==obj.ID)": entry_context,
        },
    )


def get_site_list(siteName, list_id):
    url = SERVER_URL + SITE_ACCESS_LIST_SUFFIX.format(CORPNAME, siteName, list_id)
    list_data = http_request("GET", url)
    return list_data


def get_site_list_command():
    args = demisto.args()
    response_data = get_site_list(args["siteName"], args["list_id"])
    entry_context = list_entry_context_from_response(response_data)
    entry_context_with_spaces = dict_keys_from_camelcase_to_spaces(entry_context)
    adjust_list_human_readable(entry_context_with_spaces, entry_context)

    title = f"Found data about list with ID: {args['list_id']}"
    human_readable = tableToMarkdown(title, entry_context_with_spaces, headers=LIST_HEADERS, removeNull=True)
    return_outputs(
        raw_response=response_data,
        readable_output=human_readable,
        outputs={
            "SigSciences.Corp.Site.List(val.ID==obj.ID)": entry_context,
        },
    )


def delete_site_list(siteName, list_id):
    url = SERVER_URL + SITE_ACCESS_LIST_SUFFIX.format(CORPNAME, siteName, list_id)
    list_data = http_request("DELETE", url)
    return list_data


def delete_site_list_command():
    args = demisto.args()
    response_data = delete_site_list(args["siteName"], args["list_id"])
    demisto.results(
        {
            "Type": entryTypes["note"],
            "ContentsFormat": formats["markdown"],
            "Contents": response_data,
            "HumanReadable": DELETE_SITE_LIST_TITLE.format(args["list_id"]),
        }
    )


def update_site_list(siteName, list_id, method, entries_list, description=None):
    validate_update_list_args(method, description)

    entries_in_update_format = gen_entries_data_for_update_list_request(entries_list, method)
    url = SERVER_URL + SITE_ACCESS_LIST_SUFFIX.format(CORPNAME, siteName, list_id)
    data_for_request = {"entries": entries_in_update_format}
    if description is not None:
        data_for_request["description"] = description
    response_data = http_request("PATCH", url, data=data_for_request)
    return response_data


def update_site_list_command():
    args = demisto.args()
    response_data = update_site_list(
        args["siteName"], args["list_id"], args["method"], args["entries_list"], args.get("description", None)
    )
    entry_context = list_entry_context_from_response(response_data)
    entry_context_with_spaces = dict_keys_from_camelcase_to_spaces(entry_context)
    adjust_list_human_readable(entry_context_with_spaces, entry_context)

    human_readable = tableToMarkdown(UPDATE_LIST_TITLE, entry_context_with_spaces, headers=LIST_HEADERS, removeNull=True)
    return_outputs(
        raw_response=response_data,
        readable_output=human_readable,
        outputs={
            "SigSciences.Corp.Site.List(val.ID==obj.ID)": entry_context,
        },
    )


def get_all_site_lists(siteName):
    url = SERVER_URL + SITE_CREATE_LIST_SUFFIX.format(CORPNAME, siteName)
    response_data = http_request("GET", url)
    return response_data


def get_all_site_lists_command():
    args = demisto.args()
    response_data = get_all_site_lists(args["siteName"])
    list_of_site_lists = response_data.get("data", [])

    site_lists_contexts = []
    for site_list_data in list_of_site_lists:
        cur_site_context = list_entry_context_from_response(site_list_data)
        site_lists_contexts.append(cur_site_context)

    site_lists_contexts_with_spaces = return_list_of_dicts_with_spaces(site_lists_contexts)

    for i in range(len(site_lists_contexts)):
        adjust_list_human_readable(site_lists_contexts_with_spaces[i], site_lists_contexts[i])

    sidedata = f"Number of site lists in site: {len(list_of_site_lists)}"
    human_readable = tableToMarkdown(
        LIST_OF_SITE_LISTS_TITLE, site_lists_contexts_with_spaces, headers=LIST_HEADERS, removeNull=True, metadata=sidedata
    )
    return_outputs(
        raw_response=response_data,
        readable_output=human_readable,
        outputs={
            "SigSciences.Corp.Site.List(val.ID==obj.ID)": site_lists_contexts,
        },
    )


def add_alert(siteName, long_name, tag_name, interval, threshold, enabled, action):
    validate_alert_args(siteName, long_name, tag_name, interval, threshold, enabled, action)
    url = SERVER_URL + SITE_CREATE_ALERT_SUFFIX.format(CORPNAME, siteName)
    data_for_request = {
        "tagName": tag_name,
        "longName": long_name,
        "interval": int(interval),
        "threshold": int(threshold),
        "enabled": bool(enabled),
        "action": action,
    }
    response_data = http_request("POST", url, data=data_for_request)
    return response_data


def add_alert_command():
    args = demisto.args()
    response_data = add_alert(
        args["siteName"],
        args["long_name"],
        args["tag_name"],
        args["interval"],
        args["threshold"],
        args["enabled"],
        args["action"],
    )

    entry_context = alert_entry_context_from_response(response_data)
    entry_context_with_spaces = dict_keys_from_camelcase_to_spaces(entry_context)
    # changing key of Interval to Interval (In Minutes) for human readable
    adjust_alert_human_readable(entry_context_with_spaces, entry_context)

    human_readable = tableToMarkdown(ADD_ALERT_TITLE, entry_context_with_spaces, headers=ALERT_HEADERS, removeNull=True)
    return_outputs(
        raw_response=response_data,
        readable_output=human_readable,
        outputs={
            "SigSciences.Corp.Site.Alert(val.ID==obj.ID)": entry_context,
        },
    )


def get_alert(siteName, alert_id):
    url = SERVER_URL + SITE_ACCESS_ALERT_SUFFIX.format(CORPNAME, siteName, alert_id)
    response_data = http_request("GET", url)
    return response_data


def get_alert_command():
    args = demisto.args()
    response_data = get_alert(args["siteName"], args["alert_id"])
    entry_context = alert_entry_context_from_response(response_data)
    entry_context_with_spaces = dict_keys_from_camelcase_to_spaces(entry_context)

    # changing key of Interval to Interval (In Minutes) for human readable
    adjust_alert_human_readable(entry_context_with_spaces, entry_context)

    title = f"Data found for alert id: {args['alert_id']}"
    human_readable = tableToMarkdown(title, entry_context_with_spaces, headers=ALERT_HEADERS, removeNull=True)
    return_outputs(
        raw_response=response_data,
        readable_output=human_readable,
        outputs={
            "SigSciences.Corp.Site.Alert(val.ID==obj.ID)": entry_context,
        },
    )


def delete_alert(siteName, alert_id):
    url = SERVER_URL + SITE_ACCESS_ALERT_SUFFIX.format(CORPNAME, siteName, alert_id)
    response_data = http_request("DELETE", url)
    return response_data


def delete_alert_command():
    args = demisto.args()
    response_data = delete_alert(args["siteName"], args["alert_id"])
    title = f"Alert {args['alert_id']} deleted succesfully"
    demisto.results(
        {"Type": entryTypes["note"], "ContentsFormat": formats["markdown"], "Contents": response_data, "HumanReadable": title}
    )


def update_alert(siteName, alert_id, tag_name, long_name, interval, threshold, enabled, action):
    validate_alert_args(siteName, long_name, tag_name, interval, threshold, enabled, action)
    url = SERVER_URL + SITE_ACCESS_ALERT_SUFFIX.format(CORPNAME, siteName, alert_id)
    data_for_request = {
        "tagName": tag_name,
        "longName": long_name,
        "interval": int(interval),
        "threshold": int(threshold),
        "action": action,
        "enabled": bool(enabled),
    }
    request_response = http_request("PATCH", url, data=data_for_request)
    return request_response


def update_alert_command():
    args = demisto.args()
    response_data = update_alert(
        args["siteName"],
        args["alert_id"],
        args["tag_name"],
        args["long_name"],
        args["interval"],
        args["threshold"],
        args["enabled"],
        args["action"],
    )
    title = f"Updated alert {args['alert_id']}. new values:"
    entry_context = alert_entry_context_from_response(response_data)
    entry_context_with_spaces = dict_keys_from_camelcase_to_spaces(entry_context)

    # changing key of Interval to Interval (In Minutes) for human readable
    adjust_alert_human_readable(entry_context_with_spaces, entry_context)

    human_readable = tableToMarkdown(title, entry_context_with_spaces, headers=ALERT_HEADERS, removeNull=True)
    return_outputs(
        raw_response=response_data,
        readable_output=human_readable,
        outputs={
            "SigSciences.Corp.Site.Alert(val.ID==obj.ID)": entry_context,
        },
    )


def get_all_alerts(siteName):
    url = SERVER_URL + SITE_CREATE_ALERT_SUFFIX.format(CORPNAME, siteName)
    response_data = http_request("GET", url)
    return response_data


def get_all_alerts_command():
    args = demisto.args()
    response_data = get_all_alerts(args["siteName"])
    alerts_list = response_data.get("data", [])
    alerts_contexts = []
    for alert_data in alerts_list:
        cur_alert_context = alert_entry_context_from_response(alert_data)
        alerts_contexts.append(cur_alert_context)

    alerts_contexts_with_spaces = return_list_of_dicts_with_spaces(alerts_contexts)

    # changing key of Interval to Interval (In Minutes) for human readable in all alert contexts
    for i in range(len(alerts_contexts)):
        adjust_alert_human_readable(alerts_contexts_with_spaces[i], alerts_contexts[i])

    sidedata = f"Number of alerts in site: {len(alerts_list)}"
    return_outputs(
        raw_response=response_data,
        readable_output=tableToMarkdown(
            ALERT_LIST_TITLE, alerts_contexts_with_spaces, headers=ALERT_HEADERS, removeNull=True, metadata=sidedata
        ),
        outputs={
            "SigSciences.Corp.Site.Alert(val.ID==obj.ID)": alerts_contexts,
        },
    )


def get_whitelist(siteName):
    url = SERVER_URL + WHITELIST_SUFFIX.format(CORPNAME, siteName)
    site_whitelist = http_request("GET", url)
    return site_whitelist


def get_whitelist_command():
    """Get the whitelist data for siteName"""
    args = demisto.args()
    site_whitelist = get_whitelist(args["siteName"])
    data = site_whitelist.get("data", [])
    whitelist_ips_contexts = gen_context_for_add_to_whitelist_or_blacklist(data)
    whitelist_ips_contexts_with_spaces = return_list_of_dicts_with_spaces(whitelist_ips_contexts)

    sidedata = f"Number of IPs in the Whitelist {len(data)}"
    return_outputs(
        raw_response=site_whitelist,
        readable_output=tableToMarkdown(
            WHITELIST_TITLE,
            whitelist_ips_contexts_with_spaces,
            WHITELIST_OR_BLACKLIST_HEADERS,
            removeNull=True,
            metadata=sidedata,
        ),
        outputs={
            "SigSciences.Corp.Site.Whitelist(val.ID==obj.ID)": whitelist_ips_contexts,
        },
    )


def get_blacklist(siteName):
    url = SERVER_URL + BLACKLIST_SUFFIX.format(CORPNAME, siteName)
    site_blacklist = http_request("GET", url)
    return site_blacklist


def get_blacklist_command():
    """Get blacklist data for siteName"""
    args = demisto.args()
    site_blacklist = get_blacklist(args["siteName"])
    data = site_blacklist.get("data", [])
    blacklist_ips_contexts = gen_context_for_add_to_whitelist_or_blacklist(data)
    blacklist_ips_contexts_with_spaces = return_list_of_dicts_with_spaces(blacklist_ips_contexts)

    sidedata = f"Number of IPs in the Blacklist {len(data)}"
    return_outputs(
        raw_response=site_blacklist,
        readable_output=tableToMarkdown(
            BLACKLIST_TITLE,
            blacklist_ips_contexts_with_spaces,
            WHITELIST_OR_BLACKLIST_HEADERS,
            removeNull=True,
            metadata=sidedata,
        ),
        outputs={
            "SigSciences.Corp.Site.Blacklist(val.ID==obj.ID)": blacklist_ips_contexts,
        },
    )


def add_ip_to_whitelist(siteName, ip, note, expires=None):
    url = SERVER_URL + WHITELIST_SUFFIX.format(CORPNAME, siteName)
    return add_ip_to_whitelist_or_blacklist(url, ip, note, expires)


def add_ip_to_whitelist_command():
    """Add an ip to the whitelist"""
    args = demisto.args()
    response_data, errors_data = add_ip_to_whitelist(args["siteName"], args["ip"], args["note"], args.get("expires", None))
    if response_data:
        whitelist_ip_context = gen_context_for_add_to_whitelist_or_blacklist(response_data)
        human_readable = gen_human_readable_for_add_to_whitelist_or_blacklist(whitelist_ip_context)

        return_outputs(
            raw_response=response_data,
            readable_output=tableToMarkdown(
                ADD_IP_TO_WHITELIST_TITLE,
                human_readable,
                headers=ADD_IP_HEADERS,
                removeNull=True,
                metadata=IP_ADDED_TO_WHITELIST_TITLE.format(args["ip"]),
            ),
            outputs={
                "SigSciences.Corp.Site.Whitelist(val.ID==obj.ID)": whitelist_ip_context,
            },
        )
    if errors_data:
        return_error("\n".join(errors_data))


def add_ip_to_blacklist(siteName, ip, note, expires=None):
    url = SERVER_URL + BLACKLIST_SUFFIX.format(CORPNAME, siteName)
    return add_ip_to_whitelist_or_blacklist(url, ip, note, expires)


def add_ip_to_blacklist_command():
    """Add an ip to the blacklist"""
    args = demisto.args()
    response_data, errors_data = add_ip_to_blacklist(args["siteName"], args["ip"], args["note"], args.get("expires", None))
    if response_data:
        blacklist_ip_context = gen_context_for_add_to_whitelist_or_blacklist(response_data)
        human_readable = gen_human_readable_for_add_to_whitelist_or_blacklist(blacklist_ip_context)

        return_outputs(
            raw_response=response_data,
            readable_output=tableToMarkdown(
                ADD_IP_TO_BLACKLIST_TITLE,
                human_readable,
                headers=ADD_IP_HEADERS,
                removeNull=True,
                metadata=IP_ADDED_TO_BLACKLIST_TITLE.format(args["ip"]),
            ),
            outputs={
                "SigSciences.Corp.Site.Blacklist(val.ID==obj.ID)": blacklist_ip_context,
            },
        )
    if errors_data:
        return_error("/n".join(errors_data))


def whitelist_remove_ip(siteName, ip):
    check_ip_is_valid(ip)
    site_whitelist = get_whitelist(siteName)
    data = site_whitelist.get("data", [])
    for item in data:
        if item.get("source", "") == ip:
            url = SERVER_URL + DELETE_WHITELIST_IP_SUFFIX.format(CORPNAME, siteName, item.get("id", ""))
            res = http_request("DELETE", url)

    if "res" not in locals():
        return_error(f"The IP {ip} was not found on the Whitelist")

    return site_whitelist


def whitelist_remove_ip_command():
    """Remove an ip from the whitelist"""
    args = demisto.args()
    response_data = whitelist_remove_ip(args["siteName"], args["IP"])

    demisto.results(
        {
            "Type": entryTypes["note"],
            "ContentsFormat": formats["markdown"],
            "Contents": response_data,
            "HumanReadable": WHITELIST_REMOVE_IP_TITLE.format(args["IP"]),
        }
    )


def blacklist_remove_ip(siteName, ip):
    check_ip_is_valid(ip)
    site_blacklist = get_blacklist(siteName)
    data = site_blacklist.get("data", [])
    for item in data:
        if item.get("source", "") == ip:
            url = SERVER_URL + DELETE_BLACKLIST_IP_SUFFIX.format(CORPNAME, siteName, item.get("id", ""))
            res = http_request("DELETE", url)

    if "res" not in locals():
        return_error(f"The IP {ip} was not found on the Blacklist")

    return site_blacklist


def blacklist_remove_ip_command():
    """Remove an ip from the blacklist"""
    args = demisto.args()
    response_data = blacklist_remove_ip(args["siteName"], args["IP"])

    demisto.results(
        {
            "Type": entryTypes["note"],
            "ContentsFormat": formats["markdown"],
            "Contents": response_data,
            "HumanReadable": BLACKLIST_REMOVE_IP_TITLE.format(args["IP"]),
        }
    )


def get_sites():
    url = SERVER_URL + GET_SITES_SUFFIX.format(CORPNAME)
    res = http_request("GET", url)
    return res


def get_sites_command():
    """Get the sites list"""
    res = get_sites()
    data = res.get("data", [])

    outputs = []
    for item in data:
        output = {}
        output["Name"] = item.get("name", "")
        output["CreatedDate"] = item.get("created", "")

        outputs.append(output)

    outputs_with_spaces = return_list_of_dicts_with_spaces(outputs)

    return_outputs(
        raw_response=res,
        readable_output=tableToMarkdown(SITES_LIST_TITLE, outputs_with_spaces, headers=GET_SITE_HEADERS, removeNull=True),
        outputs={
            "SigSciences.Sites(val.Name==obj.Name)": outputs,
        },
    )


def fetch_incidents():
    now_utc = datetime.utcnow()
    most_recent_event_time = ""

    last_run_data = demisto.getLastRun()
    if last_run_data:
        last_run_time = last_run_data["time"]
    else:
        date_time_interval_ago = now_utc - timedelta(minutes=int(FETCH_INTERVAL))
        date_time_interval_ago_posix = datetime_to_posix_without_milliseconds(date_time_interval_ago)
        last_run_time = date_time_interval_ago_posix

    list_of_sites_to_fetch = get_list_of_site_names_to_fetch()
    events_array = get_events_from_given_sites(list_of_sites_to_fetch, last_run_time)
    incidents = []
    for event in events_array:
        event_time = event["timestamp"]
        event_time = datetime.strptime(event_time[:-1], "%Y-%m-%dT%H:%M:%S")
        event_time = datetime_to_posix_without_milliseconds(event_time)
        if event_time > last_run_time:
            incidents.append(
                {"name": str(event["id"]) + " - SignalSciences", "occurred": event["timestamp"], "rawJSON": json.dumps(event)}
            )
        if event_time > most_recent_event_time:
            most_recent_event_time = event_time

    demisto.incidents(incidents)
    demisto.setLastRun({"time": most_recent_event_time})


""" EXECUTION CODE """


def main() -> None:
    LOG(f"command is {demisto.command()}")
    try:
        if not re.match(r"[0-9a-z_.-]+", CORPNAME):
            raise ValueError("Corporation Name should match the pattern [0-9a-z_.-]+")

        if demisto.command() == "test-module":
            test_module()
        elif demisto.command() == "fetch-incidents":
            fetch_incidents()
        elif demisto.command() == "sigsci-get-whitelist":
            get_whitelist_command()
        elif demisto.command() == "sigsci-get-blacklist":
            get_blacklist_command()
        elif demisto.command() == "sigsci-whitelist-add-ip":
            add_ip_to_whitelist_command()
        elif demisto.command() == "sigsci-blacklist-add-ip":
            add_ip_to_blacklist_command()
        elif demisto.command() == "sigsci-whitelist-remove-ip":
            whitelist_remove_ip_command()
        elif demisto.command() == "sigsci-blacklist-remove-ip":
            blacklist_remove_ip_command()
        elif demisto.command() == "sigsci-get-sites":
            get_sites_command()
        elif demisto.command() == "sigsci-create-corp-list":
            create_corp_list_command()
        elif demisto.command() == "sigsci-get-corp-list":
            get_corp_list_command()
        elif demisto.command() == "sigsci-delete-corp-list":
            delete_corp_list_command()
        elif demisto.command() == "sigsci-update-corp-list":
            update_corp_list_command()
        elif demisto.command() == "sigsci-get-all-corp-lists":
            get_all_corp_lists_command()
        elif demisto.command() == "sigsci-create-site-list":
            create_site_list_command()
        elif demisto.command() == "sigsci-get-site-list":
            get_site_list_command()
        elif demisto.command() == "sigsci-delete-site-list":
            delete_site_list_command()
        elif demisto.command() == "sigsci-update-site-list":
            update_site_list_command()
        elif demisto.command() == "sigsci-get-all-site-lists":
            get_all_site_lists_command()
        elif demisto.command() == "sigsci-add-alert":
            add_alert_command()
        elif demisto.command() == "sigsci-get-alert":
            get_alert_command()
        elif demisto.command() == "sigsci-delete-alert":
            delete_alert_command()
        elif demisto.command() == "sigsci-update-alert":
            update_alert_command()
        elif demisto.command() == "sigsci-get-all-alerts":
            get_all_alerts_command()
        elif demisto.command() == "sigsci-get-events":
            get_events_command()
        elif demisto.command() == "sigsci-expire-event":
            expire_event_command()
        elif demisto.command() == "sigsci-get-event-by-id":
            get_event_by_id_command()
        elif demisto.command() == "sigsci-get-requests":
            get_requests_command()
        elif demisto.command() == "sigsci-get-request-by-id":
            get_request_by_id_command()
    except Exception as e:
        return_error(e)


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