Dieser Commit ist enthalten in:
2025-06-07 16:33:05 +02:00
Ursprung 2743f3ff9b
Commit b3dc80a38a
15 geänderte Dateien mit 877 neuen und 4 gelöschten Zeilen

Datei anzeigen

@@ -1,5 +1,6 @@
import os
import psycopg2
from psycopg2.extras import Json
from flask import Flask, render_template, request, redirect, session, url_for, send_file
from flask_session import Session
from functools import wraps
@@ -7,6 +8,7 @@ from dotenv import load_dotenv
import pandas as pd
from datetime import datetime
import io
import json
load_dotenv()
@@ -39,6 +41,37 @@ def get_connection():
conn.set_client_encoding('UTF8')
return conn
# Audit-Log-Funktion
def log_audit(action, entity_type, entity_id=None, old_values=None, new_values=None, additional_info=None):
"""Protokolliert Änderungen im Audit-Log"""
conn = get_connection()
cur = conn.cursor()
try:
username = session.get('username', 'system')
ip_address = request.remote_addr if request else None
user_agent = request.headers.get('User-Agent') if request else None
# Konvertiere Dictionaries zu JSONB
old_json = Json(old_values) if old_values else None
new_json = Json(new_values) if new_values else None
cur.execute("""
INSERT INTO audit_log
(username, action, entity_type, entity_id, old_values, new_values,
ip_address, user_agent, additional_info)
VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s)
""", (username, action, entity_type, entity_id, old_json, new_json,
ip_address, user_agent, additional_info))
conn.commit()
except Exception as e:
print(f"Audit log error: {e}")
conn.rollback()
finally:
cur.close()
conn.close()
@app.route("/login", methods=["GET", "POST"])
def login():
if request.method == "POST":
@@ -55,6 +88,7 @@ def login():
(username == admin2_user and password == admin2_pass)):
session['logged_in'] = True
session['username'] = username
log_audit('LOGIN', 'user', additional_info=f"Erfolgreiche Anmeldung")
return redirect(url_for('dashboard'))
else:
return render_template("login.html", error="Ungültige Anmeldedaten")
@@ -63,6 +97,8 @@ def login():
@app.route("/logout")
def logout():
username = session.get('username', 'unknown')
log_audit('LOGOUT', 'user', additional_info=f"Abmeldung")
session.pop('logged_in', None)
session.pop('username', None)
return redirect(url_for('login'))
@@ -190,9 +226,22 @@ def create_license():
cur.execute("""
INSERT INTO licenses (license_key, customer_id, license_type, valid_from, valid_until, is_active)
VALUES (%s, %s, %s, %s, %s, TRUE)
RETURNING id
""", (license_key, customer_id, license_type, valid_from, valid_until))
license_id = cur.fetchone()[0]
conn.commit()
# Audit-Log
log_audit('CREATE', 'license', license_id,
new_values={
'license_key': license_key,
'customer_name': name,
'customer_email': email,
'license_type': license_type,
'valid_from': valid_from,
'valid_until': valid_until
})
cur.close()
conn.close()
@@ -290,6 +339,13 @@ def edit_license(license_id):
cur = conn.cursor()
if request.method == "POST":
# Alte Werte für Audit-Log abrufen
cur.execute("""
SELECT license_key, license_type, valid_from, valid_until, is_active
FROM licenses WHERE id = %s
""", (license_id,))
old_license = cur.fetchone()
# Update license
license_key = request.form["license_key"]
license_type = request.form["license_type"]
@@ -305,6 +361,24 @@ def edit_license(license_id):
""", (license_key, license_type, valid_from, valid_until, is_active, license_id))
conn.commit()
# Audit-Log
log_audit('UPDATE', 'license', license_id,
old_values={
'license_key': old_license[0],
'license_type': old_license[1],
'valid_from': str(old_license[2]),
'valid_until': str(old_license[3]),
'is_active': old_license[4]
},
new_values={
'license_key': license_key,
'license_type': license_type,
'valid_from': valid_from,
'valid_until': valid_until,
'is_active': is_active
})
cur.close()
conn.close()
@@ -334,9 +408,28 @@ def delete_license(license_id):
conn = get_connection()
cur = conn.cursor()
# Lizenzdetails für Audit-Log abrufen
cur.execute("""
SELECT l.license_key, c.name, l.license_type
FROM licenses l
JOIN customers c ON l.customer_id = c.id
WHERE l.id = %s
""", (license_id,))
license_info = cur.fetchone()
cur.execute("DELETE FROM licenses WHERE id = %s", (license_id,))
conn.commit()
# Audit-Log
if license_info:
log_audit('DELETE', 'license', license_id,
old_values={
'license_key': license_info[0],
'customer_name': license_info[1],
'license_type': license_info[2]
})
cur.close()
conn.close()
@@ -418,6 +511,10 @@ def edit_customer(customer_id):
cur = conn.cursor()
if request.method == "POST":
# Alte Werte für Audit-Log abrufen
cur.execute("SELECT name, email FROM customers WHERE id = %s", (customer_id,))
old_customer = cur.fetchone()
# Update customer
name = request.form["name"]
email = request.form["email"]
@@ -429,6 +526,18 @@ def edit_customer(customer_id):
""", (name, email, customer_id))
conn.commit()
# Audit-Log
log_audit('UPDATE', 'customer', customer_id,
old_values={
'name': old_customer[0],
'email': old_customer[1]
},
new_values={
'name': name,
'email': email
})
cur.close()
conn.close()
@@ -477,10 +586,23 @@ def delete_customer(customer_id):
conn.close()
return redirect("/customers")
# Kundendetails für Audit-Log abrufen
cur.execute("SELECT name, email FROM customers WHERE id = %s", (customer_id,))
customer_info = cur.fetchone()
# Kunde löschen wenn keine Lizenzen vorhanden
cur.execute("DELETE FROM customers WHERE id = %s", (customer_id,))
conn.commit()
# Audit-Log
if customer_info:
log_audit('DELETE', 'customer', customer_id,
old_values={
'name': customer_info[0],
'email': customer_info[1]
})
cur.close()
conn.close()
@@ -588,6 +710,10 @@ def export_licenses():
# Export Format
export_format = request.args.get('format', 'excel')
# Audit-Log
log_audit('EXPORT', 'license',
additional_info=f"Export aller Lizenzen als {export_format.upper()}")
filename = f'lizenzen_export_{datetime.now().strftime("%Y%m%d_%H%M%S")}'
if export_format == 'csv':
@@ -665,6 +791,10 @@ def export_customers():
# Export Format
export_format = request.args.get('format', 'excel')
# Audit-Log
log_audit('EXPORT', 'customer',
additional_info=f"Export aller Kunden als {export_format.upper()}")
filename = f'kunden_export_{datetime.now().strftime("%Y%m%d_%H%M%S")}'
if export_format == 'csv':
@@ -708,5 +838,78 @@ def export_customers():
download_name=f'{filename}.xlsx'
)
@app.route("/audit")
@login_required
def audit_log():
conn = get_connection()
cur = conn.cursor()
# Parameter
filter_user = request.args.get('user', '').strip()
filter_action = request.args.get('action', '').strip()
filter_entity = request.args.get('entity', '').strip()
page = request.args.get('page', 1, type=int)
per_page = 50
# SQL Query mit optionalen Filtern
query = """
SELECT id, timestamp, username, action, entity_type, entity_id,
old_values, new_values, ip_address, user_agent, additional_info
FROM audit_log
WHERE 1=1
"""
params = []
# Filter
if filter_user:
query += " AND LOWER(username) LIKE LOWER(%s)"
params.append(f'%{filter_user}%')
if filter_action:
query += " AND action = %s"
params.append(filter_action)
if filter_entity:
query += " AND entity_type = %s"
params.append(filter_entity)
# Gesamtanzahl für Pagination
count_query = "SELECT COUNT(*) FROM (" + query + ") as count_table"
cur.execute(count_query, params)
total = cur.fetchone()[0]
# Pagination
offset = (page - 1) * per_page
query += " ORDER BY timestamp DESC LIMIT %s OFFSET %s"
params.extend([per_page, offset])
cur.execute(query, params)
logs = cur.fetchall()
# JSON-Werte parsen
parsed_logs = []
for log in logs:
parsed_log = list(log)
# old_values und new_values sind bereits Dictionaries (JSONB)
# Keine Konvertierung nötig
parsed_logs.append(parsed_log)
# Pagination Info
total_pages = (total + per_page - 1) // per_page
cur.close()
conn.close()
return render_template("audit_log.html",
logs=parsed_logs,
filter_user=filter_user,
filter_action=filter_action,
filter_entity=filter_entity,
page=page,
total_pages=total_pages,
total=total,
username=session.get('username'))
if __name__ == "__main__":
app.run(host="0.0.0.0", port=443, ssl_context='adhoc')