shinkan-jinkendo/.claude/docs/technical/ACCESS_LAYER_AND_GOVERNANCE_PLAN.md
Lars e0ecfe927f
Some checks failed
Deploy Development / deploy (push) Successful in 35s
Test Suite / lint-backend (push) Successful in 1s
Test Suite / build-frontend (push) Successful in 6s
Test Suite / playwright-tests (push) Failing after 37s
feat: update access layer governance and visibility checks
- Enhanced ACCESS_LAYER_AND_GOVERNANCE_PLAN.md with additional details on heuristic checks and testing procedures for cross-tenant scenarios.
- Updated club_tenancy.py to recommend using `library_content_visible_to_profile` for exercise visibility checks.
- Refactored multiple routers to utilize `library_content_visible_to_profile`, improving consistency in access control across exercises and training planning.
- Bumped application version to 0.8.28 and updated changelog to reflect these changes.
2026-05-05 22:11:05 +02:00

8.5 KiB
Raw Blame History

Einheitliche Zugriffsschicht & Governance Umsetzungsplan

Status: verbindliche Umsetzungsreihenfolge (nachgelagert zum Zielbild in MULTI_TENANCY_RBAC_ARCHITECTURE.md)
Stand: 2026-05-05
Zweck: Drift vermeiden eine nachvollziehbare Schicht für Mandanten-Kontext, Sichtbarkeit und Berechtigungen, auf die alle inhaltsbezogenen Module konsistent aufbauen.

Explizit zurückgestellt (wie vereinbart): kostenpflichtiges Vereins-Membership / Tier-Limits pro Verein (club_subscriptions o. Ä.) kommt nach stabiler Zugriffs- und Datenisolationsbasis.

Separates späteres Konzept: durch Vereinsadmins definierbare Rollen mit feingranularen Rechten (Capability-Bundles in DB/UI); dieser Plan bereitet nur die Capability-Idee und Erweiterungspunkte vor.


1. Leitprinzipien

  1. Ein Mandant für Datenisolierung: club_id ist die Grenze für „vereinsgeteilte“ Inhalte. Nur Ausnahmen: explizit plattformweite/offizielle Objekte und private Objekte des Erstellers.
  2. Ein Kontext pro HTTP-Request: Mitgliedschaft und gewählter aktiver Verein werden einmal aufgelöst und validiert; Folgelogik nutzt nur noch dieses Objekt (kein verteiltes erneutes „Rates“ aus Headers).
  3. Eine fachliche Sichtbarkeits-Semantik über alle Bibliotheks- und Planungsartefakte (Übungen, Vorlagen, Rahmenprogramme, …): gleiche Enums, gleiche Leseprüfung, angepasste Listenfilter.
  4. Sparte optional verschärfen: division_id auf Objekten und später auf Rollenzuweisung ausgewertet ohne Vereinsgrenze zu sprengen.
  5. Community später additive: neue Freigabeebene oder Flags ergänzen die bestehende Semantik, ohne club-Isolation zu ersetzen.

2. Architektur der Zugriffsschicht (Schichtenmodell)

Schicht Verantwortung
Authentifizierung Session / Token → profile_id, globale role (require_auth, bestehend).
TenantContext (neu, zentral) Aus profile_id + Header X-Active-Club-Id (optional) + DB profiles.active_club_id: effektive effective_club_id nur wenn Mitgliedschaft aktiv existiert; sonch klaren Fehler (403/400 nach Konvention). Optional: Cache der Mitgliedschaftszeilen/Rollen einmal pro Request.
Governance / Objekt contra Account Für jedes geschützte Objekt: visibility, club_id, division_id (nullable), created_byeine zentrale Entscheidung can_read / can_write (interne Module, keine Copy-Paste-Logik pro Router).
Funktions-/Feature-Rechte „Darf dieser Nutzer im Verein X Trainergruppe anlegen?“ → Capability-Checks (can_manage_club_org, can_plan_in_club, …); später durch dynamische Rollen → gleiche Capability-Namen.

Ziel: Router werden dünn: laden Daten nur noch durch Hilfen, die TenantContext und Governance bereits berücksichtigen oder explizit prüfen.


3. Scope- und Sichtbarkeitsmodell (einheitlich)

Ausgangslage im Code: private | club | official (siehe club_tenancy).

Zielbild (phasenweise DB/API-Anpassung):

Wert Lesende Regel (kurz)
private Nur created_by (+ Plattform-Admin nach Policy); keine Vereinsliste ohne Ownership.
club Nur aktive Mitglieder des Objekt-club_id; Cross-Verein nie.
division (optional neue Stufe oder club + Pflicht division_id) Nur Mitglieder, die dieser Sparte zugeordnet sind (Regeln gesondert spezifizieren: Mitgliedschaft vs. Gruppe vs. Rolle division_lead).
official Plattform-weit lesbar (Superadmin publiziert/pflegt); weiterhin strikt von club-Daten getrennt.
community (reserviert) Noch nicht implementieren; Design nur additive Felder/Enum-Einträge dokumentieren, wenn erste Stories starten.

Trainer-Flow: „Privat anlegen, dann im Verein teilen“ = Transition von private zu club (oder Kopie + neue Visibility Produktentscheidung; technisch muss club_id gesetzt und Mitgliedschaft geprüft werden).


