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.
Files changed (75) hide show
  1. zscaler/__init__.py +34 -0
  2. zscaler/cache/__init__.py +0 -0
  3. zscaler/cache/cache.py +105 -0
  4. zscaler/cache/no_op_cache.py +68 -0
  5. zscaler/cache/zscaler_cache.py +161 -0
  6. zscaler/constants.py +26 -0
  7. zscaler/errors/__init__.py +0 -0
  8. zscaler/errors/error.py +10 -0
  9. zscaler/errors/http_error.py +20 -0
  10. zscaler/errors/zscaler_api_error.py +24 -0
  11. zscaler/exceptions/__init__.py +1 -0
  12. zscaler/exceptions/exceptions.py +101 -0
  13. zscaler/logger.py +57 -0
  14. zscaler/ratelimiter/__init__.py +0 -0
  15. zscaler/ratelimiter/ratelimiter.py +39 -0
  16. zscaler/user_agent.py +23 -0
  17. zscaler/utils.py +577 -0
  18. zscaler/zia/__init__.py +657 -0
  19. zscaler/zia/activate.py +52 -0
  20. zscaler/zia/admin_and_role_management.py +344 -0
  21. zscaler/zia/apptotal.py +71 -0
  22. zscaler/zia/audit_logs.py +95 -0
  23. zscaler/zia/authentication_settings.py +98 -0
  24. zscaler/zia/client.py +88 -0
  25. zscaler/zia/cloud_apps.py +406 -0
  26. zscaler/zia/device_management.py +90 -0
  27. zscaler/zia/dlp.py +784 -0
  28. zscaler/zia/errors.py +37 -0
  29. zscaler/zia/firewall.py +1104 -0
  30. zscaler/zia/forwarding_control.py +271 -0
  31. zscaler/zia/isolation_profile.py +83 -0
  32. zscaler/zia/labels.py +180 -0
  33. zscaler/zia/locations.py +661 -0
  34. zscaler/zia/sandbox.py +180 -0
  35. zscaler/zia/security.py +236 -0
  36. zscaler/zia/ssl_inspection.py +175 -0
  37. zscaler/zia/traffic.py +853 -0
  38. zscaler/zia/url_categories.py +442 -0
  39. zscaler/zia/url_filtering.py +310 -0
  40. zscaler/zia/users.py +386 -0
  41. zscaler/zia/web_dlp.py +295 -0
  42. zscaler/zia/workload_groups.py +58 -0
  43. zscaler/zia/zpa_gateway.py +187 -0
  44. zscaler/zpa/__init__.py +683 -0
  45. zscaler/zpa/app_segments.py +331 -0
  46. zscaler/zpa/app_segments_inspection.py +311 -0
  47. zscaler/zpa/app_segments_pra.py +310 -0
  48. zscaler/zpa/certificates.py +234 -0
  49. zscaler/zpa/client.py +113 -0
  50. zscaler/zpa/cloud_connector_groups.py +75 -0
  51. zscaler/zpa/connectors.py +518 -0
  52. zscaler/zpa/emergency_access.py +178 -0
  53. zscaler/zpa/errors.py +37 -0
  54. zscaler/zpa/idp.py +83 -0
  55. zscaler/zpa/inspection.py +1012 -0
  56. zscaler/zpa/isolation_profile.py +85 -0
  57. zscaler/zpa/lss.py +568 -0
  58. zscaler/zpa/machine_groups.py +79 -0
  59. zscaler/zpa/policies.py +848 -0
  60. zscaler/zpa/posture_profiles.py +122 -0
  61. zscaler/zpa/privileged_remote_access.py +862 -0
  62. zscaler/zpa/provisioning.py +271 -0
  63. zscaler/zpa/saml_attributes.py +100 -0
  64. zscaler/zpa/scim_attributes.py +117 -0
  65. zscaler/zpa/scim_groups.py +146 -0
  66. zscaler/zpa/segment_groups.py +191 -0
  67. zscaler/zpa/server_groups.py +217 -0
  68. zscaler/zpa/servers.py +202 -0
  69. zscaler/zpa/service_edges.py +404 -0
  70. zscaler/zpa/trusted_networks.py +127 -0
  71. zscaler_sdk_python-1.0.0.dist-info/LICENSE.md +21 -0
  72. zscaler_sdk_python-1.0.0.dist-info/METADATA +59 -0
  73. zscaler_sdk_python-1.0.0.dist-info/RECORD +75 -0
  74. zscaler_sdk_python-1.0.0.dist-info/WHEEL +6 -0
  75. zscaler_sdk_python-1.0.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,848 @@
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
+
18
+ import functools
19
+
20
+ from box import Box, BoxList
21
+ from requests import Response
22
+
23
+ from zscaler.utils import add_id_groups, convert_keys, snake_to_camel
24
+ from zscaler.zpa.client import ZPAClient
25
+
26
+
27
+ class PolicySetsAPI:
28
+ def __init__(self, client: ZPAClient):
29
+ self.rest = client
30
+
31
+ POLICY_MAP = {
32
+ "access": "ACCESS_POLICY",
33
+ "timeout": "TIMEOUT_POLICY",
34
+ "client_forwarding": "CLIENT_FORWARDING_POLICY",
35
+ "isolation": "ISOLATION_POLICY",
36
+ "inspection": "INSPECTION_POLICY",
37
+ "siem": "SIEM_POLICY",
38
+ }
39
+
40
+ reformat_params = [
41
+ ("app_server_group_ids", "appServerGroups"),
42
+ ("app_connector_group_ids", "appConnectorGroups"),
43
+ ]
44
+
45
+ @staticmethod
46
+ def _create_conditions(conditions: list):
47
+ """
48
+ Creates a dict template for feeding conditions into the ZPA Policies API when adding or updating a policy.
49
+
50
+ Args:
51
+ conditions (list): List of condition dicts or tuples.
52
+
53
+ Returns:
54
+ :obj:`dict`: The conditions template.
55
+
56
+ """
57
+ template = []
58
+
59
+ for condition in conditions:
60
+ if isinstance(condition, tuple) and len(condition) == 3:
61
+ # Previous tuple logic
62
+ operand = {
63
+ "operands": [
64
+ {
65
+ "objectType": condition[0].upper(),
66
+ "lhs": condition[1],
67
+ "rhs": condition[2],
68
+ }
69
+ ]
70
+ }
71
+ template.append(operand)
72
+ elif isinstance(condition, dict):
73
+ # New dictionary logic based on Go code schema
74
+ condition_template = {}
75
+
76
+ # Extracting keys from the condition dictionary
77
+ for key in ["id", "negated", "operator"]:
78
+ if key in condition:
79
+ condition_template[key] = condition[key]
80
+
81
+ # Handling the operands
82
+ operands = condition.get("operands", [])
83
+ condition_template["operands"] = []
84
+
85
+ for operand in operands:
86
+ operand_template = {}
87
+
88
+ # Extracting keys from the operand dictionary
89
+ for operand_key in [
90
+ "id",
91
+ "idp_id",
92
+ "name",
93
+ "lhs",
94
+ "rhs",
95
+ "objectType",
96
+ ]:
97
+ if operand_key in operand:
98
+ operand_template[operand_key] = operand[operand_key]
99
+
100
+ condition_template["operands"].append(operand_template)
101
+
102
+ template.append(condition_template)
103
+
104
+ return template
105
+
106
+ def get_policy(self, policy_type: str) -> Box:
107
+ """
108
+ Returns the policy and rule sets for the given policy type.
109
+
110
+ Args:
111
+ policy_type (str): The type of policy to be returned. Accepted values are:
112
+
113
+ | ``access`` - returns the Access Policy
114
+ | ``timeout`` - returns the Timeout Policy
115
+ | ``client_forwarding`` - returns the Client Forwarding Policy
116
+ | ``isolation`` - returns the Isolation Policy
117
+ | ``inspection`` - returns the Inspection Policy
118
+ | ``siem`` - returns the SIEM Policy
119
+
120
+ Returns:
121
+ :obj:`Box`: The resource record of the specified policy type.
122
+
123
+ Examples:
124
+ Request the specified Policy.
125
+
126
+ >>> pprint(zpa.policies.get_policy('access'))
127
+
128
+ """
129
+ # Map the simplified policy_type name to the name expected by the Zscaler API
130
+ mapped_policy_type = self.POLICY_MAP.get(policy_type, None)
131
+
132
+ # If the user provided an incorrect name, raise an error
133
+ if not mapped_policy_type:
134
+ raise ValueError(
135
+ f"Incorrect policy type provided: {policy_type}\n "
136
+ f"Policy type must be 'access', 'timeout', 'client_forwarding' or 'siem'."
137
+ )
138
+
139
+ return self.rest.get(f"policySet/policyType/{mapped_policy_type}")
140
+
141
+ def get_rule(self, policy_type: str, rule_id: str) -> Box:
142
+ """
143
+ Returns the specified policy rule.
144
+
145
+ Args:
146
+ policy_type (str): The type of policy to be returned. Accepted values are:
147
+
148
+ | ``access``
149
+ | ``timeout``
150
+ | ``client_forwarding``
151
+ | ``siem``
152
+ rule_id (str): The unique identifier for the policy rule.
153
+
154
+ Returns:
155
+ :obj:`Box`: The resource record for the requested rule.
156
+
157
+ Examples:
158
+ >>> policy_rule = zpa.policies.get_rule(policy_id='99999',
159
+ ... rule_id='88888')
160
+
161
+ """
162
+ # Get the policy id for the supplied policy_type
163
+ policy_id = self.get_policy(policy_type).id
164
+
165
+ return self.rest.get(f"policySet/{policy_id}/rule/{rule_id}")
166
+
167
+ def get_rule_by_name(self, policy_type: str, rule_name: str) -> Box:
168
+ """
169
+ Returns the specified policy rule by its name.
170
+
171
+ Args:
172
+ policy_type (str): The type of policy to be returned.
173
+ Accepted values are: ``access``, ``timeout``, ``client_forwarding``, ``siem``
174
+ rule_name (str): The name of the policy rule.
175
+
176
+ Returns:
177
+ :obj:`Box`: The resource record for the requested rule.
178
+
179
+ Examples:
180
+ >>> policy_rule = zpa.policies.get_rule_by_name(policy_type='access', rule_name='MyRule')
181
+
182
+ """
183
+ all_rules = self.list_rules(policy_type)
184
+ for rule in all_rules:
185
+ if rule.name == rule_name:
186
+ return rule
187
+ return None
188
+
189
+ def list_rules(self, policy_type: str, **kwargs) -> BoxList:
190
+ """
191
+ Returns policy rules for a given policy type.
192
+
193
+ Args:
194
+ policy_type (str):
195
+ The policy type. Accepted values are:
196
+
197
+ | ``access`` - returns Access Policy rules
198
+ | ``timeout`` - returns Timeout Policy rules
199
+ | ``client_forwarding`` - returns Client Forwarding Policy rules
200
+
201
+ Returns:
202
+ :obj:`list`: A list of all policy rules that match the requested type.
203
+
204
+ Examples:
205
+ >>> for policy in zpa.policies.list_type('type')
206
+ ... pprint(policy)
207
+
208
+ """
209
+
210
+ # Map the simplified policy_type name to the name expected by the Zscaler API
211
+ mapped_policy_type = self.POLICY_MAP.get(policy_type, None)
212
+
213
+ # If the user provided an incorrect name, raise an error
214
+ if not mapped_policy_type:
215
+ raise ValueError(
216
+ f"Incorrect policy type provided: {policy_type}\n "
217
+ f"Policy type must be 'access', 'timeout', 'client_forwarding' or 'siem'."
218
+ )
219
+ list, _ = self.rest.get_paginated_data(
220
+ path=f"policySet/rules/policyType/{mapped_policy_type}",
221
+ **kwargs,
222
+ api_version="v1",
223
+ )
224
+ return list
225
+
226
+ def delete_rule(self, policy_type: str, rule_id: str) -> int:
227
+ """
228
+ Deletes the specified policy rule.
229
+
230
+ Args:
231
+ policy_type (str):
232
+ The type of policy the rule belongs to. Accepted values are:
233
+
234
+ | ``access``
235
+ | ``timeout``
236
+ | ``client_forwarding``
237
+ | ``siem``
238
+ rule_id (str):
239
+ The unique identifier for the policy rule.
240
+
241
+ Returns:
242
+ :obj:`int`: The response code for the operation.
243
+
244
+ Examples:
245
+ >>> zpa.policies.delete_rule(policy_id='99999',
246
+ ... rule_id='88888')
247
+
248
+ """
249
+
250
+ # Get policy id for specified policy type
251
+ policy_id = self.get_policy(policy_type).id
252
+
253
+ return self.rest.delete(f"policySet/{policy_id}/rule/{rule_id}").status_code
254
+
255
+ def add_access_rule(
256
+ self,
257
+ name: str,
258
+ action: str,
259
+ app_connector_group_ids: list = [],
260
+ app_server_group_ids: list = [],
261
+ **kwargs,
262
+ ) -> Box:
263
+ """
264
+ Add a new Access Policy rule.
265
+
266
+ See the `ZPA Access Policy API reference <https://help.zscaler.com/zpa/access-policy-use-cases>`_
267
+ for further detail on optional keyword parameter structures.
268
+
269
+ Args:
270
+ name (str):
271
+ The name of the new rule.
272
+ action (str):
273
+ The action for the policy. Accepted values are:
274
+
275
+ | ``allow``
276
+ | ``deny``
277
+ **kwargs:
278
+ Optional keyword args.
279
+
280
+ Keyword Args:
281
+ conditions (list):
282
+ A list of conditional rule tuples. Tuples must follow the convention: `Object Type`, `LHS value`,
283
+ `RHS value`. If you are adding multiple values for the same object type then you will need
284
+ a new entry for each value.
285
+ E.g.
286
+
287
+ .. code-block:: python
288
+
289
+ [('app', 'id', '99999'),
290
+ ('app', 'id', '88888'),
291
+ ('app_group', 'id', '77777),
292
+ ('client_type', 'zpn_client_type_exporter', 'zpn_client_type_zapp'),
293
+ ('trusted_network', 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxx', True)]
294
+ custom_msg (str):
295
+ A custom message.
296
+ description (str):
297
+ A description for the rule.
298
+ app_connector_group_ids (:obj:`list` of :obj:`str`):
299
+ A list of application connector IDs that will be attached to the access policy rule.
300
+ app_server_group_ids (:obj:`list` of :obj:`str`):
301
+ A list of application server group IDs that will be attached to the access policy rule.
302
+ Returns:
303
+ :obj:`Box`: The resource record of the newly created access policy rule.
304
+
305
+ """
306
+
307
+ # Initialise the payload
308
+ payload = {
309
+ "name": name,
310
+ "action": action.upper(),
311
+ "conditions": self._create_conditions(kwargs.pop("conditions", [])),
312
+ }
313
+
314
+ if app_connector_group_ids:
315
+ payload["appConnectorGroups"] = [
316
+ {"id": group_id} for group_id in app_connector_group_ids
317
+ ]
318
+
319
+ if app_server_group_ids:
320
+ payload["appServerGroups"] = [
321
+ {"id": group_id} for group_id in app_server_group_ids
322
+ ]
323
+
324
+ add_id_groups(self.reformat_params, kwargs, payload)
325
+
326
+ # Get the policy id of the provided policy type for the URL.
327
+ policy_id = self.get_policy("access").id
328
+
329
+ # Add optional parameters to payload
330
+ for key, value in kwargs.items():
331
+ payload[snake_to_camel(key)] = value
332
+
333
+ response = self.rest.post(f"policySet/{policy_id}/rule", json=payload)
334
+ if isinstance(response, Response):
335
+ # this is only true when the creation failed (status code is not 2xx)
336
+ status_code = response.status_code
337
+ # Handle error response
338
+ raise Exception(
339
+ f"API call failed with status {status_code}: {response.json()}"
340
+ )
341
+ return response
342
+
343
+ def add_timeout_rule(self, name: str, **kwargs) -> Box:
344
+ """
345
+ Add a new Timeout Policy rule.
346
+
347
+ See the `ZPA Timeout Policy API reference <https://help.zscaler.com/zpa/timeout-policy-use-cases>`_
348
+ for further detail on optional keyword parameter structures.
349
+
350
+ Args:
351
+ name (str):
352
+ The name of the new rule.
353
+ **kwargs:
354
+ Optional parameters.
355
+
356
+ Keyword Args:
357
+ conditions (list):
358
+ A list of conditional rule tuples. Tuples must follow the convention: `Object Type`, `LHS value`,
359
+ `RHS value`. If you are adding multiple values for the same object type then you will need
360
+ a new entry for each value.
361
+ E.g.
362
+
363
+ .. code-block:: python
364
+
365
+ [('app', 'id', '926196382959075416'),
366
+ ('app', 'id', '926196382959075417'),
367
+ ('app_group', 'id', '926196382959075332),
368
+ ('client_type', 'zpn_client_type_exporter', 'zpn_client_type_zapp'),
369
+ ('trusted_network', 'b15e4cad-fa6e-8182-9fc3-8125ee6a65e1', True)]
370
+ custom_msg (str):
371
+ A custom message.
372
+ description (str):
373
+ A description for the rule.
374
+ re_auth_idle_timeout (int):
375
+ The re-authentication idle timeout value in seconds.
376
+ re_auth_timeout (int):
377
+ The re-authentication timeout value in seconds.
378
+
379
+ Returns:
380
+ :obj:`Box`: The resource record of the newly created Timeout Policy rule.
381
+
382
+ """
383
+
384
+ # Initialise the payload
385
+ payload = {
386
+ "name": name,
387
+ "action": "RE_AUTH",
388
+ "conditions": self._create_conditions(kwargs.pop("conditions", [])),
389
+ }
390
+
391
+ # Get the policy id of the provided policy type for the URL.
392
+ policy_id = self.get_policy("timeout").id
393
+
394
+ # Use specified timeouts or default to UI values
395
+ payload["reauthTimeout"] = kwargs.get("re_auth_timeout", 172800)
396
+ payload["reauthIdleTimeout"] = kwargs.get("re_auth_idle_timeout", 600)
397
+
398
+ # Add optional parameters to payload
399
+ for key, value in kwargs.items():
400
+ payload[snake_to_camel(key)] = value
401
+
402
+ response = self.rest.post(f"policySet/{policy_id}/rule", json=payload)
403
+ if isinstance(response, Response):
404
+ # this is only true when the creation failed (status code is not 2xx)
405
+ status_code = response.status_code
406
+ # Handle error response
407
+ raise Exception(
408
+ f"API call failed with status {status_code}: {response.json()}"
409
+ )
410
+ return response
411
+
412
+ def add_client_forwarding_rule(self, name: str, action: str, **kwargs) -> Box:
413
+ """
414
+ Add a new Client Forwarding Policy rule.
415
+
416
+ See the
417
+ `ZPA Client Forwarding Policy API reference <https://help.zscaler.com/zpa/client-forwarding-policy-use-cases>`_
418
+ for further detail on optional keyword parameter structures.
419
+
420
+ Args:
421
+ name (str):
422
+ The name of the new rule.
423
+ action (str):
424
+ The action for the policy. Accepted values are:
425
+
426
+ | ``intercept``
427
+ | ``intercept_accessible``
428
+ | ``bypass``
429
+ **kwargs:
430
+ Optional keyword args.
431
+
432
+ Keyword Args:
433
+ conditions (list):
434
+ A list of conditional rule tuples. Tuples must follow the convention: `Object Type`, `LHS value`,
435
+ `RHS value`. If you are adding multiple values for the same object type then you will need
436
+ a new entry for each value.
437
+ E.g.
438
+
439
+ .. code-block:: python
440
+
441
+ [('app', 'id', '926196382959075416'),
442
+ ('app', 'id', '926196382959075417'),
443
+ ('app_group', 'id', '926196382959075332),
444
+ ('client_type', 'zpn_client_type_exporter', 'zpn_client_type_zapp'),
445
+ ('trusted_network', 'b15e4cad-fa6e-8182-9fc3-8125ee6a65e1', True)]
446
+ custom_msg (str):
447
+ A custom message.
448
+ description (str):
449
+ A description for the rule.
450
+
451
+ Returns:
452
+ :obj:`Box`: The resource record of the newly created Client Forwarding Policy rule.
453
+
454
+ """
455
+
456
+ # Initialise the payload
457
+ payload = {
458
+ "name": name,
459
+ "action": action.upper(),
460
+ "conditions": self._create_conditions(kwargs.pop("conditions", [])),
461
+ }
462
+
463
+ # Get the policy id of the provided policy type for the URL.
464
+ policy_id = self.get_policy("client_forwarding").id
465
+
466
+ # Add optional parameters to payload
467
+ for key, value in kwargs.items():
468
+ payload[snake_to_camel(key)] = value
469
+
470
+ response = self.rest.post(f"policySet/{policy_id}/rule", json=payload)
471
+ if isinstance(response, Response):
472
+ # this is only true when the creation failed (status code is not 2xx)
473
+ status_code = response.status_code
474
+ # Handle error response
475
+ raise Exception(
476
+ f"API call failed with status {status_code}: {response.json()}"
477
+ )
478
+ return response
479
+
480
+ def add_isolation_rule(
481
+ self, name: str, action: str, zpn_isolation_profile_id: str, **kwargs
482
+ ) -> Box:
483
+ """
484
+ Add a new Isolation Policy rule.
485
+
486
+ See the
487
+ `ZPA Isolation Policy API reference <https://help.zscaler.com/zpa/configuring-isolation-policies-using-api>`_
488
+ for further detail on optional keyword parameter structures.
489
+
490
+ Args:
491
+ name (str):
492
+ The name of the new rule.
493
+ action (str):
494
+ The action for the policy. Accepted values are:
495
+
496
+ | ``isolate``
497
+ | ``bypass_isolate``
498
+ **kwargs:
499
+ Optional keyword args.
500
+
501
+ Keyword Args:
502
+ conditions (list):
503
+ A list of conditional rule tuples. Tuples must follow the convention: `Object Type`, `LHS value`,
504
+ `RHS value`. If you are adding multiple values for the same object type then you will need
505
+ a new entry for each value.
506
+ E.g.
507
+
508
+ .. code-block:: python
509
+
510
+ [('app', 'id', '926196382959075416'),
511
+ ('app', 'id', '926196382959075417'),
512
+ ('app_group', 'id', '926196382959075332),
513
+ ('client_type', 'zpn_client_type_exporter')]
514
+ zpn_isolation_profile_id (str):
515
+ The isolation profile ID associated with the rule
516
+ description (str):
517
+ A description for the rule.
518
+
519
+ Returns:
520
+ :obj:`Box`: The resource record of the newly created Client Isolation Policy rule.
521
+
522
+ """
523
+
524
+ # Initialise the payload
525
+ payload = {
526
+ "name": name,
527
+ "action": action.upper(),
528
+ "zpnIsolationProfileId": zpn_isolation_profile_id,
529
+ "conditions": self._create_conditions(kwargs.pop("conditions", [])),
530
+ }
531
+
532
+ # Get the policy id of the provided policy type for the URL.
533
+ policy_id = self.get_policy("isolation").id
534
+
535
+ # Add optional parameters to payload
536
+ for key, value in kwargs.items():
537
+ payload[snake_to_camel(key)] = value
538
+
539
+ response = self.rest.post(f"policySet/{policy_id}/rule", json=payload)
540
+ if isinstance(response, Response):
541
+ # this is only true when the creation failed (status code is not 2xx)
542
+ status_code = response.status_code
543
+ # Handle error response
544
+ raise Exception(
545
+ f"API call failed with status {status_code}: {response.json()}"
546
+ )
547
+ return response
548
+
549
+ def add_app_protection_rule(
550
+ self, name: str, action: str, zpn_inspection_profile_id: str, **kwargs
551
+ ) -> Box:
552
+ """
553
+ Add a new AppProtection Policy rule.
554
+
555
+ See the
556
+ `ZPA AppProtection Policy API reference <https://help.zscaler.com/zpa/configuring-appprotection-policies-using-api>`_
557
+ for further detail on optional keyword parameter structures.
558
+
559
+ Args:
560
+ name (str):
561
+ The name of the new rule.
562
+ action (str):
563
+ The action for the policy. Accepted values are:
564
+
565
+ | ``inspect``
566
+ | ``bypass_inspect``
567
+ **kwargs:
568
+ Optional keyword args.
569
+
570
+ Keyword Args:
571
+ conditions (list):
572
+ A list of conditional rule tuples. Tuples must follow the convention: `Object Type`, `LHS value`,
573
+ `RHS value`. If you are adding multiple values for the same object type then you will need
574
+ a new entry for each value.
575
+ E.g.
576
+
577
+ .. code-block:: python
578
+
579
+ [('app', 'id', '926196382959075416'),
580
+ ('app', 'id', '926196382959075417'),
581
+ ('app_group', 'id', '926196382959075332),
582
+ ('client_type', 'zpn_client_type_exporter')]
583
+ zpn_inspection_profile_id (str):
584
+ The AppProtection profile ID associated with the rule
585
+ description (str):
586
+ A description for the rule.
587
+
588
+ Returns:
589
+ :obj:`Box`: The resource record of the newly created Client Inspection Policy rule.
590
+
591
+ """
592
+
593
+ # Initialise the payload
594
+ payload = {
595
+ "name": name,
596
+ "action": action.upper(),
597
+ "zpnInspectionProfileId": zpn_inspection_profile_id,
598
+ "conditions": self._create_conditions(kwargs.pop("conditions", [])),
599
+ }
600
+
601
+ # Get the policy id of the provided policy type for the URL.
602
+ policy_id = self.get_policy("inspection").id
603
+
604
+ # Add optional parameters to payload
605
+ for key, value in kwargs.items():
606
+ payload[snake_to_camel(key)] = value
607
+
608
+ response = self.rest.post(f"policySet/{policy_id}/rule", json=payload)
609
+ if isinstance(response, Response):
610
+ # this is only true when the creation failed (status code is not 2xx)
611
+ status_code = response.status_code
612
+ # Handle error response
613
+ raise Exception(
614
+ f"API call failed with status {status_code}: {response.json()}"
615
+ )
616
+ return response
617
+
618
+ def update_rule(self, policy_type: str, rule_id: str, **kwargs) -> Box:
619
+ """
620
+ Update an existing policy rule.
621
+
622
+ Ensure you are using the correct arguments for the policy type that you want to update.
623
+
624
+ Args:
625
+ policy_type (str):
626
+ The policy type. Accepted values are:
627
+
628
+ | ``access``
629
+ | ``timeout``
630
+ | ``client_forwarding``
631
+ rule_id (str):
632
+ The unique identifier for the rule to be updated.
633
+ **kwargs:
634
+ Optional keyword args.
635
+
636
+ Keyword Args:
637
+ action (str):
638
+ The action for the policy. Accepted values are:
639
+
640
+ | ``allow``
641
+ | ``deny``
642
+ | ``intercept``
643
+ | ``intercept_accessible``
644
+ | ``bypass``
645
+ conditions (list):
646
+ A list of conditional rule tuples. Tuples must follow the convention: `Object Type`, `LHS value`,
647
+ `RHS value`. If you are adding multiple values for the same object type then you will need
648
+ a new entry for each value.
649
+ E.g.
650
+
651
+ .. code-block:: python
652
+
653
+ [('app', 'id', '926196382959075416'),
654
+ ('app', 'id', '926196382959075417'),
655
+ ('app_group', 'id', '926196382959075332),
656
+ ('client_type', 'zpn_client_type_exporter', 'zpn_client_type_zapp'),
657
+ ('trusted_network', 'b15e4cad-fa6e-8182-9fc3-8125ee6a65e1', True)]
658
+ custom_msg (str):
659
+ A custom message.
660
+ description (str):
661
+ A description for the rule.
662
+ re_auth_idle_timeout (int):
663
+ The re-authentication idle timeout value in seconds.
664
+ re_auth_timeout (int):
665
+ The re-authentication timeout value in seconds.
666
+
667
+ Returns:
668
+ :obj:`Box`: The updated policy-rule resource record.
669
+
670
+ Examples:
671
+ Updates the name only for an Access Policy rule:
672
+
673
+ >>> zpa.policies.update_rule('access', '99999', name='new_rule_name')
674
+
675
+ Updates the action only for a Client Forwarding Policy rule:
676
+
677
+ >>> zpa.policies.update_rule('client_forwarding', '888888', action='BYPASS')
678
+
679
+ """
680
+ # Get policy id for specified policy type
681
+ policy_id = self.get_policy(policy_type).id
682
+
683
+ payload = convert_keys(self.get_rule(policy_type, rule_id))
684
+
685
+ # Add optional parameters to payload
686
+ for key, value in kwargs.items():
687
+ if key == "conditions":
688
+ payload["conditions"] = self._create_conditions(value)
689
+ else:
690
+ payload[snake_to_camel(key)] = value
691
+
692
+ resp = self.rest.put(
693
+ f"policySet/{policy_id}/rule/{rule_id}", json=payload
694
+ ).status_code
695
+
696
+ # Return the object if it was updated successfully
697
+ if not isinstance(resp, Response):
698
+ return self.get_rule(policy_type, rule_id)
699
+
700
+ def update_access_rule(
701
+ self,
702
+ policy_type: str,
703
+ rule_id: str,
704
+ app_connector_group_ids: list = None,
705
+ app_server_group_ids: list = None,
706
+ **kwargs,
707
+ ) -> Box:
708
+ """
709
+ Update an existing policy rule.
710
+
711
+ Ensure you are using the correct arguments for the policy type that you want to update.
712
+
713
+ Args:
714
+ policy_type (str):
715
+ ...
716
+ rule_id (str):
717
+ ...
718
+ **kwargs:
719
+ ...
720
+
721
+ Keyword Args:
722
+ ...
723
+ app_connector_group_ids (:obj:`list` of :obj:`str`):
724
+ A list of application connector IDs that will be attached to the access policy rule. Defaults to an empty list.
725
+ app_server_group_ids (:obj:`list` of :obj:`str`):
726
+ A list of server group IDs that will be attached to the access policy rule. Defaults to an empty list.
727
+ Returns:
728
+ :obj:`Box`: The updated policy-rule resource record.
729
+
730
+ Examples:
731
+ ...
732
+ """
733
+ # Handle default values for app_connector_group_ids and app_server_group_ids
734
+ app_connector_group_ids = app_connector_group_ids or []
735
+ app_server_group_ids = app_server_group_ids or []
736
+
737
+ # Get policy id for specified policy type
738
+ policy_id = self.get_policy(policy_type).id
739
+
740
+ payload = convert_keys(self.get_rule(policy_type, rule_id))
741
+
742
+ # Update kwargs with app_connector_group_ids and app_server_group_ids for processing with add_id_groups
743
+ kwargs["app_connector_group_ids"] = app_connector_group_ids
744
+ kwargs["app_server_group_ids"] = app_server_group_ids
745
+
746
+ add_id_groups(self.reformat_params, kwargs, payload)
747
+
748
+ # Add optional parameters to payload
749
+ for key, value in kwargs.items():
750
+ if key == "conditions":
751
+ payload["conditions"] = self._create_conditions(value)
752
+ else:
753
+ payload[snake_to_camel(key)] = value
754
+
755
+ resp = self.rest.put(
756
+ f"policySet/{policy_id}/rule/{rule_id}", json=payload
757
+ ).status_code
758
+
759
+ # Return the object if it was updated successfully
760
+ if not isinstance(resp, Response):
761
+ return self.get_rule(policy_type, rule_id)
762
+
763
+ def reorder_rule(self, policy_type: str, rule_id: str, rule_order: str) -> Box:
764
+ """
765
+ Change the order of an existing policy rule.
766
+
767
+ Args:
768
+ rule_id (str):
769
+ The unique id of the rule that will be reordered.
770
+ rule_order (str):
771
+ The new order for the rule.
772
+ policy_type (str):
773
+ The policy type. Accepted values are:
774
+
775
+ | ``access``
776
+ | ``timeout``
777
+ | ``client_forwarding``
778
+
779
+ Returns:
780
+ :obj:`Box`: The updated policy rule resource record.
781
+
782
+ Examples:
783
+ Updates the order for an existing policy rule:
784
+
785
+ >>> zpa.policies.reorder_rule(policy_type='access',
786
+ ... rule_id='88888',
787
+ ... rule_order='2')
788
+
789
+ """
790
+ # Get policy id for specified policy type
791
+ policy_id = self.get_policy(policy_type).id
792
+
793
+ resp = self._put(
794
+ f"policySet/{policy_id}/rule/{rule_id}/reorder/{rule_order}"
795
+ ).status_code
796
+
797
+ if resp == 204:
798
+ return self.get_rule(policy_type, rule_id)
799
+
800
+ def sort_key(self, rules_orders: dict[str, int]):
801
+ def key(a, b):
802
+ if a.id in rules_orders and b.id in rules_orders:
803
+ if rules_orders[a.id] < rules_orders[b.id]:
804
+ return -1
805
+ return 1
806
+ if a.id in rules_orders:
807
+ return -1
808
+ elif b.id in rules_orders:
809
+ return 1
810
+
811
+ if a.rule_order < b.rule_order:
812
+ return -1
813
+ return 1
814
+
815
+ return key
816
+
817
+ def bulk_reorder_rules(self, policy_type: str, rules_orders: dict[str, int]) -> Box:
818
+ """
819
+ Bulk change the order of policy rules.
820
+
821
+ Args:
822
+ rules_orders (dict(rule_id=>order)):
823
+ A map of rule IDs and orders
824
+ policy_type (str):
825
+ The policy type. Accepted values are:
826
+
827
+ | ``access``
828
+ | ``timeout``
829
+ | ``client_forwarding``
830
+
831
+ """
832
+ # Get policy id for specified policy type
833
+ policy_set = self.get_policy(policy_type).id
834
+ all = self.list_rules(policy_type)
835
+ all.sort(key=functools.cmp_to_key(self.sort_key(rules_orders=rules_orders)))
836
+ orderedRules = [r.id for r in all]
837
+
838
+ # Construct the URL pathx
839
+ path = f"policySet/{policy_set}/reorder"
840
+
841
+ # Create a new PUT request
842
+ resp = self.rest.put(path, json=orderedRules)
843
+ if resp.status_code <= 299:
844
+ # Return the updated rule information
845
+ return None
846
+ else:
847
+ # Handle the case when the request fails (modify as needed)
848
+ return resp