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
| Tabelle | Zweck |
|---|---|
AA_APP_USER | OIDC-gemappte Benutzer (Issuer + sub-Claim). |
AA_SERVICE_ACCOUNT | Maschinen-Identitäten für M2M-Aufrufe. |
AA_PRINCIPAL_GROUP / AA_PRINCIPAL_GROUP_MEMBER | Optionale Gruppierung (z. B. aus IdP-Claims). |
AA_ROLE | Benannte Rolle, frei oder System (is_system=true). |
AA_PERMISSION | Deklarative Aktion (permission_action) auf Scope (acl_scope). |
AA_ROLE_PERMISSION | RBAC-Zuweisung: Rolle → Permission, optional auf konkreten Scope (DDM_ENTITY_TYPE / DDM_ENTITY / DDM_RELATION_TYPE). |
AA_PRINCIPAL_ROLE | Welche Identität bekommt welche Rolle, optional Scope-eingeschränkt und befristet (valid_from/valid_to). |
AA_ACL_ENTRY | Feingranulare Allow/Deny-Regel pro Principal + Scope, optional auf einzelnes Attribut. |
Wertebereiche
principal_type:user,service_account,role,grouppermission_action:read,create,update,archive,restore,soft_delete,hard_delete,relate,unrelate,export,manage_metadata,manage_permissions,read_auditacl_scope:global,tenant,entity_type,entity,relation_typepermission_effect:allow,deny
Auflösungsreihenfolge
Für jeden Request berechnet der Service-Layer für (principal, action, target):
- Sammle alle
AA_PRINCIPAL_ROLE-Einträge des Principals und seiner Gruppen, gültig zunow(), nicht soft-gelöscht. - Daraus alle
AA_ROLE_PERMISSION-Einträge → Kandidaten-Permissions mit Scope. - Sammle direkte
AA_ACL_ENTRY-Einträge für denselben Principal/Gruppen, passend zu Action und Scope. denygewinnt immer — sobald irgendein anwendbarer Eintrag (RBAC oder ACL, beliebige Spezifität)effect='deny'ist, ist die Entscheidungdeny. Spezifität spielt für die Deny-Beurteilung keine Rolle.- Andernfalls: gibt es mindestens einen anwendbaren
allow-Eintrag → Entscheidungallow. Spezifität wird nur unterallow-Einträgen verwendet, falls weitere Auswertung nötig ist (z. B. Attribut-Maskierung überattribute_key): spezifischer wins (Scopeentity>entity_type>tenant>global;relation_typeparallel zuentity_type). - 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.
| Rolle | Permissions (Kurzfassung) |
|---|---|
administrator | volle Konfiguration im Tenant, manage_permissions, hard_delete |
owner | Tenant-Owner — alle fachlichen Rechte (read/create/update/archive/relate/unrelate/export) im Tenant; kein Plattform-hard_delete, kein manage_permissions |
steward | Datenpflege: read, update, archive, restore, künftig Match/Merge (OP-29) und Approval (OP-35) |
metadata_admin | manage_metadata (Pflege DDM_ENTITY_TYPE, DDM_ENTITY_TYPE_ATTRIBUTE, DDM_RELATION_TYPE, DDM_ENUM_SET) |
editor | read, create, update, archive, relate, unrelate auf zugewiesenen Entitätstypen |
reader | read auf zugewiesenen Entitätstypen |
auditor | read_audit auf AA_AUDIT_LOG und DDM_ENTITY_VERSION |
exporter | export (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
editornur in Tenantacme. - Entitätstyp – z. B. „darf
customerlesen, aber nichtservice” - Datensatz – über
AA_ACL_ENTRYmitscope='entity'(z. B. eigene vs. fremde Datensätze) - Relationstyp – z. B. „darf
customer-has-serviceanlegen” - Attribut – über
AA_ACL_ENTRY.attribute_key(z. B.salarynur 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ürread/update/delete/relateauf 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 (readallowed) aber die konkret angeforderte Aktion nicht (update/delete/relatedenied).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'inAA_AUDIT_LOGgeschrieben — interne Wahrheit bleibt, nur die HTTP-Antwort wird vereinheitlicht.
Konsistenzregeln (DB-erzwungen)
AA_ROLE_PERMISSION.scope_*-Felder müssen zuAA_PERMISSION.scopepassen (Triggervalidate_role_permission_scope).AA_PRINCIPAL_ROLE.scopeund Scope-Felder werden per Check-Constraint konsistent gehalten.AA_ACL_ENTRY.scopeund Scope-Felder ebenso.attribute_keynur beiscope∈ {DDM_ENTITY_TYPE,DDM_ENTITY} erlaubt.(principal_type, principal_id)muss auf einen aktiven Eintrag inAA_APP_USER/AA_SERVICE_ACCOUNT/AA_PRINCIPAL_GROUPverweisen (Triggervalidate_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.idauf ((issuer, external_id)-Index). Unbekannter User → optionale Just-in-Time-Provisionierung oder403. - IdP-Gruppen-Claims werden auf
AA_PRINCIPAL_GROUP.keygemappt (Konfiguration offen). Mitgliedschaft viaAA_PRINCIPAL_GROUP_MEMBERmitmember_principal_type='user'. - Ergebnis steht für die Dauer des Requests im Service-Kontext und wird an
AA_AUDIT_LOG.actor_principal_iddurchgereicht.
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:
- Primärquelle: OIDC-Token-Claim
tenant_id(UUID oderAA_TENANT.key). Wird beim Token-Issuing vom IdP in den Token geprägt. Single-Tenant-Nutzer sind damit fest gebunden. - 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. - Validierung: Ist
X-Tenant-Idgesetzt, prüft der Service, dass der Principal in genau diesem Tenant mindestens eine aktiveAA_PRINCIPAL_ROLE-Zuweisung hat (scope_tenant_id= Header-Wert oder global). Schlägt das fehl:403 Forbidden,AA_AUDIT_LOG.action='access_denied'mit Reasontenant_mismatch. - Setzen: Der aufgelöste
tenant_idwird auf jede SQL-Operation des Requests durchgereicht (WHERE tenant_id = $ctx_tenant) und für Audit/Outbox alstenant_idmitgeschrieben. - Default-Tenant: Solange nur
publicexistiert (V1), entfallen Token-Claim und Header — Service setzt implizitpublic.
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_MEMBERein Redis-Pub/Sub-Event aufperm: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).