gateway/modules/gateway_interface.py

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