zscaler-sdk-python 0.3.0__tar.gz → 0.4.0__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.
Files changed (79) hide show
  1. {zscaler_sdk_python-0.3.0 → zscaler_sdk_python-0.4.0}/PKG-INFO +6 -5
  2. {zscaler_sdk_python-0.3.0 → zscaler_sdk_python-0.4.0}/pyproject.toml +9 -7
  3. {zscaler_sdk_python-0.3.0 → zscaler_sdk_python-0.4.0}/zscaler/__init__.py +1 -1
  4. {zscaler_sdk_python-0.3.0 → zscaler_sdk_python-0.4.0}/zscaler/cache/zscaler_cache.py +1 -1
  5. {zscaler_sdk_python-0.3.0 → zscaler_sdk_python-0.4.0}/zscaler/constants.py +1 -1
  6. {zscaler_sdk_python-0.3.0 → zscaler_sdk_python-0.4.0}/zscaler/zpa/__init__.py +140 -85
  7. {zscaler_sdk_python-0.3.0 → zscaler_sdk_python-0.4.0}/zscaler/zpa/app_segments.py +159 -77
  8. {zscaler_sdk_python-0.3.0 → zscaler_sdk_python-0.4.0}/zscaler/zpa/app_segments_inspection.py +45 -54
  9. {zscaler_sdk_python-0.3.0 → zscaler_sdk_python-0.4.0}/zscaler/zpa/app_segments_pra.py +49 -59
  10. zscaler_sdk_python-0.4.0/zscaler/zpa/authdomains.py +41 -0
  11. {zscaler_sdk_python-0.3.0 → zscaler_sdk_python-0.4.0}/zscaler/zpa/certificates.py +90 -59
  12. {zscaler_sdk_python-0.3.0 → zscaler_sdk_python-0.4.0}/zscaler/zpa/client.py +3 -0
  13. {zscaler_sdk_python-0.3.0 → zscaler_sdk_python-0.4.0}/zscaler/zpa/connectors.py +139 -88
  14. {zscaler_sdk_python-0.3.0 → zscaler_sdk_python-0.4.0}/zscaler/zpa/emergency_access.py +16 -1
  15. {zscaler_sdk_python-0.3.0 → zscaler_sdk_python-0.4.0}/zscaler/zpa/idp.py +18 -3
  16. {zscaler_sdk_python-0.3.0 → zscaler_sdk_python-0.4.0}/zscaler/zpa/inspection.py +215 -227
  17. {zscaler_sdk_python-0.3.0 → zscaler_sdk_python-0.4.0}/zscaler/zpa/machine_groups.py +24 -10
  18. zscaler_sdk_python-0.4.0/zscaler/zpa/microtenants.py +217 -0
  19. zscaler_sdk_python-0.4.0/zscaler/zpa/policies.py +2709 -0
  20. {zscaler_sdk_python-0.3.0 → zscaler_sdk_python-0.4.0}/zscaler/zpa/privileged_remote_access.py +48 -5
  21. {zscaler_sdk_python-0.3.0 → zscaler_sdk_python-0.4.0}/zscaler/zpa/provisioning.py +35 -16
  22. {zscaler_sdk_python-0.3.0 → zscaler_sdk_python-0.4.0}/zscaler/zpa/segment_groups.py +51 -41
  23. {zscaler_sdk_python-0.3.0 → zscaler_sdk_python-0.4.0}/zscaler/zpa/server_groups.py +34 -12
  24. {zscaler_sdk_python-0.3.0 → zscaler_sdk_python-0.4.0}/zscaler/zpa/servers.py +42 -22
  25. {zscaler_sdk_python-0.3.0 → zscaler_sdk_python-0.4.0}/zscaler/zpa/service_edges.py +241 -61
  26. {zscaler_sdk_python-0.3.0 → zscaler_sdk_python-0.4.0}/zscaler/zpa/trusted_networks.py +20 -5
  27. zscaler_sdk_python-0.3.0/zscaler/zpa/policies.py +0 -857
  28. zscaler_sdk_python-0.3.0/zscaler/zpa/policiesv2.py +0 -1667
  29. {zscaler_sdk_python-0.3.0 → zscaler_sdk_python-0.4.0}/LICENSE.md +0 -0
  30. {zscaler_sdk_python-0.3.0 → zscaler_sdk_python-0.4.0}/README.md +0 -0
  31. {zscaler_sdk_python-0.3.0 → zscaler_sdk_python-0.4.0}/zscaler/cache/__init__.py +0 -0
  32. {zscaler_sdk_python-0.3.0 → zscaler_sdk_python-0.4.0}/zscaler/cache/cache.py +0 -0
  33. {zscaler_sdk_python-0.3.0 → zscaler_sdk_python-0.4.0}/zscaler/cache/no_op_cache.py +0 -0
  34. {zscaler_sdk_python-0.3.0 → zscaler_sdk_python-0.4.0}/zscaler/errors/__init__.py +0 -0
  35. {zscaler_sdk_python-0.3.0 → zscaler_sdk_python-0.4.0}/zscaler/errors/error.py +0 -0
  36. {zscaler_sdk_python-0.3.0 → zscaler_sdk_python-0.4.0}/zscaler/errors/http_error.py +0 -0
  37. {zscaler_sdk_python-0.3.0 → zscaler_sdk_python-0.4.0}/zscaler/errors/zscaler_api_error.py +0 -0
  38. {zscaler_sdk_python-0.3.0 → zscaler_sdk_python-0.4.0}/zscaler/exceptions/__init__.py +0 -0
  39. {zscaler_sdk_python-0.3.0 → zscaler_sdk_python-0.4.0}/zscaler/exceptions/exceptions.py +0 -0
  40. {zscaler_sdk_python-0.3.0 → zscaler_sdk_python-0.4.0}/zscaler/logger.py +0 -0
  41. {zscaler_sdk_python-0.3.0 → zscaler_sdk_python-0.4.0}/zscaler/ratelimiter/__init__.py +0 -0
  42. {zscaler_sdk_python-0.3.0 → zscaler_sdk_python-0.4.0}/zscaler/ratelimiter/ratelimiter.py +0 -0
  43. {zscaler_sdk_python-0.3.0 → zscaler_sdk_python-0.4.0}/zscaler/user_agent.py +0 -0
  44. {zscaler_sdk_python-0.3.0 → zscaler_sdk_python-0.4.0}/zscaler/utils.py +0 -0
  45. {zscaler_sdk_python-0.3.0 → zscaler_sdk_python-0.4.0}/zscaler/zia/__init__.py +0 -0
  46. {zscaler_sdk_python-0.3.0 → zscaler_sdk_python-0.4.0}/zscaler/zia/activate.py +0 -0
  47. {zscaler_sdk_python-0.3.0 → zscaler_sdk_python-0.4.0}/zscaler/zia/admin_and_role_management.py +0 -0
  48. {zscaler_sdk_python-0.3.0 → zscaler_sdk_python-0.4.0}/zscaler/zia/apptotal.py +0 -0
  49. {zscaler_sdk_python-0.3.0 → zscaler_sdk_python-0.4.0}/zscaler/zia/audit_logs.py +0 -0
  50. {zscaler_sdk_python-0.3.0 → zscaler_sdk_python-0.4.0}/zscaler/zia/authentication_settings.py +0 -0
  51. {zscaler_sdk_python-0.3.0 → zscaler_sdk_python-0.4.0}/zscaler/zia/client.py +0 -0
  52. {zscaler_sdk_python-0.3.0 → zscaler_sdk_python-0.4.0}/zscaler/zia/cloud_apps.py +0 -0
  53. {zscaler_sdk_python-0.3.0 → zscaler_sdk_python-0.4.0}/zscaler/zia/device_management.py +0 -0
  54. {zscaler_sdk_python-0.3.0 → zscaler_sdk_python-0.4.0}/zscaler/zia/dlp.py +0 -0
  55. {zscaler_sdk_python-0.3.0 → zscaler_sdk_python-0.4.0}/zscaler/zia/errors.py +0 -0
  56. {zscaler_sdk_python-0.3.0 → zscaler_sdk_python-0.4.0}/zscaler/zia/firewall.py +0 -0
  57. {zscaler_sdk_python-0.3.0 → zscaler_sdk_python-0.4.0}/zscaler/zia/forwarding_control.py +0 -0
  58. {zscaler_sdk_python-0.3.0 → zscaler_sdk_python-0.4.0}/zscaler/zia/isolation_profile.py +0 -0
  59. {zscaler_sdk_python-0.3.0 → zscaler_sdk_python-0.4.0}/zscaler/zia/labels.py +0 -0
  60. {zscaler_sdk_python-0.3.0 → zscaler_sdk_python-0.4.0}/zscaler/zia/locations.py +0 -0
  61. {zscaler_sdk_python-0.3.0 → zscaler_sdk_python-0.4.0}/zscaler/zia/sandbox.py +0 -0
  62. {zscaler_sdk_python-0.3.0 → zscaler_sdk_python-0.4.0}/zscaler/zia/security.py +0 -0
  63. {zscaler_sdk_python-0.3.0 → zscaler_sdk_python-0.4.0}/zscaler/zia/ssl_inspection.py +0 -0
  64. {zscaler_sdk_python-0.3.0 → zscaler_sdk_python-0.4.0}/zscaler/zia/traffic.py +0 -0
  65. {zscaler_sdk_python-0.3.0 → zscaler_sdk_python-0.4.0}/zscaler/zia/url_categories.py +0 -0
  66. {zscaler_sdk_python-0.3.0 → zscaler_sdk_python-0.4.0}/zscaler/zia/url_filtering.py +0 -0
  67. {zscaler_sdk_python-0.3.0 → zscaler_sdk_python-0.4.0}/zscaler/zia/users.py +0 -0
  68. {zscaler_sdk_python-0.3.0 → zscaler_sdk_python-0.4.0}/zscaler/zia/web_dlp.py +0 -0
  69. {zscaler_sdk_python-0.3.0 → zscaler_sdk_python-0.4.0}/zscaler/zia/workload_groups.py +0 -0
  70. {zscaler_sdk_python-0.3.0 → zscaler_sdk_python-0.4.0}/zscaler/zia/zpa_gateway.py +0 -0
  71. {zscaler_sdk_python-0.3.0 → zscaler_sdk_python-0.4.0}/zscaler/zpa/README.md +0 -0
  72. {zscaler_sdk_python-0.3.0 → zscaler_sdk_python-0.4.0}/zscaler/zpa/cloud_connector_groups.py +0 -0
  73. {zscaler_sdk_python-0.3.0 → zscaler_sdk_python-0.4.0}/zscaler/zpa/errors.py +0 -0
  74. {zscaler_sdk_python-0.3.0 → zscaler_sdk_python-0.4.0}/zscaler/zpa/isolation.py +0 -0
  75. {zscaler_sdk_python-0.3.0 → zscaler_sdk_python-0.4.0}/zscaler/zpa/lss.py +0 -0
  76. {zscaler_sdk_python-0.3.0 → zscaler_sdk_python-0.4.0}/zscaler/zpa/posture_profiles.py +0 -0
  77. {zscaler_sdk_python-0.3.0 → zscaler_sdk_python-0.4.0}/zscaler/zpa/saml_attributes.py +0 -0
  78. {zscaler_sdk_python-0.3.0 → zscaler_sdk_python-0.4.0}/zscaler/zpa/scim_attributes.py +0 -0
  79. {zscaler_sdk_python-0.3.0 → zscaler_sdk_python-0.4.0}/zscaler/zpa/scim_groups.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: zscaler-sdk-python
