uiprotect 0.1.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.
Potentially problematic release.
This version of uiprotect might be problematic. Click here for more details.
- uiprotect/__init__.py +13 -0
- uiprotect/__main__.py +24 -0
- uiprotect/api.py +1936 -0
- uiprotect/cli/__init__.py +314 -0
- uiprotect/cli/backup.py +1103 -0
- uiprotect/cli/base.py +238 -0
- uiprotect/cli/cameras.py +574 -0
- uiprotect/cli/chimes.py +180 -0
- uiprotect/cli/doorlocks.py +125 -0
- uiprotect/cli/events.py +258 -0
- uiprotect/cli/lights.py +119 -0
- uiprotect/cli/liveviews.py +65 -0
- uiprotect/cli/nvr.py +154 -0
- uiprotect/cli/sensors.py +278 -0
- uiprotect/cli/viewers.py +76 -0
- uiprotect/data/__init__.py +157 -0
- uiprotect/data/base.py +1116 -0
- uiprotect/data/bootstrap.py +634 -0
- uiprotect/data/convert.py +77 -0
- uiprotect/data/devices.py +3384 -0
- uiprotect/data/nvr.py +1520 -0
- uiprotect/data/types.py +630 -0
- uiprotect/data/user.py +236 -0
- uiprotect/data/websocket.py +236 -0
- uiprotect/exceptions.py +41 -0
- uiprotect/py.typed +0 -0
- uiprotect/release_cache.json +1 -0
- uiprotect/stream.py +166 -0
- uiprotect/test_util/__init__.py +531 -0
- uiprotect/test_util/anonymize.py +257 -0
- uiprotect/utils.py +610 -0
- uiprotect/websocket.py +225 -0
- uiprotect-0.1.0.dist-info/LICENSE +23 -0
- uiprotect-0.1.0.dist-info/METADATA +245 -0
- uiprotect-0.1.0.dist-info/RECORD +37 -0
- uiprotect-0.1.0.dist-info/WHEEL +4 -0
- uiprotect-0.1.0.dist-info/entry_points.txt +3 -0
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import secrets
|
|
4
|
+
import string
|
|
5
|
+
import uuid
|
|
6
|
+
from typing import Any
|
|
7
|
+
from urllib.parse import urlparse
|
|
8
|
+
|
|
9
|
+
import typer
|
|
10
|
+
|
|
11
|
+
from uiprotect.data import ModelType
|
|
12
|
+
|
|
13
|
+
object_id_mapping: dict[str, str] = {}
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def anonymize_data(value: Any, name: str | None = None) -> Any:
|
|
17
|
+
if isinstance(value, list):
|
|
18
|
+
value = anonymize_list(value, name=name)
|
|
19
|
+
elif isinstance(value, dict):
|
|
20
|
+
value = anonymize_dict(value, name=name)
|
|
21
|
+
else:
|
|
22
|
+
value = anonymize_value(value, name=name)
|
|
23
|
+
|
|
24
|
+
return value
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def anonymize_user(user_dict: dict[str, Any]) -> dict[str, Any]:
|
|
28
|
+
for index, group_id in enumerate(user_dict.get("groups", [])):
|
|
29
|
+
user_dict["groups"][index] = anonymize_object_id(group_id)
|
|
30
|
+
|
|
31
|
+
user_dict["id"] = anonymize_object_id(user_dict["id"])
|
|
32
|
+
|
|
33
|
+
if "firstName" in user_dict:
|
|
34
|
+
user_dict["firstName"] = random_word().title()
|
|
35
|
+
user_dict["lastName"] = random_word().title()
|
|
36
|
+
user_dict["name"] = f"{user_dict['firstName']} {user_dict['lastName']}"
|
|
37
|
+
user_dict["localUsername"] = random_word()
|
|
38
|
+
user_dict["email"] = f"{user_dict['localUsername']}@example.com"
|
|
39
|
+
|
|
40
|
+
if "cloudAccount" in user_dict and user_dict["cloudAccount"] is not None:
|
|
41
|
+
user_dict["cloudAccount"]["firstName"] = user_dict["firstName"]
|
|
42
|
+
user_dict["cloudAccount"]["lastName"] = user_dict["lastName"]
|
|
43
|
+
user_dict["cloudAccount"]["name"] = user_dict["name"]
|
|
44
|
+
user_dict["cloudAccount"]["email"] = user_dict["email"]
|
|
45
|
+
user_dict["cloudAccount"]["user"] = anonymize_object_id(
|
|
46
|
+
user_dict["cloudAccount"]["user"],
|
|
47
|
+
)
|
|
48
|
+
user_dict["cloudAccount"]["id"] = anonymize_uuid(
|
|
49
|
+
user_dict["cloudAccount"]["id"],
|
|
50
|
+
)
|
|
51
|
+
user_dict["cloudAccount"]["cloudId"] = anonymize_uuid(
|
|
52
|
+
user_dict["cloudAccount"]["cloudId"],
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
camera_order = (user_dict.get("settings") or {}).get("cameraOrder")
|
|
56
|
+
if camera_order is not None:
|
|
57
|
+
for index, camera_id in enumerate(camera_order):
|
|
58
|
+
camera_order[index] = anonymize_object_id(camera_id)
|
|
59
|
+
user_dict["settings"]["cameraOrder"] = camera_order
|
|
60
|
+
|
|
61
|
+
if "allPermissions" in user_dict:
|
|
62
|
+
user_dict["allPermissions"] = anonymize_list(
|
|
63
|
+
user_dict["allPermissions"],
|
|
64
|
+
"allPermissions",
|
|
65
|
+
)
|
|
66
|
+
if "permissions" in user_dict:
|
|
67
|
+
user_dict["permissions"] = anonymize_list(
|
|
68
|
+
user_dict["permissions"],
|
|
69
|
+
"permissions",
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
return user_dict
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def anonymize_value(value: Any, name: str | None = None) -> Any:
|
|
76
|
+
if isinstance(value, str):
|
|
77
|
+
if name == "accessKey":
|
|
78
|
+
value = f"{random_number(13)}:{random_hex(24)}:{random_hex(128)}"
|
|
79
|
+
elif name == "credentials":
|
|
80
|
+
value = f"{random_hex(64)}"
|
|
81
|
+
elif name == "privateToken":
|
|
82
|
+
value = f"{random_alphanum(192)}"
|
|
83
|
+
elif name in {"host", "connectionHost", "bindAddr"}:
|
|
84
|
+
value = anonymize_ip(value)
|
|
85
|
+
elif name in {"anonymousDeviceId", "hardwareId"}:
|
|
86
|
+
value = random_identifier()
|
|
87
|
+
elif name in {"rtspAlias", "ssid"}:
|
|
88
|
+
value = random_alphanum(16)
|
|
89
|
+
elif name in {"mac", "server_id"}:
|
|
90
|
+
value = anonymize_peristent_string(value, random_hex(12).upper())
|
|
91
|
+
elif name == "bssid":
|
|
92
|
+
value = anonymize_peristent_string(value, random_seperated_mac())
|
|
93
|
+
elif name in {"latitude", "longitude"}:
|
|
94
|
+
value = "0.0"
|
|
95
|
+
elif name == "name" and value != "Default":
|
|
96
|
+
value = f"{random_word()} {random_word()}".title()
|
|
97
|
+
elif name in {"owner", "user", "camera", "liveview", "authUserId", "event"}:
|
|
98
|
+
value = anonymize_object_id(value)
|
|
99
|
+
elif name == "rtsp":
|
|
100
|
+
value = anonymize_rstp_url(value)
|
|
101
|
+
elif value.startswith("liveview:*:"):
|
|
102
|
+
liveview_id = value.split(":")[-1]
|
|
103
|
+
value = f"liveview:*:{anonymize_object_id(liveview_id)}"
|
|
104
|
+
|
|
105
|
+
return value
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def anonymize_dict(obj: dict[str, Any], name: str | None = None) -> dict[str, Any]:
|
|
109
|
+
obj_type = None
|
|
110
|
+
if "modelKey" in obj:
|
|
111
|
+
if obj["modelKey"] in [m.value for m in ModelType]:
|
|
112
|
+
obj_type = ModelType(obj["modelKey"])
|
|
113
|
+
else:
|
|
114
|
+
typer.secho(f"Unknown modelKey: {obj['modelKey']}", fg="yellow")
|
|
115
|
+
|
|
116
|
+
if obj_type == ModelType.USER:
|
|
117
|
+
return anonymize_user(obj)
|
|
118
|
+
|
|
119
|
+
for key, value in obj.items():
|
|
120
|
+
handled = False
|
|
121
|
+
if obj_type is not None or "payload" in obj:
|
|
122
|
+
if key == "id":
|
|
123
|
+
obj[key] = anonymize_object_id(value)
|
|
124
|
+
handled = True
|
|
125
|
+
elif obj_type == ModelType.EVENT:
|
|
126
|
+
if key in {"thumbnail", "heatmap"}:
|
|
127
|
+
obj[key] = anonymize_prefixed_event_id(value)
|
|
128
|
+
handled = True
|
|
129
|
+
elif key == "metadata":
|
|
130
|
+
if "sensorId" in obj[key]:
|
|
131
|
+
obj[key]["sensorId"]["text"] = anonymize_object_id(
|
|
132
|
+
obj[key]["sensorId"]["text"],
|
|
133
|
+
)
|
|
134
|
+
if "sensorName" in obj[key]:
|
|
135
|
+
obj[key]["sensorName"]["text"] = (
|
|
136
|
+
f"{random_word()} {random_word()}".title()
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
if not handled:
|
|
140
|
+
obj[key] = anonymize_data(value, name=key)
|
|
141
|
+
|
|
142
|
+
return obj
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def anonymize_list(items: list[Any], name: str | None = None) -> list[Any]:
|
|
146
|
+
for index, value in enumerate(items):
|
|
147
|
+
handled = False
|
|
148
|
+
|
|
149
|
+
if isinstance(value, str) and name in {
|
|
150
|
+
"hosts",
|
|
151
|
+
"smartDetectEvents",
|
|
152
|
+
"camera",
|
|
153
|
+
"cameras",
|
|
154
|
+
}:
|
|
155
|
+
handled = True
|
|
156
|
+
if name == "hosts":
|
|
157
|
+
items[index] = anonymize_ip(items[index])
|
|
158
|
+
elif name in {"smartDetectEvents", "camera", "cameras"}:
|
|
159
|
+
items[index] = anonymize_object_id(value)
|
|
160
|
+
|
|
161
|
+
if not handled:
|
|
162
|
+
items[index] = anonymize_data(value)
|
|
163
|
+
|
|
164
|
+
return items
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def anonymize_prefixed_event_id(event_id: str) -> str:
|
|
168
|
+
event_id = event_id[2:]
|
|
169
|
+
|
|
170
|
+
return f"e-{anonymize_object_id(event_id)}"
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def anonymize_ip(ip: Any) -> Any:
|
|
174
|
+
if not isinstance(ip, str):
|
|
175
|
+
return ip
|
|
176
|
+
|
|
177
|
+
if ip in {"0.0.0.0", "127.0.0.1", "255.255.255.255"}: # noqa: S104
|
|
178
|
+
return ip
|
|
179
|
+
|
|
180
|
+
return anonymize_peristent_string(ip, random_ip(ip))
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def anonymize_uuid(uuid_str: str) -> str:
|
|
184
|
+
return anonymize_peristent_string(uuid_str, random_identifier())
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def anonymize_object_id(obj_id: str) -> str:
|
|
188
|
+
return anonymize_peristent_string(obj_id, random_hex(24))
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def anonymize_peristent_string(value: str, default: str) -> str:
|
|
192
|
+
if value not in object_id_mapping:
|
|
193
|
+
object_id_mapping[value] = default
|
|
194
|
+
|
|
195
|
+
return object_id_mapping[value]
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
def anonymize_rstp_url(url: str) -> str:
|
|
199
|
+
parts = urlparse(url)
|
|
200
|
+
port = ""
|
|
201
|
+
if parts.port is not None and parts.port != 554:
|
|
202
|
+
port = f":{parts.port}"
|
|
203
|
+
|
|
204
|
+
return f"{parts.scheme}://{anonymize_ip(url)}{port}/{random_alphanum(16)}"
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
def random_hex(length: int) -> str:
|
|
208
|
+
return secrets.token_hex(length // 2)
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
def random_seperated_mac() -> str:
|
|
212
|
+
return ":".join(random_hex(2) for _ in range(6))
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
def random_str(length: int, choices: str) -> str:
|
|
216
|
+
return "".join(secrets.choice(choices) for _ in range(length))
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
def random_number(length: int) -> str:
|
|
220
|
+
return random_str(length, string.digits)
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
def random_word() -> str:
|
|
224
|
+
return random_char(secrets.randbelow(5) + 3)
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
def random_char(length: int) -> str:
|
|
228
|
+
return random_str(length, string.ascii_letters)
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
def random_alphanum(length: int) -> str:
|
|
232
|
+
choices = string.ascii_letters + string.ascii_letters.upper() + string.digits
|
|
233
|
+
return random_str(length, choices)
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
def random_ip(input_ip: str) -> str:
|
|
237
|
+
ip = ""
|
|
238
|
+
|
|
239
|
+
try:
|
|
240
|
+
octals = [int(i) for i in input_ip.split(".")]
|
|
241
|
+
except ValueError:
|
|
242
|
+
pass
|
|
243
|
+
else:
|
|
244
|
+
if octals[0] == 10:
|
|
245
|
+
ip = f"10.{secrets.randbelow(256)}.{secrets.randbelow(256)}.{secrets.randbelow(256)}"
|
|
246
|
+
elif octals[0] == 172 and 16 <= octals[1] <= 31:
|
|
247
|
+
ip = f"172.{secrets.randbelow(16) + 16}.{secrets.randbelow(256)}.{secrets.randbelow(256)}"
|
|
248
|
+
elif octals[0] == 192 and octals[1] == 168:
|
|
249
|
+
ip = f"192.168.{secrets.randbelow(256)}.{secrets.randbelow(256)}"
|
|
250
|
+
|
|
251
|
+
if not ip:
|
|
252
|
+
ip = f"{secrets.randbelow(255) + 1}.{secrets.randbelow(256)}.{secrets.randbelow(256)}.{secrets.randbelow(256)}"
|
|
253
|
+
return ip
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
def random_identifier() -> str:
|
|
257
|
+
return str(uuid.uuid4())
|