zscaler-sdk-python 0.9.6__tar.gz → 0.10.0__tar.gz

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 (100) hide show
  1. {zscaler_sdk_python-0.9.6 → zscaler_sdk_python-0.10.0}/PKG-INFO +1 -1
  2. {zscaler_sdk_python-0.9.6 → zscaler_sdk_python-0.10.0}/pyproject.toml +1 -1
  3. {zscaler_sdk_python-0.9.6 → zscaler_sdk_python-0.10.0}/zscaler/__init__.py +1 -1
  4. {zscaler_sdk_python-0.9.6 → zscaler_sdk_python-0.10.0}/zscaler/zia/__init__.py +55 -9
  5. {zscaler_sdk_python-0.9.6 → zscaler_sdk_python-0.10.0}/zscaler/zia/labels.py +1 -1
  6. {zscaler_sdk_python-0.9.6 → zscaler_sdk_python-0.10.0}/zscaler/zia/locations.py +10 -21
  7. zscaler_sdk_python-0.10.0/zscaler/zia/pac_files.py +429 -0
  8. {zscaler_sdk_python-0.9.6 → zscaler_sdk_python-0.10.0}/zscaler/zia/security.py +17 -5
  9. {zscaler_sdk_python-0.9.6 → zscaler_sdk_python-0.10.0}/zscaler/zia/users.py +1 -1
  10. {zscaler_sdk_python-0.9.6 → zscaler_sdk_python-0.10.0}/zscaler/zpa/__init__.py +14 -14
  11. {zscaler_sdk_python-0.9.6 → zscaler_sdk_python-0.10.0}/zscaler/zpa/policies.py +24 -50
  12. {zscaler_sdk_python-0.9.6 → zscaler_sdk_python-0.10.0}/LICENSE.md +0 -0
  13. {zscaler_sdk_python-0.9.6 → zscaler_sdk_python-0.10.0}/README.md +0 -0
  14. {zscaler_sdk_python-0.9.6 → zscaler_sdk_python-0.10.0}/zscaler/cache/__init__.py +0 -0
  15. {zscaler_sdk_python-0.9.6 → zscaler_sdk_python-0.10.0}/zscaler/cache/cache.py +0 -0
  16. {zscaler_sdk_python-0.9.6 → zscaler_sdk_python-0.10.0}/zscaler/cache/no_op_cache.py +0 -0
  17. {zscaler_sdk_python-0.9.6 → zscaler_sdk_python-0.10.0}/zscaler/cache/zscaler_cache.py +0 -0
  18. {zscaler_sdk_python-0.9.6 → zscaler_sdk_python-0.10.0}/zscaler/constants.py +0 -0
  19. {zscaler_sdk_python-0.9.6 → zscaler_sdk_python-0.10.0}/zscaler/errors/__init__.py +0 -0
  20. {zscaler_sdk_python-0.9.6 → zscaler_sdk_python-0.10.0}/zscaler/errors/error.py +0 -0
  21. {zscaler_sdk_python-0.9.6 → zscaler_sdk_python-0.10.0}/zscaler/errors/http_error.py +0 -0
  22. {zscaler_sdk_python-0.9.6 → zscaler_sdk_python-0.10.0}/zscaler/errors/zscaler_api_error.py +0 -0
  23. {zscaler_sdk_python-0.9.6 → zscaler_sdk_python-0.10.0}/zscaler/exceptions/__init__.py +0 -0
  24. {zscaler_sdk_python-0.9.6 → zscaler_sdk_python-0.10.0}/zscaler/exceptions/exceptions.py +0 -0
  25. {zscaler_sdk_python-0.9.6 → zscaler_sdk_python-0.10.0}/zscaler/logger.py +0 -0
  26. {zscaler_sdk_python-0.9.6 → zscaler_sdk_python-0.10.0}/zscaler/ratelimiter/__init__.py +0 -0
  27. {zscaler_sdk_python-0.9.6 → zscaler_sdk_python-0.10.0}/zscaler/ratelimiter/ratelimiter.py +0 -0
  28. {zscaler_sdk_python-0.9.6 → zscaler_sdk_python-0.10.0}/zscaler/user_agent.py +0 -0
  29. {zscaler_sdk_python-0.9.6 → zscaler_sdk_python-0.10.0}/zscaler/utils.py +0 -0
  30. {zscaler_sdk_python-0.9.6 → zscaler_sdk_python-0.10.0}/zscaler/zcc/__init__.py +0 -0
  31. {zscaler_sdk_python-0.9.6 → zscaler_sdk_python-0.10.0}/zscaler/zcc/client.py +0 -0
  32. {zscaler_sdk_python-0.9.6 → zscaler_sdk_python-0.10.0}/zscaler/zcc/devices.py +0 -0
  33. {zscaler_sdk_python-0.9.6 → zscaler_sdk_python-0.10.0}/zscaler/zcc/secrets.py +0 -0
  34. {zscaler_sdk_python-0.9.6 → zscaler_sdk_python-0.10.0}/zscaler/zcon/__init__.py +0 -0
  35. {zscaler_sdk_python-0.9.6 → zscaler_sdk_python-0.10.0}/zscaler/zcon/activation.py +0 -0
  36. {zscaler_sdk_python-0.9.6 → zscaler_sdk_python-0.10.0}/zscaler/zcon/admin_and_role_management.py +0 -0
  37. {zscaler_sdk_python-0.9.6 → zscaler_sdk_python-0.10.0}/zscaler/zcon/client.py +0 -0
  38. {zscaler_sdk_python-0.9.6 → zscaler_sdk_python-0.10.0}/zscaler/zcon/ecgroups.py +0 -0
  39. {zscaler_sdk_python-0.9.6 → zscaler_sdk_python-0.10.0}/zscaler/zcon/locations.py +0 -0
  40. {zscaler_sdk_python-0.9.6 → zscaler_sdk_python-0.10.0}/zscaler/zcon/provisioning.py +0 -0
  41. {zscaler_sdk_python-0.9.6 → zscaler_sdk_python-0.10.0}/zscaler/zdx/__init__.py +0 -0
  42. {zscaler_sdk_python-0.9.6 → zscaler_sdk_python-0.10.0}/zscaler/zdx/admin.py +0 -0
  43. {zscaler_sdk_python-0.9.6 → zscaler_sdk_python-0.10.0}/zscaler/zdx/alerts.py +0 -0
  44. {zscaler_sdk_python-0.9.6 → zscaler_sdk_python-0.10.0}/zscaler/zdx/apps.py +0 -0
  45. {zscaler_sdk_python-0.9.6 → zscaler_sdk_python-0.10.0}/zscaler/zdx/devices.py +0 -0
  46. {zscaler_sdk_python-0.9.6 → zscaler_sdk_python-0.10.0}/zscaler/zdx/filters.py +0 -0
  47. {zscaler_sdk_python-0.9.6 → zscaler_sdk_python-0.10.0}/zscaler/zdx/inventory.py +0 -0
  48. {zscaler_sdk_python-0.9.6 → zscaler_sdk_python-0.10.0}/zscaler/zdx/troubleshooting.py +0 -0
  49. {zscaler_sdk_python-0.9.6 → zscaler_sdk_python-0.10.0}/zscaler/zdx/users.py +0 -0
  50. {zscaler_sdk_python-0.9.6 → zscaler_sdk_python-0.10.0}/zscaler/zdx/zdx_client.py +0 -0
  51. {zscaler_sdk_python-0.9.6 → zscaler_sdk_python-0.10.0}/zscaler/zia/activate.py +0 -0
  52. {zscaler_sdk_python-0.9.6 → zscaler_sdk_python-0.10.0}/zscaler/zia/admin_and_role_management.py +0 -0
  53. {zscaler_sdk_python-0.9.6 → zscaler_sdk_python-0.10.0}/zscaler/zia/apptotal.py +0 -0
  54. {zscaler_sdk_python-0.9.6 → zscaler_sdk_python-0.10.0}/zscaler/zia/audit_logs.py +0 -0
  55. {zscaler_sdk_python-0.9.6 → zscaler_sdk_python-0.10.0}/zscaler/zia/authentication_settings.py +0 -0
  56. {zscaler_sdk_python-0.9.6 → zscaler_sdk_python-0.10.0}/zscaler/zia/client.py +0 -0
  57. {zscaler_sdk_python-0.9.6 → zscaler_sdk_python-0.10.0}/zscaler/zia/cloud_apps.py +0 -0
  58. {zscaler_sdk_python-0.9.6 → zscaler_sdk_python-0.10.0}/zscaler/zia/cloudappcontrol.py +0 -0
  59. {zscaler_sdk_python-0.9.6 → zscaler_sdk_python-0.10.0}/zscaler/zia/device_management.py +0 -0
  60. {zscaler_sdk_python-0.9.6 → zscaler_sdk_python-0.10.0}/zscaler/zia/dlp.py +0 -0
  61. {zscaler_sdk_python-0.9.6 → zscaler_sdk_python-0.10.0}/zscaler/zia/errors.py +0 -0
  62. {zscaler_sdk_python-0.9.6 → zscaler_sdk_python-0.10.0}/zscaler/zia/firewall.py +0 -0
  63. {zscaler_sdk_python-0.9.6 → zscaler_sdk_python-0.10.0}/zscaler/zia/forwarding_control.py +0 -0
  64. {zscaler_sdk_python-0.9.6 → zscaler_sdk_python-0.10.0}/zscaler/zia/isolation_profile.py +0 -0
  65. {zscaler_sdk_python-0.9.6 → zscaler_sdk_python-0.10.0}/zscaler/zia/sandbox.py +0 -0
  66. {zscaler_sdk_python-0.9.6 → zscaler_sdk_python-0.10.0}/zscaler/zia/ssl_inspection.py +0 -0
  67. {zscaler_sdk_python-0.9.6 → zscaler_sdk_python-0.10.0}/zscaler/zia/traffic.py +0 -0
  68. {zscaler_sdk_python-0.9.6 → zscaler_sdk_python-0.10.0}/zscaler/zia/url_categories.py +0 -0
  69. {zscaler_sdk_python-0.9.6 → zscaler_sdk_python-0.10.0}/zscaler/zia/url_filtering.py +0 -0
  70. {zscaler_sdk_python-0.9.6 → zscaler_sdk_python-0.10.0}/zscaler/zia/web_dlp.py +0 -0
  71. {zscaler_sdk_python-0.9.6 → zscaler_sdk_python-0.10.0}/zscaler/zia/workload_groups.py +0 -0
  72. {zscaler_sdk_python-0.9.6 → zscaler_sdk_python-0.10.0}/zscaler/zia/zpa_gateway.py +0 -0
  73. {zscaler_sdk_python-0.9.6 → zscaler_sdk_python-0.10.0}/zscaler/zpa/README.md +0 -0
  74. {zscaler_sdk_python-0.9.6 → zscaler_sdk_python-0.10.0}/zscaler/zpa/app_segments.py +0 -0
  75. {zscaler_sdk_python-0.9.6 → zscaler_sdk_python-0.10.0}/zscaler/zpa/app_segments_inspection.py +0 -0
  76. {zscaler_sdk_python-0.9.6 → zscaler_sdk_python-0.10.0}/zscaler/zpa/app_segments_pra.py +0 -0
  77. {zscaler_sdk_python-0.9.6 → zscaler_sdk_python-0.10.0}/zscaler/zpa/authdomains.py +0 -0
  78. {zscaler_sdk_python-0.9.6 → zscaler_sdk_python-0.10.0}/zscaler/zpa/certificates.py +0 -0
  79. {zscaler_sdk_python-0.9.6 → zscaler_sdk_python-0.10.0}/zscaler/zpa/client.py +0 -0
  80. {zscaler_sdk_python-0.9.6 → zscaler_sdk_python-0.10.0}/zscaler/zpa/cloud_connector_groups.py +0 -0
  81. {zscaler_sdk_python-0.9.6 → zscaler_sdk_python-0.10.0}/zscaler/zpa/connectors.py +0 -0
  82. {zscaler_sdk_python-0.9.6 → zscaler_sdk_python-0.10.0}/zscaler/zpa/emergency_access.py +0 -0
  83. {zscaler_sdk_python-0.9.6 → zscaler_sdk_python-0.10.0}/zscaler/zpa/errors.py +0 -0
  84. {zscaler_sdk_python-0.9.6 → zscaler_sdk_python-0.10.0}/zscaler/zpa/idp.py +0 -0
  85. {zscaler_sdk_python-0.9.6 → zscaler_sdk_python-0.10.0}/zscaler/zpa/inspection.py +0 -0
  86. {zscaler_sdk_python-0.9.6 → zscaler_sdk_python-0.10.0}/zscaler/zpa/isolation.py +0 -0
  87. {zscaler_sdk_python-0.9.6 → zscaler_sdk_python-0.10.0}/zscaler/zpa/lss.py +0 -0
  88. {zscaler_sdk_python-0.9.6 → zscaler_sdk_python-0.10.0}/zscaler/zpa/machine_groups.py +0 -0
  89. {zscaler_sdk_python-0.9.6 → zscaler_sdk_python-0.10.0}/zscaler/zpa/microtenants.py +0 -0
  90. {zscaler_sdk_python-0.9.6 → zscaler_sdk_python-0.10.0}/zscaler/zpa/posture_profiles.py +0 -0
  91. {zscaler_sdk_python-0.9.6 → zscaler_sdk_python-0.10.0}/zscaler/zpa/privileged_remote_access.py +0 -0
  92. {zscaler_sdk_python-0.9.6 → zscaler_sdk_python-0.10.0}/zscaler/zpa/provisioning.py +0 -0
  93. {zscaler_sdk_python-0.9.6 → zscaler_sdk_python-0.10.0}/zscaler/zpa/saml_attributes.py +0 -0
  94. {zscaler_sdk_python-0.9.6 → zscaler_sdk_python-0.10.0}/zscaler/zpa/scim_attributes.py +0 -0
  95. {zscaler_sdk_python-0.9.6 → zscaler_sdk_python-0.10.0}/zscaler/zpa/scim_groups.py +0 -0
  96. {zscaler_sdk_python-0.9.6 → zscaler_sdk_python-0.10.0}/zscaler/zpa/segment_groups.py +0 -0
  97. {zscaler_sdk_python-0.9.6 → zscaler_sdk_python-0.10.0}/zscaler/zpa/server_groups.py +0 -0
  98. {zscaler_sdk_python-0.9.6 → zscaler_sdk_python-0.10.0}/zscaler/zpa/servers.py +0 -0
  99. {zscaler_sdk_python-0.9.6 → zscaler_sdk_python-0.10.0}/zscaler/zpa/service_edges.py +0 -0
  100. {zscaler_sdk_python-0.9.6 → zscaler_sdk_python-0.10.0}/zscaler/zpa/trusted_networks.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: zscaler-sdk-python
