Módulo 4 · Autenticación y seguridad

Lección 21 — Autorización

Roles, permisos y control de acceso por recurso: quién puede tocar qué.

Publicada
En esta lección
  1. Objetivos
  2. 1. Los roles de TicketFlow
  3. 2. Los tres niveles de check
  4. 3. IDOR: el agujero número 1 de las APIs
  5. 4. RBAC, ABAC y scopes (mapa rápido)
  6. 5. La matriz de autorización de TicketFlow (ejercicio de diseño)
  7. Autoevaluación

Stack: DRF · Proyecto: TicketFlow Estado: Publicada Prerrequisito: Lección 20 — Hash y MFA


Objetivos

  1. Distinguir autenticación (quién eres) de autorización (qué puedes) y mapear los roles de TicketFlow.
  2. Implementar autorización a tres niveles: endpoint (permission_classes), objeto (has_object_permission) y dominio (reglas de servicio).
  3. Evitar los agujeros clásicos: IDOR, escalada por ausencia de checks y reglas que solo viven en el frontend.

1. Los roles de TicketFlow

RolPuede
BUYER (comprador)ver eventos, reservar para sí, ver/cancelar SUS reservas, pagar SUS reservas
ORGANIZERlo anterior + crear/editar SUS eventos, ver ventas de SUS eventos
STAFF/ADMINtodo + 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í?):

python
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?):

python
class IsOrganizerOfEvent(permissions.BasePermission):
    def has_object_permission(self, request, view, obj):
        return obj.organizer_id == request.user.id or request.user.is_staff

Nivel 3 — Dominio (la regla viva en el servicio, no en la vista):

python
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:

  1. 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).
  2. has_object_permission como segunda capa.
  3. 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 role de 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:write sin 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

  1. ¿Por qué el check de propiedad debe vivir TAMBIÉN en el servicio si ya está en el viewset?
  2. ¿Por qué el queryset filtrado responde 404 y no 403 al recurso ajeno, y qué fuga de información evita el 404?
  3. Dame un caso ABAC de TicketFlow que RBAC puro no exprese.
  4. ¿Cómo evitas que un ORGANIZER se auto-promueva a STAFF? ¿Dónde está el check exacto?
  5. ¿Qué diferencia hay entre el scope tickets:write y el rol ORGANIZER, y cuándo aplica cada uno?

Continúa con los ejercicios. Las solutions.md solo tras intentarlo.