Gitea #2: Papierkorb bekommt eigenes Recht (papierkorb.view)

can_view_papierkorb leitete Sichtbarkeit bisher aus IRGENDEINEM der
fuenf Ressourcen-Leserechte (devices/switches/credentials/users/groups
.view) ab -- da die Standardgruppe 'Benutzer' bereits devices.view und
switches.view hat, konnte praktisch jeder Benutzer den Papierkorb sehen
(gemeldet: 'Papierkorb ist fuer alle sichtbar -> Rechtesystem').

Papierkorb hat jetzt ein eigenes, dediziertes Recht (papierkorb.view,
neuer Eintrag unter PERMISSIONS/devices_group/children, automatisch im
Gruppen-Editor sichtbar). Innerhalb der Seite bleibt jede Kachel
zusaetzlich einzeln je Ressource gated wie bisher -- papierkorb.view
schaltet nur die Seite selbst frei, keine automatischen Zusatzrechte.
Die Standardgruppe bekommt das neue Recht NICHT automatisch (das ist
der Fix) -- Admins behalten per is_admin-Bypass weiterhin vollen Zugriff.

Live auf POETEST verifiziert: ein Testnutzer mit exakt den
Standardgruppen-Rechten bekam vorher 200 auf /papierkorb, jetzt 302
(Zugriff verweigert), 'Papierkorb' verschwindet aus der Navigation,
/devices bleibt unveraendert erreichbar.

Gitea #1: LDAP-Login-Retry bei Domain-Controller in anderem Netzsegment

Gemeldet: bei einem DC in einem anderen (langsameren) Netzsegment war
ein doppelter Login noetig -- der erste Versuch scheiterte mit
'Benutzer/Passwort ungueltig', obwohl die Zugangsdaten korrekt waren.
Ursache: sowohl der Service- als auch der User-Bind nutzten ein hartes
5s-Timeout (connect_timeout/receive_timeout) ohne jeden Retry -- eine
erste, 'kalte' Verbindung zu einem entfernten/langsameren Netzsegment
kann das leicht ueberschreiten (ARP-/Routing-Aufwaermen, TCP-Handshake,
initiale Info-Abfrage), waehrend ein sofortiger zweiter Versuch dann
klappt, weil der Pfad bereits 'warm' ist.

Timeouts auf 10s angehoben und ein gezielter, genau EINMALIGER
automatischer Retry ergaenzt (_ldap_connect_with_retry) -- greift NUR
bei einem rein netzwerk-/timeout-bedingten Fehler (LDAPSocketOpenError/
LDAPSocketReceiveError/LDAPSocketSendError/LDAPResponseTimeoutError),
NICHT bei einer tatsaechlich abgelehnten Anmeldung (z.B. falsches
Passwort) -- ein Tippfehler soll keinen AD-Kontosperren-Zaehler
unnoetig doppelt hochzaehlen. Isoliert unit-getestet (transienter
Fehler -> 1 Retry -> Erfolg; echte Ablehnung -> kein Retry, sofortige
Exception) und auf POETEST regressionsgetestet (LDAP dort weiterhin
aktiviert und Dienst startet fehlerfrei).

