zscaler-sdk-python 0.6.1__tar.gz → 0.7.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 (94) hide show
  1. {zscaler_sdk_python-0.6.1 → zscaler_sdk_python-0.7.0}/PKG-INFO +5 -4
  2. {zscaler_sdk_python-0.6.1 → zscaler_sdk_python-0.7.0}/pyproject.toml +9 -8
  3. {zscaler_sdk_python-0.6.1 → zscaler_sdk_python-0.7.0}/zscaler/__init__.py +3 -1
  4. {zscaler_sdk_python-0.6.1 → zscaler_sdk_python-0.7.0}/zscaler/constants.py +1 -1
  5. {zscaler_sdk_python-0.6.1 → zscaler_sdk_python-0.7.0}/zscaler/utils.py +19 -0
  6. zscaler_sdk_python-0.7.0/zscaler/zcon/__init__.py +486 -0
  7. zscaler_sdk_python-0.7.0/zscaler/zcon/activation.py +62 -0
  8. zscaler_sdk_python-0.7.0/zscaler/zcon/admin_and_role_management.py +490 -0
  9. zscaler_sdk_python-0.7.0/zscaler/zcon/client.py +88 -0
  10. zscaler_sdk_python-0.7.0/zscaler/zcon/ecgroups.py +179 -0
  11. zscaler_sdk_python-0.7.0/zscaler/zcon/locations.py +322 -0
  12. zscaler_sdk_python-0.7.0/zscaler/zcon/provisioning.py +228 -0
  13. {zscaler_sdk_python-0.6.1 → zscaler_sdk_python-0.7.0}/zscaler/zia/__init__.py +11 -5
  14. {zscaler_sdk_python-0.6.1 → zscaler_sdk_python-0.7.0}/zscaler/zia/admin_and_role_management.py +26 -0
  15. {zscaler_sdk_python-0.6.1 → zscaler_sdk_python-0.7.0}/zscaler/zia/locations.py +4 -4
  16. {zscaler_sdk_python-0.6.1 → zscaler_sdk_python-0.7.0}/zscaler/zia/workload_groups.py +30 -4
  17. {zscaler_sdk_python-0.6.1 → zscaler_sdk_python-0.7.0}/zscaler/zpa/__init__.py +2 -2
  18. {zscaler_sdk_python-0.6.1 → zscaler_sdk_python-0.7.0}/zscaler/zpa/isolation.py +1 -1
  19. {zscaler_sdk_python-0.6.1 → zscaler_sdk_python-0.7.0}/LICENSE.md +0 -0
  20. {zscaler_sdk_python-0.6.1 → zscaler_sdk_python-0.7.0}/README.md +0 -0
  21. {zscaler_sdk_python-0.6.1 → zscaler_sdk_python-0.7.0}/zscaler/cache/__init__.py +0 -0
  22. {zscaler_sdk_python-0.6.1 → zscaler_sdk_python-0.7.0}/zscaler/cache/cache.py +0 -0
  23. {zscaler_sdk_python-0.6.1 → zscaler_sdk_python-0.7.0}/zscaler/cache/no_op_cache.py +0 -0
  24. {zscaler_sdk_python-0.6.1 → zscaler_sdk_python-0.7.0}/zscaler/cache/zscaler_cache.py +0 -0
  25. {zscaler_sdk_python-0.6.1 → zscaler_sdk_python-0.7.0}/zscaler/errors/__init__.py +0 -0
  26. {zscaler_sdk_python-0.6.1 → zscaler_sdk_python-0.7.0}/zscaler/errors/error.py +0 -0
  27. {zscaler_sdk_python-0.6.1 → zscaler_sdk_python-0.7.0}/zscaler/errors/http_error.py +0 -0
  28. {zscaler_sdk_python-0.6.1 → zscaler_sdk_python-0.7.0}/zscaler/errors/zscaler_api_error.py +0 -0
  29. {zscaler_sdk_python-0.6.1 → zscaler_sdk_python-0.7.0}/zscaler/exceptions/__init__.py +0 -0
  30. {zscaler_sdk_python-0.6.1 → zscaler_sdk_python-0.7.0}/zscaler/exceptions/exceptions.py +0 -0
  31. {zscaler_sdk_python-0.6.1 → zscaler_sdk_python-0.7.0}/zscaler/logger.py +0 -0
  32. {zscaler_sdk_python-0.6.1 → zscaler_sdk_python-0.7.0}/zscaler/ratelimiter/__init__.py +0 -0
  33. {zscaler_sdk_python-0.6.1 → zscaler_sdk_python-0.7.0}/zscaler/ratelimiter/ratelimiter.py +0 -0
  34. {zscaler_sdk_python-0.6.1 → zscaler_sdk_python-0.7.0}/zscaler/user_agent.py +0 -0
  35. {zscaler_sdk_python-0.6.1 → zscaler_sdk_python-0.7.0}/zscaler/zdx/__init__.py +0 -0
  36. {zscaler_sdk_python-0.6.1 → zscaler_sdk_python-0.7.0}/zscaler/zdx/admin.py +0 -0
  37. {zscaler_sdk_python-0.6.1 → zscaler_sdk_python-0.7.0}/zscaler/zdx/alerts.py +0 -0
  38. {zscaler_sdk_python-0.6.1 → zscaler_sdk_python-0.7.0}/zscaler/zdx/apps.py +0 -0
  39. {zscaler_sdk_python-0.6.1 → zscaler_sdk_python-0.7.0}/zscaler/zdx/devices.py +0 -0
  40. {zscaler_sdk_python-0.6.1 → zscaler_sdk_python-0.7.0}/zscaler/zdx/filters.py +0 -0
  41. {zscaler_sdk_python-0.6.1 → zscaler_sdk_python-0.7.0}/zscaler/zdx/inventory.py +0 -0
  42. {zscaler_sdk_python-0.6.1 → zscaler_sdk_python-0.7.0}/zscaler/zdx/troubleshooting.py +0 -0
  43. {zscaler_sdk_python-0.6.1 → zscaler_sdk_python-0.7.0}/zscaler/zdx/users.py +0 -0
  44. {zscaler_sdk_python-0.6.1 → zscaler_sdk_python-0.7.0}/zscaler/zdx/zdx_client.py +0 -0
  45. {zscaler_sdk_python-0.6.1 → zscaler_sdk_python-0.7.0}/zscaler/zia/activate.py +0 -0
  46. {zscaler_sdk_python-0.6.1 → zscaler_sdk_python-0.7.0}/zscaler/zia/apptotal.py +0 -0
  47. {zscaler_sdk_python-0.6.1 → zscaler_sdk_python-0.7.0}/zscaler/zia/audit_logs.py +0 -0
  48. {zscaler_sdk_python-0.6.1 → zscaler_sdk_python-0.7.0}/zscaler/zia/authentication_settings.py +0 -0
  49. {zscaler_sdk_python-0.6.1 → zscaler_sdk_python-0.7.0}/zscaler/zia/client.py +0 -0
  50. {zscaler_sdk_python-0.6.1 → zscaler_sdk_python-0.7.0}/zscaler/zia/cloud_apps.py +0 -0
  51. {zscaler_sdk_python-0.6.1 → zscaler_sdk_python-0.7.0}/zscaler/zia/device_management.py +0 -0
  52. {zscaler_sdk_python-0.6.1 → zscaler_sdk_python-0.7.0}/zscaler/zia/dlp.py +0 -0
  53. {zscaler_sdk_python-0.6.1 → zscaler_sdk_python-0.7.0}/zscaler/zia/errors.py +0 -0
  54. {zscaler_sdk_python-0.6.1 → zscaler_sdk_python-0.7.0}/zscaler/zia/firewall.py +0 -0
  55. {zscaler_sdk_python-0.6.1 → zscaler_sdk_python-0.7.0}/zscaler/zia/forwarding_control.py +0 -0
  56. {zscaler_sdk_python-0.6.1 → zscaler_sdk_python-0.7.0}/zscaler/zia/isolation_profile.py +0 -0
  57. {zscaler_sdk_python-0.6.1 → zscaler_sdk_python-0.7.0}/zscaler/zia/labels.py +0 -0
  58. {zscaler_sdk_python-0.6.1 → zscaler_sdk_python-0.7.0}/zscaler/zia/sandbox.py +0 -0
  59. {zscaler_sdk_python-0.6.1 → zscaler_sdk_python-0.7.0}/zscaler/zia/security.py +0 -0
  60. {zscaler_sdk_python-0.6.1 → zscaler_sdk_python-0.7.0}/zscaler/zia/ssl_inspection.py +0 -0
  61. {zscaler_sdk_python-0.6.1 → zscaler_sdk_python-0.7.0}/zscaler/zia/traffic.py +0 -0
  62. {zscaler_sdk_python-0.6.1 → zscaler_sdk_python-0.7.0}/zscaler/zia/url_categories.py +0 -0
  63. {zscaler_sdk_python-0.6.1 → zscaler_sdk_python-0.7.0}/zscaler/zia/url_filtering.py +0 -0
  64. {zscaler_sdk_python-0.6.1 → zscaler_sdk_python-0.7.0}/zscaler/zia/users.py +0 -0
  65. {zscaler_sdk_python-0.6.1 → zscaler_sdk_python-0.7.0}/zscaler/zia/web_dlp.py +0 -0
  66. {zscaler_sdk_python-0.6.1 → zscaler_sdk_python-0.7.0}/zscaler/zia/zpa_gateway.py +0 -0
  67. {zscaler_sdk_python-0.6.1 → zscaler_sdk_python-0.7.0}/zscaler/zpa/README.md +0 -0
  68. {zscaler_sdk_python-0.6.1 → zscaler_sdk_python-0.7.0}/zscaler/zpa/app_segments.py +0 -0
  69. {zscaler_sdk_python-0.6.1 → zscaler_sdk_python-0.7.0}/zscaler/zpa/app_segments_inspection.py +0 -0
  70. {zscaler_sdk_python-0.6.1 → zscaler_sdk_python-0.7.0}/zscaler/zpa/app_segments_pra.py +0 -0
  71. {zscaler_sdk_python-0.6.1 → zscaler_sdk_python-0.7.0}/zscaler/zpa/authdomains.py +0 -0
  72. {zscaler_sdk_python-0.6.1 → zscaler_sdk_python-0.7.0}/zscaler/zpa/certificates.py +0 -0
  73. {zscaler_sdk_python-0.6.1 → zscaler_sdk_python-0.7.0}/zscaler/zpa/client.py +0 -0
  74. {zscaler_sdk_python-0.6.1 → zscaler_sdk_python-0.7.0}/zscaler/zpa/cloud_connector_groups.py +0 -0
  75. {zscaler_sdk_python-0.6.1 → zscaler_sdk_python-0.7.0}/zscaler/zpa/connectors.py +0 -0
  76. {zscaler_sdk_python-0.6.1 → zscaler_sdk_python-0.7.0}/zscaler/zpa/emergency_access.py +0 -0
  77. {zscaler_sdk_python-0.6.1 → zscaler_sdk_python-0.7.0}/zscaler/zpa/errors.py +0 -0
  78. {zscaler_sdk_python-0.6.1 → zscaler_sdk_python-0.7.0}/zscaler/zpa/idp.py +0 -0
  79. {zscaler_sdk_python-0.6.1 → zscaler_sdk_python-0.7.0}/zscaler/zpa/inspection.py +0 -0
  80. {zscaler_sdk_python-0.6.1 → zscaler_sdk_python-0.7.0}/zscaler/zpa/lss.py +0 -0
  81. {zscaler_sdk_python-0.6.1 → zscaler_sdk_python-0.7.0}/zscaler/zpa/machine_groups.py +0 -0
  82. {zscaler_sdk_python-0.6.1 → zscaler_sdk_python-0.7.0}/zscaler/zpa/microtenants.py +0 -0
  83. {zscaler_sdk_python-0.6.1 → zscaler_sdk_python-0.7.0}/zscaler/zpa/policies.py +0 -0
  84. {zscaler_sdk_python-0.6.1 → zscaler_sdk_python-0.7.0}/zscaler/zpa/posture_profiles.py +0 -0
  85. {zscaler_sdk_python-0.6.1 → zscaler_sdk_python-0.7.0}/zscaler/zpa/privileged_remote_access.py +0 -0
  86. {zscaler_sdk_python-0.6.1 → zscaler_sdk_python-0.7.0}/zscaler/zpa/provisioning.py +0 -0
  87. {zscaler_sdk_python-0.6.1 → zscaler_sdk_python-0.7.0}/zscaler/zpa/saml_attributes.py +0 -0
  88. {zscaler_sdk_python-0.6.1 → zscaler_sdk_python-0.7.0}/zscaler/zpa/scim_attributes.py +0 -0
  89. {zscaler_sdk_python-0.6.1 → zscaler_sdk_python-0.7.0}/zscaler/zpa/scim_groups.py +0 -0
  90. {zscaler_sdk_python-0.6.1 → zscaler_sdk_python-0.7.0}/zscaler/zpa/segment_groups.py +0 -0
  91. {zscaler_sdk_python-0.6.1 → zscaler_sdk_python-0.7.0}/zscaler/zpa/server_groups.py +0 -0
  92. {zscaler_sdk_python-0.6.1 → zscaler_sdk_python-0.7.0}/zscaler/zpa/servers.py +0 -0
  93. {zscaler_sdk_python-0.6.1 → zscaler_sdk_python-0.7.0}/zscaler/zpa/service_edges.py +0 -0
  94. {zscaler_sdk_python-0.6.1 → zscaler_sdk_python-0.7.0}/zscaler/zpa/trusted_networks.py +0 -0
