zscaler-sdk-python 0.1.3__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 +35 -0
  2. zscaler/cache/__init__.py +0 -0
  3. zscaler/cache/cache.py +105 -0
  4. zscaler/cache/no_op_cache.py +66 -0
  5. zscaler/cache/zscaler_cache.py +159 -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 +21 -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 +37 -0
  16. zscaler/user_agent.py +21 -0
  17. zscaler/utils.py +546 -0
  18. zscaler/zia/__init__.py +629 -0
  19. zscaler/zia/activate.py +51 -0
  20. zscaler/zia/admin_and_role_management.py +336 -0
  21. zscaler/zia/apptotal.py +71 -0
  22. zscaler/zia/audit_logs.py +93 -0
  23. zscaler/zia/authentication_settings.py +94 -0
  24. zscaler/zia/client.py +88 -0
  25. zscaler/zia/cloud_apps.py +402 -0
  26. zscaler/zia/device_management.py +90 -0
  27. zscaler/zia/dlp.py +772 -0
  28. zscaler/zia/errors.py +37 -0
  29. zscaler/zia/firewall.py +1064 -0
  30. zscaler/zia/forwarding_control.py +267 -0
  31. zscaler/zia/isolation_profile.py +83 -0
  32. zscaler/zia/labels.py +178 -0
  33. zscaler/zia/locations.py +645 -0
  34. zscaler/zia/sandbox.py +201 -0
  35. zscaler/zia/security.py +232 -0
  36. zscaler/zia/ssl_inspection.py +169 -0
  37. zscaler/zia/traffic.py +819 -0
  38. zscaler/zia/url_categories.py +422 -0
  39. zscaler/zia/url_filtering.py +306 -0
  40. zscaler/zia/users.py +378 -0
  41. zscaler/zia/web_dlp.py +291 -0
  42. zscaler/zia/workload_groups.py +58 -0
  43. zscaler/zia/zpa_gateway.py +179 -0
  44. zscaler/zpa/README.md +40 -0
  45. zscaler/zpa/__init__.py +655 -0
  46. zscaler/zpa/app_segments.py +317 -0
  47. zscaler/zpa/app_segments_inspection.py +295 -0
  48. zscaler/zpa/app_segments_pra.py +296 -0
  49. zscaler/zpa/certificates.py +230 -0
  50. zscaler/zpa/client.py +113 -0
  51. zscaler/zpa/cloud_connector_groups.py +75 -0
  52. zscaler/zpa/connectors.py +500 -0
  53. zscaler/zpa/emergency_access.py +170 -0
  54. zscaler/zpa/errors.py +37 -0
  55. zscaler/zpa/idp.py +83 -0
  56. zscaler/zpa/inspection.py +968 -0
  57. zscaler/zpa/isolation_profile.py +85 -0
  58. zscaler/zpa/lss.py +544 -0
  59. zscaler/zpa/machine_groups.py +79 -0
  60. zscaler/zpa/policies.py +824 -0
  61. zscaler/zpa/posture_profiles.py +120 -0
  62. zscaler/zpa/privileged_remote_access.py +827 -0
  63. zscaler/zpa/provisioning.py +262 -0
  64. zscaler/zpa/saml_attributes.py +96 -0
  65. zscaler/zpa/scim_attributes.py +115 -0
  66. zscaler/zpa/scim_groups.py +133 -0
  67. zscaler/zpa/segment_groups.py +183 -0
  68. zscaler/zpa/server_groups.py +211 -0
  69. zscaler/zpa/servers.py +198 -0
  70. zscaler/zpa/service_edges.py +388 -0
  71. zscaler/zpa/trusted_networks.py +125 -0
  72. zscaler_sdk_python-0.1.3.dist-info/LICENSE.md +21 -0
  73. zscaler_sdk_python-0.1.3.dist-info/METADATA +362 -0
  74. zscaler_sdk_python-0.1.3.dist-info/RECORD +75 -0
  75. zscaler_sdk_python-0.1.3.dist-info/WHEEL +4 -0
