modified: web/app.py
modified: web/blueprints/auth.py modified: web/blueprints/site_admin.py modified: web/config.py modified: web/panel_db.py modified: web/templates/admin/audit_log.html modified: web/templates/admin/dashboard.html new file: web/templates/auth/consent.html
This commit is contained in:
@@ -5,12 +5,75 @@ Getrennte Login-Seiten für Site-Admins und normale Nutzer/Gruppen-Admins.
|
||||
import json
|
||||
from datetime import datetime
|
||||
from flask import Blueprint, render_template, request, redirect, url_for, session, flash
|
||||
from panel_db import accept_group_invite, check_login, get_invite_by_token, get_user_groups, log_audit_event
|
||||
from panel_db import (
|
||||
accept_group_invite, check_login, get_invite_by_token, get_user_groups,
|
||||
log_audit_event, get_user_consent_version, set_user_consent,
|
||||
)
|
||||
from config import Config
|
||||
from limiter import limiter
|
||||
|
||||
auth = Blueprint("auth", __name__)
|
||||
|
||||
|
||||
# ── DSGVO-Einwilligungs-Check ─────────────────────────────────
|
||||
# Routen, die ohne Zustimmung erreichbar sein müssen:
|
||||
_CONSENT_EXEMPT = frozenset({
|
||||
"auth.consent", "auth.logout", "auth.login", "auth.admin_login",
|
||||
"auth.accept_invite", "privacy_policy", "static",
|
||||
})
|
||||
|
||||
|
||||
@auth.before_app_request
|
||||
def require_consent():
|
||||
"""Leitet angemeldete Nutzer auf die Zustimmungsseite, solange sie der
|
||||
aktuellen Datenschutzerklärung noch nicht zugestimmt haben."""
|
||||
if request.endpoint in _CONSENT_EXEMPT:
|
||||
return
|
||||
user_id = session.get("user_id")
|
||||
if not user_id:
|
||||
return
|
||||
# Site-Admins sind ebenfalls einwilligungspflichtig
|
||||
if session.get("needs_consent"):
|
||||
return redirect(url_for("auth.consent"))
|
||||
|
||||
|
||||
@auth.route("/consent", methods=["GET", "POST"])
|
||||
def consent():
|
||||
user_id = session.get("user_id")
|
||||
if not user_id:
|
||||
return redirect(url_for("auth.login"))
|
||||
|
||||
if request.method == "POST":
|
||||
action = request.form.get("action")
|
||||
if action == "accept":
|
||||
set_user_consent(user_id, Config.PRIVACY_POLICY_VERSION)
|
||||
log_audit_event(
|
||||
user_id, session.get("username"), "consent.given",
|
||||
details={"policy_version": Config.PRIVACY_POLICY_VERSION},
|
||||
ip_address=request.remote_addr,
|
||||
)
|
||||
session.pop("needs_consent", None)
|
||||
# Nach Zustimmung weiterleiten
|
||||
if session.get("is_site_admin"):
|
||||
return redirect(url_for("site_admin.dashboard"))
|
||||
return redirect(url_for("panel.dashboard"))
|
||||
else:
|
||||
# Ablehnen → ausloggen
|
||||
log_audit_event(
|
||||
user_id, session.get("username"), "consent.declined",
|
||||
details={"policy_version": Config.PRIVACY_POLICY_VERSION},
|
||||
ip_address=request.remote_addr,
|
||||
)
|
||||
session.clear()
|
||||
flash("You must accept the Privacy Policy to use this service.", "warning")
|
||||
return redirect(url_for("auth.login"))
|
||||
|
||||
return render_template(
|
||||
"auth/consent.html",
|
||||
policy_version=Config.PRIVACY_POLICY_VERSION,
|
||||
)
|
||||
|
||||
|
||||
@auth.route("/login", methods=["GET", "POST"])
|
||||
@limiter.limit("15 per minute", methods=["POST"])
|
||||
def login():
|
||||
@@ -34,6 +97,10 @@ def login():
|
||||
entity_type="user", entity_id=user["id"],
|
||||
ip_address=request.remote_addr,
|
||||
)
|
||||
# DSGVO: Zustimmung prüfen
|
||||
if get_user_consent_version(user["id"]) != Config.PRIVACY_POLICY_VERSION:
|
||||
session["needs_consent"] = True
|
||||
return redirect(url_for("auth.consent"))
|
||||
return redirect(url_for("panel.dashboard"))
|
||||
else:
|
||||
log_audit_event(
|
||||
@@ -65,6 +132,10 @@ def admin_login():
|
||||
entity_type="user", entity_id=user["id"],
|
||||
ip_address=request.remote_addr,
|
||||
)
|
||||
# DSGVO: Zustimmung prüfen
|
||||
if get_user_consent_version(user["id"]) != Config.PRIVACY_POLICY_VERSION:
|
||||
session["needs_consent"] = True
|
||||
return redirect(url_for("auth.consent"))
|
||||
return redirect(url_for("site_admin.dashboard"))
|
||||
elif user:
|
||||
log_audit_event(
|
||||
|
||||
@@ -48,7 +48,14 @@ def dashboard():
|
||||
"admin_count": sum(1 for u in users if u.get("is_site_admin")),
|
||||
"mail_configured": int(has_mail),
|
||||
}
|
||||
return render_template("admin/dashboard.html", groups=groups, users=users, stats=stats)
|
||||
# Letzte 10 Audit-Einträge für das Dashboard-Widget
|
||||
try:
|
||||
recent_audit, _ = db.get_audit_log(page=1, per_page=10)
|
||||
except Exception:
|
||||
recent_audit = []
|
||||
return render_template("admin/dashboard.html", groups=groups, users=users,
|
||||
stats=stats, recent_audit=recent_audit,
|
||||
retention_days=Config.AUDIT_LOG_RETENTION_DAYS)
|
||||
|
||||
|
||||
@site_admin.route("/mail", methods=["GET", "POST"])
|
||||
@@ -594,6 +601,13 @@ def user_edit(user_id):
|
||||
if not user:
|
||||
flash("User not found.", "danger")
|
||||
return redirect(url_for("site_admin.users"))
|
||||
if request.method == "GET":
|
||||
db.log_audit_event(
|
||||
session["user_id"], session["username"], "admin.view_user",
|
||||
entity_type="user", entity_id=user_id,
|
||||
details={"target": user["username"]},
|
||||
ip_address=request.remote_addr,
|
||||
)
|
||||
if request.method == "POST":
|
||||
username = request.form.get("username", "").strip()
|
||||
email = request.form.get("email", "").strip()
|
||||
@@ -702,6 +716,10 @@ def stop_view():
|
||||
@site_admin.route("/audit")
|
||||
@admin_required
|
||||
def audit_log():
|
||||
db.log_audit_event(
|
||||
session["user_id"], session["username"], "admin.view_audit_log",
|
||||
ip_address=request.remote_addr,
|
||||
)
|
||||
page = request.args.get("page", 1, type=int)
|
||||
action_f = request.args.get("action", "").strip() or None
|
||||
group_f = request.args.get("group_id", None, type=int)
|
||||
|
||||
Reference in New Issue
Block a user