@@ -1,27 +1,28 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: zscaler-sdk-python
3
- Version: 0.6.1
3
+ Version: 0.7.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
7
7
  Keywords: zscaler,sdk,zpa,zia,zdx,zcc,zcon
8
8
  Author: Zscaler, Inc.
9
9
  Author-email: devrel@zscaler.com
10
- Requires-Python: >=3.8,<4.0
10
+ Requires-Python: >=3.9,<4.0
11
11
  Classifier: Development Status :: 4 - Beta
12
12
  Classifier: Intended Audience :: Developers
13
13
  Classifier: License :: OSI Approved :: MIT License
14
14
  Classifier: Operating System :: OS Independent
15
15
  Classifier: Programming Language :: Python :: 3
16
- Classifier: Programming Language :: Python :: 3.8
17
16
  Classifier: Programming Language :: Python :: 3.9
18
17
  Classifier: Programming Language :: Python :: 3.10
19
18
  Classifier: Programming Language :: Python :: 3.11
20
19
  Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.8
21
21
  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
26
  Requires-Dist: arrow
26
27
  Requires-Dist: black (>=24.3.0) ; extra == "dev"
27
28
  Requires-Dist: certifi (>=2024.2.2)
@@ -32,7 +33,7 @@ Requires-Dist: flatdict
32
33
  Requires-Dist: idna