3
- Version: 0.3.0
3
+ Version: 0.4.0
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
@@ -23,13 +23,14 @@ Classifier: Topic :: Software Development :: Libraries :: Python Modules
23
23
  Provides-Extra: dev
24
24
  Requires-Dist: aenum ; extra == "dev"
25
25
  Requires-Dist: arrow
26
- Requires-Dist: black ; extra == "dev"
27
- Requires-Dist: certifi
26
+ Requires-Dist: black (>=24.3.0) ; extra == "dev"
27
+ Requires-Dist: certifi (>=2024.2.2)
28
28
  Requires-Dist: charset-normalizer
29
- Requires-Dist: cryptography (>=3.4,<43.0)
29
+ Requires-Dist: cryptography (>=42.0.7)
30
30
  Requires-Dist: flake8
31
31
  Requires-Dist: flatdict
32
32
  Requires-Dist: idna
33
+ Requires-Dist: okta (>=2.9.7)
33
34
  Requires-Dist: pycryptodomex
34
35
  Requires-Dist: pydash ; extra == "dev"
35
36
  Requires-Dist: python-box (>=7.1.1,<8.0.0)
@@ -37,7 +38,7 @@ Requires-Dist: python-dateutil
37
38
  Requires-Dist: pytz
38
39
  Requires-Dist: pyyaml
