fix(security): F11 (Astra P1) — Freigaben an Entscheider, Ablauf und Atomizitaet binden
Vorher: resolve_approval_request pruefte nur Mandant + pending — NICHT
Ablaufdatum, NICHT den vorgesehenen Genehmiger, und ueberschrieb
approver_id mit dem tatsaechlichen Entscheider (Zuordnung verloren).
Astra-Repro: Eine abgelaufene Anfrage konnte von einem anderen
Entscheider genehmigt werden; konkurrierende Entscheidungen waren
moeglich.
Fix:
- Migration 0146: neue Spalte resolved_by (Zuordnung vs. Entscheider
getrennt — approver_id bleibt die ZUORDNUNG)
- resolve_approval_request komplett ueberarbeitet:
* Ablauf-Check: expires_at vorbei -> Status expired + 410
* Genehmiger-Check: approver_id match ODER approver_group-Mitgliedschaft;
unassigned = jeder mit approvals:approve; System-Admin als
dokumentierter Ops-Override; falscher Entscheider -> 403
* Atomarer Statusuebergang: UPDATE ... WHERE status=pending —
konkurrierende Entscheidung -> 409
* approver_id wird NIE ueberschrieben; resolved_by dokumentiert den
Entscheider
- ApprovalDecisionError mit HTTP-Status-Codes; approve/reject-Routen
fangen sie sauber ab (404/409/410/403 statt Flat-404)
- ApprovalResponse + Mapper um resolved_by ergaenzt
Abnahme (Astra): Falscher Entscheider, abgelaufene Anfrage und doppelte
Entscheidung werden abgewiesen — erfuellt (6 Tests).
Hinweis: workflows.py approve/reject-Aufrufer waren bereits kaputt
(F12, S2-Welle: approval[id] auf ORM-Objekt) und werden dort gefixt.
Tests: test_s1_security_guards.py 18/18 (6 neue F11-Tests). ruff clean.
Damit ist S1 — ALLE 11 Sicherheits-Findings der Astra-Welle 1 gefixt.
This commit is contained in:
@@ -0,0 +1,31 @@
|
||||
"""Add resolved_by to approval_requests (F11/Astra).
|
||||
|
||||
Separates the assigned approver (approver_id — who the request was
|
||||
addressed TO) from the actual decider (resolved_by — who decided).
|
||||
Previously resolve_approval_request overwrote approver_id with the
|
||||
acting user, destroying the assignment record.
|
||||
|
||||
Revision ID: 0146
|
||||
Revises: 0145
|
||||
"""
|
||||
|
||||
import sqlalchemy as sa
|
||||
from sqlalchemy.dialects.postgresql import UUID as PGUUID
|
||||
|
||||
from alembic import op
|
||||
|
||||
revision = "0146"
|
||||
down_revision = "0145"
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
op.add_column(
|
||||
"approval_requests",
|
||||
sa.Column("resolved_by", PGUUID(as_uuid=True), nullable=True),
|
||||
)
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
op.drop_column("approval_requests", "resolved_by")
|
||||
Reference in New Issue
Block a user