UGOS 1.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ugos/__about__.py +6 -0
- ugos/__init__.py +92 -0
- ugos/_auth.py +293 -0
- ugos/_client_base.py +52 -0
- ugos/_client_calls.py +200 -0
- ugos/_client_helpers.py +217 -0
- ugos/_client_lifecycle.py +144 -0
- ugos/_client_resources.py +192 -0
- ugos/_client_transfer.py +501 -0
- ugos/_codec.py +528 -0
- ugos/_config.py +183 -0
- ugos/_data/__init__.py +1 -0
- ugos/_data/action_contracts.json +325 -0
- ugos/_data/catalog.json +1256 -0
- ugos/_dynamic.py +221 -0
- ugos/_encoding.py +159 -0
- ugos/_pagination.py +158 -0
- ugos/_redaction.py +158 -0
- ugos/_transport.py +446 -0
- ugos/catalog.py +184 -0
- ugos/client.py +113 -0
- ugos/exceptions.py +137 -0
- ugos/models/__init__.py +37 -0
- ugos/models/common.py +87 -0
- ugos/models/entities.py +124 -0
- ugos/models/users.py +43 -0
- ugos/py.typed +0 -0
- ugos/resources/__init__.py +55 -0
- ugos/resources/administrators.py +71 -0
- ugos/resources/application.py +107 -0
- ugos/resources/base.py +38 -0
- ugos/resources/certificates.py +62 -0
- ugos/resources/contentrules.py +98 -0
- ugos/resources/contenttypes.py +9 -0
- ugos/resources/fwrules.py +103 -0
- ugos/resources/generic.py +283 -0
- ugos/resources/generic_controllers.py +169 -0
- ugos/resources/groups.py +94 -0
- ugos/resources/icap.py +132 -0
- ugos/resources/idps.py +125 -0
- ugos/resources/interfaces.py +40 -0
- ugos/resources/l7.py +110 -0
- ugos/resources/licensing.py +39 -0
- ugos/resources/mailsecurity.py +101 -0
- ugos/resources/networks.py +82 -0
- ugos/resources/netzones.py +78 -0
- ugos/resources/nlist.py +72 -0
- ugos/resources/overridedomains.py +47 -0
- ugos/resources/overrides.py +109 -0
- ugos/resources/overrides_content.py +254 -0
- ugos/resources/overrides_content_a.py +187 -0
- ugos/resources/overrides_lists.py +294 -0
- ugos/resources/overrides_misc.py +342 -0
- ugos/resources/overrides_network.py +291 -0
- ugos/resources/pcap.py +64 -0
- ugos/resources/registry.py +55 -0
- ugos/resources/settings.py +72 -0
- ugos/resources/sysinfo.py +39 -0
- ugos/resources/twofa.py +62 -0
- ugos/resources/urlcategory.py +69 -0
- ugos/resources/urlfiltering.py +23 -0
- ugos/resources/urllists.py +9 -0
- ugos/resources/users.py +74 -0
- ugos-1.0.0.dist-info/METADATA +139 -0
- ugos-1.0.0.dist-info/RECORD +67 -0
- ugos-1.0.0.dist-info/WHEEL +4 -0
- ugos-1.0.0.dist-info/licenses/LICENSE +16 -0
ugos/_client_helpers.py
ADDED
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
"""Internal helpers shared by the client implementation."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import dataclasses
|
|
6
|
+
import os
|
|
7
|
+
import re
|
|
8
|
+
from collections.abc import Collection, Iterator, Mapping
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import Any
|
|
11
|
+
|
|
12
|
+
import httpx
|
|
13
|
+
|
|
14
|
+
from .exceptions import DownloadError
|
|
15
|
+
|
|
16
|
+
_VERIFY_FALSE = frozenset({"0", "false", "no", "off"})
|
|
17
|
+
_VERIFY_TRUE = frozenset({"1", "true", "yes", "on"})
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _parse_verify(value: str) -> bool | str:
|
|
21
|
+
lowered = value.strip().lower()
|
|
22
|
+
if lowered in _VERIFY_FALSE:
|
|
23
|
+
return False
|
|
24
|
+
if lowered in _VERIFY_TRUE:
|
|
25
|
+
return True
|
|
26
|
+
return value
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@dataclasses.dataclass
|
|
30
|
+
class _RetryState:
|
|
31
|
+
csrf: int = 0
|
|
32
|
+
reauth: int = 0
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@dataclasses.dataclass
|
|
36
|
+
class _DownloadSpec:
|
|
37
|
+
controller: str
|
|
38
|
+
action: str
|
|
39
|
+
path: str
|
|
40
|
+
method: str
|
|
41
|
+
params: Mapping[str, Any]
|
|
42
|
+
json_fields: Collection[str]
|
|
43
|
+
timeout: Any
|
|
44
|
+
chunk_size: int
|
|
45
|
+
limit: int | None
|
|
46
|
+
dest: str | Path | None
|
|
47
|
+
filename: str | None
|
|
48
|
+
overwrite: bool
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def _retry_allowed(action: str, idempotent: bool, exc: Exception) -> bool:
|
|
52
|
+
"""Allow automatic replay only for explicitly idempotent actions."""
|
|
53
|
+
del action, exc
|
|
54
|
+
return idempotent
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def _merge_params(payload: Mapping[str, Any] | None, params: Mapping[str, Any]) -> dict[str, Any]:
|
|
58
|
+
merged = dict(payload or {})
|
|
59
|
+
overlap = set(merged) & set(params)
|
|
60
|
+
if overlap:
|
|
61
|
+
names = ", ".join(sorted(overlap))
|
|
62
|
+
raise TypeError(f"duplicate parameters: {names}")
|
|
63
|
+
merged.update(params)
|
|
64
|
+
return merged
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def _multipart_fields(pairs: list[tuple[str, str]]) -> dict[str, Any]:
|
|
68
|
+
fields: dict[str, Any] = {}
|
|
69
|
+
for key, value in pairs:
|
|
70
|
+
if key not in fields:
|
|
71
|
+
fields[key] = value
|
|
72
|
+
elif isinstance(fields[key], list):
|
|
73
|
+
fields[key].append(value)
|
|
74
|
+
else:
|
|
75
|
+
fields[key] = [fields[key], value]
|
|
76
|
+
return fields
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def _raise_if_too_large(
|
|
80
|
+
total: int,
|
|
81
|
+
limit: int | None,
|
|
82
|
+
controller: str,
|
|
83
|
+
action: str,
|
|
84
|
+
) -> None:
|
|
85
|
+
if limit is None or total <= limit:
|
|
86
|
+
return
|
|
87
|
+
raise DownloadError(
|
|
88
|
+
f"download exceeds {limit} bytes",
|
|
89
|
+
controller=controller,
|
|
90
|
+
action=action,
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def _limited_chunks(
|
|
95
|
+
first: bytes,
|
|
96
|
+
chunks: Iterator[bytes],
|
|
97
|
+
*,
|
|
98
|
+
limit: int | None,
|
|
99
|
+
controller: str,
|
|
100
|
+
action: str,
|
|
101
|
+
) -> Iterator[bytes]:
|
|
102
|
+
total = len(first)
|
|
103
|
+
_raise_if_too_large(total, limit, controller, action)
|
|
104
|
+
if first:
|
|
105
|
+
yield first
|
|
106
|
+
for chunk in chunks:
|
|
107
|
+
total += len(chunk)
|
|
108
|
+
_raise_if_too_large(total, limit, controller, action)
|
|
109
|
+
if chunk:
|
|
110
|
+
yield chunk
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def _is_file_tuple(value: Any) -> bool:
|
|
114
|
+
if not isinstance(value, (list, tuple)) or len(value) < 2:
|
|
115
|
+
return False
|
|
116
|
+
if not isinstance(value[0], (str, bytes)):
|
|
117
|
+
return False
|
|
118
|
+
return callable(getattr(value[1], "read", None))
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def _iter_file_objects(value: Any) -> Iterator[Any]:
|
|
122
|
+
if isinstance(value, (list, tuple)):
|
|
123
|
+
if _is_file_tuple(value):
|
|
124
|
+
yield value[1]
|
|
125
|
+
return
|
|
126
|
+
for item in value:
|
|
127
|
+
yield from _iter_file_objects(item)
|
|
128
|
+
return
|
|
129
|
+
if callable(getattr(value, "read", None)):
|
|
130
|
+
yield value
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def _file_position(file_object: Any) -> int | object:
|
|
134
|
+
tell = getattr(file_object, "tell", None)
|
|
135
|
+
seek = getattr(file_object, "seek", None)
|
|
136
|
+
if not callable(tell) or not callable(seek):
|
|
137
|
+
return _NO_POSITION
|
|
138
|
+
try:
|
|
139
|
+
return int(tell())
|
|
140
|
+
except (OSError, ValueError):
|
|
141
|
+
return _NO_POSITION
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def _capture_file_positions(
|
|
145
|
+
files: Mapping[str, Any],
|
|
146
|
+
) -> list[tuple[Any, int]] | None:
|
|
147
|
+
positions: list[tuple[Any, int]] = []
|
|
148
|
+
for value in files.values():
|
|
149
|
+
for file_object in _iter_file_objects(value):
|
|
150
|
+
position = _file_position(file_object)
|
|
151
|
+
if position is _NO_POSITION:
|
|
152
|
+
return None
|
|
153
|
+
positions.append((file_object, position)) # type: ignore[arg-type]
|
|
154
|
+
return positions
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def _restore_file_positions(positions: list[tuple[Any, int]]) -> None:
|
|
158
|
+
for file_object, position in positions:
|
|
159
|
+
file_object.seek(position)
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def _filename_from_response(response: httpx.Response, path: str) -> str:
|
|
163
|
+
disposition = response.headers.get("content-disposition", "")
|
|
164
|
+
if disposition:
|
|
165
|
+
match = re.search(r"filename\*=UTF-8''([^;]+)", disposition, re.IGNORECASE)
|
|
166
|
+
if match:
|
|
167
|
+
from urllib.parse import unquote
|
|
168
|
+
|
|
169
|
+
return unquote(match.group(1).strip().strip('"'))
|
|
170
|
+
match = re.search(r'filename="?([^";]+)"?', disposition, re.IGNORECASE)
|
|
171
|
+
if match:
|
|
172
|
+
return match.group(1).strip()
|
|
173
|
+
return Path(path).name or "download.bin"
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def _is_directory_destination(text: str, path: Path) -> bool:
|
|
177
|
+
if path.is_dir():
|
|
178
|
+
return True
|
|
179
|
+
if text.endswith(("/", os.sep)):
|
|
180
|
+
return True
|
|
181
|
+
altsep = os.altsep
|
|
182
|
+
return altsep is not None and text.endswith(altsep)
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
_ERROR_BODY_LIMIT = 2 * 1024 * 1024
|
|
186
|
+
|
|
187
|
+
_NO_POSITION = object()
|
|
188
|
+
|
|
189
|
+
_WINDOWS_RESERVED = {
|
|
190
|
+
"CON",
|
|
191
|
+
"PRN",
|
|
192
|
+
"AUX",
|
|
193
|
+
"NUL",
|
|
194
|
+
*(f"COM{i}" for i in range(1, 10)),
|
|
195
|
+
*(f"LPT{i}" for i in range(1, 10)),
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
def _error_body_limit(limit: int | None) -> int:
|
|
200
|
+
return _ERROR_BODY_LIMIT if limit is None else min(limit, _ERROR_BODY_LIMIT)
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
def _sanitize_filename(name: str) -> str:
|
|
204
|
+
clean = Path(name.replace("\\", "/")).name
|
|
205
|
+
clean = re.sub(r"[\x00-\x1f]", "", clean)
|
|
206
|
+
clean = clean.replace(":", "_").strip().rstrip(" .")
|
|
207
|
+
if clean in {"", ".", ".."} or clean.split(".")[0].upper() in _WINDOWS_RESERVED:
|
|
208
|
+
return "download.bin"
|
|
209
|
+
return clean[:255]
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def _resolve_destination(dest: str | Path, filename: str) -> Path:
|
|
213
|
+
text = str(dest)
|
|
214
|
+
path = Path(dest)
|
|
215
|
+
if _is_directory_destination(text, path):
|
|
216
|
+
return path / filename
|
|
217
|
+
return path
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
"""Client lifecycle, environment and session methods."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
import warnings
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import TYPE_CHECKING, Any, cast
|
|
9
|
+
|
|
10
|
+
from .__about__ import SUPPORTED_MAJOR_VERSION
|
|
11
|
+
from ._client_base import _ClientBase
|
|
12
|
+
from ._client_helpers import _parse_verify
|
|
13
|
+
from ._config import DEFAULT_TIMEOUT
|
|
14
|
+
from .catalog import Catalog
|
|
15
|
+
from .exceptions import ConfigurationError
|
|
16
|
+
from .models.common import SessionInfo
|
|
17
|
+
|
|
18
|
+
if TYPE_CHECKING:
|
|
19
|
+
from .client import UGOSClient
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _major_version(version: str | None) -> int | None:
|
|
23
|
+
if not version:
|
|
24
|
+
return None
|
|
25
|
+
head = version.strip().lstrip("vV").split(".", 1)[0]
|
|
26
|
+
try:
|
|
27
|
+
return int(head)
|
|
28
|
+
except ValueError:
|
|
29
|
+
return None
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class _LifecycleMixin(_ClientBase):
|
|
33
|
+
@staticmethod
|
|
34
|
+
def _env_verify(overrides: dict[str, Any]) -> bool | str | Path:
|
|
35
|
+
if "verify" in overrides:
|
|
36
|
+
return overrides.pop("verify")
|
|
37
|
+
ca_bundle = os.getenv("UGOS_CA_BUNDLE")
|
|
38
|
+
if ca_bundle:
|
|
39
|
+
return ca_bundle
|
|
40
|
+
verify_env = os.getenv("UGOS_VERIFY")
|
|
41
|
+
if not verify_env:
|
|
42
|
+
return True
|
|
43
|
+
return _parse_verify(verify_env)
|
|
44
|
+
|
|
45
|
+
@staticmethod
|
|
46
|
+
def _env_timeout(overrides: dict[str, Any]) -> float | None:
|
|
47
|
+
if "timeout" in overrides:
|
|
48
|
+
return overrides.pop("timeout")
|
|
49
|
+
timeout_env = os.getenv("UGOS_TIMEOUT")
|
|
50
|
+
if not timeout_env:
|
|
51
|
+
return DEFAULT_TIMEOUT
|
|
52
|
+
try:
|
|
53
|
+
return float(timeout_env)
|
|
54
|
+
except ValueError as exc:
|
|
55
|
+
raise ConfigurationError("UGOS_TIMEOUT must be a number") from exc
|
|
56
|
+
|
|
57
|
+
@staticmethod
|
|
58
|
+
def _env_insecure(overrides: dict[str, Any]) -> None:
|
|
59
|
+
if "allow_insecure_http" in overrides:
|
|
60
|
+
return
|
|
61
|
+
insecure = os.getenv("UGOS_ALLOW_INSECURE_HTTP", "").strip().lower()
|
|
62
|
+
overrides["allow_insecure_http"] = insecure in {"1", "true", "yes", "on"}
|
|
63
|
+
|
|
64
|
+
@classmethod
|
|
65
|
+
def from_env(cls, **overrides: Any) -> UGOSClient:
|
|
66
|
+
"""Create a client from ``UGOS_*`` environment variables."""
|
|
67
|
+
|
|
68
|
+
base_url = overrides.pop("base_url", None) or os.getenv("UGOS_URL")
|
|
69
|
+
if not base_url:
|
|
70
|
+
raise ConfigurationError("UGOS_URL or base_url is required")
|
|
71
|
+
username = overrides.pop("username", None) or os.getenv("UGOS_USERNAME")
|
|
72
|
+
password = overrides.pop("password", None) or os.getenv("UGOS_PASSWORD")
|
|
73
|
+
verify = cls._env_verify(overrides)
|
|
74
|
+
timeout = cls._env_timeout(overrides)
|
|
75
|
+
cls._env_insecure(overrides)
|
|
76
|
+
return cast("type[UGOSClient]", cls)(
|
|
77
|
+
base_url=base_url,
|
|
78
|
+
username=username,
|
|
79
|
+
password=password,
|
|
80
|
+
verify=verify,
|
|
81
|
+
timeout=timeout,
|
|
82
|
+
**overrides,
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
@property
|
|
86
|
+
def base_url(self) -> str:
|
|
87
|
+
return self._config.base_url
|
|
88
|
+
|
|
89
|
+
@property
|
|
90
|
+
def session(self) -> SessionInfo | None:
|
|
91
|
+
return self._auth.session
|
|
92
|
+
|
|
93
|
+
@property
|
|
94
|
+
def is_authenticated(self) -> bool:
|
|
95
|
+
return self._auth.authenticated
|
|
96
|
+
|
|
97
|
+
@property
|
|
98
|
+
def catalog(self) -> Catalog:
|
|
99
|
+
return self._catalog
|
|
100
|
+
|
|
101
|
+
def login(
|
|
102
|
+
self,
|
|
103
|
+
username: str | None = None,
|
|
104
|
+
password: str | None = None,
|
|
105
|
+
*,
|
|
106
|
+
force: bool = False,
|
|
107
|
+
) -> SessionInfo:
|
|
108
|
+
with self._lock:
|
|
109
|
+
if username is not None:
|
|
110
|
+
self._auth._username = username
|
|
111
|
+
if password is not None:
|
|
112
|
+
self._auth._password = password
|
|
113
|
+
session = self._auth.login(force=force)
|
|
114
|
+
major = _major_version(session.version)
|
|
115
|
+
if session.version and major != SUPPORTED_MAJOR_VERSION:
|
|
116
|
+
warnings.warn(
|
|
117
|
+
f"NGFW version {session.version} is outside the supported major version "
|
|
118
|
+
f"{SUPPORTED_MAJOR_VERSION}",
|
|
119
|
+
RuntimeWarning,
|
|
120
|
+
stacklevel=2,
|
|
121
|
+
)
|
|
122
|
+
return session
|
|
123
|
+
|
|
124
|
+
def logout(self, *, best_effort: bool = True) -> bool:
|
|
125
|
+
with self._lock:
|
|
126
|
+
return self._auth.logout(best_effort=best_effort)
|
|
127
|
+
|
|
128
|
+
def close(self) -> None:
|
|
129
|
+
self._transport.close()
|
|
130
|
+
|
|
131
|
+
def __enter__(self) -> UGOSClient:
|
|
132
|
+
return cast("UGOSClient", self)
|
|
133
|
+
|
|
134
|
+
def __exit__(self, _exc_type: Any, _exc: Any, _tb: Any) -> None:
|
|
135
|
+
if self._logout_on_exit and self._auth.authenticated:
|
|
136
|
+
self.logout(best_effort=True)
|
|
137
|
+
self.close()
|
|
138
|
+
|
|
139
|
+
def __repr__(self) -> str:
|
|
140
|
+
username = self._auth.session.username if self._auth.session else None
|
|
141
|
+
return (
|
|
142
|
+
f"UGOSClient(base_url={self.base_url!r}, username={username!r}, "
|
|
143
|
+
f"password='***', session='***')"
|
|
144
|
+
)
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
"""Typed and dynamic controller resource access."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from ._client_base import _T, _ClientBase
|
|
8
|
+
from ._client_base import CONTROLLER_RE as _CONTROLLER_RE
|
|
9
|
+
from ._dynamic import ControllerProxy
|
|
10
|
+
from .exceptions import ConfigurationError, UnknownControllerError
|
|
11
|
+
from .resources.administrators import AdministratorsResource
|
|
12
|
+
from .resources.application import ApplicationResource
|
|
13
|
+
from .resources.certificates import CertificatesResource
|
|
14
|
+
from .resources.contentrules import ContentRulesResource
|
|
15
|
+
from .resources.contenttypes import ContentTypesResource
|
|
16
|
+
from .resources.fwrules import FirewallRulesResource
|
|
17
|
+
from .resources.generic_controllers import GENERIC_RESOURCES
|
|
18
|
+
from .resources.groups import GroupsResource
|
|
19
|
+
from .resources.icap import IcapResource
|
|
20
|
+
from .resources.idps import IdpsResource
|
|
21
|
+
from .resources.interfaces import InterfacesResource
|
|
22
|
+
from .resources.l7 import L7Resource
|
|
23
|
+
from .resources.licensing import LicensingResource
|
|
24
|
+
from .resources.mailsecurity import MailSecurityResource
|
|
25
|
+
from .resources.networks import NetworksResource
|
|
26
|
+
from .resources.netzones import NetzonesResource
|
|
27
|
+
from .resources.overridedomains import OverrideDomainsResource
|
|
28
|
+
from .resources.overrides import TYPED_OVERRIDES
|
|
29
|
+
from .resources.pcap import PcapResource
|
|
30
|
+
from .resources.registry import TYPED_RESOURCES
|
|
31
|
+
from .resources.settings import SettingsResource
|
|
32
|
+
from .resources.sysinfo import SysinfoResource
|
|
33
|
+
from .resources.twofa import TwoFactorResource
|
|
34
|
+
from .resources.urlcategory import UrlCategoryResource
|
|
35
|
+
from .resources.urlfiltering import UrlFilteringResource
|
|
36
|
+
from .resources.urllists import UrlListsResource
|
|
37
|
+
from .resources.users import UsersResource
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class _ResourcesMixin(_ClientBase):
|
|
41
|
+
@staticmethod
|
|
42
|
+
def _resource_class(name: str) -> Any:
|
|
43
|
+
return TYPED_RESOURCES.get(name) or TYPED_OVERRIDES.get(name) or GENERIC_RESOURCES.get(name)
|
|
44
|
+
|
|
45
|
+
def _dynamic_controller(self, name: str, strict: bool | None) -> ControllerProxy:
|
|
46
|
+
with self._lock:
|
|
47
|
+
cached = self._resources.get(name)
|
|
48
|
+
if cached is None:
|
|
49
|
+
cached = ControllerProxy(self, name, strict=strict)
|
|
50
|
+
self._resources[name] = cached
|
|
51
|
+
elif strict is not None:
|
|
52
|
+
cached._strict = strict
|
|
53
|
+
return cached
|
|
54
|
+
|
|
55
|
+
def controller(self, name: str, *, strict: bool | None = None) -> ControllerProxy:
|
|
56
|
+
if not _CONTROLLER_RE.match(name):
|
|
57
|
+
raise ConfigurationError(f"invalid controller name: {name!r}")
|
|
58
|
+
resource_cls = self._resource_class(name)
|
|
59
|
+
if resource_cls is not None:
|
|
60
|
+
return self._resource(name, resource_cls, strict=strict)
|
|
61
|
+
return self._dynamic_controller(name, strict)
|
|
62
|
+
|
|
63
|
+
def available_actions(self, controller: str) -> frozenset[str]:
|
|
64
|
+
return self._catalog.actions(controller)
|
|
65
|
+
|
|
66
|
+
def has_action(self, controller: str, action: str) -> bool:
|
|
67
|
+
return self._catalog.has_action(controller, action)
|
|
68
|
+
|
|
69
|
+
def list_actions(self, controller: str) -> frozenset[str]:
|
|
70
|
+
try:
|
|
71
|
+
return self._catalog.actions(controller)
|
|
72
|
+
except UnknownControllerError:
|
|
73
|
+
return frozenset()
|
|
74
|
+
|
|
75
|
+
def __getattr__(self, name: str) -> ControllerProxy:
|
|
76
|
+
if name.startswith("_"):
|
|
77
|
+
raise AttributeError(name)
|
|
78
|
+
if self._catalog_check and not self._catalog.has_controller(name):
|
|
79
|
+
raise AttributeError(f"unknown controller: {name}")
|
|
80
|
+
return self.controller(name)
|
|
81
|
+
|
|
82
|
+
@property
|
|
83
|
+
def administrators(self) -> AdministratorsResource:
|
|
84
|
+
return self._resource("administrators", AdministratorsResource)
|
|
85
|
+
|
|
86
|
+
@property
|
|
87
|
+
def users(self) -> UsersResource:
|
|
88
|
+
return self._resource("users", UsersResource)
|
|
89
|
+
|
|
90
|
+
@property
|
|
91
|
+
def groups(self) -> GroupsResource:
|
|
92
|
+
return self._resource("groups", GroupsResource)
|
|
93
|
+
|
|
94
|
+
@property
|
|
95
|
+
def fwrules(self) -> FirewallRulesResource:
|
|
96
|
+
return self._resource("fwrules", FirewallRulesResource)
|
|
97
|
+
|
|
98
|
+
@property
|
|
99
|
+
def netzones(self) -> NetzonesResource:
|
|
100
|
+
return self._resource("netzones", NetzonesResource)
|
|
101
|
+
|
|
102
|
+
@property
|
|
103
|
+
def interfaces(self) -> InterfacesResource:
|
|
104
|
+
return self._resource("interfaces", InterfacesResource)
|
|
105
|
+
|
|
106
|
+
@property
|
|
107
|
+
def networks(self) -> NetworksResource:
|
|
108
|
+
return self._resource("networks", NetworksResource)
|
|
109
|
+
|
|
110
|
+
@property
|
|
111
|
+
def settings(self) -> SettingsResource:
|
|
112
|
+
return self._resource("settings", SettingsResource)
|
|
113
|
+
|
|
114
|
+
@property
|
|
115
|
+
def certificates(self) -> CertificatesResource:
|
|
116
|
+
return self._resource("certificate", CertificatesResource)
|
|
117
|
+
|
|
118
|
+
@property
|
|
119
|
+
def licensing(self) -> LicensingResource:
|
|
120
|
+
return self._resource("utm", LicensingResource)
|
|
121
|
+
|
|
122
|
+
@property
|
|
123
|
+
def sysinfo(self) -> SysinfoResource:
|
|
124
|
+
return self._resource("sysinfo", SysinfoResource)
|
|
125
|
+
|
|
126
|
+
@property
|
|
127
|
+
def pcap(self) -> PcapResource:
|
|
128
|
+
return self._resource("pcap", PcapResource)
|
|
129
|
+
|
|
130
|
+
@property
|
|
131
|
+
def twofa(self) -> TwoFactorResource:
|
|
132
|
+
return self._resource("2fa", TwoFactorResource)
|
|
133
|
+
|
|
134
|
+
@property
|
|
135
|
+
def contentrules(self) -> ContentRulesResource:
|
|
136
|
+
return self._resource("contentrules", ContentRulesResource)
|
|
137
|
+
|
|
138
|
+
@property
|
|
139
|
+
def contenttypes(self) -> ContentTypesResource:
|
|
140
|
+
return self._resource("contenttypes", ContentTypesResource)
|
|
141
|
+
|
|
142
|
+
@property
|
|
143
|
+
def urlcategory(self) -> UrlCategoryResource:
|
|
144
|
+
return self._resource("urlcategory", UrlCategoryResource)
|
|
145
|
+
|
|
146
|
+
@property
|
|
147
|
+
def urlfiltering(self) -> UrlFilteringResource:
|
|
148
|
+
return self._resource("urlfiltering", UrlFilteringResource)
|
|
149
|
+
|
|
150
|
+
@property
|
|
151
|
+
def urllists(self) -> UrlListsResource:
|
|
152
|
+
return self._resource("urllists", UrlListsResource)
|
|
153
|
+
|
|
154
|
+
@property
|
|
155
|
+
def application(self) -> ApplicationResource:
|
|
156
|
+
return self._resource("application", ApplicationResource)
|
|
157
|
+
|
|
158
|
+
@property
|
|
159
|
+
def l7(self) -> L7Resource:
|
|
160
|
+
return self._resource("l7", L7Resource)
|
|
161
|
+
|
|
162
|
+
@property
|
|
163
|
+
def idps(self) -> IdpsResource:
|
|
164
|
+
return self._resource("idps", IdpsResource)
|
|
165
|
+
|
|
166
|
+
@property
|
|
167
|
+
def icap(self) -> IcapResource:
|
|
168
|
+
return self._resource("icap", IcapResource)
|
|
169
|
+
|
|
170
|
+
@property
|
|
171
|
+
def mailsecurity(self) -> MailSecurityResource:
|
|
172
|
+
return self._resource("mailsecurity", MailSecurityResource)
|
|
173
|
+
|
|
174
|
+
@property
|
|
175
|
+
def overridedomains(self) -> OverrideDomainsResource:
|
|
176
|
+
return self._resource("overridedomains", OverrideDomainsResource)
|
|
177
|
+
|
|
178
|
+
def _resource(
|
|
179
|
+
self,
|
|
180
|
+
key: str,
|
|
181
|
+
factory: type[_T],
|
|
182
|
+
*,
|
|
183
|
+
strict: bool | None = None,
|
|
184
|
+
) -> _T:
|
|
185
|
+
with self._lock:
|
|
186
|
+
cached = self._resources.get(key)
|
|
187
|
+
if cached is None or not isinstance(cached, factory):
|
|
188
|
+
cached = factory(self, strict=strict)
|
|
189
|
+
self._resources[key] = cached
|
|
190
|
+
elif strict is not None:
|
|
191
|
+
cached._strict = strict
|
|
192
|
+
return cached
|