zscaler-sdk-python 0.9.7__tar.gz → 0.10.1__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.7 → zscaler_sdk_python-0.10.1}/PKG-INFO +5 -5
  2. {zscaler_sdk_python-0.9.7 → zscaler_sdk_python-0.10.1}/pyproject.toml +6 -6
  3. {zscaler_sdk_python-0.9.7 → zscaler_sdk_python-0.10.1}/zscaler/__init__.py +1 -1
  4. {zscaler_sdk_python-0.9.7 → zscaler_sdk_python-0.10.1}/zscaler/zia/__init__.py +43 -9
  5. {zscaler_sdk_python-0.9.7 → zscaler_sdk_python-0.10.1}/zscaler/zia/labels.py +1 -1
  6. {zscaler_sdk_python-0.9.7 → zscaler_sdk_python-0.10.1}/zscaler/zia/locations.py +10 -21
  7. zscaler_sdk_python-0.10.1/zscaler/zia/pac_files.py +419 -0
  8. {zscaler_sdk_python-0.9.7 → zscaler_sdk_python-0.10.1}/zscaler/zia/security.py +2 -2
  9. {zscaler_sdk_python-0.9.7 → zscaler_sdk_python-0.10.1}/zscaler/zia/users.py +3 -3
  10. {zscaler_sdk_python-0.9.7 → zscaler_sdk_python-0.10.1}/zscaler/zpa/__init__.py +21 -21
  11. {zscaler_sdk_python-0.9.7 → zscaler_sdk_python-0.10.1}/zscaler/zpa/policies.py +8 -2
  12. {zscaler_sdk_python-0.9.7 → zscaler_sdk_python-0.10.1}/LICENSE.md +0 -0
  13. {zscaler_sdk_python-0.9.7 → zscaler_sdk_python-0.10.1}/README.md +0 -0
  14. {zscaler_sdk_python-0.9.7 → zscaler_sdk_python-0.10.1}/zscaler/cache/__init__.py +0 -0
  15. {zscaler_sdk_python-0.9.7 → zscaler_sdk_python-0.10.1}/zscaler/cache/cache.py +0 -0
  16. {zscaler_sdk_python-0.9.7 → zscaler_sdk_python-0.10.1}/zscaler/cache/no_op_cache.py +0 -0
  17. {zscaler_sdk_python-0.9.7 → zscaler_sdk_python-0.10.1}/zscaler/cache/zscaler_cache.py +0 -0
  18. {zscaler_sdk_python-0.9.7 → zscaler_sdk_python-0.10.1}/zscaler/constants.py +0 -0
  19. {zscaler_sdk_python-0.9.7 → zscaler_sdk_python-0.10.1}/zscaler/errors/__init__.py +0 -0
  20. {zscaler_sdk_python-0.9.7 → zscaler_sdk_python-0.10.1}/zscaler/errors/error.py +0 -0
  21. {zscaler_sdk_python-0.9.7 → zscaler_sdk_python-0.10.1}/zscaler/errors/http_error.py +0 -0
  22. {zscaler_sdk_python-0.9.7 → zscaler_sdk_python-0.10.1}/zscaler/errors/zscaler_api_error.py +0 -0
  23. {zscaler_sdk_python-0.9.7 → zscaler_sdk_python-0.10.1}/zscaler/exceptions/__init__.py +0 -0
  24. {zscaler_sdk_python-0.9.7 → zscaler_sdk_python-0.10.1}/zscaler/exceptions/exceptions.py +0 -0
  25. {zscaler_sdk_python-0.9.7 → zscaler_sdk_python-0.10.1}/zscaler/logger.py +0 -0
  26. {zscaler_sdk_python-0.9.7 → zscaler_sdk_python-0.10.1}/zscaler/ratelimiter/__init__.py +0 -0
  27. {zscaler_sdk_python-0.9.7 → zscaler_sdk_python-0.10.1}/zscaler/ratelimiter/ratelimiter.py +0 -0
  28. {zscaler_sdk_python-0.9.7 → zscaler_sdk_python-0.10.1}/zscaler/user_agent.py +0 -0
  29. {zscaler_sdk_python-0.9.7 → zscaler_sdk_python-0.10.1}/zscaler/utils.py +0 -0
  30. {zscaler_sdk_python-0.9.7 → zscaler_sdk_python-0.10.1}/zscaler/zcc/__init__.py +0 -0
  31. {zscaler_sdk_python-0.9.7 → zscaler_sdk_python-0.10.1}/zscaler/zcc/client.py +0 -0
  32. {zscaler_sdk_python-0.9.7 → zscaler_sdk_python-0.10.1}/zscaler/zcc/devices.py +0 -0
  33. {zscaler_sdk_python-0.9.7 → zscaler_sdk_python-0.10.1}/zscaler/zcc/secrets.py +0 -0
  34. {zscaler_sdk_python-0.9.7 → zscaler_sdk_python-0.10.1}/zscaler/zcon/__init__.py +0 -0
  35. {zscaler_sdk_python-0.9.7 → zscaler_sdk_python-0.10.1}/zscaler/zcon/activation.py +0 -0
  36. {zscaler_sdk_python-0.9.7 → zscaler_sdk_python-0.10.1}/zscaler/zcon/admin_and_role_management.py +0 -0
  37. {zscaler_sdk_python-0.9.7 → zscaler_sdk_python-0.10.1}/zscaler/zcon/client.py +0 -0
  38. {zscaler_sdk_python-0.9.7 → zscaler_sdk_python-0.10.1}/zscaler/zcon/ecgroups.py +0 -0
  39. {zscaler_sdk_python-0.9.7 → zscaler_sdk_python-0.10.1}/zscaler/zcon/locations.py +0 -0
  40. {zscaler_sdk_python-0.9.7 → zscaler_sdk_python-0.10.1}/zscaler/zcon/provisioning.py +0 -0
  41. {zscaler_sdk_python-0.9.7 → zscaler_sdk_python-0.10.1}/zscaler/zdx/__init__.py +0 -0
  42. {zscaler_sdk_python-0.9.7 → zscaler_sdk_python-0.10.1}/zscaler/zdx/admin.py +0 -0
  43. {zscaler_sdk_python-0.9.7 → zscaler_sdk_python-0.10.1}/zscaler/zdx/alerts.py +0 -0
  44. {zscaler_sdk_python-0.9.7 → zscaler_sdk_python-0.10.1}/zscaler/zdx/apps.py +0 -0
  45. {zscaler_sdk_python-0.9.7 → zscaler_sdk_python-0.10.1}/zscaler/zdx/devices.py +0 -0
  46. {zscaler_sdk_python-0.9.7 → zscaler_sdk_python-0.10.1}/zscaler/zdx/filters.py +0 -0
  47. {zscaler_sdk_python-0.9.7 → zscaler_sdk_python-0.10.1}/zscaler/zdx/inventory.py +0 -0
  48. {zscaler_sdk_python-0.9.7 → zscaler_sdk_python-0.10.1}/zscaler/zdx/troubleshooting.py +0 -0
  49. {zscaler_sdk_python-0.9.7 → zscaler_sdk_python-0.10.1}/zscaler/zdx/users.py +0 -0
  50. {zscaler_sdk_python-0.9.7 → zscaler_sdk_python-0.10.1}/zscaler/zdx/zdx_client.py +0 -0
  51. {zscaler_sdk_python-0.9.7 → zscaler_sdk_python-0.10.1}/zscaler/zia/activate.py +0 -0
  52. {zscaler_sdk_python-0.9.7 → zscaler_sdk_python-0.10.1}/zscaler/zia/admin_and_role_management.py +0 -0
  53. {zscaler_sdk_python-0.9.7 → zscaler_sdk_python-0.10.1}/zscaler/zia/apptotal.py +0 -0
  54. {zscaler_sdk_python-0.9.7 → zscaler_sdk_python-0.10.1}/zscaler/zia/audit_logs.py +0 -0
  55. {zscaler_sdk_python-0.9.7 → zscaler_sdk_python-0.10.1}/zscaler/zia/authentication_settings.py +0 -0
  56. {zscaler_sdk_python-0.9.7 → zscaler_sdk_python-0.10.1}/zscaler/zia/client.py +0 -0
  57. {zscaler_sdk_python-0.9.7 → zscaler_sdk_python-0.10.1}/zscaler/zia/cloud_apps.py +0 -0
  58. {zscaler_sdk_python-0.9.7 → zscaler_sdk_python-0.10.1}/zscaler/zia/cloudappcontrol.py +0 -0
  59. {zscaler_sdk_python-0.9.7 → zscaler_sdk_python-0.10.1}/zscaler/zia/device_management.py +0 -0
  60. {zscaler_sdk_python-0.9.7 → zscaler_sdk_python-0.10.1}/zscaler/zia/dlp.py +0 -0
  61. {zscaler_sdk_python-0.9.7 → zscaler_sdk_python-0.10.1}/zscaler/zia/errors.py +0 -0
  62. {zscaler_sdk_python-0.9.7 → zscaler_sdk_python-0.10.1}/zscaler/zia/firewall.py +0 -0
  63. {zscaler_sdk_python-0.9.7 → zscaler_sdk_python-0.10.1}/zscaler/zia/forwarding_control.py +0 -0
  64. {zscaler_sdk_python-0.9.7 → zscaler_sdk_python-0.10.1}/zscaler/zia/isolation_profile.py +0 -0
  65. {zscaler_sdk_python-0.9.7 → zscaler_sdk_python-0.10.1}/zscaler/zia/sandbox.py +0 -0
  66. {zscaler_sdk_python-0.9.7 → zscaler_sdk_python-0.10.1}/zscaler/zia/ssl_inspection.py +0 -0
  67. {zscaler_sdk_python-0.9.7 → zscaler_sdk_python-0.10.1}/zscaler/zia/traffic.py +0 -0
  68. {zscaler_sdk_python-0.9.7 → zscaler_sdk_python-0.10.1}/zscaler/zia/url_categories.py +0 -0
  69. {zscaler_sdk_python-0.9.7 → zscaler_sdk_python-0.10.1}/zscaler/zia/url_filtering.py +0 -0
  70. {zscaler_sdk_python-0.9.7 → zscaler_sdk_python-0.10.1}/zscaler/zia/web_dlp.py +0 -0
  71. {zscaler_sdk_python-0.9.7 → zscaler_sdk_python-0.10.1}/zscaler/zia/workload_groups.py +0 -0
  72. {zscaler_sdk_python-0.9.7 → zscaler_sdk_python-0.10.1}/zscaler/zia/zpa_gateway.py +0 -0
  73. {zscaler_sdk_python-0.9.7 → zscaler_sdk_python-0.10.1}/zscaler/zpa/README.md +0 -0
  74. {zscaler_sdk_python-0.9.7 → zscaler_sdk_python-0.10.1}/zscaler/zpa/app_segments.py +0 -0
  75. {zscaler_sdk_python-0.9.7 → zscaler_sdk_python-0.10.1}/zscaler/zpa/app_segments_inspection.py +0 -0
  76. {zscaler_sdk_python-0.9.7 → zscaler_sdk_python-0.10.1}/zscaler/zpa/app_segments_pra.py +0 -0
  77. {zscaler_sdk_python-0.9.7 → zscaler_sdk_python-0.10.1}/zscaler/zpa/authdomains.py +0 -0
  78. {zscaler_sdk_python-0.9.7 → zscaler_sdk_python-0.10.1}/zscaler/zpa/certificates.py +0 -0
  79. {zscaler_sdk_python-0.9.7 → zscaler_sdk_python-0.10.1}/zscaler/zpa/client.py +0 -0
  80. {zscaler_sdk_python-0.9.7 → zscaler_sdk_python-0.10.1}/zscaler/zpa/cloud_connector_groups.py +0 -0
  81. {zscaler_sdk_python-0.9.7 → zscaler_sdk_python-0.10.1}/zscaler/zpa/connectors.py +0 -0
  82. {zscaler_sdk_python-0.9.7 → zscaler_sdk_python-0.10.1}/zscaler/zpa/emergency_access.py +0 -0
  83. {zscaler_sdk_python-0.9.7 → zscaler_sdk_python-0.10.1}/zscaler/zpa/errors.py +0 -0
  84. {zscaler_sdk_python-0.9.7 → zscaler_sdk_python-0.10.1}/zscaler/zpa/idp.py +0 -0
  85. {zscaler_sdk_python-0.9.7 → zscaler_sdk_python-0.10.1}/zscaler/zpa/inspection.py +0 -0
  86. {zscaler_sdk_python-0.9.7 → zscaler_sdk_python-0.10.1}/zscaler/zpa/isolation.py +0 -0
  87. {zscaler_sdk_python-0.9.7 → zscaler_sdk_python-0.10.1}/zscaler/zpa/lss.py +0 -0
  88. {zscaler_sdk_python-0.9.7 → zscaler_sdk_python-0.10.1}/zscaler/zpa/machine_groups.py +0 -0
  89. {zscaler_sdk_python-0.9.7 → zscaler_sdk_python-0.10.1}/zscaler/zpa/microtenants.py +0 -0
  90. {zscaler_sdk_python-0.9.7 → zscaler_sdk_python-0.10.1}/zscaler/zpa/posture_profiles.py +0 -0
  91. {zscaler_sdk_python-0.9.7 → zscaler_sdk_python-0.10.1}/zscaler/zpa/privileged_remote_access.py +0 -0
  92. {zscaler_sdk_python-0.9.7 → zscaler_sdk_python-0.10.1}/zscaler/zpa/provisioning.py +0 -0
  93. {zscaler_sdk_python-0.9.7 → zscaler_sdk_python-0.10.1}/zscaler/zpa/saml_attributes.py +0 -0
  94. {zscaler_sdk_python-0.9.7 → zscaler_sdk_python-0.10.1}/zscaler/zpa/scim_attributes.py +0 -0
  95. {zscaler_sdk_python-0.9.7 → zscaler_sdk_python-0.10.1}/zscaler/zpa/scim_groups.py +0 -0
  96. {zscaler_sdk_python-0.9.7 → zscaler_sdk_python-0.10.1}/zscaler/zpa/segment_groups.py +0 -0
  97. {zscaler_sdk_python-0.9.7 → zscaler_sdk_python-0.10.1}/zscaler/zpa/server_groups.py +0 -0
  98. {zscaler_sdk_python-0.9.7 → zscaler_sdk_python-0.10.1}/zscaler/zpa/servers.py +0 -0
  99. {zscaler_sdk_python-0.9.7 → zscaler_sdk_python-0.10.1}/zscaler/zpa/service_edges.py +0 -0
  100. {zscaler_sdk_python-0.9.7 → zscaler_sdk_python-0.10.1}/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.7
