Saltar a contenido

Seguridad de la WebAPI: lo que el rol no puede, la API tampoco 10.0+

Desde la versión 10, la web API aplica la seguridad del rol del token igual que la interfaz: lo que un rol no puede ver o ejecutar en pantalla, tampoco lo puede pedir por la API, y la respuesta lo dice con un estado HTTP propio y una frase que explica el motivo. El OpenAPI de la aplicación (GET /webapi) se recorta también por rol: una integración solo ve anunciado lo que de verdad puede usar.

Esta página es para quien integra con la aplicación (n8n, un ERP, un script) y para quien la administra. Los asistentes de IA usan el mismo mecanismo: ver Asistentes de IA (MCP).


1. Quién entra en la API

Se configura en la herramienta WebAPI del área Seguridad del panel de control (en flexy2022, Admin Work Area › Security › WebAPI):

La pantalla WebAPI: interruptor general, datos autorizados y personas autorizadas
Arriba el interruptor general y los tiempos; a la izquierda lo que se expone, a la derecha quién entra
Dónde Qué decide
Enable WebAPI (WebAPI_Enabled) La API entera, encendida o apagada.
Authorized Data Qué objetos, vistas y procesos se publican y qué se puede hacer con cada objeto (ver, listar, crear, editar, borrar, imprimir). Lo no publicado no existe para la API.
Authorized People, columna del ojo Qué roles o usuarios pueden usar la API (WebAPI_Roles, WebAPI_Users; el usuario manda sobre su rol).
Authorized People, columna del robot El permiso de asistentes de IA (MCP), aparte del de la API: un rol puede tener uno sin el otro.

Y debajo de todo eso, la seguridad de siempre del rol: los permisos de objeto (ver la ficha, ver la lista), los filtros de fila y los permisos de cada proceso. Publicar un objeto en la API no se salta nada de eso.

Solo las propiedades definidas

La API sirve solo las propiedades definidas del objeto y sus claves: en las listas, en los registros, en los esquemas de alta y edición y en el OpenAPI. Una columna de la tabla que no sea propiedad del objeto no sale, aunque esté en la vista. Lo mismo vale para los asistentes de IA (MCP). Lo decide el ajuste WebAPI_OnlyDefinedProperties, que viene activado y se da por activado si falta.

WebAPI_OnlyDefinedProperties Qué sirve la API
true (por defecto) Las propiedades definidas del objeto y sus claves
false Todas las columnas de la tabla del objeto, sean propiedad o no, como antes de la versión 10

Para ocultar a la API una propiedad que sí está definida, sigue estando WebApiHidden en la propiedad.

Si una integración lee columnas que no son propiedad

Antes de la versión 10 la API devolvía todas las columnas de la tabla. Si una integración lee alguna que no es propiedad del objeto, defínela como propiedad. Poner el ajuste a false también la devuelve, pero devuelve con ella todas las demás columnas de la tabla.

El token se pide con POST /token (grant_type=password, username, password) y viaja en Authorization: Bearer ….


2. Las negativas: un estado por motivo, y la frase

Cada negativa lleva su estado HTTP y una frase en el cuerpo que dice qué se negó y por qué. Respuestas reales:

403: el rol no puede ver esa lista.

HTTP
GET /webapi/list/sysUser
Authorization: Bearer <token de un usuario con rol users>

HTTP/1.1 403 Forbidden
{"Content":{"Title":"Internal server error","Message":"Unauthorized collection sysUsers for role users","StackTrace":"","InnerMessage":null,"Msgtype":1},"Type":"Ajax"}

400: un proceso de objeto pedido sin su objeto.

HTTP
POST /webapi/exec/SyncData

HTTP/1.1 400 Bad Request
{"Content":{"Title":"Bad request","Message":"Process SyncData is defined on sysOfflineSync: send the object and the record (id or filter) to run it on","StackTrace":"","InnerMessage":"","Msgtype":0},"Type":"Ajax"}

404: el registro sobre el que se pide el proceso no existe.

HTTP
POST /webapi/exec/SyncData/sysOfflineSync/999999

HTTP/1.1 404 Not Found
{"Content":{"Title":"Not found","Message":"sysOfflineSync record not found: [Offline_Sync].[SyncId]='999999'","StackTrace":"","InnerMessage":"","Msgtype":0},"Type":"Ajax"}

La tabla completa:

Estado Cuándo Frase
401 Token que falta, caducado o inválido. —
403 El rol no puede ver la lista del objeto. Unauthorized collection {Colección} for role {Rol}
403 El rol no puede ver ese registro (permiso de objeto o filtro de fila). Unauthorized object {Objeto} for role {Rol}
403 El rol no puede ejecutar el proceso sobre ese objeto. Unauthorized process {Proceso} on {Objeto} for role {Rol}
400 Proceso definido sobre un objeto, pedido sin el objeto. Process {Proceso} is defined on {Objeto}: send the object and the record (id or filter) to run it on
400 Proceso de colección sin filter: correría sobre todos los registros. Please send filter to select the {Objeto} records to run {Proceso} on
404 El registro del proceso no existe (o el filtro de fila del rol lo oculta). {Objeto} record not found: {where}
409 El registro existe, pero su estado no permite el proceso (EnabledValues, DisabledValues, SQLEnabled). Process {Proceso} is not available for this {Objeto} record ({where}): its state does not allow it

El título del 403

En el 403 el campo Title del sobre todavía dice Internal server error. El estado y el Message son los que valen.

Leer un registro que no existe

GET /webapi/object/{Objeto}/{id} con un id que no existe responde 200 con la ficha vacía (los campos sin valor), como hasta ahora. El 404 es de la ejecución de procesos.


3. Procesos: siempre a través de su objeto

