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.utility 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()