33
34
  Requires-Dist: okta (>=2.9.7)
34
35
  Requires-Dist: pycryptodomex
35
- Requires-Dist: pydash ; extra == "dev"
36
+ Requires-Dist: pydash (>=8.0.3) ; extra == "dev"
36
37
  Requires-Dist: python-box (>=7.2.0,<8.0.0)
37
38
  Requires-Dist: python-dateutil
38
39
  Requires-Dist: pytz
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "zscaler-sdk-python"
3
- version = "0.6.1"
3
+ version = "0.7.0"
4
4
  description = "Official Python SDK for the Zscaler Products (Beta)"
5
5
  authors = ["Zscaler, Inc. <devrel@zscaler.com>"]
6
6
  license = "MIT"
@@ -28,7 +28,7 @@ packages = [
28
28
  "Bug Tracker" = "https://github.com/zscaler/zscaler-sdk-python/issues"
29
29
 
30
30
  [tool.poetry.dependencies]
31
- python = ">=3.8,<4.0"
31
+ python = ">=3.9,<4.0"
32
32
  arrow = "*"
33
33
  certifi = ">=2024.2.2"
34
34
  charset-normalizer = "*"
@@ -45,25 +45,26 @@ xmltodict = "*"
45
45
  yarl = "*"
46
46
  pycryptodomex = "*"
47
47
  aenum = "*"
48
- pydash = "*"
48
+ pydash = ">=8.0.3"
49
49
  flake8 = "*"
50
50
  pytz = "*"
51
51
  black = ">=24.3.0"
52
52
  # cryptography = ">=3.4,<43.0"
53
53
  cryptography = ">=42.0.7"
54
54
  okta = ">=2.9.7"
55
+ aiohttp = ">=3.10.2"
55
56
 
56
57
  [tool.poetry.dev-dependencies]
57
58
  black = ">=24.3.0"
58
- pytest = ">=8.2.1"
59
- pytest-asyncio = ">=0.23.7"
59
+ pytest = ">=8.2.2"
60
+ pytest-asyncio = "^0.23.8"
60
61
  pytest-mock = "*"
61
- pytest-recording = "*"
62
+ pytest-recording = "^0.13.2"
62
63
  pytest-cov = "*"
63
- pyfakefs = "*"
64
+ pyfakefs = "^5.6.0"
64
65
  isort = "*"
65
66
  wheel = "*"
66
- sphinx = "*"
67
+ sphinx = "^7.4.6"
67
68
  sphinx-autobuild = "*"
68
69
  sphinx_rtd_theme = "*"
69
70
 
@@ -29,7 +29,9 @@ __license__ = "MIT"
29
29
  __contributors__ = [
30
30
  "William Guilherme",
31
31
  ]
32
- __version__ = "0.6.1"
32
+ __version__ = "0.7.0"
33
33
 
34
+ from zscaler.zdx import ZDXClientHelper # noqa
34
35
  from zscaler.zia import ZIAClientHelper # noqa
35
36
  from zscaler.zpa import ZPAClientHelper # noqa
37
+ from zscaler.zcon import ZCONClientHelper # noqa
@@ -14,7 +14,7 @@ ZPA_BASE_URLS = {
14
14
 
15
15
  DEV_AUTH_URL = "https://authn1.dev.zpath.net/authn/v1/oauth/token"
16
16
 
17
- RETRYABLE_STATUS_CODES = {409, 429, 500, 502, 503, 504}
17
+ RETRYABLE_STATUS_CODES = {409, 412, 429, 500, 502, 503, 504}
18
18
  MAX_RETRIES = 5
19
19
  BACKOFF_FACTOR = 1
20
20
  BACKOFF_BASE_DURATION = 2
@@ -80,7 +80,10 @@ def snake_to_camel(name: str):
80
80
  "name_l10n_tag": "nameL10nTag",
81
81
  "surrogate_ip": "surrogateIP",
82
82
  "surrogate_ip_enforced_for_known_browsers": "surrogateIPEnforcedForKnownBrowsers",
83
+ "ec_vms": "ecVMs",
83
84
  "is_incomplete_dr_config": "isIncompleteDRConfig",
85
+ "ipv6_enabled": "ipV6Enabled",
86
+ "valid_ssl_certificate": "validSSLCertificate",
84
87
  "email_ids": "emailIds",
85
88
  "page_size": "pageSize",
86
89
  }
@@ -97,6 +100,22 @@ def recursive_snake_to_camel(data):
97
100
  return data
98
101
 
99
102
 
103
+ def convert_keys(data, direction="to_camel"):
104
+ converter = camel_to_snake if direction == "to_snake" else snake_to_camel
105
+
106
+ if isinstance(data, (list, BoxList)):
107
+ return [convert_keys(inner_dict, direction=direction) for inner_dict in data]
108
+ elif isinstance(data, (dict, Box)):
109
+ new_dict = {}
110
+ for k in data.keys():
111
+ v = data[k]
112
+ new_key = converter(k)
113
+ new_dict[new_key] = convert_keys(v, direction=direction) if isinstance(v, (dict, list)) else v
114
+ return new_dict
115
+ else:
116
+ return data
117
+
118
+
100
119
  def chunker(lst, n):
101
120
  """Yield successive n-sized chunks from lst."""
102
121
  for i in range(0, len(lst), n):
@@ -0,0 +1,486 @@
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 OF
10
+ # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11
+ # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12
+ # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13
+ # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14
+ # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15
+
16
+ import datetime
17
+ import logging
18
+ import os
19
+ import re
20
+ import time
21
+ import uuid
22
+ from time import sleep
23
+
24
+ import requests
25
+ from box import Box, BoxList
26
+
27
+ from zscaler import __version__
28
+ from zscaler.cache.no_op_cache import NoOpCache
29
+ from zscaler.cache.zscaler_cache import ZscalerCache
30
+ from zscaler.errors.http_error import HTTPError, ZscalerAPIError
31
+ from zscaler.exceptions.exceptions import HTTPException, ZscalerAPIException
32
+ from zscaler.logger import setup_logging
33
+ from zscaler.ratelimiter.ratelimiter import RateLimiter
34
+ from zscaler.user_agent import UserAgent
35
+ from zscaler.utils import (
36
+ convert_keys_to_snake,
37
+ dump_request,
38
+ dump_response,
39
+ format_json_response,
40
+ obfuscate_api_key,
41
+ retry_with_backoff,
42
+ )
43
+
44
+ from zscaler.zcon.client import ZCONClient
45
+ from zscaler.zcon.activation import ActivationAPI
46
+ from zscaler.zcon.admin_and_role_management import AdminAndRoleManagementAPI
47
+ from zscaler.zcon.provisioning import ProvisioningAPI
48
+ from zscaler.zcon.ecgroups import EcGroupAPI
49
+ from zscaler.zcon.locations import LocationAPI
50
+
51
+ # Setup the logger
52
+ setup_logging(logger_name="zscaler-sdk-python")
53
+ logger = logging.getLogger("zscaler-sdk-python")
54
+
55
+
56
+ class ZCONClientHelper(ZCONClient):
57
+ """
58
+ A Controller to access Endpoints in the ZCON API.
59
+
60
+ The ZCON object stores the session token and simplifies access to CRUD options within the ZCON platform.
61
+
62
+ Attributes:
63
+ api_key (str): The ZCON API key generated from the ZCON console.
64
+ username (str): The ZCON administrator username.
65
+ password (str): The ZCON administrator password.
66
+ cloud (str): The ZCON cloud for your tenancy, accepted values are:
67
+ """
68
+
69
+ _vendor = "Zscaler"
70
+ _product = "Zscaler CON Cloud"
71
+ _build = __version__
72
+ _env_base = "ZCON"
73
+ env_cloud = "zscaler"
74
+
75
+ def __init__(self, cloud=None, timeout=240, cache=None, fail_safe=False, **kw):
76
+ self.api_key = kw.get("api_key", os.getenv(f"{self._env_base}_API_KEY"))
77
+ self.username = kw.get("username", os.getenv(f"{self._env_base}_USERNAME"))
78
+ self.password = kw.get("password", os.getenv(f"{self._env_base}_PASSWORD"))
79
+ # The 'cloud' parameter should have precedence over environment variables
80
+ self.env_cloud = cloud or kw.get("cloud") or os.getenv(f"{self._env_base}_CLOUD")
81
+ if not self.env_cloud:
82
+ raise ValueError(
83
+ f"Cloud environment must be set via the 'cloud' argument or the {self._env_base}_CLOUD environment variable."
84
+ )
85
+
86
+ # URL construction
87
+ self.url = f"https://connector.{self.env_cloud}.net/api/v1"
88
+ self.conv_box = True
89
+ self.sandbox_token = kw.get("sandbox_token") or os.getenv(f"{self._env_base}_SANDBOX_TOKEN")
90
+ self.timeout = timeout
91
+ self.fail_safe = fail_safe
92
+ cache_enabled = os.environ.get("ZSCALER_CLIENT_CACHE_ENABLED", "true").lower() == "true"
93
+ if cache is None:
94
+ if cache_enabled:
95
+ ttl = int(os.environ.get("ZSCALER_CLIENT_CACHE_DEFAULT_TTL", 3600))
96
+ tti = int(os.environ.get("ZSCALER_CLIENT_CACHE_DEFAULT_TTI", 1800))
97
+ self.cache = ZscalerCache(ttl=ttl, tti=tti)
98
+ else:
99
+ self.cache = NoOpCache()
100
+ else:
101
+ self.cache = cache
102
+ # Initialize user-agent
103
+ ua = UserAgent()
104
+ self.user_agent = ua.get_user_agent_string()
105
+ # Initialize rate limiter
106
+ # You may want to adjust these parameters as per your rate limit configuration
107
+ self.rate_limiter = RateLimiter(
108
+ get_limit=2, # Adjust as per actual limit
109
+ post_put_delete_limit=2, # Adjust as per actual limit
110
+ get_freq=2, # Adjust as per actual frequency (in seconds)
111
+ post_put_delete_freq=2, # Adjust as per actual frequency (in seconds)
112
+ )
113
+ self.headers = {
114
+ "Content-Type": "application/json",
115
+ "Accept": "application/json",
116
+ "User-Agent": self.user_agent,
117
+ }
118
+ self.session_timeout_offset = datetime.timedelta(minutes=5)
119
+ self.session_refreshed = None
120
+ self.auth_details = None
121
+ self.session_id = None
122
+ self.authenticate()
123
+
124
+ def extractJSessionIDFromHeaders(self, header):
125
+ session_id_str = header.get("Set-Cookie", "")
126
+
127
+ if not session_id_str:
128
+ raise ValueError("no Set-Cookie header received")
129
+
130
+ regex = re.compile(r"JSESSIONID=(.*?);")
131
+ result = regex.search(session_id_str)
132
+
133
+ if not result:
134
+ raise ValueError("couldn't find JSESSIONID in header value")
135
+
136
+ return result.group(1)
137
+
138
+ def is_session_expired(self):
139
+ if self.auth_details is None:
140
+ return True
141
+ now = datetime.datetime.now()
142
+ if self.auth_details["passwordExpiryTime"] > 0 and (
143
+ self.session_refreshed + datetime.timedelta(seconds=-self.session_timeout_offset) < now
144
+ ):
145
+ return True
146
+ return False
147
+
148
+ @retry_with_backoff(retries=5)
149
+ def authenticate(self) -> Box:
150
+ """
151
+ Creates a ZCON authentication session.
152
+ """
153
+ api_key_chars = list(self.api_key)
154
+ api_obf = obfuscate_api_key(api_key_chars)
155
+
156
+ payload = {
157
+ "apiKey": api_obf["key"],
158
+ "username": self.username,
159
+ "password": self.password,
160
+ "timestamp": api_obf["timestamp"],
161
+ }
162
+ resp = requests.request(
163
+ "POST",
164
+ self.url + "/auth",
165
+ json=payload,
166
+ headers=self.headers,
167
+ timeout=self.timeout,
168
+ )
169
+ if resp.status_code > 299:
170
+ return resp
171
+ self.session_refreshed = datetime.datetime.now()
172
+ self.session_id = self.extractJSessionIDFromHeaders(resp.headers)
173
+ self.auth_details = resp.json()
174
+ return resp
175
+
176
+ def deauthenticate(self):
177
+ """
178
+ Ends the ZCON authentication session.
179
+ """
180
+ logout_url = self.url + "/auth"
181
+
182
+ headers = self.headers.copy()
183
+ headers.update({"Cookie": f"JSESSIONID={self.session_id}"})
184
+
185
+ try:
186
+ response = requests.delete(logout_url, headers=headers, timeout=self.timeout)
187
+ if response.status_code == 204:
188
+ self.session_id = None
189
+ self.auth_details = None
190
+ return True
191
+ else:
192
+ return False
193
+ except requests.RequestException as e:
194
+ return False
195
+
196
+ def __enter__(self):
197
+ if self.is_session_expired():
198
+ resp = self.authenticate()
199
+ if resp.status_code > 299:
200
+ raise Exception(f"Error auth:{resp.json()}")
201
+ return self
202
+
203
+ def __exit__(self, exc_type, exc_val, exc_tb):
204
+ logger.debug("deauthenticating...")
205
+ self.deauthenticate()
206
+
207
+ def send(self, method, path, json=None, params=None, data=None, headers=None):
208
+ """
209
+ Send a request to the ZCON API.
210
+
211
+ Parameters:
212
+ - method (str): The HTTP method.
213
+ - path (str): API endpoint path.
214
+ - json (dict, optional): Request payload. Defaults to None.
215
+ Returns:
216
+ - Response: Response object from the request.
217
+ """
218
+ api = self.url
219
+ url = f"{api}/{path.lstrip('/')}"
220
+ start_time = time.time()
221
+ # Update headers to include the user agent
222
+ headers_with_user_agent = self.headers.copy()
223
+ headers_with_user_agent["User-Agent"] = self.user_agent
224
+ # Generate a unique UUID for this request
225
+ request_uuid = uuid.uuid4()
226
+ if headers is not None:
227
+ headers_with_user_agent.update(headers)
228
+ dump_request(
229
+ logger,
230
+ url,
231
+ method,
232
+ json,
233
+ params,
234
+ headers_with_user_agent,
235
+ request_uuid,
236
+ body=True,
237
+ )
238
+ # Check cache before sending request
239
+ cache_key = self.cache.create_key(url, params)
240
+ if method == "GET" and self.cache.contains(cache_key):
241
+ resp = self.cache.get(cache_key)
242
+ dump_response(
243
+ logger=logger,
244
+ url=url,
245
+ method=method,
246
+ params=params,
247
+ resp=resp,
248
+ request_uuid=request_uuid,
249
+ start_time=start_time,
250
+ from_cache=True,
251
+ )
252
+ return resp
253
+
254
+ attempts = 0
255
+ while attempts < 5: # Trying a maximum of 5 times
256
+ try:
257
+ # If the token is None or expired, fetch a new token
258
+ if self.is_session_expired():
259
+ logger.warning("The provided sesion expired. Refreshing...")
260
+ self.authenticate()
261
+ resp = requests.request(
262
+ method=method,
263
+ url=url,
264
+ json=json,
265
+ data=data,
266
+ params=params,
267
+ headers=headers_with_user_agent,
268
+ timeout=self.timeout,
269
+ cookies={"JSESSIONID": self.session_id},
270
+ )
271
+ dump_response(
272
+ logger=logger,
273
+ url=url,
274
+ params=params,
275
+ method=method,
276
+ resp=resp,
277
+ request_uuid=request_uuid,
278
+ start_time=start_time,
279
+ )
280
+ if resp.status_code == 429: # HTTP Status code 429 indicates "Too Many Requests"
281
+ sleep_time = int(
282
+ resp.headers.get("Retry-After", 2)
283
+ ) # Default to 60 seconds if 'Retry-After' header is missing
284
+ logger.warning(f"Rate limit exceeded. Retrying in {sleep_time} seconds.")
285
+ sleep(sleep_time)
286
+ attempts += 1
287
+ continue
288
+ else:
289
+ break
290
+ except requests.RequestException as e:
291
+ if attempts == 4: # If it's the last attempt, raise the exception
292
+ logger.error(f"Failed to send {method} request to {url} after 5 attempts. Error: {str(e)}")
293
+ raise e
294
+ else:
295
+ logger.warning(f"Failed to send {method} request to {url}. Retrying... Error: {str(e)}")
296
+ attempts += 1
297
+ sleep(5) # Sleep for 5 seconds before retrying
298
+
299
+ # If Non-GET call, clear the
300
+ if method != "GET":
301
+ self.cache.delete(cache_key)
302
+
303
+ # Detailed logging for request and response
304
+ try:
305
+ response_data = resp.json()
306
+ except ValueError: # Using ValueError for JSON decoding errors
307
+ response_data = resp.text
308
+ # check if call was succesful
309
+ if 200 > resp.status_code or resp.status_code > 299:
310
+ # create errors
311
+ try:
312
+ error = ZscalerAPIError(url, resp, response_data)
313
+ if self.fail_safe:
314
+ raise ZscalerAPIException(url, resp, response_data)
315
+ except ZscalerAPIException:
316
+ raise
317
+ except Exception:
318
+ error = HTTPError(url, resp, response_data)
319
+ if self.fail_safe:
320
+ logger.error(response_data)
321
+ raise HTTPException(url, resp, response_data)
322
+ logger.error(error)
323
+ # Cache the response if it's a successful GET request
324
+ if method == "GET" and resp.status_code == 200:
325
+ self.cache.add(cache_key, resp)
326
+ return resp
327
+
328
+ def get(self, path, json=None, params=None):
329
+ """
330
+ Send a GET request to the ZCON API.
331
+
332
+ Parameters:
333
+ - path (str): API endpoint path.
334
+ - data (dict, optional): Request payload. Defaults to None.
335
+ Returns:
336
+ - Response: Response object from the request.
337
+ """
338
+
339
+ should_wait, delay = self.rate_limiter.wait("GET")
340
+ if should_wait:
341
+ time.sleep(delay)
342
+ response = self.send("GET", path, json, params)
343
+ if not response.ok:
344
+ raise Exception(f"GET request failed with status {response.status_code}: {response.json()}")
345
+ return format_json_response(response, box_attrs=dict())
346
+
347
+ def post(self, path, json=None, params=None, data=None, headers=None):
348
+ should_wait, delay = self.rate_limiter.wait("POST")
349
+ if should_wait:
350
+ time.sleep(delay)
351
+ response = self.send("POST", path, json, params, data=data, headers=headers)
352
+ if not response.ok:
353
+ raise Exception(f"POST request failed with status {response.status_code}: {response.json()}")
354
+ return format_json_response(response, box_attrs=dict())
355
+
356
+ def put(self, path, json=None, params=None):
357
+ should_wait, delay = self.rate_limiter.wait("PUT")
358
+ if should_wait:
359
+ time.sleep(delay)
360
+ response = self.send("PUT", path, json, params)
361
+
362
+ # Handle 204 No Content separately
363
+ if response.status_code == 204:
364
+ return response
365
+
366
+ # Handle 200 OK with content
367
+ if response.status_code == 200 and response.headers.get("content-type", "").startswith("application/json"):
368
+ return format_json_response(response, box_attrs=dict())
369
+
370
+ # Raise an exception for any other unexpected status codes
371
+ if not response.ok:
372
+ raise Exception(f"PUT request failed with status {response.status_code}: {response.json()}")
373
+
374
+ return response
375
+
376
+ def delete(self, path, json=None, params=None):
377
+ should_wait, delay = self.rate_limiter.wait("DELETE")
378
+ if should_wait:
379
+ time.sleep(delay)
380
+ response = self.send("DELETE", path, json, params)
381
+ if not response.ok:
382
+ raise Exception(f"DELETE request failed with status {response.status_code}: {response.json()}")
383
+ # Since DELETE typically returns 204 No Content, we don't need to format the response.
384
+ return response
385
+
386
+ ERROR_MESSAGES = {
387
+ "UNEXPECTED_STATUS": "Unexpected status code {status_code} received for page {page}.",
388
+ "MISSING_DATA_KEY": "The key '{data_key_name}' was not found in the response for page {page}.",
389
+ "EMPTY_RESULTS": "No results found for page {page}.",
390
+ }
391
+
392
+ def get_paginated_data(self, path=None, data_key_name=None, data_per_page=5, expected_status_code=200):
393
+ """
394
+ Fetch paginated data from the ZCON API.
395
+ ...
396
+
397
+ Returns:
398
+ - list: List of fetched items.
399
+ - str: Error message, if any occurred.
400
+ """
401
+
402
+ page = 1
403
+ ret_data = []
404
+ error_message = None
405
+
406
+ while True:
407
+ required_url = f"{path}"
408
+ should_wait, delay = self.rate_limiter.wait("GET")
409
+ if should_wait:
410
+ time.sleep(delay)
411
+
412
+ # Now proceed with sending the request
413
+ response = self.send(
414
+ method="GET",
415
+ path=required_url,
416
+ params={"page": page, "pageSize": data_per_page},
417
+ )
418
+
419
+ if response.status_code != expected_status_code:
420
+ error_message = self.ERROR_MESSAGES["UNEXPECTED_STATUS"].format(status_code=response.status_code, page=page)
421
+ logger.error(error_message)
422
+ break
423
+ data_json = response.json()
424
+ if isinstance(data_json, list):
425
+ data = data_json
426
+ else:
427
+ data = data_json.get(data_key_name)
428
+
429
+ if data is None:
430
+ error_message = self.ERROR_MESSAGES["MISSING_DATA_KEY"].format(data_key_name=data_key_name, page=page)
431
+ logger.error(error_message)
432
+ break
433
+
434
+ if not data: # Checks for empty data
435
+ logger.info(self.ERROR_MESSAGES["EMPTY_RESULTS"].format(page=page))
436
+ break
437
+
438
+ ret_data.extend(convert_keys_to_snake(data))
439
+
440
+ # Check for more pages
441
+ if len(data) == 0 or isinstance(data_json, dict) and int(response.json().get("totalPages")) <= page + 1:
442
+ break
443
+
444
+ page += 1
445
+
446
+ return BoxList(ret_data), error_message
447
+
448
+ @property
449
+ def activation(self):
450
+ """
451
+ The interface object for the :ref:`ZCON Activation Service <zcon-activation>`.
452
+
453
+ """
454
+ return ActivationAPI(self)
455
+
456
+ @property
457
+ def admin_and_role_management(self):
458
+ """
459
+ The interface object for the :ref:`ZCON Admin and Role Management interface <zcon-admin_and_role_management>`.
460
+
461
+ """
462
+ return AdminAndRoleManagementAPI(self)
463
+
464
+ @property
465
+ def ecgroups(self):
466
+ """
467
+ The interface object for the :ref:`ZCON EC Group Service <zcon-ecgroups>`.
468
+
469
+ """
470
+ return EcGroupAPI(self)
471
+
472
+ @property
473
+ def locations(self):
474
+ """
475
+ The interface object for the :ref:`ZCON Location Service <zcon-locations>`.
476
+
477
+ """
478
+ return LocationAPI(self)
479
+
480
+ @property
481
+ def provisioning(self):
482
+ """
483
+ The interface object for the :ref:`ZCON API Key Provisioning Service <zcon-provisioning>`.
484
+
485
+ """
486
+ return ProvisioningAPI(self)
@@ -0,0 +1,62 @@
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 OF
10
+ # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11
+ # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12
+ # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13
+ # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14
+ # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15
+
16
+ from box import Box
17
+ from zscaler.zcon.client import ZCONClient
18
+
19
+
20
+ class ActivationAPI:
21
+ def __init__(self, client: ZCONClient):
22
+ self.rest = client
23
+
24
+ def activate(self, force: bool = False) -> Box:
25
+ """
26
+ Activate the configuration.
27
+
28
+ Args:
29
+ force (bool): If set to True, forces the activation. Default is False.
30
+
31
+ Returns:
32
+ :obj:`Box`: The status code of the operation.
33
+
34
+ Examples:
35
+ Activate the configuration without forcing::
36
+
37
+ zcon.config.activate()
38
+
39
+ Forcefully activate the configuration::
40
+
41
+ zcon.config.activate(force=True)
42
+
43
+ """
44
+ if force:
45
+ return self.rest.put("ecAdminActivateStatus/forcedActivate")
46
+ else:
47
+ return self.rest.put("ecAdminActivateStatus/activate")
48
+
49
+ def get_status(self):
50
+ """
51
+ Get the status of the configuration.
52
+
53
+ Returns:
54
+ :obj:`Box`: The status of the configuration.
55
+
56
+ Examples:
57
+ Get the status of the configuration::
58
+
59
+ print(zcon.config.get_status())
60
+
61
+ """
62
+ return self.rest.get("ecAdminActivateStatus")