3
+ Version: 0.10.1
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
@@ -22,7 +22,7 @@ Classifier: Topic :: Security
22
22
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
23
23
  Provides-Extra: dev
24
24
  Requires-Dist: aenum ; extra == "dev"
25
- Requires-Dist: aiohttp (>=3.10.2)
25
+ Requires-Dist: aiohttp (>=3.11.11)
26
26
  Requires-Dist: arrow
27
27
  Requires-Dist: black (>=24.3.0) ; extra == "dev"
28
28
  Requires-Dist: certifi (>=2024.2.2)
@@ -34,16 +34,16 @@ Requires-Dist: idna (>=3.10)
34
34
  Requires-Dist: okta (>=2.9.7)
35
35
  Requires-Dist: pycryptodomex (>=3.20.0)
36
36
  Requires-Dist: pydash (>=8.0.3) ; extra == "dev"
37
- Requires-Dist: python-box (>=7.2.0)
37
+ Requires-Dist: python-box (>=7.3.0)
38
38
  Requires-Dist: python-dateutil
39
39
  Requires-Dist: pytz (>=2024.2)
40
40
  Requires-Dist: pyyaml
41
41
  Requires-Dist: requests (>=2.32.3)
42
42
  Requires-Dist: responses (>=0.25.3)
43
43
  Requires-Dist: restfly (>=1.5.0)
