gateway/gwserver/modules/gateway_interface.py
2025-03-19 00:59:45 +01:00

483 lines
No EOL
18 KiB
Python

import os
import logging
from typing import Dict, Any, List, Optional, Union
import importlib
from passlib.context import CryptContext
from connector_db_json import JSONDatabaseConnector
from modules.gateway_model import User, UserInDB, Token, Mandate
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 if mandate_id is not None else 1 # Root-Mandant als Standard
self.user_id = user_id if user_id is not None else 1 # Admin-Benutzer als Standard
# Datenverzeichnis
self.data_folder = "_database_gateway"
os.makedirs(self.data_folder, exist_ok=True)
logger.info("db for data_gateway attached")
# 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
# Konnektor erstellen
self.db = JSONDatabaseConnector(
db_folder=self.data_folder,
mandate_id=self.mandate_id,
user_id=self.user_id
)
# Datenbank initialisieren, falls nötig
self._initialize_database()
def _initialize_database(self):
"""
Initialisiert die Datenbank mit minimalen Objekten,
falls sie noch nicht existiert.
"""
# Prüfe, ob Mandanten existieren
mandates = self.db.get_recordset("mandates")
# Erstelle den Root-Mandanten, falls nötig
if not mandates:
logger.info("Erstelle Root-Mandant")
root_mandate = {
"id": 1,
"name": "Root",
"language": "de"
}
self.db.record_create("mandates", root_mandate)
logger.info("Root-Mandant wurde erstellt")
# Prüfe, ob Benutzer existieren
users = self.db.get_recordset("users")
# Erstelle den Admin-Benutzer, falls nötig
if not users:
logger.info("Erstelle Admin-Benutzer")
admin_user = {
"id": 1,
"mandate_id": 1, # Root-Mandant
"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!
}
self.db.record_create("users", admin_user)
logger.info("Admin-Benutzer wurde erstellt")
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"""
# Bestimme die nächste ID
mandates = self.db.get_recordset("mandates")
next_id = 1
if mandates:
next_id = max(mandate["id"] for mandate in mandates) + 1
mandate_data = {
"id": next_id,
"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
# Root-Mandant darf nicht gelöscht werden
if mandate_id == 1:
logger.warning("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")
# Bestimme die nächste ID
users = self.db.get_recordset("users")
next_id = 1
if users:
next_id = max(user["id"] for user in users) + 1
# 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 = {
"id": next_id,
"mandate_id": user_mandate_id,
"username": username,
"email": email,
"full_name": full_name,
"disabled": disabled,
"language": language,
"privilege": privilege, # Neue Eigenschaft
"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
# Workspaces des Benutzers finden und löschen
try:
workspaces = self.db.get_recordset("workspaces", record_filter={"user_id": user_id})
for workspace in workspaces:
# Agenten in diesem Workspace löschen
agents = self.db.get_recordset("agents", record_filter={"workspace_id": workspace["id"]})
for agent in agents:
# Hier könnten weitere abhängige Objekte gelöscht werden
self.db.record_delete("agents", agent["id"])
# Dateien in diesem Workspace löschen
files = self.db.get_recordset("files", record_filter={"workspace_id": workspace["id"]})
for file in files:
self.db.record_delete("files", file["id"])
# Prompts in diesem Workspace löschen
prompts = self.db.get_recordset("prompts", record_filter={"workspace_id": workspace["id"]})
for prompt in prompts:
self.db.record_delete("prompts", prompt["id"])
# Workflows in diesem Workspace löschen
workflows = self.db.get_recordset("workflows", record_filter={"workspace_id": workspace["id"]})
for workflow in workflows:
self.db.record_delete("workflows", workflow["id"])
# Den Workspace selbst löschen
self.db.record_delete("workspaces", workspace["id"])
except Exception as e:
logger.error(f"Fehler beim Löschen der Workspaces für Benutzer {user_id}: {e}")
# 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
# Root-Admin (ID 1) darf nicht gelöscht werden
if user_id == 1:
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()