# Copyright (c) 2025 Patrick Motsch # All rights reserved. """ RBAC models: AccessRule, AccessRuleContext, Role. Multi-Tenant Design: - Role hat einen unveränderlichen Kontext (mandateId, featureInstanceId, featureCode) - AccessRule referenziert Role via roleId (FK), nicht via roleLabel - Kontext-Felder sind IMMUTABLE nach Erstellung """ import uuid from typing import Optional from enum import Enum from pydantic import BaseModel, Field from modules.datamodels.datamodelBase import PowerOnModel from modules.shared.i18nRegistry import i18nModel from modules.datamodels.datamodelUtils import TextMultilingual from modules.datamodels.datamodelUam import AccessLevel class AccessRuleContext(str, Enum): """Context type enumeration""" DATA = "DATA" # Database tables and fields UI = "UI" # UI elements and features RESOURCE = "RESOURCE" # System resources (AI models, actions, etc.) @i18nModel("Rolle") class Role(PowerOnModel): """ Data model for RBAC roles. Kernkonzept: Eine Rolle existiert immer in einem spezifischen Kontext. Der Kontext ist IMMUTABLE nach Erstellung. Kontext-Typen: - mandateId=None, featureInstanceId=None → GLOBAL (Template-Rolle) - mandateId=X, featureInstanceId=None → MANDATE-Rolle - mandateId=X, featureInstanceId=Y → INSTANCE-Rolle """ id: str = Field( default_factory=lambda: str(uuid.uuid4()), description="Unique ID of the role", json_schema_extra={"label": "ID", "frontend_type": "text", "frontend_readonly": True, "frontend_visible": False, "frontend_required": False} ) roleLabel: str = Field( description="Unique role label identifier (e.g., 'admin', 'user', 'viewer')", json_schema_extra={"label": "Rollen-Label", "frontend_type": "text", "frontend_readonly": False, "frontend_required": True} ) description: TextMultilingual = Field( description="Role description in multiple languages", json_schema_extra={"label": "Beschreibung", "frontend_type": "multilingual", "frontend_readonly": False, "frontend_required": True} ) # KONTEXT - IMMUTABLE nach Create (nur Create/Delete, kein Update!) mandateId: Optional[str] = Field( default=None, description="FK → Mandate.id (CASCADE DELETE). Null = Global/Template role.", json_schema_extra={"label": "Mandant", "frontend_type": "select", "frontend_readonly": True, "frontend_visible": True, "frontend_required": False, "frontend_fk_source": "/api/mandates/", "frontend_fk_display_field": "label"} ) featureInstanceId: Optional[str] = Field( default=None, description="FK → FeatureInstance.id (CASCADE DELETE). Null = Mandate-level or Global role.", json_schema_extra={"label": "Feature-Instanz", "frontend_type": "select", "frontend_readonly": True, "frontend_visible": True, "frontend_required": False, "frontend_fk_source": "/api/features/instances", "frontend_fk_display_field": "label"} ) featureCode: Optional[str] = Field( default=None, description="Feature code (z.B. 'trustee') - für Template-Rollen", json_schema_extra={"label": "Feature-Code", "frontend_type": "text", "frontend_readonly": True, "frontend_visible": False, "frontend_required": False} ) isSystemRole: bool = Field( default=False, description="Whether this is a system role that cannot be deleted", json_schema_extra={"label": "System-Rolle", "frontend_type": "checkbox", "frontend_readonly": True, "frontend_required": False} ) @i18nModel("Zugriffsregel") class AccessRule(PowerOnModel): """ Data model for access control rules. WICHTIG: roleId referenziert die Role via FK (nicht mehr roleLabel!) Die Felder 'context' und 'roleId' sind IMMUTABLE nach Erstellung. """ id: str = Field( default_factory=lambda: str(uuid.uuid4()), description="Unique ID of the access rule", json_schema_extra={"label": "ID", "frontend_type": "text", "frontend_readonly": True, "frontend_visible": False, "frontend_required": False} ) roleId: str = Field( description="FK → Role.id (CASCADE DELETE!)", json_schema_extra={"label": "Rolle", "frontend_type": "select", "frontend_readonly": True, "frontend_required": True, "frontend_fk_source": "/api/rbac/roles", "frontend_fk_display_field": "roleLabel"} ) context: AccessRuleContext = Field( description="Context type: DATA (database), UI (interface), RESOURCE (system resources). IMMUTABLE!", json_schema_extra={"label": "Kontext", "frontend_type": "select", "frontend_readonly": True, "frontend_required": True, "frontend_options": [ {"value": "DATA", "label": "Daten"}, {"value": "UI", "label": "Oberfläche"}, {"value": "RESOURCE", "label": "Ressource"} ]} ) item: Optional[str] = Field( default=None, description="Item identifier (null = all items in context). Format: DATA: '