from __future__ import annotations from fastapi import APIRouter, Depends, HTTPException from pydantic import BaseModel from sqlalchemy import select from sqlalchemy.orm import Session from app.core.db.deps import get_common_db from app.core.security.session_auth import require_login from app.modules.core.audit.service import write_audit_log from app.modules.core.iam.models import User from app.modules.core.iam.scope import build_scope, scope_to_http, assert_can_manage_role_object from app.modules.core.rbac.models import Permission, Role, RolePermission router = APIRouter(prefix="/rbac", tags=["rbac"]) class RoleCreateRequest(BaseModel): name: str is_active: bool = True class PermissionCreateRequest(BaseModel): code: str name: str is_active: bool = True class RolePermissionUpdateRequest(BaseModel): permission_ids: list[int] = [] def _require_system_admin_scope(db: Session, current_user: User): scope = build_scope(db, current_user) if not scope.is_system_admin: raise HTTPException(status_code=403, detail="Only System Admin can access RBAC.") return scope @router.get("/roles") def list_roles( current_user: User = Depends(require_login), db: Session = Depends(get_common_db), ): _require_system_admin_scope(db, current_user) roles = db.execute(select(Role).order_by(Role.name)).scalars().all() return [{"id": r.id, "name": r.name, "is_active": r.is_active} for r in roles] @router.post("/roles") def create_role( payload: RoleCreateRequest, current_user: User = Depends(require_login), db: Session = Depends(get_common_db), ): _require_system_admin_scope(db, current_user) name = payload.name.strip() if not name: raise HTTPException(status_code=400, detail="Role name is required") exists = db.execute(select(Role).where(Role.name == name)).scalar_one_or_none() if exists: raise HTTPException(status_code=400, detail="Role already exists") role = Role(name=name, is_active=payload.is_active) db.add(role) db.commit() db.refresh(role) write_audit_log( db, action="role.create.api", entity_type="role", actor=current_user, entity_id=role.id, entity_name=role.name, target_tenant_id=current_user.tenant_id, target_branch_id=current_user.branch_id, details={"name": role.name, "is_active": role.is_active}, ) return {"status": "ok", "id": role.id} @router.get("/permissions") def list_permissions( current_user: User = Depends(require_login), db: Session = Depends(get_common_db), ): _require_system_admin_scope(db, current_user) permissions = db.execute(select(Permission).order_by(Permission.code)).scalars().all() return [{"id": p.id, "code": p.code, "name": p.name, "is_active": p.is_active} for p in permissions] @router.post("/permissions") def create_permission( payload: PermissionCreateRequest, current_user: User = Depends(require_login), db: Session = Depends(get_common_db), ): _require_system_admin_scope(db, current_user) code = payload.code.strip() if not code: raise HTTPException(status_code=400, detail="Permission code is required") exists = db.execute(select(Permission).where(Permission.code == code)).scalar_one_or_none() if exists: raise HTTPException(status_code=400, detail="Permission already exists") permission = Permission(code=code, name=payload.name.strip(), is_active=payload.is_active) db.add(permission) db.commit() db.refresh(permission) write_audit_log( db, action="permission.create.api", entity_type="permission", actor=current_user, entity_id=permission.id, entity_name=permission.code, target_tenant_id=current_user.tenant_id, target_branch_id=current_user.branch_id, details={"code": permission.code, "name": permission.name, "is_active": permission.is_active}, ) return {"status": "ok", "id": permission.id} @router.get("/roles/{role_id}") def role_detail( role_id: int, current_user: User = Depends(require_login), db: Session = Depends(get_common_db), ): scope = _require_system_admin_scope(db, current_user) role = db.execute(select(Role).where(Role.id == role_id)).scalar_one_or_none() if not role: raise HTTPException(status_code=404, detail="Role not found") try: assert_can_manage_role_object(scope, role) except Exception as exc: raise scope_to_http(exc) permission_ids = db.execute( select(RolePermission.permission_id).where(RolePermission.role_id == role.id) ).scalars().all() return { "id": role.id, "name": role.name, "is_active": role.is_active, "permission_ids": list(permission_ids), } @router.put("/roles/{role_id}/permissions") def update_role_permissions( role_id: int, payload: RolePermissionUpdateRequest, current_user: User = Depends(require_login), db: Session = Depends(get_common_db), ): _require_system_admin_scope(db, current_user) role = db.execute(select(Role).where(Role.id == role_id)).scalar_one_or_none() if not role: raise HTTPException(status_code=404, detail="Role not found") old_permission_ids = db.execute( select(RolePermission.permission_id).where(RolePermission.role_id == role.id) ).scalars().all() db.execute(RolePermission.__table__.delete().where(RolePermission.role_id == role.id)) for permission_id in payload.permission_ids: db.add(RolePermission(role_id=role.id, permission_id=permission_id)) db.commit() write_audit_log( db, action="role.permissions.update.api", entity_type="role", actor=current_user, entity_id=role.id, entity_name=role.name, target_tenant_id=current_user.tenant_id, target_branch_id=current_user.branch_id, details={ "old_permission_ids": list(old_permission_ids), "new_permission_ids": list(payload.permission_ids), }, ) return {"status": "ok"}