fix(permisos): los endpoints de datos ya no se abren sin el módulo asignado
Primera tanda del hueco reportado en el commit anterior: el permiso tapaba
la página HTML pero no los datos. Cualquier usuario con sesión podía pedir
GET /app/loadusers o DELETE /app/doc/paginas/:id escribiendo la URL, sin
tener el submódulo asignado.
No alcanzaba con poner MenuMiddleware en esas rutas: compara la ruta pedida
contra los submódulos del rol, y el endpoint de datos vive en otra ruta que
la pantalla (/app/loadusers alimenta /app/users). Compararla consigo misma
nunca coincidiría y dejaría afuera hasta a quien sí tiene el permiso.
RequiereModulo("/app/users") declara en la ruta a qué pantalla pertenece el
endpoint, y valida ese submódulo. El administrador sigue entrando a todo,
igual que en MenuMiddleware.
Esta tanda cubre lo que más duele: identidad y permisos (usuarios, roles,
módulos, submódulos), donde una fuga es escalada de privilegios, y
Documentación, que es el módulo que disparó la auditoría.
Quedan las otras tandas (contabilidad, facturas, clientes, servidores...).
Se hace por partes a propósito: cerrar 400 rutas de una puede dejar gente
afuera de pantallas que hoy usa.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
6942c04d27
commit
55f28f5c06
@@ -133,3 +133,51 @@ func PuedeVerRuta(ruta string, permitidas []string) bool {
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// RequiereModulo protege un endpoint declarando a qué pantalla pertenece.
|
||||
//
|
||||
// MenuMiddleware compara la ruta pedida contra los submódulos del rol, y eso
|
||||
// alcanza para las páginas porque su URL ES la del submódulo. Pero los
|
||||
// endpoints de datos viven en otra ruta (/app/loadusers alimenta la pantalla
|
||||
// /app/users), así que compararlos contra sí mismos nunca coincidiría y
|
||||
// dejaría afuera hasta a quien sí tiene el permiso.
|
||||
//
|
||||
// Por eso el submódulo se declara en la ruta:
|
||||
//
|
||||
// protected.Get("/loadusers", middlewares.RequiereModulo("/app/users"), controllers.GetUsers)
|
||||
//
|
||||
// Sin esto, cualquier usuario con sesión podía pedir los datos de una pantalla
|
||||
// que no tiene asignada escribiendo la URL a mano: el permiso solo tapaba el
|
||||
// HTML, no la información.
|
||||
func RequiereModulo(urlSubmodulo string) fiber.Handler {
|
||||
return func(c *fiber.Ctx) error {
|
||||
user, err := auth.User(c)
|
||||
if err != nil || user == nil {
|
||||
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{
|
||||
"error": true, "message": "Usuario no autenticado",
|
||||
})
|
||||
}
|
||||
// Mismo criterio que MenuMiddleware: el administrador entra a todo.
|
||||
if user.IsAdmin {
|
||||
return c.Next()
|
||||
}
|
||||
|
||||
datos, err := models.FindUserByID(user.ID)
|
||||
if err != nil {
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
|
||||
"error": true, "message": "Error al obtener datos del usuario",
|
||||
})
|
||||
}
|
||||
var urls []string
|
||||
for _, s := range datos.Role.Submodules {
|
||||
urls = append(urls, s.Url)
|
||||
}
|
||||
|
||||
if !PuedeVerRuta(urlSubmodulo, urls) {
|
||||
return c.Status(fiber.StatusForbidden).JSON(fiber.Map{
|
||||
"error": true, "message": "No tenés permiso para acceder a esta información",
|
||||
})
|
||||
}
|
||||
return c.Next()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,3 +47,26 @@ func TestPuedeVerRutaSinPermisos(t *testing.T) {
|
||||
t.Error("una URL vacía no debería habilitar nada")
|
||||
}
|
||||
}
|
||||
|
||||
// RequiereModulo compara contra el submódulo DECLARADO en la ruta, no contra
|
||||
// la ruta pedida. Es lo que permite proteger /app/loadusers con el permiso de
|
||||
// la pantalla /app/users: compararlo consigo mismo nunca coincidiría y
|
||||
// bloquearía incluso a quien sí tiene el permiso.
|
||||
func TestRequiereModuloUsaElSubmoduloDeclarado(t *testing.T) {
|
||||
permisos := []string{"/app/users"}
|
||||
|
||||
// El endpoint de datos vive en otra ruta que la pantalla.
|
||||
if !PuedeVerRuta("/app/users", permisos) {
|
||||
t.Error("con el permiso de la pantalla se debe poder pedir sus datos")
|
||||
}
|
||||
// Y comparar la ruta del endpoint contra el permiso NO coincide: por eso
|
||||
// hace falta declarar el submódulo en vez de mirar c.Path().
|
||||
if PuedeVerRuta("/app/loadusers", permisos) {
|
||||
t.Error("la ruta del endpoint no coincide con la del submódulo — " +
|
||||
"si coincidiera, no haría falta RequiereModulo")
|
||||
}
|
||||
// Sin el permiso, no se accede.
|
||||
if PuedeVerRuta("/app/users", []string{"/app/clientes"}) {
|
||||
t.Error("con otro permiso no se debe acceder a los datos de usuarios")
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user