3
- Version: 0.9.6
3
+ Version: 0.10.0
4
4
  Summary: Official Python SDK for the Zscaler Products (Beta)
5
5
  Home-page: https://github.com/zscaler/zscaler-sdk-python
6
6
  License: MIT
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "zscaler-sdk-python"
3
- version = "0.9.6"
3
+ version = "0.10.0"
4
4
  description = "Official Python SDK for the Zscaler Products (Beta)"
5
5
  authors = ["Zscaler, Inc. <devrel@zscaler.com>"]
6
6
  license = "MIT"
@@ -29,7 +29,7 @@ __license__ = "MIT"
29
29
  __contributors__ = [
30
30
  "William Guilherme",
31
31
  ]
32
- __version__ = "0.9.6"
32
+ __version__ = "0.10.0"
33
33
 
34
34
  from zscaler.zdx import ZDXClientHelper # noqa
35
35
  from zscaler.zia import ZIAClientHelper # noqa
@@ -39,6 +39,7 @@ from zscaler.zia.forwarding_control import ForwardingControlAPI
39
39
  from zscaler.zia.cloudappcontrol import CloudAppControlAPI
40
40
  from zscaler.zia.isolation_profile import IsolationProfileAPI
41
41
  from zscaler.zia.labels import RuleLabelsAPI