44
- Requires-Dist: six
44
+ Requires-Dist: six (>=1.17.0)
45
45
  Requires-Dist: xmltodict (>=0.14.2)
46
- Requires-Dist: yarl (>=1.14.0)
46
+ Requires-Dist: yarl (>=1.17.0)
47
47
  Project-URL: Bug Tracker, https://github.com/zscaler/zscaler-sdk-python/issues
48
48
  Project-URL: Documentation, https://zscaler-sdk-python.readthedocs.io
49
49
  Project-URL: Repository, https://github.com/zscaler/zscaler-sdk-python
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "zscaler-sdk-python"
3
- version = "0.9.7"
3
+ version = "0.10.1"
4
4
  description = "Official Python SDK for the Zscaler Products (Beta)"
5
5
  authors = ["Zscaler, Inc. <devrel@zscaler.com>"]
6
6
  license = "MIT"
@@ -33,16 +33,16 @@ arrow = "*"
33
33
  certifi = ">=2024.2.2"
34
34
  charset-normalizer = "*"
35
35
  idna = ">=3.10"
36
- python-box = ">=7.2.0"
36
+ python-box = ">=7.3.0"
37
37
  python-dateutil = "*"
38
38
  requests = ">=2.32.3"
39
39
  responses = ">=0.25.3"
