user management fix
This commit is contained in:
@@ -0,0 +1,265 @@
|
||||
import json
|
||||
import socket
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
SOCKET_PATH = Path("/run/parental-control.sock")
|
||||
SOCKET_TIMEOUT = 10
|
||||
|
||||
|
||||
class IPCError(Exception):
|
||||
"""Base exception for IPC errors."""
|
||||
|
||||
class DatabaseError(IPCError):
|
||||
"""Raised when a database operation fails."""
|
||||
|
||||
class AuthorizationError(IPCError):
|
||||
"""Raised when an administrative operation is denied."""
|
||||
|
||||
|
||||
class IPCClient:
|
||||
@staticmethod
|
||||
def call(method, **arguments):
|
||||
request = {
|
||||
"method": method,
|
||||
"args": arguments,
|
||||
}
|
||||
|
||||
payload = (
|
||||
json.dumps(
|
||||
request,
|
||||
separators=(",", ":"),
|
||||
).encode("utf-8")
|
||||
+ b"\n"
|
||||
)
|
||||
|
||||
data = bytearray()
|
||||
|
||||
try:
|
||||
with socket.socket(
|
||||
socket.AF_UNIX,
|
||||
socket.SOCK_STREAM,
|
||||
) as connection:
|
||||
|
||||
connection.settimeout(
|
||||
SOCKET_TIMEOUT
|
||||
)
|
||||
|
||||
connection.connect(
|
||||
str(SOCKET_PATH)
|
||||
)
|
||||
|
||||
connection.sendall(
|
||||
payload
|
||||
)
|
||||
|
||||
while True:
|
||||
chunk = connection.recv(
|
||||
65536
|
||||
)
|
||||
|
||||
if not chunk:
|
||||
break
|
||||
|
||||
data.extend(chunk)
|
||||
|
||||
if b"\n" in chunk:
|
||||
break
|
||||
|
||||
except FileNotFoundError:
|
||||
raise IPCError(
|
||||
"Parental control service is not running."
|
||||
)
|
||||
|
||||
except ConnectionRefusedError:
|
||||
raise IPCError(
|
||||
"Could not connect to the parental control service."
|
||||
)
|
||||
|
||||
except TimeoutError:
|
||||
raise IPCError(
|
||||
"The parental control service did not respond."
|
||||
)
|
||||
|
||||
except OSError as exc:
|
||||
raise IPCError(
|
||||
f"Could not connect to the parental control service: {exc}"
|
||||
)
|
||||
|
||||
if not data:
|
||||
raise IPCError(
|
||||
"The parental control service returned no response."
|
||||
)
|
||||
|
||||
line = bytes(data).split(
|
||||
b"\n",
|
||||
1,
|
||||
)[0]
|
||||
|
||||
try:
|
||||
response = json.loads(
|
||||
line.decode("utf-8")
|
||||
)
|
||||
|
||||
except (
|
||||
UnicodeDecodeError,
|
||||
json.JSONDecodeError,
|
||||
):
|
||||
raise IPCError(
|
||||
"The parental control service returned an invalid response."
|
||||
)
|
||||
|
||||
if response.get("ok"):
|
||||
return response.get("result")
|
||||
|
||||
error = response.get(
|
||||
"error",
|
||||
"Unknown error",
|
||||
)
|
||||
|
||||
if response.get("error_type") == "authorization":
|
||||
raise AuthorizationError(error)
|
||||
|
||||
if response.get("error_type") == "database":
|
||||
raise DatabaseError(error)
|
||||
|
||||
raise IPCError(error)
|
||||
|
||||
|
||||
class Database:
|
||||
"""
|
||||
Compatibility interface for the GUI.
|
||||
|
||||
The GUI keeps using Database.* methods, but all operations
|
||||
now go through the root parental-control daemon instead of
|
||||
accessing SQLite directly.
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def get_users():
|
||||
return IPCClient.call(
|
||||
"get_users"
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def get_user(user_id):
|
||||
return IPCClient.call(
|
||||
"get_user",
|
||||
user_id=user_id,
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def get_remaining_time(user_id):
|
||||
result = IPCClient.call(
|
||||
"get_remaining_time",
|
||||
user_id=user_id,
|
||||
)
|
||||
|
||||
return (
|
||||
result[0],
|
||||
result[1],
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def get_allowances(user_id):
|
||||
result = IPCClient.call(
|
||||
"get_allowances",
|
||||
user_id=user_id,
|
||||
)
|
||||
|
||||
return {
|
||||
int(key): value
|
||||
for key, value in result.items()
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def get_access_windows(user_id):
|
||||
result = IPCClient.call(
|
||||
"get_access_windows",
|
||||
user_id=user_id,
|
||||
)
|
||||
|
||||
return {
|
||||
int(key): [
|
||||
tuple(window)
|
||||
for window in windows
|
||||
]
|
||||
for key, windows in result.items()
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def create_user(
|
||||
username,
|
||||
enabled,
|
||||
allowances,
|
||||
access_windows,
|
||||
):
|
||||
return IPCClient.call(
|
||||
"create_user",
|
||||
username=username,
|
||||
enabled=enabled,
|
||||
allowances={
|
||||
str(key): value
|
||||
for key, value in allowances.items()
|
||||
},
|
||||
access_windows={
|
||||
str(key): [
|
||||
list(window)
|
||||
for window in windows
|
||||
]
|
||||
for key, windows in access_windows.items()
|
||||
},
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def update_user(
|
||||
user_id,
|
||||
enabled,
|
||||
allowances,
|
||||
access_windows,
|
||||
):
|
||||
return IPCClient.call(
|
||||
"update_user",
|
||||
user_id=user_id,
|
||||
enabled=enabled,
|
||||
allowances={
|
||||
str(key): value
|
||||
for key, value in allowances.items()
|
||||
},
|
||||
access_windows={
|
||||
str(key): [
|
||||
list(window)
|
||||
for window in windows
|
||||
]
|
||||
for key, windows in access_windows.items()
|
||||
},
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def delete_user(user_id):
|
||||
return IPCClient.call(
|
||||
"delete_user",
|
||||
user_id=user_id,
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def set_enabled(
|
||||
user_id,
|
||||
enabled,
|
||||
):
|
||||
return IPCClient.call(
|
||||
"set_enabled",
|
||||
user_id=user_id,
|
||||
enabled=enabled,
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def add_temporary_time(
|
||||
user_id,
|
||||
seconds,
|
||||
):
|
||||
return IPCClient.call(
|
||||
"add_temporary_time",
|
||||
user_id=user_id,
|
||||
seconds=seconds,
|
||||
)
|
||||
Reference in New Issue
Block a user