Ir al contenido

Acciones de servidor seguras: automatizar sin abrir vulnerabilidades

Validación, permisos y cuándo usar crons vs server actions
28 de septiembre de 2026 por
Acciones de servidor seguras: automatizar sin abrir vulnerabilidades
ATEMI, Aitor Atencia

Odoo · Automatización · Seguridad

Las acciones de servidor (ir.actions.server) y las reglas automatizadas permiten automatizar casi cualquier cosa sin desplegar un módulo. Precisamente por eso son un riesgo: código Python que vive en la base de datos, sin control de versiones, sin tests y editable desde el navegador por cualquiera con permisos de administración.

Logo Odoo
La regla de oro: la acción de servidor dispara, el modelo decide.

El problema

Código en la base de datos es código que nadie revisa

Una acción de servidor de tipo Ejecutar código se evalúa con safe_eval: no permite import ni acceder a atributos que empiezan por guion bajo, pero sí todo lo que el ORM expone al usuario que la ejecuta. Si alguien escribe ahí un records.sudo().write(...) o un env['res.partner'].search([]).unlink(), se ejecutará. Y como no está en Git, la próxima persona que la toque no sabrá qué cambió ni por qué.

# ❌ Lógica de negocio completa dentro de la acción de servidor
for rec in records:
    if rec.amount_total > 1000 and not rec.partner_id.vat:
        rec.sudo().write({'state': 'blocked'})
        env['mail.mail'].sudo().create({
            'email_to': 'admin@empresa.com',
            'subject': 'Pedido bloqueado %s' % rec.name,
        }).send()

Este bloque tiene tres problemas: el sudo() se salta las record rules sin justificación, el email se envía de forma síncrona dentro de la transacción y ninguna de estas reglas de negocio tiene un test que la proteja.

El patrón

La acción llama a un método; el método vive en el módulo

La forma segura es reducir la acción de servidor a una sola línea que invoca un método público del modelo. La lógica queda en Python versionado, con sus tests, su revisión de código y sus comprobaciones de permisos explícitas.

# models/sale_order.py
from odoo import _, models
from odoo.exceptions import UserError


class SaleOrder(models.Model):
    _inherit = "sale.order"

    def action_block_risky_orders(self):
        if not self.env.user.has_group("sales_team.group_sale_manager"):
            raise UserError(_("Only sales managers can block orders."))
        risky = self.filtered(
            lambda o: o.amount_total > 1000 and not o.partner_id.vat
        )
        risky.write({"state": "cancel"})
        for order in risky:
            order.message_post(body=_("Blocked: customer without VAT."))
        return True
# Contenido de la acción de servidor (Ejecutar código)
records.action_block_risky_orders()
<record id="action_server_block_risky_orders" model="ir.actions.server">
    <field name="name">Block risky orders</field>
    <field name="model_id" ref="sale.model_sale_order"/>
    <field name="binding_model_id" ref="sale.model_sale_order"/>
    <field name="state">code</field>
    <field name="code">records.action_block_risky_orders()</field>
</record>

Permisos

¿Con qué usuario se ejecuta?

Esta es la pregunta que hay que hacerse siempre. Una acción lanzada desde el menú Acción se ejecuta como el usuario que pulsa el botón, con sus ACL y record rules. Un cron se ejecuta como el usuario configurado en el propio ir.cron, que suele ser OdooBot o el administrador: todo lo que haga tendrá permisos amplios. Por eso las comprobaciones de grupo dentro del método no son redundantes: son lo único que impide que una acción pensada para gerentes la dispare cualquier usuario interno.

  • Restringe la visibilidad de la acción a los grupos que deben verla.
  • Comprueba el grupo también dentro del método: ocultar un botón no es control de acceso.
  • Si necesitas sudo(), aplícalo a la operación concreta que lo requiere, nunca a todo el recordset de entrada.

Decisión

Cron, regla automatizada o acción manual

No todas las automatizaciones deberían dispararse igual. La elección depende de qué la desencadena y de cuánto trabajo hace por ejecución.

MecanismoDisparadorÚsalo paraEvítalo si
Acción de servidor manualBotón del menú AcciónOperaciones puntuales sobre registros seleccionadosDebe ocurrir sin intervención humana
Regla automatizadaCrear, modificar, campo cambiado, fechaReacciones inmediatas y baratas a un cambioLlama a APIs externas o procesa miles de registros
ir.cronIntervalo de tiempoLotes, sincronizaciones, limpiezasEl usuario espera ver el resultado al guardar

Una regla automatizada al modificar se ejecuta dentro de la misma transacción que el write del usuario: si llama a un servicio externo lento, el formulario se queda colgado; si falla, el guardado entero se revierte. Para eso existe el cron (ver cron jobs y colas).

Resumen

Una acción de servidor segura tiene una línea de código, vive en un XML del módulo, llama a un método con tests y comprobaciones de grupo, y nunca hace sudo() sobre todo lo que recibe. Si la automatización es lenta o externa, va a un cron; si es inmediata y barata, a una regla automatizada.

en Odoo
Docker para Odoo: producción vs desarrollo
Stack ideal, composición de contenedores y diferencias de configuración