user management fix
This commit is contained in:
+60
-5
@@ -1,6 +1,8 @@
|
|||||||
|
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
import socket
|
import socket
|
||||||
|
import sqlite3
|
||||||
import struct
|
import struct
|
||||||
import subprocess
|
import subprocess
|
||||||
import threading
|
import threading
|
||||||
@@ -16,6 +18,10 @@ BUFFER_SIZE = 65536
|
|||||||
POLKIT_ACTION = "org.sarlink.parentalcontrol.modify"
|
POLKIT_ACTION = "org.sarlink.parentalcontrol.modify"
|
||||||
|
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# Peer credentials and authorization
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
def get_peer_credentials(connection):
|
def get_peer_credentials(connection):
|
||||||
credentials = connection.getsockopt(
|
credentials = connection.getsockopt(
|
||||||
socket.SOL_SOCKET,
|
socket.SOL_SOCKET,
|
||||||
@@ -26,7 +32,19 @@ def get_peer_credentials(connection):
|
|||||||
return struct.unpack("3i", credentials)
|
return struct.unpack("3i", credentials)
|
||||||
|
|
||||||
|
|
||||||
def authorize_write(pid: int) -> bool:
|
def authorize_write(pid: int, uid: int) -> bool:
|
||||||
|
"""
|
||||||
|
Authorize administrative operations.
|
||||||
|
|
||||||
|
Root is automatically authorized.
|
||||||
|
|
||||||
|
Non-root users must pass Polkit authorization.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Root is already an administrator.
|
||||||
|
if uid == 0:
|
||||||
|
return True
|
||||||
|
|
||||||
try:
|
try:
|
||||||
os.kill(pid, 0)
|
os.kill(pid, 0)
|
||||||
except OSError:
|
except OSError:
|
||||||
@@ -53,6 +71,10 @@ def authorize_write(pid: int) -> bool:
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# Database operations
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
class IPCOperations:
|
class IPCOperations:
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@@ -359,6 +381,10 @@ class IPCOperations:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# Allowed IPC methods
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
READ_METHODS = {
|
READ_METHODS = {
|
||||||
"get_users",
|
"get_users",
|
||||||
"get_user",
|
"get_user",
|
||||||
@@ -377,7 +403,15 @@ WRITE_METHODS = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def handle_request(request, pid):
|
# ============================================================
|
||||||
|
# Request handling
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
|
def handle_request(
|
||||||
|
request,
|
||||||
|
pid,
|
||||||
|
uid,
|
||||||
|
):
|
||||||
method = request.get("method")
|
method = request.get("method")
|
||||||
arguments = request.get("args", {})
|
arguments = request.get("args", {})
|
||||||
|
|
||||||
@@ -396,7 +430,7 @@ def handle_request(request, pid):
|
|||||||
return operation(**arguments)
|
return operation(**arguments)
|
||||||
|
|
||||||
if method in WRITE_METHODS:
|
if method in WRITE_METHODS:
|
||||||
if not authorize_write(pid):
|
if not authorize_write(pid, uid):
|
||||||
raise PermissionError(
|
raise PermissionError(
|
||||||
"Administrative authorization required"
|
"Administrative authorization required"
|
||||||
)
|
)
|
||||||
@@ -419,6 +453,10 @@ def handle_request(request, pid):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# Client handling
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
def handle_client(connection):
|
def handle_client(connection):
|
||||||
try:
|
try:
|
||||||
pid, uid, gid = get_peer_credentials(
|
pid, uid, gid = get_peer_credentials(
|
||||||
@@ -456,6 +494,7 @@ def handle_client(connection):
|
|||||||
result = handle_request(
|
result = handle_request(
|
||||||
request,
|
request,
|
||||||
pid,
|
pid,
|
||||||
|
uid,
|
||||||
)
|
)
|
||||||
|
|
||||||
response = {
|
response = {
|
||||||
@@ -470,6 +509,13 @@ def handle_client(connection):
|
|||||||
"error_type": "authorization",
|
"error_type": "authorization",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
except sqlite3.IntegrityError as exc:
|
||||||
|
response = {
|
||||||
|
"ok": False,
|
||||||
|
"error": str(exc),
|
||||||
|
"error_type": "database",
|
||||||
|
}
|
||||||
|
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
response = {
|
response = {
|
||||||
"ok": False,
|
"ok": False,
|
||||||
@@ -500,6 +546,10 @@ def handle_client(connection):
|
|||||||
connection.close()
|
connection.close()
|
||||||
|
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# IPC server
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
class IPCServer:
|
class IPCServer:
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
@@ -515,7 +565,10 @@ class IPCServer:
|
|||||||
self._stop_event = threading.Event()
|
self._stop_event = threading.Event()
|
||||||
|
|
||||||
def start(self):
|
def start(self):
|
||||||
if self._thread is not None:
|
if (
|
||||||
|
self._thread is not None
|
||||||
|
and self._thread.is_alive()
|
||||||
|
):
|
||||||
return
|
return
|
||||||
|
|
||||||
self.socket_path.parent.mkdir(
|
self.socket_path.parent.mkdir(
|
||||||
@@ -564,7 +617,9 @@ class IPCServer:
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
if self._thread is not None:
|
if self._thread is not None:
|
||||||
self._thread.join(timeout=2)
|
self._thread.join(
|
||||||
|
timeout=2
|
||||||
|
)
|
||||||
|
|
||||||
self._thread = None
|
self._thread = None
|
||||||
self.server_socket = None
|
self.server_socket = None
|
||||||
|
|||||||
@@ -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,
|
||||||
|
)
|
||||||
+2
-386
@@ -1,6 +1,5 @@
|
|||||||
import sys
|
import sys
|
||||||
import pwd
|
import pwd
|
||||||
import sqlite3
|
|
||||||
|
|
||||||
from PySide6.QtCore import Qt, QTime
|
from PySide6.QtCore import Qt, QTime
|
||||||
from PySide6.QtWidgets import (
|
from PySide6.QtWidgets import (
|
||||||
@@ -26,389 +25,7 @@ from PySide6.QtWidgets import (
|
|||||||
QWidget,
|
QWidget,
|
||||||
)
|
)
|
||||||
|
|
||||||
from app.database import DATABASE_PATH
|
from .client import Database, DatabaseError
|
||||||
|
|
||||||
|
|
||||||
# ============================================================
|
|
||||||
# Database
|
|
||||||
# ============================================================
|
|
||||||
|
|
||||||
class Database:
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def get_connection():
|
|
||||||
connection = sqlite3.connect(DATABASE_PATH)
|
|
||||||
connection.row_factory = sqlite3.Row
|
|
||||||
connection.execute("PRAGMA foreign_keys = ON")
|
|
||||||
return connection
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def get_users():
|
|
||||||
if not DATABASE_PATH.exists():
|
|
||||||
return []
|
|
||||||
|
|
||||||
connection = Database.get_connection()
|
|
||||||
|
|
||||||
try:
|
|
||||||
return connection.execute(
|
|
||||||
"""
|
|
||||||
SELECT *
|
|
||||||
FROM users
|
|
||||||
ORDER BY username COLLATE NOCASE
|
|
||||||
"""
|
|
||||||
).fetchall()
|
|
||||||
finally:
|
|
||||||
connection.close()
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def get_user(user_id):
|
|
||||||
connection = Database.get_connection()
|
|
||||||
|
|
||||||
try:
|
|
||||||
return connection.execute(
|
|
||||||
"""
|
|
||||||
SELECT *
|
|
||||||
FROM users
|
|
||||||
WHERE id = ?
|
|
||||||
""",
|
|
||||||
(user_id,),
|
|
||||||
).fetchone()
|
|
||||||
finally:
|
|
||||||
connection.close()
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def get_remaining_time(user_id):
|
|
||||||
if not DATABASE_PATH.exists():
|
|
||||||
return 0, 0
|
|
||||||
|
|
||||||
connection = Database.get_connection()
|
|
||||||
|
|
||||||
try:
|
|
||||||
allowance_row = connection.execute(
|
|
||||||
"""
|
|
||||||
SELECT COALESCE(
|
|
||||||
allowance_seconds,
|
|
||||||
0
|
|
||||||
) AS total
|
|
||||||
FROM daily_allowances
|
|
||||||
WHERE user_id = ?
|
|
||||||
AND weekday = CAST(
|
|
||||||
strftime('%w', 'now', 'localtime')
|
|
||||||
AS INTEGER
|
|
||||||
)
|
|
||||||
""",
|
|
||||||
(user_id,),
|
|
||||||
).fetchone()
|
|
||||||
|
|
||||||
allowance = (
|
|
||||||
allowance_row["total"]
|
|
||||||
if allowance_row
|
|
||||||
else 0
|
|
||||||
)
|
|
||||||
|
|
||||||
usage_row = connection.execute(
|
|
||||||
"""
|
|
||||||
SELECT COALESCE(
|
|
||||||
used_seconds,
|
|
||||||
0
|
|
||||||
) AS total
|
|
||||||
FROM usage
|
|
||||||
WHERE user_id = ?
|
|
||||||
AND date = date('now', 'localtime')
|
|
||||||
""",
|
|
||||||
(user_id,),
|
|
||||||
).fetchone()
|
|
||||||
|
|
||||||
used = (
|
|
||||||
usage_row["total"]
|
|
||||||
if usage_row
|
|
||||||
else 0
|
|
||||||
)
|
|
||||||
|
|
||||||
remaining = max(
|
|
||||||
allowance - used,
|
|
||||||
0,
|
|
||||||
)
|
|
||||||
|
|
||||||
return remaining, allowance
|
|
||||||
|
|
||||||
finally:
|
|
||||||
connection.close()
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def get_allowances(user_id):
|
|
||||||
connection = Database.get_connection()
|
|
||||||
|
|
||||||
try:
|
|
||||||
rows = connection.execute(
|
|
||||||
"""
|
|
||||||
SELECT weekday, allowance_seconds
|
|
||||||
FROM daily_allowances
|
|
||||||
WHERE user_id = ?
|
|
||||||
""",
|
|
||||||
(user_id,),
|
|
||||||
).fetchall()
|
|
||||||
|
|
||||||
return {
|
|
||||||
row["weekday"]: row["allowance_seconds"]
|
|
||||||
for row in rows
|
|
||||||
}
|
|
||||||
|
|
||||||
finally:
|
|
||||||
connection.close()
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def get_access_windows(user_id):
|
|
||||||
connection = Database.get_connection()
|
|
||||||
|
|
||||||
try:
|
|
||||||
rows = connection.execute(
|
|
||||||
"""
|
|
||||||
SELECT
|
|
||||||
weekday,
|
|
||||||
start_minute,
|
|
||||||
end_minute
|
|
||||||
FROM access_windows
|
|
||||||
WHERE user_id = ?
|
|
||||||
ORDER BY weekday, start_minute
|
|
||||||
""",
|
|
||||||
(user_id,),
|
|
||||||
).fetchall()
|
|
||||||
|
|
||||||
result = {}
|
|
||||||
|
|
||||||
for row in rows:
|
|
||||||
result.setdefault(
|
|
||||||
row["weekday"],
|
|
||||||
[],
|
|
||||||
).append(
|
|
||||||
(
|
|
||||||
row["start_minute"],
|
|
||||||
row["end_minute"],
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
return result
|
|
||||||
|
|
||||||
finally:
|
|
||||||
connection.close()
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def create_user(
|
|
||||||
username,
|
|
||||||
enabled,
|
|
||||||
allowances,
|
|
||||||
access_windows,
|
|
||||||
):
|
|
||||||
connection = Database.get_connection()
|
|
||||||
|
|
||||||
try:
|
|
||||||
cursor = connection.execute(
|
|
||||||
"""
|
|
||||||
INSERT INTO users (
|
|
||||||
username,
|
|
||||||
enabled
|
|
||||||
)
|
|
||||||
VALUES (?, ?)
|
|
||||||
""",
|
|
||||||
(
|
|
||||||
username,
|
|
||||||
1 if enabled else 0,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
user_id = cursor.lastrowid
|
|
||||||
|
|
||||||
for weekday, seconds in allowances.items():
|
|
||||||
connection.execute(
|
|
||||||
"""
|
|
||||||
INSERT INTO daily_allowances (
|
|
||||||
user_id,
|
|
||||||
weekday,
|
|
||||||
allowance_seconds
|
|
||||||
)
|
|
||||||
VALUES (?, ?, ?)
|
|
||||||
""",
|
|
||||||
(
|
|
||||||
user_id,
|
|
||||||
weekday,
|
|
||||||
seconds,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
for weekday, windows in access_windows.items():
|
|
||||||
for start_minute, end_minute in windows:
|
|
||||||
connection.execute(
|
|
||||||
"""
|
|
||||||
INSERT INTO access_windows (
|
|
||||||
user_id,
|
|
||||||
weekday,
|
|
||||||
start_minute,
|
|
||||||
end_minute
|
|
||||||
)
|
|
||||||
VALUES (?, ?, ?, ?)
|
|
||||||
""",
|
|
||||||
(
|
|
||||||
user_id,
|
|
||||||
weekday,
|
|
||||||
start_minute,
|
|
||||||
end_minute,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
connection.commit()
|
|
||||||
|
|
||||||
return user_id
|
|
||||||
|
|
||||||
finally:
|
|
||||||
connection.close()
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def update_user(
|
|
||||||
user_id,
|
|
||||||
enabled,
|
|
||||||
allowances,
|
|
||||||
access_windows,
|
|
||||||
):
|
|
||||||
connection = Database.get_connection()
|
|
||||||
|
|
||||||
try:
|
|
||||||
connection.execute(
|
|
||||||
"""
|
|
||||||
UPDATE users
|
|
||||||
SET enabled = ?
|
|
||||||
WHERE id = ?
|
|
||||||
""",
|
|
||||||
(
|
|
||||||
1 if enabled else 0,
|
|
||||||
user_id,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
for weekday, seconds in allowances.items():
|
|
||||||
connection.execute(
|
|
||||||
"""
|
|
||||||
INSERT INTO daily_allowances (
|
|
||||||
user_id,
|
|
||||||
weekday,
|
|
||||||
allowance_seconds
|
|
||||||
)
|
|
||||||
VALUES (?, ?, ?)
|
|
||||||
ON CONFLICT(user_id, weekday)
|
|
||||||
DO UPDATE SET
|
|
||||||
allowance_seconds =
|
|
||||||
excluded.allowance_seconds
|
|
||||||
""",
|
|
||||||
(
|
|
||||||
user_id,
|
|
||||||
weekday,
|
|
||||||
seconds,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
# Replace access windows completely.
|
|
||||||
connection.execute(
|
|
||||||
"""
|
|
||||||
DELETE FROM access_windows
|
|
||||||
WHERE user_id = ?
|
|
||||||
""",
|
|
||||||
(user_id,),
|
|
||||||
)
|
|
||||||
|
|
||||||
for weekday, windows in access_windows.items():
|
|
||||||
for start_minute, end_minute in windows:
|
|
||||||
connection.execute(
|
|
||||||
"""
|
|
||||||
INSERT INTO access_windows (
|
|
||||||
user_id,
|
|
||||||
weekday,
|
|
||||||
start_minute,
|
|
||||||
end_minute
|
|
||||||
)
|
|
||||||
VALUES (?, ?, ?, ?)
|
|
||||||
""",
|
|
||||||
(
|
|
||||||
user_id,
|
|
||||||
weekday,
|
|
||||||
start_minute,
|
|
||||||
end_minute,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
connection.commit()
|
|
||||||
|
|
||||||
finally:
|
|
||||||
connection.close()
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def delete_user(user_id):
|
|
||||||
connection = Database.get_connection()
|
|
||||||
|
|
||||||
try:
|
|
||||||
connection.execute(
|
|
||||||
"""
|
|
||||||
DELETE FROM users
|
|
||||||
WHERE id = ?
|
|
||||||
""",
|
|
||||||
(user_id,),
|
|
||||||
)
|
|
||||||
|
|
||||||
connection.commit()
|
|
||||||
|
|
||||||
finally:
|
|
||||||
connection.close()
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def set_enabled(user_id, enabled):
|
|
||||||
connection = Database.get_connection()
|
|
||||||
|
|
||||||
try:
|
|
||||||
connection.execute(
|
|
||||||
"""
|
|
||||||
UPDATE users
|
|
||||||
SET enabled = ?
|
|
||||||
WHERE id = ?
|
|
||||||
""",
|
|
||||||
(
|
|
||||||
1 if enabled else 0,
|
|
||||||
user_id,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
connection.commit()
|
|
||||||
|
|
||||||
finally:
|
|
||||||
connection.close()
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def add_temporary_time(
|
|
||||||
user_id,
|
|
||||||
seconds,
|
|
||||||
):
|
|
||||||
connection = Database.get_connection()
|
|
||||||
|
|
||||||
try:
|
|
||||||
connection.execute(
|
|
||||||
"""
|
|
||||||
INSERT INTO temporary_grants (
|
|
||||||
user_id,
|
|
||||||
seconds,
|
|
||||||
remaining_seconds
|
|
||||||
)
|
|
||||||
VALUES (?, ?, ?)
|
|
||||||
""",
|
|
||||||
(
|
|
||||||
user_id,
|
|
||||||
seconds,
|
|
||||||
seconds,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
connection.commit()
|
|
||||||
|
|
||||||
finally:
|
|
||||||
connection.close()
|
|
||||||
|
|
||||||
|
|
||||||
# ============================================================
|
# ============================================================
|
||||||
# Linux users
|
# Linux users
|
||||||
@@ -421,7 +38,6 @@ def linux_user_exists(username):
|
|||||||
except KeyError:
|
except KeyError:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
# ============================================================
|
# ============================================================
|
||||||
# Helpers
|
# Helpers
|
||||||
# ============================================================
|
# ============================================================
|
||||||
@@ -1464,7 +1080,7 @@ class UserDialog(QDialog):
|
|||||||
access_windows,
|
access_windows,
|
||||||
)
|
)
|
||||||
|
|
||||||
except sqlite3.IntegrityError:
|
except DatabaseError:
|
||||||
|
|
||||||
QMessageBox.warning(
|
QMessageBox.warning(
|
||||||
self,
|
self,
|
||||||
|
|||||||
Reference in New Issue
Block a user