fix(v2): scope and redact execution events
This commit is contained in:
@@ -244,6 +244,10 @@ def create_app(settings: Settings) -> FastAPI:
|
||||
raise Problem(403, "csrf_failed", "CSRF validation failed.")
|
||||
return user, scopes, cookie_auth
|
||||
|
||||
def enforce_scope(scopes: set[str], required: str) -> None:
|
||||
if "*" not in scopes and required not in scopes:
|
||||
raise Problem(403, "insufficient_scope", "Required scope is missing.")
|
||||
|
||||
async def audit(
|
||||
db: AsyncSession,
|
||||
request: Request,
|
||||
@@ -767,8 +771,9 @@ def create_app(settings: Settings) -> FastAPI:
|
||||
async def enqueue_execution(
|
||||
job_id: str,
|
||||
db: Annotated[AsyncSession, Depends(session)],
|
||||
_: Annotated[tuple[User, set[str], bool], Depends(require)],
|
||||
identity: Annotated[tuple[User, set[str], bool], Depends(require)],
|
||||
) -> dict[str, Any]:
|
||||
enforce_scope(identity[1], "execution:control")
|
||||
try:
|
||||
execution = await enqueue(db, job_id)
|
||||
except EnqueueError as error:
|
||||
@@ -793,8 +798,9 @@ def create_app(settings: Settings) -> FastAPI:
|
||||
async def get_execution(
|
||||
execution_id: str,
|
||||
db: Annotated[AsyncSession, Depends(session)],
|
||||
_: Annotated[tuple[User, set[str], bool], Depends(actor)],
|
||||
identity: Annotated[tuple[User, set[str], bool], Depends(actor)],
|
||||
) -> dict[str, object]:
|
||||
enforce_scope(identity[1], "execution:read")
|
||||
execution = await db.get(Execution, execution_id)
|
||||
if execution is None:
|
||||
raise Problem(404, "resource_not_found", "Execution was not found.")
|
||||
@@ -804,8 +810,9 @@ def create_app(settings: Settings) -> FastAPI:
|
||||
async def cancel_execution(
|
||||
execution_id: str,
|
||||
db: Annotated[AsyncSession, Depends(session)],
|
||||
_: Annotated[tuple[User, set[str], bool], Depends(require)],
|
||||
identity: Annotated[tuple[User, set[str], bool], Depends(require)],
|
||||
) -> dict[str, object]:
|
||||
enforce_scope(identity[1], "execution:control")
|
||||
execution = await request_cancellation(db, execution_id)
|
||||
if execution is None:
|
||||
raise Problem(409, "cancellation_not_allowed", "Execution cannot be cancelled.")
|
||||
@@ -815,8 +822,9 @@ def create_app(settings: Settings) -> FastAPI:
|
||||
async def retry_execution(
|
||||
execution_id: str,
|
||||
db: Annotated[AsyncSession, Depends(session)],
|
||||
_: Annotated[tuple[User, set[str], bool], Depends(require)],
|
||||
identity: Annotated[tuple[User, set[str], bool], Depends(require)],
|
||||
) -> dict[str, object]:
|
||||
enforce_scope(identity[1], "execution:control")
|
||||
try:
|
||||
execution = await retry(db, execution_id)
|
||||
except EnqueueError as error:
|
||||
@@ -829,8 +837,9 @@ def create_app(settings: Settings) -> FastAPI:
|
||||
async def execution_events(
|
||||
execution_id: str,
|
||||
db: Annotated[AsyncSession, Depends(session)],
|
||||
_: Annotated[tuple[User, set[str], bool], Depends(actor)],
|
||||
identity: Annotated[tuple[User, set[str], bool], Depends(actor)],
|
||||
) -> StreamingResponse:
|
||||
enforce_scope(identity[1], "execution:read")
|
||||
execution = await db.get(Execution, execution_id)
|
||||
if execution is None:
|
||||
raise Problem(404, "resource_not_found", "Execution was not found.")
|
||||
|
||||
@@ -169,9 +169,25 @@ async def retry(db: AsyncSession, execution_id: str) -> Execution | None:
|
||||
return execution
|
||||
|
||||
|
||||
def _redact_progress(value: object) -> object:
|
||||
"""Apply common redaction plus execution-specific path redaction recursively."""
|
||||
if isinstance(value, Mapping):
|
||||
safe: dict[str, object] = {}
|
||||
for key, item in value.items():
|
||||
normalized = str(key).lower()
|
||||
if any(marker in normalized for marker in ("path", "secret", "token", "password")):
|
||||
safe[str(key)] = "[REDACTED]"
|
||||
else:
|
||||
safe[str(key)] = _redact_progress(item)
|
||||
return safe
|
||||
if isinstance(value, list):
|
||||
return [_redact_progress(item) for item in value]
|
||||
return redact(value)
|
||||
|
||||
|
||||
def public_event(execution: Execution) -> dict[str, object]:
|
||||
"""Return redacted progress suitable for polling or SSE."""
|
||||
progress = redact(execution.progress)
|
||||
progress = _redact_progress(execution.progress)
|
||||
return {
|
||||
"id": execution.id,
|
||||
"state": execution.state,
|
||||
|
||||
Reference in New Issue
Block a user