CreaRack-SL

_get_org_from_request: autenticación única del CNS (sesión + Agente) tras B-01/B-11

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:

  1. Si hay sesión de navegador autenticada (request.user.is_authenticated): resuelve la organización con get_current_org(request).
  2. 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 trae tenant_id, resuelve la Organization; 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:

EndpointAcciónPermiso exigido (sesión)
POST /sentinel/insights/{id}/resultFijar el desenlace de un apply/rollbackcns:admin
POST /sentinel/insights/recover/{target}Dar por recuperado un equipo caídocns:edit
POST /sentinel/insights/{id}/explainPreguntar a la IA sobre un insightcns: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

  • /explain solo pide cns:view (igual que su hermano tutor_ask): un usuario de solo lectura con acceso al CNS puede seguir preguntando a la IA (dentro del límite por hora de ai_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]]