Permissions & RBAC Architecture
Eviworx setzt Autorisierung über ein einheitliches, rollenbasiertes Modell durch: jede Request authentifiziert als Actor (User, API-Key oder System), trägt die Permission-Matrix ihrer Rolle und wird pro Route geprüft. Diese Seite ist die zentrale Referenz für das Rechtemodell; die einzelnen API-Seiten verweisen hierher.
Die drei Prüfebenen
Jede Berechtigung wirkt auf drei unabhängigen Ebenen:
| Ebene | Frage | Durchsetzung |
|---|---|---|
| Sehen | Darf der Actor diese Entity sehen? | Backend: Sichtprüfung am Objekt, Listen-Filter |
| Ändern | Darf er dieses Feld an dieser Entity ändern? | Backend: Routen-Recht + Prüfung der geänderten Felder |
| Oberfläche | Wird Tab/Button gerendert/aktiv? | Oberfläche: blendet aus/deaktiviert (nur Bedienkomfort, keine Sicherheitsgrenze) |
Für API-Clients zählen nur die Backend-Prüfungen Sehen und Ändern. Die Oberfläche blendet lediglich aus; durchgesetzt wird immer im Backend.
Unified Actor Model
| Actor | Auth | Permissions |
|---|---|---|
user | Session-Cookie (JWT, HttpOnly) | Matrix der zugewiesenen Rolle |
apiKey | Header X-API-Key (Bearer NICHT unterstützt) | Matrix der Rolle des Keys — Key OHNE aktive Rolle → 403 API_KEY_NO_ROLE |
system | interner Trusted-Caller (Worker/System-User) | BYPASS — nur wenn der interne Aufrufer ihn ausdrücklich anfordert |
Eine Anfrage authentifiziert sich entweder per Session-Cookie oder per X-API-Key; beide durchlaufen dieselbe Rechteprüfung. Ein API-Key erhält die Rechte seiner Rolle, optional eingeschränkt durch Ablaufdatum, IP-Whitelist, Rate-Limit und allowedTemplates.
User-only vs. API-Key-only: Manche Endpoints akzeptieren nur angemeldete Benutzer und weisen API-Keys mit 403 ab, z.B. die Cost-Center-Mutationen, die License-Assignment-Routen, das Ticket-Linking und alle Approval-Entscheidungen. Umgekehrt gibt es Endpoints nur für API-Keys. Lese- und Sync-Endpoints akzeptieren meist beides, etwa die Cost-Center-Reads und POST /api/cost-centers/import für den ERP-Sync.
Rechteprüfung pro Anfrage
- Rechte-Cache: Die Matrix einer Rolle wird pro Rolle 5 min zwischengespeichert. Lesende Anfragen nutzen den Cache, ändernde Anfragen (POST, PUT, PATCH, DELETE) laden die Rechte immer frisch aus der Datenbank. Eine Rollen-Änderung über die API leert den Cache der Rolle sofort und gilt damit für alle Träger der Rolle gleichzeitig.
- Fehlendes Recht: 403 mit errorCode FORBIDDEN; required nennt das geforderte Recht — als String, bei kritischen Aktionen und bei Routen, für die eines von mehreren Rechten genügt, als Array. (Body-Beispiele)
- Keine Rechte im Token: Die Rechte werden bei jeder Anfrage anhand der Rolle geladen, nicht aus dem Token gelesen. Ein älteres Token verschafft daher keine Rechte, die der Rolle inzwischen entzogen wurden.
- Kritische Aktionen: kritische Aktionen (z.B. delete, approve, manageRoles) lesen die Rechte in jedem Fall frisch aus der Datenbank; eine gerade entzogene Berechtigung greift dort ohne Cache-Verzögerung.
- Nutzbare Rolle: Eine fehlende Rolle, eine deaktivierte Rolle und eine ungültige Rechte-Matrix zählen gleich: Sie tragen keine nutzbaren Rechte. Dieselbe Regel entscheidet überall — bei der Anmeldung, beim Token-Refresh, beim Zuweisen einer Rolle und im Verzeichnis-Sync —, damit ein Konto nicht an einer Stelle als berechtigt gilt und an der nächsten abgewiesen wird. (Anmeldung)
Routen-Recht und Objekt-Prüfung: Das Recht an der Route allein reicht nicht. Zusätzlich wird am konkreten Objekt geprüft, ob der Aufrufer es sehen und die betroffenen Felder ändern darf. Eine Anfrage kann deshalb trotz vorhandenem Routen-Recht abgelehnt werden. Status-Übergänge und Genehmigungen haben eigene Rechte.
Prüfung am einzelnen Objekt
- Sehen: Listen, Suche und Statistiken wenden dieselbe Sichtregel an wie die Einzelansicht.
- Ändern: entweder ein Grundrecht (editAll, oder editOwn für eigene Objekte) plus ein Zusatzrecht je geändertem Feld (Zuweisung → assign, Status → changeStatus, Wiederöffnen → reopen …), oder ein eigenes Recht pro Aktion, wo es keine „eigenen“ Objekte gibt.
- Reopen: eigenes Recht reopen / reopenOverride (Ticket, Incident, Problem) — getrennt von changeStatus/editAll. Ein Reopen erfordert tickets.reopen (bzw. incidents./problems.reopen); eine Rolle mit editAll, aber reopen=false, kann NICHT wiederöffnen. reopenOverride umgeht nur Window/Limit, nie die Grund- oder Approval-Pflicht. (Details)
| Edit-Modell | Domains (Beispiele) |
|---|---|
| Eigene/alle + Feldrechte | Ticket, Incident, Problem, Change, Contract, Inventory, Custom-Reports |
| Ein Recht pro Aktion | CostCenter, License, Cronjob, Settings, Notifications, Approvals (Verwaltung) |
| Asset-Typ-Sperre (zusätzliches Recht) | Asset (pro Asset-Typ Grant/Override) |
Sichtbarkeit im Detail
Über die globalen Rechte viewAll/viewOwn hinaus wird die tatsächliche Sichtbarkeit weiter eingeschränkt. Drei Einschränkungen sind im Betrieb entscheidend:
Mailbox-Zugriff (Tickets)
accessRestricted = false→ jeder mit tickets.viewAll sieht die Mailbox-Tickets.accessRestricted = true→ nur User/Rollen/Agent-Gruppen in der MailboxAccess-Liste (separate Flags canViewTickets / canBeAssigned). Selbst viewAll wird eingeschränkt.- Ausnahmen: Ersteller (customerId), zugewiesener Agent und Agents der zugewiesenen Gruppe sehen das Ticket immer.
Agent-Gruppen-Scope
Die Sichtprüfung eines Tickets berücksichtigt die Gruppen-Mitgliedschaften des Actors (assignedGroupId ∈ eigene Gruppen) — so sieht ein Agent die Queue seiner Gruppe(n) ohne globales viewAll. Es zählen nur aktive Mitgliedschaften.
Vertretung (Substitute)
Während einer genehmigten Abwesenheit sieht und bearbeitet der Vertreter die Tickets/Items des vertretenen Users, in Listen wie in der Einzelansicht. Außerhalb einer genehmigten Vertretung gilt diese Ausnahme nicht.
Custom Reports: zwei Rechte
Der Report-Builder ist ein Massendaten-/Export-Kanal und trennt deshalb bewusst ZWEI Rechte, die beide nötig sind:
| Recht | Steuert |
|---|---|
customReports.* (view*/create/edit*/delete*/export/schedule/share) | die Report-DEFINITION — Zugang zum Builder, CRUD, Teilen, Planen |
<feature>.reporting (tickets, problems, changes, incidents, assets, contracts, licenses) | die DATEN-Ebene — welche Domäne der User überhaupt massenauswerten darf („In Reports verwenden") |
Die Datenquellen im Builder folgen denselben Sichtregeln wie oben: Eine Domäne erscheint nur, wenn der User ihr reporting-Recht besitzt UND überhaupt Zeilen dieser Domäne sehen darf. Beispiele: Asset-Typ-Freigaben ohne globales Leserecht → die Quelle erscheint; reporting-Recht auf eine Domäne ohne jede Sicht → die Quelle erscheint nicht. Beim Ausführen filtert der Report zusätzlich jede Zeile nach der Sicht des Ausführenden.
- Sub-Entitäten erben das Domänen-Recht: changes.reporting schaltet auch changeTasks frei, assets.reporting auch handovers.
- Einzelne Felder erfordern zusätzlich ein eigenes Recht (Beteiligten-E-Mails → tickets.viewAll, DSGVO-Block → incidents.viewPIR) — damit ist der Report an dieser Stelle strenger als die Einzelsicht.
- Ergebnis-Sicht = Report-Sicht + customReports.export: Wer einen geteilten Report sehen darf und export besitzt, lädt dessen Ergebnisse im Scope des Ausführenden. Teilen an Rollen ist explizit (sharedWithAllRoles-Flag; eine leere Rollenliste bedeutet: mit niemandem geteilt, nicht mit allen).
Vollständige API-Details, Fehlercodes und Feld-Gates: Reports & Custom Reports API.
Verknüpfungen zwischen Objekten
Verknüpfungen (z.B. License↔Asset/User/Contract, Asset↔Contract) werden zentral geprüft: Die Route verlangt das Recht der aufrufenden Seite, und zusätzlich muss der Aufrufer beide Enden sehen dürfen, unabhängig von der Richtung:
allowed(A↔B) = permission of the calling side
AND canView(A) AND canView(B) (direction-neutral)
Verknüpfte Objekte erscheinen mit vollen Daten, wenn der Aufrufer sie sehen darf, sonst als Platzhalter (id/number/status, restricted:true).
Ticket an Ticket: Elternticket und Sub-Tickets
Tickets lassen sich auch untereinander verknüpfen: ein Ticket wird einem anderen als Sub-Ticket untergeordnet. Dafür gilt ein eigenes Recht der Ticket-Domäne, tickets.linkToTickets — mit derselben Parität wie die übrigen linkTo*-Rechte. Es deckt beide Enden der Aktion ab: das Unterordnen und Lösen der Beziehung, das Anlegen eines Tickets mit Elternticket und die Liste der Tickets, die als Sub-Ticket überhaupt in Frage kommen. Ohne das Recht bleibt die Kandidatenliste verschlossen, nicht nur der Schreibweg. Die allgemeine Regel gilt zusätzlich: der Aufrufer muss beide Tickets sehen dürfen.
Die internen Angaben zu dieser Beziehung — die Liste der Sub-Tickets, ihre Zähler, der Verlauf und der Grund einer SLA-Pause — folgen zusätzlich tickets.viewInternal, dem Recht auf die interne Sicht eines Tickets. Wer es nicht besitzt (typischerweise ein Kunde), sieht das Ticket, aber nicht seine Eltern-Kind-Angaben. Details zur Ticket-API, Übersicht der Verknüpfungs-Endpunkte.
Permission-Katalog & System-Rollen
Der Katalog (alle Module/Aktionen) ergibt sich aus den Rechten der 4 System-Rollen. System-Rollen lassen sich weder deaktivieren noch löschen noch verschieben, und settings.manageRoles lässt sich ihnen nicht entziehen; Custom-Rollen sind frei konfigurierbar. Niedrigere priority = höhere Privilegien (entscheidet bei EntraID-Konflikten).
| Role | Priority | isSystem |
|---|---|---|
ADMIN | 1000 | true |
AGENT | 2000 | true |
DATA_PROTECTION_OFFICER (DSB) | 2500 | false (vorinstalliert) |
APPROVER | 3000 | true |
END_USER | 99000 | true |
Neue Rechte nach einem Update: Kommt ein neues Recht hinzu, steht es in Custom-Rollen zunächst auf „aus“. Ersetzt es ein bestehendes Recht, übernimmt es dessen Wert, sodass bestehende Rollen nichts verlieren.