Ir al contenido

Seguridad en controladores web y rutas HTTP en Odoo

auth='user', validación de entrada y whitelist de campos en write
22 de junio de 2026 por
Seguridad en controladores web y rutas HTTP en Odoo
Atemi, Aitor Atencia

Odoo · Seguridad · HTTP

Los controladores Odoo son la frontera entre Internet y tu ORM. Una ruta mal configurada con auth='public', un write sin whitelist o un JSON sin validar puede anular meses de trabajo en ACL y record rules. Esta entrada recoge el patrón que uso en APIs REST reales.

Logo Odoo Escudo seguridad
Cada ruta HTTP decide quién entra y qué puede tocar.

Fundamentos

Anatomía de un controlador Odoo

from odoo import http
from odoo.http import request

class MyController(http.Controller):

    @http.route('/api/v1/items', type='jsonrpc', auth='user', methods=['POST'])
    def list_items(self, **kwargs):
        return request.env['my.item'].search_read([], ['name', 'state'])
ParámetroValores típicosImplicación de seguridad
authuser, public, none¿Hay sesión? ¿Usuario portal o anónimo?
typehttp, jsonrpcCSRF en formularios HTTP vs JSON API
methodsGET, POST, …Restringe verbos; evita GET con side-effects
csrfTrue (default http)Desactivar solo con autenticación alternativa

Regla práctica: auth='user' para portal y backend autenticado; auth='public' solo para landing pages sin datos sensibles; auth='none' casi nunca en módulos custom.

Autenticación

Sesión, API key y entorno del usuario

Patrón híbrido (sesión web + API key para clientes móviles):

def get_authenticated_user():
    api_key = request.httprequest.headers.get('X-API-Key')
    if api_key:
        user_id = request.env['res.users.apikeys'].sudo()._check_credentials(
            scope='rpc', key=api_key
        )
        if user_id:
            return request.env['res.users'].sudo().browse(user_id)
    user = request.env.user
    if user and not user._is_public():
        return user
    return None

def _with_authenticated_env(self, user):
    return request.env(user=user)  # Respeta ACL del usuario

El sudo() queda acotado a validar la API key (sin contexto de usuario). Las operaciones de negocio corren con request.env(user=user).

Autorización

Grupos, dominios y comprobación explícita

READER_GROUP = 'my_module.group_reader'

def _require_reader(self, user):
    if not user.has_group(READER_GROUP):
        return self._json_error('Read access required.', status=403)
    return None

@http.route('/api/v1/items/<int:item_id>', auth='user', type='jsonrpc')
def get_item(self, item_id):
    user, err = self._authenticate()
    if err:
        return err
    if deny := self._require_reader(user):
        return deny
    env = self._with_authenticated_env(user)
    item = env['my.item'].browse(item_id)
    if not item.exists():
        return self._json_error('Not found.', status=404)
    return item.read(['name', 'state'])

ACL + record rules filtran en el ORM; has_group() añade capa de rol cuando la API expone operaciones distintas (lectura vs gestión).

Entrada

Validar y sanitizar el payload

ALLOWED_PATCH_FIELDS = frozenset({'name', 'qty_on_hand'})

def _parse_json_body(self):
    raw = request.httprequest.get_data(as_text=True) or ''
    if not raw.strip():
        return {}
    payload = json.loads(raw)
    if not isinstance(payload, dict):
        raise ValidationError('Body must be a JSON object.')
    return payload

def patch_item(self, item_id):
    data = self._parse_json_body()
    safe = {k: v for k, v in data.items() if k in ALLOWED_PATCH_FIELDS}
    if not safe:
        return self._json_error('No allowed fields.', status=400)
    item.write(safe)
Backend Odoo
La lógica de negocio vive en modelos; el controlador enruta y valida.

Errores

Respuestas HTTP coherentes

CódigoCuándoQué devolver
401Sin autenticaciónMensaje genérico, sin filtrar si el ID existe
403Autenticado pero sin permisoGrupo o ACL insuficiente
404Registro inexistente o invisibleMismo mensaje en ambos casos (anti-enumeración)
400JSON inválido o campos no permitidosDetalle del campo rechazado
def _json_error(self, message, status=400):
    return request.make_json_response(
        {'ok': False, 'error': message}, status=status
    )

Checklist

Auditoría rápida de controladores

  1. rg "@http.route" odoo/addons/custom/ — inventario de rutas
  2. ¿Cada ruta tiene el auth mínimo necesario?
  3. ¿Hay sudo() en el handler? ¿Está justificado?
  4. ¿Los write/create usan whitelist de campos?
  5. ¿Se prueba con usuario portal sin permisos de admin?

Resumen

Autentica con sesión o API key acotada, opera con request.env(user=…), comprueba grupos cuando haga falta y filtra campos en cada write. Los controladores enrutan; no sustituyen ACL ni record rules. Si necesitas sudo(), que sea local y documentado — como vimos en la entrada anterior de la serie.

en Odoo
sudo() en Odoo: cuándo sí y cuándo nunca
Auditoría de usos peligrosos en controladores, computes y crons