""" Decoratoren zur Authentifizierungs- und Autorisierungsprüfung. Der Decorator authorisation_required hebt die Rollenprüfung an die Route-Ebene, analog zu flask_login.login_required. Authentifizierung (login_required) ist eingebettet, sodass pro Endpunkt nur ein Decorator nötig ist. :author: Mistral Vibe :date: 2026-09-02 :version: 1.0 """ from functools import wraps from flask import current_app from flask_login import current_user, login_required from auth.model import APP_ADMIN from auth.model.errors import AutorisationError def authorisation_required(allowed_roles=[]): """ Decorator, der sicherstellt, dass der angemeldete Benutzer für den Endpunkt autorisiert ist. Authentifizierung (login_required) ist eingebettet, sodass kein zusätzlicher @login_required-Aufruf nötig ist. Aspekte der Autorisierung (Prüfreihenfolge): 1. SysAdmin (systemweite Super-Rolle): Der SysAdmin darf alles, unabhängig von der Anwendung. Er braucht in keiner Application eine Rolle zugewiesen zu haben. Die Prüfung erfolgt über User.is_SysAdmin() (app-übergreifend). Dies ist die einzige Rolle, die applikationsübergreifend gilt — sie ist eine Systemrolle, keine App-Rolle. 2. AppAdmin (anwendungsbezogene Verwaltungsrolle): Der AppAdmin darf innerhalb der Anwendung alles, der er zugeordnet ist. Er braucht die Rolle AppAdmin nur in der aktuellen Anwendung. Der Decorator ergänzt allowed_roles intern um APP_ADMIN, sodass der AppAdmin jeden Endpunkt ohne explizite Nennung in allowed_roles passiert. Die Prüfung erfolgt über User.has_permission(appid, [..., APP_ADMIN]) — app-spezifisch, nicht systemweit. 3. Benutzer mit Basis-Rolle 'User' (Mitarbeiter): Jeder Anwender hat zwingend die Basis-Rolle 'User' (Mitarbeiter), damit er das Login-Gate in auth/routes.checklogin passiert. Diese Rolle allein berechtigt nur zu Lese-Endpunkten, bei denen MITARBEITER in allowed_roles steht. Für Schreib-Endpunkte reicht 'User' nicht. 4. Benutzer mit Zusatz-Rollen (z.B. KundenManager, später ProjektManager): Zusätzlich zur Basis-Rolle 'User' können einem Benutzer weitere Rollen zugewiesen werden. Diese gewähren weitergehende Berechtigungen für bestimmte Entitäten. Die Prüfung erfolgt app-spezifisch über User.has_permission(appid, allowed_roles). Der Benutzer muss die Zusatz-Rolle in der aktuellen Anwendung haben, nicht in einer anderen. Die appid der aktuellen Anwendung wird aus app.config["APPID"] gelesen (gesetzt in approot.py aus config.ini, Eintrag [APP] appid). Sie ist der lesbare Anwendungsname (z.B. 'KBPlusCRM') und dient der Zuordnung in der Tabelle 'applications'. Sie ist nicht identisch mit PROJEKTNAME, der nur für Datei- und Verzeichnisnamen verwendet wird. - allowed_roles: List[str] - roleids (nicht ids) der Rollen, die den Endpunkt zusätzlich zu AppAdmin/SysAdmin erlauben. AppAdmin wird intern ergänzt, muss also nicht explizit genannt werden. Der Benutzer muss die Rolle in der aktuellen Anwendung haben (geprüft via User.has_permission). """ def decorator(f): @wraps(f) @login_required def wrapper(*args, **kwargs): # appid der aktuellen Anwendung aus der Konfiguration appid = current_app.config["APPID"] # 1. SysAdmin darf systemweit alles (keine app-spezifische Prüfung) if current_user.is_SysAdmin(): return f(*args, **kwargs) # 2.-4. AppAdmin und alle anderen Rollen: app-spezifische Prüfung. # AppAdmin wird implizit zu allowed_roles hinzugefügt, damit der # AppAdmin in seiner Anwendung jeden Endpunkt ohne explizite # Nennung in allowed_roles nutzen kann. effective_roles = list(allowed_roles) + [APP_ADMIN] if not current_user.has_permission(appid, effective_roles): # Rollen des Users in der aktuellen Anwendung für die Fehlermeldung user_roles = current_user.get_userapproles(appid) raise AutorisationError( allowed_roles=allowed_roles, user_roles=user_roles ) return f(*args, **kwargs) return wrapper return decorator