Zum Inhalt springen

Autorisierung

Status: Entwurf · Spec-Kandidat: ja

Pflicht

Rechte werden serverseitig durchgesetzt (FR-012, AC-009). UI-Beschränkungen sind reine Komfortfunktionen, nie Sicherheitsmechanismus.

Modell: RBAC mit ACL-Override

Hybrider Ansatz, vollständig im mdm-Schema modelliert (siehe DDL).

Bausteine

TabelleZweck
AA_APP_USEROIDC-gemappte Benutzer (Issuer + sub-Claim).
AA_SERVICE_ACCOUNTMaschinen-Identitäten für M2M-Aufrufe.
AA_PRINCIPAL_GROUP / AA_PRINCIPAL_GROUP_MEMBEROptionale Gruppierung (z. B. aus IdP-Claims).
AA_ROLEBenannte Rolle, frei oder System (is_system=true).
AA_PERMISSIONDeklarative Aktion (permission_action) auf Scope (acl_scope).
AA_ROLE_PERMISSIONRBAC-Zuweisung: Rolle → Permission, optional auf konkreten Scope (DDM_ENTITY_TYPE / DDM_ENTITY / DDM_RELATION_TYPE).
AA_PRINCIPAL_ROLEWelche Identität bekommt welche Rolle, optional Scope-eingeschränkt und befristet (valid_from/valid_to).
AA_ACL_ENTRYFeingranulare Allow/Deny-Regel pro Principal + Scope, optional auf einzelnes Attribut.

Wertebereiche

  • principal_type: user, service_account, role, group
  • permission_action: read, create, update, archive, restore, soft_delete, hard_delete, relate, unrelate, export, manage_metadata, manage_permissions, read_audit
  • acl_scope: global, tenant, entity_type, entity, relation_type
  • permission_effect: allow, deny

Auflösungsreihenfolge

Für jeden Request berechnet der Service-Layer für (principal, action, target):

  1. Sammle alle AA_PRINCIPAL_ROLE-Einträge des Principals und seiner Gruppen, gültig zu now(), nicht soft-gelöscht.
  2. Daraus alle AA_ROLE_PERMISSION-Einträge → Kandidaten-Permissions mit Scope.
  3. Sammle direkte AA_ACL_ENTRY-Einträge für denselben Principal/Gruppen, passend zu Action und Scope.
  4. deny gewinnt immer — sobald irgendein anwendbarer Eintrag (RBAC oder ACL, beliebige Spezifität) effect='deny' ist, ist die Entscheidung deny. Spezifität spielt für die Deny-Beurteilung keine Rolle.
  5. Andernfalls: gibt es mindestens einen anwendbaren allow-Eintrag → Entscheidung allow. Spezifität wird nur unter allow-Einträgen verwendet, falls weitere Auswertung nötig ist (z. B. Attribut-Maskierung über attribute_key): spezifischer wins (Scope entity > entity_type > tenant > global; relation_type parallel zu entity_type).
  6. Default ist deny, wenn keine Regel matcht.

Begründung: deny-wins-always ist sicherer und einfacher zu reasonen als „spezifischeres allow überschreibt allgemeineres deny”. Ein expliziter Deny-Eintrag (z. B. „User darf keine Gehaltsfelder lesen”) darf nicht durch eine spätere, zufällig spezifischere Allow-Regel ausgehebelt werden.

Materialisierung in mdm.v_effective_permission — Service ruft View auf, schreibt Auflösungslogik nicht selbst nach.

System-Rollen (Seed)

Rollen sind tenant-scoped (AA_ROLE.tenant_id NOT NULL). Das Default-Set lebt als Template in Migration/Seed-Code. Beim Anlegen eines AA_TENANT instanziiert mdm.fn_provision_tenant_defaults(tenant_id) das Set einmalig in den neuen Tenant — jede Default-Rolle ist im Tenant mit is_system=true markiert und nicht löschbar. Tenant-eigene Custom-Rollen (is_system=false) sind frei. Detail: AA_ROLE, Tenant-Onboarding.

