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