Close the gaps between the documented contract (API-STANDARD, TENANCY, ENTITLEMENTS) and the implementation, and remove committed build artifacts. Security: - verify + process inbound webhooks (HMAC/handler verify, size limit, timestamp tolerance, idempotency via InboundEvent.external_id) - real 2FA login flow (pre-auth challenge; tokens only after verify/recovery) - wire HybridPermission into security/audit views; add API-key rotate and users/<id>/permissions|roles endpoints - tenant-scoped throttling on by default; webhook replay protection - verify MercadoPago webhook signatures - login brute-force guard, configurable password policy, real ALTCHA PoW Correctness: - apply verified billing webhooks idempotently (subscription/entitlement/ invoice/PaymentTransaction); scheduled payment lifecycle jobs - capture audit update diffs automatically; add audit retention purge - working notification retries, per-channel rate limits, log retention - pluggable virus scanner, upload-size limit, pipeline toggle - feature rollout %/environment targeting; settings-driven registrations - workflow guards (instance cap, route depth, self-assignment, clone on re-entry) - wire every previously-dead INFRASYNTH_* setting; drop truly dead ones Delivery: - README + CHANGELOG; CI format check + coverage gate - keep test media out of the tree; untrack .coverage, __pycache__, egg-info, docs/ and invoice artifacts
38 lines
1.2 KiB
Python
38 lines
1.2 KiB
Python
"""Retention jobs for audit data (``INFRASYNTH_AUDIT.RETENTION_DAYS``)."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import logging
|
|
from datetime import timedelta
|
|
|
|
from celery import shared_task
|
|
from django.utils import timezone
|
|
|
|
from infrasynth.shared.settings_utils import get_setting
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
@shared_task(name="infrasynth.audit.purge_expired_logs")
|
|
def purge_expired_logs(tenant_id=None):
|
|
"""Deletes audit rows older than the configured retention window.
|
|
|
|
``RETENTION_DAYS <= 0`` disables retention (rows are kept forever).
|
|
Returns a ``{model_label: deleted_count}`` mapping.
|
|
"""
|
|
retention_days = int(get_setting("INFRASYNTH_AUDIT", "RETENTION_DAYS", 365))
|
|
if retention_days <= 0:
|
|
return {}
|
|
|
|
from .models import APIInteractionLog, ModelChangeLog, SecurityEvent
|
|
|
|
cutoff = timezone.now() - timedelta(days=retention_days)
|
|
deleted: dict[str, int] = {}
|
|
for model in (ModelChangeLog, APIInteractionLog, SecurityEvent):
|
|
qs = model.objects.filter(timestamp__lt=cutoff)
|
|
if tenant_id:
|
|
qs = qs.filter(tenant_id=tenant_id)
|
|
count, _ = qs.delete()
|
|
deleted[model._meta.label] = count
|
|
logger.info("Audit retention purge removed %s", deleted)
|
|
return deleted
|