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. Ejercicio 1 — La matriz (la del profesor)
  2. Ejercicio 2 — IDOR
  3. Ejercicio 3 — Organizer
  4. Ejercicio 4 — ABAC
  5. Ejercicio 5 — Escalada
  6. Resumen del profesor

Ejercicio 1 — La matriz (la del profesor)

AcciónBUYERORGANIZERSTAFFSYSTEM
Listar/detalle eventosísísísí
Crear eventonosísíno
Editar eventonosolo el suyo (+ABAC 24h)síno
Disponibilidadsísísísí
Crear reservasí (para sí)sísíno
Ver reserva ajenanonosín/a
Cancelar reservasolo la suyanosíjob de expiración (PENDING vencidas)
Iniciar pagosolo su reservanonowebhook firmado
Ver ventasnode sus eventossíno
Reembolsarnonosíno
Cambiar rolnonosíno

Las celdas con condición son las que el framework no trae: viven en objeto + servicio.

Ejercicio 2 — IDOR

  1. Si respondió 200 con la reserva de B: agujero en vivo (y la 9172 de B con sus datos). Es EL bug de seguridad más frecuente en APIs.
  2. Con get_queryset filtrado: 404. El 404 evita confirmar la existencia del recurso (no filtra que existe pero no es tuyo; el 403 sobre IDs enumerables confirma). Con UUIDs (13) + 404: nada que enumerar ni confirmar.
  3. Orden: permisos de vista → filtrado de queryset (el objeto ni se busca) → has_object_permission (por si el objeto llegó por otra vía). Ambas capas: si mañana alguien usa get_object_or_404 sin filtro, el permission de objeto salva.

Ejercicio 3 — Organizer

  1. B sobre evento de A: 404 (queryset del organizador filtrado por organizer=request.user) o 403 con permission de objeto — el 404 es preferible por la misma fuga. B sobre el suyo: 200.
  2. organizer = serializers.HiddenField(default=serializers.CurrentUserDefault()) o read_only + asignación en perform_create. Si acepta organizer del input: cualquier organizador podría crear eventos "a nombre de" otro (y con suerte de STAFF): escalada por mass assignment.

Ejercicio 4 — ABAC

  1. Vive en el servicio (update_event) — no en el serializer ni en el permission (necesita estado del mundo y será llamada desde múltiples entradas). Lanza DomainError("event-locked", 422,...) (13).
  2. draft → 200; publicado a 48h → 200; a 2h → 422 event-locked con detail "El evento se bloquea a la edición 24h antes de comenzar".
  3. Política sugerida: STAFF cancela siempre; el organizador puede cancelar si faltan >48h; el efecto: estado CANCELLED + todas las reservas activas → CANCELLED con reembolso automático en cola (transacciones por reserva, la 10; la notificación por la 17/29). El ADR registra la decisión de reembolso automático vs manual.

Ejercicio 5 — Escalada

  1. Cerrado: role fuera de los fields del serializer de usuario; endpoint de staff aparte (POST /api/staff/users/{id}/role/) con audit log (quién, a quién, cuándo, antes/después — la 45/56).
  2. Test de regresión:
python
def test_buyer_no_puede_elescarse(self):
    self.client.force_authenticate(self.buyer)
    resp = self.client.patch("/api/users/me/", {"role": "STAFF"}, format="json")
    self.buyer.refresh_from_db()
    self.assertEqual(resp.status_code, 200)     # edita lo que puede (nombre...)
    self.assertEqual(self.buyer.role, "BUYER")  # el rol NO cambió

Resumen del profesor

  • Matriz primero: los agujeros se ven en la tabla, no en el código.
  • Tres capas: endpoint (roles), objeto (propiedad), servicio (regla viva). Las tres, siempre.
  • IDOR se cierra con queryset: el recurso ajeno no existe (404).
  • La escalada clásica: campo sensible aceptado del input. Los roles se cambian por puertas de staff con auditoría.

Después: Lección 22 — OWASP Top 10.