Utilidad require_org: resolver organización con fallo cerrado
Definición
require_org(request: HttpRequest) → Organization
Utility en core.utils.organization que resuelve la organización del request o lanza HttpError(403, "Forbidden: no organization in context").
Ubicación: core/utils/organization.py
Módulo exportador: core.utils (promovido desde local en monitoring/api/common.py)
Re-exporta: monitoring/api/common.py (backward compat)
Propósito
Reemplazar el antipatrón heredado:
org = request.user.organization or Organization.objects.first() # ✗ INSEGURO
Por un flujo explícito fail-closed:
org = require_org(request) # ✓ 403 si no hay org
Caso de uso: endpoints que REQUIEREN contexto de organización (escritura, operaciones sensibles).
Implementación
def require_org(request):
"""Resolve the request's organization or raise HttpError(403) — fail closed.
Replaces the legacy ``user.organization or Organization.objects.first()``
fallback (T5): a principal without an org must NEVER fall through to the
first tenant in the DB (cross-tenant read/write).
"""
from ninja.errors import HttpError
org = get_current_org(request)
if org is None:
raise HttpError(403, "Forbidden: no organization in context")
return org
Depende de:
get_current_org(request)— retorna org o None, no intenta adivinarninja.errors.HttpError— serializa 403 JSON limpio
Call-sites (antes de T5)
require_org era local en:
- ✓
monitoring/api/common.py(función pública interna)get_target_or_404()— resuelve MonitoringTarget scoped a org- Otros handlers de monitoring que requieren contexto
En T5 (2026-06-11):
- ✅ Promovido a
core.utils.organization - ✅ Expandido a todos los endpoints que requieren org (
PUT /api/settings/...,POST /api/admin/..., etc.) - ✅
monitoring/api/common.pyre-exporta para backward compat:from core.utils import require_org # noqa: F401
Arquitectura
Request sin auth / sin org
↓
call require_org(request)
↓
├─ get_current_org(request) retorna None
└─ → HttpError(403) ← fail-closed, no intenta Organization.objects.first()
Request con auth + org
↓
call require_org(request)
↓
├─ get_current_org(request) retorna Organization
└─ → org ← ok, procede
Tests
tests/api/test_t5_org_fallback.py::TestRequireOrgHelper:
def test_require_org_raises_without_org(self, rf, orgless_admin):
"""Lanza HttpError(403) si user.organization es None."""
from core.utils import require_org
request = rf.get("/")
request.user = orgless_admin
with pytest.raises(HttpError):
require_org(request)
def test_require_org_returns_org(self, rf, admin_user, organization):
"""Retorna org si user.organization existe."""
from core.utils import require_org
request = rf.get("/")
request.user = admin_user
assert require_org(request) == organization
Estado
- Creado: 2026-06-11 (Sesión 128, Auditoría Suprema T5)
- Status: active
- Commit: e786894
- Regresiones: 0 (suite Docker limpia)
Véase también
- [[decision—20260611—t5-organizacion-fallback-multitenant]]
- [[entity—core—utility—get-current-org]]
- [[concept—saas—multi-tenancy]]
- [[entity—core—model—organization]]
- [[entity—core—api—endpoint—api-admin]]