39
40
  Requires-Dist: requests (>=2.32.0)
40
- Requires-Dist: responses
41
+ Requires-Dist: responses (>=0.25.0)
41
42
  Requires-Dist: restfly
42
43
  Requires-Dist: six
43
44
  Requires-Dist: xmltodict
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "zscaler-sdk-python"
3
- version = "0.3.0"
3
+ version = "0.4.0"
4
4
  description = "Official Python SDK for the Zscaler Products (Beta)"
5
5
  authors = ["Zscaler, Inc. <devrel@zscaler.com>"]
6
6
  license = "MIT"
@@ -30,13 +30,13 @@ packages = [
30
30
  [tool.poetry.dependencies]
31
31
  python = ">=3.8,<4.0"
32
32
  arrow = "*"
33
- certifi = "*"
33
+ certifi = ">=2024.2.2"
34
34
  charset-normalizer = "*"
35
35
  idna = "*"
36
36
  python-box = "^7.1.1"
37
37
  python-dateutil = "*"
38
38
  requests = ">=2.32.0"
39
- responses = "*"
39
+ responses = ">=0.25.0"
40
40
  restfly = "*"
41
41
  six = "*"
42
42
  flatdict = "*"
@@ -48,12 +48,14 @@ aenum = "*"
48
48
  pydash = "*"
49
49
  flake8 = "*"
50
50
  pytz = "*"
51
- black = "*"
52
- cryptography = ">=3.4,<43.0"
51
+ black = ">=24.3.0"
52
+ # cryptography = ">=3.4,<43.0"
53
+ cryptography = ">=42.0.7"
54
+ okta = ">=2.9.7"
53
55
 
54
56
  [tool.poetry.dev-dependencies]
55
- black = "24.3.0"
56
- pytest = "^8.2.1"
57
+ black = ">=24.3.0"
58
+ pytest = ">=8.2.1"
57
59
  pytest-asyncio = ">=0.23.7"
58
60
  pytest-mock = "*"
59
61
  pytest-recording = "*"
@@ -29,7 +29,7 @@ __license__ = "MIT"
29
29
  __contributors__ = [
30
30
  "William Guilherme",
31
31
  ]
32
- __version__ = "0.3.0"
32
+ __version__ = "0.4.0"
33
33
 
34
34
  from zscaler.zia import ZIAClientHelper # noqa
35
35
  from zscaler.zpa import ZPAClientHelper # noqa
@@ -115,8 +115,8 @@ class ZscalerCache(Cache):
115
115
  """
116
116
  Clear the cache.
117
117
  """
118
+ logger.info("Clearing the entire cache.")
118
119
  self._store.clear()
119
- logger.info("Cleared the cache.")
120
120
 
121
121
  def _clean_cache(self):
122
122
  """
@@ -14,7 +14,7 @@ ZPA_BASE_URLS = {
14
14
 
15
15
  DEV_AUTH_URL = "https://authn1.dev.zpath.net/authn/v1/oauth/token"
16
16
 
17
- RETRYABLE_STATUS_CODES = {429, 500, 502, 503, 504}
17
+ RETRYABLE_STATUS_CODES = {409, 429, 500, 502, 503, 504}
18
18
  MAX_RETRIES = 5
19
19
  BACKOFF_FACTOR = 1
20
20
  BACKOFF_BASE_DURATION = 2
@@ -26,6 +26,7 @@ from zscaler.utils import (
26
26
  retry_with_backoff,
27
27
  snake_to_camel,
28
28
  )
29
+ from zscaler.zpa.authdomains import AuthDomainsAPI
29
30
  from zscaler.zpa.app_segments import ApplicationSegmentAPI
30
31
  from zscaler.zpa.app_segments_inspection import AppSegmentsInspectionAPI
31
32
  from zscaler.zpa.app_segments_pra import AppSegmentsPRAAPI
@@ -39,8 +40,8 @@ from zscaler.zpa.inspection import InspectionControllerAPI
39
40
  from zscaler.zpa.isolation import IsolationAPI
40
41
  from zscaler.zpa.lss import LSSConfigControllerAPI
41
42
  from zscaler.zpa.machine_groups import MachineGroupsAPI
43
+ from zscaler.zpa.microtenants import MicrotenantsAPI
42
44
  from zscaler.zpa.policies import PolicySetsAPI
43
- from zscaler.zpa.policiesv2 import PolicySetsV2API
44
45
  from zscaler.zpa.posture_profiles import PostureProfilesAPI
45
46
  from zscaler.zpa.privileged_remote_access import PrivilegedRemoteAccessAPI
46
47
  from zscaler.zpa.provisioning import ProvisioningKeyAPI
@@ -82,12 +83,12 @@ class ZPAClientHelper(ZPAClient):
82
83
  client_secret,
83
84
  customer_id,
84
85
  cloud,
86
+ microtenant_id=None,
85
87
  timeout=240,
86
88
  cache=None,
87
89
  fail_safe=False,
88
90
  ):
89
91
  # Initialize rate limiter
90
- # You may want to adjust these parameters as per your rate limit configuration
91
92
  self.rate_limiter = RateLimiter(
92
93
  get_limit=20, # Adjusted to allow 20 GET requests per 10 seconds
93
94
  post_put_delete_limit=10, # Adjusted to allow 10 POST/PUT/DELETE requests per 10 seconds
@@ -95,7 +96,6 @@ class ZPAClientHelper(ZPAClient):
95
96
  post_put_delete_freq=10, # Adjust frequency to 10 seconds
96
97
  )
97
98
 
98
- # Validate cloud value
99
99
  if cloud not in ZPA_BASE_URLS:
100
100
  valid_clouds = ", ".join(ZPA_BASE_URLS.keys())
101
101
  raise ValueError(
@@ -103,22 +103,20 @@ class ZPAClientHelper(ZPAClient):
103
103
  f"Please use one of the following supported values: {valid_clouds}"
104
104
  )
105
105
 
106
- # Continue with existing initialization...
107
- # Select the appropriate URL
108
106
  self.baseurl = ZPA_BASE_URLS.get(cloud, ZPA_BASE_URLS["PRODUCTION"])
109
-
110
107
  self.timeout = timeout
111
108
  self.client_id = client_id
112
109
  self.client_secret = client_secret
113
110
  self.customer_id = customer_id
114
111
  self.cloud = cloud
112
+ self.microtenant_id = microtenant_id or os.getenv("ZPA_MICROTENANT_ID")
115
113
  self.url = f"{self.baseurl}/mgmtconfig/v1/admin/customers/{customer_id}"
116
114
  self.user_config_url = f"{self.baseurl}/userconfig/v1/customers/{customer_id}"
117
115
  self.v2_url = f"{self.baseurl}/mgmtconfig/v2/admin/customers/{customer_id}"
118
116
  self.v2_lss_url = f"{self.baseurl}/mgmtconfig/v2/admin/lssConfig/customers/{customer_id}"
119
117
  self.cbi_url = f"{self.baseurl}/cbiconfig/cbi/api/customers/{customer_id}"
120
118
  self.fail_safe = fail_safe
121
- # Cache setup
119
+
122
120
  cache_enabled = os.environ.get("ZSCALER_CLIENT_CACHE_ENABLED", "true").lower() == "true"
123
121
  if cache is None:
124
122
  if cache_enabled:
@@ -130,28 +128,28 @@ class ZPAClientHelper(ZPAClient):
130
128
  else:
131
129
  self.cache = cache
132
130
 
133
- # Initialize user-agent
134
131
  ua = UserAgent()
135
132
  self.user_agent = ua.get_user_agent_string()
133
+ self.access_token = None
134
+ self.headers = {}
136
135
  self.refreshToken()
137
136
 
138
137
  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("Failed to login using provided credentials, response: %s", response)
143
- raise Exception("Failed to login using provided credentials.")
144
- self.access_token = response.json().get("access_token")
145
- self.headers = {
146
- "Content-Type": "application/json",
147
- "Accept": "application/json",
148
- "Authorization": f"Bearer {self.access_token}",
149
- "User-Agent": self.user_agent,
150
- }
138
+ if not self.access_token or is_token_expired(self.access_token):
139
+ response = self.login()
140
+ if response is None or response.status_code > 299 or not response.json():
141
+ logger.error("Failed to login using provided credentials, response: %s", response)
142
+ raise Exception("Failed to login using provided credentials.")
143
+ self.access_token = response.json().get("access_token")
144
+ self.headers = {
145
+ "Content-Type": "application/json",
146
+ "Accept": "application/json",
147
+ "Authorization": f"Bearer {self.access_token}",
148
+ "User-Agent": self.user_agent,
149
+ }
151
150
 
152
151
  @retry_with_backoff(retries=5)
153
152
  def login(self):
154
- """Log in to the ZPA API and set the access token for subsequent requests."""
155
153
  params = {"client_id": self.client_id, "client_secret": self.client_secret}
156
154
  headers = {
157
155
  "Content-Type": "application/x-www-form-urlencoded",
@@ -164,7 +162,6 @@ class ZPAClientHelper(ZPAClient):
164
162
  url = DEV_AUTH_URL + "?grant_type=CLIENT_CREDENTIALS"
165
163
  data = urllib.parse.urlencode(params)
166
164
  resp = requests.post(url, data=data, headers=headers, timeout=self.timeout)
167
- # Avoid logging all data from the response, focus on the status and a summary instead
168
165
  logger.info("Login attempt with status: %d", resp.status_code)
169
166
  return resp
170
167
  except Exception as e:
@@ -172,16 +169,6 @@ class ZPAClientHelper(ZPAClient):
172
169
  return None
173
170
 
174
171
  def send(self, method, path, json=None, params=None, api_version: str = None):
175
- """
176
- Send a request to the ZPA API.
177
-
178
- Parameters:
179
- - method (str): The HTTP method.
180
- - path (str): API endpoint path.
181
- - json (dict, optional): Request payload. Defaults to None.
182
- Returns:
183
- - Response: Response object from the request.
184
- """
185
172
  api = self.url
186
173
  if api_version is None:
187
174
  api = self.url
@@ -194,23 +181,34 @@ class ZPAClientHelper(ZPAClient):
194
181
  elif api_version == "cbiconfig_v1":
195
182
  api = self.cbi_url
196
183
 
184
+ if params is None:
185
+ params = {}
186
+
187
+ if json and "microtenant_id" in json:
188
+ microtenant_id = json.pop("microtenant_id")
189
+ else:
190
+ microtenant_id = self.microtenant_id
191
+
192
+ if microtenant_id:
193
+ params["microtenantId"] = microtenant_id
194
+
197
195
  url = f"{api}/{path.lstrip('/')}"
196
+ if params:
197
+ url = f"{url}?{urllib.parse.urlencode(params)}"
198
+
198
199
  start_time = time.time()
199
- # Update headers to include the user agent
200
200
  headers_with_user_agent = self.headers.copy()
201
201
  headers_with_user_agent["User-Agent"] = self.user_agent
202
- # Generate a unique UUID for this request
203
202
  request_uuid = uuid.uuid4()
204
- dump_request(logger, url, method, json, params, headers_with_user_agent, request_uuid)
205
- # Check cache before sending request
206
- cache_key = self.cache.create_key(url, params)
203
+ dump_request(logger, url, method, json, None, headers_with_user_agent, request_uuid)
204
+ cache_key = self.cache.create_key(url, None)
207
205
  if method == "GET" and self.cache.contains(cache_key):
208
206
  resp = self.cache.get(cache_key)
209
207
  dump_response(
210
208
  logger=logger,
211
209
  url=url,
212
210
  method=method,
213
- params=params,
211
+ params=None,
214
212
  resp=resp,
215
213
  request_uuid=request_uuid,
216
214
  start_time=start_time,
@@ -219,59 +217,63 @@ class ZPAClientHelper(ZPAClient):
219
217
  return resp
220
218
 
221
219
  attempts = 0
222
- while attempts < 5: # Trying a maximum of 5 times
220
+ while attempts < 5:
223
221
  try:
224
- # If the token is None or expired, fetch a new token
225
- if is_token_expired(self.access_token):
226
- logger.warning("The provided or fetched token was already expired. Refreshing...")
227
- self.refreshToken()
222
+ self.refreshToken()
223
+ should_wait, delay = self.rate_limiter.wait(method)
224
+ if should_wait:
225
+ logger.warning(f"Rate limit exceeded. Retrying in {delay} seconds.")
226
+ time.sleep(delay)
228
227
  resp = requests.request(
229
228
  method,
230
229
  url,
231
230
  json=json,
231
+ params=None,
232
232
  headers=headers_with_user_agent,
233
233
  timeout=self.timeout,
234
234
  )
235
235
  dump_response(
236
236
  logger=logger,
237
237
  url=url,
238
- params=params,
238
+ params=None,
239
239
  method=method,
240
240
  resp=resp,
241
241
  request_uuid=request_uuid,
242
242
  start_time=start_time,
243
243
  )
244
- if resp.status_code == 429: # HTTP Status code 429 indicates "Too Many Requests"
245
- sleep_time = int(
246
- resp.headers.get("Retry-After", 2)
247
- ) # Default to 60 seconds if 'Retry-After' header is missing
248
- logger.warning(f"Rate limit exceeded. Retrying in {sleep_time} seconds.")
249
- sleep(sleep_time)
244
+ if resp.status_code == 429:
245
+ retry_after = resp.headers.get("Retry-After")
246
+ if retry_after:
247
+ try:
248
+ sleep_time = int(retry_after)
249
+ except ValueError:
250
+ sleep_time = int(retry_after[:-1])
251
+ logger.warning(f"Rate limit exceeded. Retrying in {sleep_time} seconds.")
252
+ time.sleep(sleep_time)
253
+ else:
254
+ time.sleep(60)
250
255
  attempts += 1
251
256
  continue
252
257
  else:
253
258
  break
254
259
  except requests.RequestException as e:
255
- if attempts == 4: # If it's the last attempt, raise the exception
260
+ if attempts == 4:
256
261
  logger.error(f"Failed to send {method} request to {url} after 5 attempts. Error: {str(e)}")
257
262
  raise e
258
263
  else:
259
264
  logger.warning(f"Failed to send {method} request to {url}. Retrying... Error: {str(e)}")
260
265
  attempts += 1
261
- sleep(5) # Sleep for 5 seconds before retrying
266
+ time.sleep(5)
262
267
 
263
- # If Non-GET call, clear the
264
268
  if method != "GET":
265
- self.cache.delete(cache_key)
269
+ logger.info(f"Clearing cache for non-GET request: {method} {url}")
270
+ self.cache.clear()
266
271
 
267
- # Detailed logging for request and response
268
272
  try:
269
273
  response_data = resp.json()
270
- except ValueError: # Using ValueError for JSON decoding errors
274
+ except ValueError:
271
275
  response_data = resp.text
272
- # check if call was succesful
273
276
  if 200 > resp.status_code or resp.status_code > 299:
274
- # create errors
275
277
  try:
276
278
  error = ZscalerAPIError(url, resp, response_data)
277
279
  if self.fail_safe:
@@ -284,7 +286,6 @@ class ZPAClientHelper(ZPAClient):
284
286
  logger.error(response_data)
285
287
  raise HTTPException(response_data)
286
288
  logger.error(error)
287
- # Cache the response if it's a successful GET request
288
289
  if method == "GET" and resp.status_code == 200:
289
290
  self.cache.add(cache_key, resp)
290
291
  return resp
@@ -294,23 +295,34 @@ class ZPAClientHelper(ZPAClient):
294
295
  Send a GET request to the ZPA API.
295
296
 
296
297
  Parameters:
297
- - path (str): API endpoint path.
298
- - data (dict, optional): Request payload. Defaults to None.
298
+ path (str): API endpoint path.
299
+ json (dict, optional): Request payload. Defaults to None.
300
+ params (dict, optional): Query parameters. Defaults to None.
301
+ api_version (str, optional): API version to use. Defaults to None.
302
+
299
303
  Returns:
300
- - Response: Response object from the request.
304
+ dict: Formatted JSON response from the API.
301
305
  """
302
-
303
- # Use rate limiter before making a request
304
306
  should_wait, delay = self.rate_limiter.wait("GET")
305
307
  if should_wait:
306
308
  time.sleep(delay)
307
-
308
- # Now proceed with sending the request
309
309
  resp = self.send("GET", path, json, params, api_version=api_version)
310
310
  formatted_resp = format_json_response(resp, box_attrs=dict())
311
311
  return formatted_resp
312
312
 
313
313
  def put(self, path, json=None, params=None, api_version: str = None):
314
+ """
315
+ Send a PUT request to the ZPA API.
316
+
317
+ Parameters:
318
+ path (str): API endpoint path.
319
+ json (dict, optional): Request payload. Defaults to None.
320
+ params (dict, optional): Query parameters. Defaults to None.
321
+ api_version (str, optional): API version to use. Defaults to None.
322
+
323
+ Returns:
324
+ dict: Formatted JSON response from the API.
325
+ """
314
326
  should_wait, delay = self.rate_limiter.wait("PUT")
315
327
  if should_wait:
316
328
  time.sleep(delay)
@@ -319,6 +331,18 @@ class ZPAClientHelper(ZPAClient):
319
331
  return formatted_resp
320
332
 
321
333
  def post(self, path, json=None, params=None, api_version: str = None):
334
+ """
335
+ Send a POST request to the ZPA API.
336
+
337
+ Parameters:
338
+ path (str): API endpoint path.
339
+ json (dict, optional): Request payload. Defaults to None.
340
+ params (dict, optional): Query parameters. Defaults to None.
341
+ api_version (str, optional): API version to use. Defaults to None.
342
+
343
+ Returns:
344
+ dict: Formatted JSON response from the API.
345
+ """
322
346
  should_wait, delay = self.rate_limiter.wait("POST")
323
347
  if should_wait:
324
348
  time.sleep(delay)
@@ -327,6 +351,18 @@ class ZPAClientHelper(ZPAClient):
327
351
  return formatted_resp
328
352
 
329
353
  def delete(self, path, json=None, params=None, api_version: str = None):
354
+ """
355
+ Send a DELETE request to the ZPA API.
356
+
357
+ Parameters:
358
+ path (str): API endpoint path.
359
+ json (dict, optional): Request payload. Defaults to None.
360
+ params (dict, optional): Query parameters. Defaults to None.
361
+ api_version (str, optional): API version to use. Defaults to None.
362
+
363
+ Returns:
364
+ Response: Response object from the DELETE request.
365
+ """
330
366
  should_wait, delay = self.rate_limiter.wait("DELETE")
331
367
  if should_wait:
332
368
  time.sleep(delay)
@@ -342,6 +378,7 @@ class ZPAClientHelper(ZPAClient):
342
378
  search_field="name",
343
379
  max_pages=None,
344
380
  max_items=None,
381
+ all_entries=False, # Return all SCIM groups including the deleted ones if set to true
345
382
  sort_order=None,
346
383
  sort_by=None,
347
384
  sort_dir=None,
@@ -349,8 +386,10 @@ class ZPAClientHelper(ZPAClient):
349
386
  end_time=None,
350
387
  idp_group_id=None,
351
388
  scim_user_id=None,
389
+ scim_username=None,
352
390
  page=None,
353
- pagesize=20,
391
+ pagesize=None,
392
+ microtenant_id=None,
354
393
  ):
