# Copyright (c) 2026 PowerOn AG # 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, Dict, List, Protocol, runtime_checkable 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, "fk_target": {"db": "poweron_app", "table": "Mandate", "labelField": "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, "fk_target": {"db": "poweron_app", "table": "FeatureInstance", "labelField": "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, "fk_target": {"db": "poweron_app", "table": "Role", "labelField": "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: '