964 lines
30 KiB
Python
964 lines
30 KiB
Python
"""
|
|
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)
|
|
|
|
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 _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)
|