40
40
  restfly = ">=1.5.0"
41
- six = "*"
41
+ six = ">=1.17.0"
42
42
  flatdict = "*"
43
43
  pyyaml = "*"
44
44
  xmltodict = ">=0.14.2"
45
- yarl = ">=1.14.0"
45
+ yarl = ">=1.17.0"
46
46
  pycryptodomex = ">=3.20.0"
47
47
  aenum = "*"
48
48
  pydash = ">=8.0.3"
@@ -51,7 +51,7 @@ pytz = ">=2024.2"
51
51
  black = ">=24.3.0"
52
52
  cryptography = ">=43.0.0"
53
53
  okta = ">=2.9.7"
54
- aiohttp = ">=3.10.2"
54
+ aiohttp = ">=3.11.11"
55
55
 
56
56
  [tool.poetry.dev-dependencies]
57
57
  black = ">=24.3.0"
@@ -62,7 +62,7 @@ pytest-recording = "^0.13.2"
62
62
  pytest-cov = "*"
63
63
  pyfakefs = ">=5.6.0"
64
64
  isort = "*"
65
- wheel = "*"
65
+ wheel = ">=0.45.1"
66
66
  sphinx = "^7.4.7"
67
67
  sphinx-autobuild = "*"
68
68
  sphinx_rtd_theme = "*"
@@ -29,7 +29,7 @@ __license__ = "MIT"
29
29
  __contributors__ = [
30
30
  "William Guilherme",
31
31
  ]
32
- __version__ = "0.9.7"
32
+ __version__ = "0.10.1"
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,9 +407,13 @@ class ZIAClientHelper(ZIAClient):
406
407
  page=None,
407
408
  pagesize=None,
408
409
  search=None,
409
- max_page_size=1000, # Default to 1000, can be adjusted based on endpoint constraints
410
410
  max_items=None, # Maximum number of items to retrieve across pages
411
411
  max_pages=None, # Maximum number of pages to retrieve
412
+ type=None, # Specify type of VPN credentials (CN, IP, UFQDN, XAUTH)
413
+ include_only_without_location=None, # Include only VPN credentials not associated with any location
414
+ location_id=None, # VPN credentials for a specific location ID
415
+ managed_by=None, # VPN credentials managed by a given partner
416
+ prefix=None, # VPN credentials managed by a given partner
412
417
  ):
413
418
  """
414
419
  Fetches paginated data from the API based on specified parameters and handles pagination.
@@ -421,6 +426,11 @@ class ZIAClientHelper(ZIAClient):
421
426
  search (str): Search query to filter the results.
422
427
  max_items (int): Maximum number of items to retrieve.
423
428
  max_pages (int): Maximum number of pages to fetch.
429
+ type (str, optional): Type of VPN credentials (e.g., CN, IP, UFQDN, XAUTH).
430
+ include_only_without_location (bool, optional): Filter to include only VPN credentials not associated with a location.
431
+ location_id (int, optional): Retrieve VPN credentials for the specified location ID.
432
+ managed_by (int, optional): Retrieve VPN credentials managed by the specified partner.
433
+ prefix (int, optional): Retrieve VPN credentials managed by the specified partner.
424
434
 
425
435
  Returns:
426
436
  tuple: A tuple containing:
@@ -435,16 +445,32 @@ class ZIAClientHelper(ZIAClient):
435
445
  }
436
446
 
437
447
  # Initialize pagination parameters
448
+ # params = {
449
+ # "page": page if page is not None else 1, # Start at page 1 if not specified
450
+ # "pagesize": min(pagesize if pagesize is not None else 100, max_page_size), # Apply max_page_size limit
451
+ # }
452
+
438
453
  params = {
439
454
  "page": page if page is not None else 1, # Start at page 1 if not specified
440
- "pagesize": min(pagesize if pagesize is not None else 100, max_page_size) # Apply max_page_size limit
455
+ "pagesize": max(100, min(pagesize or 100, 10000)), # Ensure pagesize is within API limits
441
456
  }
442
-
457
+
458
+ # Add optional filters to the params if provided
443
459
  if search:
444
460
  params["search"] = search
461
+ if type:
462
+ params["type"] = type
463
+ if include_only_without_location is not None:
464
+ params["includeOnlyWithoutLocation"] = include_only_without_location
465
+ if location_id:
466
+ params["locationId"] = location_id
467
+ if managed_by:
468
+ params["managedBy"] = managed_by
469
+ if prefix:
470
+ params["prefix"] = prefix
445
471
 
446
472
  ret_data = []
447
- total_collected = 0 # Track total items collected
473
+ total_collected = 0
448
474
 
449
475
  try:
450
476
  while True:
@@ -482,13 +508,14 @@ class ZIAClientHelper(ZIAClient):
482
508
 
483
509
  # Limit data collection based on max_items
484
510
  if max_items is not None:
485
- data = data[:max_items - total_collected] # Limit items on the current page
511
+ data = data[: max_items - total_collected] # Limit items on the current page
486
512
  ret_data.extend(data)
487
513
  total_collected += len(data)
488
514
 
489
515
  # Check if we've reached max_items or max_pages limits
490
- if (max_items is not None and total_collected >= max_items) or \
491
- (max_pages is not None and params["page"] >= max_pages):
516
+ if (max_items is not None and total_collected >= max_items) or (
517
+ max_pages is not None and params["page"] >= max_pages
518
+ ):
492
519
  break
493
520
 
494
521
  # Stop if we've processed all available pages (i.e., less than requested page size)
@@ -508,7 +535,6 @@ class ZIAClientHelper(ZIAClient):
508
535
 
509
536
  return BoxList(ret_data), None
510
537
 
511
-
512
538
  @property
513
539
  def admin_and_role_management(self):
514
540
  """
