security: harden Campaign delivery effects and reconciliation
This commit is contained in:
@@ -8,6 +8,7 @@ from typing import Iterable
|
||||
|
||||
from pydantic import BaseModel, ConfigDict, Field
|
||||
|
||||
from .addressing import effective_address_lists
|
||||
from .field_values import ignored_entry_field_overrides
|
||||
from .models import AttachmentConfig, CampaignConfig, EntryConfig, FieldType, SourceType, ZipArchiveConfig, ZipPasswordMode, ZipPasswordScope, ZipRuleMode
|
||||
from ..attachments.resolver import resolve_campaign_attachments
|
||||
@@ -338,52 +339,72 @@ def _global_value_issues(config: CampaignConfig, declared_names: set[str]) -> li
|
||||
|
||||
def _delivery_issues(config: CampaignConfig) -> list[SemanticIssue]:
|
||||
issues: list[SemanticIssue] = []
|
||||
runtime_imap = config.server.runtime_imap_config()
|
||||
if config.delivery.imap_append_sent.enabled:
|
||||
issues.extend(_imap_delivery_issues(runtime_imap))
|
||||
runtime_smtp = config.server.runtime_smtp_config()
|
||||
if config.campaign.mode == "send" and not runtime_smtp:
|
||||
profile_id = (config.server.mail_profile_id or "").strip()
|
||||
if (config.campaign.mode == "send" or config.delivery.imap_append_sent.enabled) and not profile_id:
|
||||
issues.append(
|
||||
_issue(
|
||||
Severity.ERROR,
|
||||
"missing_smtp_config",
|
||||
"campaign mode is 'send', but no server.smtp configuration is present",
|
||||
"/server/smtp",
|
||||
"missing_mail_profile",
|
||||
"Select an authorized Mail-module profile; campaigns cannot store SMTP/IMAP settings or credentials.",
|
||||
"/server/mail_profile_id",
|
||||
)
|
||||
)
|
||||
if runtime_smtp:
|
||||
missing = [name for name in ["host", "port"] if getattr(runtime_smtp, name) in (None, "")]
|
||||
if missing:
|
||||
issues.append(
|
||||
_issue(
|
||||
Severity.WARNING,
|
||||
"incomplete_smtp_config",
|
||||
"SMTP settings are present, but these settings are missing: " + ", ".join(missing),
|
||||
"/server/smtp",
|
||||
)
|
||||
capabilities = config.server.profile_capabilities
|
||||
if config.campaign.mode == "send" and profile_id and not capabilities.smtp_available:
|
||||
issues.append(
|
||||
_issue(
|
||||
Severity.ERROR,
|
||||
"mail_profile_without_smtp",
|
||||
"campaign mode is 'send', but the selected Mail profile has no SMTP configuration",
|
||||
"/server/mail_profile_id",
|
||||
)
|
||||
)
|
||||
if config.delivery.imap_append_sent.enabled and profile_id and not capabilities.imap_available:
|
||||
issues.append(
|
||||
_issue(
|
||||
Severity.ERROR,
|
||||
"mail_profile_without_imap",
|
||||
"IMAP append is enabled, but the selected Mail profile has no IMAP configuration",
|
||||
"/server/mail_profile_id",
|
||||
)
|
||||
)
|
||||
return issues
|
||||
|
||||
|
||||
def _imap_delivery_issues(runtime_imap: object | None) -> list[SemanticIssue]:
|
||||
if runtime_imap is None:
|
||||
def _sender_issues(config: CampaignConfig) -> list[SemanticIssue]:
|
||||
"""Require Campaign-owned sender data before a send-mode build."""
|
||||
|
||||
if config.campaign.mode != "send":
|
||||
return []
|
||||
if config.entries.is_inline:
|
||||
return [
|
||||
_issue(
|
||||
Severity.WARNING,
|
||||
"delivery_imap_enabled_without_server_imap",
|
||||
"delivery.imap_append_sent is enabled, but no server.imap configuration is present",
|
||||
"/delivery/imap_append_sent/enabled",
|
||||
Severity.ERROR,
|
||||
"missing_sender",
|
||||
"No effective From address is configured; Campaign must resolve the sender before building.",
|
||||
f"/entries/inline/{index}/from",
|
||||
)
|
||||
for index, entry in enumerate(config.entries.inline or [])
|
||||
if entry.active and not effective_address_lists(config, entry)["from"]
|
||||
]
|
||||
missing = [name for name in ["host", "port", "username", "password"] if getattr(runtime_imap, name) in (None, "")]
|
||||
if not missing:
|
||||
if config.recipients.from_:
|
||||
return []
|
||||
defaults = config.entries.defaults
|
||||
mapping_can_supply_sender = bool(
|
||||
config.recipients.allow_individual_from
|
||||
and (
|
||||
(defaults is not None and defaults.from_)
|
||||
or "from.email" in (config.entries.mapping or {})
|
||||
)
|
||||
)
|
||||
if mapping_can_supply_sender:
|
||||
return []
|
||||
return [
|
||||
_issue(
|
||||
Severity.ERROR,
|
||||
"incomplete_imap_config",
|
||||
"IMAP append is enabled, but these IMAP settings are missing: " + ", ".join(missing),
|
||||
"/server/imap",
|
||||
"missing_sender",
|
||||
"Configure recipients.from, or allow and map an individual From address, before building a send-mode campaign.",
|
||||
"/recipients/from",
|
||||
)
|
||||
]
|
||||
|
||||
@@ -602,6 +623,7 @@ def validate_campaign_config(
|
||||
issues.extend(_attachment_path_issues(config))
|
||||
issues.extend(_zip_configuration_issues(config))
|
||||
issues.extend(_delivery_issues(config))
|
||||
issues.extend(_sender_issues(config))
|
||||
|
||||
entries = _entries_validation(
|
||||
config,
|
||||
|
||||
Reference in New Issue
Block a user