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/__init__.py
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
|
|
3
|
+
# Copyright (c) 2023, Zscaler Inc.
|
|
4
|
+
#
|
|
5
|
+
# Permission to use, copy, modify, and/or distribute this software for any
|
|
6
|
+
# purpose with or without fee is hereby granted, provided that the above
|
|
7
|
+
# copyright notice and this permission notice appear in all copies.
|
|
8
|
+
#
|
|
9
|
+
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
10
|
+
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
11
|
+
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
12
|
+
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
13
|
+
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
14
|
+
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
15
|
+
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
16
|
+
|
|
17
|
+
"""Zscaler SDK for Python
|
|
18
|
+
|
|
19
|
+
Zscaler SDK Python is an SDK that provides a uniform and easy-to-use interface for each of the Zscaler product APIs.
|
|
20
|
+
|
|
21
|
+
Documentation available at https://zscaler-sdk-python.readthedocs.io
|
|
22
|
+
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
__author__ = "Zscaler Inc"
|
|
26
|
+
__email__ = "devrel@zscaler.com"
|
|
27
|
+
__license__ = "MIT"
|
|
28
|
+
__contributors__ = [
|
|
29
|
+
"William Guilherme",
|
|
30
|
+
]
|
|
31
|
+
__version__ = "1.0.0"
|
|
32
|
+
|
|
33
|
+
from zscaler.zia import ZIAClientHelper # noqa
|
|
34
|
+
from zscaler.zpa import ZPAClientHelper # noqa
|
|
File without changes
|
zscaler/cache/cache.py
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
from urllib.parse import parse_qs, urlencode, urlparse
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class Cache:
|
|
5
|
+
"""
|
|
6
|
+
This is the ABSTRACT class that defines a Cache object for the ZPA Client
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
def __init__(self):
|
|
10
|
+
pass
|
|
11
|
+
|
|
12
|
+
def get(self, key):
|
|
13
|
+
"""
|
|
14
|
+
A method which retrieves the desired value from the cache.
|
|
15
|
+
|
|
16
|
+
Arguments:
|
|
17
|
+
key {str} -- The key used to find the desired value
|
|
18
|
+
|
|
19
|
+
Raises:
|
|
20
|
+
NotImplementedError: If the subclass inheriting this class
|
|
21
|
+
has not implemented this function
|
|
22
|
+
"""
|
|
23
|
+
raise NotImplementedError
|
|
24
|
+
|
|
25
|
+
def contains(self, key):
|
|
26
|
+
"""
|
|
27
|
+
A method which checks if the cache contains the desired value.
|
|
28
|
+
|
|
29
|
+
Arguments:
|
|
30
|
+
key {str} -- The key used to check the desired value
|
|
31
|
+
|
|
32
|
+
Raises:
|
|
33
|
+
NotImplementedError: If the subclass inheriting this class
|
|
34
|
+
has not implemented this function
|
|
35
|
+
"""
|
|
36
|
+
raise NotImplementedError
|
|
37
|
+
|
|
38
|
+
def add(self, key, value):
|
|
39
|
+
"""
|
|
40
|
+
A method which adds a key-value pair to the cache.
|
|
41
|
+
|
|
42
|
+
Arguments:
|
|
43
|
+
key {str} -- The key used to identify the entry.
|
|
44
|
+
value {[type]} -- The value in the pair
|
|
45
|
+
|
|
46
|
+
Raises:
|
|
47
|
+
NotImplementedError: If the subclass inheriting this class
|
|
48
|
+
has not implemented this function
|
|
49
|
+
"""
|
|
50
|
+
raise NotImplementedError
|
|
51
|
+
|
|
52
|
+
def delete(self, key):
|
|
53
|
+
"""
|
|
54
|
+
A method which deletes a key-value pair from the cache.
|
|
55
|
+
|
|
56
|
+
Arguments:
|
|
57
|
+
key {str} -- The key used to identify the entry
|
|
58
|
+
|
|
59
|
+
Raises:
|
|
60
|
+
NotImplementedError: If the subclass inheriting this class
|
|
61
|
+
has not implemented this function
|
|
62
|
+
"""
|
|
63
|
+
raise NotImplementedError
|
|
64
|
+
|
|
65
|
+
def clear(self):
|
|
66
|
+
"""
|
|
67
|
+
A method used to empty the cache.
|
|
68
|
+
|
|
69
|
+
Raises:
|
|
70
|
+
NotImplementedError: If the subclass inheriting this class
|
|
71
|
+
has not implemented this function
|
|
72
|
+
"""
|
|
73
|
+
raise NotImplementedError
|
|
74
|
+
|
|
75
|
+
def create_key(self, request, params):
|
|
76
|
+
"""
|
|
77
|
+
A method used to create a unique key for an entry in the cache.
|
|
78
|
+
Used with URLs that requests fire at.
|
|
79
|
+
|
|
80
|
+
Arguments:
|
|
81
|
+
request {str} -- The key to use to produce a unique key
|
|
82
|
+
|
|
83
|
+
Returns:
|
|
84
|
+
str -- Unique key based on the input URL without query parameters
|
|
85
|
+
"""
|
|
86
|
+
# Validate URL and return URL string without query parameters
|
|
87
|
+
# Parse the original URL
|
|
88
|
+
url_object = urlparse(request)
|
|
89
|
+
|
|
90
|
+
# Extract the query parameters from the URL
|
|
91
|
+
original_query_params = parse_qs(url_object.query)
|
|
92
|
+
|
|
93
|
+
# Update the query parameters with the provided `params` dictionary
|
|
94
|
+
if params is not None and len(params) > 0:
|
|
95
|
+
original_query_params.update(params)
|
|
96
|
+
|
|
97
|
+
# Create a new query string with the updated parameters
|
|
98
|
+
updated_query_string = urlencode(original_query_params, doseq=True)
|
|
99
|
+
|
|
100
|
+
# Combine the netloc, path, and the updated query string to form the new URL
|
|
101
|
+
base_url = f"{url_object.netloc}{url_object.path}"
|
|
102
|
+
if updated_query_string != "":
|
|
103
|
+
base_url = f"{base_url}?{updated_query_string}"
|
|
104
|
+
|
|
105
|
+
return base_url
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
|
|
3
|
+
from zscaler.cache.cache import Cache
|
|
4
|
+
|
|
5
|
+
# Setting up the logger
|
|
6
|
+
logging.basicConfig(
|
|
7
|
+
level=logging.DEBUG, format="%(asctime)s - %(levelname)s - %(message)s"
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class NoOpCache(Cache):
|
|
12
|
+
"""
|
|
13
|
+
This is a disabled Cache Class where no operations occur
|
|
14
|
+
in the cache.
|
|
15
|
+
Implementing the zscaler.cache.cache.Cache abstract class.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
def __init__(self):
|
|
19
|
+
super()
|
|
20
|
+
|
|
21
|
+
def get(self, key):
|
|
22
|
+
"""
|
|
23
|
+
Nothing is returned.
|
|
24
|
+
|
|
25
|
+
Arguments:
|
|
26
|
+
key {str} -- Key to look for
|
|
27
|
+
|
|
28
|
+
Returns:
|
|
29
|
+
None -- No op cache doesn't contain any data to return
|
|
30
|
+
"""
|
|
31
|
+
logging.debug("Serving from cache.")
|
|
32
|
+
return None
|
|
33
|
+
|
|
34
|
+
def contains(self, key):
|
|
35
|
+
"""
|
|
36
|
+
False is returned
|
|
37
|
+
|
|
38
|
+
Arguments:
|
|
39
|
+
key {str} -- Key to look for
|
|
40
|
+
|
|
41
|
+
Returns:
|
|
42
|
+
False -- No data to return so key can never be in cache
|
|
43
|
+
"""
|
|
44
|
+
return False
|
|
45
|
+
|
|
46
|
+
def add(self, key, value):
|
|
47
|
+
"""
|
|
48
|
+
This is a void method.
|
|
49
|
+
|
|
50
|
+
Arguments:
|
|
51
|
+
key {str} -- Key in pair
|
|
52
|
+
value {str} -- Val in pair
|
|
53
|
+
"""
|
|
54
|
+
logging.warning("Saving to cache.")
|
|
55
|
+
|
|
56
|
+
def delete(self, key):
|
|
57
|
+
"""This is a void method. No need to delete anything not contained.
|
|
58
|
+
|
|
59
|
+
Arguments:
|
|
60
|
+
key {str} -- Key to delete
|
|
61
|
+
"""
|
|
62
|
+
pass
|
|
63
|
+
|
|
64
|
+
def clear(self):
|
|
65
|
+
"""
|
|
66
|
+
This is a void method. No need to clear when nothing's stored.
|
|
67
|
+
"""
|
|
68
|
+
pass
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import time
|
|
3
|
+
from urllib.parse import urlparse
|
|
4
|
+
|
|
5
|
+
from zscaler.cache.cache import Cache
|
|
6
|
+
|
|
7
|
+
logger = logging.getLogger("zscaler-sdk-python")
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class ZscalerCache(Cache):
|
|
11
|
+
"""
|
|
12
|
+
This is a base class implementing a Cache using TTL and TTI.
|
|
13
|
+
Implementing the zscaler.cache.cache.Cache abstract class.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
def __init__(self, ttl, tti):
|
|
17
|
+
"""
|
|
18
|
+
Constructor.
|
|
19
|
+
|
|
20
|
+
Arguments:
|
|
21
|
+
ttl {float} -- Time to Live: for cache entries
|
|
22
|
+
tti {float} -- Time to Idle: for cache entries
|
|
23
|
+
"""
|
|
24
|
+
super() # Inherit from parent class
|
|
25
|
+
self._store = {} # key -> {value, TTI, TTL}
|
|
26
|
+
self._time_to_live = ttl
|
|
27
|
+
self._time_to_idle = tti
|
|
28
|
+
|
|
29
|
+
def get(self, key):
|
|
30
|
+
"""
|
|
31
|
+
Retrieves value from cache using key
|
|
32
|
+
|
|
33
|
+
Arguments:
|
|
34
|
+
key {str} -- Desired key
|
|
35
|
+
|
|
36
|
+
Returns:
|
|
37
|
+
str -- Corresponding value to given key
|
|
38
|
+
None -- Unable to find value for this key
|
|
39
|
+
"""
|
|
40
|
+
# Get current time
|
|
41
|
+
now = self._get_current_time()
|
|
42
|
+
# Check if key is in cache and valid
|
|
43
|
+
if self.contains(key):
|
|
44
|
+
entry = self._store[key]
|
|
45
|
+
# Reset TTI
|
|
46
|
+
entry["tti"] = now + self._time_to_idle
|
|
47
|
+
# Return desired value and update cache
|
|
48
|
+
self._clean_cache()
|
|
49
|
+
logger.info(f'Got value from cache for key "{key}".')
|
|
50
|
+
logger.debug(f'Cached value for key {key}: {entry["value"]}')
|
|
51
|
+
return entry["value"]
|
|
52
|
+
|
|
53
|
+
# Return None if key isn't in cache and update cache
|
|
54
|
+
self._clean_cache()
|
|
55
|
+
return None
|
|
56
|
+
|
|
57
|
+
def contains(self, key):
|
|
58
|
+
"""
|
|
59
|
+
Returns existence of key in cache, as boolean
|
|
60
|
+
|
|
61
|
+
Arguments:
|
|
62
|
+
key {str} -- Desired key
|
|
63
|
+
|
|
64
|
+
Returns:
|
|
65
|
+
bool -- Existence of key in cache
|
|
66
|
+
"""
|
|
67
|
+
return key in self._store and self._is_valid_entry(self._store[key])
|
|
68
|
+
|
|
69
|
+
def add(self, key: str, value: tuple):
|
|
70
|
+
"""
|
|
71
|
+
Adds a key-value pair to the cache.
|
|
72
|
+
|
|
73
|
+
Arguments:
|
|
74
|
+
key {str} -- Key in pair
|
|
75
|
+
value {tuple} -- Tuple of response and response body
|
|
76
|
+
"""
|
|
77
|
+
if type(key) == str and (type(value) != list or type(value[1]) != list):
|
|
78
|
+
# Get current time
|
|
79
|
+
now = self._get_current_time()
|
|
80
|
+
|
|
81
|
+
# Add new entry to cache with timers
|
|
82
|
+
self._store[key] = {
|
|
83
|
+
"value": value,
|
|
84
|
+
"tti": now + self._time_to_idle,
|
|
85
|
+
"ttl": now + self._time_to_live,
|
|
86
|
+
}
|
|
87
|
+
logger.info(f'Added to cache value for key "{key}".')
|
|
88
|
+
logger.debug(f"Cached value for key {key}: {value}.")
|
|
89
|
+
# Update cache
|
|
90
|
+
self._clean_cache()
|
|
91
|
+
|
|
92
|
+
def delete(self, key):
|
|
93
|
+
"""
|
|
94
|
+
Delete a key-value pair from the cache.
|
|
95
|
+
|
|
96
|
+
Arguments:
|
|
97
|
+
key {str} -- Desired key
|
|
98
|
+
"""
|
|
99
|
+
logger.info(f'Removing value from cache for key "{key}".')
|
|
100
|
+
# Make sure key is in cache
|
|
101
|
+
if key in self._store:
|
|
102
|
+
# Delete entry
|
|
103
|
+
del self._store[key]
|
|
104
|
+
logger.info(f'Removed value from cache for key "{key}".')
|
|
105
|
+
url_object = urlparse(key)
|
|
106
|
+
base_url = f"{url_object.netloc}{url_object.path}"
|
|
107
|
+
for other_key in self._store.keys():
|
|
108
|
+
other_url_object = urlparse(other_key)
|
|
109
|
+
other_base_url = f"{other_url_object.netloc}{other_url_object.path}"
|
|
110
|
+
if not self._is_valid_entry(
|
|
111
|
+
self._store[other_key]
|
|
112
|
+
) and other_base_url.startswith(base_url):
|
|
113
|
+
del self._store[other_key]
|
|
114
|
+
logger.info(f'Removed also value from cache for key "{other_key}".')
|
|
115
|
+
|
|
116
|
+
def clear(self):
|
|
117
|
+
"""
|
|
118
|
+
Clear the cache.
|
|
119
|
+
"""
|
|
120
|
+
self._store.clear()
|
|
121
|
+
logger.info("Cleared the cache.")
|
|
122
|
+
|
|
123
|
+
def _clean_cache(self):
|
|
124
|
+
"""
|
|
125
|
+
Updates cache by removing expired entries at time of call
|
|
126
|
+
"""
|
|
127
|
+
expired = []
|
|
128
|
+
# Check every entry
|
|
129
|
+
for key in self._store.keys():
|
|
130
|
+
# If not valid, delete
|
|
131
|
+
if not self._is_valid_entry(self._store[key]):
|
|
132
|
+
expired.append(key)
|
|
133
|
+
# Delete keys
|
|
134
|
+
for expired_key in expired:
|
|
135
|
+
self.delete(expired_key)
|
|
136
|
+
|
|
137
|
+
def _is_valid_entry(self, entry):
|
|
138
|
+
"""
|
|
139
|
+
Determines if a given cache entry is not expired.
|
|
140
|
+
|
|
141
|
+
Args:
|
|
142
|
+
entry (dict): An entry from the cache composed of value,
|
|
143
|
+
TTI, and TTL
|
|
144
|
+
|
|
145
|
+
Returns:
|
|
146
|
+
bool: Boolean value representing if entry is expired
|
|
147
|
+
"""
|
|
148
|
+
# Get Current time
|
|
149
|
+
now = self._get_current_time()
|
|
150
|
+
# Check timers and compare against current time
|
|
151
|
+
timers = [entry["tti"], entry["ttl"]]
|
|
152
|
+
return not any(timer <= now for timer in timers)
|
|
153
|
+
|
|
154
|
+
def _get_current_time(self):
|
|
155
|
+
"""
|
|
156
|
+
Helper function to get current time
|
|
157
|
+
|
|
158
|
+
Returns:
|
|
159
|
+
float: value representing the number of seconds since the epoch
|
|
160
|
+
"""
|
|
161
|
+
return time.time()
|
zscaler/constants.py
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import os
|
|
2
|
+
|
|
3
|
+
ZPA_BASE_URLS = {
|
|
4
|
+
"PRODUCTION": "https://config.private.zscaler.com",
|
|
5
|
+
"ZPATWO": "https://config.zpatwo.net",
|
|
6
|
+
"BETA": "https://config.zpabeta.net",
|
|
7
|
+
"GOV": "https://config.zpagov.net",
|
|
8
|
+
"GOVUS": "https://config.zpagov.us",
|
|
9
|
+
"PREVIEW": "https://config.zpapreview.net",
|
|
10
|
+
"QA": "https://config.qa.zpath.net",
|
|
11
|
+
"QA2": "https://pdx2-zpa-config.qa2.zpath.net",
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
RETRYABLE_STATUS_CODES = {429, 500, 502, 503, 504}
|
|
15
|
+
MAX_RETRIES = 5
|
|
16
|
+
BACKOFF_FACTOR = 1
|
|
17
|
+
BACKOFF_BASE_DURATION = 2
|
|
18
|
+
|
|
19
|
+
DATETIME_FORMAT = "%a, %d %b %Y %H:%M:%S %Z"
|
|
20
|
+
|
|
21
|
+
EPOCH_YEAR = 1970
|
|
22
|
+
EPOCH_MONTH = 1
|
|
23
|
+
EPOCH_DAY = 1
|
|
24
|
+
|
|
25
|
+
_GLOBAL_YAML_PATH = os.path.join(os.path.expanduser("~"), ".zpa", "zpa.yaml")
|
|
26
|
+
_LOCAL_YAML_PATH = os.path.join(os.getcwd(), "zpa.yaml")
|
|
File without changes
|
zscaler/errors/error.py
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import json
|
|
2
|
+
|
|
3
|
+
from zscaler.errors.error import Error
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class HTTPError(Error):
|
|
7
|
+
def __init__(self, url, response_details, response_body):
|
|
8
|
+
self.status = response_details.status
|
|
9
|
+
self.url = url
|
|
10
|
+
self.response_headers = response_details.headers
|
|
11
|
+
self.stack = ""
|
|
12
|
+
self.message = f"HTTP {self.status} {response_body}"
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class ZscalerAPIError(Error):
|
|
16
|
+
def __init__(self, url, response, response_body):
|
|
17
|
+
self.status = response.status_code
|
|
18
|
+
self.url = url
|
|
19
|
+
self.response_body = json.dumps(response_body)
|
|
20
|
+
self.message = f"ZSCALER HTTP {url} {self.status} {self.response_body}"
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
from zscaler.errors.error import Error
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
# ZPA API Errors
|
|
5
|
+
class ZPAAPIError(Error):
|
|
6
|
+
def __init__(self, url, response_details, response_body):
|
|
7
|
+
self.status = response_details.status
|
|
8
|
+
self.error_id = response_body.get("id", "")
|
|
9
|
+
self.reason = response_body.get("reason", "")
|
|
10
|
+
self.params = response_body.get("params", [])
|
|
11
|
+
|
|
12
|
+
params_string = ", ".join(self.params)
|
|
13
|
+
|
|
14
|
+
self.url = url
|
|
15
|
+
self.headers = response_details.headers
|
|
16
|
+
self.stack = ""
|
|
17
|
+
|
|
18
|
+
self.message = (
|
|
19
|
+
f"ZPA HTTP {self.status} {self.error_id} "
|
|
20
|
+
f"{self.reason}\nParameters: {params_string}"
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
# ZIA API Errors
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .exceptions import HTTPException, ZpaAPIException # noqa
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import json
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
# Zscaler Base Exceptions
|
|
5
|
+
class ZscalerBaseException(Exception):
|
|
6
|
+
def __init__(self, url, response, response_body):
|
|
7
|
+
self.status = response.status_code
|
|
8
|
+
self.url = url
|
|
9
|
+
self.response_body = json.dumps(response_body)
|
|
10
|
+
self.message = f"ZSCALER HTTP {url} {self.status} {self.response_body}"
|
|
11
|
+
|
|
12
|
+
def __repr__(self):
|
|
13
|
+
return str({"message": self.message})
|
|
14
|
+
|
|
15
|
+
def __str__(self):
|
|
16
|
+
return self.message
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class HTTPException(ZscalerBaseException):
|
|
20
|
+
pass
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class ZscalerAPIException(ZscalerBaseException):
|
|
24
|
+
pass
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
# Zscaler Private Access specific exceptions (Potential Future Use)
|
|
28
|
+
class ZpaBaseException(Exception):
|
|
29
|
+
pass
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class ZpaAPIException(ZpaBaseException):
|
|
33
|
+
pass
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class RateLimitExceededError(Exception):
|
|
37
|
+
"""Raised when the API rate limit is exceeded."""
|
|
38
|
+
|
|
39
|
+
pass
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class RetryLimitExceededError(Exception):
|
|
43
|
+
"""Raised when the maximum number of retries is exceeded."""
|
|
44
|
+
|
|
45
|
+
pass
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class CacheError(Exception):
|
|
49
|
+
"""Raised for errors related to caching operations."""
|
|
50
|
+
|
|
51
|
+
pass
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class BadRequestError(Exception):
|
|
55
|
+
"""Raised when the API responds with a 400 status code."""
|
|
56
|
+
|
|
57
|
+
pass
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class UnauthorizedError(Exception):
|
|
61
|
+
pass
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class ForbiddenError(Exception):
|
|
65
|
+
pass
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
class NotFoundError(Exception):
|
|
69
|
+
pass
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class APIClientError(Exception):
|
|
73
|
+
"""General exception related to the API client operations."""
|
|
74
|
+
|
|
75
|
+
pass
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
class InvalidCloudEnvironmentError(Exception):
|
|
79
|
+
"""Raised when an unrecognized cloud environment is specified."""
|
|
80
|
+
|
|
81
|
+
def __init__(self, cloud: str):
|
|
82
|
+
self.cloud = cloud
|
|
83
|
+
super().__init__(f"Unrecognized cloud environment: {self.cloud}")
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
class TokenExpirationError(Exception):
|
|
87
|
+
"""Raised when the authentication token has expired."""
|
|
88
|
+
|
|
89
|
+
pass
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
class TokenRefreshError(Exception):
|
|
93
|
+
"""Raised when there's an issue refreshing the authentication token."""
|
|
94
|
+
|
|
95
|
+
pass
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
class HeaderUpdateError(Exception):
|
|
99
|
+
"""Raised if there's a problem updating the session headers."""
|
|
100
|
+
|
|
101
|
+
pass
|
zscaler/logger.py
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import os
|
|
3
|
+
from http.client import HTTPConnection
|
|
4
|
+
|
|
5
|
+
LOG_FORMAT = "%(asctime)s - %(name)s - %(module)s - %(levelname)s - %(message)s"
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def setup_logging(logger_name="zscaler-sdk-python"):
|
|
9
|
+
"""
|
|
10
|
+
Set up logging with specified level and logger name.
|
|
11
|
+
Log level is controlled via ZSCALER_SDK_VERBOSE environment variable.
|
|
12
|
+
Logging can be enabled/disabled via ZSCALER_SDK_LOG environment variable.
|
|
13
|
+
|
|
14
|
+
Parameters:
|
|
15
|
+
- logger_name (str, optional): Logger name. Defaults to "zscaler-sdk-python".
|
|
16
|
+
"""
|
|
17
|
+
logging_enabled = os.getenv("ZSCALER_SDK_LOG", "false").lower() == "true"
|
|
18
|
+
|
|
19
|
+
if not logging_enabled:
|
|
20
|
+
# If logging is not enabled, set up a null handler
|
|
21
|
+
logging.disable(logging.INFO)
|
|
22
|
+
return
|
|
23
|
+
|
|
24
|
+
verbose = os.getenv("ZSCALER_SDK_VERBOSE", "false").lower() == "true"
|
|
25
|
+
log_level = logging.DEBUG if verbose else logging.INFO
|
|
26
|
+
HTTPConnection.debuglevel = 0
|
|
27
|
+
# Create a logger with the specified name
|
|
28
|
+
logger = logging.getLogger(logger_name)
|
|
29
|
+
default_logger = logging.getLogger()
|
|
30
|
+
|
|
31
|
+
# If the logger already has handlers, remove them to avoid duplicate logging
|
|
32
|
+
for handler in logger.handlers[:]:
|
|
33
|
+
logger.removeHandler(handler)
|
|
34
|
+
|
|
35
|
+
for handler in default_logger.handlers[:]:
|
|
36
|
+
default_logger.removeHandler(handler)
|
|
37
|
+
|
|
38
|
+
# Set log level
|
|
39
|
+
logger.setLevel(log_level)
|
|
40
|
+
default_logger.setLevel(log_level)
|
|
41
|
+
logging.basicConfig(level=log_level)
|
|
42
|
+
|
|
43
|
+
# Create a stream handler with the specified level and formatter
|
|
44
|
+
stream_handler = logging.StreamHandler()
|
|
45
|
+
stream_handler.setLevel(log_level)
|
|
46
|
+
log_formatter = logging.Formatter(LOG_FORMAT)
|
|
47
|
+
stream_handler.setFormatter(log_formatter)
|
|
48
|
+
|
|
49
|
+
# Add the handler to the logger
|
|
50
|
+
logger.addHandler(stream_handler)
|
|
51
|
+
|
|
52
|
+
# Option: Add FileHandler if you want logs to be written to a file.
|
|
53
|
+
if os.getenv("LOG_TO_FILE", "false").lower() == "true":
|
|
54
|
+
file_handler = logging.FileHandler(os.getenv("LOG_FILE_PATH", "sdk.log"))
|
|
55
|
+
file_handler.setLevel(log_level)
|
|
56
|
+
file_handler.setFormatter(log_formatter)
|
|
57
|
+
logger.addHandler(file_handler)
|
|
File without changes
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import threading
|
|
2
|
+
import time
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class RateLimiter:
|
|
6
|
+
def __init__(
|
|
7
|
+
self, get_limit, post_put_delete_limit, get_freq, post_put_delete_freq
|
|
8
|
+
):
|
|
9
|
+
self.lock = threading.Lock()
|
|
10
|
+
self.get_requests = []
|
|
11
|
+
self.post_put_delete_requests = []
|
|
12
|
+
self.get_limit = get_limit
|
|
13
|
+
self.post_put_delete_limit = post_put_delete_limit
|
|
14
|
+
self.get_freq = get_freq
|
|
15
|
+
self.post_put_delete_freq = post_put_delete_freq
|
|
16
|
+
|
|
17
|
+
def wait(self, method):
|
|
18
|
+
with self.lock:
|
|
19
|
+
now = time.time()
|
|
20
|
+
|
|
21
|
+
if method == "GET":
|
|
22
|
+
if len(self.get_requests) >= self.get_limit:
|
|
23
|
+
oldest_request = self.get_requests[0]
|
|
24
|
+
if now - oldest_request < self.get_freq:
|
|
25
|
+
d = self.get_freq - (now - oldest_request)
|
|
26
|
+
return True, d
|
|
27
|
+
self.get_requests.pop(0)
|
|
28
|
+
self.get_requests.append(now)
|
|
29
|
+
|
|
30
|
+
elif method in ["POST", "PUT", "DELETE"]:
|
|
31
|
+
if len(self.post_put_delete_requests) >= self.post_put_delete_limit:
|
|
32
|
+
oldest_request = self.post_put_delete_requests[0]
|
|
33
|
+
if now - oldest_request < self.post_put_delete_freq:
|
|
34
|
+
d = self.post_put_delete_freq - (now - oldest_request)
|
|
35
|
+
return True, d
|
|
36
|
+
self.post_put_delete_requests.pop(0)
|
|
37
|
+
self.post_put_delete_requests.append(now)
|
|
38
|
+
|
|
39
|
+
return False, 0
|
zscaler/user_agent.py
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import platform
|
|
2
|
+
|
|
3
|
+
from zscaler import __version__ as VERSION
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class UserAgent:
|
|
7
|
+
SDK_NAME = "zscaler-sdk-python"
|
|
8
|
+
PYTHON = "python"
|
|
9
|
+
|
|
10
|
+
def __init__(self, user_agent_extra=None):
|
|
11
|
+
python_version = platform.python_version()
|
|
12
|
+
os_name = platform.system()
|
|
13
|
+
os_version = platform.release()
|
|
14
|
+
self._user_agent_string = (
|
|
15
|
+
f"{UserAgent.SDK_NAME}/{VERSION} "
|
|
16
|
+
f"{UserAgent.PYTHON}/{python_version} "
|
|
17
|
+
f"{os_name}/{os_version}"
|
|
18
|
+
)
|
|
19
|
+
if user_agent_extra:
|
|
20
|
+
self._user_agent_string += f" {user_agent_extra}"
|
|
21
|
+
|
|
22
|
+
def get_user_agent_string(self):
|
|
23
|
+
return self._user_agent_string
|