fix: Audit-Findings — Bugs, Security-Hardening und Performance
- BUG-02: cancel_session bei mehrstufiger Auth (z.B. TOTP) - BUG-03: CalledProcessError bei reboot/shutdown abfangen - M-1+EH-01: configparser interpolation=None + Error-Handling - H-1: Exec-Cmd Validierung (absoluter Pfad erforderlich) - PERF: Theme-Guard, Default-Avatar-Cache, Avatar-Cache per User - Mutable Default Arguments in sessions.py (list → tuple) - Ungenutzten Gio-Import entfernt
This commit is contained in:
@@ -122,6 +122,38 @@ class TestLoginFlow:
|
||||
finally:
|
||||
mock.stop()
|
||||
|
||||
def test_multi_stage_auth_sends_cancel(self, tmp_path: Path) -> None:
|
||||
"""When greetd sends a second auth_message after password, cancel the session."""
|
||||
sock_path = tmp_path / "greetd.sock"
|
||||
mock = MockGreetd(sock_path)
|
||||
mock.expect({"type": "auth_message", "auth_message_type": "secret", "auth_message": "Password:"})
|
||||
mock.expect({"type": "auth_message", "auth_message_type": "secret", "auth_message": "TOTP:"})
|
||||
mock.expect({"type": "success"}) # Response to cancel_session
|
||||
mock.start()
|
||||
|
||||
try:
|
||||
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
||||
sock.connect(str(sock_path))
|
||||
|
||||
# Step 1: Create session
|
||||
response = create_session(sock, "dominik")
|
||||
assert response["type"] == "auth_message"
|
||||
|
||||
# Step 2: Send password — greetd responds with another auth_message
|
||||
response = post_auth_response(sock, "geheim")
|
||||
assert response["type"] == "auth_message"
|
||||
|
||||
# Step 3: Cancel because multi-stage auth is not supported
|
||||
response = cancel_session(sock)
|
||||
assert response["type"] == "success"
|
||||
|
||||
sock.close()
|
||||
finally:
|
||||
mock.stop()
|
||||
|
||||
# Verify cancel was sent
|
||||
assert mock.received[2] == {"type": "cancel_session"}
|
||||
|
||||
def test_cancel_session(self, tmp_path: Path) -> None:
|
||||
"""Simulate cancelling a session after create."""
|
||||
sock_path = tmp_path / "greetd.sock"
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
# ABOUTME: Tests for power actions — reboot and shutdown via loginctl.
|
||||
# ABOUTME: Uses mocking to avoid actually calling system commands.
|
||||
|
||||
import subprocess
|
||||
from unittest.mock import patch, call
|
||||
|
||||
import pytest
|
||||
@@ -19,6 +20,13 @@ class TestReboot:
|
||||
["loginctl", "reboot"], check=True
|
||||
)
|
||||
|
||||
@patch("moongreet.power.subprocess.run")
|
||||
def test_raises_on_failure(self, mock_run) -> None:
|
||||
mock_run.side_effect = subprocess.CalledProcessError(1, "loginctl")
|
||||
|
||||
with pytest.raises(subprocess.CalledProcessError):
|
||||
reboot()
|
||||
|
||||
|
||||
class TestShutdown:
|
||||
"""Tests for the shutdown power action."""
|
||||
@@ -30,3 +38,10 @@ class TestShutdown:
|
||||
mock_run.assert_called_once_with(
|
||||
["loginctl", "poweroff"], check=True
|
||||
)
|
||||
|
||||
@patch("moongreet.power.subprocess.run")
|
||||
def test_raises_on_failure(self, mock_run) -> None:
|
||||
mock_run.side_effect = subprocess.CalledProcessError(1, "loginctl")
|
||||
|
||||
with pytest.raises(subprocess.CalledProcessError):
|
||||
shutdown()
|
||||
|
||||
@@ -174,3 +174,40 @@ class TestGetUserGtkTheme:
|
||||
result = get_user_gtk_theme(config_dir=gtk_dir)
|
||||
|
||||
assert result is None
|
||||
|
||||
def test_returns_none_for_corrupt_settings_ini(self, tmp_path: Path) -> None:
|
||||
"""settings.ini without section header should not crash."""
|
||||
gtk_dir = tmp_path / ".config" / "gtk-4.0"
|
||||
gtk_dir.mkdir(parents=True)
|
||||
settings = gtk_dir / "settings.ini"
|
||||
settings.write_text("gtk-theme-name=Adwaita-dark\n")
|
||||
|
||||
result = get_user_gtk_theme(config_dir=gtk_dir)
|
||||
|
||||
assert result is None
|
||||
|
||||
def test_handles_interpolation_characters(self, tmp_path: Path) -> None:
|
||||
"""Theme names with % characters should not trigger interpolation errors."""
|
||||
gtk_dir = tmp_path / ".config" / "gtk-4.0"
|
||||
gtk_dir.mkdir(parents=True)
|
||||
settings = gtk_dir / "settings.ini"
|
||||
settings.write_text("[Settings]\ngtk-theme-name=My%Theme\n")
|
||||
|
||||
result = get_user_gtk_theme(config_dir=gtk_dir)
|
||||
|
||||
assert result == "My%Theme"
|
||||
|
||||
def test_ignores_symlinked_accountsservice_icon(self, tmp_path: Path) -> None:
|
||||
"""AccountsService icon as symlink should be ignored to prevent traversal."""
|
||||
icons_dir = tmp_path / "icons"
|
||||
icons_dir.mkdir()
|
||||
target = tmp_path / "secret.txt"
|
||||
target.write_text("sensitive data")
|
||||
icon = icons_dir / "attacker"
|
||||
icon.symlink_to(target)
|
||||
|
||||
result = get_avatar_path(
|
||||
"attacker", accountsservice_dir=icons_dir
|
||||
)
|
||||
|
||||
assert result is None
|
||||
|
||||
Reference in New Issue
Block a user