42
+ from zscaler.zia.pac_files import PacFilesAPI
42
43
  from zscaler.zia.locations import LocationsAPI
43
44
  from zscaler.zia.sandbox import CloudSandboxAPI
44
45
  from zscaler.zia.security import SecurityPolicyAPI
@@ -406,6 +407,14 @@ class ZIAClientHelper(ZIAClient):
406
407
  page=None,
407
408
  pagesize=None,
408
409
  search=None,
410
+ max_page_size=1000, # Default to 1000, can be adjusted based on endpoint constraints
411
+ max_items=None, # Maximum number of items to retrieve across pages
412
+ max_pages=None, # Maximum number of pages to retrieve
413
+ type=None, # Specify type of VPN credentials (CN, IP, UFQDN, XAUTH)
414
+ include_only_without_location=None, # Include only VPN credentials not associated with any location
415
+ location_id=None, # VPN credentials for a specific location ID
416
+ managed_by=None, # VPN credentials managed by a given partner
417
+ prefix=None, # VPN credentials managed by a given partner
409
418
  ):
410
419
  """
411
420
  Fetches paginated data from the API based on specified parameters and handles pagination.
@@ -416,6 +425,13 @@ class ZIAClientHelper(ZIAClient):
416
425
  page (int): Specific page number to fetch. Defaults to 1 if not provided.
417
426
  pagesize (int): Number of items per page, default is 100, with a maximum of 1000.
418
427
  search (str): Search query to filter the results.
428
+ max_items (int): Maximum number of items to retrieve.
429
+ max_pages (int): Maximum number of pages to fetch.
430
+ type (str, optional): Type of VPN credentials (e.g., CN, IP, UFQDN, XAUTH).
431
+ include_only_without_location (bool, optional): Filter to include only VPN credentials not associated with a location.
432
+ location_id (int, optional): Retrieve VPN credentials for the specified location ID.
433
+ managed_by (int, optional): Retrieve VPN credentials managed by the specified partner.
434
+ prefix (int, optional): Retrieve VPN credentials managed by the specified partner.
419
435
 
420
436
  Returns:
421
437
  tuple: A tuple containing:
@@ -428,15 +444,29 @@ class ZIAClientHelper(ZIAClient):
428
444
  "UNEXPECTED_STATUS": "Unexpected status code {status_code} received for page {page}.",
429
445
  "EMPTY_RESULTS": "No results found for page {page}.",
430
446
  }
431
-
432
- params = {}
433
- params["page"] = page if page is not None else 1 # Default to page 1
434
- params["pagesize"] = min(pagesize if pagesize is not None else 100, 1000) # Default pagesize is 100, max 1000
435
447
 
448
+ # Initialize pagination parameters
449
+ params = {
450
+ "page": page if page is not None else 1, # Start at page 1 if not specified
451
+ "pagesize": min(pagesize if pagesize is not None else 100, max_page_size), # Apply max_page_size limit
452
+ }
453
+
454
+ # Add optional filters to the params if provided
436
455
  if search:
437
456
  params["search"] = search
457
+ if type:
458
+ params["type"] = type
459
+ if include_only_without_location is not None:
460
+ params["includeOnlyWithoutLocation"] = include_only_without_location
461
+ if location_id:
462
+ params["locationId"] = location_id
463
+ if managed_by:
464
+ params["managedBy"] = managed_by
465
+ if prefix:
466
+ params["prefix"] = prefix
438
467
 
439
468
  ret_data = []
469
+ total_collected = 0
440
470
 
441
471
  try:
442
472
  while True:
@@ -464,18 +494,27 @@ class ZIAClientHelper(ZIAClient):
464
494
  return BoxList([]), error_msg
465
495
 
466
496
  data = convert_keys_to_snake(response_data)
467
-
497
+
468
498
  # If searching for a specific item, stop if we find a match
469
499
  if search:
470
500
  for item in data:
471
501
  if item.get("name") == search:
472
502
  ret_data.append(item)
473
503
  return BoxList(ret_data), None
474
-
475
- # If no search, collect all data from the current page
504
+
505
+ # Limit data collection based on max_items
506
+ if max_items is not None:
507
+ data = data[: max_items - total_collected] # Limit items on the current page
476
508
  ret_data.extend(data)
509
+ total_collected += len(data)
510
+
511
+ # Check if we've reached max_items or max_pages limits
512
+ if (max_items is not None and total_collected >= max_items) or (
513
+ max_pages is not None and params["page"] >= max_pages
514
+ ):
515
+ break
477
516
 
478
- # Stop if we've processed all available pages
517
+ # Stop if we've processed all available pages (i.e., less than requested page size)
479
518
  if len(data) < params["pagesize"]:
480
519
  break
481
520
 
@@ -492,7 +531,6 @@ class ZIAClientHelper(ZIAClient):
492
531
 
493
532
  return BoxList(ret_data), None
494
533
 
495
-
496
534
  @property
497
535
  def admin_and_role_management(self):
498
536
  """
