Saltar a contenido

🛠 Unidad ISS-21 · HasResourceAccess — capa 🛠 CONSTRUIR

✅ GATE de la unidad

Capa Página Para qué
🧠 Aprender — no está en la fuente comprender, explicar y relacionar
🛠 Construir esta página ejecutar, programar y verificar
✅ GATE Condiciones de cierre condición para pasar al bloque siguiente

Esta es la guía ejecutable. El cuerpo de abajo es el ISS técnico verbatim: comandos, rutas, versiones, verificaciones y criterios, sin simplificar.

Guía de estudio. Esta unidad no tiene cuaderno en material/django/iss/ISS-21/aprendizaje/. La página publica solo el ISS técnico.


ISS-21 — HasResourceAccess

Objetivo

Autorizar por método y patrón de ruta, con negación por defecto. Esta clase es la mitad de autorización del acceso JWT con token + RBAC. Sola no autentica: el JWT ya tuvo que identificar al usuario. Sin esta clase, el perfil seguiría siendo solo JWT con token.

Requisitos

  • ISS-20 superado.
  • Los modelos del ISS-16 existen.

Construcción

apps/security/permissions.py es nuevo. El archivo completo es la clase HasResourceAccess que compara request.method con request.resolver_match.route y exige activos al usuario, al RoleUser, al Role, al ResourceRole y al Resource. HEAD se evalúa como GET. OPTIONS, si ya hay usuario, no busca recurso.

cat > apps/security/permissions.py <<'EOF'
from rest_framework.permissions import BasePermission

from apps.common.status import RecordStatus
from apps.security.models import ResourceRole


class HasResourceAccess(BasePermission):
    message = "No tiene autorización para ejecutar esta operación."

    def has_permission(self, request, view):
        user = request.user
        if not user or not user.is_authenticated:
            return False
        if getattr(user, "status", None) != RecordStatus.ACTIVE:
            return False
        if request.method.upper() == "OPTIONS":
            return True

        match = request.resolver_match
        if match is None:
            return False
        method = request.method.upper()
        if method == "HEAD":
            method = "GET"
        return ResourceRole.objects.filter(
            status=RecordStatus.ACTIVE,
            resource__status=RecordStatus.ACTIVE,
            resource__method=method,
            resource__route=match.route,
            role__status=RecordStatus.ACTIVE,
            role__assignments__user=user,
            role__assignments__status=RecordStatus.ACTIVE,
        ).exists()
EOF

HEAD se evalúa como GET. OPTIONS, con usuario ya autenticado, no busca recurso: es el preflight, no una operación de negocio.

Explicación

DRF convierte el fallo así:

hay authenticators y ninguno autenticó  →  401 NotAuthenticated
usuario autenticado y el permiso falla  →  403 PermissionDenied

Por eso la misma clase produce 401 o 403 según haya identidad. No se compara la URL /api/clients/15/ con un texto guardado: se compara resolver_match.route, que en este proyecto es api/clients/<int:pk>/.

Si el filtro no encuentra una cadena completa y activa, exists() es falso. No hay un permiso implícito.

Request
   ↓
JWT
   ↓
User active?
   ↓
method + route
   ↓
¿todos los eslabones active?
   ├── no → 403
   └── sí → endpoint

Criterios de aceptación

  • AC-21-01: usuario autenticado sin concesión recibe 403 en GET /api/clients/.
  • AC-21-02: el mismo request sin usuario recibe 401.
  • AC-21-03: con la concesión, la lista responde 200.
  • AC-21-04: apagar el rol, el RoleUser, el ResourceRole o el Resource vuelve a 403.

Verificación

RbacTests en PostgreSQL, incluyendo el caso del recurso inactivo reejecutado al final. MySQL y SQL Server pasaron la suite cuando el test ya cubría rol, asignación y concesión; el caso del Resource inactivo se reconfirmó en PostgreSQL.

Evidencias

  • EVI-21-01: RbacTests PASS.

GATE

AC Verificación Evidencia Resultado
AC-21-01 403 EVI-21-01 PASS
AC-21-02 401 EVI-21-01 PASS
AC-21-03 concesión activa EVI-21-01 PASS
AC-21-04 cada eslabón inactivo EVI-21-01 PASS

✅ GATE de la unidad ISS-21 — este bloque no añade ningún criterio nuevo.

Las condiciones de cierre son las de esta misma página:

Con el GATE en verde queda habilitado el bloque siguiente de la ruta.

Navegación de la ruta: ← ISS-20 · 🛠 Construir · ↑ Ruta Django · → ISS-22 · 🛠 Construir