# RBAC Access Rules Editor - Refactoring Vorschlag ## Übersicht Dieses Dokument beschreibt die geplante Überarbeitung der RBAC Access Rules UI-Komponenten basierend auf drei Hauptanforderungen: 1. **System-Rollen bearbeitbar machen** - AccessRules für System-Rollen editierbar 2. **Checkbox-basiertes UI** - Kompakteres Layout mit Checkboxen statt Dropdowns 3. **Dot-Notation & Objekt-Katalog** - Verfügbare Objekte als Dropdown auswählbar --- ## 1. System-Rollen mit bearbeitbaren AccessRules ### Ist-Zustand Aktuell in `AdminMandateRolePermissionsPage.tsx`: ```typescript ``` Die `readOnly`-Prop wird basierend auf `isSystemRole` gesetzt, was dazu führt, dass AccessRules für System-Rollen weder angezeigt noch bearbeitet werden können. ### Soll-Zustand - **Rollen selbst** (roleLabel, description) bleiben geschützt für System-Rollen - **AccessRules** sind für alle Rollen bearbeitbar (inkl. System-Rollen) - Zugriffskontrolle erfolgt über RBAC selbst (`rbac.rules.manage` Permission) ### Implementierung #### Option A: Separate Prop für Rollen-Protection ```typescript // AdminMandateRolePermissionsPage.tsx ``` #### Option B: Immer bearbeitbar (empfohlen) Entferne die `readOnly`-Logic basierend auf `isSystemRole`: ```typescript // AdminMandateRolePermissionsPage.tsx ``` Die Zugriffskontrolle wird durch Backend-RBAC sichergestellt (nur SysAdmin kann AccessRules ändern). ### Betroffene Dateien | Datei | Änderung | |-------|----------| | `frontend_nyla/src/pages/admin/AdminMandateRolePermissionsPage.tsx` | `readOnly={false}` setzen | --- ## 2. Checkbox-basiertes Kompakt-Layout ### Ist-Zustand Aktuell werden AccessLevels (n/m/g/a) über Dropdown-Selects ausgewählt: ``` ┌──────────────────────────────────────────────────────────────────┐ │ 📊 TrusteeContract [🗑] │ ├──────────────────────────────────────────────────────────────────┤ │ VIEW READ CREATE UPDATE DELETE │ │ [✓] [Gruppe▼] [Eigene▼] [Eigene▼] [Keine▼] │ └──────────────────────────────────────────────────────────────────┘ ``` **Probleme:** - Dropdowns benötigen Klicks zum Öffnen - Nicht auf einen Blick erkennbar welche Berechtigungen gesetzt sind - Mehrere Regeln brauchen viel vertikalen Platz ### Soll-Zustand Kompaktes Checkbox-Grid in einer Zeile pro Objekt: ``` ┌────────────────────────────────────────────────────────────────────────────┐ │ OBJEKT │ VIEW │ EIGENE │ GRUPPE │ ALLE │ │ │ │ C R U D │ C R U D │C R U D│ ├────────────────────────────────────────────────────────────────────────────┤ │ data.TrusteeContract │ [✓] │ [✓] [✓] [✓] [ ]│[ ] [✓] [ ] [ ]│[ ][ ][ ][ ]│ │ data.TrusteePosition │ [✓] │ [✓] [✓] [ ] [ ]│[✓] [✓] [✓] [ ]│[ ][ ][ ][ ]│ │ ui.feature.trustee.* │ [✓] │ - - - - │ - - - - │ - - - - │ └────────────────────────────────────────────────────────────────────────────┘ ``` **Vorteile:** - Alle Berechtigungen auf einen Blick sichtbar - Schnelle Toggle-Aktionen mit einzelnem Klick - Kompakter - mehr Regeln pro Bildschirm - Intuitive Matrix-Darstellung (bekannt von Unix-Permissions) ### Neue Komponenten-Struktur ``` AccessRulesEditor/ ├── AccessRulesEditor.tsx # Haupt-Container ├── AccessRulesTable.tsx # Tabellen-basierte Darstellung (NEU) ├── AccessRuleRow.tsx # Eine Zeile = ein AccessRule (NEU) ├── AccessLevelCheckboxGroup.tsx # Checkbox-Gruppe für m/g/a (NEU) └── AccessRules.module.css # Styles ``` ### Neue Komponente: `AccessRulesTable.tsx` ```typescript interface AccessRulesTableProps { rules: AccessRule[]; context: RuleContext; readOnly?: boolean; onUpdate: (ruleId: string, updates: Partial) => void; onDelete: (ruleId: string) => void; } const AccessRulesTable: React.FC = ({ rules, context, readOnly, onUpdate, onDelete, }) => { const isDataContext = context === 'DATA'; return ( {isDataContext && ( <> )} {isDataContext && ( )} {rules.map(rule => ( ))}
Objekt (Dot-Notation) ViewEigene (m) Gruppe (g) Alle (a)
CRUD CRUD CRUD
); }; ``` ### Neue Komponente: `AccessRuleRow.tsx` ```typescript interface AccessRuleRowProps { rule: AccessRule; isDataContext: boolean; readOnly?: boolean; onUpdate: (ruleId: string, updates: Partial) => void; onDelete: (ruleId: string) => void; } const AccessRuleRow: React.FC = ({ rule, isDataContext, readOnly, onUpdate, onDelete, }) => { // Hilfsfunktion: Prüft ob Level mindestens X erreicht const hasLevel = (level: AccessLevel | null, minLevel: 'm' | 'g' | 'a'): boolean => { if (!level || level === 'n') return false; const hierarchy = ['n', 'm', 'g', 'a']; return hierarchy.indexOf(level) >= hierarchy.indexOf(minLevel); }; // Hilfsfunktion: Setzt Level basierend auf Checkbox-Änderung const toggleLevel = ( field: 'read' | 'create' | 'update' | 'delete', targetLevel: 'm' | 'g' | 'a', checked: boolean ) => { const currentLevel = rule[field] || 'n'; let newLevel: AccessLevel; if (checked) { // Aktiviere mindestens dieses Level newLevel = targetLevel; } else { // Deaktiviere dieses Level, setze auf nächst-niedrigeres const hierarchy = ['n', 'm', 'g', 'a']; const targetIndex = hierarchy.indexOf(targetLevel); newLevel = hierarchy[targetIndex - 1] as AccessLevel || 'n'; } onUpdate(rule.id, { [field]: newLevel }); }; return ( {/* Objekt-Name in Dot-Notation */} {rule.item || '(global)'} {/* View Checkbox */} onUpdate(rule.id, { view: e.target.checked })} disabled={readOnly} /> {/* CRUD Checkboxen für DATA-Kontext */} {isDataContext && ( <> {/* Eigene (m) */} {['create', 'read', 'update', 'delete'].map(op => ( toggleLevel(op as any, 'm', e.target.checked)} disabled={readOnly} /> ))} {/* Gruppe (g) */} {['create', 'read', 'update', 'delete'].map(op => ( toggleLevel(op as any, 'g', e.target.checked)} disabled={readOnly} /> ))} {/* Alle (a) */} {['create', 'read', 'update', 'delete'].map(op => ( toggleLevel(op as any, 'a', e.target.checked)} disabled={readOnly} /> ))} )} {/* Löschen-Button */} {!readOnly && ( )} ); }; ``` ### CSS für Tabellen-Layout ```css /* AccessRules.module.css - Erweiterungen */ .accessRulesTable { width: 100%; border-collapse: collapse; font-size: 0.875rem; } .accessRulesTable th, .accessRulesTable td { padding: 0.5rem 0.375rem; border-bottom: 1px solid var(--border-color); text-align: center; } .accessRulesTable th { background: var(--bg-secondary); font-weight: 600; font-size: 0.75rem; text-transform: uppercase; color: var(--text-secondary); } .colObject { text-align: left !important; min-width: 200px; } .colView { width: 50px; } .colGroup { border-left: 2px solid var(--border-color); } .subHeader th { font-size: 0.6875rem; padding: 0.25rem; background: var(--bg-tertiary); } .objectCell { text-align: left !important; } .objectCell code { font-family: 'Monaco', 'Menlo', monospace; font-size: 0.8125rem; background: var(--bg-tertiary); padding: 0.125rem 0.375rem; border-radius: 3px; } .checkboxCell { width: 32px; } .checkboxCell input[type="checkbox"] { width: 16px; height: 16px; cursor: pointer; accent-color: var(--primary-color); } .actionsCell { width: 40px; } .deleteButton { padding: 0.25rem; background: transparent; border: none; color: var(--text-tertiary); cursor: pointer; border-radius: 4px; } .deleteButton:hover { background: #fed7d7; color: #c53030; } ``` ### Betroffene Dateien | Datei | Änderung | |-------|----------| | `frontend_nyla/src/components/AccessRules/AccessRulesTable.tsx` | NEU erstellen | | `frontend_nyla/src/components/AccessRules/AccessRuleRow.tsx` | NEU erstellen | | `frontend_nyla/src/components/AccessRules/AccessRulesEditor.tsx` | Integriere `AccessRulesTable` | | `frontend_nyla/src/components/AccessRules/AccessRules.module.css` | Tabellen-Styles hinzufügen | | `frontend_nyla/src/components/AccessRules/index.ts` | Exports aktualisieren | --- ## 3. Dot-Notation & RBAC-Objekt-Katalog ### Ist-Zustand Aktuell wird das `item`-Feld als Freitext eingegeben: ```typescript // AddRuleForm.tsx (aktuell) setItem(e.target.value)} placeholder="z.B. TrusteeContract oder TrusteeContract.salary" /> ``` **Probleme:** - Benutzer muss gültige Objekt-Namen kennen - Keine Validierung gegen registrierte Objekte - Inkonsistente Schreibweisen möglich - Keine Übersicht der verfügbaren Objekte ### Backend: RBAC Catalog Service Der `RbacCatalogService` (`gateway/modules/security/rbacCatalog.py`) registriert bereits alle verfügbaren Objekte: ```python class RbacCatalogService: def __init__(self): self._uiObjects: Dict[str, Dict[str, Any]] = {} self._resourceObjects: Dict[str, Dict[str, Any]] = {} self._dataObjects: Dict[str, Dict[str, Any]] = {} # ← NEU def getUiObjects(self, featureCode: Optional[str] = None) -> List[Dict[str, Any]]: # Gibt UI-Objekte zurück (z.B. "ui.feature.trustee.dashboard") def getResourceObjects(self, featureCode: Optional[str] = None) -> List[Dict[str, Any]]: # Gibt Resource-Objekte zurück (z.B. "resource.feature.trustee.documents.create") ``` **Fehlt:** DATA-Objekte werden nicht im Katalog registriert! ### Soll-Zustand 1. **Backend:** Neuer API-Endpoint zum Abrufen verfügbarer RBAC-Objekte 2. **Backend:** DATA-Objekte (Tabellen/Entitäten) im Katalog registrieren 3. **Frontend:** Dropdown zur Auswahl aus verfügbaren Objekten 4. **Frontend:** Konsistente Dot-Notation für alle Objekte ### Backend-Implementierung #### 1. DATA-Objekte im Katalog registrieren Erweitere `rbacCatalog.py`: ```python # gateway/modules/security/rbacCatalog.py class RbacCatalogService: def __init__(self): self._uiObjects: Dict[str, Dict[str, Any]] = {} self._resourceObjects: Dict[str, Dict[str, Any]] = {} self._dataObjects: Dict[str, Dict[str, Any]] = {} # NEU def registerDataObject( self, featureCode: str, objectKey: str, label: Dict[str, str], meta: Optional[Dict[str, Any]] = None ) -> bool: """Register a DATA object (table/entity) for a feature.""" try: self._dataObjects[objectKey] = { "objectKey": objectKey, "featureCode": featureCode, "label": label, "meta": meta or {}, "type": "DATA" } return True except Exception as e: logger.error(f"Failed to register DATA object {objectKey}: {e}") return False def getDataObjects(self, featureCode: Optional[str] = None) -> List[Dict[str, Any]]: """Get all DATA objects, optionally filtered by feature.""" if featureCode: return [obj for obj in self._dataObjects.values() if obj["featureCode"] == featureCode] return list(self._dataObjects.values()) def getAllCatalogObjects(self, featureCode: Optional[str] = None) -> Dict[str, List[Dict[str, Any]]]: """Get all catalog objects grouped by type.""" return { "DATA": self.getDataObjects(featureCode), "UI": self.getUiObjects(featureCode), "RESOURCE": self.getResourceObjects(featureCode) } ``` #### 2. DATA-Objekte in Feature registrieren Erweitere `mainTrustee.py`: ```python # gateway/modules/features/trustee/mainTrustee.py # DATA Objects for RBAC catalog (Tabellen/Entitäten) DATA_OBJECTS = [ { "objectKey": "data.feature.trustee.TrusteeContract", "label": {"en": "Contract", "de": "Vertrag", "fr": "Contrat"}, "meta": {"table": "TrusteeContract", "fields": ["id", "name", "salary", ...]} }, { "objectKey": "data.feature.trustee.TrusteePosition", "label": {"en": "Position", "de": "Position", "fr": "Position"}, "meta": {"table": "TrusteePosition", "fields": ["id", "label", ...]} }, { "objectKey": "data.feature.trustee.TrusteeDocument", "label": {"en": "Document", "de": "Dokument", "fr": "Document"}, "meta": {"table": "TrusteeDocument", "fields": ["id", "filename", ...]} }, ] def registerFeature(catalogService) -> bool: # ... bestehende UI/Resource Registrierung ... # NEU: DATA-Objekte registrieren for dataObj in DATA_OBJECTS: catalogService.registerDataObject( featureCode=FEATURE_CODE, objectKey=dataObj["objectKey"], label=dataObj["label"], meta=dataObj.get("meta") ) ``` #### 3. Neuer API-Endpoint für Katalog-Objekte Erweitere `routeAdminRbacRules.py`: ```python # gateway/modules/routes/routeAdminRbacRules.py from modules.security.rbacCatalog import getCatalogService @router.get("/catalog/objects", response_model=Dict[str, Any]) @limiter.limit("60/minute") async def get_catalog_objects( request: Request, context: Optional[str] = Query(None, description="Filter by context (DATA, UI, RESOURCE)"), featureCode: Optional[str] = Query(None, description="Filter by feature code"), currentUser: User = Depends(requireSysAdmin) ) -> Dict[str, Any]: """ Get available RBAC catalog objects. Returns all registered DATA, UI and RESOURCE objects that can be used in AccessRules. Query Parameters: - context: Optional filter by context type (DATA, UI, RESOURCE) - featureCode: Optional filter by feature (e.g., "trustee") Returns: - Dictionary with objects grouped by context type, each with: - objectKey: Dot-notation identifier (e.g., "data.feature.trustee.TrusteeContract") - label: Multilingual label - featureCode: Owning feature - meta: Additional metadata Examples: - GET /api/rbac/catalog/objects → all objects - GET /api/rbac/catalog/objects?context=DATA → only DATA objects - GET /api/rbac/catalog/objects?featureCode=trustee → only trustee objects """ try: catalog = getCatalogService() if context: # Einzelner Context try: accessContext = AccessRuleContext(context.upper()) except ValueError: raise HTTPException( status_code=400, detail=f"Invalid context '{context}'. Must be one of: DATA, UI, RESOURCE" ) if accessContext == AccessRuleContext.DATA: objects = catalog.getDataObjects(featureCode) elif accessContext == AccessRuleContext.UI: objects = catalog.getUiObjects(featureCode) else: objects = catalog.getResourceObjects(featureCode) return {context.upper(): objects} else: # Alle Contexts return catalog.getAllCatalogObjects(featureCode) except HTTPException: raise except Exception as e: logger.error(f"Error getting catalog objects: {str(e)}") raise HTTPException( status_code=500, detail=f"Failed to get catalog objects: {str(e)}" ) ``` ### Frontend-Implementierung #### 1. Hook für Katalog-Objekte Erstelle `useCatalogObjects.ts`: ```typescript // frontend_nyla/src/hooks/useCatalogObjects.ts import { useState, useCallback } from 'react'; import api from '../api'; import { RuleContext } from './useAccessRules'; export interface CatalogObject { objectKey: string; featureCode: string; label: { [lang: string]: string }; meta?: Record; type: RuleContext; } interface CatalogObjects { DATA: CatalogObject[]; UI: CatalogObject[]; RESOURCE: CatalogObject[]; } export function useCatalogObjects() { const [objects, setObjects] = useState({ DATA: [], UI: [], RESOURCE: [] }); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const fetchObjects = useCallback(async ( context?: RuleContext, featureCode?: string ): Promise => { setLoading(true); setError(null); try { const params = new URLSearchParams(); if (context) params.append('context', context); if (featureCode) params.append('featureCode', featureCode); const url = `/api/rbac/catalog/objects${params.toString() ? `?${params}` : ''}`; const response = await api.get(url); const data = response.data as CatalogObjects; setObjects(data); return data; } catch (err: any) { const errorMsg = err.response?.data?.detail || err.message || 'Fehler beim Laden'; setError(errorMsg); return { DATA: [], UI: [], RESOURCE: [] }; } finally { setLoading(false); } }, []); const getObjectsByContext = useCallback((context: RuleContext): CatalogObject[] => { return objects[context] || []; }, [objects]); return { objects, loading, error, fetchObjects, getObjectsByContext, }; } ``` #### 2. Objekt-Auswahl Dropdown Aktualisiere `AddRuleForm`: ```typescript // AccessRulesEditor.tsx - AddRuleForm Komponente interface AddRuleFormProps { context: RuleContext; availableObjects: CatalogObject[]; // NEU onAdd: (rule: AccessRuleCreate) => void; onCancel: () => void; } const AddRuleForm: React.FC = ({ context, availableObjects, // NEU onAdd, onCancel }) => { const [item, setItem] = useState(''); const [useCustom, setUseCustom] = useState(false); // NEU: Toggle für Freitext // ... rest state // Gruppiere Objekte nach Feature const groupedObjects = useMemo(() => { const grouped: Record = {}; availableObjects.forEach(obj => { if (!grouped[obj.featureCode]) { grouped[obj.featureCode] = []; } grouped[obj.featureCode].push(obj); }); return grouped; }, [availableObjects]); // Aktuelle Sprache für Labels const lang = useLanguage(); // oder 'de' als Default return (
{useCustom ? ( // Freitext-Eingabe (wie bisher) setItem(e.target.value)} placeholder={getPlaceholder()} className={styles.formInput} /> ) : ( // Dropdown mit verfügbaren Objekten )} Leer lassen für globale Regel. Längster Match gewinnt.
{/* ... rest of form */}
); }; ``` #### 3. Katalog-Objekte im Editor laden ```typescript // AccessRulesEditor.tsx export const AccessRulesEditor: React.FC = ({ roleId, roleName, readOnly = false, apiBasePath = '/api/rbac', mandateId, }) => { // ... bestehende hooks // NEU: Katalog-Objekte laden const { objects: catalogObjects, fetchObjects } = useCatalogObjects(); useEffect(() => { fetchObjects(); // Alle Objekte laden beim Mount }, [fetchObjects]); // Objekte für aktuellen Tab filtern const currentContextObjects = useMemo(() => { return catalogObjects[activeTab] || []; }, [catalogObjects, activeTab]); return (
{/* ... header, tabs */}
{activeTab !== 'JSON' && ( )} {/* ... JSON tab */}
{/* ... action bar */}
); }; ``` ### Dot-Notation Schema Konsistentes Namensschema für alle RBAC-Objekte: ``` ...[.] Beispiele: ├── DATA │ ├── data.feature.trustee.TrusteeContract │ ├── data.feature.trustee.TrusteeContract.salary (Feld-Level) │ ├── data.feature.trustee.TrusteePosition │ ├── data.system.UserInDB (System-Entität) │ └── data.system.Mandate │ ├── UI │ ├── ui.feature.trustee.dashboard │ ├── ui.feature.trustee.positions │ ├── ui.admin.users (Admin-Bereich) │ ├── ui.playground.voice.settings (Playground) │ └── ui.nav.trustee (Navigation) │ └── RESOURCE ├── resource.feature.trustee.documents.create ├── resource.feature.trustee.instance-roles.manage ├── resource.ai.model.anthropic (AI-Resources) └── resource.connector.sharepoint (Connectors) ``` ### Betroffene Dateien | Datei | Änderung | |-------|----------| | **Backend** || | `gateway/modules/security/rbacCatalog.py` | `registerDataObject()`, `getDataObjects()`, `getAllCatalogObjects()` | | `gateway/modules/routes/routeAdminRbacRules.py` | Neuer Endpoint `GET /api/rbac/catalog/objects` | | `gateway/modules/features/trustee/mainTrustee.py` | `DATA_OBJECTS` Liste, Registrierung erweitern | | `gateway/modules/features/*/main*.py` | DATA_OBJECTS in allen Features | | **Frontend** || | `frontend_nyla/src/hooks/useCatalogObjects.ts` | NEU erstellen | | `frontend_nyla/src/hooks/index.ts` | Export hinzufügen | | `frontend_nyla/src/components/AccessRules/AccessRulesEditor.tsx` | Katalog-Objekte integrieren | --- ## Zusammenfassung der Änderungen ### Backend | Priorität | Datei | Beschreibung | |-----------|-------|--------------| | 1 | `rbacCatalog.py` | DATA-Objekte Registrierung | | 1 | `routeAdminRbacRules.py` | `/catalog/objects` Endpoint | | 2 | `mainTrustee.py` | DATA_OBJECTS definieren | | 2 | Alle Feature `main*.py` | DATA_OBJECTS in allen Features | ### Frontend | Priorität | Datei | Beschreibung | |-----------|-------|--------------| | 1 | `AdminMandateRolePermissionsPage.tsx` | `readOnly={false}` für System-Rollen | | 1 | `AccessRulesTable.tsx` | Neue Tabellen-Komponente | | 1 | `AccessRuleRow.tsx` | Zeilen-Komponente mit Checkboxen | | 2 | `useCatalogObjects.ts` | Hook für Katalog-Objekte | | 2 | `AccessRulesEditor.tsx` | Integration Katalog + Tabelle | | 2 | `AccessRules.module.css` | Styles für Tabelle | ### Migrations-Strategie 1. **Phase 1:** Backend-Erweiterungen (Katalog + Endpoint) 2. **Phase 2:** Frontend Checkbox-UI (ersetzt Dropdown) 3. **Phase 3:** Katalog-Integration im Frontend 4. **Phase 4:** System-Rollen bearbeitbar machen --- ## Mockups ### Kompakte Tabellen-Ansicht (Phase 2) ``` ┌─────────────────────────────────────────────────────────────────────────────────┐ │ 📊 DATEN-REGELN [+ Neue Regel] │ ├─────────────────────────────────────────────────────────────────────────────────┤ │ │ │ EIGENE │ GRUPPE │ ALLE │ │ OBJEKT │VIEW │ C R U D │ C R U D │C R U D│ ├─────────────────────────────────────────────────────────────────────────────────┤ │ data.feature.trustee.* │ ✓ │ ✓ ✓ ✓ □ │ □ ✓ □ □ │□ □ □ □│ 🗑 │ data.feature.trustee.docs │ ✓ │ ✓ ✓ ✓ ✓ │ ✓ ✓ ✓ ✓ │✓ ✓ ✓ ✓│ 🗑 └─────────────────────────────────────────────────────────────────────────────────┘ ┌─────────────────────────────────────────────────────────────────────────────────┐ │ 🖥 UI-REGELN [+ Neue Regel] │ ├─────────────────────────────────────────────────────────────────────────────────┤ │ OBJEKT │ VIEW │ │ ├─────────────────────────────────────────────────────────────────────────────────┤ │ ui.feature.trustee.dashboard │ ✓ │ 🗑 │ │ ui.feature.trustee.positions │ ✓ │ 🗑 │ │ ui.feature.trustee.documents │ ✓ │ 🗑 │ │ ui.admin.mandate-roles │ □ │ 🗑 │ └─────────────────────────────────────────────────────────────────────────────────┘ ``` ### Objekt-Auswahl Dropdown (Phase 3) ``` ┌─────────────────────────────────────────────────────────────────┐ │ Neue Regel hinzufügen │ ├─────────────────────────────────────────────────────────────────┤ │ Objekt auswählen: [Freie Eingabe →] │ │ ┌─────────────────────────────────────────────────────────────┐ │ │ │ -- Global (alle Objekte) -- ▼│ │ │ ├─────────────────────────────────────────────────────────────┤ │ │ │ ▸ TRUSTEE │ │ │ │ data.feature.trustee.TrusteeContract - Vertrag │ │ │ │ data.feature.trustee.TrusteePosition - Position │ │ │ │ data.feature.trustee.TrusteeDocument - Dokument │ │ │ │ ▸ SYSTEM │ │ │ │ data.system.UserInDB - Benutzer │ │ │ │ data.system.Mandate - Mandant │ │ │ └─────────────────────────────────────────────────────────────┘ │ │ │ │ [✓] Sichtbar (View) │ │ │ │ [Abbrechen] [Hinzufügen] │ └─────────────────────────────────────────────────────────────────┘ ``` --- ## Offene Fragen 1. **Feature-Filter:** Soll der Katalog nach aktiven Features des Mandanten gefiltert werden? Ja, der Katalog soll nach aktiven Features des Mandanten gefiltert werden. 2. **Wildcard-Patterns:** Unterstützung für `data.feature.trustee.*` (alle Objekte eines Features)? Ja, die Unterstützung für `data.feature.trustee.*` (alle Objekte eines Features) soll implementiert werden. 3. **Feld-Level Permissions:** Sollen einzelne Felder (z.B. `TrusteeContract.salary`) unterstützt werden? Ja, die Unterstützung für einzelne Felder (z.B. `TrusteeContract.salary`) soll implementiert werden. 4. **Vererbung:** Sollen Berechtigungen von übergeordneten Objekten vererbt werden? Ja, die Vererbung von Berechtigungen von übergeordneten Objekten soll implementiert werden. --- *Erstellt: 2026-01-24* *Status: Entwurf zur Diskussion*