@@ -0,0 +1,824 @@
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"] = [{"id": group_id} for group_id in app_connector_group_ids]
316
+
317
+ if app_server_group_ids:
318
+ payload["appServerGroups"] = [{"id": group_id} for group_id in app_server_group_ids]
319
+
320
+ add_id_groups(self.reformat_params, kwargs, payload)
321
+
322
+ # Get the policy id of the provided policy type for the URL.
323
+ policy_id = self.get_policy("access").id
324
+
325
+ # Add optional parameters to payload
326
+ for key, value in kwargs.items():
327
+ payload[snake_to_camel(key)] = value
328
+
329
+ response = self.rest.post(f"policySet/{policy_id}/rule", json=payload)
330
+ if isinstance(response, Response):
331
+ # this is only true when the creation failed (status code is not 2xx)
332
+ status_code = response.status_code
333
+ # Handle error response
334
+ raise Exception(f"API call failed with status {status_code}: {response.json()}")
335
+ return response
336
+
337
+ def add_timeout_rule(self, name: str, **kwargs) -> Box:
338
+ """
339
+ Add a new Timeout Policy rule.
340
+
341
+ See the `ZPA Timeout Policy API reference <https://help.zscaler.com/zpa/timeout-policy-use-cases>`_
342
+ for further detail on optional keyword parameter structures.
343
+
344
+ Args:
345
+ name (str):
346
+ The name of the new rule.
347
+ **kwargs:
348
+ Optional parameters.
349
+
350
+ Keyword Args:
351
+ conditions (list):
352
+ A list of conditional rule tuples. Tuples must follow the convention: `Object Type`, `LHS value`,
353
+ `RHS value`. If you are adding multiple values for the same object type then you will need
354
+ a new entry for each value.
355
+ E.g.
356
+
357
+ .. code-block:: python
358
+
359
+ [('app', 'id', '926196382959075416'),
360
+ ('app', 'id', '926196382959075417'),
361
+ ('app_group', 'id', '926196382959075332),
362
+ ('client_type', 'zpn_client_type_exporter', 'zpn_client_type_zapp'),
363
+ ('trusted_network', 'b15e4cad-fa6e-8182-9fc3-8125ee6a65e1', True)]
364
+ custom_msg (str):
365
+ A custom message.
366
+ description (str):
367
+ A description for the rule.
368
+ re_auth_idle_timeout (int):
369
+ The re-authentication idle timeout value in seconds.
370
+ re_auth_timeout (int):
371
+ The re-authentication timeout value in seconds.
372
+
373
+ Returns:
374
+ :obj:`Box`: The resource record of the newly created Timeout Policy rule.
375
+
376
+ """
377
+
378
+ # Initialise the payload
379
+ payload = {
380
+ "name": name,
381
+ "action": "RE_AUTH",
382
+ "conditions": self._create_conditions(kwargs.pop("conditions", [])),
383
+ }
384
+
385
+ # Get the policy id of the provided policy type for the URL.
386
+ policy_id = self.get_policy("timeout").id
387
+
388
+ # Use specified timeouts or default to UI values
389
+ payload["reauthTimeout"] = kwargs.get("re_auth_timeout", 172800)
390
+ payload["reauthIdleTimeout"] = kwargs.get("re_auth_idle_timeout", 600)
391
+
392
+ # Add optional parameters to payload
393
+ for key, value in kwargs.items():
394
+ payload[snake_to_camel(key)] = value
395
+
396
+ response = self.rest.post(f"policySet/{policy_id}/rule", json=payload)
397
+ if isinstance(response, Response):
398
+ # this is only true when the creation failed (status code is not 2xx)
399
+ status_code = response.status_code
400
+ # Handle error response
401
+ raise Exception(f"API call failed with status {status_code}: {response.json()}")
402
+ return response
403
+
404
+ def add_client_forwarding_rule(self, name: str, action: str, **kwargs) -> Box:
405
+ """
406
+ Add a new Client Forwarding Policy rule.
407
+
408
+ See the
409
+ `ZPA Client Forwarding Policy API reference <https://help.zscaler.com/zpa/client-forwarding-policy-use-cases>`_
410
+ for further detail on optional keyword parameter structures.
411
+
412
+ Args:
413
+ name (str):
414
+ The name of the new rule.
415
+ action (str):
416
+ The action for the policy. Accepted values are:
417
+
418
+ | ``intercept``
419
+ | ``intercept_accessible``
420
+ | ``bypass``
421
+ **kwargs:
422
+ Optional keyword args.
423
+
424
+ Keyword Args:
425
+ conditions (list):
426
+ A list of conditional rule tuples. Tuples must follow the convention: `Object Type`, `LHS value`,
427
+ `RHS value`. If you are adding multiple values for the same object type then you will need
428
+ a new entry for each value.
429
+ E.g.
430
+
431
+ .. code-block:: python
432
+
433
+ [('app', 'id', '926196382959075416'),
434
+ ('app', 'id', '926196382959075417'),
435
+ ('app_group', 'id', '926196382959075332),
436
+ ('client_type', 'zpn_client_type_exporter', 'zpn_client_type_zapp'),
437
+ ('trusted_network', 'b15e4cad-fa6e-8182-9fc3-8125ee6a65e1', True)]
438
+ custom_msg (str):
439
+ A custom message.
440
+ description (str):
441
+ A description for the rule.
442
+
443
+ Returns:
444
+ :obj:`Box`: The resource record of the newly created Client Forwarding Policy rule.
445
+
446
+ """
447
+
448
+ # Initialise the payload
449
+ payload = {
450
+ "name": name,
451
+ "action": action.upper(),
452
+ "conditions": self._create_conditions(kwargs.pop("conditions", [])),
453
+ }
454
+
455
+ # Get the policy id of the provided policy type for the URL.
456
+ policy_id = self.get_policy("client_forwarding").id
457
+
458
+ # Add optional parameters to payload
459
+ for key, value in kwargs.items():
460
+ payload[snake_to_camel(key)] = value
461
+
462
+ response = self.rest.post(f"policySet/{policy_id}/rule", json=payload)
463
+ if isinstance(response, Response):
464
+ # this is only true when the creation failed (status code is not 2xx)
465
+ status_code = response.status_code
466
+ # Handle error response
467
+ raise Exception(f"API call failed with status {status_code}: {response.json()}")
468
+ return response
469
+
470
+ def add_isolation_rule(self, name: str, action: str, zpn_isolation_profile_id: str, **kwargs) -> Box:
471
+ """
472
+ Add a new Isolation Policy rule.
473
+
474
+ See the
475
+ `ZPA Isolation Policy API reference <https://help.zscaler.com/zpa/configuring-isolation-policies-using-api>`_
476
+ for further detail on optional keyword parameter structures.
477
+
478
+ Args:
479
+ name (str):
480
+ The name of the new rule.
481
+ action (str):
482
+ The action for the policy. Accepted values are:
483
+
484
+ | ``isolate``
485
+ | ``bypass_isolate``
486
+ **kwargs:
487
+ Optional keyword args.
488
+
489
+ Keyword Args:
490
+ conditions (list):
491
+ A list of conditional rule tuples. Tuples must follow the convention: `Object Type`, `LHS value`,
492
+ `RHS value`. If you are adding multiple values for the same object type then you will need
493
+ a new entry for each value.
494
+ E.g.
495
+
496
+ .. code-block:: python
497
+
498
+ [('app', 'id', '926196382959075416'),
499
+ ('app', 'id', '926196382959075417'),
500
+ ('app_group', 'id', '926196382959075332),
501
+ ('client_type', 'zpn_client_type_exporter')]
502
+ zpn_isolation_profile_id (str):
503
+ The isolation profile ID associated with the rule
504
+ description (str):
505
+ A description for the rule.
506
+
507
+ Returns:
508
+ :obj:`Box`: The resource record of the newly created Client Isolation Policy rule.
509
+
510
+ """
511
+
512
+ # Initialise the payload
513
+ payload = {
514
+ "name": name,
515
+ "action": action.upper(),
516
+ "zpnIsolationProfileId": zpn_isolation_profile_id,
517
+ "conditions": self._create_conditions(kwargs.pop("conditions", [])),
518
+ }
519
+
520
+ # Get the policy id of the provided policy type for the URL.
521
+ policy_id = self.get_policy("isolation").id
522
+
523
+ # Add optional parameters to payload
524
+ for key, value in kwargs.items():
525
+ payload[snake_to_camel(key)] = value
526
+
527
+ response = self.rest.post(f"policySet/{policy_id}/rule", json=payload)
528
+ if isinstance(response, Response):
529
+ # this is only true when the creation failed (status code is not 2xx)
530
+ status_code = response.status_code
531
+ # Handle error response
532
+ raise Exception(f"API call failed with status {status_code}: {response.json()}")
533
+ return response
534
+
535
+ def add_app_protection_rule(self, name: str, action: str, zpn_inspection_profile_id: str, **kwargs) -> Box:
536
+ """
537
+ Add a new AppProtection Policy rule.
538
+
539
+ See the
540
+ `ZPA AppProtection Policy API reference <https://help.zscaler.com/zpa/configuring-appprotection-policies-using-api>`_
541
+ for further detail on optional keyword parameter structures.
542
+
543
+ Args:
544
+ name (str):
545
+ The name of the new rule.
546
+ action (str):
547
+ The action for the policy. Accepted values are:
548
+
549
+ | ``inspect``
550
+ | ``bypass_inspect``
551
+ **kwargs:
552
+ Optional keyword args.
553
+
554
+ Keyword Args:
555
+ conditions (list):
556
+ A list of conditional rule tuples. Tuples must follow the convention: `Object Type`, `LHS value`,
557
+ `RHS value`. If you are adding multiple values for the same object type then you will need
558
+ a new entry for each value.
559
+ E.g.
560
+
561
+ .. code-block:: python
562
+
563
+ [('app', 'id', '926196382959075416'),
564
+ ('app', 'id', '926196382959075417'),
565
+ ('app_group', 'id', '926196382959075332),
566
+ ('client_type', 'zpn_client_type_exporter')]
567
+ zpn_inspection_profile_id (str):
568
+ The AppProtection profile ID associated with the rule
569
+ description (str):
570
+ A description for the rule.
571
+
572
+ Returns:
573
+ :obj:`Box`: The resource record of the newly created Client Inspection Policy rule.
574
+
575
+ """
576
+
577
+ # Initialise the payload
578
+ payload = {
579
+ "name": name,
580
+ "action": action.upper(),
581
+ "zpnInspectionProfileId": zpn_inspection_profile_id,
582
+ "conditions": self._create_conditions(kwargs.pop("conditions", [])),
583
+ }
584
+
585
+ # Get the policy id of the provided policy type for the URL.
586
+ policy_id = self.get_policy("inspection").id
587
+
588
+ # Add optional parameters to payload
589
+ for key, value in kwargs.items():
590
+ payload[snake_to_camel(key)] = value
591
+
592
+ response = self.rest.post(f"policySet/{policy_id}/rule", json=payload)
593
+ if isinstance(response, Response):
594
+ # this is only true when the creation failed (status code is not 2xx)
595
+ status_code = response.status_code
596
+ # Handle error response
597
+ raise Exception(f"API call failed with status {status_code}: {response.json()}")
598
+ return response
599
+
600
+ def update_rule(self, policy_type: str, rule_id: str, **kwargs) -> Box:
601
+ """
602
+ Update an existing policy rule.
603
+
604
+ Ensure you are using the correct arguments for the policy type that you want to update.
605
+
606
+ Args:
607
+ policy_type (str):
608
+ The policy type. Accepted values are:
609
+
610
+ | ``access``
611
+ | ``timeout``
612
+ | ``client_forwarding``
613
+ rule_id (str):
614
+ The unique identifier for the rule to be updated.
615
+ **kwargs:
616
+ Optional keyword args.
617
+
618
+ Keyword Args:
619
+ action (str):
620
+ The action for the policy. Accepted values are:
621
+
622
+ | ``allow``
623
+ | ``deny``
624
+ | ``intercept``
625
+ | ``intercept_accessible``
626
+ | ``bypass``
627
+ conditions (list):
628
+ A list of conditional rule tuples. Tuples must follow the convention: `Object Type`, `LHS value`,
629
+ `RHS value`. If you are adding multiple values for the same object type then you will need
630
+ a new entry for each value.
631
+ E.g.
632
+
633
+ .. code-block:: python
634
+
635
+ [('app', 'id', '926196382959075416'),
636
+ ('app', 'id', '926196382959075417'),
637
+ ('app_group', 'id', '926196382959075332),
638
+ ('client_type', 'zpn_client_type_exporter', 'zpn_client_type_zapp'),
639
+ ('trusted_network', 'b15e4cad-fa6e-8182-9fc3-8125ee6a65e1', True)]
640
+ custom_msg (str):
641
+ A custom message.
642
+ description (str):
643
+ A description for the rule.
644
+ re_auth_idle_timeout (int):
645
+ The re-authentication idle timeout value in seconds.
646
+ re_auth_timeout (int):
647
+ The re-authentication timeout value in seconds.
648
+
649
+ Returns:
650
+ :obj:`Box`: The updated policy-rule resource record.
651
+
652
+ Examples:
653
+ Updates the name only for an Access Policy rule:
654
+
655
+ >>> zpa.policies.update_rule('access', '99999', name='new_rule_name')
656
+
657
+ Updates the action only for a Client Forwarding Policy rule:
658
+
659
+ >>> zpa.policies.update_rule('client_forwarding', '888888', action='BYPASS')
660
+
661
+ """
662
+ # Get policy id for specified policy type
663
+ policy_id = self.get_policy(policy_type).id
664
+
665
+ payload = convert_keys(self.get_rule(policy_type, rule_id))
666
+
667
+ # Add optional parameters to payload
668
+ for key, value in kwargs.items():
669
+ if key == "conditions":
670
+ payload["conditions"] = self._create_conditions(value)
671
+ else:
672
+ payload[snake_to_camel(key)] = value
673
+
674
+ resp = self.rest.put(f"policySet/{policy_id}/rule/{rule_id}", json=payload).status_code
675
+
676
+ # Return the object if it was updated successfully
677
+ if not isinstance(resp, Response):
678
+ return self.get_rule(policy_type, rule_id)
679
+
680
+ def update_access_rule(
681
+ self,
682
+ policy_type: str,
683
+ rule_id: str,
684
+ app_connector_group_ids: list = None,
685
+ app_server_group_ids: list = None,
686
+ **kwargs,
687
+ ) -> Box:
688
+ """
689
+ Update an existing policy rule.
690
+
691
+ Ensure you are using the correct arguments for the policy type that you want to update.
692
+
693
+ Args:
694
+ policy_type (str):
695
+ ...
696
+ rule_id (str):
697
+ ...
698
+ **kwargs:
699
+ ...
700
+
701
+ Keyword Args:
702
+ ...
703
+ app_connector_group_ids (:obj:`list` of :obj:`str`):
704
+ A list of application connector IDs that will be attached to the access policy rule. Defaults to an empty list.
705
+ app_server_group_ids (:obj:`list` of :obj:`str`):
706
+ A list of server group IDs that will be attached to the access policy rule. Defaults to an empty list.
707
+ Returns:
708
+ :obj:`Box`: The updated policy-rule resource record.
709
+
710
+ Examples:
711
+ ...
712
+ """
713
+ # Handle default values for app_connector_group_ids and app_server_group_ids
714
+ app_connector_group_ids = app_connector_group_ids or []
715
+ app_server_group_ids = app_server_group_ids or []
716
+
717
+ # Get policy id for specified policy type
718
+ policy_id = self.get_policy(policy_type).id
719
+
720
+ payload = convert_keys(self.get_rule(policy_type, rule_id))
721
+
722
+ # Update kwargs with app_connector_group_ids and app_server_group_ids for processing with add_id_groups
723
+ kwargs["app_connector_group_ids"] = app_connector_group_ids
724
+ kwargs["app_server_group_ids"] = app_server_group_ids
725
+
726
+ add_id_groups(self.reformat_params, kwargs, payload)
727
+
728
+ # Add optional parameters to payload
729
+ for key, value in kwargs.items():
730
+ if key == "conditions":
731
+ payload["conditions"] = self._create_conditions(value)
732
+ else:
733
+ payload[snake_to_camel(key)] = value
734
+
735
+ resp = self.rest.put(f"policySet/{policy_id}/rule/{rule_id}", json=payload).status_code
736
+
737
+ # Return the object if it was updated successfully
738
+ if not isinstance(resp, Response):
739
+ return self.get_rule(policy_type, rule_id)
740
+
741
+ def reorder_rule(self, policy_type: str, rule_id: str, rule_order: str) -> Box:
742
+ """
743
+ Change the order of an existing policy rule.
744
+
745
+ Args:
746
+ rule_id (str):
747
+ The unique id of the rule that will be reordered.
748
+ rule_order (str):
749
+ The new order for the rule.
750
+ policy_type (str):
751
+ The policy type. Accepted values are:
752
+
753
+ | ``access``
754
+ | ``timeout``
755
+ | ``client_forwarding``
756
+
757
+ Returns:
758
+ :obj:`Box`: The updated policy rule resource record.
759
+
760
+ Examples:
761
+ Updates the order for an existing policy rule:
762
+
763
+ >>> zpa.policies.reorder_rule(policy_type='access',
764
+ ... rule_id='88888',
765
+ ... rule_order='2')
766
+
767
+ """
768
+ # Get policy id for specified policy type
769
+ policy_id = self.get_policy(policy_type).id
770
+
771
+ resp = self._put(f"policySet/{policy_id}/rule/{rule_id}/reorder/{rule_order}").status_code
772
+
773
+ if resp == 204:
774
+ return self.get_rule(policy_type, rule_id)
775
+
776
+ def sort_key(self, rules_orders: dict[str, int]):
777
+ def key(a, b):
778
+ if a.id in rules_orders and b.id in rules_orders:
779
+ if rules_orders[a.id] < rules_orders[b.id]:
780
+ return -1
781
+ return 1
782
+ if a.id in rules_orders:
783
+ return -1
784
+ elif b.id in rules_orders:
785
+ return 1
786
+
787
+ if a.rule_order < b.rule_order:
788
+ return -1
789
+ return 1
790
+
791
+ return key
792
+
793
+ def bulk_reorder_rules(self, policy_type: str, rules_orders: dict[str, int]) -> Box:
794
+ """
795
+ Bulk change the order of policy rules.
796
+
797
+ Args:
798
+ rules_orders (dict(rule_id=>order)):
799
+ A map of rule IDs and orders
800
+ policy_type (str):
801
+ The policy type. Accepted values are:
802
+
803
+ | ``access``
804
+ | ``timeout``
805
+ | ``client_forwarding``
806
+
807
+ """
808
+ # Get policy id for specified policy type
809
+ policy_set = self.get_policy(policy_type).id
810
+ all = self.list_rules(policy_type)
811
+ all.sort(key=functools.cmp_to_key(self.sort_key(rules_orders=rules_orders)))
812
+ orderedRules = [r.id for r in all]
813
+
814
+ # Construct the URL pathx
815
+ path = f"policySet/{policy_set}/reorder"
816
+
817
+ # Create a new PUT request
818
+ resp = self.rest.put(path, json=orderedRules)
819
+ if resp.status_code <= 299:
820
+ # Return the updated rule information
821
+ return None
822
+ else:
823
+ # Handle the case when the request fails (modify as needed)
824
+ return resp