gateway/modules/migration/migrateUamToRbac.py
2025-12-07 13:48:39 +01:00

212 lines
9 KiB
Python

"""
Migration script to convert UAM (User Access Management) to RBAC (Role-Based Access Control).
This script:
1. Creates AccessRule table if it doesn't exist
2. Adds roleLabels column to User table if it doesn't exist
3. Converts User.privilege to User.roleLabels
4. Creates initial RBAC rules based on bootstrap logic
"""
import logging
from typing import List, Dict, Any
from modules.connectors.connectorDbPostgre import DatabaseConnector
from modules.shared.configuration import APP_CONFIG
from modules.datamodels.datamodelUam import UserInDB, UserPrivilege
from modules.datamodels.datamodelRbac import AccessRule, AccessRuleContext
from modules.datamodels.datamodelUam import AccessLevel
from modules.interfaces.interfaceBootstrap import initRbacRules
logger = logging.getLogger(__name__)
def migrateUamToRbac(db: DatabaseConnector, dryRun: bool = False) -> Dict[str, Any]:
"""
Migrate from UAM to RBAC system.
Args:
db: Database connector instance
dryRun: If True, only report what would be done without making changes
Returns:
Dictionary with migration results
"""
results = {
"schemaChanges": [],
"dataMigrations": [],
"rulesCreated": 0,
"usersUpdated": 0,
"errors": []
}
try:
# Step 1: Ensure AccessRule table exists
logger.info("Step 1: Ensuring AccessRule table exists")
if not dryRun:
db._ensureTableExists(AccessRule)
results["schemaChanges"].append("AccessRule table ensured")
else:
results["schemaChanges"].append("Would ensure AccessRule table")
# Step 2: Add roleLabels column to UserInDB table if it doesn't exist
logger.info("Step 2: Adding roleLabels column to UserInDB table")
if not dryRun:
try:
with db.connection.cursor() as cursor:
# Check if column exists
cursor.execute("""
SELECT column_name
FROM information_schema.columns
WHERE table_name = 'UserInDB' AND column_name = 'roleLabels'
""")
columnExists = cursor.fetchone() is not None
if not columnExists:
cursor.execute('ALTER TABLE "UserInDB" ADD COLUMN "roleLabels" JSONB DEFAULT \'[]\'::jsonb')
db.connection.commit()
results["schemaChanges"].append("Added roleLabels column to UserInDB")
logger.info("Added roleLabels column to UserInDB table")
else:
results["schemaChanges"].append("roleLabels column already exists")
logger.info("roleLabels column already exists in UserInDB table")
except Exception as e:
logger.error(f"Error adding roleLabels column: {e}")
results["errors"].append(f"Error adding roleLabels column: {e}")
db.connection.rollback()
else:
results["schemaChanges"].append("Would add roleLabels column to UserInDB")
# Step 3: Convert User.privilege to User.roleLabels
logger.info("Step 3: Converting User.privilege to User.roleLabels")
if not dryRun:
try:
users = db.getRecordset(UserInDB)
updatedCount = 0
for user in users:
privilege = user.get("privilege")
roleLabels = user.get("roleLabels", [])
# Skip if already has roleLabels
if roleLabels and isinstance(roleLabels, list) and len(roleLabels) > 0:
logger.debug(f"User {user.get('id')} already has roleLabels: {roleLabels}")
continue
# Convert privilege to roleLabels
if privilege == UserPrivilege.SYSADMIN.value:
newRoleLabels = ["sysadmin"]
elif privilege == UserPrivilege.ADMIN.value:
newRoleLabels = ["admin"]
elif privilege == UserPrivilege.USER.value:
newRoleLabels = ["user"]
else:
# Default to user if privilege is unknown
newRoleLabels = ["user"]
logger.warning(f"Unknown privilege '{privilege}' for user {user.get('id')}, defaulting to 'user'")
# Update user
user["roleLabels"] = newRoleLabels
db.recordModify(UserInDB, user["id"], user)
updatedCount += 1
logger.info(f"Updated user {user.get('id')} ({user.get('username')}): {privilege} -> {newRoleLabels}")
results["usersUpdated"] = updatedCount
logger.info(f"Updated {updatedCount} users with roleLabels")
except Exception as e:
logger.error(f"Error converting user privileges: {e}")
results["errors"].append(f"Error converting user privileges: {e}")
else:
# Dry run: count users that would be updated
users = db.getRecordset(UserInDB)
wouldUpdate = 0
for user in users:
roleLabels = user.get("roleLabels", [])
if not roleLabels or not isinstance(roleLabels, list) or len(roleLabels) == 0:
wouldUpdate += 1
results["usersUpdated"] = wouldUpdate
logger.info(f"Would update {wouldUpdate} users with roleLabels")
# Step 4: Create RBAC rules if they don't exist
logger.info("Step 4: Creating RBAC rules")
if not dryRun:
try:
existingRules = db.getRecordset(AccessRule)
if existingRules:
results["rulesCreated"] = len(existingRules)
results["dataMigrations"].append(f"RBAC rules already exist ({len(existingRules)} rules)")
logger.info(f"RBAC rules already exist ({len(existingRules)} rules)")
else:
# Initialize RBAC rules using bootstrap logic
initRbacRules(db)
newRules = db.getRecordset(AccessRule)
results["rulesCreated"] = len(newRules)
results["dataMigrations"].append(f"Created {len(newRules)} RBAC rules")
logger.info(f"Created {len(newRules)} RBAC rules")
except Exception as e:
logger.error(f"Error creating RBAC rules: {e}")
results["errors"].append(f"Error creating RBAC rules: {e}")
else:
existingRules = db.getRecordset(AccessRule)
if existingRules:
results["rulesCreated"] = len(existingRules)
results["dataMigrations"].append(f"RBAC rules already exist ({len(existingRules)} rules)")
else:
results["dataMigrations"].append("Would create RBAC rules")
logger.info("Migration completed successfully")
return results
except Exception as e:
logger.error(f"Migration failed: {e}")
results["errors"].append(f"Migration failed: {e}")
return results
def validateMigration(db: DatabaseConnector) -> Dict[str, Any]:
"""
Validate that migration was successful.
Args:
db: Database connector instance
Returns:
Dictionary with validation results
"""
validation = {
"valid": True,
"issues": []
}
try:
# Check that AccessRule table exists
try:
rules = db.getRecordset(AccessRule)
if not rules:
validation["valid"] = False
validation["issues"].append("AccessRule table exists but has no rules")
except Exception as e:
validation["valid"] = False
validation["issues"].append(f"AccessRule table does not exist or is not accessible: {e}")
# Check that all users have roleLabels
users = db.getRecordset(UserInDB)
usersWithoutRoles = []
for user in users:
roleLabels = user.get("roleLabels", [])
if not roleLabels or not isinstance(roleLabels, list) or len(roleLabels) == 0:
usersWithoutRoles.append({
"id": user.get("id"),
"username": user.get("username"),
"privilege": user.get("privilege")
})
if usersWithoutRoles:
validation["valid"] = False
validation["issues"].append(f"{len(usersWithoutRoles)} users without roleLabels: {[u['username'] for u in usersWithoutRoles]}")
return validation
except Exception as e:
validation["valid"] = False
validation["issues"].append(f"Validation error: {e}")
return validation