Files
2026-06-20 15:01:44 +05:30

195 lines
6.0 KiB
Python

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"}