355
394
  """
356
395
  Fetches paginated data from the ZPA API based on specified parameters and handles various types of API pagination.
@@ -393,15 +432,19 @@ class ZPAClientHelper(ZPAClient):
393
432
  if params is None:
394
433
  params = {}
395
434
 
396
- if (page is not None or pagesize != 20) and (max_pages is not None or max_items is not None):
435
+ if (page is not None or pagesize is not None) and (max_pages is not None or max_items is not None):
397
436
  raise ValueError(
398
437
  "Do not mix 'page' or 'pagesize' with 'max_pages' or 'max_items'. Choose either set of parameters."
399
438
  )
400
439
 
401
- params["pagesize"] = min(pagesize, 500) # Apply maximum constraint and handle default
440
+ params["page"] = page if page is not None else 1 # Default to page 1 if not specified
441
+ params["pagesize"] = min(pagesize if pagesize is not None else 20, 500) # Apply maximum constraint and handle default
402
442
 
403
- if page:
404
- params["page"] = page
443
+ # Check for microtenantId in function arguments first, then environment variable
444
+ if microtenant_id:
445
+ params["microtenantId"] = microtenant_id
446
+ elif self.microtenant_id and "microtenantId" not in params:
447
+ params["microtenantId"] = self.microtenant_id
405
448
 
406
449
  if search:
407
450
  api_search_field = snake_to_camel(search_field)
@@ -419,6 +462,10 @@ class ZPAClientHelper(ZPAClient):
419
462
  params["idpGroupId"] = idp_group_id
420
463
  if scim_user_id:
421
464
  params["scimUserId"] = scim_user_id
465
+ if scim_username:
466
+ params["scimUserName"] = scim_username
467
+ if all_entries:
468
+ params["allEntries"] = all_entries
422
469
 
423
470
  total_collected = 0
424
471
  ret_data = []
@@ -432,17 +479,18 @@ class ZPAClientHelper(ZPAClient):
432
479
  if should_wait:
433
480
  time.sleep(delay)
434
481
 
435
- url = f"{path}?{urllib.parse.urlencode(params)}"
436
- response = self.send("GET", url, api_version=api_version)
482
+ response = self.send("GET", path=path, params=params, api_version=api_version)
437
483
 
438
484
  if response.status_code != expected_status_code:
439
- error_msg = ERROR_MESSAGES["UNEXPECTED_STATUS"].format(status_code=response.status_code, page=page)
485
+ error_msg = ERROR_MESSAGES["UNEXPECTED_STATUS"].format(
486
+ status_code=response.status_code, page=params["page"]
487
+ )
440
488
  logger.error(error_msg)
441
489
  return BoxList([]), error_msg
442
490
 
443
491
  response_data = response.json()
444
492
  data = response_data.get("list", [])
445
- if not data and (page is None or page == 1):
493
+ if not data and (params["page"] == 1):
446
494
  error_msg = ERROR_MESSAGES["EMPTY_RESULTS"]
447
495
  logger.warn(error_msg)
448
496
  return BoxList([]), error_msg
@@ -454,12 +502,11 @@ class ZPAClientHelper(ZPAClient):
454
502
  if max_items is not None and total_collected >= max_items:
455
503
  break
456
504
 
457
- nextPage = response_data.get("nextPage")
458
- if not nextPage or (max_pages is not None and page >= max_pages):
505
+ next_page = response_data.get("nextPage")
506
+ if not next_page or (max_pages is not None and params["page"] >= max_pages):
459
507
  break
460
508
 
461
- page = nextPage if page is None else page + 1
462
- params["page"] = page
509
+ params["page"] = next_page if params["page"] is None else params["page"] + 1
463
510
 
464
511
  finally:
465
512
  time.sleep(2) # Ensure a delay between requests regardless of outcome
@@ -471,6 +518,14 @@ class ZPAClientHelper(ZPAClient):
471
518
 
472
519
  return BoxList(ret_data), None
473
520
 
521
+ @property
522
+ def authdomains(self):
523
+ """
524
+ The interface object for the :ref:`ZPA Auth Domains interface <zpa-authdomains>`.
525
+
526
+ """
527
+ return AuthDomainsAPI(self)
528
+
474
529
  @property
475
530
  def app_segments(self):
476
531
  """
@@ -568,20 +623,20 @@ class ZPAClientHelper(ZPAClient):
568
623
  return MachineGroupsAPI(self)
569
624
 
570
625
  @property
571
- def policies(self):
626
+ def microtenants(self):
572
627
  """
573
- The interface object for the :ref:`ZPA Policy Sets interface <zpa-policies>`.
628
+ The interface object for the :ref:`ZPA Microtenants interface <zpa-microtenants>`.
574
629
 
575
630
  """
576
- return PolicySetsAPI(self)
631
+ return MicrotenantsAPI(self)
577
632
 
578
633
  @property
579
- def policiesv2(self):
634
+ def policies(self):
580
635
  """
581
- The interface object for the :ref:`ZPA Policy Sets V2 interface <zpa-policiesv2>`.
636
+ The interface object for the :ref:`ZPA Policy Sets interface <zpa-policies>`.
582
637
 
583
638
  """
584
- return PolicySetsV2API(self)
639
+ return PolicySetsAPI(self)
585
640
 
586
641
  @property
587
642
  def posture_profiles(self):