Permisos
Sistema de Permisos - Agregar un feature a la DB
El sistema funciona con intersección de permisos. Un usuario puede hacer algo solo si TANTO su tipo de usuario COMO su rol específico lo permiten.
Tablas principales
funcionalidades_plataforma- Define los features de la plataforma (actividades, ensayos, stocks, etc.)funcionalidades- Define todos los permisos de los features (leer, escribir, borrar, etc.)tipo_usuario- Define límites máximos de permisos por tipo de usuario (Free, Pro, etc.)roles- Define conjuntos de permisos asignables (Admin, Editor, Solo lectura, etc.)permisos- Asigna roles a usuarios en lotes específicospermisos_estancias- Asigna roles a usuarios en campos específicosfuncionalidades_plataforma_espacios- Define qué funcionalidades están habilitadas por espacio
Agregar nuevo feature
Paso 1: Crear el feature
Agregar el feature en la tabla funcionalidades_plataforma. El parámetro "base" indica si se agrega automáticamente a nuevos espacios.
INSERT INTO funcionalidades_plataforma (funcionalidad, base)
VALUES ('trials', true);
-- Supongamos que se creó con ID 10
Paso 2: Habilitarlo en espacios
Agregar el feature a los espacios donde debe estar disponible.
UPDATE funcionalidades_plataforma_espacios
SET funcionalidades = funcionalidades || ARRAY[10]
WHERE workspace_id = 1;
Paso 3: Agregarlo al tipo de usuario
El tipo de usuario debe tener el feature habilitado en funcionalidades_plataforma.
UPDATE tipo_usuario
SET funcionalidades_plataforma = funcionalidades_plataforma || ARRAY[10]
WHERE id = 204; -- Usuario Auravant Pro
Agregar permisos del feature
Definir los permisos específicos (crear, leer, editar, eliminar).
Paso 1: Definir los permisos
Insertar las funcionalidades básicas. Los campos son: id, nombre, tipo de permiso (A=Agregar, L=Leer, M=Editar, B=Borrar), código del permiso, habilitado para aurapps, y entity.
INSERT INTO funcionalidades VALUES
(370, 'Crear ensayos', 'A', 'TA', false, 'Trials'),
(371, 'Leer ensayos', 'L', 'TL', false, 'Trials'),
(372, 'Modificar ensayos', 'M', 'TM', false, 'Trials'),
(373, 'Eliminar ensayos', 'B', 'TB', false, 'Trials');
Paso 2: Agregarlos al tipo de usuario
El tipo de usuario debe tener estos permisos en permisos_default:
UPDATE tipo_usuario
SET permisos_default = permisos_default || ARRAY[370,371,372,373]
WHERE id = 204; -- Usuario Pro
Paso 3: Agregarlos a los roles
Asignar los permisos a cada rol según corresponda:
-- Rol Admin: todos los permisos
UPDATE roles
SET permisos_default = permisos_default || ARRAY[370,371,372,373]
WHERE id = 1;
-- Rol Editor: crear, leer, editar (sin eliminar)
UPDATE roles
SET permisos_default = permisos_default || ARRAY[370,371,372]
WHERE id = 3;
-- Rol Solo lectura: solo leer
UPDATE roles
SET permisos_default = permisos_default || ARRAY[371]
WHERE id = 2;
-- Rol Agregado: crear y leer (sin editar ni eliminar)
UPDATE roles
SET permisos_default = permisos_default || ARRAY[370,371]
WHERE id = 4;
Cómo llegan los permisos al frontend
Los permisos llegan al front a través de dos endpoints principales:
getUserData - Permisos generales
Devuelve los permisos generales del usuario calculados como intersección entre su tipo de usuario y roles. Tambien devuelve los features habilitados haciendo una intersección entre los de su tipo de usuario y los del espacio:
{
"funcionalidades": [1, 2, 3, 45, 370, 371, 372],
"funcionalidades_plataforma": [10, 15, 20],
"uid": 141555,
}
getFields - Permisos específicos por campo y lote
Devuelve los permisos específicos que tiene el usuario sobre cada campo y lote, considerando su rol en cada lugar:
{
"user": {
"farms": {
"226055": {
"name": "Campo 1",
"permissions": [1, 2, 3, 370, 371],
"fields": {
"777169": {
"name": "Lote 1",
"permissions": [1, 2, 3, 370, 371, 372, 373]
}
}
}
}
}
}
Diferencias importantes:
getUserData: permisos generales del usuariogetFields: permisos específicos por campo/lote- Los permisos pueden variar según el rol del usuario en cada ubicación
Validar permisos en el backend
Para validar permisos en el backend se usa la clase Permissions que busca automáticamente los permisos del usuario.
Validar permiso en lote específico:
from commons.permisos import Permissions
# Verificar si el usuario puede crear ensayos en un lote
user_permissions = Permissions.fields(user_id=141555, fields=777169)
if 370 in user_permissions.get(777169, []):
# Usuario puede crear ensayos en este lote
pass
else:
raise ApiErr(2, "Sin permisos para crear ensayos")
Validar permiso en múltiples lotes:
# Verificar permisos en varios lotes
field_ids = [777169, 777170, 777171]
user_permissions = Permissions.fields(user_id=141555, fields=field_ids)
for field_id in field_ids:
if 370 not in user_permissions.get(field_id, []):
raise ApiErr(2, "Sin permisos en lote {}".format(field_id))
Validar permiso usando helper:
from commons.permisos import validar_permisos
# Validar usando código del permiso
if not validar_permisos(user_id=141555, codigo="TA", lote=777169):
raise ApiErr(2, "Sin permisos para crear ensayos")
Servicio interno de permisos (/services/permissions)
Además de la validación en backend con la clase Permissions, existe un servicio
interno pensado para que otros servicios (notificaciones, tareas, etc.)
consulten permisos vía HTTP. Todos los endpoints:
- Cuelgan del blueprint
blueprint_internal(aurapi_src/apis/permissions.py), con prefijo/services/permissions. - Usan
ResourceAura+@ApiAura(autenticación/validaciones estándar). - Validan el body con JSON Schema (
aurapi_src/schemas/permissions.py). - Reutilizan el mismo motor de cálculo (
commons/permisos.py), por lo que respetan grupos de supervisión/colaboración y herencia campo → lote.
Los permisos que devuelven estos endpoints son ids de la tabla
funcionalidades(FX). La letra del código de cada FX indica el tipo de acción:A=Agregar/crear,L=Leer,M=Modificar,B=Borrar. No hay un único nivel "lectura/escritura/admin"; el acceso se infiere del conjunto de FX.
Endpoints existentes
| Endpoint | Dirección | Recibe | Devuelve |
|---|---|---|---|
POST /verify/users | usuario → FX de usuario | user_id | fxs (lista de FX a nivel usuario) |
POST /verify/fields | usuario → lotes | user_id, fields (ids) | fxs {field_id: [fx...]} |
POST /verify/farms | usuario → campos | user_id, farms (ids) | fxs {farm_id: [fx...]} |
POST /verify/users/v2 | usuario → FX de usuario | user_id | igual que v1 |
POST /verify/fields/v2 | usuario → lotes | user_id, fields (uuid) | fields {field_id: {...}} |
POST /verify/farms/v2 | usuario → campos | user_id, farms (uuid) | farms {farm_id: {...}} |
POST /verify/field-users | lotes → usuarios | user_id (array), fields_id (array) | {field_id: [{user_id, fxs, codes}]} (o {field_id: [user_id]} con lite) |
Parámetros opcionales comunes: ver_id / dev_id (para intersecar con el
claimset de una extensión aurapp).
POST /verify/field-users (lote → usuarios)
Responde la pregunta inversa a verify/fields: dado un conjunto de lotes y un
conjunto de usuarios candidatos, qué usuarios tienen acceso a cada lote y con
qué FX. Pensado para casos como notificaciones ("¿a qué usuarios les notifico
sobre este lote?").
Request:
{
"user_id": [15, 16, 17, 18],
"fields_id": [1, 2, 3],
"fx": 30,
"lite": false,
"ver_id": null,
"dev_id": null
}
user_id(obligatorio): array de ids de usuario candidatos.fields_id(obligatorio): array de ids de lote.fx(opcional): id de una funcionalidad. Si se envía, un usuario solo se incluye si tiene esa FX puntual sobre el lote (igual se devuelve el set completo de sus FX).lite(opcional, defaultfalse): si estrue, la respuesta lista solo los ids de usuario por lote, sin el detalle de FX.ver_id/dev_id(opcionales): contexto de extensión.
Response (por defecto, lite=false):
{
"2": [
{"user_id": 16, "fxs": [30, 32], "codes": ["LOA", "LOM"]},
{"user_id": 17, "fxs": [30], "codes": ["LOA"]}
],
"3": [
{"user_id": 15, "fxs": [30, 31, 32], "codes": ["LOA", "LOB", "LOM"]}
]
}
- La clave es el
field_id(string en JSON). Los lotes sin usuarios con acceso se omiten. - Por cada usuario con acceso:
fxs(ids defuncionalidades) ycodes(códigos legibles equivalentes, resueltos conbidirectional_fx_map). Si una FX no está mapeada enCODE_ID_MAP, aparece enfxspero no encodes.
Response (lite=true):
{
"2": [16, 17],
"3": [15]
}
- Mismo criterio de acceso y de omisión de lotes vacíos, pero el valor por lote es solo la lista de ids de usuario.
Implementación: itera Permissions.fields(uid, fields_id) por cada usuario e
invierte el resultado a {field_id: [usuarios]}. Hace N consultas (una por
usuario), adecuado para volúmenes típicos de notificaciones; si se necesita
escalar a cientos de usuarios por request habría que reescribirlo como una única
query multi-usuario.