212 lines
9 KiB
Python
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
|