"""Chat-internal RBAC logic — two-level permission checks.""" from __future__ import annotations import logging import uuid from typing import Any from sqlalchemy import select from sqlalchemy.ext.asyncio import AsyncSession from app.core.permissions import check_permission from app.plugins.builtins.kommunikation.models import CommParticipant logger = logging.getLogger(__name__) class CommRBAC: """Chat-internal RBAC, integrated with the existing permission system.""" @staticmethod async def get_user_role( db: AsyncSession, conversation_id: uuid.UUID, user_id: uuid.UUID, ) -> str | None: """Get the user's role in a conversation, or None if not a participant.""" result = await db.execute( select(CommParticipant).where( CommParticipant.conversation_id == conversation_id, CommParticipant.participant_id == user_id, CommParticipant.participant_type == "user", CommParticipant.left_at.is_(None), ) ) participant = result.scalar_one_or_none() return participant.role if participant else None @staticmethod async def is_participant( db: AsyncSession, conversation_id: uuid.UUID, user_id: uuid.UUID, ) -> bool: """Check if a user is an active participant in a conversation.""" role = await CommRBAC.get_user_role(db, conversation_id, user_id) return role is not None @staticmethod async def can_user_write( db: AsyncSession, conversation_id: uuid.UUID, current_user: dict[str, Any], ) -> bool: """Check if user can write messages. 1. System permission 'comm:write' via check_permission 2. is_system_admin → always allowed 3. User must be participant with role 'admin' or 'member' """ if current_user.get("is_system_admin"): return True if not check_permission(current_user, "comm:write"): return False user_id = uuid.UUID(current_user["user_id"]) role = await CommRBAC.get_user_role(db, conversation_id, user_id) return role in ("admin", "member") @staticmethod async def can_user_manage( db: AsyncSession, conversation_id: uuid.UUID, current_user: dict[str, Any], ) -> bool: """Check if user can manage participants. 1. System permission 'comm:manage' via check_permission 2. is_system_admin → always allowed 3. User must be conversation admin """ if current_user.get("is_system_admin"): return True if not check_permission(current_user, "comm:manage"): return False user_id = uuid.UUID(current_user["user_id"]) role = await CommRBAC.get_user_role(db, conversation_id, user_id) return role == "admin" @staticmethod async def can_user_delete( db: AsyncSession, conversation_id: uuid.UUID, current_user: dict[str, Any], is_own_message: bool = False, ) -> bool: """Check if user can delete messages or conversation. 1. is_system_admin → always allowed 2. For own messages: comm:write suffices 3. For others' messages: comm:delete + conversation admin 4. For conversation: comm:delete + admin role """ if current_user.get("is_system_admin"): return True if is_own_message: return check_permission(current_user, "comm:write") if not check_permission(current_user, "comm:delete"): return False user_id = uuid.UUID(current_user["user_id"]) role = await CommRBAC.get_user_role(db, conversation_id, user_id) return role == "admin" @staticmethod async def can_user_create(current_user: dict[str, Any]) -> bool: """Check if user can create conversations.""" if current_user.get("is_system_admin"): return True return check_permission(current_user, "comm:create") @staticmethod async def can_user_change_role( db: AsyncSession, conversation_id: uuid.UUID, current_user: dict[str, Any], ) -> bool: """Check if user can change participant roles. Requires comm:admin permission + conversation admin role. """ if current_user.get("is_system_admin"): return True if not check_permission(current_user, "comm:admin"): return False user_id = uuid.UUID(current_user["user_id"]) role = await CommRBAC.get_user_role(db, conversation_id, user_id) return role == "admin"