security: authorize every Campaign effect version
This commit is contained in:
@@ -95,6 +95,9 @@ from govoplan_campaign.backend.db.models import (
|
||||
CampaignVersion,
|
||||
CampaignVersionWorkflowState,
|
||||
ImapAppendAttempt,
|
||||
JobImapStatus,
|
||||
JobQueueStatus,
|
||||
JobSendStatus,
|
||||
RecipientImportMappingProfile,
|
||||
SendAttempt,
|
||||
)
|
||||
@@ -358,7 +361,11 @@ def _campaign_mail_profile_id(raw_json: dict[str, object] | None) -> str | None:
|
||||
|
||||
def _require_mail_profile_use_if_needed(principal: ApiPrincipal, raw_json: dict[str, object] | None) -> None:
|
||||
if _campaign_mail_profile_id(raw_json) and not has_scope(principal, "mail:profile:use"):
|
||||
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Missing scope: mail_servers:use")
|
||||
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Missing scope: mail:profile:use")
|
||||
|
||||
|
||||
def _campaign_response_context(principal: ApiPrincipal) -> dict[str, bool]:
|
||||
return {"include_diagnostics": has_scope(principal, "campaigns:diagnostic:read")}
|
||||
|
||||
|
||||
def _campaign_version_detail_response(
|
||||
@@ -458,6 +465,23 @@ def _require_campaign_profile_use_if_needed(
|
||||
_require_mail_profile_use_if_needed(principal, version.raw_json if isinstance(version.raw_json, dict) else {})
|
||||
|
||||
|
||||
def _require_campaign_versions_profile_use(
|
||||
session: Session,
|
||||
principal: ApiPrincipal,
|
||||
campaign_id: str,
|
||||
version_ids: set[str],
|
||||
) -> None:
|
||||
"""Authorize every historical version affected by a campaign-wide action."""
|
||||
|
||||
for version_id in sorted(version_ids):
|
||||
_require_campaign_profile_use_if_needed(
|
||||
session,
|
||||
principal,
|
||||
campaign_id,
|
||||
version_id,
|
||||
)
|
||||
|
||||
|
||||
def _get_version_for_principal(
|
||||
session: Session,
|
||||
version_id: str,
|
||||
@@ -3200,6 +3224,18 @@ def resume_campaign(
|
||||
principal: ApiPrincipal = Depends(require_scope("campaigns:campaign:control")),
|
||||
):
|
||||
_get_campaign_for_principal(session, campaign_id, principal, write=True)
|
||||
version_ids = {
|
||||
row[0]
|
||||
for row in session.query(CampaignJob.campaign_version_id)
|
||||
.filter(
|
||||
CampaignJob.tenant_id == principal.tenant_id,
|
||||
CampaignJob.campaign_id == campaign_id,
|
||||
CampaignJob.queue_status == JobQueueStatus.PAUSED.value,
|
||||
)
|
||||
.distinct()
|
||||
.all()
|
||||
}
|
||||
_require_campaign_versions_profile_use(session, principal, campaign_id, version_ids)
|
||||
try:
|
||||
result = resume_campaign_jobs(session, tenant_id=principal.tenant_id, campaign_id=campaign_id)
|
||||
audit_from_principal(session, principal, action="campaign.resumed", object_type="campaign", object_id=campaign_id, details=result, commit=True)
|
||||
@@ -3232,7 +3268,23 @@ def append_sent(
|
||||
):
|
||||
_get_campaign_for_principal(session, campaign_id, principal, write=True)
|
||||
payload = payload or AppendSentRequest()
|
||||
_require_campaign_profile_use_if_needed(session, principal, campaign_id)
|
||||
version_ids = {
|
||||
row[0]
|
||||
for row in session.query(CampaignJob.campaign_version_id)
|
||||
.filter(
|
||||
CampaignJob.tenant_id == principal.tenant_id,
|
||||
CampaignJob.campaign_id == campaign_id,
|
||||
CampaignJob.send_status.in_(
|
||||
[JobSendStatus.SMTP_ACCEPTED.value, JobSendStatus.SENT.value]
|
||||
),
|
||||
CampaignJob.imap_status.in_(
|
||||
[JobImapStatus.PENDING.value, JobImapStatus.FAILED.value]
|
||||
),
|
||||
)
|
||||
.distinct()
|
||||
.all()
|
||||
}
|
||||
_require_campaign_versions_profile_use(session, principal, campaign_id, version_ids)
|
||||
try:
|
||||
result = enqueue_pending_imap_appends(
|
||||
session,
|
||||
|
||||
Reference in New Issue
Block a user