RollePermissions (Kurzfassung)
administratorvolle Konfiguration im Tenant, manage_permissions, hard_delete
ownerTenant-Owner — alle fachlichen Rechte (read/create/update/archive/relate/unrelate/export) im Tenant; kein Plattform-hard_delete, kein manage_permissions
stewardDatenpflege: read, update, archive, restore, künftig Match/Merge (OP-29) und Approval (OP-35)
metadata_adminmanage_metadata (Pflege DDM_ENTITY_TYPE, DDM_ENTITY_TYPE_ATTRIBUTE, DDM_RELATION_TYPE, DDM_ENUM_SET)
editorread, create, update, archive, relate, unrelate auf zugewiesenen Entitätstypen
readerread auf zugewiesenen Entitätstypen
auditorread_audit auf AA_AUDIT_LOG und DDM_ENTITY_VERSION
exporterexport (Massen-/Bulk-Export, FR-103)

Konkrete AA_PERMISSION-Zeilen (Aktion × Scope) sind global (tenant-übergreifend) und werden als System-Seed angelegt. Die tenant-spezifische Bindung erfolgt über AA_ROLE_PERMISSION.scope_tenant_id beim Provisioning.

Berechtigungsebenen

Mindestens auf folgenden Ebenen wird durchgesetzt:

  • Global – z. B. „darf Hard Delete”
  • Tenant – Mandant-Scope (FR-203). Beispiel: User X hat editor nur in Tenant acme.
  • Entitätstyp – z. B. „darf customer lesen, aber nicht service
  • Datensatz – über AA_ACL_ENTRY mit scope='entity' (z. B. eigene vs. fremde Datensätze)
  • Relationstyp – z. B. „darf customer-has-service anlegen”
  • Attribut – über AA_ACL_ENTRY.attribute_key (z. B. salary nur für HR sichtbar)

Durchsetzung

  • Prüfung zentral im Service-Layer vor jeder Mutation und Lese-Auflösung. Status-Codes folgen einem einheitlichen Schema gegen Information Leakage:
    • 404 Not Found — Default für read/update/delete/relate auf einer Ressource, deren Existenz der Aufrufer nicht sehen darf. Identische Antwort wie für tatsächlich nicht existente IDs.
    • 403 Forbidden — nur, wenn der Aufrufer die Ressource nachweislich sehen darf (read allowed) aber die konkret angeforderte Aktion nicht (update/delete/relate denied).
    • 401 Unauthorized — Token fehlt oder ist ungültig (vor jeder Authz-Prüfung).
  • Damit verraten 404er nicht, ob eine ID existiert und nur unsichtbar ist oder gar nicht da ist.
  • Datenbank-Sicherheitsnetz: Trigger validieren Konsistenz von Scope-Feldern und Principal-Referenzen (siehe Trigger). Kein Bypass durch fehlerhaften Service-Code.
  • Audit ist Pflicht für permission_grant, permission_revoke, role_assign, role_unassign, login, access_denied (siehe AA_AUDIT_LOG).
  • Abgelehnte Zugriffe (sowohl 404-aus-Authz als auch 403) werden mit audit_action='access_denied' in AA_AUDIT_LOG geschrieben — interne Wahrheit bleibt, nur die HTTP-Antwort wird vereinheitlicht.

Konsistenzregeln (DB-erzwungen)

  • AA_ROLE_PERMISSION.scope_*-Felder müssen zu AA_PERMISSION.scope passen (Trigger validate_role_permission_scope).
  • AA_PRINCIPAL_ROLE.scope und Scope-Felder werden per Check-Constraint konsistent gehalten.
  • AA_ACL_ENTRY.scope und Scope-Felder ebenso. attribute_key nur bei scope ∈ {DDM_ENTITY_TYPE, DDM_ENTITY} erlaubt.
  • (principal_type, principal_id) muss auf einen aktiven Eintrag in AA_APP_USER / AA_SERVICE_ACCOUNT / AA_PRINCIPAL_GROUP verweisen (Trigger validate_principal_reference).
  • Eindeutigkeit (role_id, permission_id, scope_*) und (principal_type, principal_id, role_id, scope, scope_*) jeweils nur unter aktiven (nicht soft-gelöschten) Einträgen.

OIDC-Mapping

  • Token-Validierung (siehe Authentifizierung) liefert Issuer + sub.
  • Service-Layer löst zu AA_APP_USER.id auf ((issuer, external_id)-Index). Unbekannter User → optionale Just-in-Time-Provisionierung oder 403.
  • IdP-Gruppen-Claims werden auf AA_PRINCIPAL_GROUP.key gemappt (Konfiguration offen). Mitgliedschaft via AA_PRINCIPAL_GROUP_MEMBER mit member_principal_type='user'.
  • Ergebnis steht für die Dauer des Requests im Service-Kontext und wird an AA_AUDIT_LOG.actor_principal_id durchgereicht.