4. Roadmap (verbindliche Reihenfolge)

Stufe A Foundations & Audit

  • Router-Inventar: Liste aller Endpoints mit Zugriff auf club_id, visibility, organisationalem Bezug oder Listenfiltern (Excel/Markdown-Tabelle im Repo oder unter .claude/docs/working/).
  • Definition of Done je Endpoint: „Default deny“ für tenant-sensitive Listen wenn Kontext fehlt/ungültig.
  • TenantContext-Spezifikation festhalten (Feldnamen, HTTP-Fehlercodes, Superadmin-Ausnahmen).

Stufe B TenantContext (Backend zentral)

  • FastAPI-Dependency z.B. get_tenant_context(session, x_active_club_id) → Objekt mit profile_id, global_role, effective_club_id, club_memberships (optional gekürzt).
  • Alle neuen und bestehenden sicherheitsrelevanten Änderungen nur über diese Dependency oder darauf aufbauende Helfer.
  • Abgleich mit Frontend: X-Active-Club-Id und Persistenz active_club_id (bereits vorhanden) als einzige variabler Vereinskontext-Quellen.

Stufe C Governance vereinheitlichen

  • Eine interne API-Stilebene (auch wenn mehrere Python-Funktionen):
    content_readable(...), content_writable(...) oder zweischichtig „Governance“ vs „Org-Rechte“.
  • Module angleichen: Übungen, Trainingsplanung, Rahmenprogramme, Vorlagen gleiche Regeln für club/official/private; division dort einführen, wo fachlich nötig (einheitliche Filter-Chips in UI).
  • Regressionstests: zwei Vereine, zwei Nutzer, kein Kreuzzugriff auf club-Objekte; Superadmin/Global-Path weiterhin getrennt testen.

Stufe D Sparten-Durchsetzung

  • division_lead und optional division_id auf Mitgliedschaft/Rolle auswerten bei Schreib-/Lesevorgängen für Objekte mit division_id.
  • Dokumentieren: Was gilt für Objekte mit division_id=NULL innerhalb eines Vereins (vereinsweit vs. nur „ohne Sparte“).

Stufe E Capabilities dokumentieren (ohne UI für Custom Roles)

  • Markdown-Tabelle Capability-Fingerprint: Kennungen wie content.share_club, planning.edit_unit, org.manage_members, … mit Zuordnung zu den heutigen festen Vereinsrollen.
  • Ziel: später club_custom_roles nur noch andere Kombination derselben Kennungen keine zweite Philosophie.

Stufe F Community (eigenes Epic)

  • Konzept: Freigabe additiv (Flag oder Enum), Moderation, Sichtbarkeit „öffentlich außerhalb meines Vereins“ ohne bestehende club-Isolation zu brechen.

Zurückgestellt Vereinsabo / Limits

  • Wiederöffnen wenn ACCESS_LAYER Stufe C/D stabil; dann Enforcement vor ausgewählten Writes an einen Billing-Stripe binden.

5. Drift vermeiden (Arbeitsdisziplin)

Mechanismus Inhalt
Cursor / IDE Projektregel .cursor/rules/access-layer.mdc (Router); Agents sollen nicht auf „nur require_auth“ ausweichen.
Heuristik-Check python backend/scripts/check_access_layer_hints.py; CI optional mit ACCESS_LAYER_STRICT=1. Optional danach: cd backend && pytest tests/ (nach pip install -r requirements-dev.txt).
PR-Checkliste Neuer/changed Endpoint: TenantContext verwendet? Governance für Listen + Detail? Tests für zweiten Verein?
Single Source of Truth Sichtbarkeitsregeln nur in Zugriffsmodul(en), nicht in Routers dupliziert.
Änderungen am Enum Nur zusammen mit Migration + Kurzbeschreibung in diesem Dokument (Datum/Changelog-Zeile).
Beziehung zu MULTI_TENANCY-Doc Phasen 14 dort größtenteils umgesetzt; Gap-Analyse §3 im alten Dokument historisch lesen fachlicher Zielabgleich bleibt dort, operative Reihenfolge hier.

6. Nächste konkrete Artefakte (nach diesem Plan)

  1. TenantContext-Spezifikation (ein Abschnitt in diesem Dokument oder Kurz-ADR): Request-Lebenszyklus, Fehlerbilder, Superadmin.
  2. Endpoint-Audit-Tabelle (Working-Dokument, bei jedem Merge pflegen bis Stufe C abgeschlossen).
  3. Testplan „Cross-Tenant“ (manuell oder pytest): Minimalsetup zweier Vereine — erste rein-funktionale Tests unter backend/tests/test_access_layer.py (ohne DB); Integration folgt.

Audit-Tabelle (fortlaufend): .claude/docs/working/ACCESS_LAYER_ENDPOINT_AUDIT.md


7. Referenzen

  • .claude/docs/technical/MULTI_TENANCY_RBAC_ARCHITECTURE.md übergeordnetes Zielbild & Begriffe.
  • backend/club_tenancy.py bestehende Bausteine (assert_club_member, exercise_visible_to_profile, …); Ziel ist Deren schrittweise Zusammenführung unter die neue Zugriffsschicht ohne Big-Bang.

Letzte Aktualisierung: 2026-05-05