@@ -677,3 +715,11 @@ class ZIAClientHelper(ZIAClient):
677
715
 
678
716
  """
679
717
  return WorkloadGroupsAPI(self)
718
+
719
+ @property
720
+ def pac_files(self):
721
+ """
722
+ The interface object for the :ref:`ZIA Pac Files interface <zia-pac_files>`.
723
+
724
+ """
725
+ return PacFilesAPI(self)
@@ -18,7 +18,7 @@
18
18
  from box import Box, BoxList
19
19
  from requests import Response
20
20
 
21
- from zscaler.utils import Iterator, convert_keys, snake_to_camel
21
+ from zscaler.utils import convert_keys, snake_to_camel
22
22
  from zscaler.zia import ZIAClient
23
23
 
24
24
 
@@ -18,7 +18,7 @@
18
18
  from box import Box, BoxList
19
19
  from requests import Response
20
20
 
21
- from zscaler.utils import Iterator, snake_to_camel
21
+ from zscaler.utils import snake_to_camel
22
22
  from zscaler.zia import ZIAClient
23
23
 
24
24
 
@@ -188,7 +188,7 @@ class LocationsAPI:
188
188
  Additional notes or information regarding the location or sub-location. The description cannot
189
189
  exceed 1024 characters.
190
190
  static_location_groups (list): IDs for static location groups.
191
-
191
+
192
192
  Returns:
193
193
  :obj:`Box`: The newly created location resource record
194
194
 
@@ -258,14 +258,8 @@ class LocationsAPI:
258
258
  ... pprint(sub_location)
259
259
 
260
260
  """
261
- return BoxList(
262
- Iterator(
263
- self.rest,
264
- f"locations/{location_id}/sublocations",
265
- max_pages=1,
266
- **kwargs,
267
- )
268
- )
261
+ list, _ = self.rest.get_paginated_data(path=f"/locations/{location_id}/sublocations", **kwargs)
262
+ return list
269
263
 
270
264
  def list_locations_lite(self, **kwargs) -> BoxList:
271
265
  """
@@ -305,7 +299,8 @@ class LocationsAPI:
305
299
  ... print(location)
306
300
 
307
301
  """
308
- return BoxList(Iterator(self.rest, "locations/lite", **kwargs))
302
+ list, _ = self.rest.get_paginated_data(path="/locations/lite", **kwargs)
303
+ return list
309
304
 
310
305
  def update_location(self, location_id: str, **kwargs) -> Box:
311
306
  """
@@ -504,14 +499,8 @@ class LocationsAPI:
504
499
  Get a list of all configured location groups:
505
500
  >>> location = zia.locations.list_location_groups()
506
501
  """
507
- return BoxList(
508
- Iterator(
509
- self.rest,
510
- f"locations/groups",
511
- max_pages=1,
512
- **kwargs,
513
- )
514
- )
502
+ list, _ = self.rest.get_paginated_data(path="/locations/groups", **kwargs)
503
+ return list
515
504
 
516
505
  def get_location_group_by_id(self, group_id: int) -> Box:
517
506
  """
@@ -665,5 +654,5 @@ class LocationsAPI:
665
654
  returned. Ensure you narrow your search result as much as possible to avoid this.
666
655
 
667
656
  """
