458 lines
No EOL
16 KiB
Python
458 lines
No EOL
16 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 to the Gateway system.
|
|
Manages users and mandates.
|
|
"""
|
|
|
|
def __init__(self, mandate_id: int = None, user_id: int = None):
|
|
"""
|
|
Initializes the Gateway Interface with optional mandate and user context.
|
|
|
|
Args:
|
|
mandate_id: ID of the current mandate (optional)
|
|
user_id: ID of the current user (optional)
|
|
"""
|
|
# Context can be empty during initialization
|
|
self.mandate_id = mandate_id
|
|
self.user_id = user_id
|
|
|
|
# Import data model module
|
|
try:
|
|
self.model_module = importlib.import_module("modules.gateway_model")
|
|
logger.info("gateway_model successfully imported")
|
|
except ImportError as e:
|
|
logger.error(f"Error importing gateway_model: {e}")
|
|
raise
|
|
|
|
# Initialize database
|
|
self._initialize_database()
|
|
|
|
def _initialize_database(self):
|
|
"""
|
|
Initializes the database with minimal objects
|
|
"""
|
|
|
|
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
|
|
)
|
|
|
|
# Create Root mandate if needed
|
|
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("Creating Root mandate")
|
|
root_mandate = {
|
|
"name": "Root",
|
|
"language": "de"
|
|
}
|
|
created_mandate = self.db.record_create("mandates", root_mandate)
|
|
logger.info(f"Root mandate created with ID {created_mandate['id']}")
|
|
|
|
# Update mandate context
|
|
self.mandate_id = created_mandate['id']
|
|
self.user_id = created_mandate['user_id']
|
|
|
|
# Recreate connector with correct context
|
|
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
|
|
)
|
|
|
|
# Create Admin user if needed
|
|
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("Creating Admin user")
|
|
admin_user = {
|
|
"mandate_id": self.mandate_id,
|
|
"username": "admin",
|
|
"email": "admin@example.com",
|
|
"full_name": "Administrator",
|
|
"disabled": False,
|
|
"language": "de",
|
|
"privilege": "sysadmin", # SysAdmin privilege
|
|
"hashed_password": self._get_password_hash("admin") # Use a secure password in production!
|
|
}
|
|
created_user = self.db.record_create("users", admin_user)
|
|
logger.info(f"Admin user created with ID {created_user['id']}")
|
|
|
|
# Update user context
|
|
self.user_id = created_user['id']
|
|
|
|
# Recreate connector with correct context
|
|
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]:
|
|
"""Returns the initial ID for a table"""
|
|
return self.db.get_initial_id(table)
|
|
|
|
def _get_password_hash(self, password: str) -> str:
|
|
"""Creates a hash for a password"""
|
|
return pwd_context.hash(password)
|
|
|
|
def _verify_password(self, plain_password: str, hashed_password: str) -> bool:
|
|
"""Checks if the password matches the hash"""
|
|
return pwd_context.verify(plain_password, hashed_password)
|
|
|
|
def _get_current_timestamp(self) -> str:
|
|
"""Returns the current timestamp in ISO format"""
|
|
from datetime import datetime
|
|
return datetime.now().isoformat()
|
|
|
|
# Mandate methods
|
|
|
|
def get_all_mandates(self) -> List[Dict[str, Any]]:
|
|
"""Returns all mandates"""
|
|
return self.db.get_recordset("mandates")
|
|
|
|
def get_mandate(self, mandate_id: int) -> Optional[Dict[str, Any]]:
|
|
"""Returns a mandate by its ID"""
|
|
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]:
|
|
"""Creates a new mandate"""
|
|
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]:
|
|
"""
|
|
Updates an existing mandate
|
|
|
|
Args:
|
|
mandate_id: The ID of the mandate to update
|
|
mandate_data: The mandate data to update
|
|
|
|
Returns:
|
|
Dict[str, Any]: The updated mandate data
|
|
|
|
Raises:
|
|
ValueError: If the mandate is not found
|
|
"""
|
|
# Check if the mandate exists
|
|
mandate = self.get_mandate(mandate_id)
|
|
if not mandate:
|
|
raise ValueError(f"Mandate with ID {mandate_id} not found")
|
|
|
|
# Update the mandate
|
|
updated_mandate = self.db.record_modify("mandates", mandate_id, mandate_data)
|
|
|
|
return updated_mandate
|
|
|
|
def delete_mandate(self, mandate_id: int) -> bool:
|
|
"""
|
|
Deletes a mandate and all associated users and data
|
|
|
|
Args:
|
|
mandate_id: The ID of the mandate to delete
|
|
|
|
Returns:
|
|
bool: True if the mandate was successfully deleted, otherwise False
|
|
"""
|
|
# Check if the mandate exists
|
|
mandate = self.get_mandate(mandate_id)
|
|
if not mandate:
|
|
return False
|
|
|
|
# Check if it's the initial mandate
|
|
initial_mandate_id = self.get_initial_id("mandates")
|
|
if initial_mandate_id is not None and mandate_id == initial_mandate_id:
|
|
logger.warning(f"Attempt to delete the Root mandate was prevented")
|
|
return False
|
|
|
|
# Find all users of the mandate
|
|
users = self.get_users_by_mandate(mandate_id)
|
|
|
|
# Delete all users of the mandate and their associated data
|
|
for user in users:
|
|
self.delete_user(user["id"])
|
|
|
|
# Delete the mandate
|
|
success = self.db.record_delete("mandates", mandate_id)
|
|
|
|
if success:
|
|
logger.info(f"Mandate with ID {mandate_id} was successfully deleted")
|
|
else:
|
|
logger.error(f"Error deleting mandate with ID {mandate_id}")
|
|
|
|
return success
|
|
|
|
# User methods
|
|
|
|
def get_all_users(self) -> List[Dict[str, Any]]:
|
|
"""Returns all users"""
|
|
users = self.db.get_recordset("users")
|
|
# Remove password hashes from the response
|
|
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]]:
|
|
"""
|
|
Returns all users of a specific mandate
|
|
|
|
Args:
|
|
mandate_id: The ID of the mandate
|
|
|
|
Returns:
|
|
List[Dict[str, Any]]: List of users in the mandate
|
|
"""
|
|
users = self.db.get_recordset("users", record_filter={"mandate_id": mandate_id})
|
|
# Remove password hashes from the response
|
|
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]]:
|
|
"""Returns a user by username"""
|
|
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]]:
|
|
"""Returns a user by ID"""
|
|
users = self.db.get_recordset("users", record_filter={"id": user_id})
|
|
if users:
|
|
user = users[0]
|
|
# Remove password hash from the API response
|
|
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]:
|
|
"""
|
|
Creates a new user
|
|
|
|
Args:
|
|
username: The username
|
|
password: The password
|
|
email: The email address (optional)
|
|
full_name: The full name (optional)
|
|
language: The preferred language (default: "de")
|
|
mandate_id: The ID of the mandate (optional)
|
|
disabled: Whether the user is disabled (default: False)
|
|
privilege: The privilege level (default: "user")
|
|
|
|
Returns:
|
|
Dict[str, Any]: The created user data
|
|
|
|
Raises:
|
|
ValueError: If the username already exists
|
|
"""
|
|
# Check if the username already exists
|
|
existing_user = self.get_user_by_username(username)
|
|
if existing_user:
|
|
raise ValueError(f"User '{username}' already exists")
|
|
|
|
# Use the provided mandate_id or the current context
|
|
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)
|
|
|
|
# Remove password hash from the response
|
|
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]]:
|
|
"""
|
|
Authenticates a user by username and password
|
|
|
|
Args:
|
|
username: The username
|
|
password: The password
|
|
|
|
Returns:
|
|
Optional[Dict[str, Any]]: The user data or None if authentication fails
|
|
"""
|
|
user = self.get_user_by_username(username)
|
|
|
|
if not user:
|
|
return None
|
|
|
|
if not self._verify_password(password, user.get("hashed_password", "")):
|
|
return None
|
|
|
|
# Check if the user is disabled
|
|
if user.get("disabled", False):
|
|
return None
|
|
|
|
# Create a copy without password 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]:
|
|
"""
|
|
Updates a user
|
|
|
|
Args:
|
|
user_id: The ID of the user to update
|
|
user_data: The user data to update
|
|
|
|
Returns:
|
|
Dict[str, Any]: The updated user data
|
|
|
|
Raises:
|
|
ValueError: If the user is not found
|
|
"""
|
|
# Get the current user with password hash (directly from DB)
|
|
users = self.db.get_recordset("users", record_filter={"id": user_id})
|
|
if not users:
|
|
raise ValueError(f"User with ID {user_id} not found")
|
|
|
|
user = users[0]
|
|
|
|
# If the password is being changed, hash it
|
|
if "password" in user_data:
|
|
user_data["hashed_password"] = self._get_password_hash(user_data["password"])
|
|
del user_data["password"]
|
|
|
|
# Update the user
|
|
updated_user = self.db.record_modify("users", user_id, user_data)
|
|
|
|
# Remove password hash from the response
|
|
if "hashed_password" in updated_user:
|
|
del updated_user["hashed_password"]
|
|
|
|
return updated_user
|
|
|
|
def disable_user(self, user_id: int) -> Dict[str, Any]:
|
|
"""Disables a user"""
|
|
return self.update_user(user_id, {"disabled": True})
|
|
|
|
def enable_user(self, user_id: int) -> Dict[str, Any]:
|
|
"""Enables a user"""
|
|
return self.update_user(user_id, {"disabled": False})
|
|
|
|
def _delete_user_referenced_data(self, user_id: int) -> None:
|
|
"""
|
|
Deletes all data associated with a user
|
|
|
|
Args:
|
|
user_id: The ID of the user
|
|
"""
|
|
# Here all tables are searched and all entries referencing this user are deleted
|
|
|
|
# Delete user attributes
|
|
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"Error deleting attributes for user {user_id}: {e}")
|
|
|
|
# Other tables that might reference the user
|
|
# (Depending on the application's database structure)
|
|
|
|
logger.info(f"All referenced data for user {user_id} has been deleted")
|
|
|
|
def delete_user(self, user_id: int) -> bool:
|
|
"""
|
|
Deletes a user and all associated data
|
|
|
|
Args:
|
|
user_id: The ID of the user to delete
|
|
|
|
Returns:
|
|
bool: True if the user was successfully deleted, otherwise False
|
|
"""
|
|
# Check if the user exists
|
|
users = self.db.get_recordset("users", record_filter={"id": user_id})
|
|
if not users:
|
|
return False
|
|
|
|
# Check if it's the initial user
|
|
initial_user_id = self.get_initial_id("users")
|
|
if initial_user_id is not None and user_id == initial_user_id:
|
|
logger.warning("Attempt to delete the Root Admin was prevented")
|
|
return False
|
|
|
|
# Delete all data associated with the user
|
|
self._delete_user_referenced_data(user_id)
|
|
|
|
# Delete the user
|
|
success = self.db.record_delete("users", user_id)
|
|
|
|
if success:
|
|
logger.info(f"User with ID {user_id} was successfully deleted")
|
|
else:
|
|
logger.error(f"Error deleting user with ID {user_id}")
|
|
|
|
return success
|
|
|
|
|
|
# Singleton Factory for GatewayInterface instances per context
|
|
_gateway_interfaces = {}
|
|
|
|
def get_gateway_interface(mandate_id: int = None, user_id: int = None) -> GatewayInterface:
|
|
"""
|
|
Returns a GatewayInterface instance for the specified context.
|
|
Reuses existing instances.
|
|
"""
|
|
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() |