# 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)