668
- return BoxList(Iterator(self.rest, "region/search", **kwargs))
669
-
657
+ list, _ = self.rest.get_paginated_data(path="/region/search", **kwargs)
658
+ return list
@@ -0,0 +1,429 @@
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 box import Box, BoxList
19
+ from requests import Response
20
+
21
+ from zscaler.utils import snake_to_camel
22
+ from zscaler.zia import ZIAClient
23
+
24
+
25
+ class PacFilesAPI:
26
+ def __init__(self, client: ZIAClient):
27
+ self.rest = client
28
+
29
+ def list_pac_files(self, **kwargs) -> BoxList:
30
+ """
31
+ Returns the list of ZIA Pac Files.
32
+
33
+ Keyword Args:
34
+ **max_items (int, optional):
35
+ The maximum number of items to request before stopping iteration.
36
+ **max_pages (int, optional):
37
+ The maximum number of pages to request before stopping iteration.
38
+ **page_size (int, optional):
39
+ Specifies the page size. The default size is 100, but the maximum size is 1000.
40
+
41
+ Returns:
42
+ :obj:`BoxList`: The list of Rule Labels configured in ZIA.
43
+
44
+ Examples:
45
+ List Rule Labels using default settings:
46
+
47
+ >>> for label in zia.labels.list_labels():
48
+ ... print(label)
49
+
50
+ List labels, limiting to a maximum of 10 items:
51
+
52
+ >>> for label in zia.labels.list_labels(max_items=10):
53
+ ... print(label)
54
+
55
+ List labels, returning 200 items per page for a maximum of 2 pages:
56
+
57
+ >>> for label in zia.labels.list_labels(page_size=200, max_pages=2):
58
+ ... print(label)
59
+
60
+ """
61
+ list, _ = self.rest.get_paginated_data(path="/pacFiles", **kwargs)
62
+ return list
63
+
64
+ def get_pac_file(self, pac_id: str, filter: str = None) -> Box:
65
+ """
66
+ Returns the PAC file details for a given PAC ID.
67
+
68
+ Args:
69
+ pac_id (str): The unique identifier for the PAC file.
70
+ filter (str, optional): Excludes specific information about the PAC file from
71
+ the response such as the PAC file content.
72
+ Accepts only the value 'pac_content'.
73
+
74
+ Returns:
75
+ :obj:`Box`: The PAC file resource record.
76
+
77
+ Example:
78
+ >>> pac_file = zia.get_pac_file('12345', filter='pac_content')
79
+ """
80
+ # Construct URL with optional filter
81
+ url = f"/pacFiles/{pac_id}/version"
82
+ if filter == "pac_content":
83
+ url += f"?filter={filter}"
84
+
85
+ response = self.rest.get(url)
86
+ if isinstance(response, Response):
87
+ if response.status_code != 200:
88
+ return None
89
+ return response
90
+
91
+ def get_pac_file_version(self, pac_id: str, pac_version: str, filter: str = None) -> Box:
92
+ """
93
+ Returns the PAC file version details for a given PAC ID and version.
94
+
95
+ Args:
96
+ pac_id (str): The unique identifier for the PAC file.
97
+ pac_version (str): The specific version of the PAC file.
98
+ filter (str, optional): Excludes specific information about the PAC file from
99
+ the response such as the PAC file content.
100
+ Accepts only the value 'pac_content'.
101
+
102
+ Returns:
103
+ :obj:`Box`: The PAC file version resource record.
104
+
105
+ Example:
106
+ >>> pac_file_version = zia.get_pac_file_version('12345', '1', filter='pac_content')
107
+ """
108
+ # Construct URL with optional filter
109
+ url = f"/pacFiles/{pac_id}/version/{pac_version}"
110
+ if filter == "pac_content":
111
+ url += f"?filter={filter}"
112
+
113
+ response = self.rest.get(url)
114
+ if isinstance(response, Response):
115
+ if response.status_code != 200:
116
+ return None
117
+ return response
118
+
119
+ def validate_pac_file(self, pac_file_content: str) -> Box:
120
+ """
121
+ Sends the PAC file content for validation and returns the validation result.
122
+
123
+ Args:
124
+ pac_file_content (str): The PAC file content to be validated.
125
+
126
+ Returns:
127
+ Box: The API response containing the validation result.
128
+
129
+ Raises:
130
+ Exception: If the API call fails with a non-2xx status code.
131
+
132
+ Example:
133
+ To validate PAC file content:
134
+
135
+ .. code-block:: python
136
+
137
+ pac_file_content = '''
138
+ function FindProxyForURL(url, host) {
139
+ return "PROXY gateway.example.com:80";
140
+ }
141
+ '''
142
+
143
+ response = client.validate_pac_file(pac_file_content=pac_file_content)
144
+ if response["success"]:
145
+ print("PAC file is valid.")
146
+ else:
147
+ print("PAC file validation failed.")
148
+ """
149
+ # Send only the PAC content as the raw body
150
+ response = self.rest.post("pacFiles/validate", data=pac_file_content)
151
+
152
+ # Check if the response is successful and already in Box format
153
+ if isinstance(response, Box):
154
+ return response
155
+ elif isinstance(response, Response):
156
+ if response.status_code != 200:
157
+ raise Exception(f"API call failed with status {response.status_code}: {response.json()}")
158
+ # Convert to Box if needed
159
+ return Box(response.json())
160
+
161
+ def add_pac_file(
162
+ self,
163
+ name: str,
164
+ description: str,
165
+ domain: str,
166
+ pac_commit_message: str,
167
+ pac_verification_status: str,
168
+ pac_version_status: str,
169
+ pac_content: str,
170
+ **kwargs,
171
+ ) -> Box:
172
+ """
173
+ Adds a new custom PAC file after validating the PAC content.
174
+
175
+ Args:
176
+ name (str): The name of the new PAC file.
177
+ description (str): The description of the new PAC file.
178
+ domain (str): The domain of your organization to which the PAC file applies.
179
+ pac_commit_message (str): The commit message entered while saving the PAC file version as indicated by the pacVersion field.
180
+ pac_verification_status (str): Indicates the verification status of the PAC file and if any errors are identified in the syntax.
181
+ Supported Values: `VERIFY_NOERR`, `VERIFY_ERR`, `NOVERIFY`
182
+ pac_version_status (str): Version status of the new PAC file.
183
+ Supported Values: `DEPLOYED`, `STAGE`, `LKG`
184
+ pac_content (str): The actual PAC file content to be validated and cloned.
185
+
186
+ Keyword Args:
187
+ Additional optional parameters as key-value pairs.
188
+
189
+ Returns:
190
+ Box: The newly added PAC file resource record.
191
+
192
+ Example:
193
+ >>> pac_file = zia.add_pac_file(
194
+ name="Test_Pac_File_01",
195
+ description="Test_Pac_File_01",
196
+ domain="bd-hashicorp.com",
197
+ pacCommitMessage="Test_Pac_File_01",
198
+ pacVerificationStatus="VERIFY_NOERR",
199
+ pacVersionStatus="DEPLOYED",
200
+ pacContent="function FindProxyForURL(url, host) { return 'PROXY gateway.example.com:80'; }"
201
+ )
202
+ """
203
+ validation_result = self.validate_pac_file(pac_content)
204
+ if not validation_result.success:
205
+ raise Exception("PAC content validation failed: {}".format(validation_result))
206
+
207
+ payload = {
208
+ "name": name,
209
+ "description": description,
210
+ "domain": domain,
211
+ "pacCommitMessage": pac_commit_message,
212
+ "pacVerificationStatus": pac_verification_status,
213
+ "pacVersionStatus": pac_version_status,
214
+ "pacContent": pac_content,
215
+ }
216
+
217
+ for key, value in kwargs.items():
218
+ payload[snake_to_camel(key)] = value
219
+
220
+ response = self.rest.post("pacFiles", json=payload)
221
+ if isinstance(response, Response):
222
+ if response.status_code != 200:
223
+ raise Exception(f"API call failed with status {response.status_code}: {response.json()}")
224
+ return Box(response.json())
225
+ return response
226
+
227
+ def clone_pac_file(
228
+ self,
229
+ pac_id: str,
230
+ pac_version: str,
231
+ name: str,
232
+ description: str,
233
+ domain: str,
234
+ pac_commit_message: str,
235
+ pac_verification_status: str,
236
+ pac_version_status: str,
237
+ pac_content: str,
238
+ delete_version: int = None,
239
+ **kwargs,
240
+ ) -> Box:
241
+ """
242
+ Clones an existing PAC file by creating a new PAC file based on the specified PAC ID and version.
243
+
244
+ Args:
245
+ pac_id (str): The unique identifier of the PAC file to be cloned.
246
+ pac_version (str): The specific version of the PAC file to be cloned.
247
+ name (str): The name of the new PAC file.
248
+ domain (str): The domain associated with the new PAC file.
249
+ pac_commit_message (str): Commit message for the new PAC file.
250
+ pac_verification_status (str): Verification status of the new PAC file.
251
+ Supported Values: `VERIFY_NOERR`, `VERIFY_ERR`, `NOVERIFY`
252
+ pac_version_status (str): Version status of the new PAC file.
253
+ Supported Values: `DEPLOYED`, `STAGE`, `LKG`
254
+ pac_content (str): The actual PAC file content to be validated and cloned.
255
+ delete_version (int, optional): Specifies the PAC file version to replace if the version limit of 10 is reached.
256
+
257
+ Keyword Args:
258
+ Additional optional parameters as key-value pairs.
259
+
260
+ Returns:
261
+ Box: The newly cloned PAC file resource record.
262
+
263
+ Example:
264
+ >>> pac_file = zia.clone_pac_file(
265
+ pac_id="12345",
266
+ pac_version="1",
267
+ name="Cloned_Pac_File_01",
268
+ description="Cloned_Pac_File_01",
269
+ domain="bd-hashicorp.com",
270
+ pac_commit_message="Clone of Test_Pac_File_01",
271
+ pac_verification_status="VERIFY_NOERR",
272
+ pac_version_status="DEPLOYED",
273
+ pac_content="function FindProxyForURL(url, host) { return 'PROXY gateway.example.com:80'; }",
274
+ delete_version=5
275
+ )
276
+ """
277
+ # Step 1: Validate the PAC content
278
+ validation_result = self.validate_pac_file(pac_content)
279
+ if not validation_result.success:
280
+ raise Exception("PAC content validation failed: {}".format(validation_result))
281
+
282
+ # Step 2: Check the number of PAC file versions
283
+ pac_file_details = self.get_pac_file(pac_id)
284
+ if isinstance(pac_file_details, Box):
285
+ pac_file_details = pac_file_details.to_list() # Convert Box to a list of dictionaries if needed
286
+
287
+ # Extract pac versions from details
288
+ pac_versions = [entry.get("pacVersion") for entry in pac_file_details] if pac_file_details else []
289
+ total_versions = len(pac_versions)
290
+
291
+ # Step 3: Decide on including delete_version in the URL
292
+ if total_versions >= 10:
293
+ if delete_version is None:
294
+ # If delete_version was not provided, default to the oldest version (smallest number)
295
+ delete_version = min(pac_versions)
296
+ url = f"/pacFiles/{pac_id}/version/{pac_version}?deleteVersion={delete_version}"
297
+ else:
298
+ url = f"/pacFiles/{pac_id}/version/{pac_version}"
299
+
300
+ # Step 4: Construct the payload with mandatory fields
301
+ payload = {
302
+ "name": name,
303
+ "description": description,
304
+ "domain": domain,
305
+ "pacCommitMessage": pac_commit_message,
306
+ "pacVerificationStatus": pac_verification_status,
307
+ "pacVersionStatus": pac_version_status,
308
+ "pacContent": pac_content,
309
+ }
310
+
311
+ # Add any additional optional parameters
312
+ for key, value in kwargs.items():
313
+ payload[snake_to_camel(key)] = value
314
+
315
+ # Step 5: Make the request to clone the PAC file
316
+ response = self.rest.post(url, json=payload)
317
+ if isinstance(response, Response):
318
+ if response.status_code != 200:
319
+ raise Exception(f"API call failed with status {response.status_code}: {response.json()}")
320
+ return Box(response.json())
321
+ return response
322
+
323
+ def update_pac_file(
324
+ self,
325
+ pac_id: str,
326
+ pac_version: str,
327
+ pac_version_action: str,
328
+ name: str,
329
+ description: str,
330
+ domain: str,
331
+ pac_commit_message: str,
332
+ pac_verification_status: str,
333
+ # pac_version_status: str,
334
+ pac_content: str,
335
+ new_lkg_ver: int = None,
336
+ **kwargs,
337
+ ) -> Box:
338
+ """
339
+ Performs the specified action on the PAC file version and updates the file status.
340
+ Supported actions include deploying, staging, unstaging, and marking or unmarking
341
+ the file as last known good version.
342
+
343
+ Args:
344
+ pac_id (str): The unique identifier of the PAC file to be updated.
345
+ pac_version (str): The specific version of the PAC file to be updated.
346
+ pac_version_action (str): Action to perform on the PAC version.
347
+ Supported Values: `DEPLOY`, `STAGE`, `LKG`, `UNSTAGE`, `REMOVE_LKG`
348
+ name (str): The name of the PAC file.
349
+ description (str): Description of the PAC file.
350
+ domain (str): The domain associated with the PAC file.
351
+ pac_commit_message (str): Commit message for the PAC file.
352
+ pac_verification_status (str): Verification status of the PAC file.
353
+ Supported Values: `VERIFY_NOERR`, `VERIFY_ERR`, `NOVERIFY`
354
+ pac_version_status (str): Version status of the PAC file.
355
+ Supported Values: `DEPLOYED`, `STAGE`, `LKG`
356
+ pac_content (str): The actual PAC file content to be updated.
357
+ new_lkg_ver (int, optional): Specifies a different version to be marked as the last known good version
358
+ if the action is removing the current last known good version.
359
+
360
+ Keyword Args:
361
+ Additional optional parameters as key-value pairs.
362
+
363
+ Returns:
364
+ Box: The updated PAC file resource record.
365
+
366
+ Example:
367
+ >>> pac_file = zia.update_pac_file(
368
+ pac_id="12345",
369
+ pac_version="1",
370
+ pac_version_action="DEPLOY",
371
+ name="Update_Pac_File_01",
372
+ description="Update_Pac_File_01",
373
+ domain="bd-hashicorp.com",
374
+ pac_commit_message="Update_Pac_File_01",
375
+ pac_verification_status="VERIFY_NOERR",
376
+ pac_version_status="DEPLOYED",
377
+ pac_content="function FindProxyForURL(url, host) { return 'PROXY gateway.example.com:80'; }",
378
+ new_lkg_ver=5
379
+ )
380
+ """
381
+ # Step 1: Validate the PAC content
382
+ validation_result = self.validate_pac_file(pac_content)
383
+ if not validation_result.success:
384
+ raise Exception("PAC content validation failed: {}".format(validation_result))
385
+
386
+ # Step 2: Construct the URL with mandatory parameters and optional newLKGVer
387
+ url = f"/pacFiles/{pac_id}/version/{pac_version}/action/{pac_version_action}"
388
+ if new_lkg_ver is not None:
389
+ url += f"?newLKGVer={new_lkg_ver}"
390
+
391
+ # Step 3: Construct the payload with required fields
392
+ payload = {
393
+ "name": name,
394
+ "description": description,
395
+ "domain": domain,
396
+ "pacCommitMessage": pac_commit_message,
397
+ "pacVerificationStatus": pac_verification_status,
398
+ # "pacVersionStatus": pac_version_status,
399
+ "pacContent": pac_content,
400
+ }
401
+
402
+ # Add any additional optional parameters
403
+ for key, value in kwargs.items():
404
+ payload[snake_to_camel(key)] = value
405
+
406
+ # Step 4: Make the request to update the PAC file
407
+ response = self.rest.put(url, json=payload)
408
+ if isinstance(response, Response):
409
+ if response.status_code != 200:
410
+ raise Exception(f"API call failed with status {response.status_code}: {response.json()}")
411
+ return Box(response.json())
412
+ return response
413
+
414
+ def delete_pac_file(self, pac_id):
415
+ """
416
+ Deletes the specified Pac File.
417
+
418
+ Args:
419
+ pac_id (str): The unique identifier of the Pac File that will be deleted.
420
+
421
+ Returns:
422
+ :obj:`int`: The response code for the request.
423
+
424
+ Examples
425
+ >>> user = zia.pac_files.delete_pac_file('99999')
426
+
427
+ """
428
+ return self.rest.delete(f"pacFiles/{pac_id}").status_code
429
+
@@ -163,11 +163,23 @@ class SecurityPolicyAPI:
163
163
 
