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,827 @@
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
+ from typing import Any, Dict, List, Optional
19
+
20
+ from box import Box, BoxList
21
+ from requests import Response
22
+
23
+ from zscaler.utils import is_valid_ssh_key, snake_to_camel, validate_and_convert_times
24
+ from zscaler.zpa.client import ZPAClient
25
+
26
+
27
+ class PrivilegedRemoteAccessAPI:
28
+ def __init__(self, client: ZPAClient):
29
+ self.rest = client
30
+
31
+ def list_portals(self, **kwargs) -> BoxList:
32
+ """
33
+ Returns a list of all privileged remote access portals.
34
+
35
+ Keyword Args:
36
+ **max_items (int):
37
+ The maximum number of items to request before stopping iteration.
38
+ **max_pages (int):
39
+ The maximum number of pages to request before stopping iteration.
40
+ **pagesize (int):
41
+ Specifies the page size. The default size is 20, but the maximum size is 500.
42
+ **search (str, optional):
43
+ The search string used to match against features and fields.
44
+
45
+ Returns:
46
+ :obj:`BoxList`: A list of all configured privileged remote access portals.
47
+
48
+ Examples:
49
+ >>> for pra_portal in zpa.privileged_remote_access.list_portals():
50
+ ... pprint(pra_portal)
51
+
52
+ """
53
+ list, _ = self.rest.get_paginated_data(path="/praPortal", **kwargs, api_version="v1")
54
+ return list
55
+
56
+ def get_portal(self, portal_id: str) -> Box:
57
+ """
58
+ Returns information on the specified pra portal.
59
+
60
+ Args:
61
+ portal_id (str):
62
+ The unique identifier for the pra portal.
63
+
64
+ Returns:
65
+ :obj:`Box`: The resource record for the pra portal.
66
+
67
+ Examples:
68
+ >>> pprint(zpa.privileged_remote_access.get_portal('99999'))
69
+
70
+ """
71
+ return self.rest.get(f"praPortal/{portal_id}")
72
+
73
+ def add_portal(
74
+ self,
75
+ name: str,
76
+ certificate_id: str,
77
+ domain: str,
78
+ enabled: bool = True,
79
+ user_notification_enabled: bool = True,
80
+ **kwargs,
81
+ ) -> Box:
82
+ """
83
+ Add a privileged remote access portal.
84
+
85
+ Args:
86
+ name (str):
87
+ The name of the privileged portal.
88
+ enabled (bool):
89
+ Whether or not the privileged portal is enabled. Default is True.
90
+ certificate_id (bool):
91
+ The unique identifier of the certificate.
92
+ domain (str):
93
+ The domain of the privileged portal.
94
+ **kwargs:
95
+ Optional keyword args.
96
+
97
+ Keyword Args:
98
+ description (str):
99
+ The description of the privileged portal.
100
+ user_notification (str):
101
+ The notification message displayed in the banner of the privileged portallink, if enabled.
102
+ user_notification_enabled (bool):
103
+ Indicates if the Notification Banner is enabled (true) or disabled (false)
104
+
105
+ Returns:
106
+ :obj:`Box`: The resource record for the newly created portal.
107
+
108
+ Examples:
109
+ Create a pra portal with the minimum required parameters:
110
+
111
+ >>> zpa.privileged_remote_access.add_portal(
112
+ ... name='PRA Portal Example',
113
+ ... certificate_id='123456789',
114
+ ... user_notification_enabled=True)
115
+ """
116
+ payload = {
117
+ "name": name,
118
+ "enabled": enabled,
119
+ "domain": domain,
120
+ "userNotificationEnabled": user_notification_enabled,
121
+ "certificateId": certificate_id,
122
+ }
123
+
124
+ # Add optional parameters to payload
125
+ for key, value in kwargs.items():
126
+ payload[snake_to_camel(key)] = value
127
+
128
+ response = self.rest.post("/praPortal", json=payload)
129
+ if isinstance(response, Response):
130
+ status_code = response.status_code
131
+ if status_code > 299:
132
+ return None
133
+ return self.get_portal(response.get("id"))
134
+
135
+ def update_portal(self, portal_id: str, **kwargs) -> Box:
136
+ """
137
+ Updates the specified pra portal.
138
+
139
+ Args:
140
+ portal_id (str):
141
+ The unique identifier for the portal being updated.
142
+ **kwargs:
143
+ Optional keyword args.
144
+
145
+ Keyword Args:
146
+ name (str):
147
+ The name of the privileged portal.
148
+ description (str):
149
+ The description of the privileged portal.
150
+ enabled (bool):
151
+ Whether or not the privileged portal is enabled. Default is True
152
+ certificate_id (bool):
153
+ Whether or not The unique identifier of the certificate.
154
+ domain (str):
155
+ The domain of the privileged portal.
156
+ user_notification (str):
157
+ The notification message displayed in the banner of the privileged portallink, if enabled.
158
+ user_notification_enabled (bool):
159
+ Indicates if the Notification Banner is enabled (true) or disabled (false)
160
+
161
+ Returns:
162
+ :obj:`Box`: The resource record for the updated portal.
163
+
164
+ Examples:
165
+ Update the name of a portal:
166
+
167
+ >>> zpa.privileged_remote_access.update_portal(
168
+ ... '99999',
169
+ ... name='Updated PRA Portal')
170
+
171
+ Update the pra portal:
172
+
173
+ >>> zpa.privileged_remote_access.update_portal(
174
+ ... '99999',
175
+ ... name='Updated PRA Portal')
176
+
177
+ """
178
+ # Set payload to value of existing record
179
+ payload = {snake_to_camel(k): v for k, v in self.get_portal(portal_id).items()}
180
+
181
+ # Add optional parameters to payload
182
+ for key, value in kwargs.items():
183
+ payload[snake_to_camel(key)] = value
184
+
185
+ resp = self.rest.put(f"praPortal/{portal_id}", json=payload).status_code
186
+
187
+ if resp == 204:
188
+ return self.get_portal(portal_id)
189
+
190
+ def delete_portal(self, portal_id: str) -> int:
191
+ """
192
+ Delete the specified pra portal.
193
+
194
+ Args:
195
+ portal_id (str): The unique identifier for the portal to be deleted.
196
+
197
+ Returns:
198
+ :obj:`int`: The response code for the operation.
199
+
200
+ Examples:
201
+ >>> zpa.privileged_remote_access.delete_portal('99999')
202
+
203
+ """
204
+ return self.rest.delete(f"praPortal/{portal_id}").status_code
205
+
206
+ def list_consoles(self, **kwargs) -> BoxList:
207
+ """
208
+ Returns a list of all privileged remote access consoles.
209
+
210
+ Keyword Args:
211
+ **max_items (int):
212
+ The maximum number of items to request before stopping iteration.
213
+ **max_pages (int):
214
+ The maximum number of pages to request before stopping iteration.
215
+ **pagesize (int):
216
+ Specifies the page size. The default size is 20, but the maximum size is 500.
217
+ **search (str, optional):
218
+ The search string used to match against features and fields.
219
+
220
+ Returns:
221
+ :obj:`BoxList`: A list of all configured privileged remote access consoles.
222
+
223
+ Examples:
224
+ >>> for pra_console in zpa.privileged_remote_access.list_consoles():
225
+ ... pprint(pra_console)
226
+
227
+ """
228
+ list, _ = self.rest.get_paginated_data(path="/praConsole", **kwargs, api_version="v1")
229
+ return list
230
+
231
+ def get_console(self, console_id: str) -> Box:
232
+ """
233
+ Returns information on the specified pra console.
234
+
235
+ Args:
236
+ console_id (str):
237
+ The unique identifier for the pra console.
238
+
239
+ Returns:
240
+ :obj:`Box`: The resource record for the pra console.
241
+
242
+ Examples:
243
+ >>> pprint(zpa.privileged_remote_access.get_console('99999'))
244
+
245
+ """
246
+ return self.rest.get(f"praConsole/{console_id}")
247
+
248
+ def get_console_portal(self, portal_id: str) -> Box:
249
+ """
250
+ Returns information on the specified pra console of the privileged portal.
251
+
252
+ Args:
253
+ portal_id (str):
254
+ The unique identifier of the privileged portal.
255
+
256
+ Returns:
257
+ :obj:`Box`: The resource record for the privileged portal.
258
+
259
+ Examples:
260
+ >>> pprint(zpa.privileged_remote_access.get_console_portal('99999'))
261
+
262
+ """
263
+ return self.rest.get(f"praConsole/praPortal/{portal_id}")
264
+
265
+ def add_console(
266
+ self,
267
+ name: str,
268
+ pra_application_id: str,
269
+ pra_portal_ids: list,
270
+ enabled: bool = True,
271
+ **kwargs,
272
+ ) -> Box:
273
+ """
274
+ Adds a new Privileged Remote Access (PRA) console.
275
+
276
+ Args:
277
+ name (str): The name of the PRA console.
278
+ pra_application_id (str): The unique identifier of the associated PRA application.
279
+ pra_portal_ids (list of str): A list of unique identifiers for the associated PRA portals.
280
+ enabled (bool, optional): Indicates whether the console is enabled. Defaults to True.
281
+
282
+ Keyword Args:
283
+ description (str, optional): A description for the PRA console.
284
+
285
+ Returns:
286
+ Box: A Box object containing the details of the newly created console.
287
+
288
+ Examples:
289
+ >>> zpa.privileged_remote_access.add_console(
290
+ ... name='PRA Console Example',
291
+ ... pra_application_id='999999999',
292
+ ... pra_portal_ids=['999999999'],
293
+ ... description='PRA Console Description',
294
+ ... enabled=True
295
+ ... )
296
+ """
297
+ payload = {
298
+ "name": name,
299
+ "enabled": enabled,
300
+ "praApplication": {"id": pra_application_id},
301
+ "praPortals": [{"id": portal_id} for portal_id in pra_portal_ids],
302
+ }
303
+
304
+ # Add optional parameters to payload
305
+ for key, value in kwargs.items():
306
+ payload[snake_to_camel(key)] = value
307
+
308
+ response = self.rest.post("praConsole", json=payload)
309
+ if isinstance(response, Response):
310
+ # this is only true when the creation failed (status code is not 2xx)
311
+ status_code = response.status_code
312
+ # Handle error response
313
+ raise Exception(f"API call failed with status {status_code}: {response.json()}")
314
+ return response
315
+
316
+ def update_console(
317
+ self,
318
+ console_id: str,
319
+ pra_application_id: str = None,
320
+ pra_portal_ids: list = None,
321
+ **kwargs,
322
+ ) -> Box:
323
+ """
324
+ Updates the specified PRA console. All the attributes are required by the API.
325
+
326
+ Args:
327
+ console_id (str): The unique identifier of the console being updated.
328
+
329
+ Keyword Args:
330
+ name (str): The new name of the PRA console.
331
+ description (str): The new description of the PRA console.
332
+ enabled (bool): Indicates whether the console should be enabled.
333
+ pra_application_id (str): The unique identifier of the associated PRA application to be linked with the console.
334
+ pra_portal_ids (list of str): List of unique IDs for the associated PRA portals to be linked with the console.
335
+
336
+ Returns:
337
+ Box: A Box object containing the details of the updated console.
338
+
339
+ Examples:
340
+ >>> zpa.privileged_remote_access.update_console(
341
+ ... console_id='99999',
342
+ ... name='Updated PRA Console',
343
+ ... description='Updated Description',
344
+ ... enabled=True,
345
+ ... pra_application_id='999999999',
346
+ ... pra_portal_ids=['999999999']
347
+ ... )
348
+ """
349
+ # Fetch existing console details first if necessary
350
+ existing_console = self.get_console(console_id)
351
+
352
+ # Set payload to value of existing record if needed
353
+ payload = {snake_to_camel(k): v for k, v in existing_console.items()}
354
+
355
+ if pra_application_id:
356
+ payload["praApplication"] = {"id": pra_application_id}
357
+ if pra_portal_ids:
358
+ payload["praPortals"] = [{"id": id} for id in pra_portal_ids]
359
+
360
+ # Add/Update optional parameters in payload
361
+ for key, value in kwargs.items():
362
+ payload[snake_to_camel(key)] = value
363
+
364
+ resp = self.rest.put(f"praConsole/{console_id}", json=payload).status_code
365
+ if not isinstance(resp, Response):
366
+ return self.get_console(console_id)
367
+
368
+ def delete_console(self, console_id: str) -> int:
369
+ """
370
+ Delete the specified pra console.
371
+
372
+ Args:
373
+ console_id (str): The unique identifier for the console to be deleted.
374
+
375
+ Returns:
376
+ :obj:`int`: The response code for the operation.
377
+
378
+ Examples:
379
+ >>> zpa.privileged_remote_access.delete_console('99999')
380
+
381
+ """
382
+ response = self.rest.delete(f"/praConsole/{console_id}")
383
+ if response.status_code != 204:
384
+ raise Exception(f"Failed to delete console: {response.text}")
385
+ return response.status_code
386
+
387
+ def add_bulk_console(self, consoles: List[Dict[str, Any]]) -> Box:
388
+ """
389
+ Adds a list of Privileged Remote Access (PRA) consoles in bulk.
390
+
391
+ Args:
392
+ consoles (List[Dict[str, Any]]): A list of dictionaries where each dictionary
393
+ contains details of a PRA console to be added. Required keys in each dictionary include
394
+ 'name', 'pra_application_id', and 'pra_portal_ids'. Optionally, 'enabled' and 'description'
395
+ can also be included.
396
+
397
+ Returns:
398
+ Box: A Box object containing the details of the newly created consoles.
399
+
400
+ Examples:
401
+ >>> zpa.privileged_remote_access.add_bulk_console([
402
+ ... {
403
+ ... 'name': 'PRA Console Example 1',
404
+ ... 'pra_application_id': '999999999',
405
+ ... 'pra_portal_ids': ['999999998'],
406
+ ... 'description': 'PRA Console Description 1',
407
+ ... 'enabled': True
408
+ ... },
409
+ ... {
410
+ ... 'name': 'PRA Console Example 2',
411
+ ... 'pra_application_id': '999999999',
412
+ ... 'pra_portal_ids': ['999999997'],
413
+ ... 'description': 'PRA Console Description 2',
414
+ ... 'enabled': True
415
+ ... }
416
+ ... ])
417
+ """
418
+ # Transform the input list of console dictionaries to the expected JSON payload format
419
+ payload = [
420
+ {
421
+ "name": console.get("name"),
422
+ "description": console.get("description", ""),
423
+ "enabled": console.get("enabled", True),
424
+ "praApplication": {"id": console.get("pra_application_id")},
425
+ "praPortals": [{"id": id} for id in console.get("pra_portal_ids", [])],
426
+ }
427
+ for console in consoles
428
+ ]
429
+
430
+ response = self.rest.post("praConsole/bulk", json=payload)
431
+ if isinstance(response, Response):
432
+ status_code = response.status_code
433
+ # Handle error response
434
+ raise Exception(f"API call failed with status {status_code}: {response.json()}")
435
+ return response
436
+
437
+ def list_credentials(self, **kwargs) -> BoxList:
438
+ """
439
+ Returns a list of all privileged remote access credentials.
440
+
441
+ Keyword Args:
442
+ **max_items (int):
443
+ The maximum number of items to request before stopping iteration.
444
+ **max_pages (int):
445
+ The maximum number of pages to request before stopping iteration.
446
+ **pagesize (int):
447
+ Specifies the page size. The default size is 20, but the maximum size is 500.
448
+ **search (str, optional):
449
+ The search string used to match against features and fields.
450
+
451
+ Returns:
452
+ :obj:`BoxList`: A list of all configured privileged remote access credentials.
453
+
454
+ Examples:
455
+ >>> for pra_credential in zpa.privileged_remote_access.list_credentials():
456
+ ... pprint(pra_credential)
457
+
458
+ """
459
+ list, _ = self.rest.get_paginated_data(path="/credential", **kwargs, api_version="v1")
460
+ return list
461
+
462
+ def get_credential(self, credential_id: str) -> Box:
463
+ """
464
+ Returns information on the specified pra credential.
465
+
466
+ Args:
467
+ credential_id (str):
468
+ The unique identifier for the pra credential.
469
+
470
+ Returns:
471
+ :obj:`Box`: The resource record for the pra credential.
472
+
473
+ Examples:
474
+ >>> pprint(zpa.privileged_remote_access.get_credential('99999'))
475
+
476
+ """
477
+ return self.rest.get(f"credential/{credential_id}")
478
+ # response = self.rest.get("/credential/%s" % (credential_id))
479
+ # if isinstance(response, Response):
480
+ # status_code = response.status_code
481
+ # if status_code != 200:
482
+ # return None
483
+ # return response
484
+
485
+ def add_credential(
486
+ self,
487
+ name: str,
488
+ credential_type: str,
489
+ username: Optional[str] = None,
490
+ password: Optional[str] = None,
491
+ private_key: Optional[str] = None,
492
+ **kwargs,
493
+ ) -> Box:
494
+ """
495
+ Validates input based on credential_type and adds a new credential.
496
+ """
497
+ payload = {"name": name, "credentialType": credential_type}
498
+
499
+ if credential_type == "USERNAME_PASSWORD":
500
+ if not username or not password:
501
+ raise ValueError("Username and password must be provided for USERNAME_PASSWORD type.")
502
+ payload.update({"userName": username, "password": password})
503
+
504
+ elif credential_type == "SSH_KEY":
505
+ if not username or not private_key:
506
+ raise ValueError("Username and private_key must be provided for SSH_KEY type.")
507
+ if not is_valid_ssh_key(private_key):
508
+ raise ValueError("Invalid SSH key format.")
509
+ payload.update({"userName": username, "privateKey": private_key})
510
+
511
+ elif credential_type == "PASSWORD":
512
+ if not password:
513
+ raise ValueError("Password must be provided for PASSWORD type.")
514
+ payload["password"] = password
515
+
516
+ else:
517
+ raise ValueError(f"Unsupported credential_type: {credential_type}")
518
+
519
+ # Add optional parameters to payload
520
+ for key, value in kwargs.items():
521
+ if key in ["description", "user_domain", "passphrase"]:
522
+ payload[snake_to_camel(key)] = value
523
+
524
+ response = self.rest.post("credential", json=payload)
525
+ if isinstance(response, Response):
526
+ # this is only true when the creation failed (status code is not 2xx)
527
+ status_code = response.status_code
528
+ # Handle error response
529
+ raise Exception(f"API call failed with status {status_code}: {response.json()}")
530
+ return response
531
+
532
+ def update_credential(self, credential_id: str, **kwargs) -> Box:
533
+ """
534
+ Updates a specified credential based on provided keyword arguments.
535
+
536
+ Args:
537
+ credential_id (str): The unique identifier for the credential being updated.
538
+
539
+ Keyword Args:
540
+ All attributes of the credential that can be updated including but not limited to:
541
+ - username (str): Username for 'USERNAME_PASSWORD' and 'SSH_KEY' types.
542
+ - password (str): Password for 'USERNAME_PASSWORD' and 'PASSWORD' types.
543
+ - private_key (str): SSH private key for 'SSH_KEY' type.
544
+ - description (str): Description of the credential.
545
+ - user_domain (str): Domain associated with the username.
546
+ - passphrase (str): Passphrase for the SSH private key, applicable only for 'SSH_KEY'.
547
+
548
+ Returns:
549
+ Box: The resource record for the updated credential.
550
+
551
+ Raises:
552
+ Exception: If fetching the credential fails or the required parameters are missing based on the credential type.
553
+
554
+ Examples:
555
+ Update a USERNAME_PASSWORD credential:
556
+ >>> zpa.privileged_remote_access.update_credential(
557
+ ... credential_id='2223',
558
+ ... username='jdoe',
559
+ ... name='John Doe',
560
+ ... credential_type='USERNAME_PASSWORD',
561
+ ... password='**********',
562
+ ... description='Updated credential description'
563
+ ... )
564
+ """
565
+ # Fetch the existing credential to determine the credential type
566
+ existing_credential = self.get_credential(credential_id)
567
+ if not existing_credential:
568
+ raise Exception(f"Failed to fetch credential {credential_id}")
569
+
570
+ # Validate and enforce required fields based on the credential type
571
+ credential_type = existing_credential.credential_type
572
+ required_fields = ["username", "password"] if credential_type in ["USERNAME_PASSWORD", "SSH_KEY"] else ["password"]
573
+ missing_fields = [field for field in required_fields if field not in kwargs]
574
+ if missing_fields:
575
+ raise ValueError(f"Missing required fields for '{credential_type}': {', '.join(missing_fields)}")
576
+
577
+ # Prepare the payload with the existing details and updates from kwargs
578
+ payload = {
579
+ **existing_credential.to_dict(),
580
+ **{snake_to_camel(key): value for key, value in kwargs.items()},
581
+ }
582
+
583
+ # Execute the update operation
584
+ response = self.rest.put(f"credential/{credential_id}", json=payload)
585
+ if not response.ok:
586
+ raise Exception(f"Failed to update credential {credential_id}: {response.text}")
587
+
588
+ # Fetch and return the updated credential details
589
+ return self.get_credential(credential_id)
590
+
591
+ def delete_credential(self, credential_id: str) -> int:
592
+ """
593
+ Delete the specified pra credential.
594
+
595
+ Args:
596
+ credential_id (str): The unique identifier for the credential to be deleted.
597
+
598
+ Returns:
599
+ :obj:`int`: The response code for the operation.
600
+
601
+ Examples:
602
+ >>> zpa.privileged_remote_access.delete_credential('99999')
603
+
604
+ """
605
+ return self.rest.delete(f"credential/{credential_id}").status_code
606
+
607
+ def list_approval(self, **kwargs) -> BoxList:
608
+ """
609
+ Returns a list of all privileged remote access approvals.
610
+
611
+ Keyword Args:
612
+ max_items (int):
613
+ The maximum number of items to request before stopping iteration.
614
+ max_pages (int):
615
+ The maximum number of pages to request before stopping iteration.
616
+ pagesize (int):
617
+ Specifies the page size. Default is 20, maximum is 500.
618
+ search (str, optional):
619
+ The search string used to match against features and fields.
620
+ search_field (str, optional):
621
+ The field to search against. Defaults to 'name'. Commonly used fields
622
+ include 'name' and 'email_ids'.
623
+
624
+ Returns:
625
+ :obj:`BoxList`: A list of all configured privileged remote access approvals.
626
+
627
+ Examples:
628
+ Search by default field 'name':
629
+
630
+ >>> for pra_approval in zpa.privileged_remote_access.list_approval(
631
+ ... search='Example_Name'):
632
+ ... pprint(pra_approval)
633
+
634
+ Search by 'email_ids':
635
+
636
+ >>> for approval in zpa.privileged_remote_access.list_approval(
637
+ ... search='jdoe@example.com', search_field='email_ids'):
638
+ ... pprint(approval)
639
+
640
+ Specify maximum items and use an explicit search field:
641
+
642
+ >>> approvals = zpa.privileged_remote_access.list_approval(
643
+ ... search='Example_Name', search_field='name', max_items=10)
644
+ ... for approval in approvals:
645
+ ... pprint(approval)
646
+
647
+ """
648
+ list, _ = self.rest.get_paginated_data(path="/approval", **kwargs, api_version="v1")
649
+ return list
650
+
651
+ def get_approval(self, approval_id: str) -> Box:
652
+ """
653
+ Returns information on the specified pra approval.
654
+
655
+ Args:
656
+ approval_id (str):
657
+ The unique identifier for the pra approval.
658
+
659
+ Returns:
660
+ :obj:`Box`: The resource record for the pra approval.
661
+
662
+ Examples:
663
+ >>> pprint(zpa.privileged_remote_access.get_approval('99999'))
664
+
665
+ """
666
+ return self.rest.get(f"approval/{approval_id}")
667
+
668
+ def add_approval(
669
+ self,
670
+ email_ids: list,
671
+ application_ids: list,
672
+ start_time: str,
673
+ end_time: str,
674
+ status: str,
675
+ working_hours: dict,
676
+ **kwargs,
677
+ ) -> Box:
678
+ """
679
+ Add a privileged remote access approval.
680
+
681
+ Args:
682
+ email_ids (list): The email addresses of the users that you are assigning the privileged approval to.
683
+ application_ids (list of str): A list of unique identifiers for the associated application segment ids.
684
+ start_time (str): The start timestamp in UNIX format for when the approval begins.
685
+ end_time (str): The end timestamp in UNIX format for when the approval ends.
686
+ status (str): The status of the privileged approval. Supported values are: INVALID, ACTIVE, FUTURE, EXPIRED.
687
+ working_hours (dict): Dictionary containing details of working hours.
688
+
689
+ Keyword Args:
690
+ Any additional optional parameters that can be included in the payload.
691
+
692
+ Returns:
693
+ Box: The resource record for the newly created approval.
694
+
695
+ Examples:
696
+ Create a PRA approval with the minimum required parameters and working hours:
697
+
698
+ >>> zpa.privileged_remote_access.add_approval(
699
+ ... email_ids=['jdoe@example.com'],
700
+ ... application_ids=['999999999'],
701
+ ... start_time='1712856502',
702
+ ... end_time='1714498102',
703
+ ... status='ACTIVE',
704
+ ... working_hours={
705
+ ... "start_time_cron": "0 0 16 ? * SUN,MON,TUE,WED,THU,FRI,SAT",
706
+ ... "end_time_cron": "0 0 0 ? * MON,TUE,WED,THU,FRI,SAT,SUN",
707
+ ... "start_time": "09:00",
708
+ ... "end_time": "17:00",
709
+ ... "days": ["SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT"],
710
+ ... "time_zone": "America/Vancouver"
711
+ ... }
712
+ ... )
713
+ """
714
+ start_epoch, end_epoch = validate_and_convert_times(start_time, end_time, working_hours["time_zone"])
715
+
716
+ payload = {
717
+ "emailIds": email_ids,
718
+ "applications": [{"id": application_id} for application_id in application_ids],
719
+ "startTime": start_epoch,
720
+ "endTime": end_epoch,
721
+ "status": status,
722
+ "workingHours": {
723
+ "startTimeCron": working_hours["start_time_cron"],
724
+ "endTimeCron": working_hours["end_time_cron"],
725
+ "startTime": working_hours["start_time"],
726
+ "endTime": working_hours["end_time"],
727
+ "days": working_hours["days"],
728
+ "timeZone": working_hours["time_zone"],
729
+ },
730
+ }
731
+ # Incorporate optional parameters
732
+ for key, value in kwargs.items():
733
+ payload[snake_to_camel(key)] = value
734
+
735
+ response = self.rest.post("approval", json=payload)
736
+ if isinstance(response, Response):
737
+ # this is only true when the creation failed (status code is not 2xx)
738
+ status_code = response.status_code
739
+ # Handle error response
740
+ raise Exception(f"API call failed with status {status_code}: {response.json()}")
741
+ return response
742
+
743
+ def update_approval(self, approval_id: str, **kwargs) -> Box:
744
+ """
745
+ Updates a specified approval based on provided keyword arguments.
746
+ ...
747
+ """
748
+ # Fetch existing approval details to get the current state
749
+ existing_approval = self.get_approval(approval_id)
750
+ if not existing_approval:
751
+ raise Exception(f"Failed to fetch approval {approval_id}")
752
+
753
+ # Pre-process and validate start_time and end_time if provided
754
+ if "start_time" in kwargs and "end_time" in kwargs:
755
+ start_time = kwargs["start_time"]
756
+ end_time = kwargs["end_time"]
757
+ # Assuming working_hours contains the time zone
758
+ time_zone = kwargs.get("working_hours", {}).get("time_zone", existing_approval.working_hours.time_zone)
759
+ start_epoch, end_epoch = validate_and_convert_times(start_time, end_time, time_zone)
760
+ kwargs["start_time"] = start_epoch
761
+ kwargs["end_time"] = end_epoch
762
+
763
+ # Construct payload dynamically based on existing details and updates from kwargs
764
+ payload = {
765
+ "emailIds": kwargs.get("email_ids", existing_approval.email_ids),
766
+ "applications": [
767
+ {"id": app_id}
768
+ for app_id in kwargs.get(
769
+ "application_ids",
770
+ [app["id"] for app in existing_approval.applications],
771
+ )
772
+ ],
773
+ "status": kwargs.get("status", existing_approval.status),
774
+ }
775
+
776
+ # Special handling for working_hours to preserve existing details if not fully specified in kwargs
777
+ working_hours = kwargs.get("working_hours", {})
778
+ existing_wh = existing_approval.working_hours
779
+ payload["workingHours"] = {
780
+ "startTimeCron": working_hours.get("start_time_cron", existing_wh.start_time_cron),
781
+ "endTimeCron": working_hours.get("end_time_cron", existing_wh.end_time_cron),
782
+ "startTime": working_hours.get("start_time", existing_wh.start_time),
783
+ "endTime": working_hours.get("end_time", existing_wh.end_time),
784
+ "days": working_hours.get("days", existing_wh.days),
785
+ "timeZone": working_hours.get("time_zone", existing_wh.time_zone),
786
+ }
787
+
788
+ # Add any additional provided parameters to payload
789
+ for key, value in kwargs.items():
790
+ if key not in ["email_ids", "application_ids", "working_hours"]:
791
+ payload[snake_to_camel(key)] = value
792
+
793
+ # Execute the update operation
794
+ response = self.rest.put(f"approval/{approval_id}", json=payload)
795
+ if response.status_code == 204:
796
+ return self.get_approval(approval_id)
797
+ else:
798
+ raise Exception(f"Failed to update approval {approval_id}: {response.text}")
799
+
800
+ def delete_approval(self, approval_id: str) -> int:
801
+ """
802
+ Delete the specified pra approval.
803
+
804
+ Args:
805
+ approval_id (str): The unique identifier for the approval to be deleted.
806
+
807
+ Returns:
808
+ :obj:`int`: The response code for the operation.
809
+
810
+ Examples:
811
+ >>> zpa.privileged_remote_access.delete_approval('99999')
812
+
813
+ """
814
+ return self.rest.delete(f"approval/{approval_id}").status_code
815
+
816
+ def expired_approval(self) -> int:
817
+ """
818
+ Deletes all expired privileged approvals.
819
+
820
+ Returns:
821
+ :obj:`int`: The response code for the operation.
822
+
823
+ Examples:
824
+ >>> zpa.privileged_remote_access.expired_approval('99999')
825
+
826
+ """
827
+ return self.rest.delete("approval/expired").status_code