Dos reglas que cierran caminos por los que antes se podía actuar sobre un registro sin pasar por su seguridad:

  • Un proceso definido sobre un objeto solo se ejecuta a través de ese objeto: POST /webapi/exec/{Proceso}/{Objeto}/{id} o, para la colección, POST /webapi/exec/{Proceso}/{Colección}?filter=…. Sin objeto, 400. Un proceso que no está ligado a ningún objeto sigue corriendo como siempre, sin objeto.
  • Un parámetro que es un campo del registro no se puede pisar desde el cuerpo. Si el valor por defecto de un parámetro es exactamente {{Campo}} y ese campo es del objeto, se toma del registro de la ruta y lo que llegue en el cuerpo se ignora. Así, la comprobación de estado y el proceso miran el mismo registro. Los demás parámetros ({{currentDate}}, los que el usuario rellena…) se siguen mandando.

4. El OpenAPI, recortado por rol

GET /webapi devuelve el OpenAPI de la aplicación para el token que lo pide (con Authorization: Bearer …; sin token, el de todo lo publicado):

Lo que el rol no puede Lo que pasa en el OpenAPI
Ni ver la ficha ni ver la lista de un objeto El objeto desaparece: ni rutas ni esquemas.
Ejecutar un proceso La ruta /exec de ese proceso no se anuncia.

Y lo que se anuncia, se describe mejor:

  • Cada vista lleva su descripción (la de Data views), no solo su nombre.
  • Cada proceso dice cuál es y qué hace («Run Proceso (descripción) on a Objeto record by ID»).
  • Los parámetros ocultos de un proceso salen como readOnly, fuera de los obligatorios y con la nota «Hidden: filled by the process…, do not send it».

Ver la ficha sí, la lista no

Si un rol puede ver la ficha de un objeto pero no su lista, el objeto se anuncia entero y la ruta /list/… responde 403 al pedirla. Para que una integración no la vea, quita también el permiso de ficha o no publiques el objeto.


5. Bloqueo por IP, con ámbito

La aplicación tiene una lista de direcciones bloqueadas (Bloqueos de IP, en el área Seguridad del panel de control; tabla Security_IP_Lockout). En la versión 10 vuelve a bloquear: una dirección bloqueada recibe 401 Unauthorized en todo lo que pasa por el Backend, login incluido.

Cada bloqueo tiene un ámbito (Scope):

Ámbito Qué cierra
All Toda la aplicación para esa dirección. Es el de los bloqueos que se añaden a mano.
MCP Solo las rutas del servidor MCP (/mcp y su registro y tokens); el resto de la aplicación sigue abierta para esa dirección.

Los bloqueos también se crean solos: las peticiones sospechosas (lista negra y lista gris de Sentinel, y en el MCP los registros de clientes, los canjes de token fallidos y las llamadas sin sesión) se cuentan por dirección, y al pasar de SentinelMaxBlackRequests o SentinelMaxGreyRequests en poco tiempo la dirección queda bloqueada con el ámbito de lo que la provocó.

Intentos fallidos de pedir un token

50 intentos fallidos de POST /token en 60 segundos desde la misma dirección la bloquean, con ámbito All (toda la aplicación, pantalla de entrada incluida) y sin fecha de fin: sigue bloqueada hasta que un administrador quita la fila en IP Lockouts.

  • Cuentan las credenciales malas: usuario que no existe, contraseña incorrecta, LDAP que rechaza y refresh_token inválido. No cuenta la contraseña correcta de un usuario bloqueado o sin permiso para la API.
  • Cada intento fallido queda en el log de la WebAPI (WebAPI_Logs, tipo TOKEN) con el usuario que se probó y las direcciones por las que llegó la petición, aunque el log de la WebAPI esté apagado. Así se ve de dónde vino un bloqueo.
  • Las direcciones locales (loopback) no se bloquean nunca.
  • La cuenta se lleva en memoria: cada instancia del Backend cuenta por su lado, y un reinicio empieza de cero.
Ajuste Por defecto Con 0
WebAPI_MaxFailedTokenAttempts 50 intentos No se bloquea
WebAPI_FailedTokenInterval 60 segundos No se bloquea

Detrás de un proxy, un bloqueo los cierra a todos

Si la dirección que ve el Backend es la de un proxy o la del Frontend, una sola integración con la contraseña caducada bloquea esa dirección y, con ella, a todos los que entran por ahí. Configura el proxy como se explica abajo.

La dirección del cliente

La dirección que se comprueba y que queda en el log es la del cliente, no la del Frontend: el Frontend se la pasa al Backend. Detrás de un proxy inverso, configura ForwardedHeaders como dice Proxy inverso, o todas las peticiones parecerán venir del proxy.


6. Si ya tienes una integración

Antes Ahora Qué revisar
Lista negada por rol → 200 [] 403 con la frase Un cliente que trataba la lista vacía como «no hay datos» ahora recibe un error: es lo correcto.
Ficha negada → 500 403
Negativa por rol → 401 403 (el 401 queda para el token) Un cliente que renovaba el token al ver un 401 ya no lo hará en las negativas por rol.
Proceso negado, registro inexistente o estado que no lo permite → 500 403, 404, 409
Proceso de colección sin filter → corría sobre todos 400 Manda siempre el filter.
Proceso de objeto sin objeto → corría 400 Usa la ruta con objeto y registro.
Parámetro {{Campo}} mandado en el cuerpo → se usaba Se ignora Elige el registro por la ruta.
Se servían todas las columnas de la tabla Solo las propiedades definidas y las claves (WebAPI_OnlyDefinedProperties) Define como propiedad lo que lea la integración (ver 1).
Intentos fallidos de token sin límite 50 en 60 s bloquean la dirección en toda la aplicación Revisa las credenciales de las integraciones y el proxy (ver 5).