security: harden Campaign delivery effects and reconciliation
This commit is contained in:
@@ -27,10 +27,14 @@ from govoplan_campaign.backend.db.models import (
|
||||
JobValidationStatus,
|
||||
)
|
||||
from govoplan_campaign.backend.campaign.loader import load_campaign_json, validate_against_schema
|
||||
from govoplan_campaign.backend.campaign.mail_profile_boundary import (
|
||||
assert_campaign_uses_mail_profile_reference,
|
||||
campaign_mail_profile_id,
|
||||
)
|
||||
from govoplan_campaign.backend.campaign.validation import validate_campaign_config
|
||||
from govoplan_campaign.backend.messages.builder import build_campaign_messages
|
||||
from govoplan_campaign.backend.messages.models import MessageDraft
|
||||
from govoplan_campaign.backend.sending.execution import create_execution_snapshot
|
||||
from govoplan_campaign.backend.sending.execution import create_execution_snapshot, profile_transport_revisions
|
||||
from govoplan_campaign.backend.campaign.models import CampaignConfig
|
||||
from govoplan_campaign.backend.integrations import files_integration, mail_integration
|
||||
from govoplan_campaign.backend.path_security import assert_server_safe_campaign_paths
|
||||
@@ -61,15 +65,26 @@ def load_campaign_config_from_json(
|
||||
owner_user_id: str | None = None,
|
||||
owner_group_id: str | None = None,
|
||||
) -> CampaignConfig:
|
||||
materialized = mail_integration().materialize_campaign_mail_profile_config(
|
||||
session,
|
||||
tenant_id=tenant_id,
|
||||
raw_json=raw_json,
|
||||
campaign_id=campaign_id,
|
||||
owner_user_id=owner_user_id,
|
||||
owner_group_id=owner_group_id,
|
||||
)
|
||||
validate_against_schema(materialized)
|
||||
# Validate the persisted Campaign-to-Mail contract before asking Mail for a
|
||||
# non-secret capability summary. Campaign never receives resolved transport
|
||||
# settings, account identities, or credentials.
|
||||
assert_campaign_uses_mail_profile_reference(raw_json)
|
||||
validate_against_schema(raw_json)
|
||||
materialized = copy.deepcopy(raw_json)
|
||||
profile_id = campaign_mail_profile_id(raw_json)
|
||||
if profile_id:
|
||||
summary = mail_integration().campaign_profile_delivery_summary(
|
||||
session,
|
||||
tenant_id=tenant_id,
|
||||
campaign_id=campaign_id,
|
||||
profile_id=profile_id,
|
||||
owner_user_id=owner_user_id,
|
||||
owner_group_id=owner_group_id,
|
||||
)
|
||||
materialized.setdefault("server", {})["profile_capabilities"] = {
|
||||
"smtp_available": bool(summary.get("smtp_available")),
|
||||
"imap_available": bool(summary.get("imap_available")),
|
||||
}
|
||||
return CampaignConfig.model_validate(materialized)
|
||||
|
||||
|
||||
@@ -485,13 +500,19 @@ def build_campaign_version(
|
||||
[job for job, _message in job_build_pairs],
|
||||
stage="built",
|
||||
)
|
||||
runtime_smtp = managed_config.server.runtime_smtp_config()
|
||||
if not runtime_smtp:
|
||||
raise CampaignPersistenceError("Campaign has no SMTP configuration; an execution snapshot cannot be created")
|
||||
if not managed_config.server.profile_capabilities.smtp_available:
|
||||
raise CampaignPersistenceError("The selected Mail profile has no SMTP configuration; an execution snapshot cannot be created")
|
||||
profile_id = campaign_mail_profile_id(version.raw_json if isinstance(version.raw_json, dict) else {})
|
||||
if profile_id is None:
|
||||
raise CampaignPersistenceError("Select an authorized Mail profile before building campaign messages")
|
||||
revisions = profile_transport_revisions(session, version)
|
||||
if not revisions["smtp"]:
|
||||
raise CampaignPersistenceError("The selected Mail profile has no SMTP transport revision")
|
||||
execution_snapshot, execution_snapshot_hash = create_execution_snapshot(
|
||||
version,
|
||||
smtp=runtime_smtp,
|
||||
imap=managed_config.server.runtime_imap_config(),
|
||||
mail_profile_id=profile_id,
|
||||
smtp_transport_revision=revisions["smtp"],
|
||||
imap_transport_revision=revisions["imap"],
|
||||
delivery=managed_config.delivery,
|
||||
jobs=[job for job, _message in job_build_pairs],
|
||||
build_summary=report_json,
|
||||
|
||||
Reference in New Issue
Block a user