feat: granular RBAC system with user groups, deny-list, permission registry, system-admin, self-mod prevention
This commit is contained in:
@@ -9,8 +9,10 @@ from fastapi import APIRouter, Depends, HTTPException, Query, Response, status
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.core.audit import log_audit
|
||||
from app.core.auth import get_redis
|
||||
from app.core.db import get_db
|
||||
from app.core.notifications import create_notification
|
||||
from app.core.permissions import invalidate_permission_cache
|
||||
from app.deps import get_current_user, require_admin
|
||||
from app.schemas.user import UserCreate, UserUpdate
|
||||
from app.services.user_service import user_service, _UNSET
|
||||
@@ -143,6 +145,9 @@ async def update_user(
|
||||
Uses ``model_fields_set`` to detect whether ``role_id`` was explicitly
|
||||
present in the request body (even if sent as ``null``). This allows
|
||||
the caller to clear the FK by sending ``role_id: null``.
|
||||
|
||||
Self-modification prevention: a user cannot change their own role,
|
||||
is_active status, or system_admin flag.
|
||||
"""
|
||||
tenant_id = uuid.UUID(current_user["tenant_id"])
|
||||
acting_user_id = uuid.UUID(current_user["user_id"])
|
||||
@@ -153,6 +158,14 @@ async def update_user(
|
||||
400, detail={"detail": "Invalid user_id", "code": "invalid_id"}
|
||||
) from None
|
||||
|
||||
# Self-modification prevention: cannot change own role or active status
|
||||
if uid == acting_user_id:
|
||||
if body.role is not None or body.is_active is not None or "role_id" in body.model_fields_set:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail={"detail": "Cannot modify your own role or active status", "code": "self_modification_forbidden"},
|
||||
)
|
||||
|
||||
# Determine if role_id was explicitly sent (Pydantic v2)
|
||||
role_id_sent = "role_id" in body.model_fields_set
|
||||
|
||||
@@ -188,6 +201,10 @@ async def update_user(
|
||||
|
||||
await log_audit(db, tenant_id, acting_user_id, "update", "user", uid, changes=changes)
|
||||
|
||||
# Invalidate permission cache for the updated user
|
||||
redis = get_redis()
|
||||
await invalidate_permission_cache(redis, uid, tenant_id)
|
||||
|
||||
return {
|
||||
"id": str(user.id),
|
||||
"email": user.email,
|
||||
|
||||
Reference in New Issue
Block a user