# Copyright (c) 2025 Patrick Motsch # All rights reserved. """ Centralized bootstrap interface for system initialization. Contains all bootstrap logic including mandate, users, and RBAC rules. """ import logging from typing import Optional, List, Dict, Any from passlib.context import CryptContext from modules.connectors.connectorDbPostgre import DatabaseConnector from modules.shared.configuration import APP_CONFIG from modules.datamodels.datamodelUam import ( Mandate, UserInDB, AuthAuthority, ) from modules.datamodels.datamodelRbac import ( AccessRule, AccessRuleContext, Role, ) from modules.datamodels.datamodelUam import AccessLevel logger = logging.getLogger(__name__) # Password-Hashing pwdContext = CryptContext(schemes=["argon2"], deprecated="auto") def initBootstrap(db: DatabaseConnector) -> None: """ Main bootstrap entry point - initializes all system components. Args: db: Database connector instance """ logger.info("Starting system bootstrap") # Initialize root mandate mandateId = initRootMandate(db) # Initialize admin user adminUserId = initAdminUser(db, mandateId) # Initialize event user eventUserId = initEventUser(db, mandateId) # Initialize roles initRoles(db) # Initialize RBAC rules initRbacRules(db) # Assign initial user roles if adminUserId and eventUserId: assignInitialUserRoles(db, adminUserId, eventUserId) logger.info("System bootstrap completed") def initRootMandate(db: DatabaseConnector) -> Optional[str]: """ Creates the Root mandate if it doesn't exist. Args: db: Database connector instance Returns: Mandate ID if created or found, None otherwise """ existingMandates = db.getRecordset(Mandate) if existingMandates: mandateId = existingMandates[0].get("id") logger.info(f"Root mandate already exists with ID {mandateId}") return mandateId logger.info("Creating Root mandate") rootMandate = Mandate(name="Root", language="en", enabled=True) createdMandate = db.recordCreate(Mandate, rootMandate) mandateId = createdMandate.get("id") logger.info(f"Root mandate created with ID {mandateId}") return mandateId def initAdminUser(db: DatabaseConnector, mandateId: Optional[str]) -> Optional[str]: """ Creates the Admin user if it doesn't exist. Args: db: Database connector instance mandateId: Root mandate ID Returns: User ID if created or found, None otherwise """ existingUsers = db.getRecordset(UserInDB, recordFilter={"username": "admin"}) if existingUsers: userId = existingUsers[0].get("id") logger.info(f"Admin user already exists with ID {userId}") return userId logger.info("Creating Admin user") adminUser = UserInDB( mandateId=mandateId, username="admin", email="admin@example.com", fullName="Administrator", enabled=True, language="en", roleLabels=["sysadmin"], authenticationAuthority=AuthAuthority.LOCAL, hashedPassword=_getPasswordHash(APP_CONFIG.get("APP_INIT_PASS_ADMIN_SECRET")), connections=[], ) createdUser = db.recordCreate(UserInDB, adminUser) userId = createdUser.get("id") logger.info(f"Admin user created with ID {userId}") return userId def initEventUser(db: DatabaseConnector, mandateId: Optional[str]) -> Optional[str]: """ Creates the Event user if it doesn't exist. Args: db: Database connector instance mandateId: Root mandate ID Returns: User ID if created or found, None otherwise """ existingUsers = db.getRecordset(UserInDB, recordFilter={"username": "event"}) if existingUsers: userId = existingUsers[0].get("id") logger.info(f"Event user already exists with ID {userId}") return userId logger.info("Creating Event user") eventUser = UserInDB( mandateId=mandateId, username="event", email="event@example.com", fullName="Event", enabled=True, language="en", roleLabels=["sysadmin"], authenticationAuthority=AuthAuthority.LOCAL, hashedPassword=_getPasswordHash(APP_CONFIG.get("APP_INIT_PASS_EVENT_SECRET")), connections=[], ) createdUser = db.recordCreate(UserInDB, eventUser) userId = createdUser.get("id") logger.info(f"Event user created with ID {userId}") return userId def initRoles(db: DatabaseConnector) -> None: """ Initialize standard roles if they don't exist. Args: db: Database connector instance """ logger.info("Initializing roles") standardRoles = [ Role( roleLabel="sysadmin", description={"en": "System Administrator - Full access to all system resources", "fr": "Administrateur système - Accès complet à toutes les ressources"}, isSystemRole=True ), Role( roleLabel="admin", description={"en": "Administrator - Manage users and resources within mandate scope", "fr": "Administrateur - Gérer les utilisateurs et ressources dans le périmètre du mandat"}, isSystemRole=True ), Role( roleLabel="user", description={"en": "User - Standard user with access to own records", "fr": "Utilisateur - Utilisateur standard avec accès à ses propres enregistrements"}, isSystemRole=True ), Role( roleLabel="viewer", description={"en": "Viewer - Read-only access to group records", "fr": "Visualiseur - Accès en lecture seule aux enregistrements du groupe"}, isSystemRole=True ), ] existingRoles = db.getRecordset(Role) existingRoleLabels = {role.get("roleLabel") for role in existingRoles} for role in standardRoles: if role.roleLabel not in existingRoleLabels: try: db.recordCreate(Role, role) logger.info(f"Created role: {role.roleLabel}") except Exception as e: logger.warning(f"Error creating role {role.roleLabel}: {e}") else: logger.debug(f"Role {role.roleLabel} already exists") logger.info("Roles initialization completed") def initRbacRules(db: DatabaseConnector) -> None: """ Initialize RBAC rules if they don't exist. Converts all UAM logic from interface*Access.py modules to RBAC rules. Also checks for and adds missing rules for new tables. Args: db: Database connector instance """ existingRules = db.getRecordset(AccessRule) if existingRules: logger.info(f"RBAC rules already exist ({len(existingRules)} rules)") # Check for missing rules for ChatWorkflow and Prompt tables _addMissingTableRules(db, existingRules) return logger.info("Initializing RBAC rules") # Create default role rules createDefaultRoleRules(db) # Create table-specific rules (converted from UAM logic) createTableSpecificRules(db) # Create UI context rules createUiContextRules(db) # Create RESOURCE context rules createResourceContextRules(db) # Create Action-specific RBAC rules createActionRules(db) logger.info("RBAC rules initialization completed") def createDefaultRoleRules(db: DatabaseConnector) -> None: """ Create default role rules for generic access (item = null). Args: db: Database connector instance """ defaultRules = [ # SysAdmin Role - Full access to all AccessRule( roleLabel="sysadmin", context=AccessRuleContext.DATA, item=None, view=True, read=AccessLevel.ALL, create=AccessLevel.ALL, update=AccessLevel.ALL, delete=AccessLevel.ALL, ), # Admin Role - Group-level access AccessRule( roleLabel="admin", context=AccessRuleContext.DATA, item=None, view=True, read=AccessLevel.GROUP, create=AccessLevel.GROUP, update=AccessLevel.GROUP, delete=AccessLevel.NONE, ), # User Role - My records only AccessRule( roleLabel="user", context=AccessRuleContext.DATA, item=None, view=True, read=AccessLevel.MY, create=AccessLevel.MY, update=AccessLevel.MY, delete=AccessLevel.MY, ), # Viewer Role - Read-only group access AccessRule( roleLabel="viewer", context=AccessRuleContext.DATA, item=None, view=True, read=AccessLevel.GROUP, create=AccessLevel.NONE, update=AccessLevel.NONE, delete=AccessLevel.NONE, ), ] for rule in defaultRules: db.recordCreate(AccessRule, rule) logger.info(f"Created {len(defaultRules)} default role rules") def createTableSpecificRules(db: DatabaseConnector) -> None: """ Create table-specific rules converted from UAM logic. These rules override generic rules for specific tables. Args: db: Database connector instance """ tableRules = [] # Mandate table - Only sysadmin can access tableRules.append(AccessRule( roleLabel="sysadmin", context=AccessRuleContext.DATA, item="Mandate", view=True, read=AccessLevel.ALL, create=AccessLevel.ALL, update=AccessLevel.ALL, delete=AccessLevel.ALL, )) tableRules.append(AccessRule( roleLabel="admin", context=AccessRuleContext.DATA, item="Mandate", view=False, read=AccessLevel.NONE, create=AccessLevel.NONE, update=AccessLevel.NONE, delete=AccessLevel.NONE, )) tableRules.append(AccessRule( roleLabel="user", context=AccessRuleContext.DATA, item="Mandate", view=False, read=AccessLevel.NONE, create=AccessLevel.NONE, update=AccessLevel.NONE, delete=AccessLevel.NONE, )) tableRules.append(AccessRule( roleLabel="viewer", context=AccessRuleContext.DATA, item="Mandate", view=False, read=AccessLevel.NONE, create=AccessLevel.NONE, update=AccessLevel.NONE, delete=AccessLevel.NONE, )) # UserInDB table tableRules.append(AccessRule( roleLabel="sysadmin", context=AccessRuleContext.DATA, item="UserInDB", view=True, read=AccessLevel.ALL, create=AccessLevel.ALL, update=AccessLevel.ALL, delete=AccessLevel.ALL, )) tableRules.append(AccessRule( roleLabel="admin", context=AccessRuleContext.DATA, item="UserInDB", view=True, read=AccessLevel.GROUP, create=AccessLevel.GROUP, update=AccessLevel.GROUP, delete=AccessLevel.GROUP, )) tableRules.append(AccessRule( roleLabel="user", context=AccessRuleContext.DATA, item="UserInDB", view=True, read=AccessLevel.MY, create=AccessLevel.NONE, update=AccessLevel.MY, delete=AccessLevel.NONE, )) tableRules.append(AccessRule( roleLabel="viewer", context=AccessRuleContext.DATA, item="UserInDB", view=True, read=AccessLevel.MY, create=AccessLevel.NONE, update=AccessLevel.NONE, delete=AccessLevel.NONE, )) # UserConnection table tableRules.append(AccessRule( roleLabel="sysadmin", context=AccessRuleContext.DATA, item="UserConnection", view=True, read=AccessLevel.ALL, create=AccessLevel.ALL, update=AccessLevel.ALL, delete=AccessLevel.ALL, )) tableRules.append(AccessRule( roleLabel="admin", context=AccessRuleContext.DATA, item="UserConnection", view=True, read=AccessLevel.GROUP, create=AccessLevel.GROUP, update=AccessLevel.GROUP, delete=AccessLevel.GROUP, )) tableRules.append(AccessRule( roleLabel="user", context=AccessRuleContext.DATA, item="UserConnection", view=True, read=AccessLevel.MY, create=AccessLevel.MY, update=AccessLevel.MY, delete=AccessLevel.MY, )) tableRules.append(AccessRule( roleLabel="viewer", context=AccessRuleContext.DATA, item="UserConnection", view=True, read=AccessLevel.MY, create=AccessLevel.NONE, update=AccessLevel.NONE, delete=AccessLevel.NONE, )) # DataNeutraliserConfig table tableRules.append(AccessRule( roleLabel="sysadmin", context=AccessRuleContext.DATA, item="DataNeutraliserConfig", view=True, read=AccessLevel.ALL, create=AccessLevel.ALL, update=AccessLevel.ALL, delete=AccessLevel.ALL, )) tableRules.append(AccessRule( roleLabel="admin", context=AccessRuleContext.DATA, item="DataNeutraliserConfig", view=True, read=AccessLevel.GROUP, create=AccessLevel.GROUP, update=AccessLevel.GROUP, delete=AccessLevel.GROUP, )) tableRules.append(AccessRule( roleLabel="user", context=AccessRuleContext.DATA, item="DataNeutraliserConfig", view=True, read=AccessLevel.MY, create=AccessLevel.MY, update=AccessLevel.MY, delete=AccessLevel.MY, )) tableRules.append(AccessRule( roleLabel="viewer", context=AccessRuleContext.DATA, item="DataNeutraliserConfig", view=True, read=AccessLevel.MY, create=AccessLevel.NONE, update=AccessLevel.NONE, delete=AccessLevel.NONE, )) # DataNeutralizerAttributes table tableRules.append(AccessRule( roleLabel="sysadmin", context=AccessRuleContext.DATA, item="DataNeutralizerAttributes", view=True, read=AccessLevel.ALL, create=AccessLevel.ALL, update=AccessLevel.ALL, delete=AccessLevel.ALL, )) tableRules.append(AccessRule( roleLabel="admin", context=AccessRuleContext.DATA, item="DataNeutralizerAttributes", view=True, read=AccessLevel.GROUP, create=AccessLevel.GROUP, update=AccessLevel.GROUP, delete=AccessLevel.GROUP, )) tableRules.append(AccessRule( roleLabel="user", context=AccessRuleContext.DATA, item="DataNeutralizerAttributes", view=True, read=AccessLevel.MY, create=AccessLevel.MY, update=AccessLevel.MY, delete=AccessLevel.MY, )) tableRules.append(AccessRule( roleLabel="viewer", context=AccessRuleContext.DATA, item="DataNeutralizerAttributes", view=True, read=AccessLevel.MY, create=AccessLevel.NONE, update=AccessLevel.NONE, delete=AccessLevel.NONE, )) # AuthEvent table tableRules.append(AccessRule( roleLabel="sysadmin", context=AccessRuleContext.DATA, item="AuthEvent", view=True, read=AccessLevel.ALL, create=AccessLevel.NONE, update=AccessLevel.NONE, delete=AccessLevel.ALL, )) tableRules.append(AccessRule( roleLabel="admin", context=AccessRuleContext.DATA, item="AuthEvent", view=True, read=AccessLevel.ALL, create=AccessLevel.NONE, update=AccessLevel.NONE, delete=AccessLevel.ALL, )) tableRules.append(AccessRule( roleLabel="user", context=AccessRuleContext.DATA, item="AuthEvent", view=True, read=AccessLevel.MY, create=AccessLevel.NONE, update=AccessLevel.NONE, delete=AccessLevel.NONE, )) tableRules.append(AccessRule( roleLabel="viewer", context=AccessRuleContext.DATA, item="AuthEvent", view=True, read=AccessLevel.MY, create=AccessLevel.NONE, update=AccessLevel.NONE, delete=AccessLevel.NONE, )) # ChatWorkflow table - Users can access their own workflows tableRules.append(AccessRule( roleLabel="sysadmin", context=AccessRuleContext.DATA, item="ChatWorkflow", view=True, read=AccessLevel.ALL, create=AccessLevel.ALL, update=AccessLevel.ALL, delete=AccessLevel.ALL, )) tableRules.append(AccessRule( roleLabel="admin", context=AccessRuleContext.DATA, item="ChatWorkflow", view=True, read=AccessLevel.GROUP, create=AccessLevel.GROUP, update=AccessLevel.GROUP, delete=AccessLevel.GROUP, )) tableRules.append(AccessRule( roleLabel="user", context=AccessRuleContext.DATA, item="ChatWorkflow", view=True, read=AccessLevel.MY, create=AccessLevel.MY, update=AccessLevel.MY, delete=AccessLevel.MY, )) tableRules.append(AccessRule( roleLabel="viewer", context=AccessRuleContext.DATA, item="ChatWorkflow", view=True, read=AccessLevel.MY, create=AccessLevel.NONE, update=AccessLevel.NONE, delete=AccessLevel.NONE, )) # Prompt table - Users can access their own prompts tableRules.append(AccessRule( roleLabel="sysadmin", context=AccessRuleContext.DATA, item="Prompt", view=True, read=AccessLevel.ALL, create=AccessLevel.ALL, update=AccessLevel.ALL, delete=AccessLevel.ALL, )) tableRules.append(AccessRule( roleLabel="admin", context=AccessRuleContext.DATA, item="Prompt", view=True, read=AccessLevel.GROUP, create=AccessLevel.GROUP, update=AccessLevel.GROUP, delete=AccessLevel.GROUP, )) tableRules.append(AccessRule( roleLabel="user", context=AccessRuleContext.DATA, item="Prompt", view=True, read=AccessLevel.MY, create=AccessLevel.MY, update=AccessLevel.MY, delete=AccessLevel.MY, )) tableRules.append(AccessRule( roleLabel="viewer", context=AccessRuleContext.DATA, item="Prompt", view=True, read=AccessLevel.MY, create=AccessLevel.NONE, update=AccessLevel.NONE, delete=AccessLevel.NONE, )) # Create all table-specific rules for rule in tableRules: db.recordCreate(AccessRule, rule) logger.info(f"Created {len(tableRules)} table-specific rules") def createUiContextRules(db: DatabaseConnector) -> None: """ Create UI context rules for controlling UI element visibility. These rules control which UI components users can see based on their roles. Args: db: Database connector instance """ uiRules = [] # Generic UI rules - all roles can view UI by default # Specific UI elements can override these with more restrictive rules # Sysadmin - full UI access uiRules.append(AccessRule( roleLabel="sysadmin", context=AccessRuleContext.UI, item=None, view=True, read=None, create=None, update=None, delete=None, )) # Admin - full UI access uiRules.append(AccessRule( roleLabel="admin", context=AccessRuleContext.UI, item=None, view=True, read=None, create=None, update=None, delete=None, )) # User - full UI access uiRules.append(AccessRule( roleLabel="user", context=AccessRuleContext.UI, item=None, view=True, read=None, create=None, update=None, delete=None, )) # Viewer - full UI access (can view but may have restricted actions) uiRules.append(AccessRule( roleLabel="viewer", context=AccessRuleContext.UI, item=None, view=True, read=None, create=None, update=None, delete=None, )) # Create all UI context rules for rule in uiRules: db.recordCreate(AccessRule, rule) logger.info(f"Created {len(uiRules)} UI context rules") def createResourceContextRules(db: DatabaseConnector) -> None: """ Create RESOURCE context rules for controlling resource access (AI models, actions, etc.). These rules control which resources users can access based on their roles. Args: db: Database connector instance """ resourceRules = [] # Generic resource rules - all roles can access resources by default # Specific resources can override these with more restrictive rules # Sysadmin - full resource access resourceRules.append(AccessRule( roleLabel="sysadmin", context=AccessRuleContext.RESOURCE, item=None, view=True, read=None, create=None, update=None, delete=None, )) # Admin - full resource access resourceRules.append(AccessRule( roleLabel="admin", context=AccessRuleContext.RESOURCE, item=None, view=True, read=None, create=None, update=None, delete=None, )) # User - full resource access resourceRules.append(AccessRule( roleLabel="user", context=AccessRuleContext.RESOURCE, item=None, view=True, read=None, create=None, update=None, delete=None, )) # Viewer - full resource access (can view but may have restricted actions) resourceRules.append(AccessRule( roleLabel="viewer", context=AccessRuleContext.RESOURCE, item=None, view=True, read=None, create=None, update=None, delete=None, )) # Create all RESOURCE context rules for rule in resourceRules: db.recordCreate(AccessRule, rule) logger.info(f"Created {len(resourceRules)} RESOURCE context rules") def createActionRules(db: DatabaseConnector) -> None: """ Create default RBAC rules for workflow actions. This function dynamically discovers all available actions from all methods and creates RBAC rules for them. Actions are protected via RESOURCE context with actionId as the item identifier (format: 'module.actionName'). Args: db: Database connector instance """ try: # Import method discovery to get all actions from modules.workflows.processing.shared.methodDiscovery import discoverMethods from modules.services import getInterface as getServices from modules.datamodels.datamodelUam import User # Create a temporary user context for discovery (will be filtered by RBAC later) # We need to discover methods, but we'll use a minimal user context # In production, this should use a system user or admin user try: # Try to get an admin user for discovery adminUsers = db.getRecordset("User", recordFilter={"roleLabel": "sysadmin"}, limit=1) if adminUsers: tempUser = User(**adminUsers[0]) else: # Fallback: create minimal user context tempUser = User(id="system", roleLabel="sysadmin") except: # Fallback: create minimal user context tempUser = User(id="system", roleLabel="sysadmin") # Get services and discover methods services = getServices(tempUser, None) discoverMethods(services) # Import methods catalog from modules.workflows.processing.shared.methodDiscovery import methods # Collect all action IDs allActionIds = [] for methodName, methodInfo in methods.items(): # Skip duplicate entries (same method stored with full and short name) if methodName.startswith('Method'): continue methodInstance = methodInfo['instance'] methodActions = methodInstance.actions for actionName in methodActions.keys(): actionId = f"{methodInstance.name}.{actionName}" allActionIds.append(actionId) logger.info(f"Discovered {len(allActionIds)} actions for RBAC rule creation") # Define default action access by role # SysAdmin and Admin: Access to all actions # User: Access to common actions (read, search, process, etc.) # Viewer: Read-only actions actionRules = [] # All roles: Generic access to all actions # Using item=None grants access to all resources (all actions) in RESOURCE context # SysAdmin: Access to all actions actionRules.append(AccessRule( roleLabel="sysadmin", context=AccessRuleContext.RESOURCE, item=None, # All resources (covers all actions) view=True )) # Admin: Access to all actions actionRules.append(AccessRule( roleLabel="admin", context=AccessRuleContext.RESOURCE, item=None, # All resources (covers all actions) view=True )) # User: Access to all actions (generic rights) actionRules.append(AccessRule( roleLabel="user", context=AccessRuleContext.RESOURCE, item=None, # All resources (covers all actions) view=True )) # Create all action rules for rule in actionRules: db.recordCreate(AccessRule, rule) logger.info(f"Created {len(actionRules)} action RBAC rules") except Exception as e: logger.error(f"Error creating action RBAC rules: {str(e)}", exc_info=True) # Don't fail bootstrap if action rules can't be created # They can be created manually or via migration script def _addMissingTableRules(db: DatabaseConnector, existingRules: List[Dict[str, Any]]) -> None: """ Add missing RBAC rules for tables that were added after initial bootstrap. Args: db: Database connector instance existingRules: List of existing AccessRule records """ # Check which tables already have rules existingItems = {rule.get("item") for rule in existingRules if rule.get("context") == AccessRuleContext.DATA} existingRoles = {rule.get("roleLabel") for rule in existingRules} # Tables that need rules requiredTables = ["ChatWorkflow", "Prompt"] requiredRoles = ["sysadmin", "admin", "user", "viewer"] newRules = [] for table in requiredTables: if table not in existingItems: logger.info(f"Adding missing RBAC rules for table {table}") # ChatWorkflow rules if table == "ChatWorkflow": for roleLabel in requiredRoles: if roleLabel == "sysadmin": newRules.append(AccessRule( roleLabel=roleLabel, context=AccessRuleContext.DATA, item=table, view=True, read=AccessLevel.ALL, create=AccessLevel.ALL, update=AccessLevel.ALL, delete=AccessLevel.ALL, )) elif roleLabel == "admin": newRules.append(AccessRule( roleLabel=roleLabel, context=AccessRuleContext.DATA, item=table, view=True, read=AccessLevel.GROUP, create=AccessLevel.GROUP, update=AccessLevel.GROUP, delete=AccessLevel.GROUP, )) elif roleLabel == "user": newRules.append(AccessRule( roleLabel=roleLabel, context=AccessRuleContext.DATA, item=table, view=True, read=AccessLevel.MY, create=AccessLevel.MY, update=AccessLevel.MY, delete=AccessLevel.MY, )) elif roleLabel == "viewer": newRules.append(AccessRule( roleLabel=roleLabel, context=AccessRuleContext.DATA, item=table, view=True, read=AccessLevel.MY, create=AccessLevel.NONE, update=AccessLevel.NONE, delete=AccessLevel.NONE, )) # Prompt rules (same as ChatWorkflow) elif table == "Prompt": for roleLabel in requiredRoles: if roleLabel == "sysadmin": newRules.append(AccessRule( roleLabel=roleLabel, context=AccessRuleContext.DATA, item=table, view=True, read=AccessLevel.ALL, create=AccessLevel.ALL, update=AccessLevel.ALL, delete=AccessLevel.ALL, )) elif roleLabel == "admin": newRules.append(AccessRule( roleLabel=roleLabel, context=AccessRuleContext.DATA, item=table, view=True, read=AccessLevel.GROUP, create=AccessLevel.GROUP, update=AccessLevel.GROUP, delete=AccessLevel.GROUP, )) elif roleLabel == "user": newRules.append(AccessRule( roleLabel=roleLabel, context=AccessRuleContext.DATA, item=table, view=True, read=AccessLevel.MY, create=AccessLevel.MY, update=AccessLevel.MY, delete=AccessLevel.MY, )) elif roleLabel == "viewer": newRules.append(AccessRule( roleLabel=roleLabel, context=AccessRuleContext.DATA, item=table, view=True, read=AccessLevel.MY, create=AccessLevel.NONE, update=AccessLevel.NONE, delete=AccessLevel.NONE, )) # Create missing rules if newRules: for rule in newRules: db.recordCreate(AccessRule, rule) logger.info(f"Added {len(newRules)} missing RBAC rules") def assignInitialUserRoles(db: DatabaseConnector, adminUserId: str, eventUserId: str) -> None: """ Assign initial roles to admin and event users. Args: db: Database connector instance adminUserId: Admin user ID eventUserId: Event user ID """ # Set context to admin user for bootstrap operations originalUserId = db.userId if hasattr(db, 'userId') else None try: if adminUserId: db.updateContext(adminUserId) # Update admin user with sysadmin role adminUser = db.getRecordset(UserInDB, recordFilter={"id": adminUserId}) if adminUser: adminUserData = adminUser[0] roleLabels = adminUserData.get("roleLabels") or [] if "sysadmin" not in roleLabels: adminUserData["roleLabels"] = roleLabels + ["sysadmin"] db.recordModify(UserInDB, adminUserId, adminUserData) logger.info(f"Assigned sysadmin role to admin user {adminUserId}") # Update event user with sysadmin role eventUser = db.getRecordset(UserInDB, recordFilter={"id": eventUserId}) if eventUser: eventUserData = eventUser[0] roleLabels = eventUserData.get("roleLabels") or [] if "sysadmin" not in roleLabels: eventUserData["roleLabels"] = roleLabels + ["sysadmin"] db.recordModify(UserInDB, eventUserId, eventUserData) logger.info(f"Assigned sysadmin role to event user {eventUserId}") finally: # Restore original context if it existed if originalUserId: db.updateContext(originalUserId) elif hasattr(db, 'userId'): # If original was None/empty, just set it directly db.userId = originalUserId def _getPasswordHash(password: Optional[str]) -> Optional[str]: """ Hash a password using Argon2. Args: password: Plain text password Returns: Hashed password or None if password is None """ if password is None: return None return pwdContext.hash(password)