Zscaler

Zscaler is a cloud security solution built for performance and flexible scalability. This integration enables you to manage URL and IP address allow lists and block lists, manage and update categories, get Sandbox reports, create, manage, and update IP destination groups and manually log in, log out, and activate changes in a Zscaler session.

Network Security · Zscaler Internet Access

Details

IDZscaler
ProviderZscaler
CategoryNetwork Security
From Version5.0.0
Docker Imagedemisto/python3:3.12.13.10116658
Supported ModulesAgentix XSIAM EDR Cortex Cloud Cloud Runtime Security

README

Zscaler is a cloud security solution built for performance and flexible scalability. This integration enables you to manage URL and IP address allow lists and block lists, manage and update categories, get Sandbox reports, create, manage, and update IP destination groups and manually log in, log out, and activate changes in a Zscaler session.

For the integration to work properly, the Zscaler user must have admin permissions.

Category ID is the same as the category name, except all letters are capitalized and each word is separated with an underscore instead of spaces. For example, if the category name is Other Education, then the Category ID is OTHER_EDUCATION.

A custom category ID has the format CUSTOM_01, which is not indicative of the category. Use the zscaler-get-categories command to get a custom category and its configured name.

Configure Zscaler Internet Access in Cortex

Parameter Description Required
Cloud Name (i.e. https://zsapi.zscalertwo.net) See https://help.zscaler.com/zia/getting-started-zia-api#RetrieveBaseURL on how to retrieve your cloud name. True
Username   True
Password   True
API Key   True
Source Reliability Reliability of the source providing the intelligence data. False
Auto Logout If enabled, the integration will log out after executing each command. False
Auto Activate Changes If enabled, the integration will activate the command changes after each execution. If disabled, use the ‘zscaler-activate-changes’ command to activate Zscaler command changes. False
Trust any certificate (not secure)   False
Use system proxy settings   False
Timeout (in seconds) for HTTP requests to Zscaler   False

Commands

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

zscaler-blacklist-url


Adds the specified URLs to the block list.

Base Command

zscaler-blacklist-url

Input

Argument Name Description Required
url A comma-separated list of URLs to add to block list. For example, snapchat.com,facebook.com. Required

Context Output

There is no context output for this command.

Command Example

!zscaler-blacklist-url url=phishing.com,malware.net

Human Readable Output

Added the following URLs to the block list successfully:
phishing.com
malware.net

url


Looks up the classification for the each of the specified URLs.

Base Command

url

Input

Argument Name Description Required
url A comma-separated list of URLs for which to look up the classification. For example, abc.com,xyz.com. The maximum number of URLs per call is 100. A URL cannot exceed 1024 characters. If there are multiple URLs, set the ‘multiple’ argument to ‘true’. Required
multiple Whether there are multiple URLs in the ‘url’ argument. If a URL contains commas, set this argument to ‘false’ and enter the single URL as the ‘url’ argument. Possible values are: true, false. Default is true. Optional

Context Output

Path Type Description
URL.Data string The URL that was searched.
URL.Address string The URL that was searched.
Zscaler.URL.urlClassifications string The classification of the URL. For example, MUSIC or WEB_SEARCH.
Zscaler.URL.urlClassificationsWithSecurityAlert string The classifications of the URLs that have security alerts.
URL.Malicious.Vendor string For malicious URLs, the vendor that tagged the URL as malicious.
URL.Malicious.Description string For malicious URLs, the reason the vendor tagged the URL as malicious.
DBotScore.Indicator string The indicator that was tested.
DBotScore.Type string The indicator type.
DBotScore.Vendor string The vendor used to calculate the score.
DBotScore.Score number The actual score.

Command Example

!url url=facebook.com

Context Example

{
    "DBotScore": [
        {
            "Indicator": "facebook.com",
            "Score": 1,
            "Type": "url",
            "Vendor": "Zscaler"
        }
    ],
    "URL": {
        "Address": "facebook.com",
        "Data": "facebook.com",
        "urlClassifications": "SOCIAL_NETWORKING"
    }
}

Human Readable Output

Zscaler URL Lookup

url urlClassifications
facebook.com SOCIAL_NETWORKING

ip


Looks up the classification for each of the specified IP addresses.

Base Command

ip

Input

Argument Name Description Required
ip A comma-separated list of IP address for which to look up the classification. For example, 8.8.8.8,1.2.3.4. The maximum number of URLs per call is 100. An IP address cannot exceed 1024 characters. Required

Context Output

Path Type Description
IP.Address string The IP address that was searched.
Zscaler.IP.ipClassifications string The classification of the IP address. For example, MUSIC or WEB_SEARCH.
Zscaler.IP.iplClassificationsWithSecurityAlert string Classifications that have a security alert for the IP address.
IP.Malicious.Vendor string For malicious IP addresses, the vendor that tagged the IP address as malicious.
IP.Malicious.Description string For malicious IP addresses, the reason the vendor tagged the IP address as malicious.
DBotScore.Indicator string The indicator that was tested.
DBotScore.Type string The indicator type.
DBotScore.Vendor string The vendor used to calculate the score.
DBotScore.Score number The actual score.

Command Example

!ip ip=8.8.8.8

Context Example

{
    "DBotScore": [
        {
            "Indicator": "8.8.8.8",
            "Score": 1,
            "Type": "ip",
            "Vendor": "Zscaler"
        }
    ],
    "IP": {
        "Address": "8.8.8.8",
        "ipClassifications": "WEB_SEARCH"
    }
}

Human Readable Output

Zscaler IP Lookup

ip ipClassifications
8.8.8.8 WEB_SEARCH

domain


Looks up the classification for each of the specified domains.

Base Command

domain

Input

Argument Name Description Required
domain A comma-separated list of domains for which to look up the classification. For example, example.com,google.com. Required
multiple Whether there are multiple domains in the ‘domain’ argument. If a domain contains commas, set this argument to ‘false’ and enter the single domain as the ‘domain’ argument. Possible values are: true, false. Default is true. Optional

Context Output

Path Type Description
Domain.Name string The domain that was searched.
Zscaler.Domain.domainClassifications string The classification of the domain. For example, MUSIC or WEB_SEARCH.
Zscaler.Domain.domainClassificationsWithSecurityAlert string The classifications of the domains that have security alerts.
Domain.Malicious.Vendor string For malicious domains, the vendor that tagged the domain as malicious.
Domain.Malicious.Description string For malicious domains, the reason the vendor tagged the domain as malicious.
DBotScore.Indicator string The indicator that was tested.
DBotScore.Type string The indicator type.
DBotScore.Vendor string The vendor used to calculate the score.
DBotScore.Score number The actual score.

Command Example

!domain domain=example.com

Context Example

{
    "DBotScore": [
        {
            "Indicator": "example.com",
            "Score": 1,
            "Type": "domain",
            "Vendor": "Zscaler"
        }
    ],
    "Domain": {
        "Name": "example.com"
    },
    "Zscaler": {
        "Domain": {
            "Name": "example.com",
            "domainClassifications": "REFERENCE_SITES"
        }
    }
}

Human Readable Output

Zscaler Domain Lookup

domain domainClassifications
example.com REFERENCE_SITES

Command Example with Multiple Domains

!domain domain=example.com,google.com multiple=true

Context Example

{
    "DBotScore": [
        {
            "Indicator": "example.com",
            "Score": 1,
            "Type": "domain",
            "Vendor": "Zscaler"
        },
        {
            "Indicator": "google.com",
            "Score": 1,
            "Type": "domain",
            "Vendor": "Zscaler"
        }
    ],
    "Domain": [
        {
            "Name": "example.com"
        },
        {
            "Name": "google.com"
        }
    ],
    "Zscaler": {
        "Domain": [
            {
                "Name": "example.com",
                "domainClassifications": "REFERENCE_SITES"
            },
            {
                "Name": "google.com",
                "domainClassifications": "WEB_SEARCH"
            }
        ]
    }
}

Human Readable Output

Zscaler Domain Lookup

domain domainClassifications
example.com REFERENCE_SITES
google.com WEB_SEARCH

zscaler-undo-blacklist-url


Removes the specified URLs from the block list.

Base Command

zscaler-undo-blacklist-url

Input

Argument Name Description Required
url A comma-separated list of URLs to remove from the block list. For example, snapchat.com,facebook.com. Required

Context Output

There is no context output for this command.

zscaler-whitelist-url


Adds the specified URLs to the allow list.

Base Command

zscaler-whitelist-url

Input

Argument Name Description Required
url A comma-separated list of URLs to add to the allow list. For example, snapchat.com,facebook.com. Required

Context Output

There is no context output for this command.

Command Example

!zscaler-whitelist-url url=phising.com,malware.net

Human Readable Output

Added the following URLs to the allow list successfully:
phishing.com
malware.net

zscaler-undo-whitelist-url


Removes the specified URLs from the allow list.

Base Command

zscaler-undo-whitelist-url

Input

Argument Name Description Required
url A comma-separated list of URLs to remove from the allow list. For example, snapchat.com,facebook.com. Required

Context Output

There is no context output for this command.

Command Example

!zscaler-undo-whitelist-url url=phising.com,malware.net

Human Readable Output

Removed the following URLs from the allow list successfully:
phishing.com
malware.net

zscaler-undo-whitelist-ip


Removes the specified IP addresses from the allow list.

Base Command

zscaler-undo-whitelist-ip

Input

Argument Name Description Required
ip A comma-separated list of IP addresses to remove from the allow list. For example, 8.8.8.8,1.2.3.4. Required

Context Output

There is no context output for this command.

Command Example

!zscaler-undo-whitelist-ip ip=2.2.2.2,3.3.3.3

Human Readable Output

Removed the following IP addresses from the allow list successfully:
2.2.2.2
3.3.3.3

zscaler-whitelist-ip


Adds the specified IP address to the allow list.

Base Command

zscaler-whitelist-ip

Input

Argument Name Description Required
ip A comma-separated list of IP addresses to add to the allow list. For example, 8.8.8.8,1.2.3.4. Required

Context Output

There is no context output for this command.

Command Example

!zscaler-whitelist-ip ip=2.2.2.2,3.3.3.3

Human Readable Output

Added the following IP addresses to the allow list successfully:
2.2.2.2
3.3.3.3

zscaler-undo-blacklist-ip


Removes the specified IP addresses from the block list.

Base Command

zscaler-undo-blacklist-ip

Input

Argument Name Description Required
ip A comma-separated list of IP addresses to remove from the allow list. For example, 8.8.8.8,1.2.3.4. Required

Context Output

There is no context output for this command.

Command Example

!zscaler-undo-blacklist-ip ip=2.2.2.2,3.3.3.3

Human Readable Output

Removed the following IP addresses from the block list successfully:
2.2.2.2
3.3.3.3

zscaler-blacklist-ip


Adds the specified IP addresses to the block list.

Base Command

zscaler-blacklist-ip

Input

Argument Name Description Required
ip A comma-separated list of IP addresses to add to the block list. For example, 8.8.8.8,1.2.3.4. Required

Context Output

There is no context output for this command.

Command Example

!zscaler-blacklist-ip ip=2.2.2.2,3.3.3.3

Human Readable Output

Added the following IP addresses to the block list successfully:
2.2.2.2
3.3.3.3

zscaler-category-add-url


Adds URLs to the specified category.
Ensure that the URLs are properly formatted according to Zscaler’s guidelines. For more information on valid URL formats, refer to the Zscaler URL Format Guidelines.

Base Command

zscaler-category-add-url

Input

Argument Name Description Required
category-id The ID of the category to add the specified URLs to. For example, RADIO_STATIONS. You can retrieve the category IDs by running the ‘zscaler-get-categories’ command. Required
url A comma-separated list of URLs to add to the specified category. For example, pandora.com,spotify.com.
Important:
If any URL contains a comma (,), you must pass the url argument as a JSON list wrapped in backticks (`).
Example (single URL with comma):
url=`[“https://example.com/foo,bar”]`
Example (multiple URLs with commas):
url=`[“https://example.com/foo,bar”,”https://example2.com/foo,bar”]`
Optional
retaining-parent-category-url A comma-separated list of URLs to add to the retaining parent category section inside the specified category. For example, pandora.com,spotify.com. Optional

Context Output

Path Type Description
Zscaler.Category.CustomCategory boolean True, if the category is a custom category. Otherwise, false.
Zscaler.Category.Description string The description of the category.
Zscaler.Category.ID string The ID of the category.
Zscaler.Category.URL string The URL of the category.

Command Example

!zscaler-category-add-url category-id=MUSIC url=demisto.com,apple.com

Example with a URL containing a comma:

!zscaler-category-add-url category-id="CUSTOM_123" url=`["https://example.com/foo,bar"]`

Context example

{
    "Zscaler": {
      "Category": {
        "CustomCategory": false,
        "Description": "MUSIC_DESC",
        "ID": "MUSIC",
        "URL": [
            "demisto.com",
            "apple.com"
        ]
      }
    }
}

Human Readable Output

Added the following URL addresses to category MUSIC:

  • demisto.com
  • apple.com

zscaler-category-add-ip


Adds IP address to the specified category.

Base Command

zscaler-category-add-ip

Input

Argument Name Description Required
category-id The ID of the category to add the specified IP addresses to. For example, RADIO_STATIONS. You can retrieve the category IDs by running the ‘zscaler-get-categories’ command. Required
ip A comma-separated list of IP address to add to the specified category. For example, 1.2.3.4,8.8.8.8. Optional
retaining-parent-category-ip A comma-separated list of IP address to add to the retaining parent category section inside the specified category. For example, 1.2.3.4,8.8.8.8. Optional

Context Output

Path Type Description
Zscaler.Category.CustomCategory boolean True, if the category is a custom category. Otherwise, false.
Zscaler.Category.Description string The description of the category.
Zscaler.Category.ID string The ID of the category.
Zscaler.Category.URL string The URL of the category

Command Example

!zscaler-category-add-ip category-id=REFERENCE_SITES ip=1.2.3.4,8.8.8.8

Context Example

{
    "Zscaler": {
      "Category": {
        "CustomCategory": false,
        "Description": "REFERENCE_SITES_DESC",
        "ID": "REFERENCE_SITES",
        "URL": [
            "1.2.3.4",
            "8.8.8.8"
        ]
      }
    }
}

Human Readable Output

Added the following IP addresses to category REFERENCE_SITES:

  • 1.2.3.4
  • 8.8.8.8

zscaler-category-remove-url


Removes URLs from the specified category.

Base Command

zscaler-category-remove-url

Input

Argument Name Description Required
category-id The ID of the category to remove the specified URLs from. For example, RADIO_STATIONS. You can retrieve the category IDs by running the ‘zscaler-get-categories’ command. Required
url A comma-separated list of URLs to remove from the specified category. For example, pandora.com,spotify.com. Optional
retaining-parent-category-url A comma-separated list of URLs to remove from the retaining parent category section inside the specified category. For example, pandora.com,spotify.com. Optional

Context Output

Path Type Description
Zscaler.Category.CustomCategory boolean True, if the category is a custom category. Otherwise, false.
Zscaler.Category.Description string The description of the category.
Zscaler.Category.ID string The ID of the category.
Zscaler.Category.URL string The URL of the category.

Command Example

!zscaler-category-remove-url category-id=MUSIC url=apple.com

Context Example

{
    "Zscaler": {
      "Category": {
        "CustomCategory": false,
        "Description": "MUSIC_DESC",
        "ID": "MUSIC",
        "URL": [
            "demisto.com"
        ]
      }
    }
}
Human Readable Output

Removed the following URL addresses to category MUSIC:

  • apple.com

zscaler-category-remove-ip


Removes IP address from the specified category.

Base Command

zscaler-category-remove-ip

Input

Argument Name Description Required
category-id The ID of the category to remove the specified IP addresses from. For example, RADIO_STATIONS. You can retrieve the category IDs by running the ‘zscaler-get-categories’ command. Required
ip A comma-separated list of IP addresses to remove from the specified category. For example, 1.2.3.4,8.8.8.8. Optional
retaining-parent-category-ip A comma-separated list of IP address to remove from the retaining parent category section inside the specified category. For example, 1.2.3.4,8.8.8.8. Optional

Context Output

Path Type Description
Zscaler.Category.CustomCategory boolean True, if the category is a custom category. Otherwise, false.
Zscaler.Category.Description string The description of the category.
Zscaler.Category.ID string The ID of the category.
Zscaler.Category.URL string The URL of the category.

Command Example

!zscaler-category-remove-ip category-id=REFERENCE_SITES ip=1.2.3.4

Context Example
{
    "Zscaler": {
      "Category": {
        "CustomCategory": false,
        "Description": "REFERENCE_SITES_DESC",
        "ID": "REFERENCE_SITES",
        "URL": [
            "8.8.8.8"
        ]
      }
    }
}
Human Readable Output

Removed the following IP addresses to category REFERENCE_SITES:

  • 1.2.3.4

zscaler-get-categories


Retrieves a list of all categories.

Base Command

zscaler-get-categories

Input

Argument Name Description Required
displayURL Whether to display the URLs of each category in the War Room. URLs will always be returned to the Context Data. Possible values are: true, false. Default is false. Optional
custom_categories_only Whether to retrieve only custom categories to the War Room. Possible values are: true, false. Default is false. Optional
get_ids_and_names_only Whether to retrieve only a list containing URL category IDs and names. Even if displayURL is set to true, URLs will not be returned. Please note - the API does not support the combination of custom_only and get_ids_and_names_only. Possible values are: true, false. Default is false. Optional

Context Output

Path Type Description
Zscaler.Category.ID string The ID of the category.
Zscaler.Category.CustomCategory boolean True, if the category is a custom category. Otherwise, false.
Zscaler.Category.URL string The URL of the category.
Zscaler.Category.RetainingParentCategoryURL string The URLs of the retaining parent category.
Zscaler.Category.Description string The description of the category.
Zscaler.Category.Name string The name of the category.

Command Example


#### Context Example

```json
{  
   "Zscaler":{  
      "Category":{  
         "ID":"INTERNET_SERVICES",
         "Description":"INTERNET_SERVICES_DESC",
         "URL":[  
            "google.com",
            "facebook.com"
         ],
         "CustomCategory":"false"
      },
      "ID":"CUSTOM_01",
      "Name":"CustomCategory",
      "URL":[  
         "demisto.com",
         "apple.com"
      ],
      "RetainingParentCategoryURL":[  
         "pandora.com",
         "spotify.com"
      ],
      "CustomCategory":"true"
   }
}

Human Readable Output

CustomCategory Description ID Name URL RetainingParentCategoryURL
false INTERNET_SERVICES_DESC INTERNET_SERVICES   google.com,facebook.com  
true   CUSTOM_01 CustomCategory demisto.com,apple.com pandora.com”,”spotify.com

zscaler-get-blacklist


Retrieves the Zscaler default block list.

Base Command

zscaler-get-blacklist

Input

Argument Name Description Required
filter Filter results by URL or IP objects. Possible values are: url, ip. Optional
query Query (Python regular expression) to match against. For example, 8...8. Optional

Context Output

Path Type Description
Zscaler.Blacklist string The Zscaler block list.

Command Example


#### Context Example

```json
{
    "Zscaler": {
        "Blacklist": [
            "malicious.com,
            "bad.net"
        ]
    }
}

Human Readable Output

Zscaler block list

  • malicious.com
  • bad.net

zscaler-get-whitelist


Retrieves the Zscaler default allow list.

Base Command

zscaler-get-whitelist

Input

There are no input arguments for this command.

Context Output

Path Type Description
Zscaler.Whitelist string The Zscaler allow list.

Command Example


#### Context Example

```json
{
    "Zscaler": {
        "Whitelist": [
            "demisto.com,
            "apple.com"
        ]
    }
}

Human Readable Output

Zscaler whitelist

  • demisto.com
  • apple.net

zscaler-sandbox-report


Retrieves a full or summary report of the file that was analyzed by Sandbox. The file is represented by the specified MD5 hash.

Base Command

zscaler-sandbox-report

Input

Argument Name Description Required
md5 The MD5 hash of a file. Required
details The type of report. Possible values are ‘full’ or ‘summary’. Default is ‘full’. Optional

Context Output

Path Type Description
File.MD5 string The MD5 hash of the file.
File.Malicious.Vendor string For malicious files, the vendor that tagged the file as malicious.
File.Malicious.Description string For malicious files, the reason the vendor tagged the file as malicious.
File.DetectedMalware string The malware detected in the file.
File.FileType string The file type.
DBotScore.Indicator string The MD5 hash file that was tested.
DBotScore.Type string The MD5 hash file type.
DBotScore.Vendor string The vendor that calculated the DBot score.
DBotScore.Score number The actual DBot score.

Command Example

!zscaler-sandbox-report md5=3FD0EA0AE759D58274310C022FB0CBBA details=summary

Context Example

{
    "DBotScore": {
        "Vendor": "Zscaler", 
        "Indicator": "3FD0EA0AE759D58274310C022FB0CBBA", 
        "Score": 3, 
        "Type": "file"
    }, 
    "File": {
        "Zscaler": {
            "FileType": null, 
            "DetectedMalware": ""
        }, 
        "Malicious": {
            "Vendor": "Zscaler", 
            "Description": "Classified as Malicious, with threat score: 100"
        }, 
        "MD5": "3FD0EA0AE759D58274310C022FB0CBBA"
    }
}

Human Readable Output

Full Sandbox Report
Category Indicator Vendor Score Zscaler Score Type
MALWARE_BOTNET 3FD0EA0AE759D58274310C022FB0CBBA Zscaler 3 100 file

Additional Information

image

image

zscaler-login


Manually create a Zscaler login session. This command will also try to log out of the previous session.

Base Command

zscaler-login

Input

There are no input arguments for this command.

Context Output

There is no context output for this command.

Command Example


#### Human Readable Output

>Zscaler session created successfully.

### zscaler-logout

***
Logs out of the current Zscaler session.

#### Base Command

`zscaler-logout`

#### Input

There are no input arguments for this command.

#### Context Output

There is no context output for this command.

#### Command Example

```!zscaler-logout```

#### Human Readable Output

>API session logged out of Zscaler successfully.

### zscaler-activate-changes

***
Activates the changes executed by other Zscaler commands in this session.

#### Base Command

`zscaler-activate-changes`

#### Input

There are no input arguments for this command.

#### Context Output

There is no context output for this command.

#### Command Example

```!zscaler-activate-changes```

#### Human Readable Output

>Changes have been activated successfully.

### zscaler-url-quota

***
Gets information on the number of unique URLs that are currently provisioned for your organization as well as how many URLs you can add before reaching that number.

#### Base Command

`zscaler-url-quota`

#### Input

There are no input arguments for this command.

#### Context Output

| **Path** | **Type** | **Description** |
| --- | --- | --- |
| Zscaler.remainingUrlsQuota | Number | The number of URLs you can add before reaching the quota. |
| Zscaler.uniqueUrlsProvisioned | Number | The number of unique URLs that are currently provisioned for your organization. |

### zscaler-get-users

***
Get Zscaler users

#### Base Command

`zscaler-get-users`

#### Input

| **Argument Name** | **Description** | **Required** |
| --- | --- | --- |
| name | Filer by user name. | Optional |
| page | Specifies the page offset. | Optional |
| pageSize | Specifies the page size. Default is 100. | Optional |

#### Context Output

There is no context output for this command.

### zscaler-update-user

***
Updates the user information for the specified ID.

#### Base Command

`zscaler-update-user`

#### Input

| **Argument Name** | **Description** | **Required** |
| --- | --- | --- |
| id | The unique identifier for the user. | Required |
| user | New user information. Docs: <https://help.zscaler.com/zia/api#/User%20Management/updateUser>. | Required |

#### Context Output

There is no context output for this command.

### zscaler-get-departments

***
Get a list of departments. It can be searched by name.

#### Base Command

`zscaler-get-departments`

#### Input

| **Argument Name** | **Description** | **Required** |
| --- | --- | --- |
| name | Filter by department name. | Optional |
| page | Specifies the page offset. | Optional |
| pageSize | Specifies the page size. Default is 100. | Optional |

#### Context Output

There is no context output for this command.

### zscaler-get-usergroups

***
Gets a list of groups

#### Base Command

`zscaler-get-usergroups`

#### Input

| **Argument Name** | **Description** | **Required** |
| --- | --- | --- |
| name | Filter by group name or comment. | Optional |
| page | Specifies the page offset. | Optional |
| pageSize | Specifies the page size. Default is 100. | Optional |

#### Context Output

There is no context output for this command.

### zscaler-create-ip-destination-group

***
Adds a new IP destination group.

#### Base Command

`zscaler-create-ip-destination-group`

#### Input

| **Argument Name** | **Description** | **Required** |
| --- | --- | --- |
| name | Destination IP group name. | Required |
| type | Destination IP group type (i.e., the group can contain destination IP addresses, countries, URL categories or FQDNs). Possible values are: DSTN_IP, DSTN_FQDN, DSTN_DOMAIN, DSTN_OTHER. | Required |
| addresses | Destination IP addresses, FQDNs, or wildcard FQDNs added to the group. | Optional |
| description | Additional information about the destination IP group. | Optional |
| ip_categories | Destination IP address URL categories. You can identify destinations based on the URL category of the domain. To retrieve a list of possible values, you can execute the zscaler-get-categories command. | Optional |
| countries | Destination IP address countries. You can identify destinations based on the location of a server. A list of possible values can be found here <https://help.zscaler.com/zia/firewall-policies#/ipDestinationGroups-post>. | Optional |

#### Context Output

| **Path** | **Type** | **Description** |
| --- | --- | --- |
| Zscaler.IPDestinationGroup.ID | number | Unique identifier for the destination IP group. |
| Zscaler.IPDestinationGroup.Name | string | Destination IP group name. |
| Zscaler.IPDestinationGroup.Type | string | Destination IP group type \(i.e., the group can contain destination IP addresses, countries, URL categories or FQDNs\). |
| Zscaler.IPDestinationGroup.Description | string | Destination IP group description. |
| Zscaler.IPDestinationGroup.Addresses | string | Destination IP addresses, FQDNs, or wildcard FQDNs added to the group. |
| Zscaler.IPDestinationGroup.IpCategories | string | Destination IP address URL categories. You can identify destinations based on the URL category of the domain. |
| Zscaler.IPDestinationGroup.Countries | string | Destination IP address countries. You can identify destinations based on the location of a server. |
| Zscaler.IPDestinationGroup.IsNonEditable | boolean | If set to true, the destination IP address group is non-editable. This field is applicable only to predefined IP address groups, which cannot be modified. |

#### Command Example

```!zscaler-create-ip-destination-group addresses="127.0.0.2,127.0.0.1" description=Localhost name=Test99 type=DSTN_IP```

#### Context example

```json
{
    "Zscaler.IPDestinationGroup": {
        "ID": 2000359, 
        "Name": "Test99", 
        "Type": "DSTN_IP", 
        "Addresses": [
            "127.0.0.2", 
            "127.0.0.1"
        ], 
        "Description": "Localhost",
        "IpCategories": [], 
        "Countries": [], 
        "IsNonEditable": false
    }
}

Human Readable Output

IP Destination group created

Addresses Countries Description ID IpCategories IsNonEditable Name Type
127.0.0.2,
127.0.0.1
  Localhost 2000359   false Test99 DSTN_IP

zscaler-edit-ip-destination-group


Updates the IP destination group information for the specified group ID.

Base Command

zscaler-edit-ip-destination-group

Input

Argument Name Description Required
ip_group_id The unique identifier for the IP destination group. Required
name Destination IP group name. Optional
addresses Destination IP addresses, FQDNs, or wildcard FQDNs added to the group. Optional
description Additional information about the destination IP group. Optional
ip_categories Destination IP address URL categories. You can identify destinations based on the URL category of the domain. To retrieve a list of possible values you can execute the zscaler-get-categories command. Optional
countries Destination IP address countries. You can identify destinations based on the location of a server. A list of possible values can be found here https://help.zscaler.com/zia/firewall-policies#/ipDestinationGroups/{ipGroupId}-put. Optional
is_non_editable If set to true, the destination IP address group is non-editable. This field is applicable only to predefined IP address groups, which cannot be modified. Possible values are: True, False. Default is False. Optional
override When set to true (default), the existing IP destination group configuration is replaced with the new values. When set to false, the existing configuration is preserved and new entries are appended. Possible values are: True, False. Default is True. Optional

Context Output

Path Type Description
Zscaler.IPDestinationGroup.ID number Unique identifier for the destination IP group.
Zscaler.IPDestinationGroup.Name string Destination IP group name.
Zscaler.IPDestinationGroup.Type string Destination IP group type (i.e., the group can contain destination IP addresses, countries, URL categories or FQDNs).
Zscaler.IPDestinationGroup.Description string Destination IP group description.
Zscaler.IPDestinationGroup.Addresses string Destination IP addresses, FQDNs, or wildcard FQDNs added to the group.
Zscaler.IPDestinationGroup.IpCategories string Destination IP address URL categories. You can identify destinations based on the URL category of the domain.
Zscaler.IPDestinationGroup.Countries string Destination IP address countries. You can identify destinations based on the location of a server.

Command Example

!zscaler-edit-ip-destination-group ip_group_id=2000359 addresses="127.0.0.2" description="Localhost v2" name=Test01

Context example

{
    "Zscaler.IPDestinationGroup": {
        "ID": 2000359, 
        "Name": "Test01", 
        "Type": "DSTN_IP", 
        "Description": "Localhost v2",
        "Addresses": [
          "127.0.0.2"
        ],
        "IpCategories": [], 
        "Countries": []
    }
}

Human Readable Output

IP Destination group updated

Addresses Countries Description ID IpCategories Name Type
127.0.0.2   Localhost v2 2000359   Test01 DSTN_IP

zscaler-list-ip-destination-groups


Gets a list of all IP destination groups or the IP destination group information for the specified group ID.

Base Command

zscaler-list-ip-destination-groups

Input

Argument Name Description Required
ip_group_id A comma-separated list of unique identifiers for the IP destination groups. Optional
exclude_type The IP group type to be excluded from the results. Possible values are: DSTN_IP, DSTN_FQDN, DSTN_DOMAIN, DSTN_OTHER. Optional
category_type The IP group type to be filtered from results. This argument is only supported when the ‘lite’ argument is set to True. Possible values are: DSTN_IP, DSTN_FQDN, DSTN_DOMAIN, DSTN_OTHER. Optional
include_ipv6 Retrieve IPv6 destination groups. Possible values are: True, False. Default is False. Optional
limit Limit of the results to be retrieved. Default is 50. Optional
all_results Whether to retrieve all results at once. Possible values are: True, False. Default is False. Optional
lite Whether to retrieve only limited information of IP destination groups. Includes ID, name and type of the IP destination groups. Possible values are: True, False. Default is False. Optional

Context Output

Path Type Description
Zscaler.IPDestinationGroup.ID string Unique identifier for the destination IP group.
Zscaler.IPDestinationGroup.Name string Destination IP group name.
Zscaler.IPDestinationGroup.Type string Destination IP group type (i.e., the group can contain destination IP addresses, countries, URL categories or FQDNs).
Zscaler.IPDestinationGroup.Addresses string Destination IP addresses, FQDNs, or wildcard FQDNs added to the group.
Zscaler.IPDestinationGroup.Description string Additional information about the destination IP group
Zscaler.IPDestinationGroup.IpCategories string Destination IP address URL categories. You can identify destinations based on the URL category of the domain.
Zscaler.IPDestinationGroup.Countries string Destination IP address countries. You can identify destinations based on the location of a server.

Command Example

!zscaler-list-ip-destination-groups exclude_type=DSTN_OTHER

Context example

{
    "Zscaler.IPDestinationGroup": [
    {
        "ID": 1997898, 
        "Name": "Test99", 
        "Type": "DSTN_IP", 
        "Addresses": ["127.0.0.2"], 
        "Description": "Localhost v2", 
        "IpCategories": [], 
        "Countries": []
    },
    {
        "ID": 2001335, 
        "Name": "Test01", 
        "Type": "DSTN_IP", 
        "Addresses": ["127.0.0.1"], 
        "Description": "Localhost v1", 
        "IpCategories": [], 
        "Countries": []
    }      
    ]
}

Human Readable Output

IPv4 Destination groups (2)

Addresses Countries Description ID IpCategories Name Type
127.0.0.2   Localhost v2 1997898   Test99 DSTN_IP
127.0.0.1   Localhost v1 2001335   Test01 DSTN_IP

!zscaler-list-ip-destination-groups lite=True

Context Example

[
    {
        "ID": 1964949, 
        "Name": "Russia-Region", 
        "Type": "DSTN_OTHER"
    }
]

Human Readable Output

IPv4 Destination groups lite (1)

ID Name Type    
  1964949   Russia-Region DSTN_OTHER

zscaler-delete-ip-destination-groups


Deletes the IP destination group associated with the specified group ID.

Base Command

zscaler-delete-ip-destination-groups

Input

Argument Name Description Required
ip_group_ids A comma-separated list of unique identifiers for the IP destination groups. Optional

Context Output

There is no context output for this command.

Command Example

!zscaler-delete-ip-destination-groups ip_group_id=2077311,2030563

Human Readable Output

IP Destination Group 2077311,2030563 deleted successfully

Configuration parameters

  • cloud — Cloud Name (i.e., <https://zsapi.zscalertwo.net>) (required)
  • credentials — Username (required)
  • creds_key
  • key — API Key
  • reliability — Source Reliability
  • auto_logout — Auto Logout
  • auto_activate — Auto Activate Changes
  • insecure — Trust any certificate (not secure)
  • proxy — Use system proxy settings
  • requestTimeout — Timeout (in seconds) for HTTP requests to Zscaler

Commands (31)

  • domain

    Looks up the classification for each of the specified Domains.

  • ip

    Looks up the classification for each of the specified IP addresses.

  • url

    Looks up the classification for the each of the specified URLs.

  • zscaler-activate-changes

    Activates the changes executed by other Zscaler commands in this session.

  • zscaler-blacklist-ip

    Adds the specified IP addresses to the block list.

  • zscaler-blacklist-url

    Adds the specified URLs to the block list.

  • zscaler-category-add-ip

    Adds IP address to the specified category.

  • zscaler-category-add-url

    Adds URLs to the specified category.

  • zscaler-category-remove-ip

    Removes IP address from the specified category.

  • zscaler-category-remove-url

    Removes URLs from the specified category.

  • zscaler-create-ip-destination-group

    Adds a new IP destination group.

  • zscaler-delete-ip-destination-groups

    Deletes the IP destination group associated with the specified group ID.

  • zscaler-edit-ip-destination-group

    Updates the IP destination group information for the specified group ID.

  • zscaler-get-blacklist

    Retrieves the Zscaler default block list.

  • zscaler-get-categories

    Retrieves a list of all categories.

  • zscaler-get-departments

    Get a list of departments. It can be searched by name.

  • zscaler-get-usergroups

    Gets a list of groups.

  • zscaler-get-users

    Get Zscaler users.

  • zscaler-get-whitelist

    Retrieves the Zscaler default allow list.

  • zscaler-list-ip-destination-groups

    Gets a list of all IP destination groups or the IP destination group information for the specified group ID.

  • zscaler-login

    Manually create a Zscaler login session. This command will also try to log out of the previous session.

  • zscaler-logout

    Logs out of the current Zscaler session.

  • zscaler-sandbox-report

    Retrieves a full or summary report of the file that was analyzed by Sandbox. The file is represented by the specified MD5 hash.

  • zscaler-undo-blacklist-ip

    Removes the specified IP addresses from the block list.

  • zscaler-undo-blacklist-url

    Removes the specified URLs from the block list.

  • zscaler-undo-whitelist-ip

    Removes the specified IP addresses from the allow list.

  • zscaler-undo-whitelist-url

    Removes the specified URLs from the allow list.

  • zscaler-update-user

    Updates the user information for the specified ID.

  • zscaler-url-quota

    Gets information on the number of unique URLs that are currently provisioned for your organization as well as how many URLs you can add before reaching that number.

  • zscaler-whitelist-ip

    Adds the specified IP address to the allow list.

  • zscaler-whitelist-url

    Adds the specified URLs to the allow list.

import demistomock as demisto
import CommonServerPython
from CommonServerPython import urljoin
import pytest
import json
import requests_mock


class ResponseMock:
    def __init__(self, response):
        self._json = response
        self.content = json.dumps(response)
        self.status_code = 200

    def json(self):
        return self._json


class ObjectMocker(dict):
    __getattr__ = dict.__getitem__
    __setattr__ = dict.__setitem__
    __delattr__ = dict.__delitem__


def run_command_test(command_func, args, response_path, expected_result_path, mocker, result_validator=None, resp_type="json"):
    with open(response_path) as response_f:
        response = ResponseMock(json.load(response_f))
    match resp_type:
        case "json":
            response = response.json()
        case "content":
            response = response.content

    mocker.patch("Zscaler.http_request", return_value=response)
    if command_func.__name__ in [
        "url_lookup",
        "get_users_command",
        "set_user_command",
        "get_departments_command",
        "get_usergroups_command",
        "list_ip_destination_groups",
        "create_ip_destination_group",
        "edit_ip_destination_group",
        "delete_ip_destination_groups",
    ]:
        res = command_func(args)
    else:
        res = command_func(**args)
    if result_validator:
        assert result_validator(res)
    else:
        with open(expected_result_path) as ex_f:
            expected_result = json.load(ex_f)
            if isinstance(res, CommonServerPython.CommandResults):
                assert expected_result == res.to_context()
            else:
                assert expected_result == res


@pytest.fixture(autouse=True)
def init_tests(mocker):
    params = {"cloud": "http://cloud", "credentials": {"identifier": "security", "password": "ninja"}, "key": "api"}
    mocker.patch.object(demisto, "params", return_value=params)


def test_validate_urls_invalid(mocker):
    return_error_mock = mocker.patch.object(CommonServerPython, "return_error")
    import Zscaler

    invalid_urls = ["http://not_very_valid", "https://maybe_valid.", "www.valid_url.com"]
    Zscaler.validate_urls(invalid_urls)
    assert return_error_mock.call_count == 2


def test_url_command(mocker):
    """url"""
    import Zscaler

    def validator(res):
        assert res
        assert len(res) == 2
        for command_res in res:
            assert command_res.indicator.url
            assert command_res.indicator.dbot_score
            assert command_res.outputs["urlClassifications"]
            assert command_res.outputs_prefix == "Zscaler.URL"

        return True

    run_command_test(
        command_func=Zscaler.url_lookup,
        args={"url": "https://www.demisto-news.com,https://www.demisto-search.com"},
        response_path="test_data/responses/url.json",
        expected_result_path="test_data/results/url.json",
        mocker=mocker,
        result_validator=validator,
        resp_type="content",
    )


def test_url_fails_unknown_error_code(mocker, requests_mock):
    """url"""
    import Zscaler

    Zscaler.BASE_URL = "http://cloud/api/v1"

    requests_mock.post(urljoin(Zscaler.BASE_URL, "urlLookup"), status_code=501)
    args = {"url": "https://www.demisto-news.com,https://www.demisto-search.com"}

    try:
        Zscaler.url_lookup(args)
    except Exception as ex:
        assert "following error: 501" in str(ex)


def test_url_command_with_urlClassificationsWithSecurityAlert(mocker):
    """url"""
    import Zscaler

    def validator(res):
        assert res
        assert len(res) == 1
        assert res[0].outputs["urlClassifications"] == "MISCELLANEOUS_OR_UNKNOWN"
        assert res[0].outputs["urlClassificationsWithSecurityAlert"] == "MALWARE_SITE"
        return True

    run_command_test(
        command_func=Zscaler.url_lookup,
        args={"url": "www.demisto22.com"},
        response_path="test_data/responses/url_with_urlClassificationsWithSecurityAlert.json",
        expected_result_path="test_data/results/url_with_urlClassificationsWithSecurityAlert.json",
        mocker=mocker,
        result_validator=validator,
        resp_type="content",
    )


def test_ip_command(mocker):
    """ip"""
    import Zscaler

    def validator(res):
        assert res
        assert len(res) == 2
        for command_res in res:
            assert command_res.indicator.ip
            assert command_res.indicator.dbot_score
            assert command_res.outputs["ipClassifications"]
            assert not command_res.outputs.get("urlClassifications")
            assert command_res.outputs_prefix == "Zscaler.IP"
        return True

    run_command_test(
        command_func=Zscaler.ip_lookup,
        args={"ip": "1.22.33.4"},
        response_path="test_data/responses/ip.json",
        expected_result_path="test_data/results/ip.json",
        mocker=mocker,
        result_validator=validator,
        resp_type="content",
    )


def test_domain_lookup_single(mocker):
    """
    Given:
        - A single domain to lookup
    When:
        - domain_lookup is called
    Then:
        - Verify the function returns a CommandResults object
        - Verify outputs_prefix is 'Zscaler.Domain'
        - Verify Domain.Name context path is set correctly
        - Verify DBotScore is created with correct type (DOMAIN)
        - Verify domainClassifications is properly set
    """
    import Zscaler
    from unittest.mock import patch

    mock_response = json.dumps(
        [
            {
                "url": "example.com",
                "urlClassifications": ["BUSINESS"],
                "urlClassificationsWithSecurityAlert": [],
            }
        ]
    )

    with patch.object(Zscaler, "lookup_request", return_value=mock_response):
        results = Zscaler.domain_lookup({"domain": "https://example.com"})

    assert results
    assert len(results) == 1
    assert isinstance(results[0], CommonServerPython.CommandResults)

    # Verify outputs_prefix
    assert results[0].outputs_prefix == "Zscaler.Domain"

    # Verify domain
    assert results[0].indicator.domain == "example.com"

    # Verify context outputs
    assert results[0].outputs["Name"] == "example.com"
    assert results[0].outputs["domainClassifications"] == "BUSINESS"

    # Verify DBotScore
    assert results[0].indicator.dbot_score.indicator == "example.com"
    assert results[0].indicator.dbot_score.indicator_type == CommonServerPython.DBotScoreType.DOMAIN
    assert results[0].indicator.dbot_score.score == CommonServerPython.Common.DBotScore.GOOD


def test_domain_lookup_multiple(mocker):
    """
    Given:
        - Multiple domains to lookup (comma-separated)
    When:
        - domain_lookup is called
    Then:
        - Verify the function returns multiple CommandResults objects
        - Verify each domain is processed correctly
    """
    import Zscaler
    from unittest.mock import patch

    mock_response = json.dumps(
        [
            {
                "url": "example.com",
                "urlClassifications": ["BUSINESS"],
                "urlClassificationsWithSecurityAlert": [],
            },
            {"url": "test.com", "urlClassifications": ["TECHNOLOGY"], "urlClassificationsWithSecurityAlert": []},
        ]
    )

    with patch.object(Zscaler, "lookup_request", return_value=mock_response):
        results = Zscaler.domain_lookup({"domain": "example.com,test.com"})

    assert results
    assert len(results) == 2

    # Verify first domain
    assert results[0].indicator.domain == "example.com"
    assert results[0].outputs["domainClassifications"] == "BUSINESS"

    # Verify second domain
    assert results[1].indicator.domain == "test.com"
    assert results[1].outputs["domainClassifications"] == "TECHNOLOGY"


def test_domain_lookup_with_security_alert(mocker):
    """
    Given:
        - A domain with security alert classifications
    When:
        - domain_lookup is called
    Then:
        - Verify domainClassificationsWithSecurityAlert is properly set
        - Verify DBotScore is BAD for malicious domains
        - Verify DBotScore is SUSPICIOUS for suspicious categories
    """
    import Zscaler
    from unittest.mock import patch

    # Test with malicious domain
    mock_response_malicious = json.dumps(
        [
            {
                "url": "malicious.com",
                "urlClassifications": ["MISCELLANEOUS_OR_UNKNOWN"],
                "urlClassificationsWithSecurityAlert": ["MALWARE_SITE"],
            }
        ]
    )

    with patch.object(Zscaler, "lookup_request", return_value=mock_response_malicious):
        results = Zscaler.domain_lookup({"domain": "malicious.com"})

    assert results
    assert len(results) == 1
    assert results[0].outputs["domainClassificationsWithSecurityAlert"] == "MALWARE_SITE"
    assert results[0].indicator.dbot_score.score == CommonServerPython.Common.DBotScore.BAD
    assert results[0].indicator.dbot_score.malicious_description == "MALWARE_SITE"

    # Test with suspicious domain
    mock_response_suspicious = json.dumps(
        [
            {
                "url": "suspicious.com",
                "urlClassifications": [],
                "urlClassificationsWithSecurityAlert": ["SUSPICIOUS_DESTINATION"],
            }
        ]
    )

    with patch.object(Zscaler, "lookup_request", return_value=mock_response_suspicious):
        results = Zscaler.domain_lookup({"domain": "suspicious.com"})

    assert results
    assert len(results) == 1
    assert results[0].outputs["domainClassificationsWithSecurityAlert"] == "SUSPICIOUS_DESTINATION"
    assert results[0].indicator.dbot_score.score == CommonServerPython.Common.DBotScore.SUSPICIOUS


def test_domain_lookup_miscellaneous_classification(mocker):
    """
    Given:
        - A domain with MISCELLANEOUS_OR_UNKNOWN classification and no security alerts
    When:
        - domain_lookup is called
    Then:
        - Verify DBotScore is NONE for miscellaneous domains without security alerts
    """
    import Zscaler
    from unittest.mock import patch

    mock_response = json.dumps(
        [
            {
                "url": "unknown.com",
                "urlClassifications": ["MISCELLANEOUS_OR_UNKNOWN"],
                "urlClassificationsWithSecurityAlert": [],
            }
        ]
    )

    with patch.object(Zscaler, "lookup_request", return_value=mock_response):
        results = Zscaler.domain_lookup({"domain": "unknown.com"})

    assert results
    assert len(results) == 1
    assert results[0].outputs["domainClassifications"] == "MISCELLANEOUS_OR_UNKNOWN"
    assert results[0].indicator.dbot_score.score == CommonServerPython.Common.DBotScore.NONE


def test_domain_lookup_pipe_extraction(mocker):
    """
    Given:
        - API response with domain
    When:
        - domain_lookup is called
    Then:
        - Verify the domain is used in all context outputs
    """
    import Zscaler
    from unittest.mock import patch

    mock_response = json.dumps(
        [
            {
                "url": "google.com",
                "urlClassifications": ["SEARCH_ENGINES"],
                "urlClassificationsWithSecurityAlert": [],
            }
        ]
    )

    with patch.object(Zscaler, "lookup_request", return_value=mock_response):
        results = Zscaler.domain_lookup({"domain": "google.com"})

    assert results
    assert len(results) == 1

    # Verify domain
    assert results[0].indicator.domain == "google.com"
    assert results[0].outputs["Name"] == "google.com"

    # Verify the raw response has the domain field (not url)
    assert "domain" in results[0].raw_response
    assert results[0].raw_response["domain"] == "google.com"


def test_domain_lookup_empty_classifications(mocker):
    """
    Given:
        - A domain with empty classifications arrays
    When:
        - domain_lookup is called
    Then:
        - Verify DBotScore is GOOD for domains with no classifications
    """
    import Zscaler
    from unittest.mock import patch

    mock_response = json.dumps([{"url": "clean.com", "urlClassifications": [], "urlClassificationsWithSecurityAlert": []}])

    with patch.object(Zscaler, "lookup_request", return_value=mock_response):
        results = Zscaler.domain_lookup({"domain": "clean.com"})

    assert results
    assert len(results) == 1
    assert results[0].indicator.dbot_score.score == CommonServerPython.Common.DBotScore.GOOD


def test_undo_blacklist_url_command(mocker):
    """zscaler-undo-blacklist-url"""
    import Zscaler

    run_command_test(
        command_func=Zscaler.unblacklist_url,
        args={"url": "www.demisto22.com, www.demisto33.com"},
        response_path="test_data/responses/blacklist_urls.json",
        expected_result_path="test_data/results/undo_blacklist_urls.txt",
        mocker=mocker,
        resp_type="content",
    )


def test_blacklist_url_command(mocker):
    """zscaler-blacklist-url"""
    import Zscaler

    run_command_test(
        command_func=Zscaler.blacklist_url,
        args={"url": "www.demisto22.com, www.demisto33.com"},
        response_path="test_data/responses/blacklist_urls.json",
        expected_result_path="test_data/results/blacklist_urls.txt",
        mocker=mocker,
    )


def test_category_remove_url(mocker):
    """zscaler-category-remove-url"""
    import Zscaler

    run_command_test(
        command_func=Zscaler.category_remove,
        args={
            "data": "demisto.com, dbot.com,www.demisto22.com",
            "category_id": "CUSTOM_1",
            "retaining_parent_category_data": None,
            "data_type": "url",
        },
        response_path="test_data/responses/categories.json",
        expected_result_path="test_data/results/remove_url.json",
        mocker=mocker,
    )


def test_category_remove_ip(mocker):
    """zscaler-category-remove-ip"""
    import Zscaler

    run_command_test(
        command_func=Zscaler.category_remove,
        args={"data": "1.2.3.4,8.8.8.8", "category_id": "CUSTOM_1", "retaining_parent_category_data": None, "data_type": "ip"},
        response_path="test_data/responses/categories2.json",
        expected_result_path="test_data/results/remove_ip.json",
        mocker=mocker,
    )


def test_get_categories(mocker):
    # zscaler-get-categories
    import Zscaler

    run_command_test(
        command_func=Zscaler.get_categories_command,
        args={"args": {"displayURL": "true"}},
        response_path="test_data/responses/categories2.json",
        expected_result_path="test_data/results/get_categories.json",
        mocker=mocker,
    )


def test_get_categories_custom_only(mocker):
    # zscaler-get-categories
    import Zscaler

    run_command_test(
        command_func=Zscaler.get_categories_command,
        args={"args": {"displayURL": "true", "custom_only": True}},
        response_path="test_data/responses/categories2.json",
        expected_result_path="test_data/results/get_categories.json",
        mocker=mocker,
    )


@pytest.mark.parametrize("display_url, get_ids_and_names_only", [(False, True), (True, True)])
def test_get_categories_ids_and_names_only(mocker, display_url, get_ids_and_names_only):
    # zscaler-get-categories retrieve only categories IDs and names (without urls)
    import Zscaler

    run_command_test(
        command_func=Zscaler.get_categories_command,
        args={"args": {"displayURL": display_url, "get_ids_and_names_only": get_ids_and_names_only}},
        response_path="test_data/responses/categories2_no_urls.json",
        expected_result_path="test_data/results/get_categories_no_urls.json",
        mocker=mocker,
    )


def test_url_quota_command(mocker):
    # zscaler-url-quota
    import Zscaler

    run_command_test(
        command_func=Zscaler.url_quota_command,
        args={},
        response_path="test_data/responses/url_quota.json",
        expected_result_path="test_data/results/url_quota.json",
        mocker=mocker,
    )


def test_get_blacklist(mocker):
    # zscaler-get-blacklist
    import Zscaler

    run_command_test(
        command_func=Zscaler.get_blacklist,
        args={},
        response_path="test_data/responses/blacklist_urls.json",
        expected_result_path="test_data/results/blacklist.json",
        mocker=mocker,
        resp_type="content",
    )


def test_get_blacklist_filter(requests_mock):
    """
    Given:
        - The `filter` arg set to `url`
        - API response with a URL and IP

    When:
        - Running the get-blacklist command

    Then:
        - Ensure only the URL is returned
    """
    import Zscaler

    api_res = {
        "blacklistUrls": [
            "demisto.com",
            "8.8.8.8",
        ],
    }
    requests_mock.get(
        "http://cloud/api/v1/security/advanced",
        json=api_res,
    )
    args = {
        "filter": "url",
    }
    cmd_res = Zscaler.get_blacklist_command(args)
    assert cmd_res["Contents"] == [api_res["blacklistUrls"][0]]


def test_get_blacklist_query(requests_mock):
    """
    Given:
        - The `query` arg set to `demisto`
        - API response with a URL and IP

    When:
        - Running the get-blacklist command

    Then:
        - Ensure only the URL (which contains `demisto`) is returned
    """
    import Zscaler

    api_res = {
        "blacklistUrls": [
            "demisto.com",
            "8.8.8.8",
        ],
    }
    requests_mock.get(
        "http://cloud/api/v1/security/advanced",
        json=api_res,
    )
    args = {
        "query": "demisto",
    }
    cmd_res = Zscaler.get_blacklist_command(args)
    assert cmd_res["Contents"] == [api_res["blacklistUrls"][0]]


def test_get_blacklist_query_and_filter(requests_mock):
    """
    Given:
        - The `filter` arg set to `ip`
        - The `query` arg set to `8.8.*.8`
        - API response with a URL and IP

    When:
        - Running the get-blacklist command

    Then:
        - Ensure only the IP is returned
    """
    import Zscaler

    api_res = {
        "blacklistUrls": [
            "demisto.com",
            "8.8.8.8",
        ],
    }
    requests_mock.get(
        "http://cloud/api/v1/security/advanced",
        json=api_res,
    )
    args = {
        "filter": "ip",
        "query": "8.8.*.8",
    }
    cmd_res = Zscaler.get_blacklist_command(args)
    assert cmd_res["Contents"] == [api_res["blacklistUrls"][1]]


def test_get_whitelist(mocker):
    # zscaler-get-whitelist
    import Zscaler

    run_command_test(
        command_func=Zscaler.get_whitelist,
        args={},
        response_path="test_data/responses/whitelist_url.json",
        expected_result_path="test_data/results/whitelist.json",
        mocker=mocker,
        resp_type="content",
    )


# disable-secrets-detection-start
test_data = [
    ("https://madeup.fake.com/css?family=blah:1,2,3", "true", ["madeup.fake.com/css?family=blah:1", "2", "3"]),
    ("https://madeup.fake.com/css?family=blah:1,2,3", "false", ["madeup.fake.com/css?family=blah:1,2,3"]),
]


# disable-secrets-detection-end


@pytest.mark.parametrize("url,multiple,expected_data", test_data)
def test_url_multiple_arg(url, multiple, expected_data):
    """Scenario: Submit a URL with commas in it

    Given
    - A URL with commas in it
    When
    - case A: 'multiple' argument is set to "true" (the default)
    - case B: 'multiple' argument is set to "false"
    Then
    - case A: Ensure the URL is interpreted as multiple values to be sent in the subsequent API call
    - case B: Ensure the URL is interpreted as a single value to be sent in the subsequent API call

    Args:
        url (str): The URL to submit.
        multiple (str): "true" or "false" - whether to interpret the 'url' argument as multiple comma separated values.
        expected_data (list): The data expected to be sent in the API call.
    """
    import Zscaler

    with requests_mock.mock() as m:
        # 'fake_resp_content' doesn't really matter here since we are checking the data being sent in the call,
        # not what it is that we expect to get in response
        fake_resp_content = b'[{"url": "blah", "urlClassifications": [], "urlClassificationsWithSecurityAlert": []}]'
        m.post(Zscaler.BASE_URL + "/urlLookup", content=fake_resp_content)
        args = {"url": url, "multiple": multiple}
        Zscaler.url_lookup(args)
    assert m.called
    assert m.call_count == 1
    request_data = m.last_request.json()
    assert len(request_data) == len(expected_data)
    assert request_data == expected_data


def test_login__active_session(mocker):
    """
    Scenario: test login with an active session

    Given:
     - User has authorization to login
     - There is an active session
    When:
     - login is called
    Then:
     - No login request is done
     - Result is as expected
    """
    import Zscaler

    mock_id = "mock_id"
    mocker.patch.object(demisto, "debug")
    mocker.patch.object(Zscaler, "get_integration_context", return_value={Zscaler.SESSION_ID_KEY: mock_id})
    mocker.patch.object(Zscaler, "test_module")
    Zscaler.login()

    assert Zscaler.DEFAULT_HEADERS["cookie"] == mock_id


def test_login__no_active_session(mocker):
    """
    Scenario: test login when there is no active session

    Given:
     - User has authorization to login
     - There is an active session
    When:
     - User wishes to login using login command
    Then:
     - Result is as expected
    """
    import Zscaler

    mock_header = "JSESSIONID=MOCK_ID; Path=/; Secure; HttpOnly"
    Zscaler.API_KEY = "Lcb38EvjtZVc"
    mocker.patch.object(demisto, "debug")
    mocker.patch.object(Zscaler, "get_integration_context", return_value={})
    mocker.patch.object(Zscaler, "test_module")
    mocker.patch.object(Zscaler, "http_request", return_value=ObjectMocker({"headers": {"Set-Cookie": mock_header}}))
    Zscaler.login()

    assert Zscaler.DEFAULT_HEADERS["cookie"] == "JSESSIONID=MOCK_ID"


def test_login_command(mocker):
    """
    Scenario: test successful login command

    Given:
     - User provided valid credentials
     - Integration context is empty
    When:
     - zscaler-login command is called
    Then:
     - Ensure logout is not called
     - Ensure login is called
     - Ensure readable output is as expected
    """
    import Zscaler

    mocker.patch.object(demisto, "debug")
    mocker.patch.object(Zscaler, "get_integration_context", return_value={})
    logout_mock = mocker.patch.object(Zscaler, "logout")
    login_mock = mocker.patch.object(Zscaler, "login")
    raw_res = Zscaler.login_command()

    assert not logout_mock.called
    assert login_mock.called
    assert raw_res.readable_output == "Zscaler session created successfully."


def test_login_command__load_from_context(mocker):
    """
    Scenario: test successful login command attempt with load from the context

    Given:
     - User has provided valid credentials
     - Integration context has a previous session
    When:
     - zscaler-login command is called
    Then:
     - Ensure logout is called
     - Ensure login is called
     - Ensure readable output is as expected
    """
    import Zscaler

    mocker.patch.object(demisto, "debug")
    mocker.patch.object(Zscaler, "get_integration_context", return_value={Zscaler.SESSION_ID_KEY: "test_key"})
    logout_mock = mocker.patch.object(Zscaler, "logout")
    login_mock = mocker.patch.object(Zscaler, "login")
    raw_res = Zscaler.login_command()

    assert logout_mock.called
    assert login_mock.called
    assert raw_res.readable_output == "Zscaler session created successfully."


def test_logout_command__no_context(mocker):
    """
    Scenario: logout when there's no active session

    Given:
     - There is no active session
    When:
     - logout command is performed
    Then:
     - Return readable output detailing no action was performed
    """
    import Zscaler

    mocker.patch.object(Zscaler, "get_integration_context", return_value={})
    raw_res = Zscaler.logout_command()
    assert raw_res.readable_output == "No API session was found. No action was performed."


def test_logout_command__happy_context(mocker):
    """
    Scenario: logout when there's an active session

    Given:
     - There is an active session
    When:
     - logout command is performed
    Then:
     - Return readable output detailing logout was performed
    """
    import Zscaler

    mocker.patch.object(Zscaler, "get_integration_context", return_value={Zscaler.SESSION_ID_KEY: "test_key"})
    mocker.patch.object(Zscaler, "logout", return_value=ResponseMock({}))
    raw_res = Zscaler.logout_command()
    assert raw_res.readable_output == "API session logged out of Zscaler successfully."


def test_logout_command__context_expired(mocker):
    """
    Scenario: fail to logout with AuthorizationError when there's an active session

    Given:
     - There is an active session
    When:
     - logout command is performed
     - logout action fails with AuthorizationError
    Then:
     - Return readable output detailing no logout was done
    """
    import Zscaler

    mocker.patch.object(Zscaler, "get_integration_context", return_value={Zscaler.SESSION_ID_KEY: "test_key"})
    mocker.patch.object(Zscaler, "logout", side_effect=Zscaler.AuthorizationError(""))
    raw_res = Zscaler.logout_command()
    assert raw_res.readable_output == "API session is not authenticated. No action was performed."


def test_get_users_command(mocker):
    """zscaler-get-users"""
    import Zscaler

    run_command_test(
        command_func=Zscaler.get_users_command,
        args={"pageSize": "100"},
        response_path="test_data/responses/get_users.json",
        expected_result_path="test_data/results/get_users.json",
        mocker=mocker,
    )


def test_set_user_command(mocker):
    """zscaler-update-user"""
    import Zscaler

    user_json = """{
    "department": {"id": "12","name": "user test"},
    "email": "user@test.com",
    "groups": [{"id": 13,"name": "name_test"}],
    "id": 11,
    "name": "name.test.com"
    }"""

    run_command_test(
        command_func=Zscaler.set_user_command,
        args={"id": "11", "user": user_json},
        response_path="test_data/responses/set_user.json",
        expected_result_path="test_data/results/set_user.json",
        mocker=mocker,
        resp_type="response",
    )


def test_get_departments_command(mocker):
    """zscaler-get-departments"""
    import Zscaler

    run_command_test(
        command_func=Zscaler.get_departments_command,
        args={"pageSize": "1"},
        response_path="test_data/responses/get_departments.json",
        expected_result_path="test_data/results/get_departments.json",
        mocker=mocker,
    )


def test_get_usergroups_command(mocker):
    """zscaler-get-usergroups"""
    import Zscaler

    run_command_test(
        command_func=Zscaler.get_usergroups_command,
        args={"pageSize": "100"},
        response_path="test_data/responses/get_usergroups.json",
        expected_result_path="test_data/results/get_usergroups.json",
        mocker=mocker,
    )


def test_list_ip_destination_groups__command_no_argument(mocker):
    """zscaler-list-ip-destination-groups"""
    import Zscaler

    run_command_test(
        command_func=Zscaler.list_ip_destination_groups,
        args={},
        response_path="test_data/responses/" + "list_ip_destination_groups.json",
        expected_result_path="test_data/results/" + "list_ip_destination_groups.json",
        mocker=mocker,
    )


def test_list_ip_destination_groups__command_with_id_argument(mocker):
    """zscaler-list-ip-destination-groups"""
    import Zscaler

    run_command_test(
        command_func=Zscaler.list_ip_destination_groups,
        args={"ip_group_id": "1964949"},
        response_path="test_data/responses/" + "list_ip_destination_groups_with_id.json",
        expected_result_path="test_data/results/" + "list_ip_destination_groups_with" + "_id.json",
        mocker=mocker,
    )


def test_list_ip_destination_groups__command_with_exclude_argument(mocker):
    """zscaler-list-ip-destination-groups"""
    import Zscaler

    run_command_test(
        command_func=Zscaler.list_ip_destination_groups,
        args={"exclude_type": "DSTN_OTHER"},
        response_path="test_data/responses/" + "list_ip_destination_groups_with_exclude" + ".json",
        expected_result_path="test_data/results/" + "list_ip_destination_groups_with" + "_exclude.json",
        mocker=mocker,
    )


def test_list_ip_destination_groups_command_with_lite_argument(mocker):
    """zscaler-list-ip-destination-groups-lite"""
    import Zscaler

    run_command_test(
        command_func=Zscaler.list_ip_destination_groups,
        args={"lite": "True"},
        response_path="test_data/responses/list_ip_destination_groups_lite.json",
        expected_result_path="test_data/results/list_ip_destination_groups_lite.json",
        mocker=mocker,
    )


def test_create_ip_destination_group(mocker):
    """zscaler-create-ip-destination-group"""
    import Zscaler

    run_command_test(
        command_func=Zscaler.create_ip_destination_group,
        args={"name": "Test99", "type": "DSTN_IP", "addresses": ["127.0.0.2", "127.0.0.1"], "description": "Localhost"},
        response_path="test_data/responses/" + "create_ip_destination_group.json",
        expected_result_path="test_data/results/" + "create_ip_destination_group.json",
        mocker=mocker,
    )


def test_edit_ip_destination_group(mocker):
    """zscaler-edit-ip-destination-group"""
    import Zscaler

    run_command_test(
        command_func=Zscaler.edit_ip_destination_group,
        args={"ip_group_id": "2000359", "name": "Test01", "addresses": ["127.0.0.2"], "description": "Localhost v2"},
        response_path="test_data/responses/" + "edit_ip_destination_group.json",
        expected_result_path="test_data/results/" + "edit_ip_destination_group.json",
        mocker=mocker,
    )


def test_delete_ip_destination_groups(mocker):
    """zscaler-delete-ip-destination-group"""
    import Zscaler

    run_command_test(
        command_func=Zscaler.delete_ip_destination_groups,
        args={"ip_group_id": "1964949"},
        response_path="test_data/responses/delete_ip_destination_group.json",
        expected_result_path="test_data/results/delete_ip_destination_group.json",
        mocker=mocker,
    )


def test_category_add_url(mocker):
    """
    Given:
        - A category ID, URL, and retaining parent category URL
    When:
        - category_add_url is called
    Then:
        - The URL should be added to the category
    """
    from Zscaler import category_add

    mocker.patch("Zscaler.get_category_by_id", return_value={"urls": []})
    mocker.patch("Zscaler.argToList", side_effect=[["test1.com"], ["test2.com"]])
    mocker.patch("Zscaler.add_or_remove_urls_from_category", return_value=None)

    result = category_add("1", "test1.com", "test2.com", "url")

    assert result["HumanReadable"].startswith("Added the following URL, retaining-parent-category-url addresses to category 1")


def test_category_add_url_with_category_name(mocker):
    """
    Given:
        - A category ID, URL, retaining parent category URL, and category name
    When:
        - category_add_url is called with category_name argument
    Then:
        - The URL should be added to the category with the category name passed through
    """
    from Zscaler import category_add

    mocker.patch("Zscaler.get_category_by_id", return_value={"urls": [], "customCategory": True})
    mocker.patch("Zscaler.argToList", side_effect=[["test1.com"], ["test2.com"]])
    mock_add_or_remove = mocker.patch("Zscaler.add_or_remove_urls_from_category", return_value=None)

    result = category_add("CUSTOM_01", "test1.com", "test2.com", "url", "My-Custom-Category")

    assert result["HumanReadable"].startswith(
        "Added the following URL, retaining-parent-category-url addresses to category CUSTOM_01"
    )
    # Verify category_name was passed to add_or_remove_urls_from_category
    mock_add_or_remove.assert_called_once()
    call_args = mock_add_or_remove.call_args
    assert call_args[0][4] == "My-Custom-Category"  # category_name is the 5th positional argument


def test_category_add_ip(mocker):
    """
    Given:
        - A category ID, IP address, and retaining parent category IP
    When:
        - category_add_ip is called
    Then:
        - The IP address should be added to the category
    """
    from Zscaler import category_add

    mocker.patch("Zscaler.get_categories", return_value=[{"id": 1, "urls": [], "customCategory": "true"}])
    mocker.patch("Zscaler.add_or_remove_urls_from_category", return_value={})
    result = category_add(1, "1.1.1.1", "1.1.1.1", "ip")
    assert result["HumanReadable"].startswith("Added the following IP, retaining-parent-category-ip addresses to category 1")


def test_add_or_remove_urls_from_category_with_configured_name(mocker, requests_mock):
    """
    Given:
        - A custom category with configuredName and superCategory in category_data
    When:
        - add_or_remove_urls_from_category is called
    Then:
        - Both configuredName and superCategory should be included in the request body
    """
    import Zscaler

    Zscaler.BASE_URL = "http://cloud/api/v1"

    category_data = {
        "id": "CUSTOM_01",
        "customCategory": True,
        "urls": [],
        "configuredName": "My-Custom-Category",
        "superCategory": "USER_DEFINED",
    }

    requests_mock.put("http://cloud/api/v1/urlCategories/CUSTOM_01?action=ADD_TO_LIST", json={})

    Zscaler.add_or_remove_urls_from_category(Zscaler.ADD, ["test.com"], category_data)

    assert requests_mock.called
    request_body = requests_mock.last_request.json()
    assert request_body.get("configuredName") == "My-Custom-Category"
    assert request_body.get("superCategory") == "USER_DEFINED"


def test_add_or_remove_urls_from_category_with_category_name_override(mocker, requests_mock):
    """
    Given:
        - A custom category with configuredName and superCategory in category_data
        - A category_name argument provided
    When:
        - add_or_remove_urls_from_category is called
    Then:
        - The provided category_name should override the one from category_data
        - The superCategory should still be included
    """
    import Zscaler

    Zscaler.BASE_URL = "http://cloud/api/v1"

    category_data = {
        "id": "CUSTOM_01",
        "customCategory": True,
        "urls": [],
        "configuredName": "Old-Name",
        "superCategory": "NEWS_AND_MEDIA",
    }

    requests_mock.put("http://cloud/api/v1/urlCategories/CUSTOM_01?action=ADD_TO_LIST", json={})

    Zscaler.add_or_remove_urls_from_category(Zscaler.ADD, ["test.com"], category_data, category_name="New-Name")

    assert requests_mock.called
    request_body = requests_mock.last_request.json()
    assert request_body.get("configuredName") == "New-Name"
    assert request_body.get("superCategory") == "NEWS_AND_MEDIA"


def test_add_or_remove_urls_from_category_custom_without_name_raises_error(mocker):
    """
    Given:
        - A custom category without configuredName in category_data
        - No category_name argument provided
    When:
        - add_or_remove_urls_from_category is called
    Then:
        - A DemistoException should be raised
    """
    import Zscaler
    from CommonServerPython import DemistoException

    category_data = {"id": "CUSTOM_01", "customCategory": True, "urls": []}

    with pytest.raises(DemistoException) as exc_info:
        Zscaler.add_or_remove_urls_from_category(Zscaler.ADD, ["test.com"], category_data)

    assert "configuredName" in str(exc_info.value)
    assert "category-name" in str(exc_info.value)


def test_get_category_by_id_injects_category_name(mocker):
    """
    Given:
        - A category without configuredName
        - A category_name argument provided
    When:
        - get_category_by_id is called
    Then:
        - The category_name should be injected into the returned category data
    """
    import Zscaler

    mocker.patch("Zscaler.get_categories", return_value=[{"id": "CUSTOM_01", "customCategory": True, "urls": []}])

    result = Zscaler.get_category_by_id("CUSTOM_01", "My-Custom-Category")

    assert result is not None
    assert result.get("configuredName") == "My-Custom-Category"


def test_edit_ip_destination_group_override_false(mocker):
    """
    Given:
        - An existing IP destination group ID and override='False'
    When:
        - edit_ip_destination_group is called
    Then:
        - The PUT request URL contains 'override=false'
    """
    import Zscaler

    http_mock = mocker.patch(
        "Zscaler.http_request",
        return_value={
            "id": "2000359",
            "name": "Test01",
            "type": "DSTN_IP",
            "addresses": ["127.0.0.2"],
            "description": "Localhost v2",
            "ipCategories": [],
            "countries": [],
        },
    )

    Zscaler.edit_ip_destination_group(
        {
            "ip_group_id": "2000359",
            "name": "Test01",
            "addresses": ["127.0.0.2"],
            "description": "Localhost v2",
            "override": "False",
        }
    )

    # First call is the GET to fetch existing group, second is the PUT with override param
    put_call = http_mock.call_args_list[1]
    assert "override=false" in put_call.args[1]


def test_edit_ip_destination_group_override_true_default(mocker):
    """
    Given:
        - An existing IP destination group ID with no override argument provided
    When:
        - edit_ip_destination_group is called
    Then:
        - The PUT request URL does not contains 'override' (the default)
    """
    import Zscaler

    http_mock = mocker.patch(
        "Zscaler.http_request",
        return_value={
            "id": "2000359",
            "name": "Test01",
            "type": "DSTN_IP",
            "addresses": ["127.0.0.2"],
            "description": "Localhost v2",
            "ipCategories": [],
            "countries": [],
        },
    )

    Zscaler.edit_ip_destination_group(
        {"ip_group_id": "2000359", "name": "Test01", "addresses": ["127.0.0.2"], "description": "Localhost v2"}
    )

    put_call = http_mock.call_args_list[1]
    assert "override" not in put_call.args[1]


def test_return_error_is_called_on_error(mocker, requests_mock):
    """
    Given:
        - Any command run
    When:
        - Calling login() which fails on 429
    Then:
        - Ensure an error entry is returned
    """
    import sys
    import importlib
    import Zscaler as original_zscaler

    Zscaler = importlib.reload(original_zscaler)
    mock_id = "mock_id"
    mocker.patch.object(Zscaler, "get_integration_context", return_value={Zscaler.SESSION_ID_KEY: mock_id})
    requests_mock.get("http://cloud/api/v1/status", status_code=429)
    mocker.patch.object(demisto, "results")
    mocker.patch.object(sys, "exit")
    Zscaler.main()
    error_results = demisto.results.call_args_list[0][0]
    assert "Exceeded the rate limit or quota" in error_results[0].get("Contents")