Block local file paths at Campaign server boundaries

This commit is contained in:
2026-07-21 03:17:11 +02:00
parent 78f52a36d4
commit 1d291377c0
9 changed files with 576 additions and 19 deletions

View File

@@ -107,6 +107,7 @@ from govoplan_campaign.backend.persistence.campaigns import (
validate_campaign_version,
)
from govoplan_campaign.backend.integrations import files_integration
from govoplan_campaign.backend.path_security import CampaignPathSecurityError, assert_server_safe_campaign_paths
from govoplan_campaign.backend.campaign.loader import load_campaign_json
from govoplan_campaign.backend.attachments.resolver import resolve_campaign_attachments
from govoplan_core.security.time import utc_now
@@ -373,6 +374,8 @@ def _campaign_version_detail_response(
return CampaignVersionDetailResponse.model_validate(version)
except LockedCampaignVersionError as exc:
raise HTTPException(status_code=status.HTTP_409_CONFLICT, detail=str(exc)) from exc
except CampaignPathSecurityError as exc:
raise HTTPException(status_code=status.HTTP_422_UNPROCESSABLE_CONTENT, detail=str(exc)) from exc
except CampaignPersistenceError as exc:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(exc)) from exc
except Exception as exc:
@@ -1487,6 +1490,8 @@ def fork_version_for_edit(
)
except LockedCampaignVersionError as exc:
raise HTTPException(status_code=status.HTTP_409_CONFLICT, detail=str(exc)) from exc
except CampaignPathSecurityError as exc:
raise HTTPException(status_code=status.HTTP_422_UNPROCESSABLE_CONTENT, detail=str(exc)) from exc
except CampaignPersistenceError as exc:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(exc)) from exc
@@ -3203,6 +3208,7 @@ def _attachment_preview_for_version(
include_unlinked_candidates: bool,
) -> CampaignAttachmentPreviewResponse:
files = files_integration()
assert_server_safe_campaign_paths(raw, managed_files_available=files.available)
with files.prepared_campaign_snapshot(
session,
tenant_id=principal.tenant_id,
@@ -3352,15 +3358,18 @@ def preview_campaign_attachments(
raw = payload.campaign_json if isinstance(payload.campaign_json, dict) else version.raw_json
raw = raw if isinstance(raw, dict) else {}
_require_mail_profile_use_if_needed(principal, raw)
return _attachment_preview_for_version(
session,
principal,
campaign=campaign,
version=version,
raw=raw,
include_unmatched=payload.include_unmatched,
include_unlinked_candidates=payload.include_unlinked_candidates,
)
try:
return _attachment_preview_for_version(
session,
principal,
campaign=campaign,
version=version,
raw=raw,
include_unmatched=payload.include_unmatched,
include_unlinked_candidates=payload.include_unlinked_candidates,
)
except CampaignPathSecurityError as exc:
raise HTTPException(status_code=status.HTTP_422_UNPROCESSABLE_CONTENT, detail=str(exc)) from exc
@router.post("/{campaign_id}/versions/{version_id}/attachments/link-matches", response_model=CampaignAttachmentLinkMatchesResponse)