# /opt/docker/dev/service_finder/backend/app/scripts/seed_rbac.py """ RBAC Phase 1: Seed script for database-driven Role-Based Access Control. This script initializes the database with: - 6 system roles (SUPERADMIN, ADMIN, MODERATOR, SALES_REP, SERVICE_MGR, USER) - 28 permission codes across 8 domains - Role-permission mappings according to the audit proposal matrix - User migration: maps existing UserRole enum values to new role_id FK Usage: docker exec sf_api python -m app.scripts.seed_rbac """ import asyncio import logging from sqlalchemy import select, text, update from app.db.session import AsyncSessionLocal from app.models.system.rbac import SystemRole, SystemPermission, SystemRolePermission from app.models.identity.identity import User, UserRole logging.basicConfig(level=logging.INFO, format='%(asctime)s [%(levelname)s]: %(message)s') logger = logging.getLogger("Seed-RBAC") # ────────────────────────────────────────────── # STEP A: 6 System Roles # ────────────────────────────────────────────── ROLES = [ {"name": "SUPERADMIN", "description": "Platform super administrator with full system access", "rank": 100, "is_system": True}, {"name": "ADMIN", "description": "Platform administrator with elevated management privileges", "rank": 80, "is_system": True}, {"name": "MODERATOR", "description": "Content moderator with limited administrative access", "rank": 60, "is_system": True}, {"name": "SALES_REP", "description": "Sales representative with fleet creation privileges", "rank": 40, "is_system": True}, {"name": "SERVICE_MGR", "description": "Service manager with organization-level access", "rank": 20, "is_system": True}, {"name": "USER", "description": "Regular platform user with basic access", "rank": 0, "is_system": True}, ] # ────────────────────────────────────────────── # STEP B: 28 Permission Codes # ────────────────────────────────────────────── PERMISSIONS = [ # fleet domain (6 permissions) {"code": "fleet:view", "domain": "fleet", "action": "view", "description": "View fleet/vehicle data"}, {"code": "fleet:create", "domain": "fleet", "action": "create", "description": "Create new fleet/vehicle entries"}, {"code": "fleet:edit", "domain": "fleet", "action": "edit", "description": "Edit existing fleet/vehicle data"}, {"code": "fleet:delete", "domain": "fleet", "action": "delete", "description": "Delete fleet/vehicle entries"}, {"code": "fleet:approve", "domain": "fleet", "action": "approve","description": "Approve fleet/vehicle submissions"}, # user domain (5 permissions) {"code": "user:view", "domain": "user", "action": "view", "description": "View user profiles and data"}, {"code": "user:create", "domain": "user", "action": "create", "description": "Create new user accounts"}, {"code": "user:edit", "domain": "user", "action": "edit", "description": "Edit user profiles and data"}, {"code": "user:delete", "domain": "user", "action": "delete", "description": "Delete user accounts"}, {"code": "user:manage-roles", "domain": "user", "action": "manage-roles","description": "Manage user role assignments"}, # finance domain (4 permissions) {"code": "finance:view", "domain": "finance", "action": "view", "description": "View financial data and transactions"}, {"code": "finance:edit", "domain": "finance", "action": "edit", "description": "Edit financial records"}, {"code": "finance:approve", "domain": "finance", "action": "approve", "description": "Approve financial transactions"}, {"code": "finance:refund", "domain": "finance", "action": "refund", "description": "Process refunds"}, # system domain (3 permissions) {"code": "system:view", "domain": "system", "action": "view", "description": "View system configuration"}, {"code": "system:edit", "domain": "system", "action": "edit", "description": "Edit system configuration"}, {"code": "system:manage", "domain": "system", "action": "manage", "description": "Full system management access"}, # org domain (4 permissions) {"code": "org:view", "domain": "org", "action": "view", "description": "View organization data"}, {"code": "org:edit", "domain": "org", "action": "edit", "description": "Edit organization data"}, {"code": "org:delete", "domain": "org", "action": "delete", "description": "Delete organizations"}, {"code": "org:manage-members", "domain": "org", "action": "manage-members","description": "Manage organization members"}, # reports domain (3 permissions) {"code": "reports:view", "domain": "reports", "action": "view", "description": "View reports"}, {"code": "reports:export", "domain": "reports", "action": "export", "description": "Export reports"}, {"code": "reports:manage", "domain": "reports", "action": "manage", "description": "Manage report configurations"}, # audit domain (2 permissions) {"code": "audit:view", "domain": "audit", "action": "view", "description": "View audit logs"}, {"code": "audit:export", "domain": "audit", "action": "export", "description": "Export audit logs"}, # settings domain (2 permissions) {"code": "settings:view", "domain": "settings", "action": "view", "description": "View system settings"}, {"code": "settings:edit", "domain": "settings", "action": "edit", "description": "Edit system settings"}, ] # ────────────────────────────────────────────── # STEP C: Role-Permission Mapping Matrix # Format: { "role_name": {"permission_code": True/False} } # True = granted, False = denied (explicit) # ────────────────────────────────────────────── ROLE_PERMISSION_MATRIX = { "SUPERADMIN": { # fleet - all granted "fleet:view": True, "fleet:create": True, "fleet:edit": True, "fleet:delete": True, "fleet:approve": True, # user - all granted "user:view": True, "user:create": True, "user:edit": True, "user:delete": True, "user:manage-roles": True, # finance - all granted "finance:view": True, "finance:edit": True, "finance:approve": True, "finance:refund": True, # system - all granted "system:view": True, "system:edit": True, "system:manage": True, # org - all granted "org:view": True, "org:edit": True, "org:delete": True, "org:manage-members": True, # reports - all granted "reports:view": True, "reports:export": True, "reports:manage": True, # audit - all granted "audit:view": True, "audit:export": True, # settings - all granted "settings:view": True, "settings:edit": True, }, "ADMIN": { # fleet "fleet:view": True, "fleet:create": True, "fleet:edit": True, "fleet:delete": False, "fleet:approve": True, # user "user:view": True, "user:create": True, "user:edit": True, "user:delete": False, "user:manage-roles": False, # finance "finance:view": True, "finance:edit": True, "finance:approve": False, "finance:refund": False, # system "system:view": True, "system:edit": True, "system:manage": False, # org "org:view": True, "org:edit": True, "org:delete": False, "org:manage-members": True, # reports "reports:view": True, "reports:export": True, "reports:manage": False, # audit "audit:view": False, "audit:export": False, # settings "settings:view": True, "settings:edit": True, }, "MODERATOR": { # fleet "fleet:view": True, "fleet:create": False, "fleet:edit": False, "fleet:delete": False, "fleet:approve": False, # user "user:view": True, "user:create": False, "user:edit": False, "user:delete": False, "user:manage-roles": False, # finance "finance:view": True, "finance:edit": False, "finance:approve": False, "finance:refund": False, # system "system:view": True, "system:edit": False, "system:manage": False, # org "org:view": True, "org:edit": False, "org:delete": False, "org:manage-members": False, # reports "reports:view": True, "reports:export": True, "reports:manage": False, # audit "audit:view": False, "audit:export": False, # settings "settings:view": False, "settings:edit": False, }, "SALES_REP": { # fleet "fleet:view": True, "fleet:create": True, "fleet:edit": False, "fleet:delete": False, "fleet:approve": False, # user "user:view": False, "user:create": False, "user:edit": False, "user:delete": False, "user:manage-roles": False, # finance "finance:view": False, "finance:edit": False, "finance:approve": False, "finance:refund": False, # system "system:view": False, "system:edit": False, "system:manage": False, # org "org:view": True, "org:edit": False, "org:delete": False, "org:manage-members": False, # reports "reports:view": True, "reports:export": False, "reports:manage": False, # audit "audit:view": False, "audit:export": False, # settings "settings:view": False, "settings:edit": False, }, "SERVICE_MGR": { # fleet "fleet:view": True, "fleet:create": False, "fleet:edit": False, "fleet:delete": False, "fleet:approve": False, # user "user:view": False, "user:create": False, "user:edit": False, "user:delete": False, "user:manage-roles": False, # finance "finance:view": False, "finance:edit": False, "finance:approve": False, "finance:refund": False, # system "system:view": False, "system:edit": False, "system:manage": False, # org "org:view": True, "org:edit": False, "org:delete": False, "org:manage-members": False, # reports "reports:view": True, "reports:export": False, "reports:manage": False, # audit "audit:view": False, "audit:export": False, # settings "settings:view": False, "settings:edit": False, }, "USER": { # fleet "fleet:view": True, "fleet:create": False, "fleet:edit": False, "fleet:delete": False, "fleet:approve": False, # user "user:view": False, "user:create": False, "user:edit": False, "user:delete": False, "user:manage-roles": False, # finance "finance:view": False, "finance:edit": False, "finance:approve": False, "finance:refund": False, # system "system:view": False, "system:edit": False, "system:manage": False, # org "org:view": False, "org:edit": False, "org:delete": False, "org:manage-members": False, # reports "reports:view": False, "reports:export": False, "reports:manage": False, # audit "audit:view": False, "audit:export": False, # settings "settings:view": False, "settings:edit": False, }, } # Map UserRole enum values to role names USERROLE_TO_ROLE_NAME = { UserRole.SUPERADMIN: "SUPERADMIN", UserRole.ADMIN: "ADMIN", UserRole.MODERATOR: "MODERATOR", UserRole.SALES_REP: "SALES_REP", UserRole.SERVICE_MGR: "SERVICE_MGR", UserRole.USER: "USER", } async def seed_rbac(): """Main seed function: creates roles, permissions, mappings, and migrates users.""" async with AsyncSessionLocal() as db: try: # ── STEP A: Insert Roles ── logger.info("=" * 60) logger.info("STEP A: Inserting 6 system roles...") role_map = {} # name -> id for role_data in ROLES: existing = await db.execute( select(SystemRole).where(SystemRole.name == role_data["name"]) ) role = existing.scalar_one_or_none() if role: logger.info(f" ⏭️ Role '{role_data['name']}' already exists (id={role.id}), updating...") role.description = role_data["description"] role.rank = role_data["rank"] role.is_system = role_data["is_system"] else: role = SystemRole(**role_data) db.add(role) await db.flush() logger.info(f" ✅ Created role '{role_data['name']}' (id={role.id})") role_map[role_data["name"]] = role.id await db.commit() logger.info(f" ✅ Roles committed. {len(role_map)} roles in role_map.") # ── STEP B: Insert Permissions ── logger.info("=" * 60) logger.info("STEP B: Inserting 28 permission codes...") perm_map = {} # code -> id for perm_data in PERMISSIONS: existing = await db.execute( select(SystemPermission).where(SystemPermission.code == perm_data["code"]) ) perm = existing.scalar_one_or_none() if perm: logger.info(f" ⏭️ Permission '{perm_data['code']}' already exists (id={perm.id}), updating...") perm.domain = perm_data["domain"] perm.action = perm_data["action"] perm.description = perm_data["description"] else: perm = SystemPermission(**perm_data) db.add(perm) await db.flush() logger.info(f" ✅ Created permission '{perm_data['code']}' (id={perm.id})") perm_map[perm_data["code"]] = perm.id await db.commit() logger.info(f" ✅ Permissions committed. {len(perm_map)} permissions in perm_map.") # ── STEP C: Insert Role-Permission Mappings ── logger.info("=" * 60) logger.info("STEP C: Inserting role-permission mappings...") mapping_count = 0 for role_name, perms in ROLE_PERMISSION_MATRIX.items(): role_id = role_map.get(role_name) if not role_id: logger.warning(f" ⚠️ Role '{role_name}' not found in role_map, skipping!") continue for perm_code, granted in perms.items(): perm_id = perm_map.get(perm_code) if not perm_id: logger.warning(f" ⚠️ Permission '{perm_code}' not found in perm_map, skipping!") continue # Check if mapping already exists existing = await db.execute( select(SystemRolePermission).where( SystemRolePermission.role_id == role_id, SystemRolePermission.permission_id == perm_id, ) ) rp = existing.scalar_one_or_none() if rp: if rp.granted != granted: rp.granted = granted logger.info(f" 🔄 Updated mapping: {role_name} -> {perm_code} (granted={granted})") else: rp = SystemRolePermission( role_id=role_id, permission_id=perm_id, granted=granted, ) db.add(rp) mapping_count += 1 await db.commit() logger.info(f" ✅ {mapping_count} new role-permission mappings created.") # ── STEP D: Migrate Existing Users ── logger.info("=" * 60) logger.info("STEP D: Migrating existing users to role_id...") # Get all users that don't have role_id set yet result = await db.execute( select(User).where(User.role_id.is_(None)) ) users_to_migrate = result.scalars().all() migrated_count = 0 skipped_count = 0 for user in users_to_migrate: role_name = USERROLE_TO_ROLE_NAME.get(user.role) if not role_name: logger.warning(f" ⚠️ User {user.id} has unknown role '{user.role}', skipping...") skipped_count += 1 continue role_id = role_map.get(role_name) if not role_id: logger.warning(f" ⚠️ Role '{role_name}' not found for user {user.id}, skipping...") skipped_count += 1 continue user.role_id = role_id migrated_count += 1 await db.commit() logger.info(f" ✅ Users migrated: {migrated_count} updated, {skipped_count} skipped.") # ── FINAL REPORT ── logger.info("=" * 60) logger.info("📊 RBAC SEED SUMMARY") logger.info(f" Roles: {len(role_map)}") logger.info(f" Permissions: {len(perm_map)}") # Count total role_permissions rp_result = await db.execute(select(SystemRolePermission)) total_rp = len(rp_result.scalars().all()) logger.info(f" Role-Permission Mappings: {total_rp}") # Count users with role_id user_result = await db.execute( select(User).where(User.role_id.isnot(None)) ) users_with_role = len(user_result.scalars().all()) logger.info(f" Users with role_id assigned: {users_with_role}") logger.info("=" * 60) logger.info("✅ RBAC Phase 1 seeding completed successfully!") except Exception as e: await db.rollback() logger.error(f"❌ RBAC seeding failed: {e}") raise async def main(): logger.info("🚀 Starting RBAC Phase 1 seed...") await seed_rbac() if __name__ == "__main__": asyncio.run(main())