diff --git a/LICENSE b/LICENSE index fe5e893..d0f6848 100644 --- a/LICENSE +++ b/LICENSE @@ -186,7 +186,7 @@ same "printed page" as the copyright notice for easier identification within third-party archives. - Copyright (c) 2017-2023 Splunk Inc. + Copyright (c) 2017-2024 Splunk Inc. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/README.md b/README.md index e954575..f97b6b8 100644 --- a/README.md +++ b/README.md @@ -2,16 +2,16 @@ # Zscaler Publisher: Splunk -Connector Version: 2.4.0 +Connector Version: 3.0.0 Product Vendor: Zscaler Product Name: Zscaler Product Version Supported (regex): ".\*" -Minimum Product Version: 6.0.0 +Minimum Product Version: 6.2.2 This app implements containment and investigative actions on Zscaler [comment]: # " File: README.md" -[comment]: # " Copyright (c) 2017-2023 Splunk Inc." +[comment]: # " Copyright (c) 2017-2024 Splunk Inc." [comment]: # "" [comment]: # "Licensed under the Apache License, Version 2.0 (the 'License');" [comment]: # "you may not use this file except in compliance with the License." @@ -120,6 +120,19 @@ VARIABLE | REQUIRED | TYPE | DESCRIPTION [get groups](#action-get-groups) - Gets a list of groups [add group user](#action-add-group-user) - Add user to group [remove group user](#action-remove-group-user) - Remove user from group +[get allowlist](#action-get-allowlist) - Get urls on the allow list +[get denylist](#action-get-denylist) - Get urls on the deny list +[update user](#action-update-user) - Update user with given id +[add category url](#action-add-category-url) - Add urls to a cetgory +[add category ip](#action-add-category-ip) - Add IPs to a cetgory +[remove category url](#action-remove-category-url) - Add urls to a cetgory +[remove category ip](#action-remove-category-ip) - Remove IPs to a cetgory +[create destination group](#action-create-destination-group) - Create destination group +[list destination group](#action-list-destination-group) - List destination group +[edit destination group](#action-edit-destination-group) - Edit destination group +[delete destination group](#action-delete-destination-group) - Delete destination group +[get departments](#action-get-departments) - Get a list of departments +[get category details](#action-get-category-details) - Get the urls and keywords of a category ## action: 'test connectivity' Validate the asset configuration for connectivity using supplied configuration @@ -185,12 +198,15 @@ Type: **investigate** Read only: **True** #### Action Parameters -No parameters are required for this action +PARAMETER | REQUIRED | DESCRIPTION | TYPE | CONTAINS +--------- | -------- | ----------- | ---- | -------- +**get_ids_and_names_only** | optional | Whether to retrieve only a list containing URL category IDs and names. Even if displayURL is set to true, URLs will not be returned | boolean | #### Action Output DATA PATH | TYPE | CONTAINS | EXAMPLE VALUES --------- | ---- | -------- | -------------- action_result.status | string | | test success test failed +action_result.parameter.get_ids_and_names_only | string | | RADIO_STATIONS action_result.data.\*.configuredName | string | | test Test-Caution action_result.data.\*.customCategory | boolean | | True False action_result.data.\*.customIpRangesCount | numeric | | 0 @@ -745,4 +761,465 @@ action_result.summary.message | string | | test User removed from group action_result.message | string | | test User removed from group summary.message | string | | summary.total_objects | numeric | | 1 +summary.total_objects_successful | numeric | | 1 + +## action: 'get allowlist' +Get urls on the allow list + +Type: **investigate** +Read only: **True** + +#### Action Parameters +No parameters are required for this action + +#### Action Output +DATA PATH | TYPE | CONTAINS | EXAMPLE VALUES +--------- | ---- | -------- | -------------- +action_result.status | string | | test success test failed +action_result.data.\*.url | string | | +action_result.summary.total_allowlist_items | numeric | | 10 +action_result.summary.message | string | | Allowlist retrieved +action_result.message | string | | Allowlist retrieved +summary.total_objects | numeric | | 1 +summary.total_objects_successful | numeric | | 1 + +## action: 'get denylist' +Get urls on the deny list + +Type: **investigate** +Read only: **True** + +#### Action Parameters +PARAMETER | REQUIRED | DESCRIPTION | TYPE | CONTAINS +--------- | -------- | ----------- | ---- | -------- +**filter** | optional | Filter results be url or ip | string | +**query** | optional | Regular expression to match url or ip against | string | + +#### Action Output +DATA PATH | TYPE | CONTAINS | EXAMPLE VALUES +--------- | ---- | -------- | -------------- +action_result.status | string | | test success test failed +action_result.parameter.query | string | | 8...8 +action_result.parameter.filter | string | | +action_result.data.\*.url | string | | +action_result.summary.message | string | | Blacklist retrieved +action_result.message | string | | Denylist retrieved +action_result.summary.total_denylist_items | numeric | | 10 +summary.total_objects | numeric | | 1 +summary.total_objects_successful | numeric | | 1 + +## action: 'update user' +Update user with given id + +Type: **correct** +Read only: **False** + +#### Action Parameters +PARAMETER | REQUIRED | DESCRIPTION | TYPE | CONTAINS +--------- | -------- | ----------- | ---- | -------- +**user_id** | required | ZScaler User Id | numeric | `zscaler user id` +**user** | optional | JSON object containing the user details (see https://help.zscaler.com/zia/user-management#/users/{userId}-put) | string | + +#### Action Output +DATA PATH | TYPE | CONTAINS | EXAMPLE VALUES +--------- | ---- | -------- | -------------- +action_result.status | string | | test success test failed +action_result.parameter.user | string | | +action_result.parameter.user_id | numeric | `zscaler user id` | 889814 +action_result.data.\*.adminUser | boolean | | True False +action_result.data.\*.comments | string | | test This is test user +action_result.data.\*.deleted | boolean | | True False +action_result.data.\*.department.id | numeric | | 81896690 +action_result.data.\*.department.name | string | | test IT +action_result.data.\*.email | string | `email` | test first.last@domain.com +action_result.data.\*.groups.\*.id | numeric | `zscaler group id` | 8894813 +action_result.data.\*.groups.\*.name | string | | test Super Admin +action_result.data.\*.id | numeric | `zscaler user id` | 889814 +action_result.data.\*.name | string | | test First Last +action_result.summary | string | | +action_result.summary.message | string | | test User removed from group +action_result.message | string | | test User removed from group +summary.message | string | | +summary.total_objects | numeric | | 1 +summary.total_objects_successful | numeric | | 1 + +## action: 'add category url' +Add urls to a cetgory + +Type: **generic** +Read only: **False** + +#### Action Parameters +PARAMETER | REQUIRED | DESCRIPTION | TYPE | CONTAINS +--------- | -------- | ----------- | ---- | -------- +**category_id** | required | The ID of the category to add the specified URLs to | string | +**urls** | optional | A comma-separated list of URLs to add to the specified category | string | +**retaining-parent-category-url** | optional | A comma-separated list of URLs to add to the retaining parent category section inside the specified category | string | + +#### Action Output +DATA PATH | TYPE | CONTAINS | EXAMPLE VALUES +--------- | ---- | -------- | -------------- +action_result.status | string | | success failed +action_result.parameter.category_id | string | | RADIO_STATIONS +action_result.parameter.urls | string | | +action_result.parameter.retaining-parent-category-url | string | | +action_result.data.\*.id | string | | +action_result.data.\*.val | numeric | | +action_result.data.\*.type | string | | +action_result.data.\*.urls | string | | +action_result.data.\*.scopes.\*.Type | string | | +action_result.data.\*.editable | boolean | | +action_result.data.\*.keywords | string | | +action_result.data.\*.description | string | | +action_result.data.\*.configuredName | string | | +action_result.data.\*.customCategory | boolean | | +action_result.data.\*.customUrlsCount | numeric | | +action_result.data.\*.dbCategorizedUrls | string | | +action_result.data.\*.customIpRangesCount | numeric | | +action_result.data.\*.keywordsRetainingParentCategory | string | | +action_result.data.\*.urlsRetainingParentCategoryCount | numeric | | +action_result.data.\*.ipRangesRetainingParentCategoryCount | numeric | | +action_result.message | string | | Message: Category urs updated +action_result.summary | string | | +summary.total_objects | numeric | | 1 +summary.total_objects_successful | numeric | | 1 + +## action: 'add category ip' +Add IPs to a cetgory + +Type: **generic** +Read only: **False** + +#### Action Parameters +PARAMETER | REQUIRED | DESCRIPTION | TYPE | CONTAINS +--------- | -------- | ----------- | ---- | -------- +**category_id** | required | The ID of the category to add the specified URLs to | string | +**ips** | optional | A comma-separated list of IP addresses to add to the specified category | string | +**retaining-parent-category-ip** | optional | A comma-separated list of IPs to add to the retaining parent category section inside the specified category | string | + +#### Action Output +DATA PATH | TYPE | CONTAINS | EXAMPLE VALUES +--------- | ---- | -------- | -------------- +action_result.status | string | | success failed +action_result.parameter.category_id | string | | RADIO_STATIONS +action_result.parameter.ips | string | | +action_result.parameter.retaining-parent-category-ip | string | | +action_result.data.\*.id | string | | +action_result.data.\*.val | numeric | | +action_result.data.\*.type | string | | +action_result.data.\*.urls | string | | +action_result.data.\*.scopes.\*.Type | string | | +action_result.data.\*.editable | boolean | | +action_result.data.\*.keywords | string | | +action_result.data.\*.description | string | | +action_result.data.\*.configuredName | string | | +action_result.data.\*.customCategory | boolean | | +action_result.data.\*.customUrlsCount | numeric | | +action_result.data.\*.dbCategorizedUrls | string | | +action_result.data.\*.customIpRangesCount | numeric | | +action_result.data.\*.keywordsRetainingParentCategory | string | | +action_result.data.\*.urlsRetainingParentCategoryCount | numeric | | +action_result.data.\*.ipRangesRetainingParentCategoryCount | numeric | | +action_result.message | string | | Message: Category ips updated +action_result.summary | string | | +summary.total_objects | numeric | | 1 +summary.total_objects_successful | numeric | | 1 + +## action: 'remove category url' +Add urls to a cetgory + +Type: **generic** +Read only: **False** + +#### Action Parameters +PARAMETER | REQUIRED | DESCRIPTION | TYPE | CONTAINS +--------- | -------- | ----------- | ---- | -------- +**category_id** | required | The ID of the category to add the specified URLs to | string | +**urls** | optional | A comma-separated list of URLs to remove from the specified category | string | +**retaining-parent-category-url** | optional | A comma-separated list of URLs to remove from the retaining parent category section inside the specified category | string | + +#### Action Output +DATA PATH | TYPE | CONTAINS | EXAMPLE VALUES +--------- | ---- | -------- | -------------- +action_result.status | string | | success failed +action_result.parameter.category_id | string | | RADIO_STATIONS +action_result.parameter.urls | string | | +action_result.parameter.retaining-parent-category-url | string | | +action_result.data.\*.id | string | | +action_result.data.\*.val | numeric | | +action_result.data.\*.type | string | | +action_result.data.\*.urls | string | | +action_result.data.\*.scopes.\*.Type | string | | +action_result.data.\*.editable | boolean | | +action_result.data.\*.keywords | string | | +action_result.data.\*.description | string | | +action_result.data.\*.configuredName | string | | +action_result.data.\*.customCategory | boolean | | +action_result.data.\*.customUrlsCount | numeric | | +action_result.data.\*.dbCategorizedUrls | string | | +action_result.data.\*.customIpRangesCount | numeric | | +action_result.data.\*.keywordsRetainingParentCategory | string | | +action_result.data.\*.urlsRetainingParentCategoryCount | numeric | | +action_result.data.\*.ipRangesRetainingParentCategoryCount | numeric | | +action_result.message | string | | Message: Category urls removed +action_result.summary | string | | +summary.total_objects | numeric | | 1 +summary.total_objects_successful | numeric | | 1 + +## action: 'remove category ip' +Remove IPs to a cetgory + +Type: **generic** +Read only: **False** + +#### Action Parameters +PARAMETER | REQUIRED | DESCRIPTION | TYPE | CONTAINS +--------- | -------- | ----------- | ---- | -------- +**category_id** | required | The ID of the category to add the specified URLs to | string | +**ips** | optional | A comma-separated list of IP addresses to add to the specified category | string | +**retaining-parent-category-ip** | optional | A comma-separated list of IPs to add to the retaining parent category section inside the specified category | string | + +#### Action Output +DATA PATH | TYPE | CONTAINS | EXAMPLE VALUES +--------- | ---- | -------- | -------------- +action_result.status | string | | success failed +action_result.parameter.category_id | string | | RADIO_STATIONS +action_result.parameter.ips | string | | +action_result.parameter.retaining-parent-category-ip | string | | +action_result.data.\*.id | string | | +action_result.data.\*.val | numeric | | +action_result.data.\*.type | string | | +action_result.data.\*.urls | string | | +action_result.data.\*.scopes.\*.Type | string | | +action_result.data.\*.editable | boolean | | +action_result.data.\*.keywords | string | | +action_result.data.\*.description | string | | +action_result.data.\*.configuredName | string | | +action_result.data.\*.customCategory | boolean | | +action_result.data.\*.customUrlsCount | numeric | | +action_result.data.\*.dbCategorizedUrls | string | | +action_result.data.\*.customIpRangesCount | numeric | | +action_result.data.\*.keywordsRetainingParentCategory | string | | +action_result.data.\*.urlsRetainingParentCategoryCount | numeric | | +action_result.data.\*.ipRangesRetainingParentCategoryCount | numeric | | +action_result.message | string | | Message: Category ips removed +action_result.summary | string | | +summary.total_objects | numeric | | 1 +summary.total_objects_successful | numeric | | 1 + +## action: 'create destination group' +Create destination group + +Type: **generic** +Read only: **False** + +#### Action Parameters +PARAMETER | REQUIRED | DESCRIPTION | TYPE | CONTAINS +--------- | -------- | ----------- | ---- | -------- +**name** | required | Destination IP group name | string | +**type** | required | Destination IP group type (i.e., the group can contain destination IP addresses, countries, URL categories or FQDNs) | string | +**addresses** | optional | Comma seperated string of destination IP addresses, FQDNs, or wildcard FQDNs added to the group | string | +**description** | optional | Additional information about the destination IP group. | string | +**ip_categories** | optional | Destination IP address URL categories | string | +**countries** | optional | Destination IP address countries. You can identify destinations based on the location of a server. | string | + +#### Action Output +DATA PATH | TYPE | CONTAINS | EXAMPLE VALUES +--------- | ---- | -------- | -------------- +action_result.status | string | | test success test failed +action_result.parameter.countries | string | | +action_result.parameter.ip_categories | string | | +action_result.parameter.description | string | | +action_result.parameter.addresses | string | | +action_result.parameter.type | string | | +action_result.parameter.name | string | | +action_result.data.\*.id | numeric | | +action_result.data.\*.name | string | | +action_result.data.\*.type | string | | DSTN_IP DSTN_FQDN DSTN_DOMAIN DSTN_OTHER +action_result.data.\*.addresses | string | | 192.168.1.1 +action_result.data.\*.countries | string | | +action_result.data.\*.description | string | | +action_result.data.\*.ipCategories | string | | TRADING_BROKARAGE_INSURANCE +action_result.data.\*.isNonEditable | boolean | | True False +action_result.data.\*.creatorContext | string | | +action_result.summary | string | | +action_result.summary.message | string | | test User removed from group +action_result.message | string | | test User removed from group +summary.message | string | | +summary.total_objects | numeric | | 1 +summary.total_objects_successful | numeric | | 1 + +## action: 'list destination group' +List destination group + +Type: **investigate** +Read only: **False** + +#### Action Parameters +PARAMETER | REQUIRED | DESCRIPTION | TYPE | CONTAINS +--------- | -------- | ----------- | ---- | -------- +**ip_group_ids** | optional | A comma-separated list of unique identifiers for the IP destination groups | string | +**exclude_type** | optional | The IP group type to be excluded from the results | string | +**category_type** | optional | Comma seperated list of IP group types to be filtered from results. This argument is only supported when the 'lite' argument is set to True | string | +**limit** | optional | Limit of the results to be retrieved | numeric | +**lite** | optional | Whether to retrieve only limited information of IP destination groups. Includes ID, name and type of the IP destination groups | boolean | + +#### Action Output +DATA PATH | TYPE | CONTAINS | EXAMPLE VALUES +--------- | ---- | -------- | -------------- +action_result.status | string | | test success test failed +action_result.parameter.lite | boolean | | +action_result.parameter.limit | numeric | | +action_result.parameter.category_type | string | | +action_result.parameter.exclude_type | string | | +action_result.parameter.ip_group_ids | string | | +action_result.data.\*.id | numeric | | +action_result.data.\*.name | string | | +action_result.data.\*.type | string | | DSTN_IP DSTN_FQDN DSTN_DOMAIN DSTN_OTHER +action_result.data.\*.addresses | string | | 192.168.1.1 +action_result.data.\*.countries | string | | +action_result.data.\*.description | string | | +action_result.data.\*.ipCategories | string | | TRADING_BROKARAGE_INSURANCE +action_result.data.\*.isNonEditable | boolean | | True False +action_result.data.\*.creatorContext | string | | +action_result.summary | string | | +action_result.summary.message | string | | Retreived Destination Groups +action_result.message | string | | Retreived Destination Groups +summary.message | string | | +summary.total_objects | numeric | | 1 +summary.total_objects_successful | numeric | | 1 + +## action: 'edit destination group' +Edit destination group + +Type: **generic** +Read only: **False** + +#### Action Parameters +PARAMETER | REQUIRED | DESCRIPTION | TYPE | CONTAINS +--------- | -------- | ----------- | ---- | -------- +**ip_group_id** | required | The unique identifier for the IP destination group | numeric | +**name** | optional | Destination IP group name | string | +**addresses** | optional | Comma seperated string of destination IP addresses, FQDNs, or wildcard FQDNs added to the group | string | +**description** | optional | Additional information about the destination IP group. | string | +**ip_categories** | optional | Destination IP address URL categories | string | +**countries** | optional | Destination IP address countries. You can identify destinations based on the location of a server. | string | +**is_non_editable** | optional | 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 | boolean | + +#### Action Output +DATA PATH | TYPE | CONTAINS | EXAMPLE VALUES +--------- | ---- | -------- | -------------- +action_result.status | string | | test success test failed +action_result.parameter.is_non_editable | boolean | | +action_result.parameter.countries | string | | +action_result.parameter.ip_categories | string | | +action_result.parameter.description | string | | +action_result.parameter.addresses | string | | +action_result.parameter.name | string | | +action_result.parameter.ip_group_id | numeric | | +action_result.data.\*.id | numeric | | +action_result.data.\*.name | string | | +action_result.data.\*.type | string | | DSTN_IP DSTN_FQDN DSTN_DOMAIN DSTN_OTHER +action_result.data.\*.addresses | string | | 192.168.1.1 +action_result.data.\*.countries | string | | +action_result.data.\*.description | string | | +action_result.data.\*.ipCategories | string | | TRADING_BROKARAGE_INSURANCE +action_result.data.\*.isNonEditable | boolean | | True False +action_result.data.\*.creatorContext | string | | +action_result.summary | string | | +action_result.summary.message | string | | Destination group edited +action_result.message | string | | Destination group edited +summary.message | string | | +summary.total_objects | numeric | | 1 +summary.total_objects_successful | numeric | | 1 + +## action: 'delete destination group' +Delete destination group + +Type: **generic** +Read only: **False** + +#### Action Parameters +PARAMETER | REQUIRED | DESCRIPTION | TYPE | CONTAINS +--------- | -------- | ----------- | ---- | -------- +**ip_group_ids** | optional | A comma-separated list of unique identifiers for the IP destination groups | string | + +#### Action Output +DATA PATH | TYPE | CONTAINS | EXAMPLE VALUES +--------- | ---- | -------- | -------------- +action_result.status | string | | test success test failed +action_result.parameter.ip_group_ids | string | | +action_result.data.\*.ip_group_ids | string | | +action_result.summary | string | | +action_result.summary.message | string | | Destination group deleted +action_result.message | string | | Destination group deleted +summary.message | string | | +summary.total_objects | numeric | | 1 +summary.total_objects_successful | numeric | | 1 + +## action: 'get departments' +Get a list of departments + +Type: **investigate** +Read only: **True** + +#### Action Parameters +PARAMETER | REQUIRED | DESCRIPTION | TYPE | CONTAINS +--------- | -------- | ----------- | ---- | -------- +**name** | optional | Filter by department name | string | +**page** | optional | Specifies the page offset | numeric | +**pageSize** | optional | Specifies the page size | numeric | + +#### Action Output +DATA PATH | TYPE | CONTAINS | EXAMPLE VALUES +--------- | ---- | -------- | -------------- +action_result.status | string | | test success test failed +action_result.parameter.pageSize | string | | +action_result.parameter.page | string | | +action_result.parameter.name | string | | +action_result.data.\*.id | numeric | | +action_result.data.\*.name | string | | +action_result.data.\*.isNonEditable | boolean | | +action_result.summary | string | | +action_result.summary.message | string | | Departments Retrieved +action_result.summary.total_deparments | numeric | | 97 +action_result.message | string | | Departments Retrieved +summary.message | string | | +summary.total_objects | numeric | | 1 +summary.total_objects_successful | numeric | | 1 + +## action: 'get category details' +Get the urls and keywords of a category + +Type: **investigate** +Read only: **True** + +#### Action Parameters +PARAMETER | REQUIRED | DESCRIPTION | TYPE | CONTAINS +--------- | -------- | ----------- | ---- | -------- +**category_ids** | optional | Comma seperated string of category id's to query | string | + +#### Action Output +DATA PATH | TYPE | CONTAINS | EXAMPLE VALUES +--------- | ---- | -------- | -------------- +action_result.status | string | | test success test failed +action_result.parameter.category_ids | string | | CUSTOM_001, CUSTOM_002 +action_result.data.\*.configuredName | string | | test Test-Caution +action_result.data.\*.customCategory | boolean | | True False +action_result.data.\*.keywords | string | | +action_result.data.\*.urls | string | | +action_result.data.\*.customIpRangesCount | numeric | | 0 +action_result.data.\*.customUrlsCount | numeric | | 0 +action_result.data.\*.dbCategorizedUrls | string | | test 6.5.3.2.4 +action_result.data.\*.description | string | | test OTHER_RESTRICTED_WEBSITE_DESC +action_result.data.\*.editable | boolean | | True False +action_result.data.\*.id | string | `zscaler url category` | test OTHER_RESTRICTED_WEBSITE +action_result.data.\*.ipRangesRetainingParentCategoryCount | numeric | | 0 +action_result.data.\*.scopes.\*.Type | string | | test ORGANIZATION +action_result.data.\*.type | string | | test URL_CATEGORY +action_result.data.\*.urlsRetainingParentCategoryCount | numeric | | 0 +action_result.data.\*.val | numeric | | 1 +action_result.summary.total_categories | numeric | | 97 +action_result.message | string | | Category details recieved +summary.total_objects | numeric | | 1 summary.total_objects_successful | numeric | | 1 \ No newline at end of file diff --git a/__init__.py b/__init__.py index ed269c4..04fd24e 100644 --- a/__init__.py +++ b/__init__.py @@ -1,6 +1,6 @@ # File: __init__.py # -# Copyright (c) 2017-2023 Splunk Inc. +# Copyright (c) 2017-2024 Splunk Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/manual_readme_content.md b/manual_readme_content.md new file mode 100644 index 0000000..26220a8 --- /dev/null +++ b/manual_readme_content.md @@ -0,0 +1,76 @@ +[comment]: # " File: README.md" +[comment]: # " Copyright (c) 2017-2024 Splunk Inc." +[comment]: # "" +[comment]: # "Licensed under the Apache License, Version 2.0 (the 'License');" +[comment]: # "you may not use this file except in compliance with the License." +[comment]: # "You may obtain a copy of the License at" +[comment]: # "" +[comment]: # " http://www.apache.org/licenses/LICENSE-2.0" +[comment]: # "" +[comment]: # "Unless required by applicable law or agreed to in writing, software distributed under" +[comment]: # "the License is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND," +[comment]: # "either express or implied. See the License for the specific language governing permissions" +[comment]: # "and limitations under the License." +[comment]: # "" +Below points are considered for providing the **URL Category** parameter value. + +- Entire URL category string has to be mentioned in block letters + +- The most child category on UI has to be passed as the URL category parameter value to the action + +- From the URL category value on UI, every space has to be replaced by an underscore '\_' before + passing it in the action's parameter value + + + + - For example, **Alternate Lifestyle** on UI becomes **ALTERNATE_LIFESTYLE** + +- When you specify a **url_category** , you can give it either the name you created or the ID + which is assigned to it from Zscaler. The search will first search for the name, as opposed to + the ID. So if you create a category **phantom-block** , you could use either **phantom-block** + or **CUSTOM\_\*\*** . The name for these is case sensitive. + +The following are considered for providing the **URL** parameter value. + +- The comma-separated values of **URL** should correctly be given e.g. test.com,test1.com else the + Phantom framework's parameter validator will return the error mentioning **Exception occurred: + string index out of range** . + +Configure and set up permissions for the **lookup_url** action + +- Login to Zscaler UI using the Administrator credentials. +- Once logged in, go to **Administration -> Role Management** section. +- Click on the **Edit** icon beside the role that your account uses to configure the test + connectivity. +- Go to the **Functional Scope** section, enable **Security** if disabled, and save it. + +The above steps would help run the Lookup URL action as expected. + +The Sandbox Submission API requires a separate API key and uses a different host +(csbapi.\[zscaler-cloud-name\]). For the **submit_file** action, the **sandbox_base_url** and +**sandbox_api_token** asset configuration parameters should be configured. These two asset +parameters won't affect test_connectivity. Follow the below steps to fetch these credentials for the +**submit_file** action + +- Log in to the ZIA Admin Portal using your **admin** credentials. +- Once logged in, go to **Administration -> Cloud Service API Key Management** section. In order + to view the Cloud Service API Key Management page, the admin must be assigned an admin role. +- For the Cloud Sandbox Submission API used in this action, the base URL and token are displayed + on the **Sandbox Submission API Token** tab. +- The base URL and token displayed here can be configured in the asset parameters in + **sandbox_base_url** and **sandbox_api_token** parameters respectively and will be used for the + submit_file action. + +The above steps would help run the Submit File action as expected. + +**NOTE:** This action would work according to the API behavior + +Port Information + +The app uses HTTP/ HTTPS protocol for communicating with the Zscaler server. Below are the default +ports used by Splunk SOAR. + +|         Service Name | Transport Protocol | Port | +|----------------------|--------------------|------| +|         http | tcp | 80 | +|         https | tcp | 443 | diff --git a/readme.html b/readme.html deleted file mode 100644 index b00f74c..0000000 --- a/readme.html +++ /dev/null @@ -1,79 +0,0 @@ - - -

