Volver a la wiki

Servicio publish_misses — enlaces de publicación de Signage rechazados (v1.124.0, task #276)

Propósito

signage/services/publish_misses.py registra en caché (Valkey) cada petición al enlace público /publish/<token>/ de un SignagePlayer que _get_player() (signage/views_publish.py) RECHAZA — token inexistente, revocado (publish_active=False) o caducado (publish_expires_at en el pasado; ver el hallazgo de seguridad G1 que introdujo esos dos campos, sesión 167). Antes de esta pieza (task #276), ese 404 solo quedaba en el log del contenedor: el CCIB estuvo 9 días con una pantalla sin actualizar tras un restore sin que nadie lo supiera hasta mirar los logs a mano.

Mega-auditoría B-08 (24-09-2026, commit 9e8d4123): un token revocado o caducado SÍ tiene dueño — el SignagePlayer existe y puede reactivarse con el mismo UUID —, así que su entrada guarda el organization_id del reproductor y el endpoint la filtra por la organización de quien pregunta. Antes, la lista era global: el administrador de cartelería de una organización veía los enlaces (y las IP) de las pantallas de otra.

Contrato

def record_publish_miss(token, reason: str, request=None, organization_id=None) -> None:
    """reason: 'unknown' | 'revoked' | 'expired'. organization_id: la del
    player cuando el token existe (revoked/expired); None si no tiene dueño
    (unknown). Nunca lanza — una caché caída no debe convertir un 404 en un 500."""

def list_publish_misses() -> list[dict]:
    """Enlaces rechazados con actividad en los últimos 7 días, el más
    reciente primero. Sin filtrar — uso interno de list_publish_misses_for_org."""

def list_publish_misses_for_org(organization_id) -> list[dict]:
    """Lo que puede ver una organización: sus tokens (organization_id propio)
    más los que no tienen dueño (organization_id=None, es decir 'unknown')."""

Endpoint

GET /api/monitoring/signage/publish-misses (monitoring/api/signage/deploy.py), gateado a require_perm(signage, admin). Desde B-08 ya NO es global: llama a list_publish_misses_for_org(org.id) con la organización de la sesión, así que cada administrador ve solo los enlaces revocados/caducados de su propia organización, más los unknown (sin dueño, visibles a todos). Los tokens listados están muertos: no dan acceso a nada, pero antes de B-08 exponían el UUID reactivable y la IP de pantallas ajenas.

Límite declarado

Los rechazos unknown (token que nunca existió, típico tras una restauración) y las entradas anotadas antes de este cambio (sin organization_id, caducan solas a los 7 días) se siguen mostrando a los administradores de todas las organizaciones, con su IP. Ocultarlos a quien no sea personal de la plataforma choca con el test existente test_admin_sees_list_and_viewer_does_not (exige que un administrador de Signage vea la lista global) — queda como decisión de producto pendiente, no como descuido.

Dependencias entrantes

MóduloDescripción
signage/views_publish.py::_get_player()Llama a record_publish_miss() en los 3 caminos de rechazo (unknown/revoked/expired); desde B-08 pasa organization_id=player.organization_id en revoked/expired
monitoring/api/signage/deploy.py::list_publish_misses_apiDesde B-08 llama a list_publish_misses_for_org(org.id) en vez de list_publish_misses() directo
static/js/pages/signage/SignageDeployManager.js::_refreshUnknownLinks()Pinta la lista en la pestaña Historial de Signage; un 403 (no-admin) se traduce en ocultar el bloque, sin ruido

Relación con el restore

Un SignagePlayer CREADO por un restore estrena publish_token (el token se excluye del backup a propósito, ver [[entity—racks—service—apply-full-restore-from-zip]]). El restore avisa de esto en su informe (notices.signage_players_new_links, misma pieza v1.124.0), pero si el aviso se ignora o el aparato no se reconfigura, sus peticiones al enlace viejo empiezan a aparecer aquí — es la red de seguridad de ese aviso.

Tests

Véase también

Subir