Propósito
Helper de autenticación único para los endpoints del CNS que aceptan tanto sesión de navegador como token JWT del Agente local: recibir un diagnóstico, marcar el resultado de un apply/rollback (/result), avisar de que un equipo se recuperó (/recover) y preguntar a la IA sobre un insight (/explain). Antes de la mega-auditoría de 24-09-2026 (tandas T01-T17, hallazgos B-01 y B-11) esta función estaba duplicada en dos ficheros (monitoring/api/insight_execution.py e monitoring/api/insights.py, cada uno con su propia copia) y validaba el JWT del Agente solo por su firma, sin comprobar que el Agente siguiera vivo en la flota.
Contrato
async def _get_org_from_request(request) -> Optional["Organization"]:
"""Get organization from request — supports both session auth and Agent JWT.
Returns None if neither auth method succeeds.
"""
Ubicación: monitoring/api/insight_execution.py (única definición desde v1.156.0; insights.py la importa: from .insight_execution import _get_org_from_request).
Entrada: request (async, Django Ninja).
Salida: Organization | None. None → el endpoint responde 401/400 (según el endpoint).
Lógica:
- Si hay sesión de navegador autenticada (
request.user.is_authenticated): resuelve la organización conget_current_org(request). - Si no, intenta autenticar como Agente:
terminal.api.auth.get_agent_from_request(request)— el mismo validador que usa el resto de la superficie del Agente (no una verificación de firma aislada). Si el payload traetenant_id, resuelve laOrganization; si no existe,None.
Qué cambió (mega-auditoría B-01, 24-09-2026)
Antes: el JWT se validaba con terminal.api.auth.verify_agent_token(token) — comprobaba solo la FIRMA criptográfica del token. Un token de refresco (no de acceso), el de un Agente ya borrado de la flota, o el de una época ya revocada (rotación de credenciales) seguían pasando este gate, aunque el resto de endpoints del Agente ya exigían Agente vivo + época vigente.
Ahora: pasa por get_agent_from_request (terminal/api/auth.py:166), que exige: token de tipo ACCESO (no refresco), Agente todavía en la flota (no borrado/despedido) y época vigente (no revocada por rotación). Los endpoints del CNS quedan al mismo nivel de confianza que receive_insight y el resto de la ingesta del Agente.
Consolidación: insights.py tenía su PROPIA copia de esta función (bit a bit igual, divergencia de mantenimiento clásica). Se elimina la copia y se importa desde insight_execution.py — desde v1.156.0 hay un solo punto que endurecer si aparece un hallazgo B-XX futuro sobre este gate.
Permisos por sesión (mega-auditoría B-11, 24-09-2026)
Antes de esta ronda, un usuario de solo lectura con sesión (sin ningún permiso de cns:*) podía, usando los tres endpoints de abajo, actuar sobre el CNS porque _get_org_from_request solo comprobaba QUE hubiera sesión, no el NIVEL de esa sesión — el mismo patrón de hueco que ya cerró [[incident—20260904—cola-auditoria-b-cns-insights]] en el WebSocket de Observatory (aislamiento por tenant ≠ nivel de permiso dentro de la org). Ahora, cuando la autenticación es por sesión (no por Agente — el Agente no tiene un usuario al que pedirle nivel), cada endpoint exige además:
| Endpoint | Acción | Permiso exigido (sesión) |
|---|---|---|
POST /sentinel/insights/{id}/result | Fijar el desenlace de un apply/rollback | cns:admin |
POST /sentinel/insights/recover/{target} | Dar por recuperado un equipo caído | cns:edit |
POST /sentinel/insights/{id}/explain | Preguntar a la IA sobre un insight | cns:view |
Los tres gates entran en la lista blanca de tests/api/test_write_endpoints_permission_contract.py, que desde esta ronda los vigila (antes no aparecían — un contrato de permisos que no cubre un endpoint no avisa cuando ese endpoint se relaja).
Límite honesto
/explainsolo pidecns:view(igual que su hermanotutor_ask): un usuario de solo lectura con acceso al CNS puede seguir preguntando a la IA (dentro del límite por hora deai_cost_guard).- B-16 aplazado (fuera de esta página, mismo dominio): la lista de equipos con credenciales de gestión se sirve solo al Agente PRINCIPAL (
terminal/api/sentinel.py); no está comprobado qué pasa si la pide un Agente SECUNDARIO ni la carrera al promocionar uno — un 403 podría dejar equipos sin vigilar. Recomendación registrada para una tanda futura (T04/T22): servir al secundario la lista SIN credenciales en vez de un 403. - No hay migración: el cambio es solo de lógica de autenticación/autorización, sin tocar modelos.
Tests
tests/monitoring/test_mega_T03.py (B-01: Agente borrado/revocado ya no entra; B-11: los tres endpoints exigen su permiso). tests/api/test_audit_monitoring_b.py se ajusta para dar de alta el Agente de su token en el fixture (antes no lo hacía — lo que el test afirma no cambia, solo se corrige el fixture). tests/api/test_write_endpoints_permission_contract.py incorpora los tres endpoints a su lista blanca.
Véase también
- [[incident—20260904—cola-auditoria-b-cns-insights]]
- [[incident—20260831—auditoria-suprema-2-tanda1-monitoring-racks]]
- [[concept—saas—multi-tenancy]]
- [[crearack—conceptos—usuarios-y-permisos]]
- [[feature—monitoring—auditoria-suprema-sa3-cns-insights-ia]]
- [[entity—core—service—require-perm]]