@@ -597,7 +623,7 @@ class ZIAClientHelper(ZIAClient):
597
623
 
598
624
  """
599
625
  return CloudSandboxAPI(self)
600
-
626
+
601
627
  @property
602
628
  def security(self):
603
629
  """
@@ -693,3 +719,11 @@ class ZIAClientHelper(ZIAClient):
693
719
 
694
720
  """
695
721
  return WorkloadGroupsAPI(self)
722
+
723
+ @property
724
+ def pac_files(self):
725
+ """
726
+ The interface object for the :ref:`ZIA Pac Files interface <zia-pac_files>`.
727
+
728
+ """
729
+ 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,419 @@
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 PAC Files configured in ZIA.
43
+
44
+ Examples:
45
+ List PAC Files using default settings:
46
+
47
+ >>> for pac_files in zia.pac_files.list_pac_files():
48
+ ... print(pac_files)
49
+
50
+ """
51
+ list, _ = self.rest.get_paginated_data(path="/pacFiles", **kwargs)
52
+ return list
53
+
54
+ def get_pac_file(self, pac_id: str, filter: str = None) -> Box:
55
+ """
56
+ Returns the PAC file details for a given PAC ID.
57
+
58
+ Args:
59
+ pac_id (str): The unique identifier for the PAC file.
60
+ filter (str, optional): Excludes specific information about the PAC file from
61
+ the response such as the PAC file content.
62
+ Accepts only the value 'pac_content'.
63
+
64
+ Returns:
65
+ :obj:`Box`: The PAC file resource record.
66
+
67
+ Example:
68
+ >>> pac_file = zia.get_pac_file('12345', filter='pac_content')
69
+ """
70
+ # Construct URL with optional filter
71
+ url = f"/pacFiles/{pac_id}/version"
72
+ if filter == "pac_content":
73
+ url += f"?filter={filter}"
74
+
75
+ response = self.rest.get(url)
76
+ if isinstance(response, Response):
77
+ if response.status_code != 200:
78
+ return None
79
+ return response
80
+
81
+ def get_pac_file_version(self, pac_id: str, pac_version: str, filter: str = None) -> Box:
82
+ """
83
+ Returns the PAC file version details for a given PAC ID and version.
84
+
85
+ Args:
86
+ pac_id (str): The unique identifier for the PAC file.
87
+ pac_version (str): The specific version of the PAC file.
88
+ filter (str, optional): Excludes specific information about the PAC file from
89
+ the response such as the PAC file content.
90
+ Accepts only the value 'pac_content'.
91
+
92
+ Returns:
93
+ :obj:`Box`: The PAC file version resource record.
94
+
95
+ Example:
96
+ >>> pac_file_version = zia.get_pac_file_version('12345', '1', filter='pac_content')
97
+ """
98
+ # Construct URL with optional filter
99
+ url = f"/pacFiles/{pac_id}/version/{pac_version}"
100
+ if filter == "pac_content":
101
+ url += f"?filter={filter}"
102
+
103
+ response = self.rest.get(url)
104
+ if isinstance(response, Response):
105
+ if response.status_code != 200:
106
+ return None
107
+ return response
108
+
109
+ def validate_pac_file(self, pac_file_content: str) -> Box:
110
+ """
111
+ Sends the PAC file content for validation and returns the validation result.
112
+
113
+ Args:
114
+ pac_file_content (str): The PAC file content to be validated.
115
+
116
+ Returns:
117
+ Box: The API response containing the validation result.
118
+
119
+ Raises:
120
+ Exception: If the API call fails with a non-2xx status code.
121
+
122
+ Example:
123
+ To validate PAC file content:
124
+
125
+ .. code-block:: python
126
+
127
+ pac_file_content = '''
128
+ function FindProxyForURL(url, host) {
129
+ return "PROXY gateway.example.com:80";
130
+ }
131
+ '''
132
+
133
+ response = client.validate_pac_file(pac_file_content=pac_file_content)
134
+ if response["success"]:
135
+ print("PAC file is valid.")
136
+ else:
137
+ print("PAC file validation failed.")
138
+ """
139
+ # Send only the PAC content as the raw body
140
+ response = self.rest.post("pacFiles/validate", data=pac_file_content)
141
+
142
+ # Check if the response is successful and already in Box format
143
+ if isinstance(response, Box):
144
+ return response
145
+ elif isinstance(response, Response):
146
+ if response.status_code != 200:
147
+ raise Exception(f"API call failed with status {response.status_code}: {response.json()}")
148
+ # Convert to Box if needed
149
+ return Box(response.json())
150
+
151
+ def add_pac_file(
152
+ self,
153
+ name: str,
154
+ description: str,
155
+ domain: str,
156
+ pac_commit_message: str,
157
+ pac_verification_status: str,
158
+ pac_version_status: str,
159
+ pac_content: str,
160
+ **kwargs,
161
+ ) -> Box:
162
+ """
163
+ Adds a new custom PAC file after validating the PAC content.
164
+
165
+ Args:
166
+ name (str): The name of the new PAC file.
167
+ description (str): The description of the new PAC file.
168
+ domain (str): The domain of your organization to which the PAC file applies.
169
+ pac_commit_message (str): The commit message entered while saving the PAC file version as indicated by the pacVersion field.
170
+ pac_verification_status (str): Indicates the verification status of the PAC file and if any errors are identified in the syntax.
171
+ Supported Values: `VERIFY_NOERR`, `VERIFY_ERR`, `NOVERIFY`
172
+ pac_version_status (str): Version status of the new PAC file.
173
+ Supported Values: `DEPLOYED`, `STAGE`, `LKG`
174
+ pac_content (str): The actual PAC file content to be validated and cloned.
175
+
176
+ Keyword Args:
177
+ Additional optional parameters as key-value pairs.
178
+
179
+ Returns:
180
+ Box: The newly added PAC file resource record.
181
+
182
+ Example:
183
+ >>> pac_file = zia.add_pac_file(
184
+ name="Test_Pac_File_01",
185
+ description="Test_Pac_File_01",
186
+ domain="bd-hashicorp.com",
187
+ pacCommitMessage="Test_Pac_File_01",
188
+ pacVerificationStatus="VERIFY_NOERR",
189
+ pacVersionStatus="DEPLOYED",
190
+ pacContent="function FindProxyForURL(url, host) { return 'PROXY gateway.example.com:80'; }"
191
+ )
192
+ """
193
+ validation_result = self.validate_pac_file(pac_content)
194
+ if not validation_result.success:
195
+ raise Exception("PAC content validation failed: {}".format(validation_result))
196
+
197
+ payload = {
198
+ "name": name,
199
+ "description": description,
200
+ "domain": domain,
201
+ "pacCommitMessage": pac_commit_message,
202
+ "pacVerificationStatus": pac_verification_status,
203
+ "pacVersionStatus": pac_version_status,
204
+ "pacContent": pac_content,
205
+ }
206
+
207
+ for key, value in kwargs.items():
208
+ payload[snake_to_camel(key)] = value
209
+
210
+ response = self.rest.post("pacFiles", json=payload)
211
+ if isinstance(response, Response):
212
+ if response.status_code != 200:
213
+ raise Exception(f"API call failed with status {response.status_code}: {response.json()}")
214
+ return Box(response.json())
215
+ return response
216
+
217
+ def clone_pac_file(
218
+ self,
219
+ pac_id: str,
220
+ pac_version: str,
221
+ name: str,
222
+ description: str,
223
+ domain: str,
224
+ pac_commit_message: str,
225
+ pac_verification_status: str,
226
+ pac_version_status: str,
227
+ pac_content: str,
228
+ delete_version: int = None,
229
+ **kwargs,
230
+ ) -> Box:
231
+ """
232
+ Clones an existing PAC file by creating a new PAC file based on the specified PAC ID and version.
233
+
234
+ Args:
235
+ pac_id (str): The unique identifier of the PAC file to be cloned.
236
+ pac_version (str): The specific version of the PAC file to be cloned.
237
+ name (str): The name of the new PAC file.
238
+ domain (str): The domain associated with the new PAC file.
239
+ pac_commit_message (str): Commit message for the new PAC file.
240
+ pac_verification_status (str): Verification status of the new PAC file.
241
+ Supported Values: `VERIFY_NOERR`, `VERIFY_ERR`, `NOVERIFY`
242
+ pac_version_status (str): Version status of the new PAC file.
243
+ Supported Values: `DEPLOYED`, `STAGE`, `LKG`
244
+ pac_content (str): The actual PAC file content to be validated and cloned.
245
+ delete_version (int, optional): Specifies the PAC file version to replace if the version limit of 10 is reached.
246
+
247
+ Keyword Args:
248
+ Additional optional parameters as key-value pairs.
249
+
250
+ Returns:
251
+ Box: The newly cloned PAC file resource record.
252
+
253
+ Example:
254
+ >>> pac_file = zia.clone_pac_file(
255
+ pac_id="12345",
256
+ pac_version="1",
257
+ name="Cloned_Pac_File_01",
258
+ description="Cloned_Pac_File_01",
259
+ domain="bd-hashicorp.com",
260
+ pac_commit_message="Clone of Test_Pac_File_01",
261
+ pac_verification_status="VERIFY_NOERR",
262
+ pac_version_status="DEPLOYED",
263
+ pac_content="function FindProxyForURL(url, host) { return 'PROXY gateway.example.com:80'; }",
264
+ delete_version=5
265
+ )
266
+ """
267
+ # Step 1: Validate the PAC content
268
+ validation_result = self.validate_pac_file(pac_content)
269
+ if not validation_result.success:
270
+ raise Exception("PAC content validation failed: {}".format(validation_result))
271
+
272
+ # Step 2: Check the number of PAC file versions
273
+ pac_file_details = self.get_pac_file(pac_id)
274
+ if isinstance(pac_file_details, Box):
275
+ pac_file_details = pac_file_details.to_list() # Convert Box to a list of dictionaries if needed
276
+
277
+ # Extract pac versions from details
278
+ pac_versions = [entry.get("pacVersion") for entry in pac_file_details] if pac_file_details else []
279
+ total_versions = len(pac_versions)
280
+
281
+ # Step 3: Decide on including delete_version in the URL
282
+ if total_versions >= 10:
283
+ if delete_version is None:
284
+ # If delete_version was not provided, default to the oldest version (smallest number)
285
+ delete_version = min(pac_versions)
286
+ url = f"/pacFiles/{pac_id}/version/{pac_version}?deleteVersion={delete_version}"
287
+ else:
288
+ url = f"/pacFiles/{pac_id}/version/{pac_version}"
289
+
290
+ # Step 4: Construct the payload with mandatory fields
291
+ payload = {
292
+ "name": name,
293
+ "description": description,
294
+ "domain": domain,
295
+ "pacCommitMessage": pac_commit_message,
296
+ "pacVerificationStatus": pac_verification_status,
297
+ "pacVersionStatus": pac_version_status,
298
+ "pacContent": pac_content,
299
+ }
300
+
301
+ # Add any additional optional parameters
302
+ for key, value in kwargs.items():
303
+ payload[snake_to_camel(key)] = value
304
+
305
+ # Step 5: Make the request to clone the PAC file
306
+ response = self.rest.post(url, json=payload)
307
+ if isinstance(response, Response):
308
+ if response.status_code != 200:
309
+ raise Exception(f"API call failed with status {response.status_code}: {response.json()}")
310
+ return Box(response.json())
311
+ return response
312
+
313
+ def update_pac_file(
314
+ self,
315
+ pac_id: str,
316
+ pac_version: str,
317
+ pac_version_action: str,
318
+ name: str,
319
+ description: str,
320
+ domain: str,
321
+ pac_commit_message: str,
322
+ pac_verification_status: str,
323
+ # pac_version_status: str,
324
+ pac_content: str,
325
+ new_lkg_ver: int = None,
326
+ **kwargs,
327
+ ) -> Box:
328
+ """
329
+ Performs the specified action on the PAC file version and updates the file status.
330
+ Supported actions include deploying, staging, unstaging, and marking or unmarking
331
+ the file as last known good version.
332
+
333
+ Args:
334
+ pac_id (str): The unique identifier of the PAC file to be updated.
335
+ pac_version (str): The specific version of the PAC file to be updated.
336
+ pac_version_action (str): Action to perform on the PAC version.
337
+ Supported Values: `DEPLOY`, `STAGE`, `LKG`, `UNSTAGE`, `REMOVE_LKG`
338
+ name (str): The name of the PAC file.
339
+ description (str): Description of the PAC file.
340
+ domain (str): The domain associated with the PAC file.
341
+ pac_commit_message (str): Commit message for the PAC file.
342
+ pac_verification_status (str): Verification status of the PAC file.
343
+ Supported Values: `VERIFY_NOERR`, `VERIFY_ERR`, `NOVERIFY`
344
+ pac_version_status (str): Version status of the PAC file.
345
+ Supported Values: `DEPLOYED`, `STAGE`, `LKG`
346
+ pac_content (str): The actual PAC file content to be updated.
347
+ new_lkg_ver (int, optional): Specifies a different version to be marked as the last known good version
348
+ if the action is removing the current last known good version.
349
+
350
+ Keyword Args:
351
+ Additional optional parameters as key-value pairs.
352
+
353
+ Returns:
354
+ Box: The updated PAC file resource record.
355
+
356
+ Example:
357
+ >>> pac_file = zia.update_pac_file(
358
+ pac_id="12345",
359
+ pac_version="1",
360
+ pac_version_action="DEPLOY",
361
+ name="Update_Pac_File_01",
362
+ description="Update_Pac_File_01",
363
+ domain="bd-hashicorp.com",
364
+ pac_commit_message="Update_Pac_File_01",
365
+ pac_verification_status="VERIFY_NOERR",
366
+ pac_version_status="DEPLOYED",
367
+ pac_content="function FindProxyForURL(url, host) { return 'PROXY gateway.example.com:80'; }",
368
+ new_lkg_ver=5
369
+ )
370
+ """
371
+ # Step 1: Validate the PAC content
372
+ validation_result = self.validate_pac_file(pac_content)
373
+ if not validation_result.success:
374
+ raise Exception("PAC content validation failed: {}".format(validation_result))
375
+
376
+ # Step 2: Construct the URL with mandatory parameters and optional newLKGVer
377
+ url = f"/pacFiles/{pac_id}/version/{pac_version}/action/{pac_version_action}"
378
+ if new_lkg_ver is not None:
379
+ url += f"?newLKGVer={new_lkg_ver}"
380
+
381
+ # Step 3: Construct the payload with required fields
382
+ payload = {
383
+ "name": name,
384
+ "description": description,
385
+ "domain": domain,
386
+ "pacCommitMessage": pac_commit_message,
387
+ "pacVerificationStatus": pac_verification_status,
388
+ # "pacVersionStatus": pac_version_status,
389
+ "pacContent": pac_content,
390
+ }
391
+
392
+ # Add any additional optional parameters
393
+ for key, value in kwargs.items():
394
+ payload[snake_to_camel(key)] = value
395
+
396
+ # Step 4: Make the request to update the PAC file
397
+ response = self.rest.put(url, json=payload)
398
+ if isinstance(response, Response):
399
+ if response.status_code != 200:
400
+ raise Exception(f"API call failed with status {response.status_code}: {response.json()}")
401
+ return Box(response.json())
402
+ return response
403
+
404
+ def delete_pac_file(self, pac_id):
405
+ """
406
+ Deletes the specified Pac File.
407
+
408
+ Args:
409
+ pac_id (str): The unique identifier of the Pac File that will be deleted.
410
+
411
+ Returns:
412
+ :obj:`int`: The response code for the request.
413
+
414
+ Examples
415
+ >>> user = zia.pac_files.delete_pac_file('99999')
416
+
417
+ """
418
+ return self.rest.delete(f"pacFiles/{pac_id}").status_code
419
+
@@ -166,7 +166,7 @@ class SecurityPolicyAPI:
166
166
  try:
