feat: govern legacy archive encryption
This commit is contained in:
@@ -9,6 +9,10 @@ from pydantic import BaseModel, ConfigDict
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from govoplan_campaign.backend.db.models import Campaign, CampaignJob, CampaignVersion, JobValidationStatus
|
||||
from govoplan_campaign.backend.archive_encryption import (
|
||||
CampaignArchiveEncryptionError,
|
||||
assert_archive_encryption_allowed,
|
||||
)
|
||||
from govoplan_campaign.backend.campaign.models import (
|
||||
DeliveryChannelPolicy,
|
||||
DeliveryConfig,
|
||||
@@ -22,8 +26,8 @@ from govoplan_campaign.backend.campaign.mail_profile_boundary import (
|
||||
from govoplan_campaign.backend.integrations import MailProfileError, files_integration, mail_integration
|
||||
from govoplan_campaign.backend.path_security import CampaignPathSecurityError, assert_server_safe_campaign_paths
|
||||
|
||||
SNAPSHOT_VERSION = "8"
|
||||
SUPPORTED_SNAPSHOT_VERSIONS = {"6", "7", SNAPSHOT_VERSION}
|
||||
SNAPSHOT_VERSION = "9"
|
||||
SUPPORTED_SNAPSHOT_VERSIONS = {"6", "7", "8", SNAPSHOT_VERSION}
|
||||
|
||||
|
||||
class ExecutionSnapshotError(RuntimeError):
|
||||
@@ -57,6 +61,7 @@ class ExecutionSnapshot(BaseModel):
|
||||
queueable_job_count: int = 0
|
||||
job_manifest_sha256: str | None = None
|
||||
effective_policy_sha256: str | None = None
|
||||
archive_encryption: dict[str, Any] | None = None
|
||||
smtp_transport_revision: str | None = None
|
||||
imap_transport_revision: str | None = None
|
||||
uses_mail: bool = True
|
||||
@@ -263,6 +268,7 @@ def create_execution_snapshot(
|
||||
imap_credential_id: str | None = None,
|
||||
jobs: Iterable[CampaignJob] = (),
|
||||
build_summary: dict[str, Any] | None = None,
|
||||
archive_encryption: dict[str, Any] | None = None,
|
||||
) -> tuple[dict[str, Any], str]:
|
||||
raw_json = version.raw_json if isinstance(version.raw_json, dict) else {}
|
||||
job_list = list(jobs)
|
||||
@@ -311,6 +317,7 @@ def create_execution_snapshot(
|
||||
delivery,
|
||||
snapshot_version=SNAPSHOT_VERSION,
|
||||
),
|
||||
archive_encryption=archive_encryption,
|
||||
smtp_transport_revision=smtp_transport_revision,
|
||||
imap_transport_revision=imap_transport_revision,
|
||||
uses_mail=uses_mail,
|
||||
@@ -355,6 +362,39 @@ def _assert_snapshot_matches_persisted_inputs(
|
||||
"Revalidate and rebuild the campaign before delivery."
|
||||
)
|
||||
|
||||
campaign = session.get(Campaign, version.campaign_id)
|
||||
if campaign is None:
|
||||
raise ExecutionSnapshotError("Execution snapshot Campaign no longer exists")
|
||||
try:
|
||||
current_archive_policy = assert_archive_encryption_allowed(
|
||||
session,
|
||||
campaign,
|
||||
raw_json,
|
||||
)
|
||||
except CampaignArchiveEncryptionError as exc:
|
||||
raise ExecutionSnapshotError(str(exc)) from exc
|
||||
archive_snapshot = snapshot.archive_encryption
|
||||
configured_archives = (
|
||||
((raw_json.get("attachments") or {}).get("zip") or {}).get("archives")
|
||||
if isinstance(raw_json.get("attachments"), dict)
|
||||
else None
|
||||
)
|
||||
if configured_archives and not isinstance(archive_snapshot, dict):
|
||||
raise ExecutionSnapshotError(
|
||||
"Execution snapshot has no governed archive-encryption evidence; rebuild before delivery."
|
||||
)
|
||||
if isinstance(archive_snapshot, dict):
|
||||
frozen_policy = archive_snapshot.get("policy")
|
||||
frozen_hash = (
|
||||
frozen_policy.get("policy_hash")
|
||||
if isinstance(frozen_policy, dict)
|
||||
else None
|
||||
)
|
||||
if frozen_hash != current_archive_policy.policy_hash:
|
||||
raise ExecutionSnapshotError(
|
||||
"The effective archive-encryption policy changed after build. Revalidate and rebuild before delivery."
|
||||
)
|
||||
|
||||
if effect_job is not None:
|
||||
if effect_job.campaign_version_id != version.id:
|
||||
raise ExecutionSnapshotError("Campaign job does not belong to the snapshotted version")
|
||||
@@ -494,6 +534,12 @@ def ensure_execution_snapshot(
|
||||
delivery=config.delivery,
|
||||
jobs=jobs,
|
||||
build_summary=version.build_summary if isinstance(version.build_summary, dict) else {},
|
||||
archive_encryption=(
|
||||
version.build_summary.get("archive_encryption")
|
||||
if isinstance(version.build_summary, dict)
|
||||
and isinstance(version.build_summary.get("archive_encryption"), dict)
|
||||
else None
|
||||
),
|
||||
)
|
||||
version.execution_snapshot = payload
|
||||
version.execution_snapshot_hash = digest
|
||||
|
||||
Reference in New Issue
Block a user