From 3094007d6b30feb2ee97f05293a5091ed7a0472a Mon Sep 17 00:00:00 2001
From: valueon
Date: Wed, 19 Mar 2025 00:59:45 +0100
Subject: [PATCH] full CRUD for all objects
---
gwserver/_database_gateway/mandates.json | 7 +
gwserver/_database_gateway/users.json | 13 ++
gwserver/_database_lucydom/agents.json | 4 +-
gwserver/app.py | 79 +++----
gwserver/modules/gateway_interface.py | 255 +++++++++++++++++++++--
gwserver/modules/gateway_model.py | 6 +-
gwserver/modules/lucydom_model.py | 4 -
gwserver/routes/attributes.py | 20 +-
gwserver/routes/mandate.py | 115 +++++++++-
gwserver/routes/users.py | 214 +++++++++++++++++++
10 files changed, 627 insertions(+), 90 deletions(-)
create mode 100644 gwserver/routes/users.py
diff --git a/gwserver/_database_gateway/mandates.json b/gwserver/_database_gateway/mandates.json
index d251ec2c..f25ed973 100644
--- a/gwserver/_database_gateway/mandates.json
+++ b/gwserver/_database_gateway/mandates.json
@@ -5,5 +5,12 @@
"language": "de",
"mandate_id": 1,
"user_id": 1
+ },
+ {
+ "id": 2,
+ "name": "Mandant von test",
+ "language": "de",
+ "mandate_id": 1,
+ "user_id": 1
}
]
\ No newline at end of file
diff --git a/gwserver/_database_gateway/users.json b/gwserver/_database_gateway/users.json
index 9d8e710a..1efd867d 100644
--- a/gwserver/_database_gateway/users.json
+++ b/gwserver/_database_gateway/users.json
@@ -7,7 +7,20 @@
"full_name": "Administrator",
"disabled": false,
"language": "de",
+ "privilege": "sysadmin",
"hashed_password": "$argon2id$v=19$m=65536,t=3,p=4$tJYSAmDs/T+HEALg3Nsbww$2HnCW69CkCkk82fgRhzjYtdNu3b9G7NmSJZmbUQVDOs",
"user_id": 1
+ },
+ {
+ "id": 2,
+ "mandate_id": 2,
+ "username": "test",
+ "email": "web@pamocreate.com",
+ "full_name": "Test User",
+ "disabled": false,
+ "language": "de",
+ "privilege": "user",
+ "hashed_password": "$argon2id$v=19$m=65536,t=3,p=4$3fufM+bc+19r7V3rvVfKmQ$CrJCc6r44jO4DxMHs8E61ayupBeUi6a+65VH0Q1cGo8",
+ "user_id": 2
}
]
\ No newline at end of file
diff --git a/gwserver/_database_lucydom/agents.json b/gwserver/_database_lucydom/agents.json
index f3ed4c02..d9a8ab59 100644
--- a/gwserver/_database_lucydom/agents.json
+++ b/gwserver/_database_lucydom/agents.json
@@ -3,9 +3,9 @@
"id": 2,
"mandate_id": 1,
"user_id": 1,
- "name": "Hugo",
+ "name": "Hugo C",
"type": "Transformation",
- "workspace_id": 1,
+ "workspace_id": "1",
"capabilities": "ccc",
"description": "ddd"
}
diff --git a/gwserver/app.py b/gwserver/app.py
index a283e127..830fc052 100644
--- a/gwserver/app.py
+++ b/gwserver/app.py
@@ -9,6 +9,7 @@ from typing import Dict, Any
import os
import logging
from datetime import timedelta
+import pathlib
# Import interfaces
from modules.gateway_interface import get_gateway_interface
@@ -26,15 +27,6 @@ from auth import (
# Import models - generisch importieren
import modules.gateway_model as gateway_model
-# Import router modules
-from routes.attributes import router as attributes_router
-from routes.workspace import router as workspace_router
-from routes.agent import router as agent_router
-from routes.file import router as file_router
-from routes.prompt import router as prompt_router
-from routes.workflow import router as workflow_router
-from routes.mandate import router as mandate_router
-
# Konfiguration des Loggers
logging.basicConfig(
level=logging.INFO,
@@ -56,14 +48,17 @@ app.add_middleware(
max_age=86400 # Erhöhung des Caching für Preflight-Anfragen
)
-# Statischer Folder für Frontend
-os.makedirs(os.path.join(os.getcwd(), "static"), exist_ok=True)
-app.mount("/static", StaticFiles(directory="static"), name="static")
+# Statischer Folder für Frontend - mit absolutem Pfad arbeiten
+base_dir = pathlib.Path(__file__).parent
+static_folder = base_dir / "static"
+os.makedirs(static_folder, exist_ok=True)
+app.mount("/static", StaticFiles(directory=str(static_folder)), name="static")
# Add a specific route for favicon.ico
@app.get("/favicon.ico", include_in_schema=False)
async def favicon():
- return FileResponse("static/favicon.ico")
+ favicon_path = static_folder / "favicon.ico"
+ return FileResponse(str(favicon_path))
# Generelle Elements
@app.get("/", tags=["General"])
@@ -75,7 +70,7 @@ async def root():
async def get_test():
return "OK 1.1"
-@app.options("/{full_path:path}")
+@app.options("/{full_path:path}", tags=["General"])
async def options_route(full_path: str):
return Response(status_code=200)
@@ -107,48 +102,36 @@ async def login_for_access_token(form_data: OAuth2PasswordRequestForm = Depends(
return {"access_token": access_token, "token_type": "bearer"}
-
-# Benutzerregistrierung
-@app.post("/api/users/register", response_model=gateway_model.User, tags=["General"])
-async def register_user(user_data: dict = Body(...), current_user: Dict[str, Any] = Depends(get_current_active_user)):
- """Neuen Benutzer registrieren"""
- # Nur Benutzer des gleichen Mandanten dürfen erstellt werden
- mandate_id, user_id = await get_user_context(current_user)
-
- # Gateway-Interface mit Benutzerkontext initialisieren
- gateway = get_gateway_interface(mandate_id, user_id)
-
- if "username" not in user_data or "password" not in user_data:
- raise HTTPException(status_code=400, detail="Benutzername und Passwort erforderlich")
-
- try:
- new_user = gateway.create_user(
- username=user_data["username"],
- password=user_data["password"],
- email=user_data.get("email"),
- full_name=user_data.get("full_name"),
- language=user_data.get("language", "de")
- )
- return new_user
- except ValueError as e:
- raise HTTPException(status_code=400, detail=str(e))
-
-
# Benutzerinfo abrufen
@app.get("/api/users/me", response_model=Dict[str, Any], tags=["General"])
async def read_users_me(current_user: Dict[str, Any] = Depends(get_current_active_user)):
return current_user
-
# Alle Router einbinden
-app.include_router(attributes_router)
-app.include_router(workspace_router)
-app.include_router(agent_router)
-app.include_router(file_router)
-app.include_router(prompt_router)
-app.include_router(workflow_router)
+from routes.mandate import router as mandate_router
app.include_router(mandate_router)
+from routes.users import router as users_router
+app.include_router(users_router)
+
+from routes.attributes import router as attributes_router
+app.include_router(attributes_router)
+
+from routes.workspace import router as workspace_router
+app.include_router(workspace_router)
+
+from routes.agent import router as agent_router
+app.include_router(agent_router)
+
+from routes.file import router as file_router
+app.include_router(file_router)
+
+from routes.prompt import router as prompt_router
+app.include_router(prompt_router)
+
+from routes.workflow import router as workflow_router
+app.include_router(workflow_router)
+
# Event handler beim Herunterfahren
@app.on_event("shutdown")
diff --git a/gwserver/modules/gateway_interface.py b/gwserver/modules/gateway_interface.py
index d6978d44..6cc1509b 100644
--- a/gwserver/modules/gateway_interface.py
+++ b/gwserver/modules/gateway_interface.py
@@ -91,6 +91,7 @@ class GatewayInterface:
"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!
}
@@ -139,11 +140,94 @@ class GatewayInterface:
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 des aktuellen Mandanten zurück"""
- return self.db.get_recordset("users")
+ """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"""
@@ -157,12 +241,37 @@ class GatewayInterface:
"""Gibt einen Benutzer anhand seiner ID zurück"""
users = self.db.get_recordset("users", record_filter={"id": user_id})
if users:
- return users[0]
+ 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") -> Dict[str, Any]:
- """Erstellt einen neuen Benutzer"""
+ 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:
@@ -174,14 +283,18 @@ class GatewayInterface:
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": self.mandate_id,
+ "mandate_id": user_mandate_id,
"username": username,
"email": email,
"full_name": full_name,
- "disabled": False,
+ "disabled": disabled,
"language": language,
+ "privilege": privilege, # Neue Eigenschaft
"hashed_password": self._get_password_hash(password)
}
@@ -194,7 +307,16 @@ class GatewayInterface:
return created_user
def authenticate_user(self, username: str, password: str) -> Optional[Dict[str, Any]]:
- """Authentifiziert einen Benutzer anhand von Benutzername und Passwort"""
+ """
+ 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:
@@ -203,6 +325,10 @@ class GatewayInterface:
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:
@@ -211,12 +337,26 @@ class GatewayInterface:
return authenticated_user
def update_user(self, user_id: int, user_data: Dict[str, Any]) -> Dict[str, Any]:
- """Aktualisiert einen Benutzer"""
- # Hole den aktuellen Benutzer
- user = self.get_user(user_id)
- if not user:
+ """
+ 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"])
@@ -238,6 +378,92 @@ class GatewayInterface:
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
@@ -253,6 +479,5 @@ def get_gateway_interface(mandate_id: int = None, user_id: int = None) -> Gatewa
_gateway_interfaces[context_key] = GatewayInterface(mandate_id, user_id)
return _gateway_interfaces[context_key]
-#Init
-get_gateway_interface()
-
+# Init
+get_gateway_interface()
\ No newline at end of file
diff --git a/gwserver/modules/gateway_model.py b/gwserver/modules/gateway_model.py
index 4ab16aa8..5a476fcb 100644
--- a/gwserver/modules/gateway_model.py
+++ b/gwserver/modules/gateway_model.py
@@ -40,8 +40,9 @@ class User(BaseModel):
username: str = Field(description="Benutzername für die Anmeldung")
email: Optional[str] = Field(None, description="E-Mail-Adresse des Benutzers")
full_name: Optional[str] = Field(None, description="Vollständiger Name des Benutzers")
- disabled: Optional[bool] = Field(False, description="Gibt an, ob der Benutzer deaktiviert ist")
language: str = Field(description="Bevorzugte Sprache des Benutzers")
+ disabled: Optional[bool] = Field(False, description="Gibt an, ob der Benutzer deaktiviert ist")
+ privilege: str = Field(description="Berechtigungsstufe") #sysadmin,admin,user
label: Label = Field(
default=Label(default="Benutzer", translations={"en": "User", "fr": "Utilisateur"}),
@@ -55,8 +56,9 @@ class User(BaseModel):
"username": Label(default="Benutzername", translations={"en": "Username", "fr": "Nom d'utilisateur"}),
"email": Label(default="E-Mail", translations={"en": "Email", "fr": "E-mail"}),
"full_name": Label(default="Vollständiger Name", translations={"en": "Full name", "fr": "Nom complet"}),
+ "language": Label(default="Sprache", translations={"en": "Language", "fr": "Langue"}),
"disabled": Label(default="Deaktiviert", translations={"en": "Disabled", "fr": "Désactivé"}),
- "language": Label(default="Sprache", translations={"en": "Language", "fr": "Langue"})
+ "privilege": Label(default="Berechtigungsstufe", translations={"en": "Access level", "fr": "Niveau d'accès"}),
}
diff --git a/gwserver/modules/lucydom_model.py b/gwserver/modules/lucydom_model.py
index 945421d4..e93297e9 100644
--- a/gwserver/modules/lucydom_model.py
+++ b/gwserver/modules/lucydom_model.py
@@ -83,7 +83,6 @@ class Prompt(BaseModel):
mandate_id: int = Field(description="ID des zugehörigen Mandanten")
user_id: int = Field(description="ID des Erstellers")
content: str = Field(description="Inhalt des Prompts")
- created_at: str = Field(description="Erstellungszeitpunkt")
label: Label = Field(
default=Label(default="Prompt", translations={"en": "Prompt", "fr": "Invite"}),
@@ -96,7 +95,6 @@ class Prompt(BaseModel):
"mandate_id": Label(default="Mandanten-ID", translations={"en": "Mandate ID", "fr": "ID de mandat"}),
"user_id": Label(default="Benutzer-ID", translations={"en": "User ID", "fr": "ID d'utilisateur"}),
"content": Label(default="Inhalt", translations={"en": "Content", "fr": "Contenu"}),
- "created_at": Label(default="Erstellt am", translations={"en": "Created at", "fr": "Créé le"})
}
@@ -106,7 +104,6 @@ class Workspace(BaseModel):
mandate_id: int = Field(description="ID des zugehörigen Mandanten")
user_id: int = Field(description="ID des Erstellers")
name: str = Field(description="Name des Workspaces")
- created_at: str = Field(description="Erstellungszeitpunkt")
prompts: List[Prompt] = Field(default=[], description="Liste der Prompts im Workspace")
agents: List[Agent] = Field(default=[], description="Liste der Agenten im Workspace")
dataObjectReferences: List[int] = Field(default=[], description="Referenzen zu Datenobjekten")
@@ -122,7 +119,6 @@ class Workspace(BaseModel):
"mandate_id": Label(default="Mandanten-ID", translations={"en": "Mandate ID", "fr": "ID de mandat"}),
"user_id": Label(default="Benutzer-ID", translations={"en": "User ID", "fr": "ID d'utilisateur"}),
"name": Label(default="Name", translations={"en": "Name", "fr": "Nom"}),
- "created_at": Label(default="Erstellt am", translations={"en": "Created at", "fr": "Créé le"}),
"prompts": Label(default="Prompts", translations={"en": "Prompts", "fr": "Invites"}),
"agents": Label(default="Agenten", translations={"en": "Agents", "fr": "Agents"}),
"dataObjectReferences": Label(default="Datenobjekte", translations={"en": "Data Objects", "fr": "Objets de données"})
diff --git a/gwserver/routes/attributes.py b/gwserver/routes/attributes.py
index 2544161d..8f07aace 100644
--- a/gwserver/routes/attributes.py
+++ b/gwserver/routes/attributes.py
@@ -33,16 +33,6 @@ router = APIRouter(
responses={404: {"description": "Not found"}}
)
-@router.get("/test", response_model=str)
-async def get_entity_test(s:str):
- return s+s
-
-@router.options("/{entity_type}")
-async def options_entity_attributes(
- entity_type: str = Path(..., description="Typ der Entität (z.B. workspace, agent, prompt)")
-):
- return Response(status_code=200)
-
@router.get("/{entity_type}", response_model=List[AttributeDefinition])
async def get_entity_attributes(
entity_type: str = Path(..., description="Typ der Entität (z.B. workspace, agent, prompt)"),
@@ -108,4 +98,12 @@ async def get_entity_attributes(
for agent in agents
]
- return visible_attributes
\ No newline at end of file
+ return visible_attributes
+
+
+@router.options("/{entity_type}")
+async def options_entity_attributes(
+ entity_type: str = Path(..., description="Typ der Entität (z.B. workspace, agent, prompt)")
+):
+ return Response(status_code=200)
+
diff --git a/gwserver/routes/mandate.py b/gwserver/routes/mandate.py
index 9c5f7b7c..06bae432 100644
--- a/gwserver/routes/mandate.py
+++ b/gwserver/routes/mandate.py
@@ -1,4 +1,4 @@
-from fastapi import APIRouter, HTTPException, Depends, Body
+from fastapi import APIRouter, HTTPException, Depends, Body, Path
from typing import List, Dict, Any
from fastapi import status
@@ -17,13 +17,18 @@ router = APIRouter(
@router.get("", response_model=List[Dict[str, Any]])
async def get_mandates(current_user: Dict[str, Any] = Depends(get_current_active_user)):
- """Alle verfügbaren Mandanten abrufen (nur für Admin-Benutzer)"""
+ """Alle verfügbaren Mandanten abrufen (nur für SysAdmin-Benutzer)"""
mandate_id, user_id = await get_user_context(current_user)
# Gateway-Interface mit Benutzerkontext initialisieren
gateway = get_gateway_interface(mandate_id, user_id)
- # TODO: Hier sollte eine Berechtigungsprüfung erfolgen
+ # Berechtigungsprüfung
+ if current_user.get("privilege") != "sysadmin":
+ raise HTTPException(
+ status_code=status.HTTP_403_FORBIDDEN,
+ detail="Nur System-Administratoren können alle Mandanten abrufen"
+ )
return gateway.get_all_mandates()
@@ -33,13 +38,18 @@ async def create_mandate(
mandate: Dict[str, Any] = Body(...),
current_user: Dict[str, Any] = Depends(get_current_active_user)
):
- """Einen neuen Mandanten erstellen (nur für Admin-Benutzer)"""
+ """Einen neuen Mandanten erstellen (nur für SysAdmin-Benutzer)"""
mandate_id, user_id = await get_user_context(current_user)
# Gateway-Interface mit Benutzerkontext initialisieren
gateway = get_gateway_interface(mandate_id, user_id)
- # TODO: Hier sollte eine Berechtigungsprüfung erfolgen
+ # Berechtigungsprüfung
+ if current_user.get("privilege") != "sysadmin":
+ raise HTTPException(
+ status_code=status.HTTP_403_FORBIDDEN,
+ detail="Nur System-Administratoren können Mandanten erstellen"
+ )
new_mandate = gateway.create_mandate(
name=mandate.get("name", "Neuer Mandant"),
@@ -60,8 +70,17 @@ async def get_mandate(
# Gateway-Interface mit Benutzerkontext initialisieren
gateway = get_gateway_interface(user_mandate_id, user_id)
- # TODO: Hier sollte eine Berechtigungsprüfung erfolgen
- # Nur Admins oder Benutzer des gleichen Mandanten dürfen den Mandanten sehen
+ # Berechtigungsprüfung
+ # Admin darf nur seinen eigenen Mandanten sehen, SysAdmin alle
+ is_admin = current_user.get("privilege") == "admin"
+ is_sysadmin = current_user.get("privilege") == "sysadmin"
+ is_own_mandate = user_mandate_id == mandate_id
+
+ if (is_admin and not is_own_mandate) and not is_sysadmin:
+ raise HTTPException(
+ status_code=status.HTTP_403_FORBIDDEN,
+ detail="Keine Berechtigung zum Abrufen dieses Mandanten"
+ )
mandate = gateway.get_mandate(mandate_id)
if not mandate:
@@ -70,4 +89,84 @@ async def get_mandate(
detail=f"Mandant mit ID {mandate_id} nicht gefunden"
)
- return mandate
\ No newline at end of file
+ return mandate
+
+@router.post("/{mandate_id}", response_model=Dict[str, Any])
+async def update_mandate(
+ mandate_id: int = Path(..., description="ID des zu aktualisierenden Mandanten"),
+ mandate_data: Dict[str, Any] = Body(..., description="Aktualisierte Mandantendaten"),
+ current_user: Dict[str, Any] = Depends(get_current_active_user)
+):
+ """Einen bestehenden Mandanten aktualisieren"""
+ user_mandate_id, user_id = await get_user_context(current_user)
+
+ # Gateway-Interface mit Benutzerkontext initialisieren
+ gateway = get_gateway_interface(user_mandate_id, user_id)
+
+ # Mandant existiert?
+ mandate = gateway.get_mandate(mandate_id)
+ if not mandate:
+ raise HTTPException(
+ status_code=status.HTTP_404_NOT_FOUND,
+ detail=f"Mandant mit ID {mandate_id} nicht gefunden"
+ )
+
+ # Berechtigungsprüfung
+ is_admin = current_user.get("privilege") == "admin"
+ is_sysadmin = current_user.get("privilege") == "sysadmin"
+ is_own_mandate = user_mandate_id == mandate_id
+
+ if (is_admin and not is_own_mandate) and not is_sysadmin:
+ raise HTTPException(
+ status_code=status.HTTP_403_FORBIDDEN,
+ detail="Keine Berechtigung zum Aktualisieren dieses Mandanten"
+ )
+
+ # Mandant aktualisieren
+ updated_mandate = gateway.update_mandate(
+ mandate_id=mandate_id,
+ mandate_data=mandate_data
+ )
+
+ return updated_mandate
+
+@router.delete("/{mandate_id}", status_code=status.HTTP_204_NO_CONTENT)
+async def delete_mandate(
+ mandate_id: int = Path(..., description="ID des zu löschenden Mandanten"),
+ current_user: Dict[str, Any] = Depends(get_current_active_user)
+):
+ """Einen Mandanten löschen, inklusive aller zugehörigen Benutzer und referenzierten Objekte"""
+ user_mandate_id, user_id = await get_user_context(current_user)
+
+ # Gateway-Interface mit Benutzerkontext initialisieren
+ gateway = get_gateway_interface(user_mandate_id, user_id)
+
+ # Mandant existiert?
+ mandate = gateway.get_mandate(mandate_id)
+ if not mandate:
+ raise HTTPException(
+ status_code=status.HTTP_404_NOT_FOUND,
+ detail=f"Mandant mit ID {mandate_id} nicht gefunden"
+ )
+
+ # Berechtigungsprüfung
+ is_admin = current_user.get("privilege") == "admin"
+ is_sysadmin = current_user.get("privilege") == "sysadmin"
+ is_own_mandate = user_mandate_id == mandate_id
+
+ if (is_admin and not is_own_mandate) and not is_sysadmin:
+ raise HTTPException(
+ status_code=status.HTTP_403_FORBIDDEN,
+ detail="Keine Berechtigung zum Löschen dieses Mandanten"
+ )
+
+ # Mandant löschen
+ success = gateway.delete_mandate(mandate_id)
+ if not success:
+ raise HTTPException(
+ status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
+ detail=f"Fehler beim Löschen des Mandanten mit ID {mandate_id}"
+ )
+
+ # Kein Inhalt zurückgeben bei erfolgreichem Löschen
+ return None
\ No newline at end of file
diff --git a/gwserver/routes/users.py b/gwserver/routes/users.py
new file mode 100644
index 00000000..30422963
--- /dev/null
+++ b/gwserver/routes/users.py
@@ -0,0 +1,214 @@
+from fastapi import APIRouter, HTTPException, Depends, Body, Path
+from typing import List, Dict, Any, Optional
+from fastapi import status
+
+# Import auth module
+from auth import get_current_active_user, get_user_context
+
+# Import interfaces
+from modules.gateway_interface import get_gateway_interface
+
+# Router für Benutzer-Endpunkte erstellen
+router = APIRouter(
+ prefix="/api/users",
+ tags=["Users"],
+ responses={404: {"description": "Not found"}}
+)
+
+@router.get("", response_model=List[Dict[str, Any]])
+async def get_users(current_user: Dict[str, Any] = Depends(get_current_active_user)):
+ """Alle verfügbaren Benutzer abrufen (nur für Admin/SysAdmin-Benutzer)"""
+ mandate_id, user_id = await get_user_context(current_user)
+
+ # Gateway-Interface mit Benutzerkontext initialisieren
+ gateway = get_gateway_interface(mandate_id, user_id)
+
+ # Berechtigungsprüfung
+ if current_user.get("privilege") not in ["admin", "sysadmin"]:
+ raise HTTPException(
+ status_code=status.HTTP_403_FORBIDDEN,
+ detail="Keine Berechtigung zum Abrufen der Benutzerliste"
+ )
+
+ # Admin sieht nur Benutzer des eigenen Mandanten, SysAdmin sieht alle
+ if current_user.get("privilege") == "admin":
+ return gateway.get_users_by_mandate(mandate_id)
+ else: # sysadmin
+ return gateway.get_all_users()
+
+@router.post("/register", response_model=Dict[str, Any])
+async def register_user(user_data: dict = Body(...)):
+ """Neuen Benutzer registrieren"""
+ # Bei der Registrierung keinen Benutzerkontext verwenden
+ gateway = get_gateway_interface()
+
+ if "username" not in user_data or "password" not in user_data:
+ raise HTTPException(status_code=400, detail="Benutzername und Passwort erforderlich")
+
+ try:
+ new_mandate = gateway.create_mandate(
+ name=f"Mandant von {user_data['username']}",
+ language=user_data.get("language", "de")
+ )
+
+ new_user = gateway.create_user(
+ username=user_data["username"],
+ password=user_data["password"],
+ email=user_data.get("email"),
+ full_name=user_data.get("full_name"),
+ language=user_data.get("language", "de"),
+ mandate_id=new_mandate["id"],
+ disabled=False,
+ privilege="user"
+ )
+ return new_user
+ except ValueError as e:
+ raise HTTPException(status_code=400, detail=str(e))
+
+@router.get("/{user_id}", response_model=Dict[str, Any])
+async def get_user(
+ user_id: int,
+ current_user: Dict[str, Any] = Depends(get_current_active_user)
+):
+ """Einen bestimmten Benutzer abrufen"""
+ mandate_id, current_user_id = await get_user_context(current_user)
+
+ # Gateway-Interface mit Benutzerkontext initialisieren
+ gateway = get_gateway_interface(mandate_id, current_user_id)
+
+ # Berechtigungsprüfung
+ # Benutzer darf nur sich selbst abrufen, Admin nur Benutzer des eigenen Mandanten, SysAdmin alle
+ user_to_get = gateway.get_user(user_id)
+ if not user_to_get:
+ raise HTTPException(
+ status_code=status.HTTP_404_NOT_FOUND,
+ detail=f"Benutzer mit ID {user_id} nicht gefunden"
+ )
+
+ if user_id == current_user_id:
+ # Benutzer darf sich selbst abrufen
+ pass
+ elif current_user.get("privilege") == "admin" and user_to_get.get("mandate_id") == mandate_id:
+ # Admin darf Benutzer des eigenen Mandanten abrufen
+ pass
+ elif current_user.get("privilege") == "sysadmin":
+ # SysAdmin darf alle Benutzer abrufen
+ pass
+ else:
+ raise HTTPException(
+ status_code=status.HTTP_403_FORBIDDEN,
+ detail="Keine Berechtigung zum Abrufen dieses Benutzers"
+ )
+
+ return user_to_get
+
+@router.post("/{user_id}", response_model=Dict[str, Any])
+async def update_user(
+ user_id: int = Path(..., description="ID des zu aktualisierenden Benutzers"),
+ user_data: Dict[str, Any] = Body(..., description="Aktualisierte Benutzerdaten"),
+ current_user: Dict[str, Any] = Depends(get_current_active_user)
+):
+ """Einen bestehenden Benutzer aktualisieren"""
+ mandate_id, current_user_id = await get_user_context(current_user)
+
+ # Gateway-Interface mit Benutzerkontext initialisieren
+ gateway = get_gateway_interface(mandate_id, current_user_id)
+
+ # Benutzer existiert?
+ user_to_update = gateway.get_user(user_id)
+ if not user_to_update:
+ raise HTTPException(
+ status_code=status.HTTP_404_NOT_FOUND,
+ detail=f"Benutzer mit ID {user_id} nicht gefunden"
+ )
+
+ # Berechtigungsprüfung
+ is_self_update = user_id == current_user_id
+ is_admin = current_user.get("privilege") == "admin"
+ is_sysadmin = current_user.get("privilege") == "sysadmin"
+ same_mandate = user_to_update.get("mandate_id") == mandate_id
+
+ # Filtere erlaubte Felder je nach Berechtigungsstufe
+ allowed_fields = {"username", "email", "full_name", "language"}
+ sensitive_fields = {"mandate_id", "disabled", "privilege"}
+
+ # Prüfe, ob sensitive Felder geändert werden sollen
+ sensitive_update = any(field in user_data for field in sensitive_fields)
+
+ if is_self_update and sensitive_update:
+ # Normale Benutzer dürfen ihre sensitiven Daten nicht ändern
+ raise HTTPException(
+ status_code=status.HTTP_403_FORBIDDEN,
+ detail="Keine Berechtigung zum Ändern sensitiver Benutzerdaten"
+ )
+ elif is_admin and sensitive_update and not same_mandate:
+ # Admins dürfen sensitive Daten nur für Benutzer des eigenen Mandanten ändern
+ raise HTTPException(
+ status_code=status.HTTP_403_FORBIDDEN,
+ detail="Keine Berechtigung zum Ändern sensitiver Daten für Benutzer anderer Mandanten"
+ )
+ elif not (is_self_update or (is_admin and same_mandate) or is_sysadmin):
+ # Keine Berechtigung für andere Fälle
+ raise HTTPException(
+ status_code=status.HTTP_403_FORBIDDEN,
+ detail="Keine Berechtigung zum Aktualisieren dieses Benutzers"
+ )
+
+ # Entferne nicht erlaubte Felder für normale Benutzer
+ if not (is_admin or is_sysadmin):
+ user_data = {k: v for k, v in user_data.items() if k in allowed_fields}
+
+ # User-Daten aktualisieren
+ updated_user = gateway.update_user(user_id, user_data)
+ return updated_user
+
+@router.delete("/{user_id}", status_code=status.HTTP_204_NO_CONTENT)
+async def delete_user(
+ user_id: int = Path(..., description="ID des zu löschenden Benutzers"),
+ current_user: Dict[str, Any] = Depends(get_current_active_user)
+):
+ """Einen Benutzer löschen"""
+ mandate_id, current_user_id = await get_user_context(current_user)
+
+ # Gateway-Interface mit Benutzerkontext initialisieren
+ gateway = get_gateway_interface(mandate_id, current_user_id)
+
+ # Benutzer existiert?
+ user_to_delete = gateway.get_user(user_id)
+ if not user_to_delete:
+ raise HTTPException(
+ status_code=status.HTTP_404_NOT_FOUND,
+ detail=f"Benutzer mit ID {user_id} nicht gefunden"
+ )
+
+ # Berechtigungsprüfung
+ is_self_delete = user_id == current_user_id
+ is_admin = current_user.get("privilege") == "admin"
+ is_sysadmin = current_user.get("privilege") == "sysadmin"
+ same_mandate = user_to_delete.get("mandate_id") == mandate_id
+
+ if is_self_delete:
+ # Benutzer darf sich selbst löschen
+ pass
+ elif is_admin and same_mandate:
+ # Admin darf Benutzer des eigenen Mandanten löschen
+ pass
+ elif is_sysadmin:
+ # SysAdmin darf alle Benutzer löschen
+ pass
+ else:
+ raise HTTPException(
+ status_code=status.HTTP_403_FORBIDDEN,
+ detail="Keine Berechtigung zum Löschen dieses Benutzers"
+ )
+
+ # Benutzer und alle referenzierten Objekte löschen
+ success = gateway.delete_user(user_id)
+ if not success:
+ raise HTTPException(
+ status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
+ detail=f"Fehler beim Löschen des Benutzers mit ID {user_id}"
+ )
+
+ # Kein Inhalt zurückgeben bei erfolgreichem Löschen
+ return None
\ No newline at end of file