wiki/b-reference/platform/rbac.md
2026-05-27 16:49:03 +02:00

16 KiB

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.pyinitRoles() (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)

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

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

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

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

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 UserMandateUserMandateRole; Feature-Rollen via FeatureAccessFeatureAccessRole (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: gateway/modules/shared/frontendTypes.py.


Schluessel-Dateien

Thema Pfad
RBAC-Auswertung, SQL-Integration gateway/modules/interfaces/interfaceRbac.py
AccessRule/Permission-Modelle gateway/modules/datamodels/datamodelRbac.py
Membership-Modelle gateway/modules/datamodels/datamodelMembership.py
Bootstrap (System Templates, Copy) gateway/modules/interfaces/interfaceBootstrap.py
Feature-Template-Copy gateway/modules/interfaces/interfaceFeatures.py
Feature-Registrierung gateway/modules/system/registry.py
RBAC-Rule-Resolution gateway/modules/security/rbac.py
TEMPLATE_ROLES (Beispiel) gateway/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