CreaRack-SL

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_editor cargaba 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:

  1. get_current_org(request) retorna None si el user no tiene org (no intenta adivinar)
  2. require_org(request) (nuevo) lanza HttpError(403) si no hay org — para endpoints que REQUIEREN contexto
  3. Endpoints read-only (/api/users, /api/search, /api/logs, /api/status) retornan listas vacías si get_current_org() es None
  4. Endpoints write (PUT /api/settings/...) lanzan 404 si no hay org
  5. 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: nuevo require_org() (fail-closed helper)
  • ✅ core/utils/__init__.py: require_org promovido a __all__ (core.utils)
  • ✅ monitoring/api/common.py: re-export require_org (backward compat, era local ahí)
  • ✅ core/api/* (admin, logs, search, settings, status, users): reemplazar user.organization or Organization.objects.first() por get_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_editor 404 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]]