zscaler-sdk-python 0.8.0__tar.gz → 0.9.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.
- {zscaler_sdk_python-0.8.0 → zscaler_sdk_python-0.9.1}/PKG-INFO +2 -2
- {zscaler_sdk_python-0.8.0 → zscaler_sdk_python-0.9.1}/pyproject.toml +2 -2
- {zscaler_sdk_python-0.8.0 → zscaler_sdk_python-0.9.1}/zscaler/__init__.py +2 -1
- {zscaler_sdk_python-0.8.0 → zscaler_sdk_python-0.9.1}/zscaler/utils.py +1 -5
- zscaler_sdk_python-0.9.1/zscaler/zcc/__init__.py +265 -0
- zscaler_sdk_python-0.9.1/zscaler/zcc/client.py +65 -0
- zscaler_sdk_python-0.9.1/zscaler/zcc/devices.py +220 -0
- zscaler_sdk_python-0.9.1/zscaler/zcc/secrets.py +66 -0
- {zscaler_sdk_python-0.8.0 → zscaler_sdk_python-0.9.1}/zscaler/zcon/__init__.py +1 -1
- {zscaler_sdk_python-0.8.0 → zscaler_sdk_python-0.9.1}/zscaler/zia/cloudappcontrol.py +21 -4
- {zscaler_sdk_python-0.8.0 → zscaler_sdk_python-0.9.1}/zscaler/zia/dlp.py +7 -13
- {zscaler_sdk_python-0.8.0 → zscaler_sdk_python-0.9.1}/zscaler/zia/url_categories.py +1 -2
- {zscaler_sdk_python-0.8.0 → zscaler_sdk_python-0.9.1}/zscaler/zpa/segment_groups.py +43 -0
- {zscaler_sdk_python-0.8.0 → zscaler_sdk_python-0.9.1}/LICENSE.md +0 -0
- {zscaler_sdk_python-0.8.0 → zscaler_sdk_python-0.9.1}/README.md +0 -0
- {zscaler_sdk_python-0.8.0 → zscaler_sdk_python-0.9.1}/zscaler/cache/__init__.py +0 -0
- {zscaler_sdk_python-0.8.0 → zscaler_sdk_python-0.9.1}/zscaler/cache/cache.py +0 -0
- {zscaler_sdk_python-0.8.0 → zscaler_sdk_python-0.9.1}/zscaler/cache/no_op_cache.py +0 -0
- {zscaler_sdk_python-0.8.0 → zscaler_sdk_python-0.9.1}/zscaler/cache/zscaler_cache.py +0 -0
- {zscaler_sdk_python-0.8.0 → zscaler_sdk_python-0.9.1}/zscaler/constants.py +0 -0
- {zscaler_sdk_python-0.8.0 → zscaler_sdk_python-0.9.1}/zscaler/errors/__init__.py +0 -0
- {zscaler_sdk_python-0.8.0 → zscaler_sdk_python-0.9.1}/zscaler/errors/error.py +0 -0
- {zscaler_sdk_python-0.8.0 → zscaler_sdk_python-0.9.1}/zscaler/errors/http_error.py +0 -0
- {zscaler_sdk_python-0.8.0 → zscaler_sdk_python-0.9.1}/zscaler/errors/zscaler_api_error.py +0 -0
- {zscaler_sdk_python-0.8.0 → zscaler_sdk_python-0.9.1}/zscaler/exceptions/__init__.py +0 -0
- {zscaler_sdk_python-0.8.0 → zscaler_sdk_python-0.9.1}/zscaler/exceptions/exceptions.py +0 -0
- {zscaler_sdk_python-0.8.0 → zscaler_sdk_python-0.9.1}/zscaler/logger.py +0 -0
- {zscaler_sdk_python-0.8.0 → zscaler_sdk_python-0.9.1}/zscaler/ratelimiter/__init__.py +0 -0
- {zscaler_sdk_python-0.8.0 → zscaler_sdk_python-0.9.1}/zscaler/ratelimiter/ratelimiter.py +0 -0
- {zscaler_sdk_python-0.8.0 → zscaler_sdk_python-0.9.1}/zscaler/user_agent.py +0 -0
- {zscaler_sdk_python-0.8.0 → zscaler_sdk_python-0.9.1}/zscaler/zcon/activation.py +0 -0
- {zscaler_sdk_python-0.8.0 → zscaler_sdk_python-0.9.1}/zscaler/zcon/admin_and_role_management.py +0 -0
- {zscaler_sdk_python-0.8.0 → zscaler_sdk_python-0.9.1}/zscaler/zcon/client.py +0 -0
- {zscaler_sdk_python-0.8.0 → zscaler_sdk_python-0.9.1}/zscaler/zcon/ecgroups.py +0 -0
- {zscaler_sdk_python-0.8.0 → zscaler_sdk_python-0.9.1}/zscaler/zcon/locations.py +0 -0
- {zscaler_sdk_python-0.8.0 → zscaler_sdk_python-0.9.1}/zscaler/zcon/provisioning.py +0 -0
- {zscaler_sdk_python-0.8.0 → zscaler_sdk_python-0.9.1}/zscaler/zdx/__init__.py +0 -0
- {zscaler_sdk_python-0.8.0 → zscaler_sdk_python-0.9.1}/zscaler/zdx/admin.py +0 -0
- {zscaler_sdk_python-0.8.0 → zscaler_sdk_python-0.9.1}/zscaler/zdx/alerts.py +0 -0
- {zscaler_sdk_python-0.8.0 → zscaler_sdk_python-0.9.1}/zscaler/zdx/apps.py +0 -0
- {zscaler_sdk_python-0.8.0 → zscaler_sdk_python-0.9.1}/zscaler/zdx/devices.py +0 -0
- {zscaler_sdk_python-0.8.0 → zscaler_sdk_python-0.9.1}/zscaler/zdx/filters.py +0 -0
- {zscaler_sdk_python-0.8.0 → zscaler_sdk_python-0.9.1}/zscaler/zdx/inventory.py +0 -0
- {zscaler_sdk_python-0.8.0 → zscaler_sdk_python-0.9.1}/zscaler/zdx/troubleshooting.py +0 -0
- {zscaler_sdk_python-0.8.0 → zscaler_sdk_python-0.9.1}/zscaler/zdx/users.py +0 -0
- {zscaler_sdk_python-0.8.0 → zscaler_sdk_python-0.9.1}/zscaler/zdx/zdx_client.py +0 -0
- {zscaler_sdk_python-0.8.0 → zscaler_sdk_python-0.9.1}/zscaler/zia/__init__.py +0 -0
- {zscaler_sdk_python-0.8.0 → zscaler_sdk_python-0.9.1}/zscaler/zia/activate.py +0 -0
- {zscaler_sdk_python-0.8.0 → zscaler_sdk_python-0.9.1}/zscaler/zia/admin_and_role_management.py +0 -0
- {zscaler_sdk_python-0.8.0 → zscaler_sdk_python-0.9.1}/zscaler/zia/apptotal.py +0 -0
- {zscaler_sdk_python-0.8.0 → zscaler_sdk_python-0.9.1}/zscaler/zia/audit_logs.py +0 -0
- {zscaler_sdk_python-0.8.0 → zscaler_sdk_python-0.9.1}/zscaler/zia/authentication_settings.py +0 -0
- {zscaler_sdk_python-0.8.0 → zscaler_sdk_python-0.9.1}/zscaler/zia/client.py +0 -0
- {zscaler_sdk_python-0.8.0 → zscaler_sdk_python-0.9.1}/zscaler/zia/cloud_apps.py +0 -0
- {zscaler_sdk_python-0.8.0 → zscaler_sdk_python-0.9.1}/zscaler/zia/device_management.py +0 -0
- {zscaler_sdk_python-0.8.0 → zscaler_sdk_python-0.9.1}/zscaler/zia/errors.py +0 -0
- {zscaler_sdk_python-0.8.0 → zscaler_sdk_python-0.9.1}/zscaler/zia/firewall.py +0 -0
- {zscaler_sdk_python-0.8.0 → zscaler_sdk_python-0.9.1}/zscaler/zia/forwarding_control.py +0 -0
- {zscaler_sdk_python-0.8.0 → zscaler_sdk_python-0.9.1}/zscaler/zia/isolation_profile.py +0 -0
- {zscaler_sdk_python-0.8.0 → zscaler_sdk_python-0.9.1}/zscaler/zia/labels.py +0 -0
- {zscaler_sdk_python-0.8.0 → zscaler_sdk_python-0.9.1}/zscaler/zia/locations.py +0 -0
- {zscaler_sdk_python-0.8.0 → zscaler_sdk_python-0.9.1}/zscaler/zia/sandbox.py +0 -0
- {zscaler_sdk_python-0.8.0 → zscaler_sdk_python-0.9.1}/zscaler/zia/security.py +0 -0
- {zscaler_sdk_python-0.8.0 → zscaler_sdk_python-0.9.1}/zscaler/zia/ssl_inspection.py +0 -0
- {zscaler_sdk_python-0.8.0 → zscaler_sdk_python-0.9.1}/zscaler/zia/traffic.py +0 -0
- {zscaler_sdk_python-0.8.0 → zscaler_sdk_python-0.9.1}/zscaler/zia/url_filtering.py +0 -0
- {zscaler_sdk_python-0.8.0 → zscaler_sdk_python-0.9.1}/zscaler/zia/users.py +0 -0
- {zscaler_sdk_python-0.8.0 → zscaler_sdk_python-0.9.1}/zscaler/zia/web_dlp.py +0 -0
- {zscaler_sdk_python-0.8.0 → zscaler_sdk_python-0.9.1}/zscaler/zia/workload_groups.py +0 -0
- {zscaler_sdk_python-0.8.0 → zscaler_sdk_python-0.9.1}/zscaler/zia/zpa_gateway.py +0 -0
- {zscaler_sdk_python-0.8.0 → zscaler_sdk_python-0.9.1}/zscaler/zpa/README.md +0 -0
- {zscaler_sdk_python-0.8.0 → zscaler_sdk_python-0.9.1}/zscaler/zpa/__init__.py +0 -0
- {zscaler_sdk_python-0.8.0 → zscaler_sdk_python-0.9.1}/zscaler/zpa/app_segments.py +0 -0
- {zscaler_sdk_python-0.8.0 → zscaler_sdk_python-0.9.1}/zscaler/zpa/app_segments_inspection.py +0 -0
- {zscaler_sdk_python-0.8.0 → zscaler_sdk_python-0.9.1}/zscaler/zpa/app_segments_pra.py +0 -0
- {zscaler_sdk_python-0.8.0 → zscaler_sdk_python-0.9.1}/zscaler/zpa/authdomains.py +0 -0
- {zscaler_sdk_python-0.8.0 → zscaler_sdk_python-0.9.1}/zscaler/zpa/certificates.py +0 -0
- {zscaler_sdk_python-0.8.0 → zscaler_sdk_python-0.9.1}/zscaler/zpa/client.py +0 -0
- {zscaler_sdk_python-0.8.0 → zscaler_sdk_python-0.9.1}/zscaler/zpa/cloud_connector_groups.py +0 -0
- {zscaler_sdk_python-0.8.0 → zscaler_sdk_python-0.9.1}/zscaler/zpa/connectors.py +0 -0
- {zscaler_sdk_python-0.8.0 → zscaler_sdk_python-0.9.1}/zscaler/zpa/emergency_access.py +0 -0
- {zscaler_sdk_python-0.8.0 → zscaler_sdk_python-0.9.1}/zscaler/zpa/errors.py +0 -0
- {zscaler_sdk_python-0.8.0 → zscaler_sdk_python-0.9.1}/zscaler/zpa/idp.py +0 -0
- {zscaler_sdk_python-0.8.0 → zscaler_sdk_python-0.9.1}/zscaler/zpa/inspection.py +0 -0
- {zscaler_sdk_python-0.8.0 → zscaler_sdk_python-0.9.1}/zscaler/zpa/isolation.py +0 -0
- {zscaler_sdk_python-0.8.0 → zscaler_sdk_python-0.9.1}/zscaler/zpa/lss.py +0 -0
- {zscaler_sdk_python-0.8.0 → zscaler_sdk_python-0.9.1}/zscaler/zpa/machine_groups.py +0 -0
- {zscaler_sdk_python-0.8.0 → zscaler_sdk_python-0.9.1}/zscaler/zpa/microtenants.py +0 -0
- {zscaler_sdk_python-0.8.0 → zscaler_sdk_python-0.9.1}/zscaler/zpa/policies.py +0 -0
- {zscaler_sdk_python-0.8.0 → zscaler_sdk_python-0.9.1}/zscaler/zpa/posture_profiles.py +0 -0
- {zscaler_sdk_python-0.8.0 → zscaler_sdk_python-0.9.1}/zscaler/zpa/privileged_remote_access.py +0 -0
- {zscaler_sdk_python-0.8.0 → zscaler_sdk_python-0.9.1}/zscaler/zpa/provisioning.py +0 -0
- {zscaler_sdk_python-0.8.0 → zscaler_sdk_python-0.9.1}/zscaler/zpa/saml_attributes.py +0 -0
- {zscaler_sdk_python-0.8.0 → zscaler_sdk_python-0.9.1}/zscaler/zpa/scim_attributes.py +0 -0
- {zscaler_sdk_python-0.8.0 → zscaler_sdk_python-0.9.1}/zscaler/zpa/scim_groups.py +0 -0
- {zscaler_sdk_python-0.8.0 → zscaler_sdk_python-0.9.1}/zscaler/zpa/server_groups.py +0 -0
- {zscaler_sdk_python-0.8.0 → zscaler_sdk_python-0.9.1}/zscaler/zpa/servers.py +0 -0
- {zscaler_sdk_python-0.8.0 → zscaler_sdk_python-0.9.1}/zscaler/zpa/service_edges.py +0 -0
- {zscaler_sdk_python-0.8.0 → zscaler_sdk_python-0.9.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.
|
|
3
|
+
Version: 0.9.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
|
|
@@ -34,7 +34,7 @@ Requires-Dist: idna
|
|
|
34
34
|
Requires-Dist: okta (>=2.9.7)
|
|
35
35
|
Requires-Dist: pycryptodomex
|
|
36
36
|
Requires-Dist: pydash (>=8.0.3) ; extra == "dev"
|
|
37
|
-
Requires-Dist: python-box (>=7.2.0
|
|
37
|
+
Requires-Dist: python-box (>=7.2.0)
|
|
38
38
|
Requires-Dist: python-dateutil
|
|
39
39
|
Requires-Dist: pytz
|
|
40
40
|
Requires-Dist: pyyaml
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "zscaler-sdk-python"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.9.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,7 +33,7 @@ arrow = "*"
|
|
|
33
33
|
certifi = ">=2024.2.2"
|
|
34
34
|
charset-normalizer = "*"
|
|
35
35
|
idna = "*"
|
|
36
|
-
python-box = "
|
|
36
|
+
python-box = ">=7.2.0"
|
|
37
37
|
python-dateutil = "*"
|
|
38
38
|
requests = ">=2.32.3"
|
|
39
39
|
responses = ">=0.25.3"
|
|
@@ -29,9 +29,10 @@ __license__ = "MIT"
|
|
|
29
29
|
__contributors__ = [
|
|
30
30
|
"William Guilherme",
|
|
31
31
|
]
|
|
32
|
-
__version__ = "0.
|
|
32
|
+
__version__ = "0.9.1"
|
|
33
33
|
|
|
34
34
|
from zscaler.zdx import ZDXClientHelper # noqa
|
|
35
35
|
from zscaler.zia import ZIAClientHelper # noqa
|
|
36
36
|
from zscaler.zpa import ZPAClientHelper # noqa
|
|
37
37
|
from zscaler.zcon import ZCONClientHelper # noqa
|
|
38
|
+
from zscaler.zcc import ZCCClientHelper # noqa
|
|
@@ -184,11 +184,7 @@ def transform_common_id_fields(id_groups: list, kwargs: dict, payload: dict):
|
|
|
184
184
|
elif key == "cbi_profile":
|
|
185
185
|
# Special handling for cbi_profile
|
|
186
186
|
if isinstance(value, dict) and all(k in value for k in ["id", "name", "url"]):
|
|
187
|
-
payload[payload_key] = {
|
|
188
|
-
"id": value["id"],
|
|
189
|
-
"name": value["name"],
|
|
190
|
-
"url": value["url"]
|
|
191
|
-
}
|
|
187
|
+
payload[payload_key] = {"id": value["id"], "name": value["name"], "url": value["url"]}
|
|
192
188
|
elif key == "cloud_app_risk_profile":
|
|
193
189
|
# Special handling for cloudAppRiskProfile
|
|
194
190
|
if isinstance(value, dict) and "id" in value:
|
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import os
|
|
3
|
+
import urllib.parse
|
|
4
|
+
import uuid
|
|
5
|
+
import time
|
|
6
|
+
import requests
|
|
7
|
+
from datetime import datetime, timedelta
|
|
8
|
+
|
|
9
|
+
from zscaler import __version__
|
|
10
|
+
from zscaler.user_agent import UserAgent
|
|
11
|
+
from zscaler.utils import (
|
|
12
|
+
dump_request,
|
|
13
|
+
dump_response,
|
|
14
|
+
format_json_response,
|
|
15
|
+
is_token_expired,
|
|
16
|
+
retry_with_backoff,
|
|
17
|
+
)
|
|
18
|
+
from zscaler.logger import setup_logging
|
|
19
|
+
from .devices import DevicesAPI
|
|
20
|
+
from .secrets import SecretsAPI
|
|
21
|
+
|
|
22
|
+
from zscaler.zcc.client import ZCCClient
|
|
23
|
+
|
|
24
|
+
# Setup the logger
|
|
25
|
+
setup_logging(logger_name="zscaler-sdk-python")
|
|
26
|
+
logger = logging.getLogger("zscaler-sdk-python")
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class ZCCClientHelper(ZCCClient):
|
|
30
|
+
"""
|
|
31
|
+
A Controller to access Endpoints in the Zscaler Mobile Admin Portal API.
|
|
32
|
+
|
|
33
|
+
The ZCC object stores the session token and simplifies access to CRUD options within the ZCC Portal.
|
|
34
|
+
|
|
35
|
+
Attributes:
|
|
36
|
+
client_id (str): The ZCC Client ID generated from the ZCC Portal.
|
|
37
|
+
client_secret (str): The ZCC Client Secret generated from the ZCC Portal.
|
|
38
|
+
cloud (str): The Zscaler cloud for your tenancy, accepted values are:
|
|
39
|
+
|
|
40
|
+
* ``zscaler``
|
|
41
|
+
* ``zscalerone``
|
|
42
|
+
* ``zscalertwo``
|
|
43
|
+
* ``zscalerthree``
|
|
44
|
+
* ``zscloud``
|
|
45
|
+
* ``zscalerbeta``
|
|
46
|
+
override_url (str):
|
|
47
|
+
If supplied, this attribute can be used to override the production URL that is derived
|
|
48
|
+
from supplying the `cloud` attribute. Use this attribute if you have a non-standard tenant URL
|
|
49
|
+
(e.g. internal test instance etc). When using this attribute, there is no need to supply the `cloud`
|
|
50
|
+
attribute. The override URL will be prepended to the API endpoint suffixes. The protocol must be included
|
|
51
|
+
i.e. http:// or https://.
|
|
52
|
+
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
_vendor = "Zscaler"
|
|
56
|
+
_product = "Zscaler Mobile Admin Portal"
|
|
57
|
+
_backoff = 3
|
|
58
|
+
_build = __version__
|
|
59
|
+
_env_base = "ZCC"
|
|
60
|
+
_env_cloud = "zscaler"
|
|
61
|
+
|
|
62
|
+
RATE_LIMIT = 100 # 100 API calls per hour
|
|
63
|
+
DOWNLOAD_DEVICES_LIMIT = 3 # 3 calls per day
|
|
64
|
+
RATE_LIMIT_RESET_TIME = timedelta(hours=1)
|
|
65
|
+
DOWNLOAD_DEVICES_RESET_TIME = timedelta(days=1)
|
|
66
|
+
|
|
67
|
+
def __init__(self, **kw):
|
|
68
|
+
self._apikey = kw.get("apikey", os.getenv(f"{self._env_base}_CLIENT_ID"))
|
|
69
|
+
self._secret_key = kw.get("secret_key", os.getenv(f"{self._env_base}_CLIENT_SECRET"))
|
|
70
|
+
self._env_cloud = (
|
|
71
|
+
os.getenv(f"{self._env_base}_CLOUD") if os.getenv(f"{self._env_base}_CLOUD") is not None else kw.get("cloud")
|
|
72
|
+
)
|
|
73
|
+
self.login_url = f"https://api-mobile.{self._env_cloud}.net/papi/auth/v1/login"
|
|
74
|
+
self.url = f"https://api-mobile.{self._env_cloud}.net/papi/public/v1"
|
|
75
|
+
|
|
76
|
+
self.user_agent = UserAgent().get_user_agent_string() # Ensure this returns a string
|
|
77
|
+
self.auth_token = None
|
|
78
|
+
self.headers = {}
|
|
79
|
+
self.refreshToken()
|
|
80
|
+
|
|
81
|
+
# Initialize rate limit tracking
|
|
82
|
+
self.last_request_time = datetime.utcnow()
|
|
83
|
+
self.request_count = 0
|
|
84
|
+
|
|
85
|
+
# Track specific download devices endpoint usage
|
|
86
|
+
self.download_devices_count = 0
|
|
87
|
+
self.download_devices_last_reset = datetime.utcnow()
|
|
88
|
+
|
|
89
|
+
def __enter__(self):
|
|
90
|
+
self.refreshToken()
|
|
91
|
+
return self
|
|
92
|
+
|
|
93
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
94
|
+
logger.debug("Deauthenticating...")
|
|
95
|
+
|
|
96
|
+
def refreshToken(self):
|
|
97
|
+
if not self.auth_token or is_token_expired(self.auth_token):
|
|
98
|
+
response = self.login()
|
|
99
|
+
if response is None or response.status_code > 299 or not response.json():
|
|
100
|
+
logger.error("Failed to login using provided credentials, response: %s", response)
|
|
101
|
+
raise Exception("Failed to login using provided credentials.")
|
|
102
|
+
self.auth_token = response.json().get("jwtToken")
|
|
103
|
+
self.headers = {
|
|
104
|
+
"Content-Type": "application/json",
|
|
105
|
+
"Accept": "*/*",
|
|
106
|
+
"auth-token": f"{self.auth_token}",
|
|
107
|
+
"User-Agent": self.user_agent,
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
@retry_with_backoff(retries=5)
|
|
111
|
+
def login(self):
|
|
112
|
+
data = {"apiKey": self._apikey, "secretKey": self._secret_key}
|
|
113
|
+
headers = {
|
|
114
|
+
"Content-Type": "application/json",
|
|
115
|
+
"Accept": "*/*",
|
|
116
|
+
"User-Agent": self.user_agent,
|
|
117
|
+
}
|
|
118
|
+
try:
|
|
119
|
+
url = self.login_url
|
|
120
|
+
resp = requests.post(url, json=data, headers=headers)
|
|
121
|
+
logger.info("Login attempt with status: %d", resp.status_code)
|
|
122
|
+
return resp
|
|
123
|
+
except Exception as e:
|
|
124
|
+
logger.error("Login failed due to an exception: %s", str(e))
|
|
125
|
+
return None
|
|
126
|
+
|
|
127
|
+
def check_rate_limit(self, path):
|
|
128
|
+
"""
|
|
129
|
+
Checks the rate limit and adjusts the request timing accordingly.
|
|
130
|
+
"""
|
|
131
|
+
current_time = datetime.utcnow()
|
|
132
|
+
time_since_last_request = current_time - self.last_request_time
|
|
133
|
+
|
|
134
|
+
# Reset the rate limit counter if the reset time has passed
|
|
135
|
+
if time_since_last_request >= self.RATE_LIMIT_RESET_TIME:
|
|
136
|
+
self.request_count = 0
|
|
137
|
+
self.last_request_time = current_time
|
|
138
|
+
|
|
139
|
+
if "/downloadDevices" in path:
|
|
140
|
+
time_since_last_reset = current_time - self.download_devices_last_reset
|
|
141
|
+
if time_since_last_reset >= self.DOWNLOAD_DEVICES_RESET_TIME:
|
|
142
|
+
self.download_devices_count = 0
|
|
143
|
+
self.download_devices_last_reset = current_time
|
|
144
|
+
if self.download_devices_count >= self.DOWNLOAD_DEVICES_LIMIT:
|
|
145
|
+
logger.warning("Rate limit exceeded for /downloadDevices endpoint. Backing off...")
|
|
146
|
+
time.sleep(24 * 60 * 60) # Back off for a day
|
|
147
|
+
self.download_devices_count = 0
|
|
148
|
+
self.download_devices_last_reset = datetime.utcnow()
|
|
149
|
+
self.download_devices_count += 1
|
|
150
|
+
else:
|
|
151
|
+
if self.request_count >= self.RATE_LIMIT:
|
|
152
|
+
logger.warning("Rate limit exceeded. Backing off...")
|
|
153
|
+
time.sleep(3600) # Back off for an hour
|
|
154
|
+
self.request_count = 0
|
|
155
|
+
self.last_request_time = datetime.utcnow()
|
|
156
|
+
self.request_count += 1
|
|
157
|
+
|
|
158
|
+
def send(self, method, path, json=None, params=None, stream=False):
|
|
159
|
+
api = self.url
|
|
160
|
+
if params is None:
|
|
161
|
+
params = {}
|
|
162
|
+
url = f"{api}/{path.lstrip('/')}"
|
|
163
|
+
if params:
|
|
164
|
+
url = f"{url}?{urllib.parse.urlencode(params)}"
|
|
165
|
+
|
|
166
|
+
start_time = time.time()
|
|
167
|
+
headers_with_user_agent = self.headers.copy()
|
|
168
|
+
headers_with_user_agent["User-Agent"] = self.user_agent
|
|
169
|
+
request_uuid = str(uuid.uuid4())
|
|
170
|
+
dump_request(logger, url, method, json, None, headers_with_user_agent, request_uuid)
|
|
171
|
+
|
|
172
|
+
# Check rate limits
|
|
173
|
+
self.check_rate_limit(path)
|
|
174
|
+
|
|
175
|
+
attempts = 0
|
|
176
|
+
while attempts < 5:
|
|
177
|
+
try:
|
|
178
|
+
self.refreshToken()
|
|
179
|
+
resp = requests.request(
|
|
180
|
+
method,
|
|
181
|
+
url,
|
|
182
|
+
json=json,
|
|
183
|
+
params=None,
|
|
184
|
+
headers=headers_with_user_agent,
|
|
185
|
+
stream=stream,
|
|
186
|
+
)
|
|
187
|
+
dump_response(
|
|
188
|
+
logger=logger,
|
|
189
|
+
url=url,
|
|
190
|
+
params=None,
|
|
191
|
+
method=method,
|
|
192
|
+
resp=resp,
|
|
193
|
+
request_uuid=request_uuid,
|
|
194
|
+
start_time=start_time,
|
|
195
|
+
)
|
|
196
|
+
if resp.status_code == 429:
|
|
197
|
+
retry_after = resp.headers.get("Retry-After")
|
|
198
|
+
if retry_after:
|
|
199
|
+
try:
|
|
200
|
+
sleep_time = int(retry_after)
|
|
201
|
+
except ValueError:
|
|
202
|
+
sleep_time = int(retry_after[:-1])
|
|
203
|
+
logger.warning(f"Rate limit exceeded. Retrying in {sleep_time} seconds.")
|
|
204
|
+
time.sleep(sleep_time)
|
|
205
|
+
else:
|
|
206
|
+
time.sleep(60)
|
|
207
|
+
attempts += 1
|
|
208
|
+
continue
|
|
209
|
+
else:
|
|
210
|
+
break
|
|
211
|
+
except requests.RequestException as e:
|
|
212
|
+
if attempts == 4:
|
|
213
|
+
logger.error(f"Failed to send {method} request to {url} after 5 attempts. Error: {str(e)}")
|
|
214
|
+
raise e
|
|
215
|
+
else:
|
|
216
|
+
logger.warning(f"Failed to send {method} request to {url}. Retrying... Error: {str(e)}")
|
|
217
|
+
attempts += 1
|
|
218
|
+
time.sleep(5)
|
|
219
|
+
|
|
220
|
+
return resp
|
|
221
|
+
|
|
222
|
+
def get(self, path, json=None, params=None, stream=False):
|
|
223
|
+
"""
|
|
224
|
+
Send a GET request to the ZCC API.
|
|
225
|
+
|
|
226
|
+
Parameters:
|
|
227
|
+
path (str): API endpoint path.
|
|
228
|
+
json (dict, optional): Request payload. Defaults to None.
|
|
229
|
+
params (dict, optional): Query parameters. Defaults to None.
|
|
230
|
+
stream (bool, optional): Whether to stream the response content. Defaults to False.
|
|
231
|
+
|
|
232
|
+
Returns:
|
|
233
|
+
Response: The response object from the API request.
|
|
234
|
+
"""
|
|
235
|
+
resp = self.send("GET", path, json, params, stream=stream)
|
|
236
|
+
if stream:
|
|
237
|
+
return resp
|
|
238
|
+
formatted_resp = format_json_response(resp, box_attrs=dict())
|
|
239
|
+
return formatted_resp
|
|
240
|
+
|
|
241
|
+
def post(self, path, json=None, params=None):
|
|
242
|
+
"""
|
|
243
|
+
Send a POST request to the ZCC API.
|
|
244
|
+
|
|
245
|
+
Parameters:
|
|
246
|
+
path (str): API endpoint path.
|
|
247
|
+
json (dict, optional): Request payload. Defaults to None.
|
|
248
|
+
params (dict, optional): Query parameters. Defaults to None.
|
|
249
|
+
|
|
250
|
+
Returns:
|
|
251
|
+
dict: Formatted JSON response from the API.
|
|
252
|
+
"""
|
|
253
|
+
resp = self.send("POST", path, json, params)
|
|
254
|
+
formatted_resp = format_json_response(resp, box_attrs=dict())
|
|
255
|
+
return formatted_resp
|
|
256
|
+
|
|
257
|
+
@property
|
|
258
|
+
def devices(self):
|
|
259
|
+
"""The interface object for the :ref:`ZCC Devices interface <zcc-devices>`."""
|
|
260
|
+
return DevicesAPI(self)
|
|
261
|
+
|
|
262
|
+
@property
|
|
263
|
+
def secrets(self):
|
|
264
|
+
"""The interface object for the :ref:`ZCC Secrets interface <zcc-secrets>`."""
|
|
265
|
+
return SecretsAPI(self)
|
|
@@ -0,0 +1,65 @@
|
|
|
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
|
+
class ZCCClient:
|
|
19
|
+
def __init__():
|
|
20
|
+
pass
|
|
21
|
+
|
|
22
|
+
def get(
|
|
23
|
+
self,
|
|
24
|
+
path: str,
|
|
25
|
+
json=None,
|
|
26
|
+
params=None,
|
|
27
|
+
stream=False,
|
|
28
|
+
):
|
|
29
|
+
"""
|
|
30
|
+
Send a GET request to the ZCC API.
|
|
31
|
+
Parameters:
|
|
32
|
+
- path (str): API endpoint path.
|
|
33
|
+
- json (str): the request body.
|
|
34
|
+
- params (dict): the query params
|
|
35
|
+
"""
|
|
36
|
+
pass
|
|
37
|
+
|
|
38
|
+
def get_paginated_data(
|
|
39
|
+
self,
|
|
40
|
+
path=None,
|
|
41
|
+
params=None,
|
|
42
|
+
search=None,
|
|
43
|
+
search_field="name",
|
|
44
|
+
page=None,
|
|
45
|
+
pagesize=20,
|
|
46
|
+
):
|
|
47
|
+
"""
|
|
48
|
+
Send a GET request to the ZCC API to fetch all pages of a resources.
|
|
49
|
+
Parameters:
|
|
50
|
+
- path (str): API endpoint path.
|
|
51
|
+
- data_key_name (str): list field key.
|
|
52
|
+
- data_per_page: the page size
|
|
53
|
+
- params (dict): the query params
|
|
54
|
+
"""
|
|
55
|
+
pass
|
|
56
|
+
|
|
57
|
+
def post(self, path: str, json=None, params=None):
|
|
58
|
+
"""
|
|
59
|
+
Send a POST request to the ZCC API.
|
|
60
|
+
Parameters:
|
|
61
|
+
- path (str): API endpoint path.
|
|
62
|
+
- json (str): the request body.
|
|
63
|
+
- params (dict): the query params
|
|
64
|
+
"""
|
|
65
|
+
pass
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
import shutil
|
|
2
|
+
from datetime import datetime
|
|
3
|
+
from box import BoxList
|
|
4
|
+
from zscaler.zcc.client import ZCCClient
|
|
5
|
+
from zscaler.utils import convert_keys, zcc_param_map
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class DevicesAPI:
|
|
9
|
+
|
|
10
|
+
def __init__(self, client: ZCCClient):
|
|
11
|
+
self.rest = client
|
|
12
|
+
|
|
13
|
+
def download_devices(
|
|
14
|
+
self,
|
|
15
|
+
filename: str = None,
|
|
16
|
+
os_types: list = None,
|
|
17
|
+
registration_types: list = None,
|
|
18
|
+
):
|
|
19
|
+
"""
|
|
20
|
+
Downloads the list of devices in the Client Connector Portal as a CSV file.
|
|
21
|
+
|
|
22
|
+
By default, this method will create a file named `zcc-devices-YYmmDD-HH_MM_SS.csv`. This can be overridden by
|
|
23
|
+
specifying the ``filename`` argument.
|
|
24
|
+
|
|
25
|
+
Notes:
|
|
26
|
+
This API endpoint is heavily rate-limited by Zscaler and as of NOV 2022 only 3 calls per-day are allowed.
|
|
27
|
+
|
|
28
|
+
Args:
|
|
29
|
+
filename (str):
|
|
30
|
+
The name of the file that you want to save to disk.
|
|
31
|
+
os_types (list):
|
|
32
|
+
A list of OS Types to filter the device list. Omitting this argument will result in all OS types being
|
|
33
|
+
matched.
|
|
34
|
+
Valid options are:
|
|
35
|
+
|
|
36
|
+
- ios
|
|
37
|
+
- android
|
|
38
|
+
- windows
|
|
39
|
+
- macos
|
|
40
|
+
- linux
|
|
41
|
+
registration_types (list):
|
|
42
|
+
A list of device registration states to filter the device list.
|
|
43
|
+
Valid options are:
|
|
44
|
+
|
|
45
|
+
- all (provides all states except for 'removed')
|
|
46
|
+
- registered
|
|
47
|
+
- removal_pending
|
|
48
|
+
- unregistered
|
|
49
|
+
- removed
|
|
50
|
+
- quarantined
|
|
51
|
+
|
|
52
|
+
Returns:
|
|
53
|
+
:obj:`str`: The local filename for the CSV file that was downloaded.
|
|
54
|
+
|
|
55
|
+
Examples:
|
|
56
|
+
Create a CSV with all OS types and all registration types:
|
|
57
|
+
|
|
58
|
+
>>> zcc.devices.download_devices(registration_types=["all", "removed"])
|
|
59
|
+
|
|
60
|
+
Create a CSV for Windows and macOS devices that are in the `registered` state:
|
|
61
|
+
|
|
62
|
+
>>> zcc.devices.download_devices(os_types=["windows", "macos"],
|
|
63
|
+
... registration_types=["registered"])
|
|
64
|
+
|
|
65
|
+
Create a CSV with filename `unregistered.csv` for devices in the unregistered state:
|
|
66
|
+
|
|
67
|
+
>>> zcc.devices.download_devices(filename="unregistered.csv",
|
|
68
|
+
... registration_types=["unregistered"])
|
|
69
|
+
|
|
70
|
+
"""
|
|
71
|
+
|
|
72
|
+
if not filename:
|
|
73
|
+
filename = f"zcc-devices-{datetime.now().strftime('%Y%m%d-%H_%M_%S')}.csv"
|
|
74
|
+
|
|
75
|
+
params = {}
|
|
76
|
+
|
|
77
|
+
# Simplify the os_type argument, raise an error if the user supplies the wrong one.
|
|
78
|
+
if os_types:
|
|
79
|
+
for item in os_types:
|
|
80
|
+
os_type = zcc_param_map["os"].get(item, None)
|
|
81
|
+
if os_type:
|
|
82
|
+
if "osTypes" not in params:
|
|
83
|
+
params["osTypes"] = str(os_type)
|
|
84
|
+
else:
|
|
85
|
+
params["osTypes"] += "," + str(os_type)
|
|
86
|
+
else:
|
|
87
|
+
raise ValueError("Invalid os_type specified. Check the pyZscaler documentation for valid os_type options.")
|
|
88
|
+
|
|
89
|
+
# Simplify the registration_type argument, raise an error if the user supplies the wrong one.
|
|
90
|
+
if registration_types:
|
|
91
|
+
for item in registration_types:
|
|
92
|
+
reg_type = zcc_param_map["reg_type"].get(item, None)
|
|
93
|
+
if reg_type:
|
|
94
|
+
if "registrationTypes" not in params:
|
|
95
|
+
params["registrationTypes"] = str(reg_type)
|
|
96
|
+
else:
|
|
97
|
+
params["registrationTypes"] += "," + str(reg_type)
|
|
98
|
+
else:
|
|
99
|
+
raise ValueError(
|
|
100
|
+
"Invalid registration_type specified. Check the pyZscaler documentation for valid "
|
|
101
|
+
"registration_type options."
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
# Create the local file and stream the device list csv to it
|
|
105
|
+
with self.rest.get("downloadDevices", params=params, stream=True) as r:
|
|
106
|
+
with open(filename, "wb") as f:
|
|
107
|
+
f.write(r.content)
|
|
108
|
+
|
|
109
|
+
return filename
|
|
110
|
+
|
|
111
|
+
def list_devices(self, **kwargs) -> BoxList:
|
|
112
|
+
"""
|
|
113
|
+
Returns the list of devices enrolled in the Client Connector Portal.
|
|
114
|
+
|
|
115
|
+
Keyword Args:
|
|
116
|
+
os_type (str):
|
|
117
|
+
Filter by device operating system. Valid options are:
|
|
118
|
+
|
|
119
|
+
- ios
|
|
120
|
+
- android
|
|
121
|
+
- windows
|
|
122
|
+
- macos
|
|
123
|
+
- linux
|
|
124
|
+
page (int):
|
|
125
|
+
Return a specific page number.
|
|
126
|
+
page_size (int):
|
|
127
|
+
Specify the number of devices per page, defaults to ``30``.
|
|
128
|
+
user_name (str):
|
|
129
|
+
Filter by the enrolled user for the device.
|
|
130
|
+
|
|
131
|
+
Returns:
|
|
132
|
+
:obj:`BoxList`: A list containing devices using ZCC enrolled in the Client Connector Portal.
|
|
133
|
+
|
|
134
|
+
Examples:
|
|
135
|
+
Prints all devices in the Client Connector Portal to the console:
|
|
136
|
+
|
|
137
|
+
>>> for device in zcc.devices.list_devices():
|
|
138
|
+
... print(device)
|
|
139
|
+
|
|
140
|
+
"""
|
|
141
|
+
payload = convert_keys(dict(kwargs))
|
|
142
|
+
|
|
143
|
+
# Simplify the os_type argument, raise an error if the user supplies the wrong one.
|
|
144
|
+
if kwargs.get("os_type"):
|
|
145
|
+
os_type = zcc_param_map["os"].get(payload["osType"], None)
|
|
146
|
+
if os_type:
|
|
147
|
+
payload["osType"] = os_type
|
|
148
|
+
else:
|
|
149
|
+
raise ValueError("Invalid os_type specified. Check the Zscaler documentation for valid os_type options.")
|
|
150
|
+
|
|
151
|
+
return self.rest.get("getDevices", **payload)
|
|
152
|
+
|
|
153
|
+
def remove_devices(self, force: bool = False, **kwargs):
|
|
154
|
+
"""
|
|
155
|
+
Removes the specified devices from the Zscaler Client Connector Portal.
|
|
156
|
+
|
|
157
|
+
Notes:
|
|
158
|
+
You must be using API credentials with the `Write` role.
|
|
159
|
+
You must specify at least one criterion from `Keyword Args` to remove devices.
|
|
160
|
+
|
|
161
|
+
Args:
|
|
162
|
+
force (bool):
|
|
163
|
+
Setting force to ``True`` removes the enrolled device from the portal. You can only remove devices that
|
|
164
|
+
are in the `registered` or `device removal pending` state.
|
|
165
|
+
**kwargs:
|
|
166
|
+
Optional keyword args.
|
|
167
|
+
|
|
168
|
+
Keyword Args:
|
|
169
|
+
client_connector_version (list):
|
|
170
|
+
A list of client connector versions that will be removed. You must supply the exact version number, i.e.
|
|
171
|
+
if the Client Connector version is `3.2.0.18` you must specify `3.2.0.18` and not `3.2`.
|
|
172
|
+
os_type (str):
|
|
173
|
+
The OS Type for the devices to be removed. Valid options are:
|
|
174
|
+
|
|
175
|
+
- ios
|
|
176
|
+
- android
|
|
177
|
+
- windows
|
|
178
|
+
- macos
|
|
179
|
+
- linux
|
|
180
|
+
udids (list):
|
|
181
|
+
A list of Unique Device IDs.
|
|
182
|
+
user_name (str):
|
|
183
|
+
The username of the user whose devices will be removed.
|
|
184
|
+
|
|
185
|
+
Returns:
|
|
186
|
+
:obj:`Box`: Server response containing the total number of devices removed.
|
|
187
|
+
|
|
188
|
+
Examples:
|
|
189
|
+
Soft-remove devices using ZCC version 3.7.1.44 from the Client Connector Portal:
|
|
190
|
+
|
|
191
|
+
>>> zcc.devices.remove_devices(client_connector_version=["3.7.1.44"])
|
|
192
|
+
|
|
193
|
+
Soft-remove Android devices from the Client Connector Portal:
|
|
194
|
+
|
|
195
|
+
>>> zcc.devices.remove_devices(os_type="android")
|
|
196
|
+
|
|
197
|
+
Hard-remove devices from the Client Connector Portal by UDID:
|
|
198
|
+
|
|
199
|
+
>>> zcc.devices.remove_devices(force=True, udids=["99999", "88888", "77777"])
|
|
200
|
+
|
|
201
|
+
Hard-remove Android devices for johnno@widgets.co from the Client Connector Portal:
|
|
202
|
+
|
|
203
|
+
>>> zcc.devices.remove_devices(force=True, os_type="android",
|
|
204
|
+
... user_name="johnno@widgets.co")
|
|
205
|
+
|
|
206
|
+
"""
|
|
207
|
+
payload = convert_keys(dict(kwargs))
|
|
208
|
+
|
|
209
|
+
# Simplify the os_type argument, raise an error if the user supplies the wrong one.
|
|
210
|
+
if kwargs.get("os_type"):
|
|
211
|
+
os_type = zcc_param_map["os"].get(payload["osType"], None)
|
|
212
|
+
if os_type:
|
|
213
|
+
payload["osType"] = os_type
|
|
214
|
+
else:
|
|
215
|
+
raise ValueError("Invalid os_type specified. Check the pyZscaler documentation for valid os_type options.")
|
|
216
|
+
|
|
217
|
+
if force:
|
|
218
|
+
return self.rest.post("forceRemoveDevices", json=payload)
|
|
219
|
+
else:
|
|
220
|
+
return self.rest.post("removeDevices", json=payload)
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
from zscaler.zcc.client import ZCCClient
|
|
2
|
+
from zscaler.utils import zcc_param_map
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class SecretsAPI:
|
|
6
|
+
def __init__(self, client: ZCCClient):
|
|
7
|
+
self.rest = client
|
|
8
|
+
|
|
9
|
+
def get_otp(self, device_id: str):
|
|
10
|
+
"""
|
|
11
|
+
Returns the OTP code for the specified device id.
|
|
12
|
+
|
|
13
|
+
Args:
|
|
14
|
+
device_id (str): The unique id for the enrolled device that the OTP will be obtained for.
|
|
15
|
+
|
|
16
|
+
Returns:
|
|
17
|
+
:obj:`Box`: A dictionary containing the requested OTP code for the specified device id.
|
|
18
|
+
|
|
19
|
+
Examples:
|
|
20
|
+
Obtain the OTP code for a device and print it to console:
|
|
21
|
+
|
|
22
|
+
>>> otp_code = zcc.secrets.get_otp('System-Serial-Number:1234ABCDEF')
|
|
23
|
+
... print(otp_code.otp)
|
|
24
|
+
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
payload = {"udid": device_id}
|
|
28
|
+
|
|
29
|
+
return self.rest.get("getOtp", params=payload)
|
|
30
|
+
|
|
31
|
+
def get_passwords(self, username: str, os_type: str = "windows"):
|
|
32
|
+
"""
|
|
33
|
+
Return passwords for the specified username and device OS type.
|
|
34
|
+
|
|
35
|
+
Args:
|
|
36
|
+
username (str): The username that the device belongs to.
|
|
37
|
+
os_type (str): The OS Type for the device, defaults to `windows`. Valid options are:
|
|
38
|
+
|
|
39
|
+
- ios
|
|
40
|
+
- android
|
|
41
|
+
- windows
|
|
42
|
+
- macos
|
|
43
|
+
- linux
|
|
44
|
+
|
|
45
|
+
Returns:
|
|
46
|
+
:obj:`Box`: Dictionary containing passwords for the specified username's device.
|
|
47
|
+
|
|
48
|
+
Examples:
|
|
49
|
+
Print macOS device passwords for username test@example.com:
|
|
50
|
+
|
|
51
|
+
>>> print(zcc.secrets.get_passwords(username='test@example.com',
|
|
52
|
+
... os_type='macos'))
|
|
53
|
+
|
|
54
|
+
"""
|
|
55
|
+
|
|
56
|
+
# Simplify the os_type argument, raise an error if the user supplies the wrong one.
|
|
57
|
+
os_type = zcc_param_map["os"].get(os_type, None)
|
|
58
|
+
if not os_type:
|
|
59
|
+
raise ValueError("Invalid os_type specified. Check the pyZscaler documentation for valid os_type options.")
|
|
60
|
+
|
|
61
|
+
params = {
|
|
62
|
+
"username": username,
|
|
63
|
+
"osType": os_type,
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return self.rest.get("getPasswords", params=params)
|
|
@@ -110,6 +110,27 @@ class CloudAppControlAPI:
|
|
|
110
110
|
"""
|
|
111
111
|
return self.rest.get(f"webApplicationRules/{rule_type}/{rule_id}")
|
|
112
112
|
|
|
113
|
+
def get_rule_by_name(self, rule_type: str, rule_name: str) -> Box:
|
|
114
|
+
"""
|
|
115
|
+
Retrieves a specific Cloud App Control rule by its name and type.
|
|
116
|
+
|
|
117
|
+
Args:
|
|
118
|
+
rule_type (str): The type of rules to search within (e.g., "WEBMAIL").
|
|
119
|
+
rule_name (str): The name of the rule to retrieve.
|
|
120
|
+
|
|
121
|
+
Returns:
|
|
122
|
+
:obj:`Box`: The Cloud App Control rule if found, otherwise None.
|
|
123
|
+
|
|
124
|
+
Examples:
|
|
125
|
+
>>> rule = zia.cloudappcontrol.get_rule_by_name('WEBMAIL', 'Webmail Rule-1')
|
|
126
|
+
... print(rule)
|
|
127
|
+
"""
|
|
128
|
+
rules = self.list_rules(rule_type)
|
|
129
|
+
for rule in rules:
|
|
130
|
+
if rule.get("name") == rule_name:
|
|
131
|
+
return rule
|
|
132
|
+
return None
|
|
133
|
+
|
|
113
134
|
def add_rule(self, rule_type: str, name: str, **kwargs) -> Box:
|
|
114
135
|
"""
|
|
115
136
|
Adds a new cloud app control filter rule.
|
|
@@ -474,10 +495,6 @@ class CloudAppControlAPI:
|
|
|
474
495
|
# Set payload to value of existing record and convert nested dict keys.
|
|
475
496
|
payload = convert_keys(self.get_rule(rule_type, rule_id))
|
|
476
497
|
|
|
477
|
-
# Convert enabled to API format if present in kwargs
|
|
478
|
-
if "enabled" in kwargs:
|
|
479
|
-
kwargs["state"] = "ENABLED" if kwargs.pop("enabled") else "DISABLED"
|
|
480
|
-
|
|
481
498
|
# Convert enabled to API format if present in kwargs
|
|
482
499
|
if "enabled" in kwargs:
|
|
483
500
|
kwargs["state"] = "ENABLED" if kwargs.pop("enabled") else "DISABLED"
|
|
@@ -52,7 +52,7 @@ class DLPAPI:
|
|
|
52
52
|
if isinstance(list, Response):
|
|
53
53
|
return None
|
|
54
54
|
return list
|
|
55
|
-
|
|
55
|
+
|
|
56
56
|
def get_dict(self, dict_id: str) -> Box:
|
|
57
57
|
"""
|
|
58
58
|
Returns the DLP Dictionary that matches the specified DLP Dictionary id.
|
|
@@ -398,13 +398,7 @@ class DLPAPI:
|
|
|
398
398
|
else:
|
|
399
399
|
return response
|
|
400
400
|
|
|
401
|
-
def add_dlp_engine(
|
|
402
|
-
self,
|
|
403
|
-
name: str,
|
|
404
|
-
engine_expression=None,
|
|
405
|
-
custom_dlp_engine=None,
|
|
406
|
-
**kwargs
|
|
407
|
-
) -> Box:
|
|
401
|
+
def add_dlp_engine(self, name: str, engine_expression=None, custom_dlp_engine=None, **kwargs) -> Box:
|
|
408
402
|
"""
|
|
409
403
|
Adds a new dlp engine.
|
|
410
404
|
Args:
|
|
@@ -888,7 +882,7 @@ class DLPAPI:
|
|
|
888
882
|
if isinstance(response, Response):
|
|
889
883
|
return None
|
|
890
884
|
return response
|
|
891
|
-
|
|
885
|
+
|
|
892
886
|
def list_edm_schema_lite(self, schema_name: str = None, active_only: bool = None, fetch_tokens: bool = None) -> BoxList:
|
|
893
887
|
"""
|
|
894
888
|
Returns the list of active EDM templates (or EDM schemas) and their criteria (or token details), only.
|
|
@@ -896,7 +890,7 @@ class DLPAPI:
|
|
|
896
890
|
Args:
|
|
897
891
|
schema_name (str): The EDM schema name.
|
|
898
892
|
active_only (bool): If set to true, only active EDM templates (or schemas) are returned in the response.
|
|
899
|
-
fetch_tokens (bool): If set to true, the criteria
|
|
893
|
+
fetch_tokens (bool): If set to true, the criteria for the active templates are returned in the response.
|
|
900
894
|
|
|
901
895
|
Returns:
|
|
902
896
|
:obj:`BoxList`: A list containing ZIA DLP Engines.
|
|
@@ -911,11 +905,11 @@ class DLPAPI:
|
|
|
911
905
|
"""
|
|
912
906
|
params = {}
|
|
913
907
|
if schema_name is not None:
|
|
914
|
-
params[
|
|
908
|
+
params["schemaName"] = schema_name
|
|
915
909
|
if active_only is not None:
|
|
916
|
-
params[
|
|
910
|
+
params["activeOnly"] = active_only
|
|
917
911
|
if fetch_tokens is not None:
|
|
918
|
-
params[
|
|
912
|
+
params["fetchTokens"] = fetch_tokens
|
|
919
913
|
|
|
920
914
|
response = self.rest.get("/dlpExactDataMatchSchemas/lite", params=params)
|
|
921
915
|
if isinstance(response, Response):
|
|
@@ -58,7 +58,7 @@ class URLCategoriesAPI:
|
|
|
58
58
|
def review_domains_post(self, urls: list) -> BoxList:
|
|
59
59
|
"""
|
|
60
60
|
For the specified list of URLs, finds matching entries present in existing custom URL categories.
|
|
61
|
-
|
|
61
|
+
Allows the review of URLs for matches in existing categories to group related entries in a single category.
|
|
62
62
|
|
|
63
63
|
Args:
|
|
64
64
|
urls (list): A list of URLs to perform a category lookup on. Maximum 100 URLs per request.
|
|
@@ -104,7 +104,6 @@ class URLCategoriesAPI:
|
|
|
104
104
|
else:
|
|
105
105
|
return self.rest.put("urlCategories/review/domains", json=urls)
|
|
106
106
|
|
|
107
|
-
|
|
108
107
|
def list_categories(self, custom_only: bool = False, only_counts: bool = False) -> BoxList:
|
|
109
108
|
"""
|
|
110
109
|
Returns information on URL categories.
|
|
@@ -173,6 +173,49 @@ class SegmentGroupsAPI:
|
|
|
173
173
|
if not isinstance(resp, Response):
|
|
174
174
|
return self.get_group(group_id)
|
|
175
175
|
|
|
176
|
+
# REQUIRES DEPLOYMENT OF ET-76506 IN PRODUCTION BEFORE ENABLING IT.
|
|
177
|
+
def update_group_v2(self, group_id: str, **kwargs) -> Box:
|
|
178
|
+
"""
|
|
179
|
+
Updates an existing segment group using v2 endpoint.
|
|
180
|
+
|
|
181
|
+
Args:
|
|
182
|
+
group_id (str): The unique identifier for the segment group to be updated.
|
|
183
|
+
**kwargs: Optional keyword args.
|
|
184
|
+
|
|
185
|
+
Keyword Args:
|
|
186
|
+
name (str): The name of the new segment group.
|
|
187
|
+
enabled (bool): Enable the segment group.
|
|
188
|
+
application_ids (:obj:`list` of :obj:`dict`): Unique application IDs to associate with the segment group.
|
|
189
|
+
config_space (str): The config space for the segment group. Can either be DEFAULT or SIEM.
|
|
190
|
+
description (str): A description for the segment group.
|
|
191
|
+
policy_migrated (bool):
|
|
192
|
+
microtenant_id (str): The microtenant ID to be used for this request.
|
|
193
|
+
|
|
194
|
+
Returns:
|
|
195
|
+
:obj:`Box`: The resource record for the updated segment group.
|
|
196
|
+
|
|
197
|
+
Examples:
|
|
198
|
+
Updating the name of a segment group:
|
|
199
|
+
|
|
200
|
+
>>> zpa.segment_groups.update_group_v2('99999',
|
|
201
|
+
... name='updated_name')
|
|
202
|
+
|
|
203
|
+
"""
|
|
204
|
+
payload = {snake_to_camel(k): v for k, v in self.get_group(group_id).items()}
|
|
205
|
+
|
|
206
|
+
if kwargs.get("application_ids"):
|
|
207
|
+
payload["applications"] = [{"id": app_id} for app_id in kwargs.pop("application_ids")]
|
|
208
|
+
|
|
209
|
+
for key, value in kwargs.items():
|
|
210
|
+
payload[snake_to_camel(key)] = value
|
|
211
|
+
|
|
212
|
+
microtenant_id = kwargs.pop("microtenant_id", None)
|
|
213
|
+
params = {"microtenantId": microtenant_id} if microtenant_id else {}
|
|
214
|
+
|
|
215
|
+
resp = self.rest.put(f"segmentGroup/{group_id}", json=payload, params=params, api_version="v2").status_code
|
|
216
|
+
if not isinstance(resp, Response):
|
|
217
|
+
return self.get_group(group_id)
|
|
218
|
+
|
|
176
219
|
def delete_group(self, group_id: str, **kwargs) -> int:
|
|
177
220
|
"""
|
|
178
221
|
Deletes the specified segment group.
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{zscaler_sdk_python-0.8.0 → zscaler_sdk_python-0.9.1}/zscaler/zcon/admin_and_role_management.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{zscaler_sdk_python-0.8.0 → zscaler_sdk_python-0.9.1}/zscaler/zia/admin_and_role_management.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{zscaler_sdk_python-0.8.0 → zscaler_sdk_python-0.9.1}/zscaler/zia/authentication_settings.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{zscaler_sdk_python-0.8.0 → zscaler_sdk_python-0.9.1}/zscaler/zpa/app_segments_inspection.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{zscaler_sdk_python-0.8.0 → zscaler_sdk_python-0.9.1}/zscaler/zpa/privileged_remote_access.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|