All checks were successful
Deploy Development / deploy (push) Successful in 48s
Test Suite / pytest-backend (push) Successful in 45s
Test Suite / lint-backend (push) Successful in 1s
Test Suite / build-frontend (push) Successful in 15s
Test Suite / k6 /health Baseline (push) Successful in 34s
Test Suite / playwright-tests (push) Successful in 1m35s
Document cross-app architecture patterns, Mitai alignment, and family entitlement standards. Documentation only; no runtime changes. Co-authored-by: Cursor <cursoragent@cursor.com>
171 lines
7.8 KiB
Markdown
171 lines
7.8 KiB
Markdown
# Access Layer & Tenant – Designprinzipien (Extraktion)
|
||
|
||
**Status:** Analyse / Arbeitspapier
|
||
**Stand:** 2026-07-04
|
||
**Geltungsbereich:** Modul „Access Layer & Tenant Governance“ — keine Shinkan-Gesamtarchitektur, keine Kampfsport-Domänenlogik
|
||
|
||
**Serie:** Designprinzipien für Produktfamilie · Shinkan Dokument 1 von 15
|
||
**Mitai-Vergleich:** kein direktes Gegenstück (Mitai ist profil-zentriert, kein Vereins-Mandant)
|
||
|
||
**Kernkomponenten:**
|
||
|
||
| Bereich | Pfade |
|
||
|---------|-------|
|
||
| TenantContext | `backend/tenant_context.py` |
|
||
| Governance-Helfer | `backend/club_tenancy.py` |
|
||
| Listenfilter SQL | `library_content_visibility_sql()` in `tenant_context.py` |
|
||
| Endpoint-Audit | `.claude/docs/working/ACCESS_LAYER_ENDPOINT_AUDIT.md` |
|
||
| Cursor-Regel | `.cursor/rules/access-layer.mdc` |
|
||
| Heuristik-Check | `backend/scripts/check_access_layer_hints.py` |
|
||
| Normative Spec | `.claude/docs/technical/ACCESS_LAYER_AND_GOVERNANCE_PLAN.md` |
|
||
|
||
---
|
||
|
||
## Modul
|
||
|
||
**Access Layer & Tenant Governance**
|
||
|
||
Zentraler Querschnitt für Mandanten-Kontext (`club_id`), Sichtbarkeit (`private`/`club`/`official`) und einheitliche Les-/Schreibregeln für Bibliotheksartefakte (Übungen, Medien, Rahmenprogramme, Vorlagen, Progressionsgraphen).
|
||
|
||
---
|
||
|
||
## Fachliche Verantwortung
|
||
|
||
1. **TenantContext pro HTTP-Request** — Auflösung aus Session + Header `X-Active-Club-Id` + Profilfeld `active_club_id`.
|
||
2. **Datenisolierung** — `club_id` als Grenze für vereinsgeteilte Inhalte; Cross-Verein ausgeschlossen.
|
||
3. **Einheitliche Sichtbarkeits-Semantik** — gleiche Enums und Prüflogik über alle Bibliotheksmodule.
|
||
4. **Governance-Transitionen** — Regeln beim Wechsel `private` → `club` → `official` und beim Löschen.
|
||
5. **Listenfilter** — SQL-Baustein statt „SELECT *“ in jedem Router.
|
||
|
||
### Administrierte Konfigurationen
|
||
|
||
| Konfiguration | Speicherort | Inhalt |
|
||
|---------------|-------------|--------|
|
||
| Aktiver Verein | `profiles.active_club_id` | Persistierter UI-Kontext |
|
||
| Request-Override | Header `X-Active-Club-Id` | Client-seitiger Mandantenwechsel |
|
||
| Sichtbarkeit | Spalte `visibility` je Objekt | `private`, `club`, `official` |
|
||
| Vereinszuordnung | Spalte `club_id` | Pflicht bei `club`-Inhalten |
|
||
|
||
### Bewusst nicht hardcodiert
|
||
|
||
- Welche Objekte welchen Verein haben (Daten)
|
||
- Individuelle Freigabeentscheidungen (Workflow)
|
||
|
||
### Hardcodiert (Code)
|
||
|
||
- Enum-Werte und Leseregeln in `club_tenancy.py` / `library_content_visibility_sql`
|
||
- Plattform-Admin-Ausnahmen (`is_platform_admin`, `is_superadmin`)
|
||
- Rollencodes für Schreib-/Löschregeln (`club_admin`, `trainer`, …)
|
||
|
||
---
|
||
|
||
## Designprinzipien
|
||
|
||
### 1. Ein Mandant pro Request (`TenantContext`)
|
||
|
||
| | |
|
||
|---|---|
|
||
| **Prinzip** | `Depends(get_tenant_context)` liefert `profile_id`, `global_role`, `effective_club_id`, Mitgliedschaften — einmal pro Request. |
|
||
| **Begründung** | Kein verteiltes „Rates“ aus Headers; konsistente Filter in allen Routern. |
|
||
| **Quelle** | `tenant_context.py`; `ACCESS_LAYER_AND_GOVERNANCE_PLAN.md` §2 |
|
||
| **Tragfähigkeit** | **hoch** |
|
||
| **Einschränkung** | Nicht alle Endpoints migriert; Audit-Tabelle zeigt Restbestand mit nur `require_auth`. |
|
||
|
||
### 2. `club_id` als Datenisolierungsgrenze
|
||
|
||
| | |
|
||
|---|---|
|
||
| **Prinzip** | Vereinsgeteilte Inhalte sind nur für aktive Mitglieder des Objekt-`club_id` lesbar — nie Cross-Verein. |
|
||
| **Begründung** | Mandantenfähigkeit für Vereinsplattform; Compliance bei geteilten Trainingsinhalten. |
|
||
| **Quelle** | `library_content_visibility_sql()`; Tests `test_access_layer*.py` |
|
||
| **Tragfähigkeit** | **hoch** |
|
||
| **Einschränkung** | `division`-Verschärfung noch nicht durchgängig; reserviert im Plan. |
|
||
|
||
### 3. Einheitliche Visibility-Semantik
|
||
|
||
| | |
|
||
|---|---|
|
||
| **Prinzip** | `private` \| `club` \| `official` mit gleicher Bedeutung für Übungen, Medien, Rahmen, Module, Graphen. |
|
||
| **Begründung** | Nutzer und Trainer verstehen ein Freigabemodell; UI-Feld „Freigabelevel“ durchgängig. |
|
||
| **Quelle** | `club_tenancy.py`; `FACHLICHE_NUTZERFUNKTIONEN.md` §4.7 |
|
||
| **Tragfähigkeit** | **hoch** |
|
||
| **Einschränkung** | `community`-Stufe nur dokumentiert, nicht implementiert. |
|
||
|
||
### 4. Zentraler SQL-Filter für Bibliothekslisten
|
||
|
||
| | |
|
||
|---|---|
|
||
| **Prinzip** | Listen nutzen `library_content_visibility_sql(alias, profile_id, role, effective_club_id)` — nicht handgeschriebene WHERE-Kopien. |
|
||
| **Begründung** | Drift-Vermeidung; ein Fix gilt für alle Kataloge. |
|
||
| **Quelle** | `tenant_context.py`; Router `exercises.py`, `training_framework_programs.py`, … |
|
||
| **Tragfähigkeit** | **hoch** |
|
||
| **Einschränkung** | Einzelne Legacy-Queries können noch abweichen. |
|
||
|
||
### 5. Governance-Transitionen explizit prüfen
|
||
|
||
| | |
|
||
|---|---|
|
||
| **Prinzip** | Wechsel von `visibility`/`club_id` über `assert_library_content_governance_transition` + `assert_valid_governance_visibility`. |
|
||
| **Begründung** | „Privat → Verein teilen“ und „official herabstufen“ sind sicherheitsrelevante Aktionen. |
|
||
| **Quelle** | `club_tenancy.py` |
|
||
| **Tragfähigkeit** | **hoch** |
|
||
| **Einschränkung** | Nicht jedes Modul ruft Transition-Helper bei PATCH auf. |
|
||
|
||
### 6. Löschregeln nach Visibility-Stufe
|
||
|
||
| | |
|
||
|---|---|
|
||
| **Prinzip** | `assert_library_content_deletable`: privat → Ersteller/Vereinsadmin-Kontext; club → Vereinsadmin; official → Plattform-Admin. |
|
||
| **Begründung** | Schutz vor versehentlichem Löschen fremder oder offizieller Inhalte. |
|
||
| **Quelle** | `club_tenancy.py` |
|
||
| **Tragfähigkeit** | **hoch** |
|
||
| **Einschränkung** | Medien-Lifecycle hat zusätzliche Stufen (Papierkorb) — Schnittmenge beachten. |
|
||
|
||
### 7. Aktiver Verein: Header + Profil synchron
|
||
|
||
| | |
|
||
|---|---|
|
||
| **Prinzip** | Frontend sendet `X-Active-Club-Id`; Backend validiert gegen Mitgliedschaft; Profil speichert `active_club_id`. |
|
||
| **Begründung** | Einheitlicher Mandanten-Kontext über API und UI. |
|
||
| **Quelle** | `frontend/src/api/client.js` (`mergeActiveClubHeader`); `profiles` Router |
|
||
| **Tragfähigkeit** | **hoch** |
|
||
| **Einschränkung** | Onboarding-Nutzer ohne Verein: eingeschränkter Nav-Modus. |
|
||
|
||
### 8. Plattform-Admin als Audit-Pfad, nicht als Bypass
|
||
|
||
| | |
|
||
|---|---|
|
||
| **Prinzip** | Plattform-Admins sehen fremde `club`-Inhalte nur mit expliziter Regel (Mitgliedschaft oder Audit-Ausnahme in SQL). |
|
||
| **Begründung** | Superuser-Zugriff ohne Mandanten-Leak in normalen Trainer-Flows. |
|
||
| **Quelle** | `library_content_visibility_sql` — `club_ok_plat`-Zweig |
|
||
| **Tragfähigkeit** | **mittel** |
|
||
| **Einschränkung** | Feinheiten zwischen `admin` und `superadmin` (z. B. `official`, Legal Hold) separat geregelt. |
|
||
|
||
### 9. Endpoint-Audit als lebendes Inventar
|
||
|
||
| | |
|
||
|---|---|
|
||
| **Prinzip** | Jeder sicherheitsrelevante Endpoint-Eintrag in `ACCESS_LAYER_ENDPOINT_AUDIT.md`; PR-Checkliste verlangt Update. |
|
||
| **Begründung** | Sichtbarkeit des Migrationsstands; kein stilles Ausweichen auf `require_auth` allein. |
|
||
| **Quelle** | `.claude/docs/working/ACCESS_LAYER_ENDPOINT_AUDIT.md`; `check_access_layer_hints.py` |
|
||
| **Tragfähigkeit** | **hoch** (prozessual) |
|
||
| **Einschränkung** | CI-Strict-Modus optional, nicht überall aktiv. |
|
||
|
||
---
|
||
|
||
## Nicht übernehmen
|
||
|
||
1. **Endpoints nur mit `require_auth`** bei tenant-sensitiven Daten — führt zu IDOR und fehlenden Listenfiltern.
|
||
2. **Visibility-Logik pro Router duplizieren** — historische Drift zwischen Übungen und Planung.
|
||
3. **`division` vor stabiler Vereins-Isolation** — Reihenfolge im Plan: erst Stufe C, dann D.
|
||
4. **Community-Freigabe ohne additive Felder** — würde `club`-Isolation brechen.
|
||
5. **Client-seitige Mandantenfilter ohne Server-Enforcement** — UI-Hiding reicht nicht.
|
||
|
||
---
|
||
|
||
## Verwandte Dokumentation
|
||
|
||
- [ACCESS_LAYER_AND_GOVERNANCE_PLAN.md](../../../.claude/docs/technical/ACCESS_LAYER_AND_GOVERNANCE_PLAN.md)
|
||
- [MULTI_TENANCY_RBAC_ARCHITECTURE.md](../../../.claude/docs/technical/MULTI_TENANCY_RBAC_ARCHITECTURE.md)
|
||
- [DESIGN_PRINCIPLES_INDEX.md](./DESIGN_PRINCIPLES_INDEX.md)
|