374 lines
16 KiB
Markdown
374 lines
16 KiB
Markdown
<!-- status: canonical -->
|
|
<!-- lastReviewed: 2026-05-23 -->
|
|
<!-- verifiedAgainst: gateway (codebase audit 2026-04-17, UDB-FlagEdit-Doku 2026-05-23) -->
|
|
|
|
# RBAC-System
|
|
|
|
## Ueberblick
|
|
|
|
Das Role-Based Access Control baut auf vier Stufen auf: **System**, **Mandant**, **Feature** und **Feature-Instanz**. Berechtigungen werden in **Access Rules** pro Rollenlabel und Kontext (DATA, UI, RESOURCE) gebunden. Auswertung erfolgt zentral ueber `interfaceRbac.py`; Zielbild: moeglichst **filternd in SQL** statt vollstaendiger Tabellen-Scans in Python. Nutzer koennen mehrere Rollenlabels gleichzeitig tragen; die effektive Berechtigung entsteht aus **Oeffnungslogik (Union)** ueber alle Rollen.
|
|
|
|
## Platform-Governance-Autoritaet (zwei orthogonale Flags)
|
|
|
|
Neben den Mandanten-scoped Rollen kennt das System zwei **plattformweite User-Flags** auf der `User`-Tabelle. Sie sind **einzeln vergebbar** und decken zwei unabhaengige Autoritaets-Achsen ab:
|
|
|
|
| Flag | Zweck | RBAC-Bypass | FastAPI-Dependency |
|
|
| --------------------- | -------------------------------------------------------------- | ----------- | ------------------------- |
|
|
| `isSysAdmin` | Infrastruktur-Operator (Logs, Tokens, DB-Health, i18n-Master, global-scoped Files/Sources) | **ja** | `requireSysAdmin` |
|
|
| `isPlatformAdmin` | Cross-Mandate-Governance (User-/Mandate-/RBAC-/Feature-Registry-Verwaltung ueber alle Mandanten) | **nein** | `requirePlatformAdmin` |
|
|
|
|
Wichtig:
|
|
|
|
- `isSysAdmin` wirkt als **harter RBAC-Engine-Bypass** in `rbac.py:getUserPermissions`. Reserviert fuer Infrastruktur-Operationen, die unabhaengig vom RBAC-Schema funktionieren muessen.
|
|
- `isPlatformAdmin` gibt **keinen** impliziten Daten-Zugriff. Er erlaubt nur den Zugriff auf Admin-Routen, die explizit `requirePlatformAdmin` deklarieren (z.B. `routeDataMandates`, `routeAdminRbacRules`, `routeBilling`-Cross-Mandate, `routeAdminUserAccessOverview`).
|
|
- Die historische `sysadmin`-Rolle im Root-Mandant wurde **eliminiert**. Eine einmalige idempotente Migration in `interfaceBootstrap._migrateAndDropSysAdminRole()` befoerdert ehemalige Rolleninhaber zu `isPlatformAdmin=True` und entfernt Rolle, AccessRules und `UserMandateRole`-Eintraege.
|
|
|
|
Beispiel-Profile:
|
|
|
|
- "Operations-Engineer" → `isSysAdmin=true`, `isPlatformAdmin=false` (kann Logs und DB pruefen, aber keine User/Mandate verwalten).
|
|
- "Customer-Success-Admin" → `isSysAdmin=false`, `isPlatformAdmin=true` (kann Mandanten deblockieren, User-Access-Overview sehen, aber nicht in Server-Logs schauen).
|
|
- "Plattform-Super-Admin" → beide auf `true`.
|
|
|
|
Details + Migrations-Plan: `wiki/c-work/4-done/2026-04-sysadmin-authority-split.md`.
|
|
|
|
---
|
|
|
|
## 4-Stufen-Hierarchie
|
|
|
|
```
|
|
System (PowerOn Platform)
|
|
|
|
|
+-- System Template Rollen (isSystemRole=true, mandateId=null, featureCode=null)
|
|
| Labels: "admin", "user", "viewer"
|
|
| -> kopiert bei Mandant-Erstellung via copySystemRolesToMandate()
|
|
|
|
|
+-- Feature Template Rollen (isSystemRole=false, mandateId=null, featureCode=X)
|
|
| Labels: "workspace-admin", "workspace-user", "workspace-viewer",
|
|
| "graphicalEditor-admin", "graphicalEditor-user", ...
|
|
| -> kopiert bei Feature-Instanz-Erstellung via _copyTemplateRoles()
|
|
|
|
|
+-- Mandant (Tenant)
|
|
+-- Mandate-Rollen (Instanzen der System Templates)
|
|
| mandateId=X, featureInstanceId=null, featureCode=null
|
|
| "admin", "user", "viewer"
|
|
| Zuordnung: UserMandateRole <- UserMandate <- User
|
|
|
|
|
+-- Feature-Instanz A (z.B. "AI Workspace Produktion")
|
|
| +-- Feature-Instanz-Rollen (Instanzen der Feature Templates)
|
|
| | mandateId=X, featureInstanceId=Y, featureCode="workspace"
|
|
| | "workspace-admin", "workspace-user", "workspace-viewer"
|
|
| | Zuordnung: FeatureAccessRole <- FeatureAccess <- User
|
|
| +-- Daten (Workflows, Files, Chats, ...)
|
|
|
|
|
+-- Feature-Instanz B (z.B. "Graphical Editor Dev")
|
|
+-- Feature-Instanz-Rollen
|
|
+-- Daten (Workflows, Runs, Tasks, ...)
|
|
```
|
|
|
|
### Sonderfaelle
|
|
|
|
- **SysAdmin / PlatformAdmin**: Plattformweite Autoritaet wird ueber **User-Flags** geregelt (`isSysAdmin`, `isPlatformAdmin`), nicht ueber Rollen. Siehe Abschnitt "Platform-Governance-Autoritaet".
|
|
- **SystemUser / EventUser**: Technische System-Accounts, nicht an echte Benutzer gebunden. Beide bekommen `isSysAdmin=true`; nur `admin` bekommt zusaetzlich `isPlatformAdmin=true`.
|
|
|
|
---
|
|
|
|
## Zwei getrennte Template-Rollen-Systeme
|
|
|
|
Templates sind **Blaupausen**. Sie definieren welche Rollen und Berechtigungen ein neuer Mandant oder eine neue Feature-Instanz erhaelt. Die beiden Template-Systeme sind **vollstaendig getrennt** -- kein Kreuz-Zuweisen.
|
|
|
|
### System Template Rollen (fuer Mandanten)
|
|
|
|
| Feld | Wert |
|
|
|------|------|
|
|
| `isSystemRole` | `true` |
|
|
| `mandateId` | `null` |
|
|
| `featureInstanceId` | `null` |
|
|
| `featureCode` | `null` |
|
|
| `roleLabel` | `"admin"`, `"user"`, `"viewer"` |
|
|
| Definiert in | `interfaceBootstrap.py` → `initRoles()` (Zeilen ~473-540) |
|
|
| Kopier-Mechanismus | `copySystemRolesToMandate()` in `interfaceBootstrap.py` |
|
|
| Ausgeloest bei | Mandant-Erstellung (`createMandate()`, `_provisionMandateForUser()`) |
|
|
| Bootstrap-Sync | `_ensureAllMandatesHaveSystemRoles()` beim Startup |
|
|
|
|
### Feature Template Rollen (fuer Feature-Instanzen)
|
|
|
|
| Feld | Wert |
|
|
|------|------|
|
|
| `isSystemRole` | `false` |
|
|
| `mandateId` | `null` |
|
|
| `featureInstanceId` | `null` |
|
|
| `featureCode` | z.B. `"workspace"`, `"automation2"`, `"trustee"` |
|
|
| `roleLabel` | z.B. `"workspace-admin"`, `"workspace-user"` |
|
|
| Definiert in | `TEMPLATE_ROLES` in jedem Feature-Main (z.B. `mainWorkspace.py` Zeilen ~83-135) |
|
|
| Sync in DB | `_syncTemplateRolesToDb()` bei `registerFeature()` |
|
|
| Kopier-Mechanismus | `_copyTemplateRoles()` in `interfaceFeatures.py` (Zeilen ~209-304) |
|
|
| Ausgeloest bei | Feature-Instanz-Erstellung (`createFeatureInstance(copyTemplateRoles=True)`) |
|
|
|
|
### TEMPLATE_ROLES Format (Beispiel Workspace)
|
|
|
|
```python
|
|
TEMPLATE_ROLES = [
|
|
{
|
|
"roleLabel": "workspace-admin",
|
|
"description": {"en": "Workspace Administrator", "de": "Workspace-Administrator"},
|
|
"accessRules": [
|
|
{"context": "UI", "item": "ui.feature.workspace", "view": True},
|
|
{"context": "RESOURCE", "item": "resource.feature.workspace", "view": True},
|
|
{"context": "DATA", "item": None, "read": "a", "create": "a", "update": "a", "delete": "a", "view": True}
|
|
]
|
|
},
|
|
{
|
|
"roleLabel": "workspace-user",
|
|
"description": {"en": "Workspace User", "de": "Workspace-Benutzer"},
|
|
"accessRules": [
|
|
{"context": "UI", "item": "ui.feature.workspace", "view": True},
|
|
{"context": "RESOURCE", "item": "resource.feature.workspace", "view": True},
|
|
{"context": "DATA", "item": None, "read": "m", "create": "m", "update": "m", "delete": "m", "view": True}
|
|
]
|
|
}
|
|
]
|
|
```
|
|
|
|
### Kopier-Invariante
|
|
|
|
Templates werden **nur bei Erstellung** kopiert. Spaetere Aenderungen an Templates werden **nicht** automatisch an bestehende Mandanten oder Feature-Instanzen propagiert. Fuer nachtraeglichen Sync existiert `syncRolesFromTemplate()` in `interfaceFeatures.py` -- aber dies muss explizit aufgerufen werden.
|
|
|
|
---
|
|
|
|
## Datenmodell
|
|
|
|
### Role
|
|
|
|
```python
|
|
class Role(PowerOnModel):
|
|
id: str # UUID, auto-generiert
|
|
mandateId: Optional[str] # null = Global/Template
|
|
featureInstanceId: Optional[str] # null = nicht Instanz-spezifisch
|
|
featureCode: Optional[str] # z.B. "workspace", "graphicalEditor"
|
|
roleLabel: str # z.B. "workspace-admin"
|
|
description: TextMultilingual # {"en": "...", "de": "..."}
|
|
isSystemRole: bool # true nur fuer System Template Rollen
|
|
```
|
|
|
|
**Immutable nach Erstellung:** `mandateId`, `featureInstanceId`, `featureCode` (erzwungen in `datamodelRbac.py`).
|
|
|
|
### AccessRule
|
|
|
|
```python
|
|
class AccessRule(PowerOnModel):
|
|
id: str
|
|
roleId: str # FK -> Role
|
|
context: AccessRuleContext # DATA | UI | RESOURCE
|
|
item: Optional[str] # null = generic, oder spezifischer Key
|
|
view: bool
|
|
read: Optional[AccessLevel] # nur bei DATA relevant
|
|
create: Optional[AccessLevel]
|
|
update: Optional[AccessLevel]
|
|
delete: Optional[AccessLevel]
|
|
```
|
|
|
|
### AccessLevel
|
|
|
|
```python
|
|
class AccessLevel(str, Enum):
|
|
NONE = "n" # Kein Zugriff
|
|
OWN = "o" # Nur eigene Datensaetze (userId-Match)
|
|
MANDATE = "m" # Alle im gleichen Mandanten
|
|
ALL = "a" # Alle (mandantenuebergreifend, nur SysAdmin)
|
|
```
|
|
|
|
### Membership-Modelle
|
|
|
|
```python
|
|
class UserMandate(PowerOnModel):
|
|
userId: str
|
|
mandateId: str
|
|
enabled: bool
|
|
|
|
class UserMandateRole(PowerOnModel):
|
|
userMandateId: str # FK -> UserMandate
|
|
roleId: str # FK -> Role (Mandate-Rolle)
|
|
|
|
class FeatureAccess(PowerOnModel):
|
|
userId: str
|
|
featureInstanceId: str
|
|
enabled: bool
|
|
|
|
class FeatureAccessRole(PowerOnModel):
|
|
featureAccessId: str # FK -> FeatureAccess
|
|
roleId: str # FK -> Role (Feature-Instanz-Rolle)
|
|
```
|
|
|
|
### ER-Diagramm (Vereinfacht)
|
|
|
|
```
|
|
User
|
|
|
|
|
+-- UserMandate (1:N)
|
|
| |
|
|
| +-- UserMandateRole (1:N) ---> Role (mandateId=X)
|
|
| |
|
|
| +-- AccessRule (1:N)
|
|
|
|
|
+-- FeatureAccess (1:N)
|
|
|
|
|
+-- FeatureAccessRole (1:N) ---> Role (featureInstanceId=Y)
|
|
|
|
|
+-- AccessRule (1:N)
|
|
```
|
|
|
|
---
|
|
|
|
## Access-Rule-Kontexte
|
|
|
|
### DATA
|
|
|
|
Steuert CRUD-Berechtigungen auf Datenbank-Ebene.
|
|
|
|
- **Item-Format**: Auto-generiert aus Tabellenname + Namespace (z.B. `data.uam.UserInDB`, `data.feature.trustee.TrusteePosition`)
|
|
- **Prueft**: `read`, `create`, `update`, `delete` als Access Levels
|
|
- **`view`**: Boolean, steuert Grundsichtbarkeit
|
|
- **Mapping**: `TABLE_NAMESPACE` in `interfaceRbac.py` ordnet Modellnamen logischen Namespaces zu
|
|
- **`buildDataObjectKey()`**: Baut Keys wie `data.{namespace}.{ModelName}` oder `data.feature.{featureCode}.{ModelName}`
|
|
|
|
### UI
|
|
|
|
Steuert Sichtbarkeit und Aktivierung von Oberflaechenelementen.
|
|
|
|
- **Item-Format**: Kaskadierende Strings (z.B. `playground.voice.settings`, `ui.feature.workspace.editor`)
|
|
- **Prueft**: Nur `view` (Boolean)
|
|
- **Verwendung**: Navigation, Seitenleiste, Buttons, Tabs
|
|
|
|
### RESOURCE
|
|
|
|
Steuert Zugriff auf Systemressourcen.
|
|
|
|
- **Item-Format**: Kaskadierende Strings (z.B. `ai.model.anthropic`, `resource.feature.workspace.execute`)
|
|
- **Prueft**: Primaer `view` (Boolean)
|
|
- **Verwendung**: KI-Modelle, Aktionen, Features
|
|
|
|
---
|
|
|
|
## Resolution-Algorithmus
|
|
|
|
### Priority-System
|
|
|
|
| Rollen-Typ | Scope | Priority |
|
|
|------------|-------|----------|
|
|
| **Global/Template** | `mandateId=null, featureInstanceId=null` | 1 (niedrigste) |
|
|
| **Mandate-Rolle** | `mandateId=X, featureInstanceId=null` | 2 |
|
|
| **Feature-Instanz-Rolle** | `mandateId=X, featureInstanceId=Y` | 3 (hoechste) |
|
|
|
|
### Resolution-Ablauf
|
|
|
|
1. **Rollen laden**: Mandate-Rollen via `UserMandate` → `UserMandateRole`; Feature-Rollen via `FeatureAccess` → `FeatureAccessRole` (`getRulesForUserBulk()` in `rbac.py`)
|
|
2. **AccessRules laden** fuer alle gefundenen Rollen
|
|
3. **Gruppierung** nach Priority-Stufe
|
|
4. **Hoechste Prioritaet gewinnt** bei DATA-Permissions
|
|
5. **View wird OR-verknuepft** ueber alle Rollen der hoechsten Prioritaetsstufe
|
|
6. **Item-Spezifitaet** innerhalb einer Stufe: exact > prefix > generic (laengster passender Praefix gewinnt)
|
|
|
|
### DATA: Oeffnungsrechte (CUD vs. Read)
|
|
|
|
| Read-Level | Maximales CUD |
|
|
|------------|---------------|
|
|
| `n` (NONE) | Kein CUD moeglich |
|
|
| `o` (OWN) | CUD hoechstens `o` oder `n` |
|
|
| `m` (MANDATE) | CUD hoechstens `m`, `o` oder `n` |
|
|
| `a` (ALL) | Alle Stufen fuer CUD zulaessig |
|
|
|
|
### Mehrere Rollen (Union-Logik)
|
|
|
|
Ueber Rollen hinweg gilt **Union** (OR): wenn eine Rolle nach interner Aufloesung `view: true` liefert, gilt das Element als sichtbar/erlaubt. Fuer DATA werden die **freizuegigsten** Stufen ueber Rollen kombiniert.
|
|
|
|
### SQL-Integration
|
|
|
|
`buildRbacWhereClause()` in `interfaceRbac.py` uebersetzt die aufgeloesten Access Levels in SQL-WHERE-Bedingungen:
|
|
- `a`: Kein Filter
|
|
- `m`: `WHERE mandateId = :mandateId`
|
|
- `o`: `WHERE _createdBy = :userId`
|
|
- `n`: `WHERE 1=0` (kein Zugriff)
|
|
|
|
RBAC-gefilterte Abfragen laufen ueber `getRecordsetWithRBAC()`.
|
|
|
|
---
|
|
|
|
## Bootstrap-Ablauf
|
|
|
|
Beim Gateway-Start (`initBootstrap()` in `interfaceBootstrap.py`):
|
|
|
|
```
|
|
1. initRootMandate() -- Root-Mandant sicherstellen
|
|
2. _deduplicateRoles() -- Duplikate bereinigen, isSystemRole-Flags fixen
|
|
3. initRoles() -- System Template Rollen (admin/user/viewer) anlegen
|
|
4. initRbacRules() -- Default AccessRules auf Template-Rollen
|
|
5. _ensureAllMandatesHaveSystemRoles()
|
|
-- copySystemRolesToMandate() fuer jeden Mandanten
|
|
6. _initSysAdminRole() -- SysAdmin-Rolle auf Root-Mandant
|
|
7. _ensureUiContextRules() -- UI-Kontext-Regeln pruefen
|
|
8. Admin/Event-User sichern
|
|
9. assignInitialUserMemberships()
|
|
-- UserMandate + UserMandateRole fuer Admin/SysAdmin
|
|
```
|
|
|
|
Danach bei Feature-Registrierung (`registerAllFeaturesInCatalog()` → `registerFeature()`):
|
|
|
|
```
|
|
10. _syncTemplateRolesToDb() -- Feature Template Rollen upserten
|
|
-- AccessRules fuer Templates synchronisieren
|
|
```
|
|
|
|
Bei Laufzeit:
|
|
|
|
```
|
|
- createMandate() -- copySystemRolesToMandate() fuer neuen Mandanten
|
|
- createFeatureInstance() -- _copyTemplateRoles() fuer neue Feature-Instanz
|
|
```
|
|
|
|
---
|
|
|
|
## Systemfelder (DATA)
|
|
|
|
Felder `id` und Namen mit fuehrendem `_` (z.B. `_createdBy`, `_createdAt`) sind fuer Anwendungs-CUD geschuetzt. Der Connector erzwingt das unabhaengig von Access Rules.
|
|
|
|
## Frontend-Optionen fuer Rollen
|
|
|
|
`frontend_options` an Feldern kann statische Listen oder String-Referenzen (z.B. `"user.role"`) nutzen. Dynamische Optionen ueber `/api/options/{optionsName}`. Typ-Hilfen: `platform-core/modules/shared/frontendTypes.py`.
|
|
|
|
---
|
|
|
|
## Schluessel-Dateien
|
|
|
|
| Thema | Pfad |
|
|
|-------|------|
|
|
| RBAC-Auswertung, SQL-Integration | `platform-core/modules/interfaces/interfaceRbac.py` |
|
|
| AccessRule/Permission-Modelle | `platform-core/modules/datamodels/datamodelRbac.py` |
|
|
| Membership-Modelle | `platform-core/modules/datamodels/datamodelMembership.py` |
|
|
| Bootstrap (System Templates, Copy) | `platform-core/modules/interfaces/interfaceBootstrap.py` |
|
|
| Feature-Template-Copy | `platform-core/modules/interfaces/interfaceFeatures.py` |
|
|
| Feature-Registrierung | `platform-core/modules/system/registry.py` |
|
|
| RBAC-Rule-Resolution | `platform-core/modules/security/rbac.py` |
|
|
| TEMPLATE_ROLES (Beispiel) | `platform-core/modules/features/workspace/mainWorkspace.py` |
|
|
|
|
## Feature-Admin-Gate fuer UDB-Flag-Edits
|
|
|
|
Die Unified Data Bar (siehe `b-reference/platform/unified-data-bar.md`) erlaubt das Editieren von `neutralize` / `ragIndexEnabled` / `neutralizeFields` auf `FeatureDataSource`-Nodes ausschliesslich fuer **Feature-Admins** der jeweiligen Feature-Instanz. Implementierung in `serviceKnowledge/udbNodes.py::_isFeatureAdmin(rootIf, userId, featureInstanceId)`:
|
|
|
|
1. lade die `FeatureAccess`-Row fuer (userId, featureInstanceId)
|
|
2. lade ihre `FeatureAccessRole`-Eintraege und die referenzierten `Role`-Records
|
|
3. erlaube nur, wenn mindestens eine Rolle `Role.roleLabel.endswith("-admin")` erfuellt (z.B. `workspace-admin`, `trustee-admin`, ...)
|
|
|
|
**Wichtig:**
|
|
- Es ist kein impliziter Bypass fuer `isSysAdmin` oder `isPlatformAdmin` vorgesehen — UDB-Flag-Edits sind ein **Daten-Verantwortungs-Akt** und nicht ein Plattform-Operations-Akt. Plattform-Administratoren ohne Feature-Admin-Rolle in der Instanz bekommen 403.
|
|
- Die DataSource-Familie (`conn|...`, `svc|...`, `ds|...`) wird ueber Ownership (`rec.userId == context.user.id`) gechecked, nicht ueber RBAC-Rollen.
|
|
|
|
Validierung des Wertes erfolgt in `routeUdb._validateFlagValue`; insbesondere setzt `scope="global"` zusaetzlich `context.isSysAdmin` voraus.
|
|
|
|
## Regeln / Invarianten
|
|
|
|
- Mandanten-Rollen und Feature-Instanz-Rollen **strikt trennen** (keine Kreuz-Zuweisung der Label-Typen)
|
|
- Templates werden nur bei Erstellung kopiert -- keine automatische Propagierung
|
|
- **Spezifisch vor generisch** innerhalb einer Rolle; **Union** ueber Rollen
|
|
- UI/RESOURCE: Sichtbar nur wenn `view: true`; nicht berechtigte UI-Eintraege werden gar nicht geliefert
|
|
- DATA: CUD darf nicht ueber dem jeweiligen Read-Level liegen
|
|
- Performance-Ziel: DATA-Zugriffe mit RBAC-Filter in SQL
|