Mandantentrennung (FR-203)

Mandanten-Säule ist Bestandteil des Datenmodells (siehe AA_TENANT). In V1 läuft alles auf Default-Tenant public; bei Aktivierung weiterer Tenants:

  • Service-Layer löst Tenant aus Auth-Kontext auf und setzt ihn als impliziten Filter auf jede Mutation und Lese-Abfrage.
  • Permission-Auflösung berücksichtigt acl_scope='tenant' (scope_tenant_id).
  • Cross-Tenant-Zugriffe sind verboten und durch DB-Trigger gegen Beziehungen abgesichert.
  • Optional zusätzlich: Postgres Row-Level-Security pro tenant_id (siehe Offene Punkte).

Auflösung des Tenant-Kontexts

Reihenfolge je Request:

  1. Primärquelle: OIDC-Token-Claim tenant_id (UUID oder AA_TENANT.key). Wird beim Token-Issuing vom IdP in den Token geprägt. Single-Tenant-Nutzer sind damit fest gebunden.
  2. Optionaler Override: HTTP-Header X-Tenant-Id. Erlaubt Multi-Tenant-Nutzern (Plattform-Admin, Konzern-Mitarbeiter mit Rollen in mehreren Tenants) den Wechsel ohne Re-Auth.
  3. Validierung: Ist X-Tenant-Id gesetzt, prüft der Service, dass der Principal in genau diesem Tenant mindestens eine aktive AA_PRINCIPAL_ROLE-Zuweisung hat (scope_tenant_id = Header-Wert oder global). Schlägt das fehl: 403 Forbidden, AA_AUDIT_LOG.action='access_denied' mit Reason tenant_mismatch.
  4. Setzen: Der aufgelöste tenant_id wird auf jede SQL-Operation des Requests durchgereicht (WHERE tenant_id = $ctx_tenant) und für Audit/Outbox als tenant_id mitgeschrieben.
  5. Default-Tenant: Solange nur public existiert (V1), entfallen Token-Claim und Header — Service setzt implizit public.

Subdomain-basiertes Routing (acme.api.example.com) ist explizit nicht vorgesehen (Operations-Aufwand DNS/TLS, keine fachliche Anforderung).

Caching von Permissions (OP-24, gesetzt 2026-04-29)

Effektive Permissions werden nicht bei jeder Lese-Anfrage neu aus v_effective_permission berechnet. V1 nutzt einen Redis-Cache mit Session-Scope:

  • Key-Form: perm:<tenant_id>:<principal_type>:<principal_id>:<session_id>
  • Wert: vorberechnete Permission-Map für den Login-Scope (Aktion → Scope-Liste mit Effekt).
  • TTL: Session-Lebensdauer, max 24 h. Lazy-Refresh nach 5 min Inaktivität.
  • Invalidierung: Service-Layer publiziert nach Mutationen an AA_ROLE_PERMISSION, AA_PRINCIPAL_ROLE, AA_ACL_ENTRY, AA_PRINCIPAL_GROUP_MEMBER ein Redis-Pub/Sub-Event auf perm:invalidate:<tenant>:<principal> (Wildcard * bei Rollen-/Permission-Mutationen, die viele Principals betreffen). Konsumenten leeren betroffene Keys.

DB-materialisierter effective_permission_cache (OP-49) bleibt zurückgestellt, bis die Redis-Variante nicht mehr ausreicht.

Postgres-RLS — bewusst nicht aktiviert (OP-25, gesetzt 2026-04-29)

V1 nutzt kein RLS. Begründung:

  • ACL-Modell mit Group-Vererbung und tenant-scoped Rollen ist in RLS-Policies schwer testbar.
  • Permission-Resolution würde sowohl im Service-Layer-Cache als auch in jeder Policy doppelt formuliert — Drift-Risiko.

Verteidigungstiefe wird stattdessen durch (a) konsequenten Tenant-Filter im Service, (b) DB-Tenant-Validierungs-Trigger gegen cross-tenant-Verletzungen und (c) Audit-Alarmierung gewährleistet. Re-Bewertung, falls externe Compliance-Audits Bedenken äußern.

Offen

  • Konkretes Mapping IdP-Claims → AA_PRINCIPAL_GROUP (Detail).

Verwandte Dokumente