zscaler-sdk-python 0.10.3__tar.gz → 0.10.5__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/PKG-INFO +2 -2
- {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/pyproject.toml +4 -4
- {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/zscaler/__init__.py +1 -1
- {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/zscaler/zcc/__init__.py +115 -1
- {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/zscaler/zcc/client.py +11 -6
- {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/zscaler/zcc/devices.py +3 -1
- {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/zscaler/zia/__init__.py +195 -34
- {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/zscaler/zia/client.py +1 -1
- {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/zscaler/zia/cloud_apps.py +4 -2
- {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/zscaler/zia/isolation_profile.py +1 -2
- {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/zscaler/zia/labels.py +3 -5
- {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/zscaler/zia/locations.py +12 -14
- {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/zscaler/zia/pac_files.py +254 -50
- {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/zscaler/zia/traffic.py +7 -7
- {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/zscaler/zia/url_categories.py +77 -5
- {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/zscaler/zia/users.py +3 -5
- {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/zscaler/zia/workload_groups.py +2 -4
- {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/zscaler/zia/zpa_gateway.py +7 -3
- {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/zscaler/zpa/policies.py +12 -12
- {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/LICENSE.md +0 -0
- {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/README.md +0 -0
- {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/zscaler/cache/__init__.py +0 -0
- {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/zscaler/cache/cache.py +0 -0
- {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/zscaler/cache/no_op_cache.py +0 -0
- {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/zscaler/cache/zscaler_cache.py +0 -0
- {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/zscaler/constants.py +0 -0
- {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/zscaler/errors/__init__.py +0 -0
- {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/zscaler/errors/error.py +0 -0
- {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/zscaler/errors/http_error.py +0 -0
- {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/zscaler/errors/zscaler_api_error.py +0 -0
- {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/zscaler/exceptions/__init__.py +0 -0
- {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/zscaler/exceptions/exceptions.py +0 -0
- {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/zscaler/logger.py +0 -0
- {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/zscaler/ratelimiter/__init__.py +0 -0
- {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/zscaler/ratelimiter/ratelimiter.py +0 -0
- {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/zscaler/user_agent.py +0 -0
- {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/zscaler/utils.py +0 -0
- {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/zscaler/zcc/secrets.py +0 -0
- {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/zscaler/zcon/__init__.py +0 -0
- {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/zscaler/zcon/activation.py +0 -0
- {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/zscaler/zcon/admin_and_role_management.py +0 -0
- {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/zscaler/zcon/client.py +0 -0
- {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/zscaler/zcon/ecgroups.py +0 -0
- {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/zscaler/zcon/locations.py +0 -0
- {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/zscaler/zcon/provisioning.py +0 -0
- {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/zscaler/zdx/__init__.py +0 -0
- {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/zscaler/zdx/admin.py +0 -0
- {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/zscaler/zdx/alerts.py +0 -0
- {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/zscaler/zdx/apps.py +0 -0
- {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/zscaler/zdx/devices.py +0 -0
- {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/zscaler/zdx/filters.py +0 -0
- {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/zscaler/zdx/inventory.py +0 -0
- {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/zscaler/zdx/troubleshooting.py +0 -0
- {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/zscaler/zdx/users.py +0 -0
- {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/zscaler/zdx/zdx_client.py +0 -0
- {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/zscaler/zia/activate.py +0 -0
- {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/zscaler/zia/admin_and_role_management.py +0 -0
- {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/zscaler/zia/apptotal.py +0 -0
- {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/zscaler/zia/audit_logs.py +0 -0
- {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/zscaler/zia/authentication_settings.py +0 -0
- {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/zscaler/zia/cloudappcontrol.py +0 -0
- {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/zscaler/zia/device_management.py +0 -0
- {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/zscaler/zia/dlp.py +0 -0
- {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/zscaler/zia/errors.py +0 -0
- {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/zscaler/zia/firewall.py +0 -0
- {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/zscaler/zia/forwarding_control.py +0 -0
- {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/zscaler/zia/sandbox.py +0 -0
- {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/zscaler/zia/security.py +0 -0
- {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/zscaler/zia/ssl_inspection.py +0 -0
- {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/zscaler/zia/url_filtering.py +0 -0
- {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/zscaler/zia/web_dlp.py +0 -0
- {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/zscaler/zpa/README.md +0 -0
- {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/zscaler/zpa/__init__.py +0 -0
- {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/zscaler/zpa/app_segments.py +0 -0
- {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/zscaler/zpa/app_segments_inspection.py +0 -0
- {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/zscaler/zpa/app_segments_pra.py +0 -0
- {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/zscaler/zpa/authdomains.py +0 -0
- {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/zscaler/zpa/certificates.py +0 -0
- {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/zscaler/zpa/client.py +0 -0
- {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/zscaler/zpa/cloud_connector_groups.py +0 -0
- {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/zscaler/zpa/connectors.py +0 -0
- {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/zscaler/zpa/emergency_access.py +0 -0
- {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/zscaler/zpa/errors.py +0 -0
- {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/zscaler/zpa/idp.py +0 -0
- {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/zscaler/zpa/inspection.py +0 -0
- {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/zscaler/zpa/isolation.py +0 -0
- {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/zscaler/zpa/lss.py +0 -0
- {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/zscaler/zpa/machine_groups.py +0 -0
- {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/zscaler/zpa/microtenants.py +0 -0
- {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/zscaler/zpa/posture_profiles.py +0 -0
- {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/zscaler/zpa/privileged_remote_access.py +0 -0
- {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/zscaler/zpa/provisioning.py +0 -0
- {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/zscaler/zpa/saml_attributes.py +0 -0
- {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/zscaler/zpa/scim_attributes.py +0 -0
- {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/zscaler/zpa/scim_groups.py +0 -0
- {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/zscaler/zpa/segment_groups.py +0 -0
- {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/zscaler/zpa/server_groups.py +0 -0
- {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/zscaler/zpa/servers.py +0 -0
- {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/zscaler/zpa/service_edges.py +0 -0
- {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/zscaler/zpa/trusted_networks.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: zscaler-sdk-python
|
|
3
|
-
Version: 0.10.
|
|
3
|
+
Version: 0.10.5
|
|
4
4
|
Summary: Official Python SDK for the Zscaler Products (Beta)
|
|
5
5
|
Home-page: https://github.com/zscaler/zscaler-sdk-python
|
|
6
6
|
License: MIT
|
|
@@ -22,7 +22,6 @@ Classifier: Topic :: Security
|
|
|
22
22
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
23
23
|
Provides-Extra: dev
|
|
24
24
|
Requires-Dist: aenum ; extra == "dev"
|
|
25
|
-
Requires-Dist: aiohttp (>=3.11.11)
|
|
26
25
|
Requires-Dist: arrow
|
|
27
26
|
Requires-Dist: black (>=24.3.0) ; extra == "dev"
|
|
28
27
|
Requires-Dist: certifi (>=2024.2.2)
|
|
@@ -31,6 +30,7 @@ Requires-Dist: cryptography (>=43.0.0)
|
|
|
31
30
|
Requires-Dist: flake8
|
|
32
31
|
Requires-Dist: flatdict
|
|
33
32
|
Requires-Dist: idna (>=3.10)
|
|
33
|
+
Requires-Dist: jinja2 (>=3.1.6)
|
|
34
34
|
Requires-Dist: okta (>=2.9.7)
|
|
35
35
|
Requires-Dist: pycryptodomex (>=3.20.0)
|
|
36
36
|
Requires-Dist: pydash (>=8.0.3) ; extra == "dev"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "zscaler-sdk-python"
|
|
3
|
-
version = "0.10.
|
|
3
|
+
version = "0.10.5"
|
|
4
4
|
description = "Official Python SDK for the Zscaler Products (Beta)"
|
|
5
5
|
authors = ["Zscaler, Inc. <devrel@zscaler.com>"]
|
|
6
6
|
license = "MIT"
|
|
@@ -51,16 +51,16 @@ pytz = ">=2024.2"
|
|
|
51
51
|
black = ">=24.3.0"
|
|
52
52
|
cryptography = ">=43.0.0"
|
|
53
53
|
okta = ">=2.9.7"
|
|
54
|
-
|
|
54
|
+
jinja2 = ">=3.1.6"
|
|
55
55
|
|
|
56
56
|
[tool.poetry.dev-dependencies]
|
|
57
57
|
black = ">=24.3.0"
|
|
58
58
|
pytest = ">=8.3.1"
|
|
59
|
-
pytest-asyncio = "^0.
|
|
59
|
+
pytest-asyncio = "^0.25.3"
|
|
60
60
|
pytest-mock = "*"
|
|
61
61
|
pytest-recording = "^0.13.2"
|
|
62
62
|
pytest-cov = "*"
|
|
63
|
-
pyfakefs = ">=5.
|
|
63
|
+
pyfakefs = ">=5.7.4"
|
|
64
64
|
isort = "*"
|
|
65
65
|
wheel = ">=0.45.1"
|
|
66
66
|
sphinx = "^7.4.7"
|
|
@@ -5,7 +5,7 @@ import uuid
|
|
|
5
5
|
import time
|
|
6
6
|
import requests
|
|
7
7
|
from datetime import datetime, timedelta
|
|
8
|
-
|
|
8
|
+
from box import Box, BoxList
|
|
9
9
|
from zscaler import __version__
|
|
10
10
|
from zscaler.user_agent import UserAgent
|
|
11
11
|
from zscaler.utils import (
|
|
@@ -14,7 +14,9 @@ from zscaler.utils import (
|
|
|
14
14
|
format_json_response,
|
|
15
15
|
is_token_expired,
|
|
16
16
|
retry_with_backoff,
|
|
17
|
+
convert_keys_to_snake
|
|
17
18
|
)
|
|
19
|
+
from zscaler.ratelimiter.ratelimiter import RateLimiter
|
|
18
20
|
from zscaler.logger import setup_logging
|
|
19
21
|
from .devices import DevicesAPI
|
|
20
22
|
from .secrets import SecretsAPI
|
|
@@ -74,6 +76,14 @@ class ZCCClientHelper(ZCCClient):
|
|
|
74
76
|
self.url = f"https://api-mobile.{self._env_cloud}.net/papi/public/v1"
|
|
75
77
|
|
|
76
78
|
self.user_agent = UserAgent().get_user_agent_string() # Ensure this returns a string
|
|
79
|
+
# Initialize rate limiter
|
|
80
|
+
# You may want to adjust these parameters as per your rate limit configuration
|
|
81
|
+
self.rate_limiter = RateLimiter(
|
|
82
|
+
get_limit=2, # Adjust as per actual limit
|
|
83
|
+
post_put_delete_limit=2, # Adjust as per actual limit
|
|
84
|
+
get_freq=2, # Adjust as per actual frequency (in seconds)
|
|
85
|
+
post_put_delete_freq=2, # Adjust as per actual frequency (in seconds)
|
|
86
|
+
)
|
|
77
87
|
self.auth_token = None
|
|
78
88
|
self.headers = {}
|
|
79
89
|
self.refreshToken()
|
|
@@ -263,3 +273,107 @@ class ZCCClientHelper(ZCCClient):
|
|
|
263
273
|
def secrets(self):
|
|
264
274
|
"""The interface object for the :ref:`ZCC Secrets interface <zcc-secrets>`."""
|
|
265
275
|
return SecretsAPI(self)
|
|
276
|
+
|
|
277
|
+
def get_paginated_data(
|
|
278
|
+
self,
|
|
279
|
+
path=None,
|
|
280
|
+
expected_status_code=200,
|
|
281
|
+
page=None,
|
|
282
|
+
pagesize=None,
|
|
283
|
+
search=None,
|
|
284
|
+
):
|
|
285
|
+
"""
|
|
286
|
+
Fetches paginated data from the API based on specified parameters and handles pagination.
|
|
287
|
+
|
|
288
|
+
Args:
|
|
289
|
+
path (str): The API endpoint path to send requests to.
|
|
290
|
+
expected_status_code (int): The expected HTTP status code for a successful request.
|
|
291
|
+
page (int, optional): Specific page number to fetch (1-based).
|
|
292
|
+
pagesize (int, optional): Number of items per page, default is 100, max is 10,000.
|
|
293
|
+
search (str, optional): Search query to filter the results.
|
|
294
|
+
|
|
295
|
+
Returns:
|
|
296
|
+
tuple: A tuple containing:
|
|
297
|
+
- BoxList: A list of fetched items wrapped in a BoxList for easy access.
|
|
298
|
+
- str: An error message if any occurred during the data fetching process.
|
|
299
|
+
"""
|
|
300
|
+
logger = logging.getLogger(__name__)
|
|
301
|
+
|
|
302
|
+
ERROR_MESSAGES = {
|
|
303
|
+
"UNEXPECTED_STATUS": "Unexpected status code {status_code} received for page {page}.",
|
|
304
|
+
"EMPTY_RESULTS": "No results found for page {page}.",
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
# ✅ Ensure 'pageSize' is always converted to camelCase
|
|
308
|
+
params = {
|
|
309
|
+
"page": page if page is not None else 1, # Default is 1-based page
|
|
310
|
+
"pageSize": pagesize if pagesize is not None else 100, # Default is 100, max is 10,000
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
# Add optional filters to the params if provided
|
|
314
|
+
if search:
|
|
315
|
+
params["search"] = search
|
|
316
|
+
|
|
317
|
+
# If the user specifies a single page, fetch only that page
|
|
318
|
+
if page is not None:
|
|
319
|
+
response = self.send("GET", path=path, params=params)
|
|
320
|
+
if response.status_code != expected_status_code:
|
|
321
|
+
error_msg = ERROR_MESSAGES["UNEXPECTED_STATUS"].format(status_code=response.status_code, page=params["page"])
|
|
322
|
+
logger.error(error_msg)
|
|
323
|
+
return BoxList([]), error_msg
|
|
324
|
+
|
|
325
|
+
response_data = response.json()
|
|
326
|
+
if not isinstance(response_data, list):
|
|
327
|
+
error_msg = ERROR_MESSAGES["EMPTY_RESULTS"].format(page=params["page"])
|
|
328
|
+
logger.warn(error_msg)
|
|
329
|
+
return BoxList([]), error_msg
|
|
330
|
+
|
|
331
|
+
data = convert_keys_to_snake(response_data)
|
|
332
|
+
return BoxList(data), None
|
|
333
|
+
|
|
334
|
+
# If no page is specified, iterate through pages to fetch all items
|
|
335
|
+
ret_data = []
|
|
336
|
+
|
|
337
|
+
try:
|
|
338
|
+
while True:
|
|
339
|
+
should_wait, delay = self.rate_limiter.wait("GET")
|
|
340
|
+
if should_wait:
|
|
341
|
+
time.sleep(delay)
|
|
342
|
+
|
|
343
|
+
# Send the request to the API
|
|
344
|
+
response = self.send("GET", path=path, params=params)
|
|
345
|
+
|
|
346
|
+
# Check for unexpected status code
|
|
347
|
+
if response.status_code != expected_status_code:
|
|
348
|
+
error_msg = ERROR_MESSAGES["UNEXPECTED_STATUS"].format(
|
|
349
|
+
status_code=response.status_code, page=params["page"]
|
|
350
|
+
)
|
|
351
|
+
logger.error(error_msg)
|
|
352
|
+
return BoxList([]), error_msg
|
|
353
|
+
|
|
354
|
+
# Parse the response as a flat list of items
|
|
355
|
+
response_data = response.json()
|
|
356
|
+
if not isinstance(response_data, list):
|
|
357
|
+
error_msg = ERROR_MESSAGES["EMPTY_RESULTS"].format(page=params["page"])
|
|
358
|
+
logger.warn(error_msg)
|
|
359
|
+
return BoxList([]), error_msg
|
|
360
|
+
|
|
361
|
+
data = convert_keys_to_snake(response_data)
|
|
362
|
+
ret_data.extend(data)
|
|
363
|
+
|
|
364
|
+
# Stop if fewer items than pageSize are returned (indicating last page)
|
|
365
|
+
if len(data) < params["pageSize"]:
|
|
366
|
+
break
|
|
367
|
+
|
|
368
|
+
# Move to the next page
|
|
369
|
+
params["page"] += 1
|
|
370
|
+
|
|
371
|
+
finally:
|
|
372
|
+
time.sleep(2) # Ensure a delay between requests regardless of outcome
|
|
373
|
+
|
|
374
|
+
if not ret_data:
|
|
375
|
+
error_msg = ERROR_MESSAGES["EMPTY_RESULTS"].format(page=params["page"])
|
|
376
|
+
logger.warn(error_msg)
|
|
377
|
+
return BoxList([]), error_msg
|
|
378
|
+
|
|
379
|
+
return BoxList(ret_data), None
|
|
@@ -36,13 +36,18 @@ class ZCCClient:
|
|
|
36
36
|
pass
|
|
37
37
|
|
|
38
38
|
def get_paginated_data(
|
|
39
|
+
# self,
|
|
40
|
+
# path=None,
|
|
41
|
+
# params=None,
|
|
42
|
+
# search=None,
|
|
43
|
+
# search_field="name",
|
|
44
|
+
# page=None,
|
|
45
|
+
# pagesize=20,
|
|
39
46
|
self,
|
|
40
|
-
path=None,
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
page=None,
|
|
45
|
-
pagesize=20,
|
|
47
|
+
path: str = None,
|
|
48
|
+
data_key_name: str = None,
|
|
49
|
+
data_per_page: int = 500,
|
|
50
|
+
expected_status_code=200,
|
|
46
51
|
):
|
|
47
52
|
"""
|
|
48
53
|
Send a GET request to the ZCC API to fetch all pages of a resources.
|
|
@@ -148,7 +148,9 @@ class DevicesAPI:
|
|
|
148
148
|
else:
|
|
149
149
|
raise ValueError("Invalid os_type specified. Check the Zscaler documentation for valid os_type options.")
|
|
150
150
|
|
|
151
|
-
|
|
151
|
+
list, _ = self.rest.get_paginated_data(path="getDevices", **payload)
|
|
152
|
+
return list
|
|
153
|
+
# return self.rest.get("getDevices", **payload)
|
|
152
154
|
|
|
153
155
|
def remove_devices(self, force: bool = False, **kwargs):
|
|
154
156
|
"""
|
|
@@ -169,11 +169,21 @@ class ZIAClientHelper(ZIAClient):
|
|
|
169
169
|
return result.group(1)
|
|
170
170
|
|
|
171
171
|
def is_session_expired(self):
|
|
172
|
+
"""
|
|
173
|
+
Checks whether the current session is expired.
|
|
174
|
+
|
|
175
|
+
Returns:
|
|
176
|
+
bool: True if the session is expired or if the session details are missing.
|
|
177
|
+
"""
|
|
172
178
|
if self.auth_details is None:
|
|
173
179
|
return True
|
|
180
|
+
|
|
174
181
|
now = datetime.datetime.now()
|
|
175
|
-
|
|
182
|
+
|
|
183
|
+
password_expiry_time = self.auth_details.get("passwordExpiryTime", -1)
|
|
184
|
+
if password_expiry_time > 0 and (self.session_refreshed - self.session_timeout_offset < now):
|
|
176
185
|
return True
|
|
186
|
+
|
|
177
187
|
return False
|
|
178
188
|
|
|
179
189
|
@retry_with_backoff(MAX_RETRIES)
|
|
@@ -401,6 +411,145 @@ class ZIAClientHelper(ZIAClient):
|
|
|
401
411
|
time.sleep(delay)
|
|
402
412
|
return self.send("DELETE", path, json, params)
|
|
403
413
|
|
|
414
|
+
# def get_paginated_data(
|
|
415
|
+
# self,
|
|
416
|
+
# path=None,
|
|
417
|
+
# expected_status_code=200,
|
|
418
|
+
# page=None,
|
|
419
|
+
# pagesize=None,
|
|
420
|
+
# search=None,
|
|
421
|
+
# max_items=None, # Maximum number of items to retrieve across pages
|
|
422
|
+
# max_pages=None, # Maximum number of pages to retrieve
|
|
423
|
+
# type=None, # Specify type of VPN credentials (CN, IP, UFQDN, XAUTH)
|
|
424
|
+
# include_only_without_location=None, # Include only VPN credentials not associated with any location
|
|
425
|
+
# location_id=None, # VPN credentials for a specific location ID
|
|
426
|
+
# managed_by=None, # VPN credentials managed by a given partner
|
|
427
|
+
# prefix=None, # VPN credentials managed by a given partner
|
|
428
|
+
# ):
|
|
429
|
+
# """
|
|
430
|
+
# Fetches paginated data from the API based on specified parameters and handles pagination.
|
|
431
|
+
|
|
432
|
+
# Args:
|
|
433
|
+
# path (str): The API endpoint path to send requests to.
|
|
434
|
+
# expected_status_code (int): The expected HTTP status code for a successful request. Defaults to 200.
|
|
435
|
+
# page (int): Specific page number to fetch. Defaults to 1 if not provided.
|
|
436
|
+
# pagesize (int): Number of items per page, default is 100, with a maximum of 10000.
|
|
437
|
+
# search (str): Search query to filter the results.
|
|
438
|
+
# max_items (int): Maximum number of items to retrieve.
|
|
439
|
+
# max_pages (int): Maximum number of pages to fetch.
|
|
440
|
+
# type (str, optional): Type of VPN credentials (e.g., CN, IP, UFQDN, XAUTH).
|
|
441
|
+
# include_only_without_location (bool, optional): Filter to include only VPN credentials not associated with a location.
|
|
442
|
+
# location_id (int, optional): Retrieve VPN credentials for the specified location ID.
|
|
443
|
+
# managed_by (int, optional): Retrieve VPN credentials managed by the specified partner.
|
|
444
|
+
# prefix (int, optional): Retrieve VPN credentials managed by a given partner.
|
|
445
|
+
|
|
446
|
+
# Returns:
|
|
447
|
+
# tuple: A tuple containing:
|
|
448
|
+
# - BoxList: A list of fetched items wrapped in a BoxList for easy access.
|
|
449
|
+
# - str: An error message if any occurred during the data fetching process.
|
|
450
|
+
# """
|
|
451
|
+
# logger = logging.getLogger(__name__)
|
|
452
|
+
|
|
453
|
+
# ERROR_MESSAGES = {
|
|
454
|
+
# "UNEXPECTED_STATUS": "Unexpected status code {status_code} received for page {page}.",
|
|
455
|
+
# "EMPTY_RESULTS": "No results found for page {page}.",
|
|
456
|
+
# }
|
|
457
|
+
|
|
458
|
+
# # Initialize parameters
|
|
459
|
+
# params = {
|
|
460
|
+
# "page": page if page is not None else 1, # Start at page 1 if not specified
|
|
461
|
+
# "pageSize": pagesize if pagesize is not None else 100, # Allow any user-defined pagesize
|
|
462
|
+
# }
|
|
463
|
+
|
|
464
|
+
# # Add optional filters to the params if provided
|
|
465
|
+
# if search:
|
|
466
|
+
# params["search"] = search
|
|
467
|
+
# if type:
|
|
468
|
+
# params["type"] = type
|
|
469
|
+
# if include_only_without_location is not None:
|
|
470
|
+
# params["includeOnlyWithoutLocation"] = include_only_without_location
|
|
471
|
+
# if location_id:
|
|
472
|
+
# params["locationId"] = location_id
|
|
473
|
+
# if managed_by:
|
|
474
|
+
# params["managedBy"] = managed_by
|
|
475
|
+
# if prefix:
|
|
476
|
+
# params["prefix"] = prefix
|
|
477
|
+
|
|
478
|
+
# # If the user specifies a page, fetch only that page
|
|
479
|
+
# if page is not None:
|
|
480
|
+
# response = self.send("GET", path=path, params=params)
|
|
481
|
+
# if response.status_code != expected_status_code:
|
|
482
|
+
# error_msg = ERROR_MESSAGES["UNEXPECTED_STATUS"].format(status_code=response.status_code, page=params["page"])
|
|
483
|
+
# logger.error(error_msg)
|
|
484
|
+
# return BoxList([]), error_msg
|
|
485
|
+
|
|
486
|
+
# response_data = response.json()
|
|
487
|
+
# if not isinstance(response_data, list):
|
|
488
|
+
# error_msg = ERROR_MESSAGES["EMPTY_RESULTS"].format(page=params["page"])
|
|
489
|
+
# logger.warn(error_msg)
|
|
490
|
+
# return BoxList([]), error_msg
|
|
491
|
+
|
|
492
|
+
# data = convert_keys_to_snake(response_data)
|
|
493
|
+
# return BoxList(data), None
|
|
494
|
+
|
|
495
|
+
# # If no page is specified, iterate through pages to fetch all items
|
|
496
|
+
# ret_data = []
|
|
497
|
+
# total_collected = 0
|
|
498
|
+
# try:
|
|
499
|
+
# while True:
|
|
500
|
+
# should_wait, delay = self.rate_limiter.wait("GET")
|
|
501
|
+
# if should_wait:
|
|
502
|
+
# time.sleep(delay)
|
|
503
|
+
|
|
504
|
+
# # Send the request to the API
|
|
505
|
+
# response = self.send("GET", path=path, params=params)
|
|
506
|
+
|
|
507
|
+
# # Check for unexpected status code
|
|
508
|
+
# if response.status_code != expected_status_code:
|
|
509
|
+
# error_msg = ERROR_MESSAGES["UNEXPECTED_STATUS"].format(
|
|
510
|
+
# status_code=response.status_code, page=params["page"]
|
|
511
|
+
# )
|
|
512
|
+
# logger.error(error_msg)
|
|
513
|
+
# return BoxList([]), error_msg
|
|
514
|
+
|
|
515
|
+
# # Parse the response as a flat list of items
|
|
516
|
+
# response_data = response.json()
|
|
517
|
+
# if not isinstance(response_data, list):
|
|
518
|
+
# error_msg = ERROR_MESSAGES["EMPTY_RESULTS"].format(page=params["page"])
|
|
519
|
+
# logger.warn(error_msg)
|
|
520
|
+
# return BoxList([]), error_msg
|
|
521
|
+
|
|
522
|
+
# data = convert_keys_to_snake(response_data)
|
|
523
|
+
|
|
524
|
+
# # Limit data collection based on max_items
|
|
525
|
+
# if max_items is not None:
|
|
526
|
+
# data = data[: max_items - total_collected] # Limit items on the current page
|
|
527
|
+
# ret_data.extend(data)
|
|
528
|
+
# total_collected += len(data)
|
|
529
|
+
|
|
530
|
+
# # Check if we've reached max_items or max_pages limits
|
|
531
|
+
# if (max_items is not None and total_collected >= max_items) or (
|
|
532
|
+
# max_pages is not None and params["page"] >= max_pages
|
|
533
|
+
# ):
|
|
534
|
+
# break
|
|
535
|
+
|
|
536
|
+
# # Stop if fewer items than pageSize are returned
|
|
537
|
+
# if len(data) < params["pageSize"]:
|
|
538
|
+
# break
|
|
539
|
+
|
|
540
|
+
# # Move to the next page
|
|
541
|
+
# params["page"] += 1
|
|
542
|
+
|
|
543
|
+
# finally:
|
|
544
|
+
# time.sleep(2) # Ensure a delay between requests regardless of outcome
|
|
545
|
+
|
|
546
|
+
# if not ret_data:
|
|
547
|
+
# error_msg = ERROR_MESSAGES["EMPTY_RESULTS"].format(page=params["page"])
|
|
548
|
+
# logger.warn(error_msg)
|
|
549
|
+
# return BoxList([]), error_msg
|
|
550
|
+
|
|
551
|
+
# return BoxList(ret_data), None
|
|
552
|
+
|
|
404
553
|
def get_paginated_data(
|
|
405
554
|
self,
|
|
406
555
|
path=None,
|
|
@@ -410,23 +559,27 @@ class ZIAClientHelper(ZIAClient):
|
|
|
410
559
|
search=None,
|
|
411
560
|
max_items=None, # Maximum number of items to retrieve across pages
|
|
412
561
|
max_pages=None, # Maximum number of pages to retrieve
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
562
|
+
page_number=None, # New: Zero-based page number
|
|
563
|
+
limit=None, # New: Max number of items per page (default: 1000)
|
|
564
|
+
type=None,
|
|
565
|
+
include_only_without_location=None,
|
|
566
|
+
location_id=None,
|
|
567
|
+
managed_by=None,
|
|
568
|
+
prefix=None,
|
|
418
569
|
):
|
|
419
570
|
"""
|
|
420
571
|
Fetches paginated data from the API based on specified parameters and handles pagination.
|
|
421
572
|
|
|
422
573
|
Args:
|
|
423
574
|
path (str): The API endpoint path to send requests to.
|
|
424
|
-
expected_status_code (int): The expected HTTP status code for a successful request.
|
|
425
|
-
page (int): Specific page number to fetch
|
|
426
|
-
pagesize (int): Number of items per page, default is 100,
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
575
|
+
expected_status_code (int): The expected HTTP status code for a successful request.
|
|
576
|
+
page (int, optional): Specific page number to fetch (1-based).
|
|
577
|
+
pagesize (int, optional): Number of items per page, default is 100, max is 10,000.
|
|
578
|
+
page_number (int, optional): Specifies the page number (zero-based, starting at 0).
|
|
579
|
+
limit (int, optional): Specifies the maximum number of items retrieved per page (default 1000).
|
|
580
|
+
search (str, optional): Search query to filter the results.
|
|
581
|
+
max_items (int, optional): Maximum number of items to retrieve across pages.
|
|
582
|
+
max_pages (int, optional): Maximum number of pages to retrieve.
|
|
430
583
|
type (str, optional): Type of VPN credentials (e.g., CN, IP, UFQDN, XAUTH).
|
|
431
584
|
include_only_without_location (bool, optional): Filter to include only VPN credentials not associated with a location.
|
|
432
585
|
location_id (int, optional): Retrieve VPN credentials for the specified location ID.
|
|
@@ -445,11 +598,18 @@ class ZIAClientHelper(ZIAClient):
|
|
|
445
598
|
"EMPTY_RESULTS": "No results found for page {page}.",
|
|
446
599
|
}
|
|
447
600
|
|
|
448
|
-
# Initialize parameters
|
|
449
|
-
params = {
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
601
|
+
# Initialize parameters based on what the user has provided
|
|
602
|
+
params = {}
|
|
603
|
+
|
|
604
|
+
# ✅ Use new API pagination model (pageNumber, limit) if provided
|
|
605
|
+
if page_number is not None or limit is not None:
|
|
606
|
+
params["pageNumber"] = page_number if page_number is not None else 0 # Default is 0
|
|
607
|
+
params["limit"] = limit if limit is not None else 1000 # Default is 1000
|
|
608
|
+
|
|
609
|
+
# ✅ Otherwise, use existing pagination parameters (page, pageSize)
|
|
610
|
+
else:
|
|
611
|
+
params["page"] = page if page is not None else 1 # Default is 1-based page
|
|
612
|
+
params["pageSize"] = pagesize if pagesize is not None else 100 # Default is 100, max is 10,000
|
|
453
613
|
|
|
454
614
|
# Add optional filters to the params if provided
|
|
455
615
|
if search:
|
|
@@ -465,19 +625,17 @@ class ZIAClientHelper(ZIAClient):
|
|
|
465
625
|
if prefix:
|
|
466
626
|
params["prefix"] = prefix
|
|
467
627
|
|
|
468
|
-
# If the user specifies a page, fetch only that page
|
|
469
|
-
if page is not None:
|
|
628
|
+
# If the user specifies a single page, fetch only that page
|
|
629
|
+
if page is not None or page_number is not None:
|
|
470
630
|
response = self.send("GET", path=path, params=params)
|
|
471
631
|
if response.status_code != expected_status_code:
|
|
472
|
-
error_msg = ERROR_MESSAGES["UNEXPECTED_STATUS"].format(
|
|
473
|
-
status_code=response.status_code, page=params["page"]
|
|
474
|
-
)
|
|
632
|
+
error_msg = ERROR_MESSAGES["UNEXPECTED_STATUS"].format(status_code=response.status_code, page=params.get("page", params.get("pageNumber", 0)))
|
|
475
633
|
logger.error(error_msg)
|
|
476
634
|
return BoxList([]), error_msg
|
|
477
635
|
|
|
478
636
|
response_data = response.json()
|
|
479
637
|
if not isinstance(response_data, list):
|
|
480
|
-
error_msg = ERROR_MESSAGES["EMPTY_RESULTS"].format(page=params
|
|
638
|
+
error_msg = ERROR_MESSAGES["EMPTY_RESULTS"].format(page=params.get("page", params.get("pageNumber", 0)))
|
|
481
639
|
logger.warn(error_msg)
|
|
482
640
|
return BoxList([]), error_msg
|
|
483
641
|
|
|
@@ -487,6 +645,7 @@ class ZIAClientHelper(ZIAClient):
|
|
|
487
645
|
# If no page is specified, iterate through pages to fetch all items
|
|
488
646
|
ret_data = []
|
|
489
647
|
total_collected = 0
|
|
648
|
+
|
|
490
649
|
try:
|
|
491
650
|
while True:
|
|
492
651
|
should_wait, delay = self.rate_limiter.wait("GET")
|
|
@@ -499,7 +658,7 @@ class ZIAClientHelper(ZIAClient):
|
|
|
499
658
|
# Check for unexpected status code
|
|
500
659
|
if response.status_code != expected_status_code:
|
|
501
660
|
error_msg = ERROR_MESSAGES["UNEXPECTED_STATUS"].format(
|
|
502
|
-
status_code=response.status_code, page=params
|
|
661
|
+
status_code=response.status_code, page=params.get("page", params.get("pageNumber", 0))
|
|
503
662
|
)
|
|
504
663
|
logger.error(error_msg)
|
|
505
664
|
return BoxList([]), error_msg
|
|
@@ -507,7 +666,7 @@ class ZIAClientHelper(ZIAClient):
|
|
|
507
666
|
# Parse the response as a flat list of items
|
|
508
667
|
response_data = response.json()
|
|
509
668
|
if not isinstance(response_data, list):
|
|
510
|
-
error_msg = ERROR_MESSAGES["EMPTY_RESULTS"].format(page=params
|
|
669
|
+
error_msg = ERROR_MESSAGES["EMPTY_RESULTS"].format(page=params.get("page", params.get("pageNumber", 0)))
|
|
511
670
|
logger.warn(error_msg)
|
|
512
671
|
return BoxList([]), error_msg
|
|
513
672
|
|
|
@@ -521,28 +680,30 @@ class ZIAClientHelper(ZIAClient):
|
|
|
521
680
|
|
|
522
681
|
# Check if we've reached max_items or max_pages limits
|
|
523
682
|
if (max_items is not None and total_collected >= max_items) or (
|
|
524
|
-
max_pages is not None and params
|
|
683
|
+
max_pages is not None and params.get("page", params.get("pageNumber", 0)) >= max_pages
|
|
525
684
|
):
|
|
526
685
|
break
|
|
527
686
|
|
|
528
|
-
# Stop if fewer items than pageSize are returned
|
|
529
|
-
if len(data) < params
|
|
687
|
+
# Stop if fewer items than pageSize/limit are returned
|
|
688
|
+
if len(data) < params.get("pageSize", params.get("limit", 1000)):
|
|
530
689
|
break
|
|
531
690
|
|
|
532
691
|
# Move to the next page
|
|
533
|
-
|
|
692
|
+
if "page" in params:
|
|
693
|
+
params["page"] += 1
|
|
694
|
+
elif "pageNumber" in params:
|
|
695
|
+
params["pageNumber"] += 1
|
|
534
696
|
|
|
535
697
|
finally:
|
|
536
698
|
time.sleep(2) # Ensure a delay between requests regardless of outcome
|
|
537
699
|
|
|
538
700
|
if not ret_data:
|
|
539
|
-
error_msg = ERROR_MESSAGES["EMPTY_RESULTS"].format(page=params
|
|
701
|
+
error_msg = ERROR_MESSAGES["EMPTY_RESULTS"].format(page=params.get("page", params.get("pageNumber", 0)))
|
|
540
702
|
logger.warn(error_msg)
|
|
541
703
|
return BoxList([]), error_msg
|
|
542
704
|
|
|
543
705
|
return BoxList(ret_data), None
|
|
544
706
|
|
|
545
|
-
|
|
546
707
|
# def get_paginated_data(
|
|
547
708
|
# self,
|
|
548
709
|
# path=None,
|
|
@@ -597,7 +758,7 @@ class ZIAClientHelper(ZIAClient):
|
|
|
597
758
|
# "page": page if page is not None else 1, # Start at page 1 if not specified
|
|
598
759
|
# "pagesize": max(100, min(pagesize or 100, 10000)), # Ensure pagesize is within API limits
|
|
599
760
|
# }
|
|
600
|
-
|
|
761
|
+
|
|
601
762
|
# # Add optional filters to the params if provided
|
|
602
763
|
# if search:
|
|
603
764
|
# params["search"] = search
|
|
@@ -717,7 +878,7 @@ class ZIAClientHelper(ZIAClient):
|
|
|
717
878
|
|
|
718
879
|
"""
|
|
719
880
|
return CloudAppsAPI(self)
|
|
720
|
-
|
|
881
|
+
|
|
721
882
|
@property
|
|
722
883
|
def dlp(self):
|
|
723
884
|
"""
|
|
@@ -774,7 +935,7 @@ class ZIAClientHelper(ZIAClient):
|
|
|
774
935
|
|
|
775
936
|
"""
|
|
776
937
|
return CloudSandboxAPI(self)
|
|
777
|
-
|
|
938
|
+
|
|
778
939
|
@property
|
|
779
940
|
def security(self):
|
|
780
941
|
"""
|
|
@@ -367,7 +367,7 @@ class CloudAppsAPI:
|
|
|
367
367
|
|
|
368
368
|
return self.rest.post(f"shadowIT/applications/{entity}/exportCsv", json=payload).text
|
|
369
369
|
|
|
370
|
-
def list_apps(self):
|
|
370
|
+
def list_apps(self, **kwargs):
|
|
371
371
|
"""
|
|
372
372
|
List all predefined and custom cloud applications by name and id.
|
|
373
373
|
|
|
@@ -382,7 +382,9 @@ class CloudAppsAPI:
|
|
|
382
382
|
print(app.name)
|
|
383
383
|
|
|
384
384
|
"""
|
|
385
|
-
|
|
385
|
+
list, _ = self.rest.get_paginated_data(path="cloudApplications/lite", **kwargs)
|
|
386
|
+
return list
|
|
387
|
+
# return self.rest.get("cloudApplications/lite")
|
|
386
388
|
|
|
387
389
|
def list_custom_tags(self):
|
|
388
390
|
"""
|
|
@@ -17,7 +17,6 @@
|
|
|
17
17
|
|
|
18
18
|
from box import BoxList
|
|
19
19
|
|
|
20
|
-
from zscaler.utils import snake_to_camel
|
|
21
20
|
from zscaler.zia.client import ZIAClient
|
|
22
21
|
|
|
23
22
|
|
|
@@ -25,7 +24,7 @@ class IsolationProfileAPI:
|
|
|
25
24
|
def __init__(self, client: ZIAClient):
|
|
26
25
|
self.rest = client
|
|
27
26
|
|
|
28
|
-
def list_isolation_profiles(self
|
|
27
|
+
def list_isolation_profiles(self) -> BoxList:
|
|
29
28
|
"""
|
|
30
29
|
Returns a list of all profiles in the Isolation Profile field for URL Filtering rules and Cloud App Control rules.
|
|
31
30
|
|
|
@@ -31,11 +31,9 @@ class RuleLabelsAPI:
|
|
|
31
31
|
Returns the list of ZIA Rule Labels.
|
|
32
32
|
|
|
33
33
|
Keyword Args:
|
|
34
|
-
**
|
|
35
|
-
|
|
36
|
-
**
|
|
37
|
-
The maximum number of pages to request before stopping iteration.
|
|
38
|
-
**page_size (int, optional):
|
|
34
|
+
**page (int, optional):
|
|
35
|
+
Specifies the page offset.
|
|
36
|
+
**pagesize (int, optional):
|
|
39
37
|
Specifies the page size. The default size is 100, but the maximum size is 1000.
|
|
40
38
|
|
|
41
39
|
Returns:
|