feat: Unified Messaging System — kommunikation plugin, AI/Proactive/System participants, MessageSidebar, Rich Content Renderer
Phase 1: Backend plugin kommunikation (13 files, 10 tables, REST API, WebSocket, RBAC, DMS Bridge, Participant Registry, Mini-App Registry, Search Provider) Phase 2: AI plugins as participants (ai_assistant + ai_proactive dock as participants, heartbeat job) Phase 3: system_notif plugin (system events → chat messages, pinned System room) Phase 4: Frontend MessageSidebar (replaces AISidebar, same design, comm API client, WebSocket hook, commStore) Phase 5: Rich Content Block Renderer (11 components: Markdown, HTML, Image, Audio, Video, File, ActionCard, ContactCard, MiniApp, BlockRenderer) BasePlugin: added services property + _container in on_activate
This commit is contained in:
@@ -0,0 +1,136 @@
|
||||
"""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"
|
||||
Reference in New Issue
Block a user