fix: Audit-Findings — Login-Thread, Traversable-Assets, Session-Exec
- Login-IPC in Background-Thread ausgelagert, UI friert nicht mehr ein bei langsamem PAM/greetd (PERF-2/BUG-4) - importlib.resources korrekt via as_file()/read_text() statt Path(str()) — funktioniert in ZIP-Wheels (BUG-1) - is_absolute()-Check für Session-Exec entfernt, greetd löst PATH selbst auf — relative Executables wie 'sway' blockieren nicht mehr (LOGIC-1) - ValueError (json.JSONDecodeError) im except abgefangen (BUG-2)
This commit is contained in:
parent
9a964aaecb
commit
65d3ba64f9
@ -6,7 +6,8 @@ import shlex
|
|||||||
import socket
|
import socket
|
||||||
import stat
|
import stat
|
||||||
import subprocess
|
import subprocess
|
||||||
from importlib.resources import files
|
import threading
|
||||||
|
from importlib.resources import as_file, files
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
import gi
|
import gi
|
||||||
@ -72,7 +73,9 @@ class GreeterWindow(Gtk.ApplicationWindow):
|
|||||||
# Background wallpaper (blurred and darkened)
|
# Background wallpaper (blurred and darkened)
|
||||||
bg_path = self._config.background
|
bg_path = self._config.background
|
||||||
if not bg_path or not bg_path.exists():
|
if not bg_path or not bg_path.exists():
|
||||||
bg_path = Path(str(DEFAULT_WALLPAPER_PATH))
|
# Extract package wallpaper to a real filesystem path (works in ZIP wheels too)
|
||||||
|
self._wallpaper_ctx = as_file(DEFAULT_WALLPAPER_PATH)
|
||||||
|
bg_path = self._wallpaper_ctx.__enter__()
|
||||||
if bg_path.exists():
|
if bg_path.exists():
|
||||||
background = Gtk.Picture()
|
background = Gtk.Picture()
|
||||||
background.set_filename(str(bg_path))
|
background.set_filename(str(bg_path))
|
||||||
@ -249,10 +252,10 @@ class GreeterWindow(Gtk.ApplicationWindow):
|
|||||||
)
|
)
|
||||||
if avatar_path and avatar_path.exists():
|
if avatar_path and avatar_path.exists():
|
||||||
self._set_avatar_from_file(avatar_path, user.username)
|
self._set_avatar_from_file(avatar_path, user.username)
|
||||||
elif DEFAULT_AVATAR_PATH.exists():
|
|
||||||
self._set_default_avatar()
|
|
||||||
else:
|
else:
|
||||||
self._avatar_image.set_from_icon_name("avatar-default-symbolic")
|
# Default avatar — _set_default_avatar uses Traversable.read_text()
|
||||||
|
# which works in ZIP wheels too, no exists() check needed
|
||||||
|
self._set_default_avatar()
|
||||||
|
|
||||||
# Apply user's GTK theme if available
|
# Apply user's GTK theme if available
|
||||||
self._apply_user_theme(user)
|
self._apply_user_theme(user)
|
||||||
@ -379,6 +382,11 @@ class GreeterWindow(Gtk.ApplicationWindow):
|
|||||||
pass
|
pass
|
||||||
self._greetd_sock = None
|
self._greetd_sock = None
|
||||||
|
|
||||||
|
def _set_login_sensitive(self, sensitive: bool) -> None:
|
||||||
|
"""Enable or disable login controls during authentication."""
|
||||||
|
self._password_entry.set_sensitive(sensitive)
|
||||||
|
self._session_dropdown.set_sensitive(sensitive)
|
||||||
|
|
||||||
def _attempt_login(self, user: User, password: str, session: Session) -> None:
|
def _attempt_login(self, user: User, password: str, session: Session) -> None:
|
||||||
"""Attempt to authenticate and start a session via greetd IPC."""
|
"""Attempt to authenticate and start a session via greetd IPC."""
|
||||||
sock_path = os.environ.get("GREETD_SOCK")
|
sock_path = os.environ.get("GREETD_SOCK")
|
||||||
@ -389,6 +397,17 @@ class GreeterWindow(Gtk.ApplicationWindow):
|
|||||||
if not self._validate_greetd_sock(sock_path):
|
if not self._validate_greetd_sock(sock_path):
|
||||||
return
|
return
|
||||||
|
|
||||||
|
# Disable UI while authenticating — the IPC runs in a background thread
|
||||||
|
self._set_login_sensitive(False)
|
||||||
|
thread = threading.Thread(
|
||||||
|
target=self._login_worker,
|
||||||
|
args=(user, password, session, sock_path),
|
||||||
|
daemon=True,
|
||||||
|
)
|
||||||
|
thread.start()
|
||||||
|
|
||||||
|
def _login_worker(self, user: User, password: str, session: Session, sock_path: str) -> None:
|
||||||
|
"""Run greetd IPC in a background thread to avoid blocking the GTK main loop."""
|
||||||
try:
|
try:
|
||||||
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
||||||
sock.settimeout(10.0)
|
sock.settimeout(10.0)
|
||||||
@ -399,8 +418,7 @@ class GreeterWindow(Gtk.ApplicationWindow):
|
|||||||
response = create_session(sock, user.username)
|
response = create_session(sock, user.username)
|
||||||
|
|
||||||
if response.get("type") == "error":
|
if response.get("type") == "error":
|
||||||
self._show_greetd_error(response, self._strings.auth_failed)
|
GLib.idle_add(self._on_login_error, response, self._strings.auth_failed)
|
||||||
self._close_greetd_sock()
|
|
||||||
return
|
return
|
||||||
|
|
||||||
# Step 2: Send password if auth message received
|
# Step 2: Send password if auth message received
|
||||||
@ -409,47 +427,59 @@ class GreeterWindow(Gtk.ApplicationWindow):
|
|||||||
|
|
||||||
if response.get("type") == "error":
|
if response.get("type") == "error":
|
||||||
self._failed_attempts[user.username] = self._failed_attempts.get(user.username, 0) + 1
|
self._failed_attempts[user.username] = self._failed_attempts.get(user.username, 0) + 1
|
||||||
self._show_greetd_error(response, self._strings.wrong_password)
|
|
||||||
warning = faillock_warning(self._failed_attempts[user.username], self._strings)
|
warning = faillock_warning(self._failed_attempts[user.username], self._strings)
|
||||||
if warning:
|
GLib.idle_add(self._on_login_auth_error, response, warning)
|
||||||
current = self._error_label.get_text()
|
|
||||||
self._error_label.set_text(f"{current}\n{warning}")
|
|
||||||
self._close_greetd_sock()
|
|
||||||
return
|
return
|
||||||
|
|
||||||
if response.get("type") == "auth_message":
|
if response.get("type") == "auth_message":
|
||||||
# Multi-stage auth (e.g. TOTP) is not supported
|
# Multi-stage auth (e.g. TOTP) is not supported
|
||||||
cancel_session(sock)
|
cancel_session(sock)
|
||||||
self._close_greetd_sock()
|
GLib.idle_add(self._on_login_error, None, self._strings.multi_stage_unsupported)
|
||||||
self._show_error(self._strings.multi_stage_unsupported)
|
|
||||||
return
|
return
|
||||||
|
|
||||||
# Step 3: Start session
|
# Step 3: Start session
|
||||||
if response.get("type") == "success":
|
if response.get("type") == "success":
|
||||||
cmd = shlex.split(session.exec_cmd)
|
cmd = shlex.split(session.exec_cmd)
|
||||||
if not cmd or not Path(cmd[0]).is_absolute():
|
if not cmd:
|
||||||
self._show_error(self._strings.invalid_session_command)
|
|
||||||
cancel_session(sock)
|
cancel_session(sock)
|
||||||
self._close_greetd_sock()
|
GLib.idle_add(self._on_login_error, None, self._strings.invalid_session_command)
|
||||||
return
|
return
|
||||||
response = start_session(sock, cmd)
|
response = start_session(sock, cmd)
|
||||||
|
|
||||||
if response.get("type") == "success":
|
if response.get("type") == "success":
|
||||||
self._save_last_user(user.username)
|
self._save_last_user(user.username)
|
||||||
self._close_greetd_sock()
|
self._close_greetd_sock()
|
||||||
self.get_application().quit()
|
GLib.idle_add(self.get_application().quit)
|
||||||
return
|
return
|
||||||
else:
|
else:
|
||||||
self._show_greetd_error(response, self._strings.session_start_failed)
|
GLib.idle_add(self._on_login_error, response, self._strings.session_start_failed)
|
||||||
|
|
||||||
self._close_greetd_sock()
|
self._close_greetd_sock()
|
||||||
|
|
||||||
except ConnectionError as e:
|
except ConnectionError as e:
|
||||||
self._close_greetd_sock()
|
self._close_greetd_sock()
|
||||||
self._show_error(self._strings.connection_error.format(error=e))
|
GLib.idle_add(self._on_login_error, None, self._strings.connection_error.format(error=e))
|
||||||
except OSError as e:
|
except (OSError, ValueError) as e:
|
||||||
self._close_greetd_sock()
|
self._close_greetd_sock()
|
||||||
self._show_error(self._strings.socket_error.format(error=e))
|
GLib.idle_add(self._on_login_error, None, self._strings.socket_error.format(error=e))
|
||||||
|
|
||||||
|
def _on_login_error(self, response: dict | None, message: str) -> None:
|
||||||
|
"""Handle login error on the GTK main thread."""
|
||||||
|
if response:
|
||||||
|
self._show_greetd_error(response, message)
|
||||||
|
else:
|
||||||
|
self._show_error(message)
|
||||||
|
self._close_greetd_sock()
|
||||||
|
self._set_login_sensitive(True)
|
||||||
|
|
||||||
|
def _on_login_auth_error(self, response: dict, warning: str | None) -> None:
|
||||||
|
"""Handle authentication failure with optional faillock warning on the GTK main thread."""
|
||||||
|
self._show_greetd_error(response, self._strings.wrong_password)
|
||||||
|
if warning:
|
||||||
|
current = self._error_label.get_text()
|
||||||
|
self._error_label.set_text(f"{current}\n{warning}")
|
||||||
|
self._close_greetd_sock()
|
||||||
|
self._set_login_sensitive(True)
|
||||||
|
|
||||||
def _cancel_pending_session(self) -> None:
|
def _cancel_pending_session(self) -> None:
|
||||||
"""Cancel any in-progress greetd session."""
|
"""Cancel any in-progress greetd session."""
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user