zscaler-sdk-python 1.0.0__py2.py3-none-any.whl
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/__init__.py +34 -0
- zscaler/cache/__init__.py +0 -0
- zscaler/cache/cache.py +105 -0
- zscaler/cache/no_op_cache.py +68 -0
- zscaler/cache/zscaler_cache.py +161 -0
- zscaler/constants.py +26 -0
- zscaler/errors/__init__.py +0 -0
- zscaler/errors/error.py +10 -0
- zscaler/errors/http_error.py +20 -0
- zscaler/errors/zscaler_api_error.py +24 -0
- zscaler/exceptions/__init__.py +1 -0
- zscaler/exceptions/exceptions.py +101 -0
- zscaler/logger.py +57 -0
- zscaler/ratelimiter/__init__.py +0 -0
- zscaler/ratelimiter/ratelimiter.py +39 -0
- zscaler/user_agent.py +23 -0
- zscaler/utils.py +577 -0
- zscaler/zia/__init__.py +657 -0
- zscaler/zia/activate.py +52 -0
- zscaler/zia/admin_and_role_management.py +344 -0
- zscaler/zia/apptotal.py +71 -0
- zscaler/zia/audit_logs.py +95 -0
- zscaler/zia/authentication_settings.py +98 -0
- zscaler/zia/client.py +88 -0
- zscaler/zia/cloud_apps.py +406 -0
- zscaler/zia/device_management.py +90 -0
- zscaler/zia/dlp.py +784 -0
- zscaler/zia/errors.py +37 -0
- zscaler/zia/firewall.py +1104 -0
- zscaler/zia/forwarding_control.py +271 -0
- zscaler/zia/isolation_profile.py +83 -0
- zscaler/zia/labels.py +180 -0
- zscaler/zia/locations.py +661 -0
- zscaler/zia/sandbox.py +180 -0
- zscaler/zia/security.py +236 -0
- zscaler/zia/ssl_inspection.py +175 -0
- zscaler/zia/traffic.py +853 -0
- zscaler/zia/url_categories.py +442 -0
- zscaler/zia/url_filtering.py +310 -0
- zscaler/zia/users.py +386 -0
- zscaler/zia/web_dlp.py +295 -0
- zscaler/zia/workload_groups.py +58 -0
- zscaler/zia/zpa_gateway.py +187 -0
- zscaler/zpa/__init__.py +683 -0
- zscaler/zpa/app_segments.py +331 -0
- zscaler/zpa/app_segments_inspection.py +311 -0
- zscaler/zpa/app_segments_pra.py +310 -0
- zscaler/zpa/certificates.py +234 -0
- zscaler/zpa/client.py +113 -0
- zscaler/zpa/cloud_connector_groups.py +75 -0
- zscaler/zpa/connectors.py +518 -0
- zscaler/zpa/emergency_access.py +178 -0
- zscaler/zpa/errors.py +37 -0
- zscaler/zpa/idp.py +83 -0
- zscaler/zpa/inspection.py +1012 -0
- zscaler/zpa/isolation_profile.py +85 -0
- zscaler/zpa/lss.py +568 -0
- zscaler/zpa/machine_groups.py +79 -0
- zscaler/zpa/policies.py +848 -0
- zscaler/zpa/posture_profiles.py +122 -0
- zscaler/zpa/privileged_remote_access.py +862 -0
- zscaler/zpa/provisioning.py +271 -0
- zscaler/zpa/saml_attributes.py +100 -0
- zscaler/zpa/scim_attributes.py +117 -0
- zscaler/zpa/scim_groups.py +146 -0
- zscaler/zpa/segment_groups.py +191 -0
- zscaler/zpa/server_groups.py +217 -0
- zscaler/zpa/servers.py +202 -0
- zscaler/zpa/service_edges.py +404 -0
- zscaler/zpa/trusted_networks.py +127 -0
- zscaler_sdk_python-1.0.0.dist-info/LICENSE.md +21 -0
- zscaler_sdk_python-1.0.0.dist-info/METADATA +59 -0
- zscaler_sdk_python-1.0.0.dist-info/RECORD +75 -0
- zscaler_sdk_python-1.0.0.dist-info/WHEEL +6 -0
- zscaler_sdk_python-1.0.0.dist-info/top_level.txt +1 -0
zscaler/zpa/__init__.py
ADDED
|
@@ -0,0 +1,683 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import os
|
|
3
|
+
import time
|
|
4
|
+
import urllib.parse
|
|
5
|
+
import uuid
|
|
6
|
+
from time import sleep
|
|
7
|
+
|
|
8
|
+
import requests
|
|
9
|
+
from box import BoxList
|
|
10
|
+
|
|
11
|
+
from zscaler import __version__
|
|
12
|
+
from zscaler.cache.no_op_cache import NoOpCache
|
|
13
|
+
from zscaler.cache.zscaler_cache import ZscalerCache
|
|
14
|
+
from zscaler.constants import ZPA_BASE_URLS
|
|
15
|
+
from zscaler.errors.http_error import HTTPError, ZscalerAPIError
|
|
16
|
+
from zscaler.exceptions.exceptions import HTTPException, ZscalerAPIException
|
|
17
|
+
from zscaler.logger import setup_logging
|
|
18
|
+
from zscaler.ratelimiter.ratelimiter import RateLimiter
|
|
19
|
+
from zscaler.user_agent import UserAgent
|
|
20
|
+
from zscaler.utils import (
|
|
21
|
+
convert_keys_to_snake,
|
|
22
|
+
dump_request,
|
|
23
|
+
dump_response,
|
|
24
|
+
format_json_response,
|
|
25
|
+
is_token_expired,
|
|
26
|
+
retry_with_backoff,
|
|
27
|
+
snake_to_camel,
|
|
28
|
+
)
|
|
29
|
+
from zscaler.zpa.app_segments import ApplicationSegmentAPI
|
|
30
|
+
from zscaler.zpa.app_segments_inspection import AppSegmentsInspectionAPI
|
|
31
|
+
from zscaler.zpa.app_segments_pra import AppSegmentsPRAAPI
|
|
32
|
+
from zscaler.zpa.certificates import CertificatesAPI
|
|
33
|
+
from zscaler.zpa.client import ZPAClient
|
|
34
|
+
from zscaler.zpa.cloud_connector_groups import CloudConnectorGroupsAPI
|
|
35
|
+
from zscaler.zpa.connectors import AppConnectorControllerAPI
|
|
36
|
+
from zscaler.zpa.emergency_access import EmergencyAccessAPI
|
|
37
|
+
from zscaler.zpa.idp import IDPControllerAPI
|
|
38
|
+
from zscaler.zpa.inspection import InspectionControllerAPI
|
|
39
|
+
from zscaler.zpa.isolation_profile import IsolationProfileAPI
|
|
40
|
+
from zscaler.zpa.lss import LSSConfigControllerAPI
|
|
41
|
+
from zscaler.zpa.machine_groups import MachineGroupsAPI
|
|
42
|
+
from zscaler.zpa.policies import PolicySetsAPI
|
|
43
|
+
from zscaler.zpa.posture_profiles import PostureProfilesAPI
|
|
44
|
+
from zscaler.zpa.privileged_remote_access import PrivilegedRemoteAccessAPI
|
|
45
|
+
from zscaler.zpa.provisioning import ProvisioningKeyAPI
|
|
46
|
+
from zscaler.zpa.saml_attributes import SAMLAttributesAPI
|
|
47
|
+
from zscaler.zpa.scim_attributes import ScimAttributeHeaderAPI
|
|
48
|
+
from zscaler.zpa.scim_groups import SCIMGroupsAPI
|
|
49
|
+
from zscaler.zpa.segment_groups import SegmentGroupsAPI
|
|
50
|
+
from zscaler.zpa.server_groups import ServerGroupsAPI
|
|
51
|
+
from zscaler.zpa.servers import AppServersAPI
|
|
52
|
+
from zscaler.zpa.service_edges import ServiceEdgesAPI
|
|
53
|
+
from zscaler.zpa.trusted_networks import TrustedNetworksAPI
|
|
54
|
+
|
|
55
|
+
# Setup the logger
|
|
56
|
+
setup_logging(logger_name="zscaler-sdk-python")
|
|
57
|
+
logger = logging.getLogger("zscaler-sdk-python")
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class ZPAClientHelper(ZPAClient):
|
|
61
|
+
"""A Controller to access Endpoints in the Zscaler Private Access (ZPA) API.
|
|
62
|
+
|
|
63
|
+
The ZPA object stores the session token and simplifies access to API interfaces within ZPA.
|
|
64
|
+
|
|
65
|
+
Attributes:
|
|
66
|
+
client_id (str): The ZPA API client ID generated from the ZPA console.
|
|
67
|
+
client_secret (str): The ZPA API client secret generated from the ZPA console.
|
|
68
|
+
customer_id (str): The ZPA tenant ID found in the Administration > Company menu in the ZPA console.
|
|
69
|
+
cloud (str): The Zscaler cloud for your tenancy, accepted values are:
|
|
70
|
+
|
|
71
|
+
* ``production``
|
|
72
|
+
* ``beta``
|
|
73
|
+
* ``gov``
|
|
74
|
+
* ``govus``
|
|
75
|
+
* ``zpatwo``
|
|
76
|
+
"""
|
|
77
|
+
|
|
78
|
+
def __init__(
|
|
79
|
+
self,
|
|
80
|
+
client_id,
|
|
81
|
+
client_secret,
|
|
82
|
+
customer_id,
|
|
83
|
+
cloud,
|
|
84
|
+
timeout=240,
|
|
85
|
+
cache=None,
|
|
86
|
+
fail_safe=False,
|
|
87
|
+
):
|
|
88
|
+
# Initialize rate limiter
|
|
89
|
+
# You may want to adjust these parameters as per your rate limit configuration
|
|
90
|
+
self.rate_limiter = RateLimiter(
|
|
91
|
+
get_limit=20, # Adjusted to allow 20 GET requests per 10 seconds
|
|
92
|
+
post_put_delete_limit=10, # Adjusted to allow 10 POST/PUT/DELETE requests per 10 seconds
|
|
93
|
+
get_freq=10, # Adjust frequency to 10 seconds
|
|
94
|
+
post_put_delete_freq=10, # Adjust frequency to 10 seconds
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
# Validate cloud value
|
|
98
|
+
if cloud not in ZPA_BASE_URLS:
|
|
99
|
+
valid_clouds = ", ".join(ZPA_BASE_URLS.keys())
|
|
100
|
+
raise ValueError(
|
|
101
|
+
f"The provided ZPA_CLOUD value '{cloud}' is not supported. "
|
|
102
|
+
f"Please use one of the following supported values: {valid_clouds}"
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
# Continue with existing initialization...
|
|
106
|
+
# Select the appropriate URL
|
|
107
|
+
self.baseurl = ZPA_BASE_URLS.get(cloud, ZPA_BASE_URLS["PRODUCTION"])
|
|
108
|
+
|
|
109
|
+
self.timeout = timeout
|
|
110
|
+
self.client_id = client_id
|
|
111
|
+
self.client_secret = client_secret
|
|
112
|
+
self.customer_id = customer_id
|
|
113
|
+
self.cloud = cloud
|
|
114
|
+
self.url = f"{self.baseurl}/mgmtconfig/v1/admin/customers/{customer_id}"
|
|
115
|
+
self.user_config_url = f"{self.baseurl}/userconfig/v1/customers/{customer_id}"
|
|
116
|
+
self.v2_url = f"{self.baseurl}/mgmtconfig/v2/admin/customers/{customer_id}"
|
|
117
|
+
self.cbi_url = f"{self.baseurl}/cbiconfig/cbi/api/customers/{customer_id}"
|
|
118
|
+
self.fail_safe = fail_safe
|
|
119
|
+
# Cache setup
|
|
120
|
+
cache_enabled = (
|
|
121
|
+
os.environ.get("ZSCALER_CLIENT_CACHE_ENABLED", "true").lower() == "true"
|
|
122
|
+
)
|
|
123
|
+
if cache is None:
|
|
124
|
+
if cache_enabled:
|
|
125
|
+
ttl = int(os.environ.get("ZSCALER_CLIENT_CACHE_DEFAULT_TTL", 3600))
|
|
126
|
+
tti = int(os.environ.get("ZSCALER_CLIENT_CACHE_DEFAULT_TTI", 1800))
|
|
127
|
+
self.cache = ZscalerCache(ttl=ttl, tti=tti)
|
|
128
|
+
else:
|
|
129
|
+
self.cache = NoOpCache()
|
|
130
|
+
else:
|
|
131
|
+
self.cache = cache
|
|
132
|
+
|
|
133
|
+
# Initialize user-agent
|
|
134
|
+
ua = UserAgent()
|
|
135
|
+
self.user_agent = ua.get_user_agent_string()
|
|
136
|
+
self.refreshToken()
|
|
137
|
+
|
|
138
|
+
def refreshToken(self):
|
|
139
|
+
# login
|
|
140
|
+
response = self.login()
|
|
141
|
+
if response is None or response.status_code > 299 or not response.json():
|
|
142
|
+
logger.error(
|
|
143
|
+
"Failed to login using provided credentials, response: %s", response
|
|
144
|
+
)
|
|
145
|
+
raise Exception("Failed to login using provided credentials.")
|
|
146
|
+
self.access_token = response.json().get("access_token")
|
|
147
|
+
self.headers = {
|
|
148
|
+
"Content-Type": "application/json",
|
|
149
|
+
"Accept": "application/json",
|
|
150
|
+
"Authorization": f"Bearer {self.access_token}",
|
|
151
|
+
"User-Agent": self.user_agent,
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
@retry_with_backoff(retries=5)
|
|
155
|
+
def login(self):
|
|
156
|
+
"""Log in to the ZPA API and set the access token for subsequent requests."""
|
|
157
|
+
data = urllib.parse.urlencode(
|
|
158
|
+
{"client_id": self.client_id, "client_secret": self.client_secret}
|
|
159
|
+
)
|
|
160
|
+
headers = {
|
|
161
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
162
|
+
"Accept": "application/json",
|
|
163
|
+
"User-Agent": self.user_agent,
|
|
164
|
+
}
|
|
165
|
+
try:
|
|
166
|
+
url = f"{self.baseurl}/signin"
|
|
167
|
+
resp = requests.post(url, data=data, headers=headers, timeout=self.timeout)
|
|
168
|
+
# Avoid logging all data from the response, focus on the status and a summary instead
|
|
169
|
+
logger.info("Login attempt with status: %d", resp.status_code)
|
|
170
|
+
return resp
|
|
171
|
+
except Exception as e:
|
|
172
|
+
logger.error("Login failed due to an exception: %s", str(e))
|
|
173
|
+
return None
|
|
174
|
+
|
|
175
|
+
def send(self, method, path, json=None, params=None, api_version: str = None):
|
|
176
|
+
"""
|
|
177
|
+
Send a request to the ZPA API.
|
|
178
|
+
|
|
179
|
+
Parameters:
|
|
180
|
+
- method (str): The HTTP method.
|
|
181
|
+
- path (str): API endpoint path.
|
|
182
|
+
- json (dict, optional): Request payload. Defaults to None.
|
|
183
|
+
Returns:
|
|
184
|
+
- Response: Response object from the request.
|
|
185
|
+
"""
|
|
186
|
+
api = self.url
|
|
187
|
+
if api_version is None:
|
|
188
|
+
api = self.url
|
|
189
|
+
elif api_version == "v2":
|
|
190
|
+
api = self.v2_url
|
|
191
|
+
elif api_version == "userconfig_v1":
|
|
192
|
+
api = self.user_config_url
|
|
193
|
+
|
|
194
|
+
url = f"{api}/{path.lstrip('/')}"
|
|
195
|
+
start_time = time.time()
|
|
196
|
+
# Update headers to include the user agent
|
|
197
|
+
headers_with_user_agent = self.headers.copy()
|
|
198
|
+
headers_with_user_agent["User-Agent"] = self.user_agent
|
|
199
|
+
# Generate a unique UUID for this request
|
|
200
|
+
request_uuid = uuid.uuid4()
|
|
201
|
+
dump_request(
|
|
202
|
+
logger, url, method, json, params, headers_with_user_agent, request_uuid
|
|
203
|
+
)
|
|
204
|
+
# Check cache before sending request
|
|
205
|
+
cache_key = self.cache.create_key(url, params)
|
|
206
|
+
if method == "GET" and self.cache.contains(cache_key):
|
|
207
|
+
resp = self.cache.get(cache_key)
|
|
208
|
+
dump_response(
|
|
209
|
+
logger=logger,
|
|
210
|
+
url=url,
|
|
211
|
+
method=method,
|
|
212
|
+
params=params,
|
|
213
|
+
resp=resp,
|
|
214
|
+
request_uuid=request_uuid,
|
|
215
|
+
start_time=start_time,
|
|
216
|
+
from_cache=True,
|
|
217
|
+
)
|
|
218
|
+
return resp
|
|
219
|
+
|
|
220
|
+
attempts = 0
|
|
221
|
+
while attempts < 5: # Trying a maximum of 5 times
|
|
222
|
+
try:
|
|
223
|
+
# If the token is None or expired, fetch a new token
|
|
224
|
+
if is_token_expired(self.access_token):
|
|
225
|
+
logger.warning(
|
|
226
|
+
"The provided or fetched token was already expired. Refreshing..."
|
|
227
|
+
)
|
|
228
|
+
self.refreshToken()
|
|
229
|
+
resp = requests.request(
|
|
230
|
+
method,
|
|
231
|
+
url,
|
|
232
|
+
json=json,
|
|
233
|
+
headers=headers_with_user_agent,
|
|
234
|
+
timeout=self.timeout,
|
|
235
|
+
)
|
|
236
|
+
dump_response(
|
|
237
|
+
logger=logger,
|
|
238
|
+
url=url,
|
|
239
|
+
params=params,
|
|
240
|
+
method=method,
|
|
241
|
+
resp=resp,
|
|
242
|
+
request_uuid=request_uuid,
|
|
243
|
+
start_time=start_time,
|
|
244
|
+
)
|
|
245
|
+
if (
|
|
246
|
+
resp.status_code == 429
|
|
247
|
+
): # HTTP Status code 429 indicates "Too Many Requests"
|
|
248
|
+
sleep_time = int(
|
|
249
|
+
resp.headers.get("Retry-After", 2)
|
|
250
|
+
) # Default to 60 seconds if 'Retry-After' header is missing
|
|
251
|
+
logger.warning(
|
|
252
|
+
f"Rate limit exceeded. Retrying in {sleep_time} seconds."
|
|
253
|
+
)
|
|
254
|
+
sleep(sleep_time)
|
|
255
|
+
attempts += 1
|
|
256
|
+
continue
|
|
257
|
+
else:
|
|
258
|
+
break
|
|
259
|
+
except requests.RequestException as e:
|
|
260
|
+
if attempts == 4: # If it's the last attempt, raise the exception
|
|
261
|
+
logger.error(
|
|
262
|
+
f"Failed to send {method} request to {url} after 5 attempts. Error: {str(e)}"
|
|
263
|
+
)
|
|
264
|
+
raise e
|
|
265
|
+
else:
|
|
266
|
+
logger.warning(
|
|
267
|
+
f"Failed to send {method} request to {url}. Retrying... Error: {str(e)}"
|
|
268
|
+
)
|
|
269
|
+
attempts += 1
|
|
270
|
+
sleep(5) # Sleep for 5 seconds before retrying
|
|
271
|
+
|
|
272
|
+
# If Non-GET call, clear the
|
|
273
|
+
if method != "GET":
|
|
274
|
+
self.cache.delete(cache_key)
|
|
275
|
+
|
|
276
|
+
# Detailed logging for request and response
|
|
277
|
+
try:
|
|
278
|
+
response_data = resp.json()
|
|
279
|
+
except ValueError: # Using ValueError for JSON decoding errors
|
|
280
|
+
response_data = resp.text
|
|
281
|
+
# check if call was succesful
|
|
282
|
+
if 200 > resp.status_code or resp.status_code > 299:
|
|
283
|
+
# create errors
|
|
284
|
+
try:
|
|
285
|
+
error = ZscalerAPIError(url, resp, response_data)
|
|
286
|
+
if self.fail_safe:
|
|
287
|
+
raise ZscalerAPIException(response_data)
|
|
288
|
+
except ZscalerAPIException:
|
|
289
|
+
raise
|
|
290
|
+
except Exception:
|
|
291
|
+
error = HTTPError(url, resp, response_data)
|
|
292
|
+
if self.fail_safe:
|
|
293
|
+
logger.error(response_data)
|
|
294
|
+
raise HTTPException(response_data)
|
|
295
|
+
logger.error(error)
|
|
296
|
+
# Cache the response if it's a successful GET request
|
|
297
|
+
if method == "GET" and resp.status_code == 200:
|
|
298
|
+
self.cache.add(cache_key, resp)
|
|
299
|
+
return resp
|
|
300
|
+
|
|
301
|
+
def get(self, path, json=None, params=None, api_version: str = None):
|
|
302
|
+
"""
|
|
303
|
+
Send a GET request to the ZPA API.
|
|
304
|
+
|
|
305
|
+
Parameters:
|
|
306
|
+
- path (str): API endpoint path.
|
|
307
|
+
- data (dict, optional): Request payload. Defaults to None.
|
|
308
|
+
Returns:
|
|
309
|
+
- Response: Response object from the request.
|
|
310
|
+
"""
|
|
311
|
+
|
|
312
|
+
# Use rate limiter before making a request
|
|
313
|
+
should_wait, delay = self.rate_limiter.wait("GET")
|
|
314
|
+
if should_wait:
|
|
315
|
+
time.sleep(delay)
|
|
316
|
+
|
|
317
|
+
# Now proceed with sending the request
|
|
318
|
+
resp = self.send("GET", path, json, params, api_version=api_version)
|
|
319
|
+
formatted_resp = format_json_response(resp, box_attrs=dict())
|
|
320
|
+
return formatted_resp
|
|
321
|
+
|
|
322
|
+
def put(self, path, json=None, params=None, api_version: str = None):
|
|
323
|
+
should_wait, delay = self.rate_limiter.wait("PUT")
|
|
324
|
+
if should_wait:
|
|
325
|
+
time.sleep(delay)
|
|
326
|
+
resp = self.send("PUT", path, json, params, api_version=api_version)
|
|
327
|
+
formatted_resp = format_json_response(resp, box_attrs=dict())
|
|
328
|
+
return formatted_resp
|
|
329
|
+
|
|
330
|
+
def post(self, path, json=None, params=None, api_version: str = None):
|
|
331
|
+
should_wait, delay = self.rate_limiter.wait("POST")
|
|
332
|
+
if should_wait:
|
|
333
|
+
time.sleep(delay)
|
|
334
|
+
resp = self.send("POST", path, json, params, api_version=api_version)
|
|
335
|
+
formatted_resp = format_json_response(resp, box_attrs=dict())
|
|
336
|
+
return formatted_resp
|
|
337
|
+
|
|
338
|
+
def delete(self, path, json=None, params=None, api_version: str = None):
|
|
339
|
+
should_wait, delay = self.rate_limiter.wait("DELETE")
|
|
340
|
+
if should_wait:
|
|
341
|
+
time.sleep(delay)
|
|
342
|
+
return self.send("DELETE", path, json, params, api_version=api_version)
|
|
343
|
+
|
|
344
|
+
def get_paginated_data(
|
|
345
|
+
self,
|
|
346
|
+
path=None,
|
|
347
|
+
params=None,
|
|
348
|
+
expected_status_code=200,
|
|
349
|
+
api_version: str = None,
|
|
350
|
+
search=None,
|
|
351
|
+
search_field="name",
|
|
352
|
+
max_pages=None,
|
|
353
|
+
max_items=None,
|
|
354
|
+
sort_order=None,
|
|
355
|
+
sort_by=None,
|
|
356
|
+
sort_dir=None,
|
|
357
|
+
start_time=None,
|
|
358
|
+
end_time=None,
|
|
359
|
+
idp_group_id=None,
|
|
360
|
+
scim_user_id=None,
|
|
361
|
+
page=None,
|
|
362
|
+
pagesize=20,
|
|
363
|
+
):
|
|
364
|
+
"""
|
|
365
|
+
Fetches paginated data from the ZPA API based on specified parameters and handles various types of API pagination.
|
|
366
|
+
|
|
367
|
+
Args:
|
|
368
|
+
path (str): The API endpoint path to send requests to.
|
|
369
|
+
params (dict): Initial set of query parameters for the API request.
|
|
370
|
+
expected_status_code (int): The expected HTTP status code for a successful request. Defaults to 200.
|
|
371
|
+
api_version (str): Specifies the version of the API to be used. Helps in routing within the API service.
|
|
372
|
+
search (str): Search query to filter the results based on specific conditions.
|
|
373
|
+
search_field (str): The field name against which to search the query. Default is "name".
|
|
374
|
+
max_pages (int): The maximum number of pages to fetch. If None, fetches all available pages.
|
|
375
|
+
max_items (int): The maximum number of items to fetch across all pages. Stops fetching once reached.
|
|
376
|
+
sort_order (str): Specifies the order of sorting (e.g., 'ASC' or 'DSC').
|
|
377
|
+
sort_by (str): Specifies the field name by which the results should be sorted.
|
|
378
|
+
sort_dir (str): Specifies the direction of sorting. This is similar to `sort_order` and can be used interchangeably.
|
|
379
|
+
start_time (str): The start of a time range for filtering data based on modification time.
|
|
380
|
+
end_time (str): The end of a time range for filtering data based on modification time.
|
|
381
|
+
idp_group_id (str): Identifier for a specific IDP group, used for fetching data related to that group.
|
|
382
|
+
scim_user_id (str): Identifier for a specific SCIM user, used for fetching data related to that user.
|
|
383
|
+
page (int): Specific page number to fetch. Overrides automatic pagination.
|
|
384
|
+
pagesize (int): Number of items per page, default is 20 as per API specification, maximum is 500.
|
|
385
|
+
|
|
386
|
+
Returns:
|
|
387
|
+
tuple: A tuple containing:
|
|
388
|
+
- BoxList: A list of fetched items wrapped in a BoxList for easy access.
|
|
389
|
+
- str: An error message if any occurred during the data fetching process.
|
|
390
|
+
|
|
391
|
+
Raises:
|
|
392
|
+
Logs errors and warnings through the configured logger when requests fail or if no data is found.
|
|
393
|
+
"""
|
|
394
|
+
logger = logging.getLogger(__name__)
|
|
395
|
+
|
|
396
|
+
ERROR_MESSAGES = {
|
|
397
|
+
"UNEXPECTED_STATUS": "Unexpected status code {status_code} received for page {page}.",
|
|
398
|
+
"MISSING_DATA_KEY": "The key 'list' was not found in the response for page {page}.",
|
|
399
|
+
"EMPTY_RESULTS": "No results found for all requested pages.",
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
if params is None:
|
|
403
|
+
params = {}
|
|
404
|
+
|
|
405
|
+
if (page is not None or pagesize != 20) and (
|
|
406
|
+
max_pages is not None or max_items is not None
|
|
407
|
+
):
|
|
408
|
+
raise ValueError(
|
|
409
|
+
"Do not mix 'page' or 'pagesize' with 'max_pages' or 'max_items'. Choose either set of parameters."
|
|
410
|
+
)
|
|
411
|
+
|
|
412
|
+
params["pagesize"] = min(
|
|
413
|
+
pagesize, 500
|
|
414
|
+
) # Apply maximum constraint and handle default
|
|
415
|
+
|
|
416
|
+
if page:
|
|
417
|
+
params["page"] = page
|
|
418
|
+
|
|
419
|
+
if search:
|
|
420
|
+
api_search_field = snake_to_camel(search_field)
|
|
421
|
+
params["search"] = f"{api_search_field} EQ {search}"
|
|
422
|
+
if sort_order:
|
|
423
|
+
params["sortOrder"] = sort_order
|
|
424
|
+
if sort_by:
|
|
425
|
+
params["sortBy"] = sort_by
|
|
426
|
+
if sort_dir:
|
|
427
|
+
params["sortdir"] = sort_dir
|
|
428
|
+
if start_time and end_time:
|
|
429
|
+
params["startTime"] = start_time
|
|
430
|
+
params["endTime"] = end_time
|
|
431
|
+
if idp_group_id:
|
|
432
|
+
params["idpGroupId"] = idp_group_id
|
|
433
|
+
if scim_user_id:
|
|
434
|
+
params["scimUserId"] = scim_user_id
|
|
435
|
+
|
|
436
|
+
total_collected = 0
|
|
437
|
+
ret_data = []
|
|
438
|
+
|
|
439
|
+
try:
|
|
440
|
+
while True:
|
|
441
|
+
if max_pages is not None and (page is not None and page > max_pages):
|
|
442
|
+
break
|
|
443
|
+
|
|
444
|
+
should_wait, delay = self.rate_limiter.wait("GET")
|
|
445
|
+
if should_wait:
|
|
446
|
+
time.sleep(delay)
|
|
447
|
+
|
|
448
|
+
url = f"{path}?{urllib.parse.urlencode(params)}"
|
|
449
|
+
response = self.send("GET", url, api_version=api_version)
|
|
450
|
+
|
|
451
|
+
if response.status_code != expected_status_code:
|
|
452
|
+
error_msg = ERROR_MESSAGES["UNEXPECTED_STATUS"].format(
|
|
453
|
+
status_code=response.status_code, page=page
|
|
454
|
+
)
|
|
455
|
+
logger.error(error_msg)
|
|
456
|
+
return BoxList([]), error_msg
|
|
457
|
+
|
|
458
|
+
response_data = response.json()
|
|
459
|
+
data = response_data.get("list", [])
|
|
460
|
+
if not data and (page is None or page == 1):
|
|
461
|
+
error_msg = ERROR_MESSAGES["EMPTY_RESULTS"]
|
|
462
|
+
logger.warn(error_msg)
|
|
463
|
+
return BoxList([]), error_msg
|
|
464
|
+
|
|
465
|
+
data = convert_keys_to_snake(data)
|
|
466
|
+
ret_data.extend(
|
|
467
|
+
data[: max_items - total_collected]
|
|
468
|
+
if max_items is not None
|
|
469
|
+
else data
|
|
470
|
+
)
|
|
471
|
+
total_collected += len(data)
|
|
472
|
+
|
|
473
|
+
if max_items is not None and total_collected >= max_items:
|
|
474
|
+
break
|
|
475
|
+
|
|
476
|
+
nextPage = response_data.get("nextPage")
|
|
477
|
+
if not nextPage or (max_pages is not None and page >= max_pages):
|
|
478
|
+
break
|
|
479
|
+
|
|
480
|
+
page = nextPage if page is None else page + 1
|
|
481
|
+
params["page"] = page
|
|
482
|
+
|
|
483
|
+
finally:
|
|
484
|
+
time.sleep(1) # Ensure a delay between requests regardless of outcome
|
|
485
|
+
|
|
486
|
+
if not ret_data:
|
|
487
|
+
error_msg = ERROR_MESSAGES["EMPTY_RESULTS"]
|
|
488
|
+
logger.warn(error_msg)
|
|
489
|
+
return BoxList([]), error_msg
|
|
490
|
+
|
|
491
|
+
return BoxList(ret_data), None
|
|
492
|
+
|
|
493
|
+
@property
|
|
494
|
+
def app_segments(self):
|
|
495
|
+
"""
|
|
496
|
+
The interface object for the :ref:`ZPA Application Segments interface <zpa-app_segments>`.
|
|
497
|
+
|
|
498
|
+
"""
|
|
499
|
+
return ApplicationSegmentAPI(self)
|
|
500
|
+
|
|
501
|
+
@property
|
|
502
|
+
def app_segments_pra(self):
|
|
503
|
+
"""
|
|
504
|
+
The interface object for the :ref:`ZPA Application Segments PRA interface <zpa-app_segments_pra>`.
|
|
505
|
+
|
|
506
|
+
"""
|
|
507
|
+
return AppSegmentsPRAAPI(self)
|
|
508
|
+
|
|
509
|
+
@property
|
|
510
|
+
def app_segments_inspection(self):
|
|
511
|
+
"""
|
|
512
|
+
The interface object for the :ref:`ZPA Application Segments PRA interface <zpa-app_segments_inspection>`.
|
|
513
|
+
|
|
514
|
+
"""
|
|
515
|
+
return AppSegmentsInspectionAPI(self)
|
|
516
|
+
|
|
517
|
+
@property
|
|
518
|
+
def certificates(self):
|
|
519
|
+
"""
|
|
520
|
+
The interface object for the :ref:`ZPA Browser Access Certificates interface <zpa-certificates>`.
|
|
521
|
+
|
|
522
|
+
"""
|
|
523
|
+
return CertificatesAPI(self)
|
|
524
|
+
|
|
525
|
+
@property
|
|
526
|
+
def isolation_profile(self):
|
|
527
|
+
"""
|
|
528
|
+
The interface object for the :ref:`ZPA Isolation Profiles <zpa-isolation_profile>`.
|
|
529
|
+
|
|
530
|
+
"""
|
|
531
|
+
return IsolationProfileAPI(self)
|
|
532
|
+
|
|
533
|
+
@property
|
|
534
|
+
def cloud_connector_groups(self):
|
|
535
|
+
"""
|
|
536
|
+
The interface object for the :ref:`ZPA Cloud Connector Groups interface <zpa-cloud_connector_groups>`.
|
|
537
|
+
|
|
538
|
+
"""
|
|
539
|
+
return CloudConnectorGroupsAPI(self)
|
|
540
|
+
|
|
541
|
+
@property
|
|
542
|
+
def connectors(self):
|
|
543
|
+
"""
|
|
544
|
+
The interface object for the :ref:`ZPA Connectors interface <zpa-connectors>`.
|
|
545
|
+
|
|
546
|
+
"""
|
|
547
|
+
return AppConnectorControllerAPI(self)
|
|
548
|
+
|
|
549
|
+
@property
|
|
550
|
+
def emergency_access(self):
|
|
551
|
+
"""
|
|
552
|
+
The interface object for the :ref:`ZPA Emergency Access interface <zpa-emergency_access>`.
|
|
553
|
+
|
|
554
|
+
"""
|
|
555
|
+
return EmergencyAccessAPI(self)
|
|
556
|
+
|
|
557
|
+
@property
|
|
558
|
+
def idp(self):
|
|
559
|
+
"""
|
|
560
|
+
The interface object for the :ref:`ZPA IDP interface <zpa-idp>`.
|
|
561
|
+
|
|
562
|
+
"""
|
|
563
|
+
return IDPControllerAPI(self)
|
|
564
|
+
|
|
565
|
+
@property
|
|
566
|
+
def inspection(self):
|
|
567
|
+
"""
|
|
568
|
+
The interface object for the :ref:`ZPA Inspection interface <zpa-inspection>`.
|
|
569
|
+
|
|
570
|
+
"""
|
|
571
|
+
return InspectionControllerAPI(self)
|
|
572
|
+
|
|
573
|
+
@property
|
|
574
|
+
def lss(self):
|
|
575
|
+
"""
|
|
576
|
+
The interface object for the :ref:`ZIA Log Streaming Service Config interface <zpa-lss>`.
|
|
577
|
+
|
|
578
|
+
"""
|
|
579
|
+
return LSSConfigControllerAPI(self)
|
|
580
|
+
|
|
581
|
+
@property
|
|
582
|
+
def machine_groups(self):
|
|
583
|
+
"""
|
|
584
|
+
The interface object for the :ref:`ZPA Machine Groups interface <zpa-machine_groups>`.
|
|
585
|
+
|
|
586
|
+
"""
|
|
587
|
+
return MachineGroupsAPI(self)
|
|
588
|
+
|
|
589
|
+
@property
|
|
590
|
+
def policies(self):
|
|
591
|
+
"""
|
|
592
|
+
The interface object for the :ref:`ZPA Policy Sets interface <zpa-policies>`.
|
|
593
|
+
|
|
594
|
+
"""
|
|
595
|
+
return PolicySetsAPI(self)
|
|
596
|
+
|
|
597
|
+
@property
|
|
598
|
+
def posture_profiles(self):
|
|
599
|
+
"""
|
|
600
|
+
The interface object for the :ref:`ZPA Posture Profiles interface <zpa-posture_profiles>`.
|
|
601
|
+
|
|
602
|
+
"""
|
|
603
|
+
return PostureProfilesAPI(self)
|
|
604
|
+
|
|
605
|
+
@property
|
|
606
|
+
def privileged_remote_access(self):
|
|
607
|
+
"""
|
|
608
|
+
The interface object for the :ref:`ZPA Privileged Remote Access interface <zpa-privileged_remote_access>`.
|
|
609
|
+
|
|
610
|
+
"""
|
|
611
|
+
return PrivilegedRemoteAccessAPI(self)
|
|
612
|
+
|
|
613
|
+
@property
|
|
614
|
+
def provisioning(self):
|
|
615
|
+
"""
|
|
616
|
+
The interface object for the :ref:`ZPA Provisioning interface <zpa-provisioning>`.
|
|
617
|
+
|
|
618
|
+
"""
|
|
619
|
+
return ProvisioningKeyAPI(self)
|
|
620
|
+
|
|
621
|
+
@property
|
|
622
|
+
def saml_attributes(self):
|
|
623
|
+
"""
|
|
624
|
+
The interface object for the :ref:`ZPA SAML Attributes interface <zpa-saml_attributes>`.
|
|
625
|
+
|
|
626
|
+
"""
|
|
627
|
+
return SAMLAttributesAPI(self)
|
|
628
|
+
|
|
629
|
+
@property
|
|
630
|
+
def scim_attributes(self):
|
|
631
|
+
"""
|
|
632
|
+
The interface object for the :ref:`ZPA SCIM Attributes interface <zpa-scim_attributes>`.
|
|
633
|
+
|
|
634
|
+
"""
|
|
635
|
+
return ScimAttributeHeaderAPI(self)
|
|
636
|
+
|
|
637
|
+
@property
|
|
638
|
+
def scim_groups(self):
|
|
639
|
+
"""
|
|
640
|
+
The interface object for the :ref:`ZPA SCIM Groups interface <zpa-scim_groups>`.
|
|
641
|
+
|
|
642
|
+
"""
|
|
643
|
+
return SCIMGroupsAPI(self)
|
|
644
|
+
|
|
645
|
+
@property
|
|
646
|
+
def segment_groups(self):
|
|
647
|
+
"""
|
|
648
|
+
The interface object for the :ref:`ZPA Segment Groups interface <zpa-segment_groups>`.
|
|
649
|
+
|
|
650
|
+
"""
|
|
651
|
+
return SegmentGroupsAPI(self)
|
|
652
|
+
|
|
653
|
+
@property
|
|
654
|
+
def server_groups(self):
|
|
655
|
+
"""
|
|
656
|
+
The interface object for the :ref:`ZPA Server Groups interface <zpa-server_groups>`.
|
|
657
|
+
|
|
658
|
+
"""
|
|
659
|
+
return ServerGroupsAPI(self)
|
|
660
|
+
|
|
661
|
+
@property
|
|
662
|
+
def servers(self):
|
|
663
|
+
"""
|
|
664
|
+
The interface object for the :ref:`ZPA Application Servers interface <zpa-app_servers>`.
|
|
665
|
+
|
|
666
|
+
"""
|
|
667
|
+
return AppServersAPI(self)
|
|
668
|
+
|
|
669
|
+
@property
|
|
670
|
+
def service_edges(self):
|
|
671
|
+
"""
|
|
672
|
+
The interface object for the :ref:`ZPA Service Edges interface <zpa-service_edges>`.
|
|
673
|
+
|
|
674
|
+
"""
|
|
675
|
+
return ServiceEdgesAPI(self)
|
|
676
|
+
|
|
677
|
+
@property
|
|
678
|
+
def trusted_networks(self):
|
|
679
|
+
"""
|
|
680
|
+
The interface object for the :ref:`ZPA Trusted Networks interface <zpa-trusted_networks>`.
|
|
681
|
+
|
|
682
|
+
"""
|
|
683
|
+
return TrustedNetworksAPI(self)
|