459 lines
No EOL
17 KiB
Python
459 lines
No EOL
17 KiB
Python
import os
|
|
import logging
|
|
from typing import Dict, Any, List, Optional, Union
|
|
import importlib
|
|
from passlib.context import CryptContext
|
|
|
|
from connectors.connector_db_json import DatabaseConnector
|
|
from modules.configuration import APP_CONFIG
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
# Password-Hashing
|
|
pwd_context = CryptContext(schemes=["argon2"], deprecated="auto")
|
|
|
|
|
|
class GatewayInterface:
|
|
"""
|
|
Interface zum Gateway-System.
|
|
Verwaltet Benutzer und Mandanten.
|
|
"""
|
|
|
|
def __init__(self, mandate_id: int = None, user_id: int = None):
|
|
"""
|
|
Initialisiert das Gateway-Interface mit optionalem Mandanten- und Benutzerkontext.
|
|
|
|
Args:
|
|
mandate_id: ID des aktuellen Mandanten (optional)
|
|
user_id: ID des aktuellen Benutzers (optional)
|
|
"""
|
|
# Bei der Initialisierung kann der Kontext leer sein
|
|
self.mandate_id = mandate_id
|
|
self.user_id = user_id
|
|
|
|
# Datenmodell-Modul importieren
|
|
try:
|
|
self.model_module = importlib.import_module("modules.gateway_model")
|
|
logger.info("gateway_model erfolgreich importiert")
|
|
except ImportError as e:
|
|
logger.error(f"Fehler beim Importieren von gateway_model: {e}")
|
|
raise
|
|
|
|
# Datenbank initialisieren
|
|
self._initialize_database()
|
|
|
|
def _initialize_database(self):
|
|
"""
|
|
Initialisiert die Datenbank mit minimalen Objekten
|
|
"""
|
|
|
|
self.db = DatabaseConnector(
|
|
db_host=APP_CONFIG.get("DB_SYSTEM_HOST"),
|
|
db_database=APP_CONFIG.get("DB_SYSTEM_DATABASE"),
|
|
db_user=APP_CONFIG.get("DB_SYSTEM_USER"),
|
|
db_password=APP_CONFIG.get("DB_SYSTEM_PASSWORD_SECRET"),
|
|
mandate_id=self.mandate_id if self.mandate_id else 0,
|
|
user_id=self.user_id if self.user_id else 0
|
|
)
|
|
|
|
# Erstelle den Root-Mandanten, falls nötig
|
|
existing_mandate_id = self.get_initial_id("mandates")
|
|
mandates = self.db.get_recordset("mandates")
|
|
if existing_mandate_id is None or not mandates:
|
|
logger.info("Erstelle Root-Mandant")
|
|
root_mandate = {
|
|
"name": "Root",
|
|
"language": "de"
|
|
}
|
|
created_mandate = self.db.record_create("mandates", root_mandate)
|
|
logger.info(f"Root-Mandant wurde erstellt mit ID {created_mandate['id']}")
|
|
|
|
# Aktualisiere den Mandanten-Kontext
|
|
self.mandate_id = created_mandate['id']
|
|
self.user_id = created_mandate['user_id']
|
|
|
|
# Konnektor mit korrektem Kontext neu erstellen
|
|
self.db = DatabaseConnector(
|
|
db_host=APP_CONFIG.get("DB_SYSTEM_HOST"),
|
|
db_database=APP_CONFIG.get("DB_SYSTEM_DATABASE"),
|
|
db_user=APP_CONFIG.get("DB_SYSTEM_USER"),
|
|
db_password=APP_CONFIG.get("DB_SYSTEM_PASSWORD_SECRET"),
|
|
mandate_id=self.mandate_id,
|
|
user_id=self.user_id
|
|
)
|
|
|
|
# Erstelle den Admin-Benutzer, falls nötig
|
|
existing_user_id = self.get_initial_id("users")
|
|
users = self.db.get_recordset("users")
|
|
if existing_user_id is None or not users:
|
|
logger.info("Erstelle Admin-Benutzer")
|
|
admin_user = {
|
|
"mandate_id": self.mandate_id,
|
|
"username": "admin",
|
|
"email": "admin@example.com",
|
|
"full_name": "Administrator",
|
|
"disabled": False,
|
|
"language": "de",
|
|
"privilege": "sysadmin", # SysAdmin-Berechtigung
|
|
"hashed_password": self._get_password_hash("admin") # In der Produktion ein sicheres Passwort verwenden!
|
|
}
|
|
created_user = self.db.record_create("users", admin_user)
|
|
logger.info(f"Admin-Benutzer wurde erstellt mit ID {created_user['id']}")
|
|
|
|
# Aktualisiere den Benutzer-Kontext
|
|
self.user_id = created_user['id']
|
|
|
|
# Konnektor mit korrektem Kontext neu erstellen
|
|
self.db = DatabaseConnector(
|
|
db_host=APP_CONFIG.get("DB_SYSTEM_HOST"),
|
|
db_database=APP_CONFIG.get("DB_SYSTEM_DATABASE"),
|
|
db_user=APP_CONFIG.get("DB_SYSTEM_USER"),
|
|
db_password=APP_CONFIG.get("DB_SYSTEM_PASSWORD_SECRET"),
|
|
mandate_id=self.mandate_id,
|
|
user_id=self.user_id )
|
|
|
|
def get_initial_id(self, table: str) -> Optional[int]:
|
|
""" Gibt die initiale ID für eine Tabelle zurück """
|
|
return self.db.get_initial_id(table)
|
|
|
|
def _get_password_hash(self, password: str) -> str:
|
|
"""Erstellt einen Hash für ein Passwort"""
|
|
return pwd_context.hash(password)
|
|
|
|
def _verify_password(self, plain_password: str, hashed_password: str) -> bool:
|
|
"""Überprüft, ob das Passwort zum Hash passt"""
|
|
return pwd_context.verify(plain_password, hashed_password)
|
|
|
|
def _get_current_timestamp(self) -> str:
|
|
"""Gibt den aktuellen Zeitstempel im ISO-Format zurück"""
|
|
from datetime import datetime
|
|
return datetime.now().isoformat()
|
|
|
|
# Mandanten-Methoden
|
|
|
|
def get_all_mandates(self) -> List[Dict[str, Any]]:
|
|
"""Gibt alle Mandanten zurück"""
|
|
return self.db.get_recordset("mandates")
|
|
|
|
def get_mandate(self, mandate_id: int) -> Optional[Dict[str, Any]]:
|
|
"""Gibt einen Mandanten anhand seiner ID zurück"""
|
|
mandates = self.db.get_recordset("mandates", record_filter={"id": mandate_id})
|
|
if mandates:
|
|
return mandates[0]
|
|
return None
|
|
|
|
def create_mandate(self, name: str, language: str = "de") -> Dict[str, Any]:
|
|
"""Erstellt einen neuen Mandanten"""
|
|
mandate_data = {
|
|
"name": name,
|
|
"language": language
|
|
}
|
|
|
|
return self.db.record_create("mandates", mandate_data)
|
|
|
|
def update_mandate(self, mandate_id: int, mandate_data: Dict[str, Any]) -> Dict[str, Any]:
|
|
"""
|
|
Aktualisiert einen bestehenden Mandanten
|
|
|
|
Args:
|
|
mandate_id: Die ID des zu aktualisierenden Mandanten
|
|
mandate_data: Die zu aktualisierenden Mandantendaten
|
|
|
|
Returns:
|
|
Dict[str, Any]: Die aktualisierten Mandantendaten
|
|
|
|
Raises:
|
|
ValueError: Wenn der Mandant nicht gefunden wurde
|
|
"""
|
|
# Prüfe, ob der Mandant existiert
|
|
mandate = self.get_mandate(mandate_id)
|
|
if not mandate:
|
|
raise ValueError(f"Mandant mit ID {mandate_id} nicht gefunden")
|
|
|
|
# Aktualisiere den Mandanten
|
|
updated_mandate = self.db.record_modify("mandates", mandate_id, mandate_data)
|
|
|
|
return updated_mandate
|
|
|
|
def delete_mandate(self, mandate_id: int) -> bool:
|
|
"""
|
|
Löscht einen Mandanten und alle damit verbundenen Benutzer und Daten
|
|
|
|
Args:
|
|
mandate_id: Die ID des zu löschenden Mandanten
|
|
|
|
Returns:
|
|
bool: True, wenn der Mandant erfolgreich gelöscht wurde, sonst False
|
|
"""
|
|
# Prüfe, ob der Mandant existiert
|
|
mandate = self.get_mandate(mandate_id)
|
|
if not mandate:
|
|
return False
|
|
|
|
# Prüfe, ob es der initiale Mandant ist
|
|
initial_mandate_id = self.get_initial_id("mandates")
|
|
if initial_mandate_id is not None and mandate_id == initial_mandate_id:
|
|
logger.warning(f"Versuch, den Root-Mandanten zu löschen, wurde verhindert")
|
|
return False
|
|
|
|
# Finde alle Benutzer des Mandanten
|
|
users = self.get_users_by_mandate(mandate_id)
|
|
|
|
# Lösche alle Benutzer des Mandanten und ihre zugehörigen Daten
|
|
for user in users:
|
|
self.delete_user(user["id"])
|
|
|
|
# Lösche den Mandanten
|
|
success = self.db.record_delete("mandates", mandate_id)
|
|
|
|
if success:
|
|
logger.info(f"Mandant mit ID {mandate_id} wurde erfolgreich gelöscht")
|
|
else:
|
|
logger.error(f"Fehler beim Löschen des Mandanten mit ID {mandate_id}")
|
|
|
|
return success
|
|
|
|
# Benutzer-Methoden
|
|
|
|
def get_all_users(self) -> List[Dict[str, Any]]:
|
|
"""Gibt alle Benutzer zurück"""
|
|
users = self.db.get_recordset("users")
|
|
# Entferne die Passwort-Hashes aus der Rückgabe
|
|
for user in users:
|
|
if "hashed_password" in user:
|
|
del user["hashed_password"]
|
|
return users
|
|
|
|
def get_users_by_mandate(self, mandate_id: int) -> List[Dict[str, Any]]:
|
|
"""
|
|
Gibt alle Benutzer eines bestimmten Mandanten zurück
|
|
|
|
Args:
|
|
mandate_id: Die ID des Mandanten
|
|
|
|
Returns:
|
|
List[Dict[str, Any]]: Liste der Benutzer des Mandanten
|
|
"""
|
|
users = self.db.get_recordset("users", record_filter={"mandate_id": mandate_id})
|
|
# Entferne die Passwort-Hashes aus der Rückgabe
|
|
for user in users:
|
|
if "hashed_password" in user:
|
|
del user["hashed_password"]
|
|
return users
|
|
|
|
def get_user_by_username(self, username: str) -> Optional[Dict[str, Any]]:
|
|
"""Gibt einen Benutzer anhand seines Benutzernamens zurück"""
|
|
users = self.db.get_recordset("users")
|
|
for user in users:
|
|
if user.get("username") == username:
|
|
return user
|
|
return None
|
|
|
|
def get_user(self, user_id: int) -> Optional[Dict[str, Any]]:
|
|
"""Gibt einen Benutzer anhand seiner ID zurück"""
|
|
users = self.db.get_recordset("users", record_filter={"id": user_id})
|
|
if users:
|
|
user = users[0]
|
|
# Entferne das Passwort-Hash aus der Rückgabe für die API
|
|
if "hashed_password" in user:
|
|
user_copy = user.copy()
|
|
del user_copy["hashed_password"]
|
|
return user_copy
|
|
return user
|
|
return None
|
|
|
|
def create_user(self, username: str, password: str, email: str = None,
|
|
full_name: str = None, language: str = "de", mandate_id: int = None,
|
|
disabled: bool = False, privilege: str = "user") -> Dict[str, Any]:
|
|
"""
|
|
Erstellt einen neuen Benutzer
|
|
|
|
Args:
|
|
username: Der Benutzername
|
|
password: Das Passwort
|
|
email: Die E-Mail-Adresse (optional)
|
|
full_name: Der vollständige Name (optional)
|
|
language: Die bevorzugte Sprache (Standard: "de")
|
|
mandate_id: Die ID des Mandanten (optional)
|
|
disabled: Ob der Benutzer deaktiviert ist (Standard: False)
|
|
privilege: Die Berechtigungsstufe (Standard: "user")
|
|
|
|
Returns:
|
|
Dict[str, Any]: Die erstellten Benutzerdaten
|
|
|
|
Raises:
|
|
ValueError: Wenn der Benutzername bereits existiert
|
|
"""
|
|
# Prüfe, ob der Benutzername bereits existiert
|
|
existing_user = self.get_user_by_username(username)
|
|
if existing_user:
|
|
raise ValueError(f"Benutzer '{username}' existiert bereits")
|
|
|
|
# Verwende den übergebenen mandate_id oder den aktuellen Kontext
|
|
user_mandate_id = mandate_id if mandate_id is not None else self.mandate_id
|
|
|
|
user_data = {
|
|
"mandate_id": user_mandate_id,
|
|
"username": username,
|
|
"email": email,
|
|
"full_name": full_name,
|
|
"disabled": disabled,
|
|
"language": language,
|
|
"privilege": privilege,
|
|
"hashed_password": self._get_password_hash(password)
|
|
}
|
|
|
|
created_user = self.db.record_create("users", user_data)
|
|
|
|
# Entferne das Passwort-Hash aus der Rückgabe
|
|
if "hashed_password" in created_user:
|
|
del created_user["hashed_password"]
|
|
|
|
return created_user
|
|
|
|
def authenticate_user(self, username: str, password: str) -> Optional[Dict[str, Any]]:
|
|
"""
|
|
Authentifiziert einen Benutzer anhand von Benutzername und Passwort
|
|
|
|
Args:
|
|
username: Der Benutzername
|
|
password: Das Passwort
|
|
|
|
Returns:
|
|
Optional[Dict[str, Any]]: Die Benutzerdaten oder None, wenn die Authentifizierung fehlschlägt
|
|
"""
|
|
user = self.get_user_by_username(username)
|
|
|
|
if not user:
|
|
return None
|
|
|
|
if not self._verify_password(password, user.get("hashed_password", "")):
|
|
return None
|
|
|
|
# Prüfe, ob der Benutzer deaktiviert ist
|
|
if user.get("disabled", False):
|
|
return None
|
|
|
|
# Erstelle eine Kopie ohne Passwort-Hash
|
|
authenticated_user = {**user}
|
|
if "hashed_password" in authenticated_user:
|
|
del authenticated_user["hashed_password"]
|
|
|
|
return authenticated_user
|
|
|
|
def update_user(self, user_id: int, user_data: Dict[str, Any]) -> Dict[str, Any]:
|
|
"""
|
|
Aktualisiert einen Benutzer
|
|
|
|
Args:
|
|
user_id: Die ID des zu aktualisierenden Benutzers
|
|
user_data: Die zu aktualisierenden Benutzerdaten
|
|
|
|
Returns:
|
|
Dict[str, Any]: Die aktualisierten Benutzerdaten
|
|
|
|
Raises:
|
|
ValueError: Wenn der Benutzer nicht gefunden wurde
|
|
"""
|
|
# Hole den aktuellen Benutzer mit Hash-Passwort (direkt aus der DB)
|
|
users = self.db.get_recordset("users", record_filter={"id": user_id})
|
|
if not users:
|
|
raise ValueError(f"Benutzer mit ID {user_id} nicht gefunden")
|
|
|
|
user = users[0]
|
|
|
|
# Wenn das Passwort geändert werden soll, hashe es
|
|
if "password" in user_data:
|
|
user_data["hashed_password"] = self._get_password_hash(user_data["password"])
|
|
del user_data["password"]
|
|
|
|
# Aktualisiere den Benutzer
|
|
updated_user = self.db.record_modify("users", user_id, user_data)
|
|
|
|
# Entferne das Passwort-Hash aus der Rückgabe
|
|
if "hashed_password" in updated_user:
|
|
del updated_user["hashed_password"]
|
|
|
|
return updated_user
|
|
|
|
def disable_user(self, user_id: int) -> Dict[str, Any]:
|
|
"""Deaktiviert einen Benutzer"""
|
|
return self.update_user(user_id, {"disabled": True})
|
|
|
|
def enable_user(self, user_id: int) -> Dict[str, Any]:
|
|
"""Aktiviert einen Benutzer"""
|
|
return self.update_user(user_id, {"disabled": False})
|
|
|
|
def _delete_user_referenced_data(self, user_id: int) -> None:
|
|
"""
|
|
Löscht alle Daten, die mit einem Benutzer verbunden sind
|
|
|
|
Args:
|
|
user_id: Die ID des Benutzers
|
|
"""
|
|
# Hier werden alle Tabellen durchsucht und alle Einträge gelöscht,
|
|
# die auf diesen Benutzer verweisen
|
|
|
|
# Attribute des Benutzers löschen
|
|
try:
|
|
attributes = self.db.get_recordset("attributes", record_filter={"user_id": user_id})
|
|
for attribute in attributes:
|
|
self.db.record_delete("attributes", attribute["id"])
|
|
except Exception as e:
|
|
logger.error(f"Fehler beim Löschen der Attribute für Benutzer {user_id}: {e}")
|
|
|
|
# Weitere Tabellen, die auf den Benutzer verweisen könnten
|
|
# (Je nach Datenbankstruktur der Anwendung)
|
|
|
|
logger.info(f"Alle referenzierten Daten für Benutzer {user_id} wurden gelöscht")
|
|
|
|
def delete_user(self, user_id: int) -> bool:
|
|
"""
|
|
Löscht einen Benutzer und alle damit verbundenen Daten
|
|
|
|
Args:
|
|
user_id: Die ID des zu löschenden Benutzers
|
|
|
|
Returns:
|
|
bool: True, wenn der Benutzer erfolgreich gelöscht wurde, sonst False
|
|
"""
|
|
# Prüfe, ob der Benutzer existiert
|
|
users = self.db.get_recordset("users", record_filter={"id": user_id})
|
|
if not users:
|
|
return False
|
|
|
|
# Prüfe, ob es der initiale Benutzer ist
|
|
initial_user_id = self.get_initial_id("users")
|
|
if initial_user_id is not None and user_id == initial_user_id:
|
|
logger.warning("Versuch, den Root-Admin zu löschen, wurde verhindert")
|
|
return False
|
|
|
|
# Lösche alle mit dem Benutzer verbundenen Daten
|
|
self._delete_user_referenced_data(user_id)
|
|
|
|
# Lösche den Benutzer
|
|
success = self.db.record_delete("users", user_id)
|
|
|
|
if success:
|
|
logger.info(f"Benutzer mit ID {user_id} wurde erfolgreich gelöscht")
|
|
else:
|
|
logger.error(f"Fehler beim Löschen des Benutzers mit ID {user_id}")
|
|
|
|
return success
|
|
|
|
|
|
# Singleton-Factory für GatewayInterface-Instanzen pro Kontext
|
|
_gateway_interfaces = {}
|
|
|
|
def get_gateway_interface(mandate_id: int = None, user_id: int = None) -> GatewayInterface:
|
|
"""
|
|
Gibt eine GatewayInterface-Instanz für den angegebenen Kontext zurück.
|
|
Wiederverwendet bestehende Instanzen.
|
|
"""
|
|
context_key = f"{mandate_id}_{user_id}"
|
|
if context_key not in _gateway_interfaces:
|
|
_gateway_interfaces[context_key] = GatewayInterface(mandate_id, user_id)
|
|
return _gateway_interfaces[context_key]
|
|
|
|
# Init
|
|
get_gateway_interface() |