"""Universal entity permission model — ACLs for ANY entity in the system. This single table stores permissions for contacts, files, mailboxes, calendar events, tasks, workflows, and any future entity type. Architecture: - entity_type + entity_id identify the datensatz - principal_type + principal_id identify who gets access - permission_level defines what they can do - expires_at enables time-limited sharing Resolution (highest wins): 1. Owner → 'owner' (from owner_id on the entity) 2. Direct user permission 3. Group permission (via user_groups) 4. Role permission (via user_tenants.role_id) 5. No access → 'none' """ from __future__ import annotations import uuid from datetime import datetime from sqlalchemy import ( CheckConstraint, DateTime, ForeignKey, Index, String, UniqueConstraint, func, ) from sqlalchemy.dialects.postgresql import UUID as PGUUID from sqlalchemy.orm import Mapped, mapped_column from app.core.db import Base, TenantMixin class EntityPermission(Base, TenantMixin): """Universal ACL entry for any entity in the system. entity_type examples: 'contact', 'dms_file', 'mailbox', 'calendar_event', 'task', 'workflow', 'contact_folder', etc. principal_type: 'user', 'group', 'role', 'guest' permission_level: 'none' | 'read' | 'write' | 'admin' | 'delete' - none: explicit deny (overrides allow) - read: view the entity - write: read + edit entity fields - admin: write + delete + manage permissions - delete: admin + transfer ownership """ __tablename__ = "entity_permissions" __table_args__ = ( UniqueConstraint( "entity_type", "entity_id", "principal_type", "principal_id", "tenant_id", name="uq_ep_entity_principal_tenant", ), CheckConstraint( "principal_type IN ('user', 'group', 'role', 'guest')", name="ck_ep_principal_type", ), CheckConstraint( "permission_level IN ('none', 'read', 'write', 'admin', 'delete')", name="ck_ep_permission_level", ), Index("ix_ep_entity", "entity_type", "entity_id"), Index("ix_ep_principal", "principal_type", "principal_id"), Index("ix_ep_tenant", "tenant_id"), Index("ix_ep_expires", "expires_at"), ) id: Mapped[uuid.UUID] = mapped_column( PGUUID(as_uuid=True), primary_key=True, default=uuid.uuid4 ) entity_type: Mapped[str] = mapped_column(String(50), nullable=False) entity_id: Mapped[uuid.UUID] = mapped_column( PGUUID(as_uuid=True), nullable=False ) principal_type: Mapped[str] = mapped_column(String(10), nullable=False) principal_id: Mapped[uuid.UUID] = mapped_column( PGUUID(as_uuid=True), nullable=False ) permission_level: Mapped[str] = mapped_column( String(20), nullable=False, default="read" ) expires_at: Mapped[datetime | None] = mapped_column( DateTime(timezone=True), nullable=True, default=None ) created_by: Mapped[uuid.UUID | None] = mapped_column( PGUUID(as_uuid=True), ForeignKey("users.id", ondelete="SET NULL"), nullable=True, ) created_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), nullable=False, server_default=func.now() ) updated_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), nullable=False, server_default=func.now(), onupdate=func.now(), )