167
167
  # Send the POST request to add URLs to the blacklist
168
168
  response = self.rest.post("security/advanced/blacklistUrls?action=ADD_TO_LIST", json=payload)
169
-
169
+
170
170
  # Check if the response includes an empty 'blacklistUrls', signaling no update
171
171
  if "blacklistUrls" in response and not response["blacklistUrls"]:
172
172
  raise Exception("Failed to add URLs to blacklist: The API response returned an empty 'blacklistUrls' list.")
@@ -177,7 +177,7 @@ class SecurityPolicyAPI:
177
177
  return updated_blacklist
178
178
  else:
179
179
  raise Exception("Failed to add URLs to blacklist: URLs were not present in the updated blacklist.")
180
-
180
+
181
181
  except Exception as exc:
182
182
  raise Exception(f"Failed to add URLs to blacklist: {exc}")
183
183
 
@@ -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
 
@@ -71,7 +71,7 @@ class UserManagementAPI:
71
71
  """
72
72
  list, _ = self.rest.get_paginated_data(path="/departments", **kwargs)
73
73
  return list
74
-
74
+
75
75
  def get_department(self, department_id: str) -> Box:
76
76
  """
77
77
  Returns the department details for a given department.
@@ -170,7 +170,7 @@ class UserManagementAPI:
170
170
  **name (str, optional):
