434 lines
35 KiB
Markdown
434 lines
35 KiB
Markdown
<!-- status: done -->
|
||
<!-- started: 2026-04-17 -->
|
||
<!-- finished: 2026-04-17 -->
|
||
<!-- component: gateway | ui-nyla | platform -->
|
||
|
||
# SysAdmin-Authority-Modell konsolidieren: Flag + Rolle → zwei klar getrennte Flags
|
||
|
||
## Beschreibung und Kontext
|
||
|
||
Das PORTA-Berechtigungsmodell kennt heute **zwei parallel gepflegte
|
||
SysAdmin-Marker**, die sich überschneiden:
|
||
|
||
1. **`User.isSysAdmin` FLAG** (Spalte in `User`-Tabelle)
|
||
- Wirkt als **RBAC-Engine-Bypass** in `rbac.py:getUserPermissions` (Zeile 88)
|
||
→ umgeht alle AccessRules und gibt vollen Zugriff.
|
||
- Geprüft via `ctx.isSysAdmin`, `requireSysAdmin`, direktes
|
||
`getattr(user, 'isSysAdmin', False)`.
|
||
- Verwendet in Routen für System-Operationen (Tokens, Logs, DB-Health,
|
||
i18n-Master, Teams-SystemBot, global-scoped Files/Sources).
|
||
|
||
2. **`sysadmin` ROLE** (UserMandateRole im Root-Mandant)
|
||
- Gedacht als normale Rolle im Root-Mandant.
|
||
- Gibt implizit **mandanten-übergreifende** Admin-Rechte, weil die zugehörigen
|
||
AccessRules auf User-/Mandate-/RBAC-Verwaltung eingestellt sind.
|
||
- Geprüft via `ctx.hasSysAdminRole`, `_hasSysAdminRole(userId)`,
|
||
`requireSysAdminRole`.
|
||
- Verwendet in Routen für cross-mandate Admin-Operationen
|
||
(User-Management, Mandate-Management, RBAC-Rules, i18n, Subscription).
|
||
|
||
### Warum der aktuelle Zustand problematisch ist
|
||
|
||
1. **Architektonisch inkonsistent:** Alle anderen Rollen sind **mandanten-scoped**.
|
||
Nur `sysadmin` ist eine Rolle im Root-Mandant, die **Autorität über andere
|
||
Mandanten** verleiht. Das durchbricht die Mandanten-Isolation konzeptionell.
|
||
2. **Doppelte Quelle der Wahrheit:** Flag und Rolle können auseinander driften.
|
||
Siehe konkreter Security-Bug (Issue 4): Admin entfernt Flag über UI, Rolle
|
||
bleibt → User behält alle Cross-Mandate-Rechte.
|
||
3. **Verwirrende Semantik:** „SysAdmin" kann zwei unterschiedliche Dinge
|
||
bedeuten — je nachdem, wer fragt.
|
||
4. **Fehlende Differenzierung:** Es ist heute nicht möglich, jemanden
|
||
auszustatten mit
|
||
- „darf die Plattform betreiben" (Logs, Tokens, DB-Health)
|
||
aber nicht mit
|
||
- „darf über alle Mandanten User und Rollen verwalten".
|
||
Oder umgekehrt. Beides hängt am selben Flag bzw. an derselben Rolle.
|
||
|
||
**Risiko bei Nicht-Umsetzung:**
|
||
- **SECURITY:** Ex-SysAdmin mit entferntem Flag behält Cross-Mandate-Admin-Rechte
|
||
(aktiver Bug).
|
||
- Aufbau technischer Schuld: jede neue Admin-Route muss zwischen Flag und
|
||
Rolle wählen — Entwickler wählen falsch.
|
||
- Audit/Compliance: schwer nachzuweisen, wer welche Autorität warum hat.
|
||
|
||
## Fokus und kritische Details
|
||
|
||
- Drei Kategorien von Autorität sind heute konzeptionell vermischt:
|
||
- **Infrastruktur-Autorität** (Kategorie A): Logs, Tokens, DB-Health,
|
||
Registry-Sync, i18n-Master, Billing-Webhooks, Teams-SystemBot.
|
||
- **Plattform-Governance-Autorität** (Kategorie B–E): Cross-Mandate
|
||
User-Mgmt, Mandate-Mgmt, RBAC-Catalog, Feature-Registry,
|
||
User-Access-Overview, Mandanten-Deblockierung.
|
||
- **Mandanten-Autorität**: alles innerhalb eines spezifischen Mandanten
|
||
(inkl. Root-Mandant) — hier sind Rollen das richtige Konzept.
|
||
- Die `sysadmin`-Rolle existiert heute nur im Root-Mandant
|
||
(`interfaceBootstrap.py:_initSysAdminRole`, `isSystemRole=False`).
|
||
Ihre AccessRules regeln aber Autorität über **alle** Mandanten.
|
||
- Die RBAC-Engine hat für `isSysAdmin=True` einen **harten Bypass**
|
||
(`rbac.py:88`). Das ist kein Missbrauch, sondern eine **bewusste Design-
|
||
Entscheidung** — so funktionieren „system-ops" unabhängig vom RBAC-Schema.
|
||
- Bestehende DB-Stände können inkonsistent sein → Einmalige Migration nötig.
|
||
|
||
## Ziel und Nicht-Ziele
|
||
|
||
**Ziel (Empfehlung, siehe Entscheidungen unten)**
|
||
|
||
- Zwei klar getrennte, **einzeln zuteilbare Berechtigungs-Dimensionen**:
|
||
- `User.isSysAdmin` (umbenannt von Bedeutung her → **„Infrastructure/System
|
||
Operator"**): RBAC-Bypass für Infrastruktur-Operationen, **keine** implizite
|
||
Autorität über Mandanten-Daten oder -Verwaltung.
|
||
- `User.isPlatformAdmin` (**neu**): Cross-Mandate-Governance ohne RBAC-
|
||
Bypass. Prüfung in den jeweiligen Admin-Routen explizit.
|
||
- **Eliminierung der `sysadmin`-Rolle** aus dem Root-Mandant.
|
||
Die Autorität, die bisher an der Rolle hing, wird an `isPlatformAdmin`
|
||
gebunden.
|
||
- Beide Flags einzeln vergebbar, einzeln sichtbar im UI, einzeln auditierbar.
|
||
- Beispiele für eine klare Trennung:
|
||
- „Operations-Engineer" → `isSysAdmin=true`, `isPlatformAdmin=false`
|
||
(kann Logs und DB prüfen, 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`.
|
||
|
||
**Explizit NICHT**
|
||
|
||
- Einführung einer neuen Rollen-Tabelle (`PlatformRole`) — Overkill für zwei
|
||
heute bekannte Autoritäts-Achsen; bei Bedarf später zusätzliche Flags.
|
||
- Änderung der Mandanten-scoped Rollen (admin/user/viewer etc.)
|
||
- Änderung der RBAC-Engine-Semantik — `isSysAdmin`-Bypass bleibt; er
|
||
beschränkt sich jetzt aber **eindeutig auf Infrastruktur-Zwecke**.
|
||
- Wiederverwendung des Begriffs „sysadmin" in der Namensgebung für Cross-
|
||
Mandate-Governance (explizit vermeiden — eindeutige Namen, keine
|
||
Ambiguität).
|
||
|
||
## Architektur-Analyse: verworfene Alternativen
|
||
|
||
### Alternative A (verworfen): Rolle behalten, Flag eliminieren
|
||
|
||
- Nur `sysadmin`-Rolle, Flag weg.
|
||
- **Verworfen**, weil:
|
||
- Rolle bleibt konzeptionell im Mandanten-Kontext gefangen.
|
||
- RBAC-Bypass für Infrastruktur-Operationen wäre schwerer umzusetzen
|
||
(müsste an Rolle hängen → Root-Mandant-Query bei jedem Request).
|
||
- Infrastruktur-Routen benötigen oft **keinen** Mandanten-Kontext.
|
||
|
||
### Alternative B (verworfen): Flag behalten, Rolle eliminieren, eine Flag-Dimension
|
||
|
||
- Nur `isSysAdmin` flag, Rolle weg. **Eine einzige** Dimension.
|
||
- **Verworfen**, weil:
|
||
- Keine Trennung „pure Infrastruktur" vs. „Cross-Mandate-Governance".
|
||
- Wer Logs lesen darf, kann automatisch auch Mandanten deblockieren —
|
||
das ist zu grob und macht Auditierbarkeit schwierig.
|
||
- Vom User explizit gewünschte Differenzierung geht verloren („sysadmin
|
||
flag sauber = keine Daten-Berechtigung, nur Admin-Zwecke").
|
||
|
||
### Alternative C (verworfen): Separate `PlatformRole`-Tabelle
|
||
|
||
- Neues Schema `UserPlatformRole` mit Enum `SYSTEM_OPERATOR` +
|
||
`PLATFORM_ADMIN` (+ Zukunft).
|
||
- **Verworfen für Phase 1**, weil:
|
||
- Zwei Autoritäts-Achsen heute bekannt → zwei Flags sind die einfachste,
|
||
ausreichende Lösung.
|
||
- Einführung einer neuen Tabelle = Migration + zusätzliche Query-Overhead.
|
||
- Kann **später** eingeführt werden, wenn eine dritte Achse entsteht —
|
||
die beiden Flags migrieren dann trivial in die neue Struktur.
|
||
|
||
### Alternative D (EMPFOHLEN): Zwei orthogonale Flags, Rolle eliminiert
|
||
|
||
- `isSysAdmin` behält Bedeutung + Bypass, wird aber semantisch auf
|
||
Infrastruktur eingeschränkt.
|
||
- `isPlatformAdmin` neu für Cross-Mandate-Governance.
|
||
- `sysadmin`-Rolle wird aus Root-Mandant + AccessRules entfernt.
|
||
- **Vorteile:**
|
||
- Eine Autorität = genau ein Marker (keine Sync-Pflicht).
|
||
- Einzeln vergebbar → feinere Governance.
|
||
- Keine Rollen mehr, die Mandanten-Grenzen durchbrechen.
|
||
- RBAC-Engine-Logik wird einfacher (kein root-mandate-role-lookup mehr).
|
||
- **Nachteile:**
|
||
- Migration nötig.
|
||
- Alle `ctx.hasSysAdminRole` / `requireSysAdminRole` Callsites müssen
|
||
umgeschrieben werden.
|
||
|
||
→ **Entscheidung: Alternative D.**
|
||
|
||
## Zielarchitektur im Detail
|
||
|
||
### Autoritäts-Matrix (nach Umsetzung)
|
||
|
||
| Autorität | Scope | Marker | Prüfung in Route |
|
||
| -------------------------------------- | ------------------------------ | ------------------------------------------- | --------------------------------------------- |
|
||
| Logs, Tokens, DB-Health, i18n-Master | global (ohne Mandant) | `isSysAdmin=true` | `requireSysAdmin` |
|
||
| RBAC-Engine-Bypass | global (Datenzugriff) | `isSysAdmin=true` | `rbac.py:getUserPermissions` (unverändert) |
|
||
| User-Mgmt cross-mandate | cross-mandate | `isPlatformAdmin=true` | `requirePlatformAdmin` (neu) |
|
||
| Mandate-Mgmt (create/edit/block) | cross-mandate | `isPlatformAdmin=true` | `requirePlatformAdmin` |
|
||
| RBAC-Rules-Catalog | cross-mandate (Templates) | `isPlatformAdmin=true` | `requirePlatformAdmin` |
|
||
| Feature-Registry (Admin-Features-UI) | cross-mandate | `isPlatformAdmin=true` | `requirePlatformAdmin` |
|
||
| User-Access-Overview | cross-mandate | `isPlatformAdmin=true` | `requirePlatformAdmin` |
|
||
| Billing-Admin-Overview (alle Mandate) | cross-mandate | `isPlatformAdmin=true` | `requirePlatformAdmin` |
|
||
| Billing eines Mandanten | 1 Mandant | Mandanten-Rolle `admin` | `requireMandateAdmin(mandateId)` (bestehend) |
|
||
| User-Verwaltung innerhalb Mandant | 1 Mandant | Mandanten-Rolle `admin` | `requireMandateAdmin(mandateId)` |
|
||
| Feature-Instance-Verwaltung | 1 Mandant | Mandanten-Rolle `admin` | `requireMandateAdmin(mandateId)` |
|
||
|
||
### Neue Auth-Helpers (in `modules/auth/authentication.py`)
|
||
|
||
```python
|
||
@property
|
||
def isPlatformAdmin(self) -> bool:
|
||
return getattr(self.user, 'isPlatformAdmin', False)
|
||
|
||
def requirePlatformAdmin(currentUser: User = Depends(getCurrentUser)) -> User:
|
||
if not getattr(currentUser, 'isPlatformAdmin', False):
|
||
raise HTTPException(403, "Platform admin privileges required")
|
||
audit_logger.logSecurityEvent(userId=str(currentUser.id),
|
||
mandateId="system", action="platform_admin_action",
|
||
details="Cross-mandate governance operation")
|
||
return currentUser
|
||
```
|
||
|
||
`requireSysAdmin` (bestehend) wird **enger definiert** — nur noch für
|
||
Infrastruktur-Routen.
|
||
|
||
### Migration (einmalig, beim Boot, idempotent)
|
||
|
||
```
|
||
for user in alle User:
|
||
hadSysadminRole = _hasSysAdminRole_legacy(user.id)
|
||
# Alte Rolle impliziert sowohl Cross-Mandate-Admin als auch (historisch)
|
||
# impliziten Infrastruktur-Zugriff. Setze neuen Flag konservativ.
|
||
if hadSysadminRole and not user.isPlatformAdmin:
|
||
user.isPlatformAdmin = True
|
||
log.warning(f"Migration: granted isPlatformAdmin to {user.id}")
|
||
# Flag bleibt wie es ist (unabhängig).
|
||
after:
|
||
drop AccessRules WHERE roleId = sysadmin_root_role
|
||
drop UserMandateRole WHERE roleId = sysadmin_root_role
|
||
drop Role WHERE id = sysadmin_root_role
|
||
```
|
||
|
||
## Referenzen (anzupassende Stellen)
|
||
|
||
### Backend — Datamodel
|
||
|
||
| Datei | Was | Änderung |
|
||
| --------------------------------------------- | ---------------------------------------------------- | ------------------------------------------------------------ |
|
||
| `platform-core/modules/datamodels/datamodelUam.py` | `User` Pydantic + `UserInDB` | Neues Feld `isPlatformAdmin: bool = False` + Validator |
|
||
| `platform-core/modules/datamodels/datamodelUam.py` | Docstring `isSysAdmin` | Neue Semantik („Infrastructure/System Operator") |
|
||
|
||
### Backend — Auth
|
||
|
||
| Datei | Was | Änderung |
|
||
| --------------------------------------------- | ----------------------------------------------------------------------------- | --------------------------------------------- |
|
||
| `platform-core/modules/auth/authentication.py` | `_hasSysAdminRole`, `requireSysAdminRole`, `_getRootMandateRoleIds` | **Löschen** (bzw. als deprecated Stub behalten für transitional Phase) |
|
||
| `platform-core/modules/auth/authentication.py` | `RequestContext.hasSysAdminRole` | **Löschen** |
|
||
| `platform-core/modules/auth/authentication.py` | `RequestContext.isPlatformAdmin` (neu) | Property |
|
||
| `platform-core/modules/auth/authentication.py` | `requirePlatformAdmin` (neu) | Dependency |
|
||
| `platform-core/modules/auth/__init__.py` | Exporte | `requirePlatformAdmin` exportieren |
|
||
|
||
### Backend — Bootstrap
|
||
|
||
| Datei | Was | Änderung |
|
||
| ----------------------------------------------------- | -------------------------------------------------- | ------------------------------------------------------------------ |
|
||
| `platform-core/modules/interfaces/interfaceBootstrap.py` | `_initSysAdminRole` + `_createSysAdminAccessRules` + `_ensureSysAdminAccessRules` | **Löschen** (inkl. aller damit verbundener AccessRules) |
|
||
| `platform-core/modules/interfaces/interfaceBootstrap.py` | `_ensureAdminUser`, `_ensureEventUser` | Setze `isPlatformAdmin=True` zusätzlich zu `isSysAdmin=True` |
|
||
| `platform-core/modules/interfaces/interfaceBootstrap.py` | Neu: `_migrateSysAdminRoleToPlatformAdminFlag()` | Einmalige Migration beim Boot; idempotent |
|
||
|
||
### Backend — Routen (alle Callsites von `hasSysAdminRole` / `requireSysAdminRole`)
|
||
|
||
| Datei | Callsites | Änderung |
|
||
| --------------------------------------------------------------------- | ------------------------------------------------------------------ | ---------------------------------------------------------------------------------- |
|
||
| `platform-core/modules/routes/routeDataUsers.py` | Z.106, 159, 236, 325, 445, 517, 571, 586, 919 | `hasSysAdminRole` → `isPlatformAdmin` |
|
||
| `platform-core/modules/routes/routeDataUsers.py` | `_syncSysAdminRole` (aus Sync-Phase 1) | **Löschen** (nicht mehr nötig) |
|
||
| `platform-core/modules/routes/routeDataUsers.py` | `update_user`: Flag-Sync-Logik | Vereinfachen (kein Rolle-Sync mehr) |
|
||
| `platform-core/modules/routes/routeDataMandates.py` | Z.104, 226, 258, 380, 437, 510, 1054 | `hasSysAdminRole`/`requireSysAdminRole` → `isPlatformAdmin`/`requirePlatformAdmin` |
|
||
| `platform-core/modules/routes/routeDataMandates.py` | Reverse-Sync (Z.916–944 aus Sync-Phase 1) | **Löschen** |
|
||
| `platform-core/modules/routes/routeAdminRbacRules.py` | 11 Stellen (Z.245, 365, 490, 537, 588, 668, 756, 839, 1020, 1079, 1140, 1204, 1360) | `hasSysAdminRole`/`requireSysAdminRole` → `isPlatformAdmin`/`requirePlatformAdmin` |
|
||
| `platform-core/modules/routes/routeAdminFeatures.py` | 22 Stellen | analog |
|
||
| `platform-core/modules/routes/routeAdminUserAccessOverview.py` | Z.78, 126, 220 | analog |
|
||
| `platform-core/modules/routes/routeAdminDatabaseHealth.py` | Z.44, 56, 68, 93 | bleibt `requireSysAdmin` (Infrastruktur) |
|
||
| `platform-core/modules/routes/routeAdminLogs.py` | Z.66, 107 | bleibt `requireSysAdmin` (Infrastruktur) |
|
||
| `platform-core/modules/routes/routeAdminDemoConfig.py` | Z.28, 44, 69 | `isPlatformAdmin` (Data-Change-Operation) |
|
||
| `platform-core/modules/routes/routeBilling.py` | Z.89, 145, 737, 1464, 1487 | Cross-Mandate-Views → `isPlatformAdmin`; Mandate-eigene → unverändert |
|
||
| `platform-core/modules/routes/routeI18n.py` | Z.829, 847, 860, 876, 914, 942 | i18n-Master/System → `requireSysAdmin`; Übersetzungs-Management → `isPlatformAdmin`|
|
||
| `platform-core/modules/routes/routeSubscription.py` | Z.49, 306, 488 | `isPlatformAdmin` |
|
||
| `platform-core/modules/routes/routeInvitations.py` | Z.189, 894 | `isPlatformAdmin` |
|
||
| `platform-core/modules/routes/routeSystem.py` | Z.481, 299, 306, 360, 370, 375, 385, 398, 408 | `isPlatformAdmin` für Navigations-Sichtbarkeit |
|
||
| `platform-core/modules/routes/routeAudit.py` | Z.134 | `isPlatformAdmin` (Cross-Mandate-Audit) |
|
||
| `platform-core/modules/routes/routeNotifications.py` | 518 | unverändert (`addRoleToUserMandate` betrifft keine sysadmin mehr) |
|
||
| `platform-core/modules/routes/routeDataFiles.py` | Z.11, 548, 850, 1044 | Global-Scope → `isSysAdmin` bleibt (Infra-Daten) |
|
||
| `platform-core/modules/routes/routeDataSources.py` | Z.10, 56 | analog |
|
||
| `platform-core/modules/routes/routeWorkflowDashboard.py` | 9 Stellen | Cross-Mandate-Übersicht → `isPlatformAdmin` |
|
||
| `platform-core/modules/features/trustee/routeFeatureTrustee.py` | Z.107, 141, 159, 161, 1814 | prüfen pro Callsite (meist `isPlatformAdmin`) |
|
||
| `platform-core/modules/features/teamsbot/routeFeatureTeamsbot.py` | 8 Stellen | System-Bot-Registrierung → `isSysAdmin` bleibt; User-Mgmt → `isPlatformAdmin` |
|
||
| `platform-core/modules/features/chatbot/routeFeatureChatbot.py` | Z.105 | `isPlatformAdmin` |
|
||
| `platform-core/modules/features/realEstate/routeFeatureRealEstate.py` | Z.119 | `isPlatformAdmin` |
|
||
| `platform-core/modules/routes/routeRealEstate.py` | Z.124 | `isPlatformAdmin` |
|
||
| `platform-core/modules/features/workspace/...` | (suchen) | pro Callsite prüfen |
|
||
| `platform-core/modules/interfaces/interfaceDbManagement.py` | Z.637–643 `_isSysAdmin` | Bleibt an Flag gebunden (RBAC-Bypass in Data-Management) |
|
||
|
||
### Backend — Services / Demo / Tests
|
||
|
||
| Datei | Was | Änderung |
|
||
| -------------------------------------------------------------- | ------------------------------------ | ---------------------------------------------------------- |
|
||
| `platform-core/modules/demoConfigs/investorDemo2026.py` | Z.188, 222 | `isPlatformAdmin=True` statt Rolle; `isSysAdmin` wie gehabt |
|
||
| `platform-core/modules/serviceCenter/services/serviceAgent/...` | Z.568 | Liest Flag — kein Change |
|
||
| `platform-core/tests/**` | alle `hasSysAdminRole`/`requireSysAdminRole` Mentions | Tests anpassen |
|
||
| `platform-core/tests/integration/rbac/test_sysadmin_sync.py` (geplant Phase 1) | **entfällt** bzw. wird zu `test_platform_admin_flag.py` | |
|
||
|
||
### Frontend
|
||
|
||
| Datei | Was | Änderung |
|
||
| --------------------------------------------------------------------- | ------------------------------------------------------ | --------------------------------------------------------------------------------- |
|
||
| `ui-nyla/src/types/mandate.ts` | User-Typ Z.143 | `isPlatformAdmin: boolean` ergänzen |
|
||
| `ui-nyla/src/api/userApi.ts` / `authApi.ts` | User-Schema | `isPlatformAdmin?: boolean` ergänzen |
|
||
| `ui-nyla/src/utils/userCache.ts` | User-Cache | `isPlatformAdmin` ergänzen |
|
||
| `ui-nyla/src/pages/admin/AdminUsersPage.tsx` | Edit-Formular | Zwei separate Toggles: "Systemadmin" und "Plattformadmin"; Tooltip mit Erklärung |
|
||
| `ui-nyla/src/pages/admin/AdminUserAccessOverviewPage.tsx` | User-Grid, Detail-Ansicht | Beide Flags anzeigen; Spalten + Filter |
|
||
| `ui-nyla/src/pages/billing/BillingAdmin.tsx` | Z.449, 505, 508, 669 | `isSysAdmin` → `isPlatformAdmin` für Cross-Mandate-View |
|
||
| `ui-nyla/src/pages/views/teamsbot/*.tsx` | Z.23, 24, 446, 329 | prüfen; meist `isPlatformAdmin` für Admin-UI, `isSysAdmin` für SystemBot-Config |
|
||
| `ui-nyla/src/hooks/useUsers.ts` | Z.35, 39, 74, 88, 92, 218, 222 | Beide Flags mitgeben |
|
||
|
||
## Entscheidungen
|
||
|
||
| Datum | Entscheidung | Begründung |
|
||
| ---------- | --------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------ |
|
||
| 2026-04-17 | `sysadmin`-Rolle im Root-Mandant wird eliminiert | Eine Rolle im Mandanten-Kontext, die Autorität über andere Mandanten gibt, ist architektonisch falsch |
|
||
| 2026-04-17 | Zwei orthogonale Flags: `isSysAdmin` (Infrastruktur) + `isPlatformAdmin` (Cross-Mandate-Governance) | Deckt die heute bekannten zwei Autoritäts-Achsen sauber ab; einzeln vergebbar, einzeln auditierbar |
|
||
| 2026-04-17 | Keine neue `PlatformRole`-Tabelle | Zwei Achsen → zwei Flags reichen; Tabelle wäre Over-Engineering; spätere Migration trivial möglich |
|
||
| 2026-04-17 | `isSysAdmin`-RBAC-Bypass bleibt erhalten | Bewusste Engine-Design-Entscheidung; Infrastruktur-Ops brauchen Unabhängigkeit vom RBAC-Schema |
|
||
| 2026-04-17 | `requirePlatformAdmin` prüft Flag **ohne** RBAC-Bypass | Plattform-Governance ist Admin-UI-Recht, nicht Daten-Bypass-Recht |
|
||
| 2026-04-17 | Migration idempotent beim Boot, nicht als separates Migrations-Skript | Konsistent mit bisherigem Bootstrap-Stil; kein Down-Time-Risiko |
|
||
| 2026-04-17 | `isPlatformAdmin` gibt **keinen** impliziten Daten-Zugriff auf Mandante | Separation of concerns: Governance ≠ Daten |
|
||
| 2026-04-17 | Phase-1-Sync-Code (Flag↔Rolle) wird zurückgerollt | Wird durch finale Lösung obsolet; aktuell committete Helper `_syncSysAdminRole` etc. entfernen |
|
||
|
||
## Umsetzungs-Checkliste
|
||
|
||
### Phase A — Datamodel + Auth
|
||
|
||
- [ ] `User.isPlatformAdmin` Feld in Pydantic + DB-Schema
|
||
- [ ] `_normalizePlatformAdmin` Validator
|
||
- [ ] `RequestContext.isPlatformAdmin` Property
|
||
- [ ] `requirePlatformAdmin` Dependency + Audit
|
||
- [ ] Export in `modules/auth/__init__.py`
|
||
- [ ] Docstring-Update `isSysAdmin` Field: neue enge Semantik
|
||
|
||
### Phase B — Migration + Bootstrap
|
||
|
||
- [ ] `_migrateSysAdminRoleToPlatformAdminFlag()` in `interfaceBootstrap.py`
|
||
- [ ] Aufruf nach `_initSysAdminRole` (vor dessen Entfernung)
|
||
- [ ] Test: vorhandene sysadmin-Rolle + User mit UserMandateRole → Flag=True
|
||
- [ ] `_ensureAdminUser`, `_ensureEventUser`: `isPlatformAdmin=True` setzen
|
||
- [ ] `investorDemo2026.py`: analog
|
||
|
||
### Phase C — Routen umstellen
|
||
|
||
- [ ] Alle Callsites laut Referenz-Tabelle bearbeiten
|
||
- [ ] Nach Umstellung: Code-Such nach `hasSysAdminRole` / `requireSysAdminRole`
|
||
→ muss 0 Treffer ergeben (ausser deprecated-Stub)
|
||
- [ ] Navigation (`routeSystem.py`): Menü-Sichtbarkeit korrekt anpassen
|
||
|
||
### Phase D — Phase-1-Sync-Code zurückrollen
|
||
|
||
- [ ] `routeDataUsers.py:_syncSysAdminRole` entfernen
|
||
- [ ] `routeDataUsers.py:update_user` Sync-Aufruf entfernen
|
||
- [ ] `routeDataUsers.py:create_user` Sync-Aufruf entfernen
|
||
- [ ] `routeDataMandates.py:update_user_roles_in_mandate` Reverse-Sync entfernen
|
||
|
||
### Phase E — sysadmin-Rolle entfernen
|
||
|
||
- [ ] `_initSysAdminRole` entfernen
|
||
- [ ] `_createSysAdminAccessRules`, `_ensureSysAdminAccessRules` entfernen
|
||
- [ ] Migration: sysadmin-Role + AccessRules + UserMandateRole-Einträge löschen
|
||
- [ ] `_hasSysAdminRole`, `requireSysAdminRole` entfernen (oder als deprecated
|
||
für 1 Release behalten)
|
||
|
||
### Phase F — Frontend
|
||
|
||
- [ ] Typen + userCache erweitern
|
||
- [ ] `AdminUsersPage`: zwei separate Toggles + Tooltip
|
||
- [ ] `AdminUserAccessOverviewPage`: Flag-Spalten + Filter
|
||
- [ ] Callsites Frontend (BillingAdmin, Teamsbot-Pages etc.)
|
||
|
||
### Querschnitt
|
||
|
||
- [ ] **RBAC / Permissions:** Keine neuen AccessRules. Alte sysadmin-AccessRules gelöscht.
|
||
- [ ] **Neutralisierung:** User-Neutralisierung setzt beide Flags auf False.
|
||
- [ ] **Navigation / Routing:** Admin-Menü-Sichtbarkeit via `isPlatformAdmin`.
|
||
- [ ] **Billing-Impact:** keiner.
|
||
- [ ] **Tests:** Unit-Tests für `requirePlatformAdmin`, Integrations-Tests für
|
||
Migration.
|
||
|
||
## Akzeptanzkriterien
|
||
|
||
| # | Kriterium (Given-When-Then) | Prio |
|
||
| -- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------ |
|
||
| 1 | Given User X mit `isSysAdmin=true`, `isPlatformAdmin=false`, When X ruft `GET /api/admin/mandates` auf, Then 403 | must |
|
||
| 2 | Given User X mit `isSysAdmin=false`, `isPlatformAdmin=true`, When X ruft `GET /api/admin/database-health` auf, Then 403 | must |
|
||
| 3 | Given User X mit `isPlatformAdmin=true`, When X ruft `GET /api/admin/mandates` auf, Then 200 mit allen Mandanten | must |
|
||
| 4 | Given DB-Migration: User Y hatte `sysadmin`-Rolle in Root-Mandant, When Gateway bootet, Then Y hat `isPlatformAdmin=true`; sysadmin-Rolle/AccessRules sind weg | must |
|
||
| 5 | Given Admin entzieht User X den `isPlatformAdmin`-Flag, When X im nächsten Request `GET /api/admin/rbac-rules` aufruft, Then 403 (ohne Token-Refresh, nächster Request prüft live DB) | must |
|
||
| 6 | Given Admin entzieht User X den `isSysAdmin`-Flag, When X im nächsten Request `GET /api/admin/logs` aufruft, Then 403 | must |
|
||
| 7 | Given `Admin > USER`-Formular, When Admin bearbeitet User, Then zwei separate Toggles „Systemadmin" und „Plattformadmin" sind sichtbar + dokumentiert | must |
|
||
| 8 | Given User X versucht sich selbst `isSysAdmin` oder `isPlatformAdmin` zu verändern, Then 403 (Self-Protection für beide Flags) | must |
|
||
| 9 | Given `_hasSysAdminRole` / `requireSysAdminRole` werden importiert, When Codebase gescannt wird, Then 0 Treffer (oder nur deprecated-Stub mit Logger-Warning) | should |
|
||
| 10 | Given `sysadmin`-Rolle existiert noch in DB, When Migration läuft, Then Rolle + zugehörige UserMandateRole + AccessRules werden gelöscht; Flag der betroffenen User ist konsistent gesetzt | must |
|
||
| 11 | Given „Operations-Engineer"-User mit `isSysAdmin=true`, `isPlatformAdmin=false`, When er die Admin-UI öffnet, Then sieht er System-Menüpunkte (Logs, DB-Health), aber keine Mandate-/User-Verwaltung | should |
|
||
| 12 | Given „Customer-Success-Admin" mit `isSysAdmin=false`, `isPlatformAdmin=true`, When er die Admin-UI öffnet, Then sieht er User-/Mandate-Verwaltung, aber keine Logs | should |
|
||
|
||
## Testplan
|
||
|
||
| ID | AC | Art | Automatisiert | Repo-Pfad | Status |
|
||
| --- | ---- | ------------------------ | ------------- | ------------------------------------------------------------------ | ------- |
|
||
| T1 | 1 | api-integration | ja | `platform-core/tests/integration/rbac/test_platform_admin_flag.py` | done |
|
||
| T2 | 2 | api-integration | ja | dito | done |
|
||
| T3 | 3 | api-integration | ja | dito | done |
|
||
| T4 | 4,10 | migration-unit | ja | `platform-core/tests/unit/rbac/test_sysadmin_migration.py` | done |
|
||
| T5 | 5,6 | api-integration | ja | `platform-core/tests/integration/rbac/test_platform_admin_flag.py` | done |
|
||
| T6 | 7 | frontend (Playwright) | optional | `ui-nyla/e2e/admin-users-flags.spec.ts` (neu) | open |
|
||
| T7 | 8 | api-unit | ja | `platform-core/tests/integration/rbac/test_platform_admin_flag.py` | done |
|
||
| T8 | 9 | codebase-scan | ja (CI-Check) | `scripts/check_no_sysadmin_role.py` (CI-Gate) | done |
|
||
| T9 | 11,12| manuelle UI-Regression | manuell | Siehe Security-Regression-Manual | open |
|
||
|
||
**Security-Regression-Manual (T9)**
|
||
|
||
1. User A: `isSysAdmin=true`, `isPlatformAdmin=false`. Login.
|
||
- `Admin > Logs` → ok.
|
||
- `Admin > DB-Health` → ok.
|
||
- `Admin > Mandate` → 403/nicht sichtbar im Menü.
|
||
- `Admin > User` → 403/nicht sichtbar.
|
||
2. User B: `isSysAdmin=false`, `isPlatformAdmin=true`. Login.
|
||
- `Admin > Mandate` → ok.
|
||
- `Admin > User` → ok.
|
||
- `Admin > User Access Overview` → ok.
|
||
- `Admin > Logs` → 403.
|
||
- `Admin > DB-Health` → 403.
|
||
3. User C: beide `false`. Login.
|
||
- Keine Admin-Menüpunkte sichtbar.
|
||
4. Flag-Entzug im laufenden Betrieb: Admin entzieht B das `isPlatformAdmin`-Flag.
|
||
B's nächster Request auf `Admin > Mandate` → 403 (ohne Re-Login).
|
||
5. sysadmin-Rolle-Check: kein User hat mehr eine sysadmin-Rolle in DB
|
||
(`SELECT COUNT(*) FROM "Role" WHERE "roleLabel" = 'sysadmin'` = 0).
|
||
|
||
## Links
|
||
|
||
- RBAC-Referenz: `wiki/b-reference/platform/rbac.md`
|
||
- Auth-Modul: `platform-core/modules/auth/authentication.py`
|
||
- Ausgangspunkt Testing PORTA 2026-04-17 (Issue 4: Security-Bug)
|
||
|
||
## Abschluss
|
||
|
||
- [x] `wiki/b-reference/platform/rbac.md`: Abschnitt „Platform-Governance-Autoritaet" ergaenzt
|
||
- [ ] `wiki/TOPICS.md`: Topic „Authority-Modell: System/Platform/Mandate" anlegen
|
||
- [x] Dieses Dokument nach Abschluss → `wiki/c-work/4-done/` verschoben
|
||
- [x] CI-Gate `platform-core/scripts/check_no_sysadmin_role.py` (Acceptance T8 / AC#9)
|
||
|
||
### Umsetzungs-Status
|
||
|
||
- Phase A (Datamodel + Auth) : done
|
||
- Phase B (Migration + Bootstrap) : done
|
||
- Phase C (Routen umstellen) : done — 0 Treffer fuer hasSysAdminRole/requireSysAdminRole/_hasSysAdminRole
|
||
- Phase D (Phase-1-Sync zurueckgerollt) : done
|
||
- Phase E (sysadmin-Rolle entfernt) : done — Stubs entfernt, Migration entfernt Rolle/AccessRules/Memberships
|
||
- Phase F (Frontend) : done — Typen + AdminUserAccessOverview + AdminMandates + BillingAdmin + useUsers
|
||
- Tests + CI-Gate : done — T1-T5, T7, T8 automatisiert (13 grün); T6 (Playwright) + T9 (manuell) optional/offen
|
||
|