T5 (Auditoría Suprema R6): Eliminación del fallback a primer tenant en operaciones multi-tenant
Problema
El código heredado (era monoemresa) contenía el antipatrón Organization.objects.first() como fallback en ~26 call-sites:
- core/api:
admin.py(×5),settings.py(×7),users.py(×7),logs.py(×2),search.py(×1),status.py(×1) - core/views:
index()dashboard,project_report_view() - racks/views:
index(),rack_editor()
Consecuencia: Un principal autenticado sin organización asignada (o anónimo en ciertos endpoints legacy) caía automáticamente al primer tenant de la BD:
- ✗ Leía usuarios, logs, búsquedas, ajustes de una empresa ajena
- ✗ Escribía: editaba los ajustes de sesión, onboarding, incluso la empresa
- ✗
rack_editorcargaba racks ajenos sin scope de org (acceso a datos de otro cliente)
En un producto SaaS multi-cliente, esto es innegociable.
Solución
Fail-closed: eliminar todos los fallbacks. La regla es estricta:
get_current_org(request)retornaNonesi el user no tiene org (no intenta adivinar)require_org(request)(nuevo) lanzaHttpError(403)si no hay org — para endpoints que REQUIEREN contexto- Endpoints read-only (
/api/users,/api/search,/api/logs,/api/status) retornan listas vacías siget_current_org()esNone - Endpoints write (
PUT /api/settings/...) lanzan 404 si no hay org - Vistas (views.py): dashboard y reports muestran vacío, no caen a otro tenant
Además: rack_editor ahora scoped a org. Antes get_object_or_404(Rack, id=...), ahora get_object_or_404(Rack, id=..., organization=org).
Cambios de código
- ✅
core/utils/organization.py: nuevorequire_org()(fail-closed helper) - ✅
core/utils/__init__.py:require_orgpromovido a__all__(core.utils) - ✅
monitoring/api/common.py: re-exportrequire_org(backward compat, era local ahí) - ✅ core/api/* (admin, logs, search, settings, status, users): reemplazar
user.organization or Organization.objects.first()porget_current_org(request) - ✅ core/views.py + racks/views.py: idem, sin fallback
- ✅ 14 tests nuevos (
tests/api/test_t5_org_fallback.py):- Admin sin org no ve/escribe usuarios del tenant #1
- CREATE/UPDATE de usuarios 400-404 sin org
- Settings (company, session-timeout) 404 sin org
- Search, logs, status retornan vacío/defaults sin org
- Dashboard de racks vacío sin org
rack_editor404 cross-tenant (extra bonus)
Alineación
- Raíz R6 del Informe Supremo: “Aislamiento multi-tenant incompleto”
- Tanda T5: Transversal, cierre de deudas de arquitectura
- Sesión 128 (2026-06-11)
- Entrada RELEASE_NOTES: sí (s128 con descripción ejecutiva)
- Entrada CHANGELOG: sí (Unreleased → 2026-06-11)
Impacto esperado
✅ Breaking: un bot/script que dependa de Organization.objects.first() como fallback fallará (intencional — forza uso explícito de org context)
✅ No-op para usuarios normales: todo principal tiene org asignada en flujos normales
✅ Seguridad: cierra cross-tenant read/write en admin flows
Estado actual
Merged a main el 2026-06-11 (commit e786894). Suite de tests en Docker sin regresiones (diff de fallos vs base = 0).
Véase también
- [[entity—core—utility—require-org]]
- [[incident—20260611—r6-cross-tenant-read-write-legacy]]
- [[concept—saas—multi-tenancy]]
- [[entity—core—model—organization]]
- [[entity—core—api—endpoint—api-users]]