Zusaetzlich: /login aktiv gegen SQL-Injection/Login-Bypass/SSTI-Payloads
getestet (13 Payload-Varianten inkl. UNION SELECT, DROP TABLE, Jinja-
SSTI) -- keine Auffaelligkeiten, durchgehend parametrisierte Queries
bestaetigt, users-Tabelle unveraendert.
This commit is contained in:
2026-08-22 09:31:24 +02:00
parent 33753e4f41
commit 6ec9e5fd56
+55 -15
View File
@@ -23,7 +23,13 @@ import paramiko
import yaml
import ssl
from ldap3 import Server, Connection, Tls, ALL, SIMPLE, BASE
from ldap3.core.exceptions import LDAPException
from ldap3.core.exceptions import (
LDAPException,
LDAPSocketOpenError,
LDAPSocketReceiveError,
LDAPSocketSendError,
LDAPResponseTimeoutError,
)
from ldap3.utils.conv import escape_filter_chars
LDAP_MATCHING_RULE_IN_CHAIN = "1.2.840.113556.1.4.1941"
@@ -196,6 +202,12 @@ PERMISSIONS = {
"edit": "maintenance.edit",
},
},
"papierkorb": {
"label": "Papierkorb",
"rows": {
"view": "papierkorb.view",
},
},
},
},
"logs_group": {
@@ -264,6 +276,7 @@ PERMISSION_LABELS = {
"credentials.edit": "Zugangsdaten ändern (inkl. Löschen)",
"maintenance.view": "Wartungsseite ansehen (Geräte-Status, Job-Verlauf)",
"maintenance.edit": "Wartungsaktionen ausführen (SSH-Update, SSH-Neustart)",
"papierkorb.view": "Papierkorb ansehen (gelöschte Geräte/Switche/Zugangsdaten/Benutzer/Gruppen — jede Kategorie zusätzlich nur mit deren eigenem Leserecht sichtbar)",
"logs_live.view": "Live-Log lesen", "logs_activity.view": "Änderungslog lesen",
"logs_kea.view": "Kea-DHCP-Log lesen",
"users.view": "Benutzer lesen", "users.create": "Benutzer anlegen",
@@ -516,13 +529,14 @@ class User(UserMixin):
@property
def can_view_papierkorb(self):
"""Papierkorb-Seite: sichtbar sobald mind. eine der fünf dort
gelisteten Ressourcen (Geräte/Switche/Zugangsdaten/Benutzer/Gruppen)
View-Recht hat jede Kachel darin ist ohnehin einzeln je Ressource
gated (siehe papierkorb.html)."""
return self.has_any_permission(
["devices.view", "switches.view", "credentials.view", "users.view", "groups.view"]
)
"""Papierkorb-Seite: eigenes, dediziertes Recht (papierkorb.view) --
vorher implizit sichtbar für jeden mit irgendeiner der fünf
Ressourcen-Leserechte, wodurch praktisch jeder Benutzer den
Papierkorb sehen konnte (Standardgruppe hat bereits devices.view).
Innerhalb der Seite ist jede Kachel zusätzlich einzeln je Ressource
gated (siehe papierkorb.html) -- papierkorb.view allein zeigt also
noch keine Inhalte, sondern schaltet nur die Seite selbst frei."""
return self.has_permission("papierkorb.view")
@property
def can_view_live_log(self):
@@ -1232,6 +1246,32 @@ def _ldap_settings():
}
_LDAP_TRANSIENT_EXCEPTIONS = (
LDAPSocketOpenError,
LDAPSocketReceiveError,
LDAPSocketSendError,
LDAPResponseTimeoutError,
)
def _ldap_connect_with_retry(factory):
"""Baut eine LDAP-Verbindung auf, mit genau einem automatischen
Wiederholversuch bei einem rein netzwerk-/timeout-bedingten Fehler
(z.B. ein Domain-Controller in einem anderen, langsameren Netzsegment
-- die allererste Verbindung dorthin ist oft spürbar langsamer als
jede folgende, live reproduziert: erster Login schlug mit "Benutzer/
Passwort ungültig" fehl, ein sofortiger zweiter Versuch klappte). Der
Retry greift NICHT bei einer tatsächlich abgelehnten Anmeldung (z.B.
falsches Passwort, eigene Exception-Klasse in ldap3) -- sonst würde
ein einzelner Tippfehler beim Passwort einen AD-Kontosperren-Zähler
unnötig doppelt statt einmal hochzählen. factory ist ein parameterloser
Callable, das die Verbindung aufbaut (auto_bind=True) und zurückgibt."""
try:
return factory()
except _LDAP_TRANSIENT_EXCEPTIONS:
return factory()
def _ldap_connect_service(cfg):
"""Baut Server-Objekt + Service-Konto-Verbindung auf. Wirft nie —
liefert (server, connection) oder (None, None) samt Fehlermeldung im
@@ -1248,11 +1288,11 @@ def _ldap_connect_service(cfg):
tls = None
if cfg["use_ssl"] and cfg.get("tls_skip_verify"):
tls = Tls(validate=ssl.CERT_NONE)
server = Server(cfg["server"], port=port, use_ssl=cfg["use_ssl"], tls=tls, get_info=ALL, connect_timeout=5)
conn = Connection(
server = Server(cfg["server"], port=port, use_ssl=cfg["use_ssl"], tls=tls, get_info=ALL, connect_timeout=10)
conn = _ldap_connect_with_retry(lambda: Connection(
server, user=cfg["bind_dn"], password=bind_password,
authentication=SIMPLE, auto_bind=True, receive_timeout=5,
)
authentication=SIMPLE, auto_bind=True, receive_timeout=10,
))
return (server, conn), None
except LDAPException as e:
return None, str(e)
@@ -1563,10 +1603,10 @@ def _ldap_authenticate(username, password):
pass
try:
user_conn = Connection(
user_conn = _ldap_connect_with_retry(lambda: Connection(
server, user=user_dn, password=password,
authentication=SIMPLE, auto_bind=True, receive_timeout=5,
)
authentication=SIMPLE, auto_bind=True, receive_timeout=10,
))
user_conn.unbind()
except LDAPException:
return False, None