fix(backend): resolve refresh-token expiry deadlock, missing Jira on normal campaign approval, discarded threat-actor campaign start_date

- /auth/refresh now allows a short grace window past expiry and checks
  the blacklist, so an active session's silent refresh no longer fails
  the instant its own token expires
- normal manager /approve flow now creates Jira tickets for the campaign
  and its already-linked tests, matching the admin-only /activate path
- GenerateFromActorPayload now accepts start_date and threads it through
  to the new campaign instead of silently discarding it
This commit is contained in:
kitos
2026-07-07 13:42:26 +02:00
parent fefe70baed
commit bd26b09827
6 changed files with 236 additions and 31 deletions
+44 -25
View File
@@ -134,6 +134,42 @@ logger = logging.getLogger(__name__)
router = APIRouter(prefix="/campaigns", tags=["campaigns"])
def _create_jira_tickets_for_campaign(db: Session, campaign: Campaign, campaign_id: str, user: User) -> None:
"""Create Jira tickets for a campaign and its already-linked tests, if missing.
Shared by both paths that bring a campaign to ``active``: the admin-only
emergency `/activate` override and the normal manager `/approve` flow.
Tests may already be linked to the campaign from when it was still a
draft, i.e. before any Jira ticket existed for the campaign — so they
need their own tickets created here too. Best-effort: failures are
logged, not raised, since a Jira outage must not block activation.
"""
try:
from app.services.jira_service import (
auto_create_campaign_issue,
auto_create_test_issue,
get_campaign_jira_key,
get_test_jira_key,
)
campaign_jira_key = get_campaign_jira_key(db, campaign_id)
if not campaign_jira_key:
campaign_jira_key = auto_create_campaign_issue(db, campaign, user)
if campaign_jira_key:
for ct in campaign.campaign_tests:
if ct.test and not get_test_jira_key(db, ct.test.id):
auto_create_test_issue(
db, ct.test, user,
parent_ticket_override=campaign_jira_key,
campaign_start_date=campaign.start_date,
)
db.commit()
except Exception:
logger.exception(
"Jira ticket creation failed while activating campaign %s",
campaign_id,
)
# ── Pydantic schemas ─────────────────────────────────────────────────
class CampaignCreate(BaseModel):
@@ -498,6 +534,11 @@ def approve_campaign_endpoint(
)
uow.commit()
db.refresh(campaign)
# Create Jira tickets for campaign and its already-linked tests (non-fatal).
# Mirrors the admin-only /activate override — this is the normal path.
_create_jira_tickets_for_campaign(db, campaign, campaign_id, current_user)
return serialize_campaign(db, campaign)
@@ -837,30 +878,7 @@ def activate_campaign(
# Create Jira tickets for campaign and tests at activation time (non-fatal).
# Campaign ticket is created here if it doesn't already exist (deferred from creation).
try:
from app.services.jira_service import (
auto_create_campaign_issue,
auto_create_test_issue,
get_campaign_jira_key,
get_test_jira_key,
)
campaign_jira_key = get_campaign_jira_key(db, campaign_id)
if not campaign_jira_key:
campaign_jira_key = auto_create_campaign_issue(db, campaign, current_user)
if campaign_jira_key:
for ct in campaign.campaign_tests:
if ct.test and not get_test_jira_key(db, ct.test.id):
auto_create_test_issue(
db, ct.test, current_user,
parent_ticket_override=campaign_jira_key,
campaign_start_date=campaign.start_date,
)
db.commit()
except Exception:
logger.exception(
"Jira ticket creation failed during activation of campaign %s",
campaign_id,
)
_create_jira_tickets_for_campaign(db, campaign, campaign_id, current_user)
return serialize_campaign(db, campaign)
@@ -950,7 +968,7 @@ def get_campaign_progress_endpoint(
# ---------------------------------------------------------------------------
class GenerateFromActorPayload(BaseModel):
pass
start_date: Optional[datetime] = None
@router.post("/from-threat-actor/{actor_id}", status_code=201)
@@ -980,6 +998,7 @@ def generate_campaign_from_actor(
db,
uuid.UUID(actor_id),
current_user,
start_date=payload.start_date,
)
# Open context manager