171
171
  Filters by user name. This is a `partial` match.
172
172
  **page_size (int, optional):
173
- Specifies the page size. The default size is 100, but the maximum size is 1000.
173
+ Specifies the page size. The default size is 100, but the maximum size is 10000.
174
174
  **sort_by (str):
175
175
  The field name to sort by, supported values: id, name, creationTime or modifiedTime (default to name)
176
176
  **sort_order (str):
@@ -369,27 +369,27 @@ class ZPAClientHelper(ZPAClient):
369
369
  return self.send("DELETE", path, json, params, api_version=api_version)
370
370
 
371
371
  def get_paginated_data(
372
- self,
373
- path=None,
374
- params=None,
375
- expected_status_code=200,
376
- api_version: str = None,
377
- search=None,
378
- search_field="name",
379
- max_pages=None,
380
- max_items=None,
381
- all_entries=False,
382
- sort_order=None,
383
- sort_by=None,
384
- sort_dir=None,
385
- start_time=None,
386
- end_time=None,
387
- idp_group_id=None,
388
- scim_user_id=None,
389
- scim_username=None,
390
- page=None,
391
- pagesize=None,
392
- microtenant_id=None,
372
+ self,
373
+ path=None,
374
+ params=None,
375
+ expected_status_code=200,
376
+ api_version: str = None,
377
+ search=None,
378
+ search_field="name",
379
+ max_pages=None,
380
+ max_items=None,
381
+ all_entries=False,
382
+ sort_order=None,
383
+ sort_by=None,
384
+ sort_dir=None,
385
+ start_time=None,
386
+ end_time=None,
387
+ idp_group_id=None,
388
+ scim_user_id=None,
389
+ scim_username=None,
390
+ page=None,
391
+ pagesize=None,
392
+ microtenant_id=None,
393
393
  ):
