Server: CRM-Domain mit REST-API, Autorisierung und CLI
Neues crm-Paket mit Datenmodell, Geschäftslogik und REST-Endpunkten
für die Entität Kunde (/xhr/kunde/{list,get,create,update,delete}).
authorisation_required-Decorator: app-spezifische Rollenprüfung via
has_permission(appid, ...). SysAdmin systemweit, AppAdmin implizit in
seiner Anwendung, User (Mitarbeiter) als Basis-Rolle, Zusatz-Rollen
wie KundenManager additiv.
Flask CLI: init-db (Schema + Musterdaten + App-Rollen) und reset-db
(Bestätigung, behält SysAdmin).
config.ini: Neuer appid-Eintrag (KBPlusCRM), getrennt vom technischen
PROJEKTNAME. checkdb: appid aus config, Migration _db_V01_02 für
bestehende DBs.
Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
This commit is contained in:
1 parent
65f81a0ee8
commit
13eaaec9c1
12 files changed
+627
-8
No files matched your search
@@ -0,0 +1,97 @@
|
||||
"""
|
||||
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 <vibe@mistral.ai>
|
||||
: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
|
||||
@@ -3,6 +3,7 @@ from auth import model
|
||||
from sqlalchemy import text
|
||||
from sqlalchemy.exc import OperationalError
|
||||
from datetime import datetime
|
||||
from flask import current_app
|
||||
from db import dbauth
|
||||
|
||||
session = dbauth.session
|
||||
@@ -48,8 +49,11 @@ def _db_V01_01():
|
||||
|
||||
application = model.Application()
|
||||
application.id = 1
|
||||
application.appid = "Project01"
|
||||
application.name = "Project01 (Name ändern)"
|
||||
# appid und name aus der Konfiguration übernehmen (config.ini [APP] appid).
|
||||
# Die appid ist der lesbare Anwendungsname, der zur Autorisierung verwendet
|
||||
# wird; nicht identisch mit PROJEKTNAME (technischer Bezeichner).
|
||||
application.appid = current_app.config["APPID"]
|
||||
application.name = current_app.config["APPID"]
|
||||
session.add(application)
|
||||
|
||||
userapp = model.Userapp()
|
||||
@@ -61,6 +65,23 @@ def _db_V01_01():
|
||||
_updatesysteminfo('01.01')
|
||||
session.commit()
|
||||
|
||||
def _db_V01_02():
|
||||
"""
|
||||
Migriert bestehende Application-Datensätze, deren appid noch den alten
|
||||
Platzhalter 'Project01' enthält, auf die appid aus der aktuellen Konfiguration
|
||||
(config.ini [APP] appid). Wird nach reset-db nicht benötigt (da _db_V01_01
|
||||
direkt den korrekten Wert setzt), greift aber bei Upgrades bestehender DBs.
|
||||
"""
|
||||
appid = current_app.config["APPID"]
|
||||
application: model.Application = model.Application.query.filter(
|
||||
model.Application.appid == "Project01"
|
||||
).first()
|
||||
if application is not None:
|
||||
application.appid = appid
|
||||
application.name = appid
|
||||
_updatesysteminfo('01.02')
|
||||
session.commit()
|
||||
|
||||
def _updatesysteminfo(versionnr):
|
||||
s: model.Systeminfo = model.Systeminfo.query.get(1)
|
||||
s.version = versionnr
|
||||
|
||||
Reference in new issue
Block a user