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