Stack: DRF · Proyecto: TicketFlow Estado: Publicada Prerrequisito: Lección 20 — Hash y MFA
Objetivos
- Distinguir autenticación (quién eres) de autorización (qué puedes) y mapear los roles de TicketFlow.
- Implementar autorización a tres niveles: endpoint (permission_classes), objeto (has_object_permission) y dominio (reglas de servicio).
- Evitar los agujeros clásicos: IDOR, escalada por ausencia de checks y reglas que solo viven en el frontend.
1. Los roles de TicketFlow
| Rol | Puede |
|---|---|
| BUYER (comprador) | ver eventos, reservar para sí, ver/cancelar SUS reservas, pagar SUS reservas |
| ORGANIZER | lo anterior + crear/editar SUS eventos, ver ventas de SUS eventos |
| STAFF/ADMIN | todo + reembolsos, desactivar eventos, gestionar usuarios |
| SYSTEM (webhook/worker) | acciones firmadas de pasarela, jobs de expiración |
Autenticación (18) responde "eres Ana, user_id=7". Autorización responde: "¿Ana puede VER la reserva 9172?" — y la respuesta correcta es "solo si es suya": la parte que los frameworks no traen por defecto.
2. Los tres niveles de check
Nivel 1 — Endpoint (¿puede esta clase de usuario llamar aquí?):
class EventViewSet(ModelViewSet):
def get_permissions(self):
if self.action in ("create", "update", "partial_update", "destroy"):
return [IsOrganizer()] # custom permission
return [AllowAny()]Nivel 2 — Objeto (¿puede tocar ESTE recurso?):
class IsOrganizerOfEvent(permissions.BasePermission):
def has_object_permission(self, request, view, obj):
return obj.organizer_id == request.user.id or request.user.is_staffNivel 3 — Dominio (la regla viva en el servicio, no en la vista):
def cancelar_reserva(user, reservation):
if reservation.user_id != user.id and not user.is_staff:
raise Forbidden() # la regla de propiedad VIVE en el servicio
...Por qué el nivel 3 importa: los check de viewset se saltan si mañana llamas al servicio desde un comando de management, un worker de Celery o un webhook interno. El servicio es la última línea: toda regla de propiedad se evalúa allí también (defensa en profundidad).
3. IDOR: el agujero número 1 de las APIs
Insecure Direct Object Reference: GET /api/reservations/9173/ cuando la 9173 es de otro — si el viewset no filtra por dueño, el 404 nunca llega y el dato se filtra. Defensas combinadas:
- Queryset filtrado (la más robusta):
get_queryset(): return Reservation.objects.filter(user=request.user)— el objeto de otro NI EXISTE en tu universo → 404, no 403 (no confirmas existencia). has_object_permissioncomo segunda capa.- UUIDs públicos (13) para que enumerar sea caro — higiene, no control.
Con staff: el queryset añade la excepción (| Q(user=request.user) | Q(...) o un branch) — pero explícito, no "admin pasa por accidente".
4. RBAC, ABAC y scopes (mapa rápido)
- RBAC (role-based): BUYER/ORGANIZER/STAFF — roles con permisos fijos. Simple y suficiente para el 80%: implementar con roles en el user (ya tienes
rolede la 00b) + permissions por rol. - ABAC (attribute-based): reglas por atributos ("el organizador puede editar su evento SOLO mientras state=DRAFT o el evento no empezó") — no es un framework: es la regla de dominio que escribes en el servicio.
- Scopes (OAuth, 19): para clientes API (integradores):
tickets:writesin ser un "rol humano". Los scopes limitan AL CLIENTE; los roles limitan AL USUARIO; conviven.
Escalada de privilegios a vigilar: un ORGANIZER que se auto-asigna STAFF vía PATCH /users (el serializer de usuarios jamás acepta role desde entrada no-staff); un BUYER que se promueve organizador de un evento ajeno (el check del organizador es por objeto, no por rol).
5. La matriz de autorización de TicketFlow (ejercicio de diseño)
Antes de escribir checks, escribe la tabla: filas=acciones, columnas=roles, celdas=sí/no/condición. La matriz es el artefacto de review: los agujeros se ven en la tabla antes que en el código. En los ejercicios la construyes completa.
Autoevaluación
- ¿Por qué el check de propiedad debe vivir TAMBIÉN en el servicio si ya está en el viewset?
- ¿Por qué el queryset filtrado responde 404 y no 403 al recurso ajeno, y qué fuga de información evita el 404?
- Dame un caso ABAC de TicketFlow que RBAC puro no exprese.
- ¿Cómo evitas que un ORGANIZER se auto-promueva a STAFF? ¿Dónde está el check exacto?
- ¿Qué diferencia hay entre el scope
tickets:writey el rol ORGANIZER, y cuándo aplica cada uno?
Continúa con los ejercicios. Las solutions.md solo tras intentarlo.