Rechtesystem-Lücke schließen: Live-Log war nur @login_required statt admin-geschützt

Vollständiges Audit aller Routen gegen das Rechtesystem durchgeführt. Die
Nav-Sichtbarkeit stufte "Live" (Live-Log) bereits korrekt als admin-only ein
(_nav_key_visible: logs_live -> user.is_admin), die zugehörigen Routen
/logs und /get_log prüften das aber nie tatsächlich — nur @login_required.
Jeder eingeloggte Benutzer, unabhängig von seinen Gruppen-Rechten, konnte
das Live-Log damit direkt per URL aufrufen, obwohl die Sidebar es korrekt
ausblendete.

- /logs: fehlende is_admin-Prüfung ergänzt (Flash + Redirect, konsistent zu
  activity_log()/users()/groups()/settings()).
- /get_log: fehlende is_admin-Prüfung ergänzt (403, da per AJAX abgerufen).

Alle anderen neu hinzugekommenen Seiten (Benutzer, Gruppen, Systemein-
stellungen, Im-/Export, Änderungslog, Jetzt-prüfen-Trigger) waren bereits
korrekt und bewusst exklusiv auf is_admin geprüft — dort gibt es keine
sinnvolle granularere Rechte-Stufe unterhalb von Admin (Gruppenverwaltung
steuert das Rechtesystem selbst, Systemeinstellungen/Import-Export/manuelle
Prüfung wirken global). Zugangsdaten bleiben bewusst an switches.*-Rechte
gekoppelt (bestehende, dokumentierte Design-Entscheidung). README um eine
Übersicht ergänzt, welche Bereiche bewusst ohne granulare Rechte auskommen
und warum.

Mit Testbenutzer ohne jede Gruppenzugehörigkeit verifiziert: /logs liefert
jetzt 302 (Redirect), /get_log liefert 403 statt beide zuvor 200.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-10 16:01:39 +02:00
co-authored by Claude Sonnet 5
parent c4f682b5de
commit 52b14b8ef7
2 changed files with 16 additions and 0 deletions
+11
View File
@@ -145,6 +145,17 @@ Aktion (Buttons, Toggle-Switches, Formulare) ist an das jeweilige Recht
gekoppelt — im Frontend ausgeblendet **und** im Backend serverseitig gekoppelt — im Frontend ausgeblendet **und** im Backend serverseitig
durchgesetzt, unabhängig vom Frontend. durchgesetzt, unabhängig vom Frontend.
**Bewusst ohne granulare Rechte, exklusiv Admins vorbehalten:** Benutzer,
Gruppen, Systemeinstellungen (Prüfintervall), Im-/Export, Änderungslog,
Live-Log und der manuelle „Jetzt prüfen“-Trigger. Für diese Bereiche gibt es
keine sinnvolle Teilmenge unterhalb von „Admin“ — Gruppenverwaltung steuert
direkt das Rechtesystem selbst (Delegation wäre eine Privilegien-Eskalation),
Systemeinstellungen/Import-Export/manuelle Prüfung wirken sich global aus,
und das Änderungslog protokolliert alle Benutzer systemweit. Live-Log und
`/get_log` waren zwischenzeitlich nur über `@login_required` statt echter
Admin-Prüfung abgesichert (die Sidebar blendete den Punkt zwar korrekt aus,
die Route selbst nicht) — inzwischen serverseitig nachgezogen.
Datenmodell: `groups` (inkl. `is_default`-Flag), `group_permissions` Datenmodell: `groups` (inkl. `is_default`-Flag), `group_permissions`
(Gruppe → Recht), `user_groups` (Benutzer → Gruppe). Bestehende Datenbanken (Gruppe → Recht), `user_groups` (Benutzer → Gruppe). Bestehende Datenbanken
werden beim App-Start automatisch migriert (`_ensure_schema()` in `app.py`, werden beim App-Start automatisch migriert (`_ensure_schema()` in `app.py`,
+5
View File
@@ -1626,6 +1626,8 @@ def _ssh_terminal_inner(ws):
@app.route("/get_log") @app.route("/get_log")
@login_required @login_required
def get_log(): def get_log():
if not current_user.is_admin:
return "Keine Berechtigung.", 403
latest_log = _latest_log_file() latest_log = _latest_log_file()
if not latest_log: if not latest_log:
return "Keine Logfiles gefunden." return "Keine Logfiles gefunden."
@@ -1645,6 +1647,9 @@ def get_log():
@app.route("/logs") @app.route("/logs")
@login_required @login_required
def logs(): def logs():
if not current_user.is_admin:
flash("Nur Admins dürfen das Live-Log einsehen.", "danger")
return redirect(url_for("index"))
interval = int(get_setting("interval", 5)) interval = int(get_setting("interval", 5))
latest_log = _latest_log_file() latest_log = _latest_log_file()