security: harden Campaign delivery effects and reconciliation
This commit is contained in:
@@ -12,6 +12,9 @@ from govoplan_core.core.campaigns import (
|
||||
)
|
||||
from govoplan_core.core.module_guards import drop_table_retirement_provider, persistent_table_uninstall_guard
|
||||
from govoplan_core.core.modules import (
|
||||
DocumentationCondition,
|
||||
DocumentationLink,
|
||||
DocumentationTopic,
|
||||
FrontendModule,
|
||||
MigrationSpec,
|
||||
ModuleContext,
|
||||
@@ -59,7 +62,7 @@ PERMISSIONS = (
|
||||
_permission("campaigns:campaign:control", "Control delivery", "Pause, resume or cancel queued and sending jobs.", "Campaigns"),
|
||||
_permission("campaigns:campaign:send", "Send campaigns", "Start real SMTP delivery.", "Campaigns"),
|
||||
_permission("campaigns:campaign:retry", "Retry delivery", "Retry failed or unattempted delivery jobs.", "Campaigns"),
|
||||
_permission("campaigns:campaign:reconcile", "Reconcile delivery", "Resolve outcome-unknown SMTP attempts after inspection.", "Campaigns"),
|
||||
_permission("campaigns:campaign:reconcile", "Reconcile delivery", "Resolve outcome-unknown SMTP or IMAP attempts after inspection.", "Campaigns"),
|
||||
_permission("campaigns:diagnostic:read", "View campaign diagnostics", "Inspect worker claims and internal storage locators for campaign delivery troubleshooting.", "Campaign operations"),
|
||||
_permission("campaigns:recipient:read", "View recipients", "Read recipient lists and recipient-specific campaign data.", "Recipients"),
|
||||
_permission("campaigns:recipient:write", "Edit recipients", "Create and edit recipient rows and field values.", "Recipients"),
|
||||
@@ -144,7 +147,7 @@ def _campaigns_router(context: ModuleContext):
|
||||
manifest = ModuleManifest(
|
||||
id="campaigns",
|
||||
name="Campaigns",
|
||||
version="0.1.8",
|
||||
version="0.1.9",
|
||||
required_capabilities=(CAPABILITY_AUTH_PRINCIPAL_RESOLVER, CAPABILITY_AUTH_PERMISSION_EVALUATOR),
|
||||
optional_dependencies=("files", "mail", "notifications", "addresses"),
|
||||
provides_interfaces=(
|
||||
@@ -163,8 +166,8 @@ manifest = ModuleManifest(
|
||||
),
|
||||
ModuleInterfaceRequirement(
|
||||
name="mail.campaign_delivery",
|
||||
version_min="0.1.0",
|
||||
version_max_exclusive="0.2.0",
|
||||
version_min="0.2.0",
|
||||
version_max_exclusive="0.3.0",
|
||||
optional=True,
|
||||
),
|
||||
ModuleInterfaceRequirement(
|
||||
@@ -258,6 +261,118 @@ manifest = ModuleManifest(
|
||||
label="Campaigns",
|
||||
),
|
||||
),
|
||||
documentation=(
|
||||
DocumentationTopic(
|
||||
id="campaigns.mail-profile-user-journey",
|
||||
title="Choose a Mail profile for campaign delivery",
|
||||
summary="Campaigns reference an authorized Mail profile and never store SMTP/IMAP settings or credentials.",
|
||||
body="Open the campaign Mail settings, select an available profile, test it through Mail, and save. Validation and delivery recheck profile authorization. A changed transport identity requires a new validation and build.",
|
||||
layer="available",
|
||||
documentation_types=("user",),
|
||||
audience=("campaign_manager", "campaign_reviewer", "campaign_sender"),
|
||||
order=46,
|
||||
conditions=(
|
||||
DocumentationCondition(
|
||||
required_modules=("campaigns", "mail"),
|
||||
any_scopes=("campaigns:campaign:read", "mail:profile:use"),
|
||||
),
|
||||
),
|
||||
links=(
|
||||
DocumentationLink(label="Campaigns", href="/campaigns", kind="runtime"),
|
||||
DocumentationLink(label="Mail profiles", href="/mail", kind="runtime"),
|
||||
),
|
||||
related_modules=("mail",),
|
||||
unlocks=("Profile-backed SMTP delivery and optional IMAP append-to-Sent.",),
|
||||
metadata={
|
||||
"kind": "workflow",
|
||||
"route": "/campaigns/{campaign_id}/mail",
|
||||
"screen": "Campaign Mail settings",
|
||||
"prerequisites": [
|
||||
"Campaign and Mail are installed.",
|
||||
"You may edit the current campaign version and use at least one authorized Mail profile.",
|
||||
],
|
||||
"steps": [
|
||||
"Open the campaign and go to Mail settings.",
|
||||
"Select an available Mail profile; Campaign stores only its stable identifier.",
|
||||
"Test SMTP and, when configured, IMAP through the Mail module.",
|
||||
"Save, validate, and build the campaign before queueing delivery.",
|
||||
],
|
||||
"outcome": "The editable campaign version references an authorized Mail-owned delivery profile without copying transport settings or credentials.",
|
||||
"verification": "Reopen Mail settings, confirm the selected profile, then run validation and verify that the build completes without profile-drift errors.",
|
||||
"related_topic_ids": [
|
||||
"campaigns.mail-profile-governance",
|
||||
"campaigns.mail-profile-operations",
|
||||
"mail.profile-ownership-and-consumers",
|
||||
],
|
||||
},
|
||||
),
|
||||
DocumentationTopic(
|
||||
id="campaigns.mail-profile-governance",
|
||||
title="Govern Campaign-to-Mail profile references",
|
||||
summary="Mail owns transport definitions and encrypted credentials; Campaign owns only the selected profile reference and delivery evidence.",
|
||||
body="Grant mail:profile:use to campaign authors, constrain profile availability through Mail policy, and keep effective credential inheritance enabled. Inline transport fields are rejected. Legacy records remain unchanged until an explicit, audited profile migration creates or updates an editable version.",
|
||||
layer="configured",
|
||||
documentation_types=("admin",),
|
||||
audience=("tenant_admin", "mail_admin", "campaign_admin"),
|
||||
order=47,
|
||||
conditions=(
|
||||
DocumentationCondition(
|
||||
required_modules=("campaigns", "mail"),
|
||||
any_scopes=("mail:profile:write", "admin:policies:read", "system:settings:read"),
|
||||
),
|
||||
),
|
||||
links=(
|
||||
DocumentationLink(label="Mail profiles", href="/mail", kind="runtime"),
|
||||
DocumentationLink(label="Campaign schema", href="/api/v1/campaigns/schema", kind="api"),
|
||||
),
|
||||
related_modules=("mail", "access"),
|
||||
unlocks=("Auditable, reusable transport configuration across campaigns.",),
|
||||
metadata={
|
||||
"kind": "reference",
|
||||
"route": "/campaigns/{campaign_id}/mail-policy",
|
||||
"screen": "Campaign Mail policy",
|
||||
"section": "Profile authorization and credential inheritance",
|
||||
"related_topic_ids": [
|
||||
"campaigns.mail-profile-user-journey",
|
||||
"campaigns.mail-profile-operations",
|
||||
"mail.profile-ownership-and-consumers",
|
||||
],
|
||||
},
|
||||
),
|
||||
DocumentationTopic(
|
||||
id="campaigns.mail-profile-operations",
|
||||
title="Operate profile-backed campaign delivery",
|
||||
summary="Workers re-authorize and resolve Mail profiles at execution time while Campaign retains only opaque Mail-owned revisions and outcomes.",
|
||||
body="A legacy snapshot, unauthorized or inactive profile, profile-reference mismatch, or changed SMTP/IMAP transport revision stops delivery. Preserve the record, migrate or correct the profile selection, revalidate, rebuild, and only then queue again. Password-only rotation remains possible without copying secrets into Campaign. Uncertain SMTP and IMAP effects remain blocked until an evidence-backed operator reconciliation.",
|
||||
layer="configured",
|
||||
documentation_types=("admin",),
|
||||
audience=("campaign_sender", "campaign_operator", "mail_admin"),
|
||||
order=48,
|
||||
conditions=(
|
||||
DocumentationCondition(
|
||||
required_modules=("campaigns", "mail"),
|
||||
any_scopes=("campaigns:diagnostic:read", "campaigns:campaign:reconcile", "mail:profile:test"),
|
||||
),
|
||||
),
|
||||
links=(
|
||||
DocumentationLink(label="Campaign operator queue", href="/operator", kind="runtime"),
|
||||
DocumentationLink(label="Campaign reports", href="/reports", kind="runtime"),
|
||||
),
|
||||
related_modules=("mail", "audit"),
|
||||
unlocks=("Fail-closed recovery without exposing Mail credentials.",),
|
||||
metadata={
|
||||
"kind": "reference",
|
||||
"route": "/operator",
|
||||
"screen": "Campaign operator queue",
|
||||
"section": "Profile-backed delivery recovery",
|
||||
"related_topic_ids": [
|
||||
"campaigns.mail-profile-user-journey",
|
||||
"campaigns.mail-profile-governance",
|
||||
"mail.profile-ownership-and-consumers",
|
||||
],
|
||||
},
|
||||
),
|
||||
),
|
||||
capability_factories={
|
||||
CAPABILITY_CAMPAIGNS_ACCESS: lambda context: __import__(
|
||||
"govoplan_campaign.backend.capabilities",
|
||||
|
||||
Reference in New Issue
Block a user