394
394
  """
395
395
  Fetches paginated data from the ZPA API based on specified parameters and handles various types of API pagination.
@@ -15,8 +15,6 @@
15
15
  # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16
16
 
17
17
 
18
- import functools
19
-
20
18
  from box import Box, BoxList
21
19
  from requests import Response
22
20
 
@@ -61,6 +59,7 @@ class PolicySetsAPI:
61
59
  """
62
60
  template = []
63
61
  app_and_app_group_operands = []
62
+ scim_and_scim_group_operands = []
64
63
  object_types_to_operands = {
65
64
  "CONSOLE": [],
66
65
  "MACHINE_GRP": [],
@@ -100,6 +99,8 @@ class PolicySetsAPI:
100
99
 
101
100
  if object_type in ["APP", "APP_GROUP"]:
102
101
  app_and_app_group_operands.append(operand)
102
+ elif object_type in ["SCIM","SCIM_GROUP"]:
103
+ scim_and_scim_group_operands.append(operand)
103
104
  elif object_type in object_types_to_operands:
104
105
  object_types_to_operands[object_type].append(operand)
105
106
 
@@ -131,6 +132,11 @@ class PolicySetsAPI:
131
132
  app_group_operator = operators_for_types.get("APP", "OR")
132
133
  template.append({"operator": app_group_operator, "operands": app_and_app_group_operands})
133
134
 
135
+ # Combine SCIM and SCIM_GROUP operands with their specific operator
136
+ if scim_and_scim_group_operands:
137
+ scim_group_operator = operators_for_types.get("SCIM_GROUP", "OR")
138
+ template.append({"operator": scim_group_operator, "operands": scim_and_scim_group_operands})
139
+
134
140
  # Combine other object types into their blocks with their respective operator
135
141
  for object_type, operands in object_types_to_operands.items():
136
142
  if operands: