137 lines
4.6 KiB
Python
137 lines
4.6 KiB
Python
|
|
"""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"
|