164
164
  payload = {"blacklistUrls": url_list}
165
165
 
166
- resp = self.rest.post("security/advanced/blacklistUrls?action=ADD_TO_LIST", json=payload).status_code
167
-
168
- # Return the object if it was updated successfully
169
- if resp == 204:
170
- return self.get_blacklist()
166
+ try:
167
+ # Send the POST request to add URLs to the blacklist
168
+ response = self.rest.post("security/advanced/blacklistUrls?action=ADD_TO_LIST", json=payload)
169
+
170
+ # Check if the response includes an empty 'blacklistUrls', signaling no update
171
+ if "blacklistUrls" in response and not response["blacklistUrls"]:
172
+ raise Exception("Failed to add URLs to blacklist: The API response returned an empty 'blacklistUrls' list.")
173
+
174
+ # Verify the URLs were added by checking the current blacklist
175
+ updated_blacklist = self.get_blacklist()
176
+ if all(url in updated_blacklist for url in url_list):
177
+ return updated_blacklist
178
+ else:
179
+ raise Exception("Failed to add URLs to blacklist: URLs were not present in the updated blacklist.")
180
+
181
+ except Exception as exc:
182
+ raise Exception(f"Failed to add URLs to blacklist: {exc}")
171
183
 
172
184
  def replace_blacklist(self, url_list: list) -> BoxList:
173
185
  """
@@ -17,7 +17,7 @@
17
17
 
18
18
  from box import Box, BoxList
19
19
 
20
- from zscaler.utils import Iterator, convert_keys, snake_to_camel
20
+ from zscaler.utils import convert_keys, snake_to_camel
21
21
  from zscaler.zia import ZIAClient
22
22
 
23
23
 
@@ -378,7 +378,7 @@ class ZPAClientHelper(ZPAClient):
378
378
  search_field="name",
379
379
  max_pages=None,
380
380
  max_items=None,
381
- all_entries=False, # Return all SCIM groups including the deleted ones if set to true
381
+ all_entries=False,
382
382
  sort_order=None,
383
383
  sort_by=None,
384
384
  sort_dir=None,
@@ -432,15 +432,10 @@ class ZPAClientHelper(ZPAClient):
432
432
  if params is None:
433
433
  params = {}
434
434
 
435
- if (page is not None or pagesize is not None) and (max_pages is not None or max_items is not None):
436
- raise ValueError(
437
- "Do not mix 'page' or 'pagesize' with 'max_pages' or 'max_items'. Choose either set of parameters."
438
- )
439
-
440
- params["page"] = page if page is not None else 1 # Default to page 1 if not specified
441
- params["pagesize"] = min(pagesize if pagesize is not None else 20, 500) # Apply maximum constraint and handle default
435
+ # Set initial pagination params
436
+ params["page"] = page or 1
437
+ params["pagesize"] = min(pagesize, 500) if pagesize else 500
442
438
 
443
- # Check for microtenantId in function arguments first, then environment variable
444
439
  if microtenant_id:
445
440
  params["microtenantId"] = microtenant_id
446
441
  elif self.microtenant_id and "microtenantId" not in params:
@@ -472,7 +467,8 @@ class ZPAClientHelper(ZPAClient):
472
467
 
473
468
  try:
474
469
  while True:
475
- if max_pages is not None and (page is not None and page > max_pages):
470
+ # Stop if max_pages reached
471
+ if max_pages is not None and params["page"] > max_pages:
476
472
  break
477
473
 
478
474
  should_wait, delay = self.rate_limiter.wait("GET")
@@ -490,23 +486,27 @@ class ZPAClientHelper(ZPAClient):
490
486
 
491
487
  response_data = response.json()
492
488
  data = response_data.get("list", [])
493
- if not data and (params["page"] == 1):
489
+ if not data and params["page"] == 1:
494
490
  error_msg = ERROR_MESSAGES["EMPTY_RESULTS"]
495
491
  logger.warn(error_msg)
496
492
  return BoxList([]), error_msg
497
493
 
494
+ # Convert and extend the collected data
498
495
  data = convert_keys_to_snake(data)
499
496
  ret_data.extend(data[: max_items - total_collected] if max_items is not None else data)
500
497
  total_collected += len(data)
501
498
 
499
+ # Check if we’ve collected the max_items
502
500
  if max_items is not None and total_collected >= max_items:
503
501
  break
504
502
 
505
- next_page = response_data.get("nextPage")
506
- if not next_page or (max_pages is not None and params["page"] >= max_pages):
503
+ # Determine if there is a next page based on totalPages, converting totalPages to an integer if present
504
+ total_pages = int(response_data.get("totalPages", 0)) # Default to 0 if not provided
505
+ if not total_pages or params["page"] >= total_pages:
507
506
  break
508
507
 
509
- params["page"] = next_page if params["page"] is None else params["page"] + 1
508
+ # Move to the next page
509
+ params["page"] += 1
510
510
 
511
511
  finally:
512
512
  time.sleep(2) # Ensure a delay between requests regardless of outcome
@@ -56,7 +56,7 @@ class PolicySetsAPI:
56
56
  conditions (list): List of condition dicts or tuples.
57
57
 
58
58
  Returns:
59
- :obj:`dict`: The conditions template.
59
+ :obj:`list`: The conditions template.
60
60
 
61
61
  """
