🛠 Unidad ISS-25 · Pruebas 401 y 403 — capa 🛠 CONSTRUIR
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-25/aprendizaje/. La página publica solo el ISS técnico.El propio ISS técnico cierra aquí la Fase II — Auth con RBAC (sección «Cierre de la Fase II — Auth con RBAC»). No hay una unidad de cierre aparte.
ISS-25 — Pruebas 401 y 403
Objetivo
Dejar por escrito, en tests, la diferencia entre no tener identidad y no tener permiso.
401 corresponde a OPEN mal credenciado o a un JWT que no identifica un usuario active. 403 corresponde solo al acceso JWT con token + RBAC: el JWT es válido y la concesión no.
Requisitos
- ISS-24 superado.
- No basta con probarlo una vez en Swagger.
Construcción
AuthFlowTests ya cubre login, refresh, reuso, logout y perfil. Esos métodos se escribieron en el ISS de cada endpoint. Este ISS agrega RbacTests al final de apps/security/tests.py. El usuario está autenticado y, sin concesión, GET /api/clients/ responde 403. Sin identidad, el mismo GET responde 401. Cada eslabón inactive de la cadena (rol, RoleUser, ResourceRole, Resource) vuelve a 403.
ARCHIVO: apps/security/tests.py
AL FINAL DEL ARCHIVO, AGREGAR:
class RbacTests(APITestCase):
def setUp(self):
self.user = create_active_user()
self.client.force_authenticate(user=self.user)
def test_missing_grant_is_403_and_missing_identity_is_401(self):
denied = self.client.get("/api/clients/")
self.assertEqual(denied.status_code, status.HTTP_403_FORBIDDEN)
self.client.force_authenticate(user=None)
anonymous = self.client.get("/api/clients/")
self.assertEqual(anonymous.status_code, status.HTTP_401_UNAUTHORIZED)
El segundo método, test_any_inactive_link_denies, concede GET /api/clients/, comprueba 200 y luego pasa a inactive, uno por uno, el rol, el RoleUser, el ResourceRole y el Resource. Cada uno debe responder 403. Hay que importar grant desde apps.common.testing.
RbacTests cubre:
autenticado sin concesión → 403
sin autenticar → 401
rol inactive → 403
RoleUser inactive → 403
ResourceRole inactive → 403
Resource inactive → 403
Explicación
401 y 403 no se intercambian para “que se vea un error”. 401 significa que la petición no demostró una identidad vigente. 403 significa que la identidad está vigente y la operación no está concedida. Un usuario inactivo con un JWT bien firmado es 401: la identidad que el token nombra ya no está vigente.
Cómo probarlo
Capa HTTP del cierre. El ISS-23 ya demostró el camino que autoriza: RoleUser más ResourceRole y el GET pasa a 200. Aquí se prueba el camino que niega. python manage.py test apps corre también los ISS anteriores. El caso nuevo es RbacTests, sobre GET /api/clients/.
python manage.py test apps.security.tests.RbacTests
│
▼
GET /api/clients/
│
├── sin identidad 401
├── identidad vigente, sin concesión 403
├── concesión active 200
└── uno de estos eslabones inactive
Role · RoleUser · ResourceRole · Resource 403
En Swagger, con la semilla del ISS-24, el mismo corte se ve en GET /api/clients/. El test HTTP es el que apaga cada eslabón. Swagger muestra los dos casos que no piden editar la matriz a mano.
swagger-ui después de seed_rbac
│
├── sin Authorize 401
├── Authorize de un usuario sin rol admin 403
└── Authorize del usuario con rol admin 200
Criterios de aceptación
- AC-25-01: la suite
appspasa. - AC-25-02: existe un test cuyo resultado esperado es 401.
- AC-25-03: existe un test cuyo resultado esperado es 403.
- AC-25-04: el 403 no aparece cuando falta el token.
Verificación
Suite completa en PostgreSQL: 30 tests OK, ya con el CRUD HTTP de cada entidad. MySQL y SQL Server quedaron en 24 OK en la corrida anterior, antes de esos casos.
Evidencias
- EVI-25-01:
Ran 30 tests/OKen PostgreSQL. - EVI-25-02:
RbacTestsOK en la repetición de PostgreSQL.
GATE
| AC | Verificación | Evidencia | Resultado |
|---|---|---|---|
| AC-25-01 | suite | EVI-25-01 | PASS |
| AC-25-02 | test 401 | EVI-25-02 | PASS |
| AC-25-03 | test 403 | EVI-25-02 | PASS |
| AC-25-04 | anónimo no es 403 | EVI-25-02 | PASS |
Cierre de la Fase II — Auth con RBAC
Esta fase agrega identidad y autorización sobre el negocio ya cerrado en el ISS-14. Aquí quedan definidos los tres accesos, y cada endpoint de la API usa uno solo.
| Acceso | Endpoints |
|---|---|
| OPEN | POST /api/auth/login/, /api/auth/refresh/, /api/auth/logout/, esquema y Swagger |
| JWT con token | GET/PATCH /api/auth/profile/ |
| JWT con token + RBAC | Clientes, catálogo, disponibles, ventas y el CRUD de usuarios, roles, recursos y asignaciones |
Qué queda construido
Custom User con hash de Django. Access token no persistido. Refresh con hash, familia, rotación y revocación por reuso. Logout del refresh. Perfil solo con JWT, sin matriz. Matriz Role → Resource. Permiso HasResourceAccess sobre el negocio. 401 sin identidad. 403 con identidad y sin concesión. Semilla tomada de resolve().route. Swagger con Bearer.
Verificación
python manage.py check
python manage.py test apps
python manage.py spectacular --validate
python manage.py seed_rbac
| Pieza | Resultado |
|---|---|
| Login username y email | PASS |
| Usuario inactive y clave mala = 401 | PASS |
| Rotación y reuso de familia | PASS |
| Logout 204 y refresh posterior 401 | PASS |
| Perfil JWT con token, y access inválido tras desactivar | PASS |
| 401 frente a 403 | PASS |
| Eslabones inactive | PASS en PostgreSQL para los cuatro eslabones |
| Esquema OpenAPI | PASS (--validate exit 0) |
| PostgreSQL | 30 tests OK, con el CRUD de cada entidad |
| MySQL y SQL Server | 24 tests OK en la corrida anterior |
| Oracle | NO VERIFICADO EN ESTE ENTORNO |
GATE AUTH/RBAC
| AC | Verificación | Evidencia | Resultado |
|---|---|---|---|
| Hash de contraseña y de refresh | tests de login | suite | PASS |
| OPEN, JWT con token, JWT con token + RBAC | contrato y tests | suite | PASS |
| Deny by default | RbacTests |
suite | PASS |
| 401 distinto de 403 | tests explícitos | suite | PASS |
| Swagger Bearer | extensión OpenAPI | --validate |
PASS |
| Oracle | conexión | ORA-28000 y ORA-01017 | NO VERIFICADO |
El GATE de esta fase se supera en los tres motores donde la suite corrió. No incluye una ejecución Oracle.
✅ GATE de la unidad ISS-25 — este bloque no añade ningún criterio nuevo.
Las condiciones de cierre son las de esta misma página:
- 📋 Criterios de aceptación → Criterios de aceptación
- ✅ GATE → GATE
- 🔎 Verificación → Verificación
Esta es la última unidad de la ruta.
Navegación de la ruta: ← ISS-24 · 🛠 Construir · ↑ Ruta Django