Below points are considered for providing the URL Category parameter value. -

-

- -

The following are considered for providing the URL parameter value. -

-

- -

Configure and set up permissions for the lookup_url action

-

-

- The above steps would help run the Lookup URL action as expected. -

- -

The Sandbox Submission API requires a separate API key and uses a different host (csbapi.[zscaler-cloud-name]). For the submit_file action, the sandbox_base_url and sandbox_api_token asset configuration parameters should be configured. These two asset parameters won't affect test_connectivity. Follow the below steps to fetch these credentials for the submit_file action

-

-

- The above steps would help run the Submit File action as expected. -

NOTE: This action would work according to the API behavior

-

- -

Port Information

-

- The app uses HTTP/ HTTPS protocol for communicating with the Zscaler server. Below are the default ports used by Splunk SOAR. - - - - - - - - - - - - - - - - -
        Service NameTransport ProtocolPort
        httptcp80
        httpstcp443
-

diff --git a/release_notes/3.0.0.md b/release_notes/3.0.0.md new file mode 100644 index 0000000..1dcf0a8 --- /dev/null +++ b/release_notes/3.0.0.md @@ -0,0 +1,14 @@ +* [PAPP-34457] + * New `get blacklist` action that retrieves the Zscaler default block list. + * New `get whitelist` action that retrieves the Zscaler default allow list. + * New `update user` action that updates the user information for the specified ID. + * New `add category url:` action that adds URLs to the specified category. + * New `add category IP:` action that adds IPs to the specified category. + * New `remove category url:` action that removes URLs from the specified category. + * New `remove category IP:` action that removes IPs from the specified category. + * New `get categories:` action that retrieves a list of all categories. + * New `create destination groups:` action that adds a new IP destination group. + * New `edit destination groups:` action that updates the IP destination group information for the specified group ID. + * New `list destination groups:` action that gets a list of all IP destination groups or the IP destination group information for the specified group ID. + * New `delete destination groups:` action that deletes the IP destination groups for the specified group IDs. + * New `get departments:` action that gets a list of departments. It can be searched by name. \ No newline at end of file diff --git a/zscaler.json b/zscaler.json index 5ed9741..27f6e64 100644 --- a/zscaler.json +++ b/zscaler.json @@ -9,13 +9,13 @@ "product_name": "Zscaler", "product_version_regex": ".*", "publisher": "Splunk", - "license": "Copyright (c) 2017-2023 Splunk Inc.", - "app_version": "2.4.0", + "license": "Copyright (c) 2017-2024 Splunk Inc.", + "app_version": "3.0.0", "utctime_updated": "2022-01-24T06:29:48.000000Z", "package_name": "phantom_zscaler", "main_module": "zscaler_connector.py", "python_version": "3", - "min_phantom_version": "6.0.0", + "min_phantom_version": "6.2.2", "fips_compliant": true, "latest_tested_versions": [ "Cloud v6.2, 29th June 2023" @@ -316,7 +316,19 @@ "description": "List all URL categories", "type": "investigate", "read_only": true, - "parameters": {}, + "parameters": { + "get_ids_and_names_only": { + "description": "Whether to retrieve only a list containing URL category IDs and names. Even if displayURL is set to true, URLs will not be returned", + "data_type": "boolean", + "order": 0, + "default": false, + "example_values": [ + true, + false + ], + "primary": true + } + }, "output": [ { "data_path": "action_result.status", @@ -326,6 +338,13 @@ "test failed" ] }, + { + "data_path": "action_result.parameter.get_ids_and_names_only", + "data_type": "string", + "example_values": [ + "RADIO_STATIONS" + ] + }, { "data_path": "action_result.data.*.configuredName", "data_type": "string", @@ -3000,6 +3019,2025 @@ "type": "table" }, "versions": "EQ(*)" + }, + { + "action": "get allowlist", + "identifier": "get_allowlist", + "description": "Get urls on the allow list", + "type": "investigate", + "read_only": true, + "parameters": {}, + "output": [ + { + "data_path": "action_result.status", + "data_type": "string", + "example_values": [ + "test success", + "test failed" + ] + }, + { + "data_path": "action_result.data.*.url", + "data_type": "string", + "column_name": "allowlist url", + "column_order": 0 + }, + { + "data_path": "action_result.summary.total_allowlist_items", + "data_type": "numeric", + "example_values": [ + 10 + ] + }, + { + "data_path": "action_result.summary.message", + "data_type": "string", + "example_values": [ + "Allowlist retrieved" + ] + }, + { + "data_path": "action_result.message", + "data_type": "string", + "example_values": [ + "Allowlist retrieved" + ] + }, + { + "data_path": "summary.total_objects", + "data_type": "numeric", + "example_values": [ + 1 + ] + }, + { + "data_path": "summary.total_objects_successful", + "data_type": "numeric", + "example_values": [ + 1 + ] + } + ], + "render": { + "title": "Allowlist", + "type": "table" + }, + "versions": "EQ(*)" + }, + { + "action": "get denylist", + "identifier": "get_denylist", + "description": "Get urls on the deny list", + "type": "investigate", + "read_only": true, + "parameters": { + "filter": { + "description": "Filter results be url or ip", + "data_type": "string", + "primary": true, + "value_list": [ + "url", + "ip" + ], + "order": 0 + }, + "query": { + "description": "Regular expression to match url or ip against", + "data_type": "string", + "primary": true, + "example_values": [ + "8...8" + ], + "order": 1 + } + }, + "output": [ + { + "data_path": "action_result.status", + "data_type": "string", + "example_values": [ + "test success", + "test failed" + ] + }, + { + "data_path": "action_result.parameter.query", + "data_type": "string", + "column_name": "Query", + "example_values": [ + "8...8" + ] + }, + { + "data_path": "action_result.parameter.filter", + "data_type": "string", + "column_name": "Filter", + "value_list": [ + "url", + "ip" + ] + }, + { + "data_path": "action_result.data.*.url", + "data_type": "string", + "column_name": "denylist url", + "column_order": 0 + }, + { + "data_path": "action_result.summary.message", + "data_type": "string", + "example_values": [ + "Blacklist retrieved" + ] + }, + { + "data_path": "action_result.message", + "data_type": "string", + "example_values": [ + "Denylist retrieved" + ] + }, + { + "data_path": "action_result.summary.total_denylist_items", + "data_type": "numeric", + "example_values": [ + 10 + ] + }, + { + "data_path": "summary.total_objects", + "data_type": "numeric", + "example_values": [ + 1 + ] + }, + { + "data_path": "summary.total_objects_successful", + "data_type": "numeric", + "example_values": [ + 1 + ] + } + ], + "render": { + "title": "Denylist", + "type": "table" + }, + "versions": "EQ(*)" + }, + { + "action": "update user", + "identifier": "update_user", + "description": "Update user with given id", + "type": "correct", + "read_only": false, + "parameters": { + "user_id": { + "description": "ZScaler User Id", + "data_type": "numeric", + "required": true, + "primary": true, + "contains": [ + "zscaler user id" + ], + "example_values": [ + 889814 + ], + "order": 0 + }, + "user": { + "description": "JSON object containing the user details (see https://help.zscaler.com/zia/user-management#/users/{userId}-put)", + "data_type": "string", + "primary": true, + "order": 1 + } + }, + "output": [ + { + "data_path": "action_result.status", + "data_type": "string", + "column_name": "Status", + "example_values": [ + "test success", + "test failed" + ] + }, + { + "data_path": "action_result.parameter.user", + "data_type": "string", + "column_name": "User" + }, + { + "data_path": "action_result.parameter.user_id", + "data_type": "numeric", + "contains": [ + "zscaler user id" + ], + "column_name": "User ID", + "example_values": [ + 889814 + ] + }, + { + "data_path": "action_result.data.*.adminUser", + "data_type": "boolean", + "example_values": [ + true, + false + ], + "column_name": "Is Admin User", + "column_order": 9 + }, + { + "data_path": "action_result.data.*.comments", + "data_type": "string", + "example_values": [ + "test This is test user" + ], + "column_name": "Comments", + "column_order": 8 + }, + { + "data_path": "action_result.data.*.deleted", + "data_type": "boolean", + "example_values": [ + true, + false + ], + "column_name": "Is Deleted", + "column_order": 7 + }, + { + "data_path": "action_result.data.*.department.id", + "data_type": "numeric", + "example_values": [ + 81896690 + ], + "column_name": "Department ID", + "column_order": 6 + }, + { + "data_path": "action_result.data.*.department.name", + "data_type": "string", + "example_values": [ + "test IT" + ], + "column_name": "Department name", + "column_order": 5 + }, + { + "data_path": "action_result.data.*.email", + "data_type": "string", + "contains": [ + "email" + ], + "example_values": [ + "test first.last@domain.com" + ], + "column_name": "User Email", + "column_order": 4 + }, + { + "data_path": "action_result.data.*.groups.*.id", + "data_type": "numeric", + "contains": [ + "zscaler group id" + ], + "example_values": [ + 8894813 + ], + "column_name": "Group ID", + "column_order": 3 + }, + { + "data_path": "action_result.data.*.groups.*.name", + "data_type": "string", + "example_values": [ + "test Super Admin" + ], + "column_name": "Group Name", + "column_order": 2 + }, + { + "data_path": "action_result.data.*.id", + "data_type": "numeric", + "contains": [ + "zscaler user id" + ], + "example_values": [ + 889814 + ], + "column_name": "User ID", + "column_order": 0 + }, + { + "data_path": "action_result.data.*.name", + "data_type": "string", + "example_values": [ + "test First Last" + ], + "column_name": "User Name", + "column_order": 1 + }, + { + "data_path": "action_result.summary", + "data_type": "string" + }, + { + "data_path": "action_result.summary.message", + "data_type": "string", + "example_values": [ + "test User removed from group" + ] + }, + { + "data_path": "action_result.message", + "data_type": "string", + "example_values": [ + "test User removed from group" + ] + }, + { + "data_path": "summary.message", + "data_type": "string" + }, + { + "data_path": "summary.total_objects", + "data_type": "numeric", + "example_values": [ + 1 + ] + }, + { + "data_path": "summary.total_objects_successful", + "data_type": "numeric", + "example_values": [ + 1 + ] + } + ], + "render": { + "type": "table" + }, + "versions": "EQ(*)" + }, + { + "action": "add category url", + "description": "Add urls to a cetgory", + "type": "generic", + "identifier": "add_category_url", + "read_only": false, + "parameters": { + "category_id": { + "description": "The ID of the category to add the specified URLs to", + "data_type": "string", + "order": 0, + "example_values": [ + "RADIO_STATIONS" + ], + "primary": true, + "required": true + }, + "urls": { + "description": "A comma-separated list of URLs to add to the specified category", + "data_type": "string", + "order": 1, + "primary": true + }, + "retaining-parent-category-url": { + "description": "A comma-separated list of URLs to add to the retaining parent category section inside the specified category", + "data_type": "string", + "primary": true, + "order": 2 + } + }, + "output": [ + { + "data_path": "action_result.status", + "data_type": "string", + "example_values": [ + "success", + "failed" + ] + }, + { + "data_path": "action_result.parameter.category_id", + "data_type": "string", + "example_values": [ + "RADIO_STATIONS" + ] + }, + { + "data_path": "action_result.parameter.urls", + "data_type": "string" + }, + { + "data_path": "action_result.parameter.retaining-parent-category-url", + "data_type": "string" + }, + { + "data_path": "action_result.data.*.id", + "data_type": "string", + "column_name": "URL category", + "column_order": 0 + }, + { + "data_path": "action_result.data.*.val", + "data_type": "numeric", + "column_name": "Value", + "column_order": 1 + }, + { + "data_path": "action_result.data.*.type", + "data_type": "string", + "column_name": "Category type", + "column_order": 2 + }, + { + "data_path": "action_result.data.*.urls", + "data_type": "string", + "column_name": "URLs added", + "column_order": 3 + }, + { + "data_path": "action_result.data.*.scopes.*.Type", + "data_type": "string", + "column_name": "Category scope", + "column_order": 4 + }, + { + "data_path": "action_result.data.*.editable", + "data_type": "boolean", + "column_name": "Is editable", + "column_order": 5 + }, + { + "data_path": "action_result.data.*.keywords", + "data_type": "string", + "column_name": "Category keywords", + "column_order": 6 + }, + { + "data_path": "action_result.data.*.description", + "data_type": "string", + "column_name": "Category description", + "column_order": 7 + }, + { + "data_path": "action_result.data.*.configuredName", + "data_type": "string", + "column_name": "Configured Name", + "column_order": 8 + }, + { + "data_path": "action_result.data.*.customCategory", + "data_type": "boolean", + "column_name": "Is Custom Category", + "column_order": 9 + }, + { + "data_path": "action_result.data.*.customUrlsCount", + "data_type": "numeric", + "column_name": "Custom url count", + "column_order": 10 + }, + { + "data_path": "action_result.data.*.dbCategorizedUrls", + "data_type": "string", + "column_name": "Parent urls", + "column_order": 11 + }, + { + "data_path": "action_result.data.*.customIpRangesCount", + "data_type": "numeric", + "column_name": "Custom ip ranges count", + "column_order": 12 + }, + { + "data_path": "action_result.data.*.keywordsRetainingParentCategory", + "data_type": "string", + "column_name": "Parent url keywords", + "column_order": 13 + }, + { + "data_path": "action_result.data.*.urlsRetainingParentCategoryCount", + "data_type": "numeric", + "column_name": "Number of parent urls", + "column_order": 14 + }, + { + "data_path": "action_result.data.*.ipRangesRetainingParentCategoryCount", + "data_type": "numeric", + "column_name": "Number of parent ips", + "column_order": 15 + }, + { + "data_path": "action_result.message", + "data_type": "string", + "example_values": [ + "Message: Category urs updated" + ] + }, + { + "data_path": "action_result.summary", + "data_type": "string" + }, + { + "data_path": "summary.total_objects", + "data_type": "numeric", + "example_values": [ + 1 + ] + }, + { + "data_path": "summary.total_objects_successful", + "data_type": "numeric", + "example_values": [ + 1 + ] + } + ], + "render": { + "type": "table" + }, + "versions": "EQ(*)" + }, + { + "action": "add category ip", + "description": "Add IPs to a cetgory", + "type": "generic", + "identifier": "add_category_ip", + "read_only": false, + "parameters": { + "category_id": { + "description": "The ID of the category to add the specified URLs to", + "data_type": "string", + "order": 0, + "example_values": [ + "RADIO_STATIONS" + ], + "primary": true, + "required": true + }, + "ips": { + "description": "A comma-separated list of IP addresses to add to the specified category", + "data_type": "string", + "order": 1, + "primary": true + }, + "retaining-parent-category-ip": { + "description": "A comma-separated list of IPs to add to the retaining parent category section inside the specified category", + "data_type": "string", + "primary": true, + "order": 2 + } + }, + "output": [ + { + "data_path": "action_result.status", + "data_type": "string", + "example_values": [ + "success", + "failed" + ] + }, + { + "data_path": "action_result.parameter.category_id", + "data_type": "string", + "example_values": [ + "RADIO_STATIONS" + ] + }, + { + "data_path": "action_result.parameter.ips", + "data_type": "string" + }, + { + "data_path": "action_result.parameter.retaining-parent-category-ip", + "data_type": "string" + }, + { + "data_path": "action_result.data.*.id", + "data_type": "string", + "column_name": "URL category", + "column_order": 0 + }, + { + "data_path": "action_result.data.*.val", + "data_type": "numeric", + "column_name": "Value", + "column_order": 1 + }, + { + "data_path": "action_result.data.*.type", + "data_type": "string", + "column_name": "Category type", + "column_order": 2 + }, + { + "data_path": "action_result.data.*.urls", + "data_type": "string", + "column_name": "URLs added", + "column_order": 3 + }, + { + "data_path": "action_result.data.*.scopes.*.Type", + "data_type": "string", + "column_name": "Category scope", + "column_order": 4 + }, + { + "data_path": "action_result.data.*.editable", + "data_type": "boolean", + "column_name": "Is editable", + "column_order": 5 + }, + { + "data_path": "action_result.data.*.keywords", + "data_type": "string", + "column_name": "Category keywords", + "column_order": 6 + }, + { + "data_path": "action_result.data.*.description", + "data_type": "string", + "column_name": "Category description", + "column_order": 7 + }, + { + "data_path": "action_result.data.*.configuredName", + "data_type": "string", + "column_name": "Configured Name", + "column_order": 8 + }, + { + "data_path": "action_result.data.*.customCategory", + "data_type": "boolean", + "column_name": "Is Custom Category", + "column_order": 9 + }, + { + "data_path": "action_result.data.*.customUrlsCount", + "data_type": "numeric", + "column_name": "Custom url count", + "column_order": 10 + }, + { + "data_path": "action_result.data.*.dbCategorizedUrls", + "data_type": "string", + "column_name": "Parent urls", + "column_order": 11 + }, + { + "data_path": "action_result.data.*.customIpRangesCount", + "data_type": "numeric", + "column_name": "Custom ip ranges count", + "column_order": 12 + }, + { + "data_path": "action_result.data.*.keywordsRetainingParentCategory", + "data_type": "string", + "column_name": "Parent url keywords", + "column_order": 13 + }, + { + "data_path": "action_result.data.*.urlsRetainingParentCategoryCount", + "data_type": "numeric", + "column_name": "Number of parent urls", + "column_order": 14 + }, + { + "data_path": "action_result.data.*.ipRangesRetainingParentCategoryCount", + "data_type": "numeric", + "column_name": "Number of parent ips", + "column_order": 15 + }, + { + "data_path": "action_result.message", + "data_type": "string", + "example_values": [ + "Message: Category ips updated" + ] + }, + { + "data_path": "action_result.summary", + "data_type": "string" + }, + { + "data_path": "summary.total_objects", + "data_type": "numeric", + "example_values": [ + 1 + ] + }, + { + "data_path": "summary.total_objects_successful", + "data_type": "numeric", + "example_values": [ + 1 + ] + } + ], + "render": { + "type": "table" + }, + "versions": "EQ(*)" + }, + { + "action": "remove category url", + "description": "Add urls to a cetgory", + "type": "generic", + "identifier": "remove_category_url", + "read_only": false, + "parameters": { + "category_id": { + "description": "The ID of the category to add the specified URLs to", + "data_type": "string", + "order": 0, + "example_values": [ + "RADIO_STATIONS" + ], + "primary": true, + "required": true + }, + "urls": { + "description": "A comma-separated list of URLs to remove from the specified category", + "data_type": "string", + "order": 1, + "primary": true + }, + "retaining-parent-category-url": { + "description": "A comma-separated list of URLs to remove from the retaining parent category section inside the specified category", + "data_type": "string", + "primary": true, + "order": 2 + } + }, + "output": [ + { + "data_path": "action_result.status", + "data_type": "string", + "example_values": [ + "success", + "failed" + ], + "column_order": 0, + "column_name": "Status" + }, + { + "data_path": "action_result.parameter.category_id", + "data_type": "string", + "example_values": [ + "RADIO_STATIONS" + ] + }, + { + "data_path": "action_result.parameter.urls", + "data_type": "string" + }, + { + "data_path": "action_result.parameter.retaining-parent-category-url", + "data_type": "string" + }, + { + "data_path": "action_result.data.*.id", + "data_type": "string" + }, + { + "data_path": "action_result.data.*.val", + "data_type": "numeric" + }, + { + "data_path": "action_result.data.*.type", + "data_type": "string" + }, + { + "data_path": "action_result.data.*.urls", + "data_type": "string" + }, + { + "data_path": "action_result.data.*.scopes.*.Type", + "data_type": "string" + }, + { + "data_path": "action_result.data.*.editable", + "data_type": "boolean" + }, + { + "data_path": "action_result.data.*.keywords", + "data_type": "string" + }, + { + "data_path": "action_result.data.*.description", + "data_type": "string" + }, + { + "data_path": "action_result.data.*.configuredName", + "data_type": "string" + }, + { + "data_path": "action_result.data.*.customCategory", + "data_type": "boolean" + }, + { + "data_path": "action_result.data.*.customUrlsCount", + "data_type": "numeric" + }, + { + "data_path": "action_result.data.*.dbCategorizedUrls", + "data_type": "string" + }, + { + "data_path": "action_result.data.*.customIpRangesCount", + "data_type": "numeric" + }, + { + "data_path": "action_result.data.*.keywordsRetainingParentCategory", + "data_type": "string" + }, + { + "data_path": "action_result.data.*.urlsRetainingParentCategoryCount", + "data_type": "numeric" + }, + { + "data_path": "action_result.data.*.ipRangesRetainingParentCategoryCount", + "data_type": "numeric" + }, + { + "data_path": "action_result.message", + "data_type": "string", + "example_values": [ + "Message: Category urls removed" + ] + }, + { + "data_path": "action_result.summary", + "data_type": "string" + }, + { + "data_path": "summary.total_objects", + "data_type": "numeric", + "example_values": [ + 1 + ] + }, + { + "data_path": "summary.total_objects_successful", + "data_type": "numeric", + "example_values": [ + 1 + ] + } + ], + "render": { + "type": "table" + }, + "versions": "EQ(*)" + }, + { + "action": "remove category ip", + "description": "Remove IPs to a cetgory", + "type": "generic", + "identifier": "remove_category_ip", + "read_only": false, + "parameters": { + "category_id": { + "description": "The ID of the category to add the specified URLs to", + "data_type": "string", + "order": 0, + "example_values": [ + "RADIO_STATIONS" + ], + "primary": true, + "required": true + }, + "ips": { + "description": "A comma-separated list of IP addresses to add to the specified category", + "data_type": "string", + "order": 1, + "primary": true + }, + "retaining-parent-category-ip": { + "description": "A comma-separated list of IPs to add to the retaining parent category section inside the specified category", + "data_type": "string", + "primary": true, + "order": 2 + } + }, + "output": [ + { + "data_path": "action_result.status", + "data_type": "string", + "example_values": [ + "success", + "failed" + ], + "column_order": 0, + "column_name": "Status" + }, + { + "data_path": "action_result.parameter.category_id", + "data_type": "string", + "example_values": [ + "RADIO_STATIONS" + ] + }, + { + "data_path": "action_result.parameter.ips", + "data_type": "string" + }, + { + "data_path": "action_result.parameter.retaining-parent-category-ip", + "data_type": "string" + }, + { + "data_path": "action_result.data.*.id", + "data_type": "string" + }, + { + "data_path": "action_result.data.*.val", + "data_type": "numeric" + }, + { + "data_path": "action_result.data.*.type", + "data_type": "string" + }, + { + "data_path": "action_result.data.*.urls", + "data_type": "string" + }, + { + "data_path": "action_result.data.*.scopes.*.Type", + "data_type": "string" + }, + { + "data_path": "action_result.data.*.editable", + "data_type": "boolean" + }, + { + "data_path": "action_result.data.*.keywords", + "data_type": "string" + }, + { + "data_path": "action_result.data.*.description", + "data_type": "string" + }, + { + "data_path": "action_result.data.*.configuredName", + "data_type": "string" + }, + { + "data_path": "action_result.data.*.customCategory", + "data_type": "boolean" + }, + { + "data_path": "action_result.data.*.customUrlsCount", + "data_type": "numeric" + }, + { + "data_path": "action_result.data.*.dbCategorizedUrls", + "data_type": "string" + }, + { + "data_path": "action_result.data.*.customIpRangesCount", + "data_type": "numeric" + }, + { + "data_path": "action_result.data.*.keywordsRetainingParentCategory", + "data_type": "string" + }, + { + "data_path": "action_result.data.*.urlsRetainingParentCategoryCount", + "data_type": "numeric" + }, + { + "data_path": "action_result.data.*.ipRangesRetainingParentCategoryCount", + "data_type": "numeric" + }, + { + "data_path": "action_result.message", + "data_type": "string", + "example_values": [ + "Message: Category ips removed" + ] + }, + { + "data_path": "action_result.summary", + "data_type": "string" + }, + { + "data_path": "summary.total_objects", + "data_type": "numeric", + "example_values": [ + 1 + ] + }, + { + "data_path": "summary.total_objects_successful", + "data_type": "numeric", + "example_values": [ + 1 + ] + } + ], + "render": { + "type": "table" + }, + "versions": "EQ(*)" + }, + { + "action": "create destination group", + "identifier": "create_destination_group", + "description": "Create destination group", + "type": "generic", + "read_only": false, + "parameters": { + "name": { + "description": "Destination IP group name", + "data_type": "string", + "required": true, + "primary": true, + "order": 0 + }, + "type": { + "description": "Destination IP group type (i.e., the group can contain destination IP addresses, countries, URL categories or FQDNs)", + "data_type": "string", + "required": true, + "primary": true, + "example_values": [ + "DSTN_IP", + "DSTN_FQDN", + "DSTN_DOMAIN", + "DSTN_OTHER" + ], + "order": 1 + }, + "addresses": { + "description": "Comma seperated string of destination IP addresses, FQDNs, or wildcard FQDNs added to the group", + "data_type": "string", + "order": 2 + }, + "description": { + "description": "Additional information about the destination IP group.", + "data_type": "string", + "order": 3 + }, + "ip_categories": { + "description": "Destination IP address URL categories", + "data_type": "string", + "order": 4 + }, + "countries": { + "description": "Destination IP address countries. You can identify destinations based on the location of a server.", + "data_type": "string", + "order": 5 + } + }, + "output": [ + { + "data_path": "action_result.status", + "data_type": "string", + "column_name": "Status", + "column_order": 2, + "example_values": [ + "test success", + "test failed" + ] + }, + { + "data_path": "action_result.parameter.countries", + "data_type": "string", + "column_name": "Countries" + }, + { + "data_path": "action_result.parameter.ip_categories", + "data_type": "string", + "column_name": "Ip Categories" + }, + { + "data_path": "action_result.parameter.description", + "data_type": "string", + "column_name": "Description" + }, + { + "data_path": "action_result.parameter.addresses", + "data_type": "string", + "column_name": "Addresses" + }, + { + "data_path": "action_result.parameter.type", + "data_type": "string", + "column_name": "Type" + }, + { + "data_path": "action_result.parameter.name", + "data_type": "string", + "column_name": "Name" + }, + { + "data_path": "action_result.data.*.id", + "data_type": "numeric", + "column_name": "Group ID", + "column_order": 0 + }, + { + "data_path": "action_result.data.*.name", + "data_type": "string", + "column_name": "Group name", + "column_order": 1 + }, + { + "data_path": "action_result.data.*.type", + "data_type": "string", + "example_values": [ + "DSTN_IP", + "DSTN_FQDN", + "DSTN_DOMAIN", + "DSTN_OTHER" + ], + "column_name": "Group type", + "column_order": 3 + }, + { + "data_path": "action_result.data.*.addresses", + "data_type": "string", + "example_values": [ + "192.168.1.1" + ], + "column_name": "Destination addresses", + "column_order": 4 + }, + { + "data_path": "action_result.data.*.countries", + "data_type": "string", + "column_name": "Destination countries", + "column_order": 5 + }, + { + "data_path": "action_result.data.*.description", + "data_type": "string", + "column_name": "Description", + "column_order": 6 + }, + { + "data_path": "action_result.data.*.ipCategories", + "data_type": "string", + "example_values": [ + "TRADING_BROKARAGE_INSURANCE" + ], + "column_name": "Destination categories", + "column_order": 7 + }, + { + "data_path": "action_result.data.*.isNonEditable", + "data_type": "boolean", + "example_values": [ + true, + false + ], + "column_name": "Is editable", + "column_order": 8 + }, + { + "data_path": "action_result.data.*.creatorContext", + "data_type": "string", + "column_name": "Creator Context", + "column_order": 9 + }, + { + "data_path": "action_result.summary", + "data_type": "string" + }, + { + "data_path": "action_result.summary.message", + "data_type": "string", + "example_values": [ + "test User removed from group" + ] + }, + { + "data_path": "action_result.message", + "data_type": "string", + "example_values": [ + "test User removed from group" + ] + }, + { + "data_path": "summary.message", + "data_type": "string" + }, + { + "data_path": "summary.total_objects", + "data_type": "numeric", + "example_values": [ + 1 + ] + }, + { + "data_path": "summary.total_objects_successful", + "data_type": "numeric", + "example_values": [ + 1 + ] + } + ], + "render": { + "type": "table" + }, + "versions": "EQ(*)" + }, + { + "action": "list destination group", + "identifier": "list_destination_group", + "description": "List destination group", + "type": "investigate", + "read_only": false, + "parameters": { + "ip_group_ids": { + "description": "A comma-separated list of unique identifiers for the IP destination groups", + "data_type": "string", + "primary": true, + "order": 0 + }, + "exclude_type": { + "description": "The IP group type to be excluded from the results", + "data_type": "string", + "primary": true, + "example_values": [ + "DSTN_IP", + "DSTN_FQDN", + "DSTN_DOMAIN", + "DSTN_OTHER" + ], + "order": 1 + }, + "category_type": { + "description": "Comma seperated list of IP group types to be filtered from results. This argument is only supported when the 'lite' argument is set to True", + "data_type": "string", + "example_values": [ + "DSTN_IP", + "DSTN_FQDN", + "DSTN_DOMAIN", + "DSTN_OTHER" + ], + "order": 2 + }, + "limit": { + "description": "Limit of the results to be retrieved", + "data_type": "numeric", + "default": 50, + "order": 3 + }, + "lite": { + "description": "Whether to retrieve only limited information of IP destination groups. Includes ID, name and type of the IP destination groups", + "data_type": "boolean", + "example_values": [ + true, + false + ], + "default": false, + "order": 4 + } + }, + "output": [ + { + "data_path": "action_result.status", + "data_type": "string", + "column_name": "Status", + "example_values": [ + "test success", + "test failed" + ] + }, + { + "data_path": "action_result.parameter.lite", + "data_type": "boolean", + "column_name": "Lite" + }, + { + "data_path": "action_result.parameter.limit", + "data_type": "numeric", + "column_name": "Limit" + }, + { + "data_path": "action_result.parameter.category_type", + "data_type": "string", + "column_name": "Category Type" + }, + { + "data_path": "action_result.parameter.exclude_type", + "data_type": "string", + "column_name": "Exclude Type" + }, + { + "data_path": "action_result.parameter.ip_group_ids", + "data_type": "string", + "column_name": "Ip Group ID" + }, + { + "data_path": "action_result.data.*.id", + "data_type": "numeric", + "column_name": "Group ID", + "column_order": 0 + }, + { + "data_path": "action_result.data.*.name", + "data_type": "string", + "column_name": "Group name", + "column_order": 1 + }, + { + "data_path": "action_result.data.*.type", + "data_type": "string", + "example_values": [ + "DSTN_IP", + "DSTN_FQDN", + "DSTN_DOMAIN", + "DSTN_OTHER" + ], + "column_name": "Group type", + "column_order": 2 + }, + { + "data_path": "action_result.data.*.addresses", + "data_type": "string", + "example_values": [ + "192.168.1.1" + ], + "column_name": "Destination addresses", + "column_order": 3 + }, + { + "data_path": "action_result.data.*.countries", + "data_type": "string", + "column_name": "Destination countries", + "column_order": 4 + }, + { + "data_path": "action_result.data.*.description", + "data_type": "string", + "column_name": "Description", + "column_order": 5 + }, + { + "data_path": "action_result.data.*.ipCategories", + "data_type": "string", + "example_values": [ + "TRADING_BROKARAGE_INSURANCE" + ], + "column_name": "Destination categories", + "column_order": 6 + }, + { + "data_path": "action_result.data.*.isNonEditable", + "data_type": "boolean", + "example_values": [ + true, + false + ], + "column_name": "Is editable", + "column_order": 7 + }, + { + "data_path": "action_result.data.*.creatorContext", + "data_type": "string", + "column_name": "Creator Context", + "column_order": 8 + }, + { + "data_path": "action_result.summary", + "data_type": "string" + }, + { + "data_path": "action_result.summary.message", + "data_type": "string", + "example_values": [ + "Retreived Destination Groups" + ] + }, + { + "data_path": "action_result.message", + "data_type": "string", + "example_values": [ + "Retreived Destination Groups" + ] + }, + { + "data_path": "summary.message", + "data_type": "string" + }, + { + "data_path": "summary.total_objects", + "data_type": "numeric", + "example_values": [ + 1 + ] + }, + { + "data_path": "summary.total_objects_successful", + "data_type": "numeric", + "example_values": [ + 1 + ] + } + ], + "render": { + "type": "table" + }, + "versions": "EQ(*)" + }, + { + "action": "edit destination group", + "identifier": "edit_destination_group", + "description": "Edit destination group", + "type": "generic", + "read_only": false, + "parameters": { + "ip_group_id": { + "description": "The unique identifier for the IP destination group", + "data_type": "numeric", + "required": true, + "primary": true, + "order": 0 + }, + "name": { + "description": "Destination IP group name", + "data_type": "string", + "primary": true, + "order": 1 + }, + "addresses": { + "description": "Comma seperated string of destination IP addresses, FQDNs, or wildcard FQDNs added to the group", + "data_type": "string", + "order": 2 + }, + "description": { + "description": "Additional information about the destination IP group.", + "data_type": "string", + "order": 3 + }, + "ip_categories": { + "description": "Destination IP address URL categories", + "data_type": "string", + "order": 4 + }, + "countries": { + "description": "Destination IP address countries. You can identify destinations based on the location of a server.", + "data_type": "string", + "order": 5 + }, + "is_non_editable": { + "description": "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", + "data_type": "boolean", + "default": false, + "example_values": [ + true, + false + ], + "order": 6 + } + }, + "output": [ + { + "data_path": "action_result.status", + "data_type": "string", + "column_name": "Status", + "column_order": 2, + "example_values": [ + "test success", + "test failed" + ] + }, + { + "data_path": "action_result.parameter.is_non_editable", + "data_type": "boolean", + "column_name": "Is Non Editable" + }, + { + "data_path": "action_result.parameter.countries", + "data_type": "string", + "column_name": "Countries" + }, + { + "data_path": "action_result.parameter.ip_categories", + "data_type": "string", + "column_name": "Ip Categories" + }, + { + "data_path": "action_result.parameter.description", + "data_type": "string", + "column_name": "Description" + }, + { + "data_path": "action_result.parameter.addresses", + "data_type": "string", + "column_name": "Addresses" + }, + { + "data_path": "action_result.parameter.name", + "data_type": "string", + "column_name": "Name" + }, + { + "data_path": "action_result.parameter.ip_group_id", + "data_type": "numeric", + "column_name": "IP Group Id" + + }, + { + "data_path": "action_result.data.*.id", + "data_type": "numeric", + "column_name": "Group ID", + "column_order": 0 + }, + { + "data_path": "action_result.data.*.name", + "data_type": "string", + "column_name": "Group name", + "column_order": 1 + }, + { + "data_path": "action_result.data.*.type", + "data_type": "string", + "example_values": [ + "DSTN_IP", + "DSTN_FQDN", + "DSTN_DOMAIN", + "DSTN_OTHER" + ], + "column_name": "Group type", + "column_order": 3 + }, + { + "data_path": "action_result.data.*.addresses", + "data_type": "string", + "example_values": [ + "192.168.1.1" + ], + "column_name": "Destination addresses", + "column_order": 4 + }, + { + "data_path": "action_result.data.*.countries", + "data_type": "string", + "column_name": "Destination countries", + "column_order": 5 + }, + { + "data_path": "action_result.data.*.description", + "data_type": "string", + "column_name": "Description", + "column_order": 6 + }, + { + "data_path": "action_result.data.*.ipCategories", + "data_type": "string", + "example_values": [ + "TRADING_BROKARAGE_INSURANCE" + ], + "column_name": "Destination categories", + "column_order": 7 + }, + { + "data_path": "action_result.data.*.isNonEditable", + "data_type": "boolean", + "example_values": [ + true, + false + ], + "column_name": "Is editable", + "column_order": 8 + }, + { + "data_path": "action_result.data.*.creatorContext", + "data_type": "string", + "column_name": "Creator Context", + "column_order": 9 + }, + { + "data_path": "action_result.summary", + "data_type": "string" + }, + { + "data_path": "action_result.summary.message", + "data_type": "string", + "example_values": [ + "Destination group edited" + ] + }, + { + "data_path": "action_result.message", + "data_type": "string", + "example_values": [ + "Destination group edited" + ] + }, + { + "data_path": "summary.message", + "data_type": "string" + }, + { + "data_path": "summary.total_objects", + "data_type": "numeric", + "example_values": [ + 1 + ] + }, + { + "data_path": "summary.total_objects_successful", + "data_type": "numeric", + "example_values": [ + 1 + ] + } + ], + "render": { + "type": "table" + }, + "versions": "EQ(*)" + }, + { + "action": "delete destination group", + "identifier": "delete_destination_group", + "description": "Delete destination group", + "type": "generic", + "read_only": false, + "parameters": { + "ip_group_ids": { + "description": "A comma-separated list of unique identifiers for the IP destination groups", + "data_type": "string", + "primary": true, + "order": 0 + } + }, + "output": [ + { + "data_path": "action_result.status", + "data_type": "string", + "column_name": "Status", + "column_order": 0, + "example_values": [ + "test success", + "test failed" + ] + }, + { + "data_path": "action_result.parameter.ip_group_ids", + "data_type": "string", + "column_name": "IP Group Ids" + }, + { + "data_path": "action_result.data.*.ip_group_ids", + "data_type": "string" + }, + { + "data_path": "action_result.summary", + "data_type": "string" + }, + { + "data_path": "action_result.summary.message", + "data_type": "string", + "example_values": [ + "Destination group deleted" + ] + }, + { + "data_path": "action_result.message", + "data_type": "string", + "example_values": [ + "Destination group deleted" + ] + }, + { + "data_path": "summary.message", + "data_type": "string" + }, + { + "data_path": "summary.total_objects", + "data_type": "numeric", + "example_values": [ + 1 + ] + }, + { + "data_path": "summary.total_objects_successful", + "data_type": "numeric", + "example_values": [ + 1 + ] + } + ], + "render": { + "type": "table" + }, + "versions": "EQ(*)" + }, + { + "action": "get departments", + "identifier": "get_departments", + "description": "Get a list of departments", + "type": "investigate", + "read_only": true, + "parameters": { + "name": { + "description": "Filter by department name", + "data_type": "string", + "primary": true, + "order": 0 + }, + "page": { + "description": "Specifies the page offset", + "data_type": "numeric", + "primary": true, + "order": 1 + }, + "pageSize": { + "description": "Specifies the page size", + "default": 100, + "data_type": "numeric", + "primary": true, + "order": 2 + } + }, + "output": [ + { + "data_path": "action_result.status", + "data_type": "string", + "column_name": "Status", + "example_values": [ + "test success", + "test failed" + ] + }, + { + "data_path": "action_result.parameter.pageSize", + "data_type": "string", + "column_name": "Page Size" + }, + { + "data_path": "action_result.parameter.page", + "data_type": "string", + "column_name": "Page" + }, + { + "data_path": "action_result.parameter.name", + "data_type": "string", + "column_name": "Name" + }, + { + "data_path": "action_result.data.*.id", + "data_type": "numeric", + "column_name": "Department Id", + "column_order": 0 + + }, + { + "data_path": "action_result.data.*.name", + "data_type": "string", + "column_name": "Department Name", + "column_order": 1 + }, + { + "data_path": "action_result.data.*.isNonEditable", + "data_type": "boolean", + "column_name": "Is editable", + "column_order": 2 + }, + { + "data_path": "action_result.summary", + "data_type": "string" + }, + { + "data_path": "action_result.summary.message", + "data_type": "string", + "example_values": [ + "Departments Retrieved" + ] + }, + { + "data_path": "action_result.summary.total_deparments", + "data_type": "numeric", + "example_values": [ + 97 + ] + }, + { + "data_path": "action_result.message", + "data_type": "string", + "example_values": [ + "Departments Retrieved" + ] + }, + { + "data_path": "summary.message", + "data_type": "string" + }, + { + "data_path": "summary.total_objects", + "data_type": "numeric", + "example_values": [ + 1 + ] + }, + { + "data_path": "summary.total_objects_successful", + "data_type": "numeric", + "example_values": [ + 1 + ] + } + ], + "render": { + "type": "table" + }, + "versions": "EQ(*)" + }, + { + "action": "get category details", + "identifier": "get_category_details", + "description": "Get the urls and keywords of a category", + "type": "investigate", + "read_only": true, + "parameters": { + "category_ids": { + "description": "Comma seperated string of category id's to query", + "data_type": "string", + "order": 0, + "example_values": [ + "CUSTOM_001, CUSTOM_002" + ], + "primary": true + } + }, + "output": [ + { + "data_path": "action_result.status", + "data_type": "string", + "example_values": [ + "test success", + "test failed" + ] + }, + { + "data_path": "action_result.parameter.category_ids", + "data_type": "string", + "example_values": [ + "CUSTOM_001, CUSTOM_002" + ] + }, + { + "data_path": "action_result.data.*.configuredName", + "data_type": "string", + "example_values": [ + "test Test-Caution" + ], + "column_order": 2, + "column_name": "Configured Name" + }, + { + "data_path": "action_result.data.*.customCategory", + "data_type": "boolean", + "column_name": "Is Custom Category", + "column_order": 4, + "example_values": [ + true, + false + ] + }, + { + "data_path": "action_result.data.*.keywords", + "data_type": "string", + "column_order": 5, + "column_name": "Category Keywords" + }, + { + "data_path": "action_result.data.*.urls", + "data_type": "string", + "column_order": 6, + "column_name": "Category Urls" + }, + { + "data_path": "action_result.data.*.customIpRangesCount", + "data_type": "numeric", + "example_values": [ + 0 + ] + }, + { + "data_path": "action_result.data.*.customUrlsCount", + "data_type": "numeric", + "example_values": [ + 0 + ] + }, + { + "data_path": "action_result.data.*.dbCategorizedUrls", + "data_type": "string", + "example_values": [ + "test 6.5.3.2.4" + ] + }, + { + "data_path": "action_result.data.*.description", + "data_type": "string", + "column_name": "Description", + "column_order": 3, + "example_values": [ + "test OTHER_RESTRICTED_WEBSITE_DESC" + ] + }, + { + "data_path": "action_result.data.*.editable", + "data_type": "boolean", + "column_name": "Editable", + "column_order": 7, + "example_values": [ + true, + false + ] + }, + { + "data_path": "action_result.data.*.id", + "data_type": "string", + "column_name": "Category ID", + "column_order": 0, + "contains": [ + "zscaler url category" + ], + "example_values": [ + "test OTHER_RESTRICTED_WEBSITE" + ] + }, + { + "data_path": "action_result.data.*.ipRangesRetainingParentCategoryCount", + "data_type": "numeric", + "example_values": [ + 0 + ] + }, + { + "data_path": "action_result.data.*.scopes.*.Type", + "data_type": "string", + "example_values": [ + "test ORGANIZATION" + ] + }, + { + "data_path": "action_result.data.*.type", + "data_type": "string", + "example_values": [ + "test URL_CATEGORY" + ], + "column_order": 1, + "column_name": "Type" + }, + { + "data_path": "action_result.data.*.urlsRetainingParentCategoryCount", + "data_type": "numeric", + "example_values": [ + 0 + ] + }, + { + "data_path": "action_result.data.*.val", + "data_type": "numeric", + "example_values": [ + 1 + ] + }, + { + "data_path": "action_result.summary.total_categories", + "data_type": "numeric", + "example_values": [ + 97 + ] + }, + { + "data_path": "action_result.message", + "data_type": "string", + "example_values": [ + "Category details recieved" + ] + }, + { + "data_path": "summary.total_objects", + "data_type": "numeric", + "example_values": [ + 1 + ] + }, + { + "data_path": "summary.total_objects_successful", + "data_type": "numeric", + "example_values": [ + 1 + ] + } + ], + "render": { + "type": "table" + }, + "versions": "EQ(*)" } ], "pip_dependencies": { diff --git a/zscaler_connector.py b/zscaler_connector.py index dd0686c..038e8db 100644 --- a/zscaler_connector.py +++ b/zscaler_connector.py @@ -1,6 +1,6 @@ # File: zscaler_connector.py # -# Copyright (c) 2017-2023 Splunk Inc. +# Copyright (c) 2017-2024 Splunk Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -15,6 +15,7 @@ # # # Phantom App imports +import ipaddress import json import re import time @@ -104,10 +105,11 @@ def _validate_integer(self, action_result, parameter, key, allow_zero=False): def _process_empty_response(self, response, action_result): if response.status_code == 200 or response.status_code == 204: return RetVal(phantom.APP_SUCCESS, {}) - return RetVal(action_result.set_status( - phantom.APP_ERROR, - "Status code : {}. Empty response and no information in the header".format(response.status_code)), - None + return RetVal( + action_result.set_status( + phantom.APP_ERROR, "Status code : {}. Empty response and no information in the header".format(response.status_code) + ), + None, ) def _process_html_response(self, response, action_result): @@ -121,22 +123,24 @@ def _process_html_response(self, response, action_result): for element in soup(["script", "style", "footer", "nav"]): element.extract() err_text = soup.text - split_lines = err_text.split('\n') + split_lines = err_text.split("\n") split_lines = [x.strip() for x in split_lines if x.strip()] - err_text = '\n'.join(split_lines) + err_text = "\n".join(split_lines) except Exception as e: err_text = "Cannot parse err details" self.debug_print("{}. Error: {}".format(err_text, e)) err_text = err_text - msg = "Please check the asset configuration parameters (the base_url should not end with "\ + msg = ( + "Please check the asset configuration parameters (the base_url should not end with " "/api/v1 e.g. https://admin.zscaler_instance.net)." + ) if len(err_text) <= 500: msg += "Status Code: {0}. Data from server:\n{1}\n".format(status_code, err_text) - msg = msg.replace('{', '{{').replace('}', '}}') + msg = msg.replace("{", "{{").replace("}", "}}") return RetVal(action_result.set_status(phantom.APP_ERROR, msg), None) def _process_json_response(self, r, action_result): @@ -145,8 +149,12 @@ def _process_json_response(self, r, action_result): try: resp_json = r.json() except Exception as e: - return RetVal(action_result.set_status(phantom.APP_ERROR, "Unable to parse JSON response. Error: {0}" - .format(self._get_err_msg_from_exception(e))), None) + return RetVal( + action_result.set_status( + phantom.APP_ERROR, "Unable to parse JSON response. Error: {0}".format(self._get_err_msg_from_exception(e)) + ), + None, + ) # Please specify the status codes here if 200 <= r.status_code < 399: @@ -154,32 +162,30 @@ def _process_json_response(self, r, action_result): # You should process the error returned in the json try: - msg = resp_json['message'] + msg = resp_json["message"] except Exception: - msg = "Error from server. Status Code: {0} Data from server: {1}".format( - r.status_code, r.text.replace('{', '{{').replace('}', '}}') - ) + msg = "Error from server. Status Code: {0} Data from server: {1}".format(r.status_code, r.text.replace("{", "{{").replace("}", "}}")) return RetVal(action_result.set_status(phantom.APP_ERROR, msg), None) def _process_response(self, r, action_result): # store the r_text in debug data, it will get dumped in the logs if the action fails - if hasattr(action_result, 'add_debug_data'): - action_result.add_debug_data({'r_status_code': r.status_code}) - action_result.add_debug_data({'r_text': r.text}) - action_result.add_debug_data({'r_headers': r.headers}) + if hasattr(action_result, "add_debug_data"): + action_result.add_debug_data({"r_status_code": r.status_code}) + action_result.add_debug_data({"r_text": r.text}) + action_result.add_debug_data({"r_headers": r.headers}) # Process each 'Content-Type' of response separately # Process a json response - if 'json' in r.headers.get('Content-Type', ''): + if "json" in r.headers.get("Content-Type", ""): return self._process_json_response(r, action_result) # Process an HTML response, Do this no matter what the api talks. # There is a high chance of a PROXY in between phantom and the rest of # world, in case of errors, PROXY's return HTML, this function parses # the error and adds it to the action_result. - if 'html' in r.headers.get('Content-Type', ''): + if "html" in r.headers.get("Content-Type", ""): return self._process_html_response(r, action_result) # it's not content-type that is to be parsed, handle an empty response @@ -188,13 +194,13 @@ def _process_response(self, r, action_result): # everything else is actually an error at this point msg = "Can't process response from server. Status Code: {0} Data from server: {1}".format( - r.status_code, r.text.replace('{', '{{').replace('}', '}}') + r.status_code, r.text.replace("{", "{{").replace("}", "}}") ) return RetVal(action_result.set_status(phantom.APP_ERROR, msg), None) def _is_ip(self, input_ip_address): - """ Function that checks given address and return True if address is valid IPv4 or IPV6 address. + """Function that checks given address and return True if address is valid IPv4 or IPV6 address. :param input_ip_address: IP address :return: status (success/failure) @@ -212,15 +218,16 @@ def _is_ip(self, input_ip_address): return True - def _make_rest_call(self, endpoint, action_result, headers=None, params=None, - data=None, method="get", use_json=True, timeout=ZSCALER_DEFAULT_TIMEOUT): + def _make_rest_call( + self, endpoint, action_result, headers=None, params=None, data=None, method="get", use_json=True, timeout=ZSCALER_DEFAULT_TIMEOUT + ): resp_json = None if headers is None: headers = {} - if self.get_action_identifier() != 'submit_file': + if self.get_action_identifier() != "submit_file": headers.update(self._headers) try: @@ -229,29 +236,16 @@ def _make_rest_call(self, endpoint, action_result, headers=None, params=None, return RetVal(action_result.set_status(phantom.APP_ERROR, "Invalid method: {0}".format(method)), resp_json) # Create a URL to connect to - url = '{}{}'.format(self._base_url, endpoint) + url = "{}{}".format(self._base_url, endpoint) try: if use_json: - r = request_func( - url, - json=data, - headers=headers, - params=params, - timeout=timeout - ) + r = request_func(url, json=data, headers=headers, params=params, timeout=timeout) else: - r = request_func( - url, - data=data, - headers=headers, - params=params, - timeout=timeout - ) + r = request_func(url, data=data, headers=headers, params=params, timeout=timeout) except Exception as e: error_message = self._get_err_msg_from_exception(e) error_message = re.sub(ZSCALER_MATCH_REGEX, ZSCALER_REPLACE_REGEX, error_message) - return RetVal(action_result.set_status(phantom.APP_ERROR, "Error Connecting to Zscaler server. {}" - .format(error_message)), resp_json) + return RetVal(action_result.set_status(phantom.APP_ERROR, "Error Connecting to Zscaler server. {}".format(error_message)), resp_json) self._response = r @@ -277,16 +271,16 @@ def _make_rest_call_helper(self, *args, **kwargs): if phantom.is_fail(ret_val): if self._response is None: return ret_val, response - if self._response.status_code == 409 and self._retry_rest_call: # Lock not available + if self._response.status_code == 409 and self._retry_rest_call != 0: # Lock not available # This basically just means we need to try again self.debug_print("Error 409: Lock not available") self.send_progress("Error 409: Lock not available: Retrying in 1 second") time.sleep(1) - self._retry_rest_call = False # make it to false to avoid extra rest call + self._retry_rest_call -= 1 # reduce the number of retries return self._make_rest_call_helper(*args, **kwargs) - if self._response.status_code == 429 and self._retry_rest_call: # Rate limit exceeded + if self._response.status_code == 429 and self._retry_rest_call != 0: # Rate limit exceeded try: - retry_time = self._response.json()['Retry-After'] + retry_time = self._response.json()["Retry-After"] except KeyError: self.debug_print("KeyError") return ret_val, response @@ -296,7 +290,7 @@ def _make_rest_call_helper(self, *args, **kwargs): return retry_time, response self.send_progress("Exceeded rate limit: Retrying after {}".format(retry_time)) time.sleep(seconds_to_wait) - self._retry_rest_call = False # make it to false to avoid extra rest call + self._retry_rest_call -= 1 # reduce the number of retries return self._make_rest_call_helper(*args, **kwargs) return ret_val, response @@ -319,42 +313,25 @@ def _init_session(self): try: timestamp, obf_api_key = self._obfuscate_api_key(api_key) except Exception: - return self.set_status( - phantom.APP_ERROR, - "Error obfuscating API key" - ) + return self.set_status(phantom.APP_ERROR, "Error obfuscating API key") - body = { - 'apiKey': obf_api_key, - 'username': username, - 'password': password, - 'timestamp': timestamp - } + body = {"apiKey": obf_api_key, "username": username, "password": password, "timestamp": timestamp} action_result = ActionResult() - ret_val, _ = self._make_rest_call_helper( - '/api/v1/authenticatedSession', - action_result, data=body, - method='post' - ) + ret_val, _ = self._make_rest_call_helper("/api/v1/authenticatedSession", action_result, data=body, method="post") if phantom.is_fail(ret_val): - self.debug_print('Error starting Zscaler session: {}'.format(action_result.get_message())) - return self.set_status( - phantom.APP_ERROR, - 'Error starting Zscaler session: {}'.format(action_result.get_message()) - ) + self.debug_print("Error starting Zscaler session: {}".format(action_result.get_message())) + return self.set_status(phantom.APP_ERROR, "Error starting Zscaler session: {}".format(action_result.get_message())) else: - self.save_progress('Successfully started Zscaler session') - self._headers = { - 'cookie': self._response.headers['Set-Cookie'].split(';')[0].strip() - } + self.save_progress("Successfully started Zscaler session") + self._headers = {"cookie": self._response.headers["Set-Cookie"].split(";")[0].strip()} return phantom.APP_SUCCESS def _deinit_session(self): action_result = ActionResult() config = self.get_config() - self._base_url = config['base_url'].rstrip('/') - ret_val, response = self._make_rest_call_helper('/api/v1/authenticatedSession', action_result, method='delete') + self._base_url = config["base_url"].rstrip("/") + ret_val, response = self._make_rest_call_helper("/api/v1/authenticatedSession", action_result, method="delete") if phantom.is_fail(ret_val): self.debug_print("Deleting the authenticated session failed on the ZScaler server.") @@ -378,58 +355,55 @@ def _filter_endpoints(self, action_result, to_add, existing, action, name): if not endpoints: summary = action_result.set_summary({}) - summary['updated'] = [] - summary['ignored'] = to_add + summary["updated"] = [] + summary["ignored"] = to_add return RetVal(action_result.set_status(phantom.APP_SUCCESS, msg), None) return RetVal(phantom.APP_SUCCESS, endpoints) def _get_blocklist(self, action_result): - return self._make_rest_call_helper('/api/v1/security/advanced', action_result) + return self._make_rest_call_helper("/api/v1/security/advanced", action_result) def _check_blocklist(self, action_result, endpoints, action): ret_val, response = self._get_blocklist(action_result) if phantom.is_fail(ret_val): return RetVal(ret_val, None) - blocklist = response.get('blacklistUrls', []) + blocklist = response.get("blacklistUrls", []) - return self._filter_endpoints(action_result, endpoints, blocklist, action, 'Blocklist') + return self._filter_endpoints(action_result, endpoints, blocklist, action, "Blocklist") def _amend_blocklist(self, action_result, endpoints, action): ret_val, filtered_endpoints = self._check_blocklist(action_result, endpoints, action) if phantom.is_fail(ret_val) or filtered_endpoints is None: return ret_val - params = {'action': action} - data = { - "blacklistUrls": filtered_endpoints - } + params = {"action": action} + data = {"blacklistUrls": filtered_endpoints} ret_val, response = self._make_rest_call_helper( - '/api/v1/security/advanced/blacklistUrls', action_result, params=params, - data=data, method="post" + "/api/v1/security/advanced/blacklistUrls", action_result, params=params, data=data, method="post" ) if phantom.is_fail(ret_val) and self._response.status_code != 204: return ret_val summary = action_result.set_summary({}) - summary['updated'] = filtered_endpoints - summary['ignored'] = list(set(endpoints) - set(filtered_endpoints)) + summary["updated"] = filtered_endpoints + summary["ignored"] = list(set(endpoints) - set(filtered_endpoints)) # Encode the unicode IP or URL strings - summary['updated'] = [element for element in summary['updated']] - summary['ignored'] = [element for element in summary['ignored']] + summary["updated"] = [element for element in summary["updated"]] + summary["ignored"] = [element for element in summary["ignored"]] return action_result.set_status(phantom.APP_SUCCESS) def _get_allowlist(self, action_result): - return self._make_rest_call_helper('/api/v1/security', action_result) + return self._make_rest_call_helper("/api/v1/security", action_result) def _check_allowlist(self, action_result, endpoints, action): ret_val, response = self._get_allowlist(action_result) if phantom.is_fail(ret_val): return RetVal(ret_val, None) - allowlist = response.get('whitelistUrls', []) + allowlist = response.get("whitelistUrls", []) self._allowlist = allowlist - return self._filter_endpoints(action_result, endpoints, allowlist, action, 'Allowlist') + return self._filter_endpoints(action_result, endpoints, allowlist, action, "Allowlist") def _amend_allowlist(self, action_result, endpoints, action): ret_val, filtered_endpoints = self._check_allowlist(action_result, endpoints, action) @@ -441,44 +415,34 @@ def _amend_allowlist(self, action_result, endpoints, action): else: to_add_endpoints = list(set(self._allowlist) - set(filtered_endpoints)) - data = { - "whitelistUrls": to_add_endpoints - } - ret_val, response = self._make_rest_call_helper( - '/api/v1/security', action_result, - data=data, method='put' - ) + data = {"whitelistUrls": to_add_endpoints} + ret_val, response = self._make_rest_call_helper("/api/v1/security", action_result, data=data, method="put") if phantom.is_fail(ret_val): return ret_val action_result.add_data(response) summary = action_result.set_summary({}) - summary['updated'] = filtered_endpoints - summary['ignored'] = list(set(endpoints) - set(filtered_endpoints)) + summary["updated"] = filtered_endpoints + summary["ignored"] = list(set(endpoints) - set(filtered_endpoints)) # Encode the unicode IP or URL strings - summary['updated'] = [element for element in summary['updated']] - summary['ignored'] = [element for element in summary['ignored']] + summary["updated"] = [element for element in summary["updated"]] + summary["ignored"] = [element for element in summary["ignored"]] return action_result.set_status(phantom.APP_SUCCESS) def _get_category(self, action_result, category): - ret_val, response = self._make_rest_call_helper('/api/v1/urlCategories', action_result) + ret_val, response = self._make_rest_call_helper("/api/v1/urlCategories", action_result) if phantom.is_fail(ret_val): return ret_val, response for cat in response: - if cat.get('configuredName', None) == category: + if cat.get("configuredName", None) == category: return RetVal(phantom.APP_SUCCESS, cat) for cat in response: - if cat['id'] == category: + if cat["id"] == category: return RetVal(phantom.APP_SUCCESS, cat) - return RetVal( - action_result.set_status( - phantom.APP_ERROR, "Unable to find category" - ), - None - ) + return RetVal(action_result.set_status(phantom.APP_ERROR, "Unable to find category"), None) def _check_category(self, action_result, endpoints, category, action): ret_val, response = self._get_category(action_result, category) @@ -486,159 +450,153 @@ def _check_category(self, action_result, endpoints, category, action): return ret_val, response self._category = response - urls = response.get('dbCategorizedUrls', []) + urls = response.get("dbCategorizedUrls", []) - return self._filter_endpoints(action_result, endpoints, urls, action, 'Category') + return self._filter_endpoints(action_result, endpoints, urls, action, "Category") def _amend_category(self, action_result, endpoints, category, action): ret_val, filtered_endpoints = self._check_category(action_result, endpoints, category, action) if phantom.is_fail(ret_val) or filtered_endpoints is None: return ret_val - params = {'action': action } + params = {"action": action} data = { - "configuredName": self._category.get('configuredName'), + "configuredName": self._category.get("configuredName"), "keywordsRetainingParentCategory": self._category.get("keywordsRetainingParentCategory", []), "urls": [], - "dbCategorizedUrls": filtered_endpoints + "dbCategorizedUrls": filtered_endpoints, } ret_val, response = self._make_rest_call_helper( - '/api/v1/urlCategories/{}'.format(self._category['id']), - action_result, data=data, method='put', params=params, timeout=None + "/api/v1/urlCategories/{}".format(self._category["id"]), action_result, data=data, method="put", params=params, timeout=None ) if phantom.is_fail(ret_val): return ret_val action_result.add_data(response) summary = action_result.set_summary({}) - summary['updated'] = filtered_endpoints - summary['ignored'] = list(set(endpoints) - set(filtered_endpoints)) + summary["updated"] = filtered_endpoints + summary["ignored"] = list(set(endpoints) - set(filtered_endpoints)) # Encode the unicode IP or URL strings - summary['updated'] = [element for element in summary['updated']] - summary['ignored'] = [element for element in summary['ignored']] + summary["updated"] = [element for element in summary["updated"]] + summary["ignored"] = [element for element in summary["ignored"]] return action_result.set_status(phantom.APP_SUCCESS) def _block_endpoint(self, action_result, endpoints, category): list_endpoints = list() - list_endpoints = [x.strip() for x in endpoints.split(',')] + list_endpoints = [x.strip() for x in endpoints.split(",") if x.strip()] endpoints = list(filter(None, list_endpoints)) endpoints = self._truncate_protocol(endpoints) - if self.get_action_identifier() in ['block_url']: + if self.get_action_identifier() in ["block_url"]: ret_val = self._check_for_overlength(action_result, endpoints) if phantom.is_fail(ret_val): return ret_val if category is None: - return self._amend_blocklist(action_result, endpoints, 'ADD_TO_LIST') + return self._amend_blocklist(action_result, endpoints, "ADD_TO_LIST") else: - return self._amend_category(action_result, endpoints, category, 'ADD_TO_LIST') + return self._amend_category(action_result, endpoints, category, "ADD_TO_LIST") def _unblock_endpoint(self, action_result, endpoints, category): list_endpoints = list() - list_endpoints = [x.strip() for x in endpoints.split(',')] + list_endpoints = [x.strip() for x in endpoints.split(",") if x.strip()] endpoints = list(filter(None, list_endpoints)) endpoints = self._truncate_protocol(endpoints) - if self.get_action_identifier() in ['unblock_url']: + if self.get_action_identifier() in ["unblock_url"]: ret_val = self._check_for_overlength(action_result, endpoints) if phantom.is_fail(ret_val): return ret_val if category is None: - return self._amend_blocklist(action_result, endpoints, 'REMOVE_FROM_LIST') + return self._amend_blocklist(action_result, endpoints, "REMOVE_FROM_LIST") else: - return self._amend_category(action_result, endpoints, category, 'REMOVE_FROM_LIST') + return self._amend_category(action_result, endpoints, category, "REMOVE_FROM_LIST") def _handle_block_ip(self, param): action_result = self.add_action_result(ActionResult(dict(param))) - return self._block_endpoint(action_result, param['ip'], param.get('url_category')) + return self._block_endpoint(action_result, param["ip"], param.get("url_category")) def _handle_block_url(self, param): action_result = self.add_action_result(ActionResult(dict(param))) - return self._block_endpoint(action_result, param['url'], param.get('url_category')) + return self._block_endpoint(action_result, param["url"], param.get("url_category")) def _handle_unblock_ip(self, param): action_result = self.add_action_result(ActionResult(dict(param))) - return self._unblock_endpoint(action_result, param['ip'], param.get('url_category')) + return self._unblock_endpoint(action_result, param["ip"], param.get("url_category")) def _handle_unblock_url(self, param): action_result = self.add_action_result(ActionResult(dict(param))) - return self._unblock_endpoint(action_result, param['url'], param.get('url_category')) + return self._unblock_endpoint(action_result, param["url"], param.get("url_category")) def _allowlist_endpoint(self, action_result, endpoints, category): list_endpoints = list() - list_endpoints = [x.strip() for x in endpoints.split(',')] + list_endpoints = [x.strip() for x in endpoints.split(",")] endpoints = list(filter(None, list_endpoints)) endpoints = self._truncate_protocol(endpoints) - if self.get_action_identifier() in ['allow_url']: + if self.get_action_identifier() in ["allow_url"]: ret_val = self._check_for_overlength(action_result, endpoints) if phantom.is_fail(ret_val): return ret_val if category is None: - return self._amend_allowlist(action_result, endpoints, 'ADD_TO_LIST') + return self._amend_allowlist(action_result, endpoints, "ADD_TO_LIST") else: - return self._amend_category(action_result, endpoints, category, 'ADD_TO_LIST') + return self._amend_category(action_result, endpoints, category, "ADD_TO_LIST") def _unallow_endpoint(self, action_result, endpoints, category): list_endpoints = list() - list_endpoints = [x.strip() for x in endpoints.split(',')] + list_endpoints = [x.strip() for x in endpoints.split(",")] endpoints = list(filter(None, list_endpoints)) endpoints = self._truncate_protocol(endpoints) - if self.get_action_identifier() in ['unallow_url']: + if self.get_action_identifier() in ["unallow_url"]: ret_val = self._check_for_overlength(action_result, endpoints) if phantom.is_fail(ret_val): return ret_val if category is None: - return self._amend_allowlist(action_result, endpoints, 'REMOVE_FROM_LIST') + return self._amend_allowlist(action_result, endpoints, "REMOVE_FROM_LIST") else: - return self._amend_category(action_result, endpoints, category, 'REMOVE_FROM_LIST') + return self._amend_category(action_result, endpoints, category, "REMOVE_FROM_LIST") def _handle_allow_ip(self, param): action_result = self.add_action_result(ActionResult(dict(param))) - return self._allowlist_endpoint(action_result, param['ip'], param.get('url_category')) + return self._allowlist_endpoint(action_result, param["ip"], param.get("url_category")) def _handle_allow_url(self, param): action_result = self.add_action_result(ActionResult(dict(param))) - return self._allowlist_endpoint(action_result, param['url'], param.get('url_category')) + return self._allowlist_endpoint(action_result, param["url"], param.get("url_category")) def _handle_unallow_ip(self, param): action_result = self.add_action_result(ActionResult(dict(param))) - return self._unallow_endpoint(action_result, param['ip'], param.get('url_category')) + return self._unallow_endpoint(action_result, param["ip"], param.get("url_category")) def _handle_unallow_url(self, param): action_result = self.add_action_result(ActionResult(dict(param))) - return self._unallow_endpoint(action_result, param['url'], param.get('url_category')) + return self._unallow_endpoint(action_result, param["url"], param.get("url_category")) def _lookup_endpoint(self, action_result, endpoints): if not endpoints: action_result.set_status(phantom.APP_ERROR, "Please provide valid list of URL(s)") - ret_val, response = self._make_rest_call_helper( - '/api/v1/urlLookup', action_result, - data=endpoints, method='post' - ) + ret_val, response = self._make_rest_call_helper("/api/v1/urlLookup", action_result, data=endpoints, method="post") if phantom.is_fail(ret_val): return ret_val - ret_val, blocklist_response = self._make_rest_call_helper( - '/api/v1/security/advanced', action_result, method='get' - ) + ret_val, blocklist_response = self._make_rest_call_helper("/api/v1/security/advanced", action_result, method="get") if phantom.is_fail(ret_val): return ret_val for e in endpoints: - if e in blocklist_response.get('blacklistUrls', []): - [response[i].update({"blocklisted": True}) for i, item in enumerate(response) if item['url'] == e] + if e in blocklist_response.get("blacklistUrls", []): + [response[i].update({"blocklisted": True}) for i, item in enumerate(response) if item["url"] == e] else: - [response[i].update({"blocklisted": False}) for i, item in enumerate(response) if item['url'] == e] + [response[i].update({"blocklisted": False}) for i, item in enumerate(response) if item["url"] == e] action_result.update_data(response) @@ -653,16 +611,14 @@ def _handle_get_report(self, param): action_result = self.add_action_result(ActionResult(dict(param))) - file_hash = param['file_hash'] + file_hash = param["file_hash"] - ret_val, sandbox_report = self._make_rest_call_helper('/api/v1/sandbox/report/{0}?details=full' - .format(file_hash), action_result) + ret_val, sandbox_report = self._make_rest_call_helper("/api/v1/sandbox/report/{0}?details=full".format(file_hash), action_result) if phantom.is_fail(ret_val): return action_result.get_status() - if sandbox_report.get(ZSCALER_JSON_FULL_DETAILS) and ZSCLAER_ERR_MD5_UNKNOWN_MSG in sandbox_report.get( - ZSCALER_JSON_FULL_DETAILS): + if sandbox_report.get(ZSCALER_JSON_FULL_DETAILS) and ZSCLAER_ERR_MD5_UNKNOWN_MSG in sandbox_report.get(ZSCALER_JSON_FULL_DETAILS): return action_result.set_status(phantom.APP_ERROR, sandbox_report.get(ZSCALER_JSON_FULL_DETAILS)) action_result.add_data(sandbox_report) @@ -680,49 +636,50 @@ def _handle_submit_file(self, param): if not (self._sandbox_api_token and self._sandbox_base_url): return action_result.set_status( - phantom.APP_ERROR, "Please provide ZScaler Sandbox Base URL and API token to submit the file to Sandbox") + phantom.APP_ERROR, "Please provide ZScaler Sandbox Base URL and API token to submit the file to Sandbox" + ) self._base_url = self._sandbox_base_url try: - file_id = param['vault_id'] + file_id = param["vault_id"] success, msg, file_info = phantom_rules.vault_info(vault_id=file_id) file_info = list(file_info)[0] except IndexError: - return action_result.set_status(phantom.APP_ERROR, 'Vault file could not be found with supplied Vault ID') + return action_result.set_status(phantom.APP_ERROR, "Vault file could not be found with supplied Vault ID") except Exception as e: err_msg = self._get_err_msg_from_exception(e) self.debug_print("Vault ID not valid. Error: {}".format(err_msg)) - return action_result.set_status(phantom.APP_ERROR, 'Vault ID not valid') + return action_result.set_status(phantom.APP_ERROR, "Vault ID not valid") - params = { - 'force': 1 if param.get('force', False) else 0, - 'api_token': self._sandbox_api_token - } + params = {"force": 1 if param.get("force", False) else 0, "api_token": self._sandbox_api_token} - with open(file_info.get('path'), 'rb') as f: + with open(file_info.get("path"), "rb") as f: data = f.read() - ret_val, resp_json = self._make_rest_call_helper('/zscsb/submit', - action_result, params=params, data=data, method='post', use_json=False) + ret_val, resp_json = self._make_rest_call_helper("/zscsb/submit", action_result, params=params, data=data, method="post", use_json=False) if phantom.is_fail(ret_val): return action_result.get_status() - if resp_json.get('code') != 200: - return action_result.set_status(phantom.APP_ERROR, - "Status code: {} Details: {}. Please make sure ZScaler Sandbox Base URL and API token are configured correctly" - .format(resp_json.get('code'), resp_json.get('message'))) + if resp_json.get("code") != 200: + return action_result.set_status( + phantom.APP_ERROR, + "Status code: {} Details: {}. Please make sure ZScaler Sandbox Base URL and API token are configured correctly".format( + resp_json.get("code"), resp_json.get("message") + ), + ) action_result.add_data(resp_json) - if resp_json.get('message') == '/submit response OK': + if resp_json.get("message") == "/submit response OK": msg = ZSCALER_SANDBOX_SUBMIT_FILE_MSG else: - if resp_json.get('message').lower() != resp_json.get('sandboxSubmission').lower(): - msg = 'Status Code: {}. Data from server: {}. {}.'.format(resp_json.get('code'), resp_json.get('sandboxSubmission'), - resp_json.get('message')) + if resp_json.get("message").lower() != resp_json.get("sandboxSubmission").lower(): + msg = "Status Code: {}. Data from server: {}. {}.".format( + resp_json.get("code"), resp_json.get("sandboxSubmission"), resp_json.get("message") + ) else: - msg = 'Status Code: {}. Data from server: {}'.format(resp_json.get('code'), resp_json.get('message')) + msg = "Status Code: {}. Data from server: {}".format(resp_json.get("code"), resp_json.get("message")) return action_result.set_status(phantom.APP_SUCCESS, msg) @@ -734,16 +691,25 @@ def _handle_list_url_categories(self, param): """ action_result = self.add_action_result(ActionResult(dict(param))) - ret_val, list_url_categories = self._make_rest_call_helper('/api/v1/urlCategories', action_result) + ret_val, list_url_categories = self._make_rest_call_helper("/api/v1/urlCategories", action_result) if phantom.is_fail(ret_val): return action_result.get_status() + get_ids_and_names_only = param.get("get_ids_and_names_only", False) + for url_category in list_url_categories: - action_result.add_data(url_category) + if get_ids_and_names_only: + category_lite = {} + category_lite["id"] = url_category["id"] + if "configuredName" in url_category: + category_lite["configuredName"] = url_category["configuredName"] + action_result.add_data(category_lite) + else: + action_result.add_data(url_category) summary = action_result.update_summary({}) - summary['total_url_categories'] = action_result.get_data_size() + summary["total_url_categories"] = action_result.get_data_size() return action_result.set_status(phantom.APP_SUCCESS) @@ -751,7 +717,7 @@ def _handle_lookup_ip(self, param): action_result = self.add_action_result(ActionResult(dict(param))) list_endpoints = list() - list_endpoints = [x.strip() for x in param['ip'].split(',')] + list_endpoints = [x.strip() for x in param["ip"].split(",")] endpoints = list(filter(None, list_endpoints)) return self._lookup_endpoint(action_result, endpoints) @@ -761,7 +727,7 @@ def _handle_lookup_url(self, param): action_result = self.add_action_result(ActionResult(dict(param))) list_endpoints = list() - list_endpoints = [x.strip() for x in param['url'].split(',')] + list_endpoints = [x.strip() for x in param["url"].split(",")] endpoints = list(filter(None, list_endpoints)) endpoints = self._truncate_protocol(endpoints) @@ -780,9 +746,9 @@ def _truncate_protocol(self, endpoints): """ for i in range(len(endpoints)): if endpoints[i].startswith("http://"): - endpoints[i] = endpoints[i][(len("http://")):] + endpoints[i] = endpoints[i][(len("http://")) :] # noqa elif endpoints[i].startswith("https://"): - endpoints[i] = endpoints[i][(len("https://")):] + endpoints[i] = endpoints[i][(len("https://")) :] # noqa return endpoints @@ -793,8 +759,10 @@ def _check_for_overlength(self, action_result, endpoints): """ for url in endpoints: if len(url) > 1024: - return action_result.set_status(phantom.APP_ERROR, - "Please provide valid comma-separated values in the action parameter. Max allowed length for each value is 1024.") + return action_result.set_status( + phantom.APP_ERROR, + "Please provide valid comma-separated values in the action parameter. Max allowed length for each value is 1024.", + ) return phantom.APP_SUCCESS def _handle_get_admin_users(self, param): @@ -805,31 +773,31 @@ def _handle_get_admin_users(self, param): """ action_result = self.add_action_result(ActionResult(dict(param))) - ret_val, limit = self._validate_integer(action_result, param.get('limit', ZSCALER_MAX_PAGESIZE), ZSCALER_LIMIT_KEY) + ret_val, limit = self._validate_integer(action_result, param.get("limit", ZSCALER_MAX_PAGESIZE), ZSCALER_LIMIT_KEY) if phantom.is_fail(ret_val): return action_result.get_status() params = {} admin_users = [] - params['page'] = 1 + params["page"] = 1 while True: if limit < ZSCALER_MAX_PAGESIZE: - params['pageSize'] = limit + params["pageSize"] = limit else: - params['pageSize'] = ZSCALER_MAX_PAGESIZE - ret_val, get_admin_users = self._make_rest_call_helper('/api/v1/adminUsers', action_result, params=params) + params["pageSize"] = ZSCALER_MAX_PAGESIZE + ret_val, get_admin_users = self._make_rest_call_helper("/api/v1/adminUsers", action_result, params=params) if phantom.is_fail(ret_val): return action_result.get_status() for admin_user in get_admin_users: admin_users.append(admin_user) - limit = limit - params['pageSize'] + limit = limit - params["pageSize"] if limit <= 0 or len(get_admin_users) == 0: break - params['page'] += 1 + params["page"] += 1 for user in admin_users: action_result.add_data(user) summary = action_result.update_summary({}) - summary['total_admin_users'] = action_result.get_data_size() + summary["total_admin_users"] = action_result.get_data_size() return action_result.set_status(phantom.APP_SUCCESS) @@ -850,27 +818,22 @@ def _handle_get_users(self, param): if not param: return action_result.set_status(phantom.APP_ERROR, "No filters provided") - ret_val, limit = self._validate_integer(action_result, param.get('limit', ZSCALER_MAX_PAGESIZE), ZSCALER_LIMIT_KEY) + ret_val, limit = self._validate_integer(action_result, param.get("limit", ZSCALER_MAX_PAGESIZE), ZSCALER_LIMIT_KEY) if phantom.is_fail(ret_val): return action_result.get_status() - params = { - "name": param.get('name'), - "dept": param.get('dept'), - "group": param.get('group'), - 'page': 1 - } + params = {"name": param.get("name"), "dept": param.get("dept"), "group": param.get("group"), "page": 1} users = [] while True: - params['pageSize'] = min(limit, ZSCALER_MAX_PAGESIZE) - ret_val, get_users = self._make_rest_call_helper('/api/v1/users', action_result, params=params, timeout=None) + params["pageSize"] = min(limit, ZSCALER_MAX_PAGESIZE) + ret_val, get_users = self._make_rest_call_helper("/api/v1/users", action_result, params=params, timeout=None) if phantom.is_fail(ret_val): return action_result.get_status() for user in get_users: users.append(user) - limit = limit - params['pageSize'] + limit = limit - params["pageSize"] if limit <= 0 or len(get_users) == 0: break - params['page'] += 1 + params["page"] += 1 # Add the response into the data section for user in users: @@ -878,7 +841,7 @@ def _handle_get_users(self, param): # Add a dictionary that is made up of the most important values from data into the summary summary = action_result.update_summary({}) - summary['total_users'] = action_result.get_data_size() + summary["total_users"] = action_result.get_data_size() return action_result.set_status(phantom.APP_SUCCESS) @@ -891,29 +854,29 @@ def _handle_get_groups(self, param): self.save_progress("In action handler for: {0}".format(self.get_action_identifier())) action_result = self.add_action_result(ActionResult(dict(param))) - ret_val, limit = self._validate_integer(action_result, param.get('limit', ZSCALER_MAX_PAGESIZE), ZSCALER_LIMIT_KEY) + ret_val, limit = self._validate_integer(action_result, param.get("limit", ZSCALER_MAX_PAGESIZE), ZSCALER_LIMIT_KEY) if phantom.is_fail(ret_val): return action_result.get_status() - params = {"search": param.get('search')} + params = {"search": param.get("search")} groups = [] - params['page'] = 1 + params["page"] = 1 while True: - params['pageSize'] = min(limit, ZSCALER_MAX_PAGESIZE) - ret_val, get_groups = self._make_rest_call_helper('/api/v1/groups', action_result, params=params) + params["pageSize"] = min(limit, ZSCALER_MAX_PAGESIZE) + ret_val, get_groups = self._make_rest_call_helper("/api/v1/groups", action_result, params=params) if phantom.is_fail(ret_val): return action_result.get_status() for group in get_groups: groups.append(group) - limit = limit - params['pageSize'] + limit = limit - params["pageSize"] if limit <= 0 or len(get_groups) == 0: break - params['page'] += 1 + params["page"] += 1 for group in groups: action_result.add_data(group) summary = action_result.update_summary({}) - summary['total_groups'] = action_result.get_data_size() + summary["total_groups"] = action_result.get_data_size() return action_result.set_status(phantom.APP_SUCCESS) @@ -928,29 +891,29 @@ def _handle_add_group_user(self, param): action_result = self.add_action_result(ActionResult(dict(param))) - user_id = param['user_id'] - group_id = param['group_id'] - ret_val, user_response = self._make_rest_call_helper(f'/api/v1/users/{user_id}', action_result) + user_id = param["user_id"] + group_id = param["group_id"] + ret_val, user_response = self._make_rest_call_helper(f"/api/v1/users/{user_id}", action_result) if phantom.is_fail(ret_val): return action_result.get_status() - ret_val, group_response = self._make_rest_call_helper(f'/api/v1/groups/{group_id}', action_result) + ret_val, group_response = self._make_rest_call_helper(f"/api/v1/groups/{group_id}", action_result) if phantom.is_fail(ret_val): return action_result.get_status() summary = action_result.update_summary({}) - if group_response in user_response['groups']: - summary['message'] = "User already in group" + if group_response in user_response["groups"]: + summary["message"] = "User already in group" action_result.add_data(group_response) return action_result.set_status(phantom.APP_SUCCESS, "User already in group") - user_response['groups'].append(group_response) + user_response["groups"].append(group_response) data = user_response - ret_val, response = self._make_rest_call_helper(f'/api/v1/users/{user_id}', action_result, data=data, method='put') + ret_val, response = self._make_rest_call_helper(f"/api/v1/users/{user_id}", action_result, data=data, method="put") if phantom.is_fail(ret_val): return action_result.get_status() action_result.add_data(response) - summary['message'] = "User successfully added to group" + summary["message"] = "User successfully added to group" return action_result.set_status(phantom.APP_SUCCESS) def _handle_remove_group_user(self, param): @@ -963,33 +926,533 @@ def _handle_remove_group_user(self, param): self.save_progress("In action handler for: {0}".format(self.get_action_identifier())) action_result = self.add_action_result(ActionResult(dict(param))) - user_id = param['user_id'] - group_id = param['group_id'] - ret_val, user_response = self._make_rest_call_helper(f'/api/v1/users/{user_id}', action_result) + user_id = param["user_id"] + group_id = param["group_id"] + ret_val, user_response = self._make_rest_call_helper(f"/api/v1/users/{user_id}", action_result) if phantom.is_fail(ret_val): return action_result.get_status() summary = action_result.update_summary({}) - if group_id not in [item['id'] for item in user_response['groups']]: - summary['message'] = "User already removed from group" + if group_id not in [item["id"] for item in user_response["groups"]]: + summary["message"] = "User already removed from group" action_result.add_data(user_response) return action_result.set_status(phantom.APP_SUCCESS, "User already removed from group") - for index, group in enumerate(user_response['groups']): - if group_id == group['id']: - user_response['groups'].pop(index) + for index, group in enumerate(user_response["groups"]): + if group_id == group["id"]: + user_response["groups"].pop(index) data = user_response - ret_val, response = self._make_rest_call_helper(f'/api/v1/users/{user_id}', action_result, data=data, method='put') + ret_val, response = self._make_rest_call_helper(f"/api/v1/users/{user_id}", action_result, data=data, method="put") + + if phantom.is_fail(ret_val): + return action_result.get_status() + + action_result.add_data(response) + summary["message"] = "User removed from group" + + return action_result.set_status(phantom.APP_SUCCESS) + + def _handle_get_allowlist(self, param): + """ + This action is used to get the default allowlist in zscaler + :return: status phantom.APP_ERROR/phantom.APP_SUCCESS(along with appropriate message) + """ + self.save_progress("In action handler for: {0}".format(self.get_action_identifier())) + action_result = self.add_action_result(ActionResult(dict(param))) + + ret_val, response = self._get_allowlist(action_result) + if phantom.is_fail(ret_val): + return RetVal(ret_val, None) + + allowlist = response.get("whitelistUrls", []) + for allowed in allowlist: + action_result.add_data({"url": allowed}) + summary = action_result.update_summary({}) + summary["total_allowlist_items"] = action_result.get_data_size() + summary["message"] = "allowlist retrieved" + + return action_result.set_status(phantom.APP_SUCCESS) + + def _is_ip_address(self, address): + try: + ipaddress.ip_address(address) + return True + except ValueError: + return False + + def _handle_get_denylist(self, param): + """ + This action is used to get the denylist in zscaler + :return: status phantom.APP_ERROR/phantom.APP_SUCCESS(along with appropriate message) + """ + self.save_progress("In action handler for: {0}".format(self.get_action_identifier())) + action_result = self.add_action_result(ActionResult(dict(param))) + + ret_val, response = self._get_blocklist(action_result) + if phantom.is_fail(ret_val): + return RetVal(ret_val, None) + + filter = param.get("filter") + query = param.get("query") + + summary = action_result.update_summary({}) + summary["message"] = "Denylist retrieved" + + blocklist = response.get("blacklistUrls", []) + for blocked in blocklist: + is_ip = self._is_ip_address(blocked) + if filter == "ip" and not is_ip: + continue + if filter == "url" and is_ip: + continue + if query and not re.fullmatch(query, blocked): + continue + action_result.add_data({"url": blocked}) + + summary["total_denylist_items"] = action_result.get_data_size() + return action_result.set_status(phantom.APP_SUCCESS) + + def _handle_update_user(self, param): + self.save_progress("In action handler for: {0}".format(self.get_action_identifier())) + action_result = self.add_action_result(ActionResult(dict(param))) + user_id = param["user_id"] + + try: + data = json.loads(param.get("user", "{}")) + except Exception as e: + return action_result.set_status(phantom.APP_ERROR, "User object needs to be valid json: {}".format(e)) + + ret_val, response = self._make_rest_call_helper(f"/api/v1/users/{user_id}", action_result, data=data, method="put") + + if phantom.is_fail(ret_val): + return action_result.get_status() + + self.debug_print(response) + action_result.add_data(response) + summary = action_result.update_summary({}) + summary["message"] = "User updated" + return action_result.set_status(phantom.APP_SUCCESS) + + def _get_category_details(self, id, action_result): + ret_val, response = self._make_rest_call_helper(f"/api/v1/urlCategories/{id}", action_result) + if phantom.is_fail(ret_val): + return action_result.get_status(), None + return phantom.APP_SUCCESS, response + + def _add_to_category(self, data, parent_data, cat_details, category_id, action_result): + new_data = cat_details.get("urls", []) + new_data.extend(data) + if new_data: + cat_details["urls"] = new_data + + new_parent_data = cat_details.get("dbCategorizedUrls", []) + new_parent_data.extend(parent_data) + if new_parent_data: + cat_details["dbCategorizedUrls"] = new_parent_data + + ret_val, response = self._make_rest_call_helper(f"/api/v1/urlCategories/{category_id}", action_result, data=cat_details, method="put") + return ret_val, response + + def _handle_add_category_url(self, param): + self.save_progress("In action handler for: {0}".format(self.get_action_identifier())) + action_result = self.add_action_result(ActionResult(dict(param))) + category_id = param["category_id"] + + ret_val, category_details = self._get_category_details(category_id, action_result) + if phantom.is_fail(ret_val): + return action_result.get_status() + + is_custom_category = category_details.get("customCategory", False) + + if not is_custom_category: + action_result.set_status(phantom.APP_ERROR, "Category with {0} is a default category, which cannot be modified".format(category_id)) + return action_result.get_status() + + urls = param.get("urls", "") + urls_list = [item.strip() for item in urls.split(",") if item.strip()] + retaining_parent_category_url = param.get("retaining-parent-category-url", "") + parent_urls = [item.strip() for item in retaining_parent_category_url.split(",") if item.strip()] + ret_val, response = self._add_to_category(urls_list, parent_urls, category_details, category_id, action_result) if phantom.is_fail(ret_val): return action_result.get_status() action_result.add_data(response) - summary['message'] = "User removed from group" + summary = action_result.update_summary({}) + summary["message"] = "Category urls updated" return action_result.set_status(phantom.APP_SUCCESS) + def _handle_add_category_ip(self, param): + self.save_progress("In action handler for: {0}".format(self.get_action_identifier())) + action_result = self.add_action_result(ActionResult(dict(param))) + category_id = param["category_id"] + + ret_val, category_details = self._get_category_details(category_id, action_result) + if phantom.is_fail(ret_val): + return action_result.get_status() + + ips = param.get("ips", "") + ips_list = [item.strip() for item in ips.split(",") if item.strip()] + retaining_parent_category_ip = param.get("retaining-parent-category-ip", "") + parent_ips = [item.strip() for item in retaining_parent_category_ip.split(",") if item.strip()] + ret_val, response = self._add_to_category(ips_list, parent_ips, category_details, category_id, action_result) + + if phantom.is_fail(ret_val): + return action_result.get_status() + + action_result.add_data(response) + summary = action_result.update_summary({}) + summary["message"] = "Category ips updated" + + return action_result.set_status(phantom.APP_SUCCESS) + + def _handle_remove_category_ip(self, param): + self.save_progress("In action handler for: {0}".format(self.get_action_identifier())) + action_result = self.add_action_result(ActionResult(dict(param))) + category_id = param["category_id"] + + ret_val, category_details = self._get_category_details(category_id, action_result) + if phantom.is_fail(ret_val): + return action_result.get_status() + + ips = param.get("ips", "") + ips_to_remove = [item.strip() for item in ips.split(",") if item.strip()] + retaining_parent_category_ips = param.get("retaining-parent-category-ip", "") + parent_ips_to_remove = [item.strip() for item in retaining_parent_category_ips.split(",") if item.strip()] + + ret_val, response = self._remove_from_category(ips_to_remove, parent_ips_to_remove, category_details, category_id, action_result) + + if phantom.is_fail(ret_val): + return action_result.get_status() + + action_result.add_data(response) + summary = action_result.update_summary({}) + summary["message"] = "Category ips removed" + + return action_result.set_status(phantom.APP_SUCCESS) + + def _remove_from_category(self, data, parent_data, cat_details, category_id, action_result): + data_set = set(data) + new_data = [] + for point in cat_details.get("urls", []): + if point not in data_set: + new_data.append(point) + + parent_data_set = set(parent_data) + new_parent_data = [] + for point in cat_details.get("dbCategorizedUrls", []): + if point not in parent_data_set: + new_parent_data.append(point) + + cat_details["urls"] = new_data + cat_details["dbCategorizedUrls"] = new_parent_data + + ret_val, response = self._make_rest_call_helper(f"/api/v1/urlCategories/{category_id}", action_result, data=cat_details, method="put") + return ret_val, response + + def _handle_remove_category_url(self, param): + self.save_progress("In action handler for: {0}".format(self.get_action_identifier())) + action_result = self.add_action_result(ActionResult(dict(param))) + category_id = param["category_id"] + + ret_val, category_details = self._get_category_details(category_id, action_result) + if phantom.is_fail(ret_val): + return action_result.get_status() + + urls = param.get("urls", "") + urls_to_remove = [item.strip() for item in urls.split(",") if item.strip()] + retaining_parent_category_url = param.get("retaining-parent-category-url", "") + parent_urls_to_remove = [item.strip() for item in retaining_parent_category_url.split(",") if item.strip()] + + ret_val, response = self._remove_from_category(urls_to_remove, parent_urls_to_remove, category_details, category_id, action_result) + + if phantom.is_fail(ret_val): + return action_result.get_status() + + action_result.add_data(response) + summary = action_result.update_summary({}) + summary["message"] = "Category urls removed" + + return action_result.set_status(phantom.APP_SUCCESS) + + def _handle_create_destination_group(self, param): + """ + This action is used to create an IP Destination Group + :param name: IP destination group name + :param type: IP destination group type + :param addresses: Destination IP addresses, FQDNs, or wildcard FQDNs + :param description: Additional information about the destination IP group + :param ip_categories: Destination IP address URL categories + :param countries: Destination IP address countries + :return: status phantom.APP_ERROR/phantom.APP_SUCCESS(along with appropriate message) + """ + self.save_progress("In action handler for: {0}".format(self.get_action_identifier())) + action_result = self.add_action_result(ActionResult(dict(param))) + + addresses = param.get("addresses", "") + ip_categories = param.get("ip_categories", "") + countries = param.get("countries", "") + + data = {} + data["name"] = param["name"] + data["type"] = param["type"] + if addresses: + data["addresses"] = [item.strip() for item in addresses.split(",")] + data["description"] = param.get("description", "") + if ip_categories: + data["ipCategories"] = [item.strip() for item in ip_categories.split(",")] + if countries: + data["countries"] = [item.strip() for item in countries.split(",")] + + ret_val, response = self._make_rest_call_helper("/api/v1/ipDestinationGroups", action_result, data=data, method="post") + if phantom.is_fail(ret_val): + return action_result.get_status() + + action_result.add_data(response) + summary = action_result.update_summary({}) + summary["message"] = "Destination Group Created" + + return action_result.set_status(phantom.APP_SUCCESS) + + def _get_destination_group(self, id, action_result, exclude_type=None, category_type=None, lite=False): + + ret_val, response = self._make_rest_call_helper(f"/api/v1/ipDestinationGroups/{id}", action_result) + if phantom.is_fail(ret_val): + return action_result.get_status(), None + + group_type = response["type"] + + if group_type == exclude_type: + return phantom.APP_SUCCESS, None + + if lite: + if category_type and group_type not in category_type: + return phantom.APP_SUCCESS, None + + lite_resp = {"id": response["id"], "name": response["name"], "type": group_type} + return phantom.APP_SUCCESS, lite_resp + + return phantom.APP_SUCCESS, response + + def _get_batched_groups(self, endpoint, params, action_result): + limit = params["pageSize"] + + while True: + params["pageSize"] = min(limit, ZSCALER_MAX_PAGESIZE) + ret_val, get_groups = self._make_rest_call_helper("/api/v1" + endpoint, action_result, params=params) + self.debug_print("get groups is {0}".format(get_groups)) + if phantom.is_fail(ret_val): + return action_result.get_status() + for group in get_groups: + if "extensions" in group: + extensions = group.pop("extensions") + for key in extensions: + group[key] = extensions[key] + action_result.add_data(group) + limit = limit - params["pageSize"] + if limit <= 0 or len(get_groups) == 0: + break + params["page"] += 1 + + return phantom.APP_SUCCESS + + def _handle_list_destination_group(self, param): + """ + This action is used to list IP Destination Groups + :param ip_group_ids: Destination groups to retrieve + :param exclude_type: Group types to exclude from search + :param category_type: Destination types to filter by + :param limit: Number of groups to retrieve + :param lite: Retrieve limited information for each group + :return: status phantom.APP_ERROR/phantom.APP_SUCCESS(along with appropriate message) + """ + self.save_progress("In action handler for: {0}".format(self.get_action_identifier())) + action_result = self.add_action_result(ActionResult(dict(param))) + + ip_group_ids = param.get("ip_group_ids", "") + ip_ids_lst = [item.strip() for item in ip_group_ids.split(",") if item.strip()] + exclude_type = param.get("exclude_type", "") + category_type = param.get("category_type", "") + category_type_list = [item.strip() for item in category_type.split(",") if item.strip()] + limit = param.get("limit", 50) + lite = param.get("lite", False) + + params = {} + endpoint = "/ipDestinationGroups" + params["excludeType"] = exclude_type + self.debug_print("ip id list {0}".format(ip_ids_lst)) + if ip_ids_lst: + for ip in ip_ids_lst: + ret_val, response = self._get_destination_group(ip, action_result, exclude_type, category_type, lite) + if phantom.is_fail(ret_val): + return action_result.get_status() + action_result.add_data(response) + + summary = action_result.update_summary({}) + summary["message"] = "Destination groups retrieved" + return action_result.set_status(phantom.APP_SUCCESS) + + elif lite: + endpoint = "/ipDestinationGroups/lite" + params["type"] = category_type_list + + params["page"] = 1 + params["pageSize"] = limit + ret_val = self._get_batched_groups(endpoint, params, action_result) + if phantom.is_fail(ret_val): + return action_result.get_status() + + # action_result.add_data(destination_groups) + summary = action_result.update_summary({}) + summary["message"] = "Destination groups retrieved" + return action_result.set_status(phantom.APP_SUCCESS) + + def _handle_edit_destination_group(self, param): + """ + This action is used to edit an IP Destination Group + :param ip_group_id: Id of destination group to edit + :param name: IP destination group name + :param addresses: Destination IP addresses, FQDNs, or wildcard FQDNs + :param description: Additional information about the destination IP group + :param ip_categories: Destination IP address URL categories + :param countries: Destination IP address countries + :param is_non_editable: If set to true, the destination IP address group is non-editable + :return: status phantom.APP_ERROR/phantom.APP_SUCCESS(along with appropriate message) + """ + self.save_progress("In action handler for: {0}".format(self.get_action_identifier())) + action_result = self.add_action_result(ActionResult(dict(param))) + + group_id = param["ip_group_id"] + + ret_val, group_resp = self._get_destination_group(group_id, action_result) + if phantom.is_fail(ret_val): + return action_result.get_status() + group_resp["name"] = param.get("name", group_resp["name"]) + if param.get("addresses"): + new_addresses = [item.strip() for item in param.get("addresses", "").split(",") if item.strip()] + group_resp["addresses"] = new_addresses + group_resp["description"] = param.get("description", group_resp["description"]) + if param.get("ip_categories"): + new_ip_categories = [item.strip() for item in param.get("ip_categories", "").split(",") if item.strip()] + group_resp["ipCategories"] = new_ip_categories + if param.get("countries"): + new_countries = [item.strip() for item in param.get("countries", "").split(",") if item.strip()] + group_resp["countries"] = new_countries + group_resp["isNonEditable"] = param.get("is_non_editable", False) + + ret_val, response = self._make_rest_call_helper(f"/api/v1/ipDestinationGroups/{group_id}", action_result, data=group_resp, method="put") + if phantom.is_fail(ret_val): + return action_result.get_status() + + action_result.add_data(response) + summary = action_result.update_summary({}) + summary["message"] = "Destination Group Edited" + + return action_result.set_status(phantom.APP_SUCCESS) + + def _handle_delete_destination_group(self, param): + """ + This action is used to delete IP Destination Groups + :param ip_group_ids: Ids of destination group to delete + :return: status phantom.APP_ERROR/phantom.APP_SUCCESS(along with appropriate message) + """ + self.save_progress("In action handler for: {0}".format(self.get_action_identifier())) + action_result = self.add_action_result(ActionResult(dict(param))) + + group_ids = param.get("ip_group_ids", "") + list_group_ids = [item.strip() for item in group_ids.split(",") if item.strip()] + + for group_id in list_group_ids: + ret_val, response = self._make_rest_call_helper(f"/api/v1/ipDestinationGroups/{group_id}", action_result, method="delete") + if phantom.is_fail(ret_val): + return action_result.get_status() + action_result.add_data({"ip_group_id": group_id}) + + summary = action_result.update_summary({}) + summary["message"] = "Destination groups deleted" + return action_result.set_status(phantom.APP_SUCCESS) + + def _handle_get_category_details(self, param): + """ + This action is used to get category details of specfic categories + :param category_ids: Ids of category's to query + :return: status phantom.APP_ERROR/phantom.APP_SUCCESS(along with appropriate message) + """ + self.save_progress("In action handler for: {0}".format(self.get_action_identifier())) + action_result = self.add_action_result(ActionResult(dict(param))) + + group_ids = param.get("category_ids", "") + list_category_ids = [item.strip() for item in group_ids.split(",") if item.strip()] + + for category_id in list_category_ids: + ret_val, category_details = self._get_category_details(category_id, action_result) + if phantom.is_fail(ret_val): + return action_result.get_status() + action_result.add_data(category_details) + + summary = action_result.update_summary({}) + summary["message"] = "Category details recieved" + summary["total_categories"] = action_result.get_data_size() + return action_result.set_status(phantom.APP_SUCCESS) + + def _handle_get_departments(self, param): + """ + This action is used to get departments + :param name: Filter by department name + :param page: Specifies the page offset + :param pageSize: Specifies the page size. Defaul is 100 + :return: status phantom.APP_ERROR/phantom.APP_SUCCESS(along with appropriate message) + """ + self.save_progress("In action handler for: {0}".format(self.get_action_identifier())) + action_result = self.add_action_result(ActionResult(dict(param))) + + name = param.get("name") + page_size = param.get("pageSize") + page_num = param.get("page", 1) + + endpoint = f"/api/v1/departments?page={page_num}&pageSize={page_size}" + + if name: + endpoint = f"/api/v1/departments?page={page_num}&pageSize={page_size}&search={name}&limitSearch=true" + + ret_val, response = self._make_rest_call_helper(endpoint, action_result) + + if phantom.is_fail(ret_val): + return action_result.get_status() + + for department in response: + action_result.add_data(department) + + summary = action_result.update_summary({}) + summary["message"] = "Departments retrieved" + summary["total_deparments"] = action_result.get_data_size() + return action_result.set_status(phantom.APP_SUCCESS) + + def _handle_additional_actions(self, action_id, param): + ret_val = phantom.APP_SUCCESS + + if action_id == "create_destination_group": + ret_val = self._handle_create_destination_group(param) + + elif action_id == "list_destination_group": + ret_val = self._handle_list_destination_group(param) + + elif action_id == "edit_destination_group": + ret_val = self._handle_edit_destination_group(param) + + elif action_id == "delete_destination_group": + ret_val = self._handle_delete_destination_group(param) + + elif action_id == "get_departments": + ret_val = self._handle_get_departments(param) + + elif action_id == "get_category_details": + ret_val = self._handle_get_category_details(param) + + return ret_val + def handle_action(self, param): ret_val = phantom.APP_SUCCESS @@ -999,63 +1462,88 @@ def handle_action(self, param): self.debug_print("action_id", self.get_action_identifier()) - if action_id == 'test_connectivity': + if action_id == "test_connectivity": ret_val = self._handle_test_connectivity(param) - elif action_id == 'list_url_categories': + elif action_id == "list_url_categories": ret_val = self._handle_list_url_categories(param) - elif action_id == 'get_report': + elif action_id == "get_report": ret_val = self._handle_get_report(param) - elif action_id == 'block_ip': + elif action_id == "block_ip": ret_val = self._handle_block_ip(param) - elif action_id == 'block_url': + elif action_id == "block_url": ret_val = self._handle_block_url(param) - elif action_id == 'unblock_ip': + elif action_id == "unblock_ip": ret_val = self._handle_unblock_ip(param) - elif action_id == 'unblock_url': + elif action_id == "unblock_url": ret_val = self._handle_unblock_url(param) - elif action_id == 'allow_ip': + elif action_id == "allow_ip": ret_val = self._handle_allow_ip(param) - elif action_id == 'allow_url': + elif action_id == "allow_url": ret_val = self._handle_allow_url(param) - elif action_id == 'unallow_ip': + elif action_id == "unallow_ip": ret_val = self._handle_unallow_ip(param) - elif action_id == 'unallow_url': + elif action_id == "unallow_url": ret_val = self._handle_unallow_url(param) - elif action_id == 'lookup_ip': + elif action_id == "lookup_ip": ret_val = self._handle_lookup_ip(param) - elif action_id == 'lookup_url': + elif action_id == "lookup_url": ret_val = self._handle_lookup_url(param) - elif action_id == 'submit_file': + elif action_id == "submit_file": ret_val = self._handle_submit_file(param) - elif action_id == 'get_admin_users': + elif action_id == "get_admin_users": ret_val = self._handle_get_admin_users(param) - elif action_id == 'get_users': + elif action_id == "get_users": ret_val = self._handle_get_users(param) - elif action_id == 'get_groups': + elif action_id == "get_groups": ret_val = self._handle_get_groups(param) - elif action_id == 'add_group_user': + elif action_id == "add_group_user": ret_val = self._handle_add_group_user(param) - elif action_id == 'remove_group_user': + elif action_id == "remove_group_user": ret_val = self._handle_remove_group_user(param) + elif action_id == "get_allowlist": + ret_val = self._handle_get_allowlist(param) + + elif action_id == "get_denylist": + ret_val = self._handle_get_denylist(param) + + elif action_id == "update_user": + ret_val = self._handle_update_user(param) + + elif action_id == "add_category_url": + ret_val = self._handle_add_category_url(param) + + elif action_id == "add_category_ip": + ret_val = self._handle_add_category_ip(param) + + elif action_id == "remove_category_url": + ret_val = self._handle_remove_category_url(param) + + elif action_id == "remove_category_ip": + ret_val = self._handle_remove_category_ip(param) + + else: + # passing the action handling to another function to decrease FLAKE_8 complexity + ret_val = self._handle_additional_actions(action_id, param) + return ret_val def initialize(self): @@ -1068,17 +1556,17 @@ def initialize(self): self._state = {"app_version": self.get_app_json().get("app_version")} config = self.get_config() - self._base_url = config['base_url'].rstrip('/') - self._username = config['username'] - self._password = config['password'] - self._api_key = config['api_key'] - self._sandbox_base_url = config.get('sandbox_base_url', None) + self._base_url = config["base_url"].rstrip("/") + self._username = config["username"] + self._password = config["password"] + self._api_key = config["api_key"] + self._sandbox_base_url = config.get("sandbox_base_url", None) if self._sandbox_base_url: - self._sandbox_base_url = self._sandbox_base_url.rstrip('/') - self._sandbox_api_token = config.get('sandbox_api_token', None) + self._sandbox_base_url = self._sandbox_base_url.rstrip("/") + self._sandbox_api_token = config.get("sandbox_api_token", None) self._headers = {} - self._retry_rest_call = True - self.set_validator('ipv6', self._is_ip) + self._retry_rest_call = 5 + self.set_validator("ipv6", self._is_ip) return self._init_session() @@ -1088,7 +1576,7 @@ def finalize(self): return self._deinit_session() -if __name__ == '__main__': +if __name__ == "__main__": import argparse import sys @@ -1099,35 +1587,33 @@ def finalize(self): argparser = argparse.ArgumentParser() - argparser.add_argument('input_test_json', help='Input Test JSON file') - argparser.add_argument('-u', '--username', help='username', required=False) - argparser.add_argument('-p', '--password', help='password', required=False) - argparser.add_argument('-v', '--verify', action='store_true', help='verify', required=False, default=False) + argparser.add_argument("input_test_json", help="Input Test JSON file") + argparser.add_argument("-u", "--username", help="username", required=False) + argparser.add_argument("-p", "--password", help="password", required=False) + argparser.add_argument("-v", "--verify", action="store_true", help="verify", required=False, default=False) args = argparser.parse_args() verify = args.verify session_id = None - if (args.username and args.password): + if args.username and args.password: login_url = BaseConnector._get_phantom_base_url() + "login" try: print("Accessing the Login page") - r = requests.get( - login_url, verify=verify, timeout=ZSCALER_DEFAULT_TIMEOUT) - csrftoken = r.cookies['csrftoken'] - data = {'username': args.username, 'password': args.password, 'csrfmiddlewaretoken': csrftoken} - headers = {'Cookie': 'csrftoken={0}'.format(csrftoken), 'Referer': login_url} + r = requests.get(login_url, verify=verify, timeout=ZSCALER_DEFAULT_TIMEOUT) + csrftoken = r.cookies["csrftoken"] + data = {"username": args.username, "password": args.password, "csrfmiddlewaretoken": csrftoken} + headers = {"Cookie": "csrftoken={0}".format(csrftoken), "Referer": login_url} print("Logging into Platform to get the session id") - r2 = requests.post( - login_url, verify=verify, data=data, headers=headers, timeout=ZSCALER_DEFAULT_TIMEOUT) - session_id = r2.cookies['sessionid'] + r2 = requests.post(login_url, verify=verify, data=data, headers=headers, timeout=ZSCALER_DEFAULT_TIMEOUT) + session_id = r2.cookies["sessionid"] except Exception as e: print(("Unable to get session id from the platform. Error: {0}".format(str(e)))) sys.exit(1) - if (len(sys.argv) < 2): + if len(sys.argv) < 2: print("No test json specified as input") sys.exit(0) @@ -1139,8 +1625,8 @@ def finalize(self): connector = ZscalerConnector() connector.print_progress_message = True - if (session_id is not None): - in_json['user_session_token'] = session_id + if session_id is not None: + in_json["user_session_token"] = session_id ret_val = connector._handle_action(json.dumps(in_json), None) print(json.dumps(json.loads(ret_val), indent=4)) diff --git a/zscaler_consts.py b/zscaler_consts.py index d34a5c4..ae4caac 100644 --- a/zscaler_consts.py +++ b/zscaler_consts.py @@ -1,6 +1,6 @@ # File: zscaler_consts.py # -# Copyright (c) 2017-2023 Splunk Inc. +# Copyright (c) 2017-2024 Splunk Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -12,9 +12,9 @@ # the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, # either express or implied. See the License for the specific language governing permissions # and limitations under the License. -ZSCALER_JSON_FULL_DETAILS = 'Full Details' -ZSCLAER_ERR_MD5_UNKNOWN_MSG = 'md5 is unknown or analysis has yet not been completed' -ZSCALER_SANDBOX_GET_REPORT_MSG = 'Sandbox report successfully fetched for the provided md5 hash' +ZSCALER_JSON_FULL_DETAILS = "Full Details" +ZSCLAER_ERR_MD5_UNKNOWN_MSG = "md5 is unknown or analysis has yet not been completed" +ZSCALER_SANDBOX_GET_REPORT_MSG = "Sandbox report successfully fetched for the provided md5 hash" ZSCALER_SANDBOX_SUBMIT_FILE_MSG = "Successfully submitted the file to Sandbox" ZSCALER_ERR_MSG_UNAVAILABLE = "Error message unavailable. Please check the asset configuration and|or action parameters" ZSCALER_STATE_FILE_CORRUPT_ERR = ( @@ -24,7 +24,7 @@ ZSCALER_MAX_PAGESIZE = 1000 ZSCALER_DEFAULT_TIMEOUT = 30 -# Constants relating to '_validate_integer' +# Constants relating to "_validate_integer" ZSCALER_VALID_INTEGER_MSG = "Please provide a valid integer value in the {param}" ZSCALER_NON_NEGATIVE_INTEGER_MSG = "Please provide a valid non-negative integer value in the {param}" ZSCALER_POSITIVE_INTEGER_MSG = "Please provide a valid non-zero positive integer value in the {param}" diff --git a/zscaler_get_admin_users.html b/zscaler_get_admin_users.html index 0bad440..ac3779d 100644 --- a/zscaler_get_admin_users.html +++ b/zscaler_get_admin_users.html @@ -10,7 +10,7 @@ {% block widget_content %}