62
62
  template = []
@@ -78,75 +78,47 @@ class PolicySetsAPI:
78
78
  "COUNTRY_CODE": [],
79
79
  }
80
80
 
81
- current_operator = "OR" # Default operator
82
-
81
+ operators_for_types = {} # Dictionary to store specific operators for each object type
82
+
83
83
  for condition in conditions:
84
- # Check if the first item is an operator, like "AND" or "OR"
84
+ # Check if the first item in a tuple is an operator, like "AND" or "OR"
85
85
  if isinstance(condition, tuple) and isinstance(condition[0], str) and condition[0].upper() in ["AND", "OR"]:
86
- current_operator = condition[0].upper() # Set the current operator
86
+ operator = condition[0].upper()
87
87
  condition = condition[1] # The second element is the actual condition
88
+ else:
89
+ operator = "OR" # Default operator if none specified
88
90
 
91
+ # Process each condition and categorize by object type and operator
89
92
  if isinstance(condition, tuple) and len(condition) == 3:
90
- # Handle each object type according to its pattern
91
93
  object_type = condition[0].upper()
92
94
  lhs = condition[1]
93
95
  rhs = condition[2]
96
+ operand = {"objectType": object_type, "lhs": lhs, "rhs": rhs}
97
+
98
+ # Track the operator for the current object type
99
+ operators_for_types[object_type] = operator
94
100
 
95
101
  if object_type in ["APP", "APP_GROUP"]:
96
- app_and_app_group_operands.append({"objectType": object_type, "lhs": "id", "rhs": rhs})
102
+ app_and_app_group_operands.append(operand)
97
103
  elif object_type in object_types_to_operands:
98
- if object_type == "CLIENT_TYPE":
99
- if rhs in {
100
- "zpn_client_type_exporter",
101
- "zpn_client_type_machine_tunnel",
102
- "zpn_client_type_ip_anchoring",
103
- "zpn_client_type_edge_connector",
104
- "zpn_client_type_zapp",
105
- "zpn_client_type_slogger",
106
- }:
107
- object_types_to_operands[object_type].append({"objectType": object_type, "lhs": "id", "rhs": rhs})
108
- elif object_type in [
109
- "PLATFORM",
110
- "POSTURE",
111
- "TRUSTED_NETWORK",
112
- "SAML",
113
- "SCIM",
114
- "SCIM_GROUP",
115
- "COUNTRY_CODE",
116
- ]:
117
- object_types_to_operands[object_type].append({"objectType": object_type, "lhs": lhs, "rhs": rhs})
118
- else:
119
- object_types_to_operands[object_type].append({"objectType": object_type, "lhs": "id", "rhs": rhs})
120
-
104
+ object_types_to_operands[object_type].append(operand)
105
+
121
106
  elif isinstance(condition, dict):
122
- # This part allows passing operator explicitly through conditions
123
107
  if "operator" in condition:
124
- current_operator = condition["operator"]
125
- continue # Move to the next condition after setting the operator
108
+ operators_for_types["default"] = condition["operator"]
109
+ continue # Skip to the next condition after setting the operator
126
110
 
127
- # Handle the dictionary logic based on the Go code schema
128
111
  condition_template = {}
129
- # Extracting keys from the condition dictionary
130
112
  for key in ["id", "negated", "operator"]:
131
113
  if key in condition:
132
114
  condition_template[key] = condition[key]
133
115
 
134
- # Handling the operands
135
116
  operands = condition.get("operands", [])
136
117
  condition_template["operands"] = []
137
118
 
138
119
  for operand in operands:
139
120
  operand_template = {}
140
-
141
- # Extracting keys from the operand dictionary
142
- for operand_key in [
143
- "id",
144
- "idp_id",
145
- "name",
146
- "lhs",
147
- "rhs",
148
- "objectType",
149
- ]:
121
+ for operand_key in ["id", "idp_id", "name", "lhs", "rhs", "objectType"]:
150
122
  if operand_key in operand:
151
123
  operand_template[operand_key] = operand[operand_key]
152
124
 
@@ -154,14 +126,16 @@ class PolicySetsAPI:
154
126
 
155
127
  template.append(condition_template)
156
128
 
157
- # Combine APP and APP_GROUP operands into one block
129
+ # Combine APP and APP_GROUP operands with their specific operator
158
130
  if app_and_app_group_operands:
159
- template.append({"operator": current_operator, "operands": app_and_app_group_operands})
131
+ app_group_operator = operators_for_types.get("APP", "OR")
132
+ template.append({"operator": app_group_operator, "operands": app_and_app_group_operands})
160
133
 
161
- # Combine other object types into their own blocks
134
+ # Combine other object types into their blocks with their respective operator
162
135
  for object_type, operands in object_types_to_operands.items():
163
136
  if operands:
164
- template.append({"operator": current_operator, "operands": operands})
137
+ operator = operators_for_types.get(object_type, "OR")
138
+ template.append({"operator": operator, "operands": operands})
165
139
 
166
140
  return template
167
141