""" Interface to the Gateway system. Manages users and mandates for authentication. """ import os import logging from typing import Dict, Any, List, Optional, Union import importlib from passlib.context import CryptContext from connectors.connectorDbJson import DatabaseConnector from modules.configuration import APP_CONFIG logger = logging.getLogger(__name__) # Password-Hashing pwdContext = CryptContext(schemes=["argon2"], deprecated="auto") class GatewayInterface: """ Interface to the Gateway system. Manages users and mandates. """ def __init__(self, mandateId: int = None, userId: int = None): """ Initializes the Gateway Interface with optional mandate and user context. Args: mandateId: ID of the current mandate (optional) userId: ID of the current user (optional) """ # Context can be empty during initialization self.mandateId = mandateId self.userId = userId # Import data model module try: self.modelModule = importlib.import_module("modules.gatewayModel") logger.info("gatewayModel successfully imported") except ImportError as e: logger.error(f"Error importing gatewayModel: {e}") raise # Initialize database self._initializeDatabase() def _initializeDatabase(self): """ Initializes the database with minimal objects """ self.db = DatabaseConnector( dbHost=APP_CONFIG.get("DB_SYSTEM_HOST"), dbDatabase=APP_CONFIG.get("DB_SYSTEM_DATABASE"), dbUser=APP_CONFIG.get("DB_SYSTEM_USER"), dbPassword=APP_CONFIG.get("DB_SYSTEM_PASSWORD_SECRET"), mandateId=self.mandateId if self.mandateId else 0, userId=self.userId if self.userId else 0 ) # Create Root mandate if needed existingMandateId = self.getInitialId("mandates") mandates = self.db.getRecordset("mandates") if existingMandateId is None or not mandates: logger.info("Creating Root mandate") rootMandate = { "name": "Root", "language": "de" } createdMandate = self.db.recordCreate("mandates", rootMandate) logger.info(f"Root mandate created with ID {createdMandate['id']}") # Update mandate context self.mandateId = createdMandate['id'] self.userId = createdMandate['userId'] # Recreate connector with correct context self.db = DatabaseConnector( dbHost=APP_CONFIG.get("DB_SYSTEM_HOST"), dbDatabase=APP_CONFIG.get("DB_SYSTEM_DATABASE"), dbUser=APP_CONFIG.get("DB_SYSTEM_USER"), dbPassword=APP_CONFIG.get("DB_SYSTEM_PASSWORD_SECRET"), mandateId=self.mandateId, userId=self.userId ) # Create Admin user if needed existingUserId = self.getInitialId("users") users = self.db.getRecordset("users") if existingUserId is None or not users: logger.info("Creating Admin user") adminUser = { "mandateId": self.mandateId, "username": "admin", "email": "admin@example.com", "fullName": "Administrator", "disabled": False, "language": "de", "privilege": "sysadmin", # SysAdmin privilege "hashedPassword": self._getPasswordHash("admin") # Use a secure password in production! } createdUser = self.db.recordCreate("users", adminUser) logger.info(f"Admin user created with ID {createdUser['id']}") # Update user context self.userId = createdUser['id'] # Recreate connector with correct context self.db = DatabaseConnector( dbHost=APP_CONFIG.get("DB_SYSTEM_HOST"), dbDatabase=APP_CONFIG.get("DB_SYSTEM_DATABASE"), dbUser=APP_CONFIG.get("DB_SYSTEM_USER"), dbPassword=APP_CONFIG.get("DB_SYSTEM_PASSWORD_SECRET"), mandateId=self.mandateId, userId=self.userId ) def getInitialId(self, table: str) -> Optional[int]: """Returns the initial ID for a table""" return self.db.getInitialId(table) def _getPasswordHash(self, password: str) -> str: """Creates a hash for a password""" return pwdContext.hash(password) def _verifyPassword(self, plainPassword: str, hashedPassword: str) -> bool: """Checks if the password matches the hash""" return pwdContext.verify(plainPassword, hashedPassword) def _getCurrentTimestamp(self) -> str: """Returns the current timestamp in ISO format""" from datetime import datetime return datetime.now().isoformat() # Mandate methods def getAllMandates(self) -> List[Dict[str, Any]]: """Returns all mandates""" return self.db.getRecordset("mandates") def getMandate(self, mandateId: int) -> Optional[Dict[str, Any]]: """Returns a mandate by its ID""" mandates = self.db.getRecordset("mandates", recordFilter={"id": mandateId}) if mandates: return mandates[0] return None def createMandate(self, name: str, language: str = "de") -> Dict[str, Any]: """Creates a new mandate""" mandateData = { "name": name, "language": language } return self.db.recordCreate("mandates", mandateData) # User methods def getAllUsers(self) -> List[Dict[str, Any]]: """Returns all users""" users = self.db.getRecordset("users") # Remove password hashes from the response for user in users: if "hashedPassword" in user: del user["hashedPassword"] return users def getUsersByMandate(self, mandateId: int) -> List[Dict[str, Any]]: """ Returns all users of a specific mandate Args: mandateId: The ID of the mandate Returns: List[Dict[str, Any]]: List of users in the mandate """ users = self.db.getRecordset("users", recordFilter={"mandateId": mandateId}) # Remove password hashes from the response for user in users: if "hashedPassword" in user: del user["hashedPassword"] return users def getUserByUsername(self, username: str) -> Optional[Dict[str, Any]]: """Returns a user by username""" users = self.db.getRecordset("users") for user in users: if user.get("username") == username: return user return None def getUser(self, userId: int) -> Optional[Dict[str, Any]]: """Returns a user by ID""" users = self.db.getRecordset("users", recordFilter={"id": userId}) if users: user = users[0] # Remove password hash from the API response if "hashedPassword" in user: userCopy = user.copy() del userCopy["hashedPassword"] return userCopy return user return None def authenticateUser(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.getUserByUsername(username) if not user: return None if not self._verifyPassword(password, user.get("hashedPassword", "")): return None # Check if the user is disabled if user.get("disabled", False): return None # Create a copy without password hash authenticatedUser = {**user} if "hashedPassword" in authenticatedUser: del authenticatedUser["hashedPassword"] return authenticatedUser # Singleton factory for GatewayInterface instances per context _gatewayInterfaces = {} def getGatewayInterface(mandateId: int = None, userId: int = None) -> GatewayInterface: """ Returns a GatewayInterface instance for the specified context. Reuses existing instances. Args: mandateId: ID of the mandate userId: ID of the user Returns: GatewayInterface instance """ contextKey = f"{mandateId}_{userId}" if contextKey not in _gatewayInterfaces: _gatewayInterfaces[contextKey] = GatewayInterface(mandateId, userId) return _gatewayInterfaces[contextKey] # Initialize the interface getGatewayInterface()