34 KiB
RBAC Access Rules Editor - Refactoring Vorschlag
Übersicht
Dieses Dokument beschreibt die geplante Überarbeitung der RBAC Access Rules UI-Komponenten basierend auf drei Hauptanforderungen:
- System-Rollen bearbeitbar machen - AccessRules für System-Rollen editierbar
- Checkbox-basiertes UI - Kompakteres Layout mit Checkboxen statt Dropdowns
- Dot-Notation & Objekt-Katalog - Verfügbare Objekte als Dropdown auswählbar
1. System-Rollen mit bearbeitbaren AccessRules
Ist-Zustand
Aktuell in AdminMandateRolePermissionsPage.tsx:
<AccessRulesEditor
roleId={role.id}
roleName={role.roleLabel}
readOnly={role.isSystemRole} // ← System-Rollen sind komplett read-only
...
/>
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.managePermission)
Implementierung
Option A: Separate Prop für Rollen-Protection
// AdminMandateRolePermissionsPage.tsx
<AccessRulesEditor
roleId={role.id}
roleName={role.roleLabel}
readOnly={false} // AccessRules immer bearbeitbar
roleProtected={role.isSystemRole} // Nur Rolle selbst geschützt
apiBasePath="/api/rbac"
mandateId={selectedMandateId}
/>
Option B: Immer bearbeitbar (empfohlen)
Entferne die readOnly-Logic basierend auf isSystemRole:
// AdminMandateRolePermissionsPage.tsx
<AccessRulesEditor
roleId={role.id}
roleName={role.roleLabel}
readOnly={false} // Alle AccessRules bearbeitbar
apiBasePath="/api/rbac"
mandateId={selectedMandateId}
/>
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
interface AccessRulesTableProps {
rules: AccessRule[];
context: RuleContext;
readOnly?: boolean;
onUpdate: (ruleId: string, updates: Partial<AccessRule>) => void;
onDelete: (ruleId: string) => void;
}
const AccessRulesTable: React.FC<AccessRulesTableProps> = ({
rules,
context,
readOnly,
onUpdate,
onDelete,
}) => {
const isDataContext = context === 'DATA';
return (
<table className={styles.accessRulesTable}>
<thead>
<tr>
<th className={styles.colObject}>Objekt (Dot-Notation)</th>
<th className={styles.colView}>View</th>
{isDataContext && (
<>
<th className={styles.colGroup} colSpan={4}>Eigene (m)</th>
<th className={styles.colGroup} colSpan={4}>Gruppe (g)</th>
<th className={styles.colGroup} colSpan={4}>Alle (a)</th>
</>
)}
<th className={styles.colActions}></th>
</tr>
{isDataContext && (
<tr className={styles.subHeader}>
<th></th>
<th></th>
<th>C</th><th>R</th><th>U</th><th>D</th>
<th>C</th><th>R</th><th>U</th><th>D</th>
<th>C</th><th>R</th><th>U</th><th>D</th>
<th></th>
</tr>
)}
</thead>
<tbody>
{rules.map(rule => (
<AccessRuleRow
key={rule.id}
rule={rule}
isDataContext={isDataContext}
readOnly={readOnly}
onUpdate={onUpdate}
onDelete={onDelete}
/>
))}
</tbody>
</table>
);
};
Neue Komponente: AccessRuleRow.tsx
interface AccessRuleRowProps {
rule: AccessRule;
isDataContext: boolean;
readOnly?: boolean;
onUpdate: (ruleId: string, updates: Partial<AccessRule>) => void;
onDelete: (ruleId: string) => void;
}
const AccessRuleRow: React.FC<AccessRuleRowProps> = ({
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 (
<tr className={styles.ruleRow}>
{/* Objekt-Name in Dot-Notation */}
<td className={styles.objectCell}>
<code>{rule.item || '(global)'}</code>
</td>
{/* View Checkbox */}
<td className={styles.checkboxCell}>
<input
type="checkbox"
checked={rule.view}
onChange={(e) => onUpdate(rule.id, { view: e.target.checked })}
disabled={readOnly}
/>
</td>
{/* CRUD Checkboxen für DATA-Kontext */}
{isDataContext && (
<>
{/* Eigene (m) */}
{['create', 'read', 'update', 'delete'].map(op => (
<td key={`m-${op}`} className={styles.checkboxCell}>
<input
type="checkbox"
checked={hasLevel(rule[op as keyof AccessRule] as AccessLevel, 'm')}
onChange={(e) => toggleLevel(op as any, 'm', e.target.checked)}
disabled={readOnly}
/>
</td>
))}
{/* Gruppe (g) */}
{['create', 'read', 'update', 'delete'].map(op => (
<td key={`g-${op}`} className={styles.checkboxCell}>
<input
type="checkbox"
checked={hasLevel(rule[op as keyof AccessRule] as AccessLevel, 'g')}
onChange={(e) => toggleLevel(op as any, 'g', e.target.checked)}
disabled={readOnly}
/>
</td>
))}
{/* Alle (a) */}
{['create', 'read', 'update', 'delete'].map(op => (
<td key={`a-${op}`} className={styles.checkboxCell}>
<input
type="checkbox"
checked={hasLevel(rule[op as keyof AccessRule] as AccessLevel, 'a')}
onChange={(e) => toggleLevel(op as any, 'a', e.target.checked)}
disabled={readOnly}
/>
</td>
))}
</>
)}
{/* Löschen-Button */}
<td className={styles.actionsCell}>
{!readOnly && (
<button
className={styles.deleteButton}
onClick={() => onDelete(rule.id)}
title="Regel löschen"
>
<FaTrash />
</button>
)}
</td>
</tr>
);
};
CSS für Tabellen-Layout
/* 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:
// AddRuleForm.tsx (aktuell)
<input
type="text"
value={item}
onChange={(e) => 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:
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
- Backend: Neuer API-Endpoint zum Abrufen verfügbarer RBAC-Objekte
- Backend: DATA-Objekte (Tabellen/Entitäten) im Katalog registrieren
- Frontend: Dropdown zur Auswahl aus verfügbaren Objekten
- Frontend: Konsistente Dot-Notation für alle Objekte
Backend-Implementierung
1. DATA-Objekte im Katalog registrieren
Erweitere rbacCatalog.py:
# 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:
# 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:
# 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:
// 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<string, any>;
type: RuleContext;
}
interface CatalogObjects {
DATA: CatalogObject[];
UI: CatalogObject[];
RESOURCE: CatalogObject[];
}
export function useCatalogObjects() {
const [objects, setObjects] = useState<CatalogObjects>({ DATA: [], UI: [], RESOURCE: [] });
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const fetchObjects = useCallback(async (
context?: RuleContext,
featureCode?: string
): Promise<CatalogObjects> => {
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:
// AccessRulesEditor.tsx - AddRuleForm Komponente
interface AddRuleFormProps {
context: RuleContext;
availableObjects: CatalogObject[]; // NEU
onAdd: (rule: AccessRuleCreate) => void;
onCancel: () => void;
}
const AddRuleForm: React.FC<AddRuleFormProps> = ({
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<string, CatalogObject[]> = {};
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 (
<form className={styles.addRuleForm} onSubmit={handleSubmit}>
<div className={styles.formGroup}>
<label className={styles.formLabel}>
Objekt auswählen
<button
type="button"
className={styles.toggleCustom}
onClick={() => setUseCustom(!useCustom)}
>
{useCustom ? '← Aus Katalog wählen' : 'Freie Eingabe →'}
</button>
</label>
{useCustom ? (
// Freitext-Eingabe (wie bisher)
<input
type="text"
value={item}
onChange={(e) => setItem(e.target.value)}
placeholder={getPlaceholder()}
className={styles.formInput}
/>
) : (
// Dropdown mit verfügbaren Objekten
<select
value={item}
onChange={(e) => setItem(e.target.value)}
className={styles.formSelect}
>
<option value="">-- Global (alle Objekte) --</option>
{Object.entries(groupedObjects).map(([feature, objs]) => (
<optgroup key={feature} label={feature.toUpperCase()}>
{objs.map(obj => (
<option key={obj.objectKey} value={obj.objectKey}>
{obj.objectKey} - {obj.label[lang] || obj.label.en}
</option>
))}
</optgroup>
))}
</select>
)}
<span className={styles.formHint}>
Leer lassen für globale Regel. Längster Match gewinnt.
</span>
</div>
{/* ... rest of form */}
</form>
);
};
3. Katalog-Objekte im Editor laden
// AccessRulesEditor.tsx
export const AccessRulesEditor: React.FC<AccessRulesEditorProps> = ({
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 (
<div className={styles.accessRulesEditor}>
{/* ... header, tabs */}
<div className={styles.tabContent}>
{activeTab !== 'JSON' && (
<RulesSection
context={activeTab}
rules={groupedRules[activeTab]}
availableObjects={currentContextObjects} // NEU
readOnly={readOnly}
onUpdate={handleUpdate}
onDelete={handleDelete}
onAdd={handleAdd}
/>
)}
{/* ... JSON tab */}
</div>
{/* ... action bar */}
</div>
);
};
Dot-Notation Schema
Konsistentes Namensschema für alle RBAC-Objekte:
<type>.<scope>.<feature>.<entity>[.<field>]
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
- Phase 1: Backend-Erweiterungen (Katalog + Endpoint)
- Phase 2: Frontend Checkbox-UI (ersetzt Dropdown)
- Phase 3: Katalog-Integration im Frontend
- 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
- Feature-Filter: Soll der Katalog nach aktiven Features des Mandanten gefiltert werden? Ja, der Katalog soll nach aktiven Features des Mandanten gefiltert werden.
- Wildcard-Patterns: Unterstützung für
data.feature.trustee.*(alle Objekte eines Features)? Ja, die Unterstützung fürdata.feature.trustee.*(alle Objekte eines Features) soll implementiert werden. - 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. - 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