Saltar a contenido

Política de permisos

Novedades en v1.6.0

  • El límite conocido 10 se cierra, salvo un caso. En Odoo 10 a 12, la sesión vigilada decide con los campos del modelo las claves de un objeto pasado por posición a un método de negocio: una clave que no es campo del modelo ni clave de contexto permitida se rechaza (regla context), y si los campos no se pueden leer, la llamada se rechaza (regla unverified). Lo que queda: una clave de contexto con el nombre de un campo del modelo se lee como un valor. Ver Límites conocidos.
  • Nuevo digest: readonly, standard y builder rechazan un poco más, y cambia la versión de los datos de niveles que entra en el digest, así que el digest de cada perfil cambia (standard sin archivo: 21524d1933f3). full no cambia de comportamiento. Siguen siendo 28 herramientas.
  • odoo_version nombra la copia de odoo-tools que responde (bloque plugin), también con la política bloqueada. Una respuesta sin policy viene de una copia anterior a la 1.5.0, sin política de permisos. Ver Qué reportan las herramientas.

Novedades en v1.5.1

  • Un contexto pasado por posición, en Odoo 10 a 12: esas series también toman el contexto de una llamada del argumento posicional que sigue a los parámetros del método. Las reglas de contexto se aplican ahora a ese argumento: en la capa A en todas las series y, en 10 a 12, otra vez en la sesión vigilada. Lo que quedaba abierto es el límite conocido 10, que la v1.6.0 cierra salvo un caso.
  • Nuevo digest: readonly, standard y builder rechazan un poco más, y cambia la versión de los datos de niveles que entra en el digest, así que el digest de cada perfil cambia (standard sin archivo: 9f3166860347). full no cambia de comportamiento. Siguen siendo 28 herramientas.
  • El hook de inicio de sesión ya no lee las opciones de la política: avisa cuando el último arranque del servidor quedó bloqueado, con su código E, a partir del diagnóstico local del servidor.

Novedades en v1.5.0

  • Política de permisos: el servidor MCP aplica un perfil de permisos encima de los derechos de acceso del usuario de Odoo. Por defecto, standard: lee, crea, actualiza y corre métodos de negocio, pero no borra, no hace cambios estilo Studio, no envía correo ni SMS (las notas internas sí), no cambia usuarios, permisos, módulos, ajustes, cuentas bancarias, secuencias fiscales ni el historial del chatter, y no lee secretos guardados.
  • Dos opciones nuevas: Permission profile (odoo_policy) y Permission policy file (odoo_policy_file), ambas con valor por defecto.
  • Registro de auditoría: una línea JSON por llamada a herramienta, rechazos incluidos, sin valores ni secretos.
  • Para conservar el comportamiento de la 1.4, elige el perfil full.

El servidor MCP de odoo-tools aplica una política de permisos encima de los derechos de acceso del propio usuario de Odoo. Es un techo: nunca concede lo que Odoo rechaza, solo quita derechos, y nunca es más débil que Read-only mode. Funciona igual en Community, Enterprise y Odoo Online, porque corre en el servidor MCP y no necesita ningún módulo de Odoo. Las reglas de acceso, las reglas de registro y los grupos de campos de Odoo siguen aplicando siempre.

La referencia autoritativa es docs/policy.md del repositorio; esta página la resume y la explica.

Las dos opciones

Opción del plugin Variable de entorno Valor por defecto Qué es
Permission profile (odoo_policy) ODOO_POLICY standard readonly, standard, builder o full
Permission policy file (odoo_policy_file) ODOO_POLICY_FILE vacío ruta absoluta opcional a un archivo JSON con reglas por modelo encima del perfil

Se cambian en /plugin → odoo-tools → Configure options. En un contenedor o en un servidor standalone se usan las variables de entorno.

Para conservar el comportamiento de la 1.4, elige full

El perfil por defecto se endurece dentro de esta versión menor. Si quieres que el agente conserve todos los derechos que tiene el usuario de Odoo (por ejemplo, en la instancia de tu propia empresa), pon Permission profile en full (ODOO_POLICY=full en un contenedor o en una ejecución standalone). full sigue rechazando la lista corta NUNCA (cambios de API keys y desinstalación de módulos) y conserva las dos revisiones de argumentos que la hacen cumplir (ver Perfiles).

La política se lee una sola vez, cuando arranca el servidor. Un cambio necesita reconectar el servidor (/mcp en Claude Code); el nuevo digest aparece entonces en odoo_version y en la línea de inicio de la auditoría.

Qué protege y qué no

En el modo local (stdio), la política de permisos limita lo que Claude puede hacer a través de las herramientas MCP de odoo-tools. Protege contra errores y contra instrucciones escondidas en los datos que Claude lee. No limita a la persona que tiene la API key, y no es un sandbox para un agente con acceso sin restricciones a la shell del mismo equipo. La frontera que resiste frente a todos es el propio usuario de Odoo: dale al agente un usuario de Odoo dedicado y de mínimo privilegio (no un administrador) y una API key con caducidad. En un futuro modo remoto, donde el servidor guarde la credencial de Odoo, la misma política se volverá una aplicación real de permisos.

Tampoco trabaja a nivel de registro (las reglas de registro son de Odoo) ni es un sandbox de lo que un método de negocio hace dentro de Odoo (ver Límites conocidos). odoo_version advierte (W10) cuando el usuario de Odoo es administrador (base.group_system): los derechos de acceso de Odoo entonces no acotan nada, y esta política es la única capa que acota.

Operaciones

Operación Significado Viene de
read Datos de registros: buscar, leer, contar, agrupar, exportar, traducciones, adjuntos de un registro las herramientas de lectura; métodos de clase lectura por odoo_execute
create Crear registros, incluidos copy, name_create y las importaciones que insertan filas odoo_create, las importaciones, métodos de clase creación
write Cambiar registros existentes, incluidos archivar, traducciones e importaciones que actualizan filas odoo_write, odoo_translate_set, las importaciones, métodos de clase escritura
unlink Borrar registros, incluidos los comandos x2many que borran líneas odoo_unlink, unlink por odoo_execute, comandos x2many
call Correr un método del modelo por odoo_execute, opcionalmente limitado a una lista de métodos solo odoo_execute
schema Un cambio estilo Studio en un modelo (un campo nuevo x_, una automatización sobre el modelo) odoo_add_field, odoo_add_automation
inspect Metadatos de campos y revisiones de acceso de un modelo odoo_fields_get, odoo_api_catalog, odoo_access_check, y las llamadas fields_get propias de las herramientas
  • inspect nunca lo concede una regla: se permite en un modelo cuando cualquier otra operación está permitida ahí. Un modelo con todas sus operaciones rechazadas (el nivel secrets) también oculta su lista de campos.
  • Exportar es read: una exportación devuelve lo mismo que odoo_search_read.
  • Las importaciones necesitan create y write, porque el load() de Odoo inserta y actualiza.

Perfiles

Operación por clase de modelo readonly standard (por defecto) builder full
read, modelos de negocio sí sí sí sí
create, write, modelos de negocio no sí sí sí
unlink, modelos de negocio no no no sí
call, métodos de negocio no sí sí sí
call de un método de salida no solo notas internas solo notas internas sí
schema, modelos de negocio no no sí sí
read de modelos security, outbound y customization sí sí sí sí
cualquier cambio en modelos security y outbound no no no sí
cualquier cambio en modelos customization no no sí sí
cualquier cosa en modelos secrets no no no sí
campos con aspecto de credencial retenidos retenidos retenidos (se liberan los campos de código de las automatizaciones) se devuelven
reglas de adjuntos, identidad, enlace de delegación y contexto activas activas activas apagadas
NUNCA, las revisiones de nombres y el sobre del guard activas activas activas activas
formas de argumentos y consultas de modelos relacionados (reglas shape y unverified) activas activas activas activas

Dos hechos que conviene tener presentes:

  • builder equivale a full en confidencialidad e integridad. Deja que el agente cree acciones de servidor y automatizaciones: Python que corre en el servidor de Odoo, con sudo() disponible, quizá después como el administrador que lo dispare. Ese código puede leer un parámetro del sistema y copiarlo a un campo legible. Los niveles bajo builder frenan errores a través de las herramientas genéricas; no frenan a un agente que escribe código de automatización. Usa builder solo en sandboxes y staging. Cargarlo emite la advertencia W6.
  • full es el comportamiento de la 1.4, salvo NUNCA, las revisiones de nombres, el sobre del guard (que solo frena un error del propio odoo-tools) y dos revisiones de argumentos de las que dependen NUNCA y un archivo de política. Por eso unas pocas llamadas que la 1.4 enviaba necesitan otra forma:
    • se rechazan los argumentos que la política no puede leer con certeza (regla shape), por ejemplo más de tres argumentos posicionales a read_group por odoo_execute (las posiciones posteriores cambian entre series de Odoo), o una palabra clave que no conoce en un método de lectura o escritura (los argumentos de un método de negocio no se leen). Pasa los argumentos por nombre, o usa la herramienta dedicada;
    • las definiciones de campos detrás de una ruta con puntos, y detrás de un valor que puede ser un comando x2many (una lista, false o null en una escritura), se leen primero con fields_get: un comando api_key_ids en res.users llega a res.users.apikeys, que NUNCA cierra. Si Odoo rechaza esa consulta, la llamada se rechaza (regla unverified). Un nombre de campo simple, o un valor simple, no necesita consulta.

standard rechaza unlink pero permite call: borrar es el único verbo genérico irreversible (Odoo no tiene papelera, y archivar es el camino reversible que sugiere el rechazo), mientras que rechazar call rompería action_confirm, action_post y los flujos de asistentes. En su lugar se cierran los métodos de salida. Para producción con un usuario administrador, el ejemplo endurecido limita call a listas de métodos.

Herramientas que lista cada perfil

tools/list oculta una herramienta que la política prohíbe en todos los modelos, igual que Read-only mode oculta las herramientas de escritura. Ocultar solo aproxima por exceso: una herramienta listada todavía puede rechazarse para un modelo concreto, y una herramienta oculta que se llama por su nombre devuelve un error que explica por qué (nunca una herramienta desconocida). Read-only mode (odoo_readonly, ODOO_READONLY=1) oculta las 9 herramientas de escritura en todos los perfiles.

Perfil Herramientas no listadas Listadas
readonly odoo_create, odoo_write, odoo_unlink, odoo_execute, odoo_translate_set, odoo_import_preview, odoo_import, odoo_add_field, odoo_add_automation 19
standard odoo_unlink, odoo_add_field, odoo_add_automation 25
builder ninguna 28
full ninguna 28
cualquier perfil con Read-only mode activo odoo_create, odoo_write, odoo_unlink, odoo_execute, odoo_translate_set, odoo_import_preview, odoo_import, odoo_add_field, odoo_add_automation 19

Un archivo de política puede ocultar más: una herramienta en tools.disabled deja de listarse.

Las instrucciones del servidor ganan una línea que nombra el perfil, su digest y las herramientas ocultas, y dice que un rechazo que nombra la política de permisos es definitivo. Fuera de full, la descripción de cada herramienta que recibe un modelo termina con un recordatorio corto del perfil, por ejemplo [Policy 'standard': no deletes or secrets; users, rights, mail-out and customization read-only. See odoo_version.].

Niveles sensibles

Cuatro niveles (tiers) están cerrados por defecto. Un modelo pertenece a un nivel por su nombre exacto o por un patrón prefijo.* (que abarca los nombres bajo el prefijo, no el prefijo mismo), por sus alias y por sus padres de delegación. Cuando cualquier nivel de un modelo retiene una operación, la operación se rechaza, y el rechazo nombra el primer nivel en este orden. Los nombres que no existen en una versión nunca coinciden; los marcados best-effort se confirman contra bases reales. Las columnas dicen qué permite cada perfil dentro del nivel; full lo permite todo.

Nivel Modelos readonly standard builder Por qué
secrets ir.config_parameter, res.config, res.config.*, ir.mail_server, fetchmail.server, payment.provider, payment.acquirer, payment.token, iap.account, res.users.apikeys.show, res.company.ldap, auth_totp.device (best-effort), l10n_mx_edi.certificate (best-effort), certificate.certificate (best-effort), certificate.key (best-effort), account.online.link (best-effort), account.online.account (best-effort), mail.mail nada nada nada Guardan credenciales, o valores que actúan como credenciales (parámetros del sistema, contraseñas de servidores de correo y LDAP, llaves y tokens de proveedores de pago, tokens de IAP, una API key nueva, tokens de sincronización bancaria, certificados y llaves CFDI, correos de restablecimiento en cola). Leerlos ya es el daño.
security res.users, res.users.*, res.groups, res.groups.*, ir.model.access, ir.rule, ir.module.module, base.module.*, change.password.*, portal.wizard, portal.wizard.*, auth_totp.wizard, auth.oauth.provider, res.partner.bank, base.partner.merge.automatic.wizard, mail.message, mail.tracking.value, ir.sequence, ir.sequence.date_range lectura lectura lectura Identidad y permisos, redirección del inicio de sesión, cuentas bancarias, fusiones de contactos, historial del chatter y secuencias fiscales: la lectura sigue abierta; los cambios necesitan el perfil full o una entrada que nombre el modelo exacto.
outbound mail.compose.message, mail.followers, mail.template, sms.sms, sms.composer, sms.template, portal.share, mailing.* (best-effort), snailmail.letter, account.move.send, account.move.send.* (best-effort), account.invoice.send, mail.mail lectura lectura lectura Envían correo, SMS, cartas o enlaces para compartir, o deciden qué se envía.
customization ir.*, base.automation, base.action.rule, studio.*, base_import.import, base.language.install, base.language.import, excepto ir.attachment lectura lectura todo Acciones de servidor, automatizaciones, tareas programadas, vistas, assets, campos manuales, valores por defecto y otros registros ir.* corren código o cambian cómo se comporta Odoo después, y los motores de importación masiva y de idiomas insertan, actualizan o instalan entre modelos; necesitan el perfil builder.

Por qué se cierran los modelos customization:

  • ir.actions.server, ir.cron y base.automation corren Python en disparadores posteriores, como otros usuarios, con sudo() disponible; ir.server.object.lines guarda expresiones Python.
  • El QWeb de ir.ui.view se compila a código (un script guardado en el sitio web público está a una escritura de distancia); ir.asset inyecta JavaScript y CSS.
  • Un ir.model.fields manual puede llevar código compute; ir.model.data reapunta identificadores externos; ir.default fija valores por defecto en cualquier modelo; ir.property (hasta la 17) guarda valores por compañía como las cuentas por defecto.
  • ir.filters, ir.embedded.actions, ir.ui.menu, ir.actions.* (redirecciones de URL, reportes impresos) e ir.translation (hasta la 15). Como prefijo, ir.* cubre también los modelos que nadie listó.
  • base_import.import (el motor de importación masiva) y los asistentes de idioma están en este nivel y solo se alcanzan por sus herramientas, así que odoo_execute no puede llevar el load() del importador a un modelo cerrado.

Los traslapes son intencionales: ir.config_parameter e ir.mail_server son a la vez ir.* y secrets, así que builder los mantiene cerrados; ir.model.access, ir.rule, ir.module.module e ir.sequence son a la vez ir.* y security, así que builder no puede cambiar permisos, instalar módulos ni renumerar facturas.

Comportamiento en la v1.5.0: account.journal

En la v1.5.0, account.journal (con su cuenta bancaria y sus secuencias) es un modelo de negocio: sus escrituras no pertenecen a ningún nivel. Si esas escrituras se suman a un nivel se decidirá tras la verificación en vivo de la facturación CFDI con la v1.5.0.

NUNCA

Ningún perfil y ninguna regla permiten esto, full incluido. El cargador rechaza un archivo que lo intente (E18).

Modelo Operaciones Por qué
res.users.apikeys create, write, unlink, call odoo-tools nunca crea, cambia ni revoca API keys
res.users.apikeys.description read, create, write, unlink, call, schema el asistente de creación de API keys
ir.module.module call (button_immediate_uninstall, button_uninstall, module_uninstall) desinstalar un módulo borra sus datos
base.module.uninstall read, create, write, unlink, call, schema el asistente de desinstalación

Los métodos privados no necesitan entrada: la revisión del nombre del método (^[A-Za-z][A-Za-z0-9_]{0,127}$) rechaza un _ inicial en todos los transportes, igual que Odoo. Los nombres de modelo deben cumplir ^[a-z0-9_.]{1,128}$.

Reglas integradas

El perfil full apaga las reglas de enlace de delegación, de ir.attachment, de identidad, de métodos de salida y de contexto que siguen. Dos partes siguen activas en todos los perfiles, porque NUNCA y las reglas de un archivo de política aplican también bajo full: un hijo de delegación sigue tomando la decisión de su padre, y los registros relacionados se siguen revisando.

Delegación (_inherits)

Un modelo hijo que delega en un padre cerrado escribe los campos del padre: website.page delega en ir.ui.view, así que escribir su arch escribe una vista QWeb. Por eso un hijo hereda los niveles de su padre para toda operación excepto read: las escrituras en website.page necesitan builder, mientras que la lectura sigue abierta. Los hijos son website.page y website.controller.page (ir.ui.view), ir.cron y base.automation en 11-16 (ir.actions.server), y product.document y documents.document (ir.attachment).

Comportamiento en la v1.5.0: la decisión del padre incluye las reglas del archivo

La decisión completa del padre aplica también al hijo, reglas del archivo incluidas: una regla exacta sobre website.page (o ir.cron) no abre por sí sola sus escrituras. Abre también el padre (ir.ui.view, o ir.actions.server), o usa el perfil builder.

El campo de enlace de un hijo (view_id, ir_attachment_id, etc.) no se puede fijar por las herramientas genéricas (regla delegation-link): apuntarlo a un registro existente expondría o cambiaría ese registro a través del hijo.

ir.attachment queda anclado a su padre

ir.attachment guarda los campos binarios de otros modelos, incluidas llaves de certificados, así que una lectura genérica podría alcanzar el nivel secrets por la puerta de atrás. Una llamada genérica sobre ir.attachment debe nombrar su padre:

  • Una búsqueda (todo método de lectura del modelo que recibe un dominio de primer nivel: search, search_read, search_count, read_group, formatted_read_group, web_search_read, web_read_group, name_search, web_name_search, read_grouping_sets, formatted_read_grouping_sets, search_fetch y los lectores de pivote y panel read_progress_bar, search_panel_select_range, search_panel_select_multi_range) necesita un ["res_model", "=", X] o ["res_model", "in", [...]] de primer nivel cuyos modelos permitan todos read, y ninguna hoja sobre res_field. El guard agrega ["res_field", "=", False], así que una búsqueda nunca ve los archivos guardados en campos. Un lector search_panel_* no lleva un dominio de primer nivel, así que sobre ir.attachment nunca puede nombrar al padre y se rechaza.
  • Una llamada por ids lee primero res_model y res_field de esos ids (una lectura interna que el agente no recibe). Ningún id puede tener res_field, y cada res_model debe permitir read (para lecturas) o write (para cambios).
  • Una creación (o una escritura de estos valores) no puede ser public ni fijar res_field; su res_model debe permitir write. Esto vale también cuando la escritura llega a ir.attachment por un hijo de delegación: product.document y documents.document exponen public, res_field y datas del padre, así que crear o volver público un adjunto descargable por cualquiera a través de uno de ellos se rechaza, igual que directamente en ir.attachment.

Los adjuntos de campos se siguen leyendo por su modelo padre (odoo_read de image_1920 en product.template), y odoo_record_documents sigue funcionando con sus propias llamadas internas ancladas.

Identidad (el correo de contacto de un usuario)

El enlace de restablecimiento de contraseña de un usuario va al correo de su contacto (partner), así que cambiar ese correo por res.partner equivaldría a tomar la cuenta. Un write sobre res.partner que fija email hace que el guard lea antes user_ids de esos contactos; si alguno es un usuario (usuarios de portal incluidos), la llamada necesita también write sobre res.users, que el nivel security retiene.

Comportamiento en la v1.5.0: importaciones que actualizan contactos

Una importación a res.partner con una columna email y una columna id o .id (una actualización de contactos existentes) se rechaza, y el rechazo apunta a odoo_write, que revisa cada contacto.

Métodos de salida

Un método de negocio puede enviar datos fuera: message_post con un contacto que el agente eligió le escribe a quien sea, y message_subscribe reenvía cada mensaje futuro. Estos métodos se rechazan como call salvo que una entrada que nombre el modelo exacto los liste, con una razón:

action_send_and_print, action_send_mail, message_mail_with_source, message_notify, message_post, message_post_with_source, message_post_with_template, message_post_with_view, message_subscribe, send_and_print_action, send_mail

Las notas internas están permitidas. message_post pasa como un call simple cuando sus argumentos con nombre llevan subtype_xmlid "mail.mt_note" (13+) o subtype "mail.mt_note" (10-12), no hay partner_ids, message_type falta o es "comment", y no se pasa ningún otro argumento posicional. Una nota interna no le notifica a nadie por correo.

Claves de contexto

Una llamada solo puede llevar estas claves de contexto, más default_<campo> con un valor simple (texto, número, booleano o nulo) sobre un campo que no esté retenido:

active_id, active_ids, active_model, active_test, allowed_company_ids, bin_size, button_validate_picking_ids, lang, location, mail_create_nosubscribe, mo_ids_to_backorder, picking_ids_not_to_backorder, read_group_expand, skip_backorder, skip_immediate, tz, uid, validate_analytic, warehouse

Los asistentes de validación de inventario y manufactura necesitan algunas de estas claves: sin button_validate_picking_ids o skip_backorder, por ejemplo, el asistente devuelve True y no valida nada. El botón Confirmar/Publicar de la factura envía validate_analytic (16+), que account.move._post lee para forzar la validación de la distribución analítica, y el cliente web de Odoo pone el id del usuario conectado en uid en cada llamada; es informativo, porque el usuario que actúa es la sesión autenticada, no context['uid']. Ninguna de las claves permitidas suprime el seguimiento del chatter. tracking_disable, mail_notrack y mail_create_nolog faltan a propósito: silencian el seguimiento del chatter que el nivel security protege como evidencia.

Comportamiento en la v1.5.0: un default_<campo> de contexto debe ser un valor simple

Bajo readonly, standard y builder, un default_<campo> que contiene una lista o un objeto se rechaza (regla context) antes de enviar nada a Odoo, para toda herramienta, método y modelo, y dondequiera que esté el contexto (kwargs["context"], un contexto anidado en una especificación de lectura, o uno anidado en el fields_spec de onchange): comandos x2many ([(0, 0, {...})], [(4, id)], [(6, 0, ids)]), una lista de ids, valores anidados, incluso una lista vacía. Odoo aplica un valor por defecto de contexto a cada registro creado mientras corre la llamada: el que nombra la llamada, las líneas que crean sus comandos x2many y los registros de otros modelos que crea un método de negocio, un asistente o una importación (action_confirm, un asistente de validación, load). Un valor así no se puede revisar contra el único modelo que nombra la llamada. El rechazo nombra la clave, nunca su valor. Pasa esos valores en los values del registro, donde cada comando se decide contra el modelo al que llega, o usa el perfil full. Por ejemplo, crea el asistente de backorder de manufactura con sus líneas en values:

{"model": "mrp.production.backorder", "method": "create",
 "args": [{"mrp_production_ids": [[6, 0, [5]]],
           "mrp_production_backorder_line_ids": [[0, 0, {"mrp_production_id": 5,
                                                          "to_backorder": true}]]}]}

en lugar de las claves default_mrp_production_ids y default_mrp_production_backorder_line_ids de su acción, y el asistente de backorder de inventario con pick_ids: [(4, id)] en values en lugar de default_pick_ids.

Además, un default_<campo> simple que nombra un campo estructural se rechaza por construcción en una llamada que puede crear un registro (no en una lectura, donde no llena nada): el campo de enlace de una delegación cuyo padre es sensible (default_ir_attachment_id, default_view_id y los demás enlaces a ir.attachment, a un nivel o a un modelo NUNCA), y default_public o default_res_field. Un valor por defecto inocuo (default_move_type: "out_invoice") sigue corriendo, salvo que su campo esté retenido. default_res_model y default_res_id siguen permitidos, porque la programación de actividades y el redactor de correo los usan (ver el límite conocido 9). full no cambia.

Desde la v1.5.1, estas reglas se aplican también a un objeto pasado por posición, que Odoo 10 a 12 toman como contexto, y desde la v1.6.0 la sesión vigilada decide con los campos del modelo las demás claves de un objeto así: ver el límite conocido 10.

Registros relacionados: comandos x2many y rutas de campos

Una escritura se revisa para cada registro que cambia, no solo para el modelo que nombra. En values (create, write, los valores por defecto de copy, web_save):

Comando one2many many2many
(0, _, vals) create en el comodelo, luego sus vals igual
(1, id, vals) write en el comodelo, luego sus vals igual
(2, id) unlink en el comodelo igual
(3, id), (5,), (6, _, ids), una lista de ids, False en un write: unlink en el comodelo (Odoo borra las líneas quitadas cuando el inverso es ondelete='cascade'); en un create: write en el comodelo write en el comodelo cuando un nivel, una regla del archivo o NUNCA rechaza write ahí; si no, read
(4, id) write en el comodelo (cambia de padre la línea) como arriba
cualquier otra cosa, profundidad mayor a 4, o un campo que Odoo no reporta mientras lleva comandos rechazado (shape) igual

Así, en res.partner, user_ids: [(1, uid, {"groups_id": ...})] es una escritura en res.users y en res.groups, los cambios de bank_ids son escrituras en res.partner.bank, y order_line: [(5,)] es un borrado bajo standard.

Comportamiento en la v1.5.0: enlaces many2many a usuarios

Un enlace many2many a un modelo cuya escritura está retenida cuenta como un cambio en él, y por eso se rechaza bajo standard, por ejemplo los grupos de un usuario (res.users.groups_id) o los usuarios de una compañía (res.company.user_ids). Dos enlaces que solo hacen referencia a usuarios cuentan como una lectura de res.users y corren bajo standard: los asignados de una tarea (project.task.user_ids) y los miembros de un equipo de helpdesk (helpdesk.team.member_ids). Cualquier otro many2many hacia res.users sigue contando como escritura.

Las lecturas se revisan a lo largo de sus rutas: cada salto de una hoja de dominio (incluidos any y not any), la specification anidada de web_read y web_search_read, las rutas de export_data, las columnas de importación y los términos con puntos de groupby y order. Cada modelo alcanzado debe permitir read, y ningún segmento puede ser un campo retenido. En las columnas de importación, una ruta hacia un one2many (order_line/product_id) crea y escribe líneas en el comodelo. Cuando no se pueden leer los metadatos de campos que necesitan estas revisiones, la llamada se rechaza (regla unverified), en todos los perfiles.

Campos con aspecto de credencial

Fuera de full, un campo cuyo nombre coincide con (^|_)(password|passwd|pass|pwd|secret|token|apikey|api_key|private_key|privkey|credentials?)($|_), o que es signup_url, smtp_pass o totp_secret, queda retenido. No lo está un nombre que denota una relación o un conteo de tokens en lugar de un valor: los nombres exactos payment_token_id, payment_token_ids y token_count, y cualquier nombre que termine en token_id, token_ids o token_count (así token_id, suitable_payment_token_ids y payment_token_count también). Solo hacen referencia a payment.token, del nivel secrets, cuyos valores siguen sellados. El code de ir.actions.server, ir.cron y base.automation, el webhook_url de las acciones de servidor y el webhook_uuid de las automatizaciones también se retienen, salvo bajo builder (las integraciones a menudo escriben tokens ahí).

Un campo retenido se rechaza dondequiera que aparezca: listas de campos, el field de las herramientas de traducción, claves de values (incluidos los valores de comandos anidados), columnas de importación y rutas de exportación (cada segmento), claves de specification a cualquier profundidad, groupby, los campos origen de los agregados, order y cada segmento de una ruta de dominio. Un filtro sobre un secreto lo filtraría adivinando de a poco. Las lecturas pierden las claves retenidas a cualquier profundidad, y el resultado de la herramienta las nombra en policy_withheld_fields (por ejemplo ["res.company.l10n_mx_edi_pac_password"]), así que nada desaparece en silencio.

Todo método público que lee o filtra datos de registros pasa por estas revisiones, no solo search/read: search_fetch, fetch, onchange, read_progress_bar, search_panel_select_range, search_panel_select_multi_range, copy_data, el web_name_search del cliente web, los lectores de pivote read_grouping_sets y formatted_read_grouping_sets, y filtered_domain son lecturas, así que sus dominios, conjuntos de agrupación, campos y especificaciones se analizan y se rechazan como cualquier otro. read_progress_bar agrupa por progress_bar["field"] (y suma sum_field), una segunda dimensión de agrupación que se revisa como su group_by; su resultado no se redacta, porque sus claves son los valores distintos de esos campos (ya revisados), no nombres de campos.

Un método que sigue siendo un call simple (un verbo de negocio como action_confirm, o un método que ninguna versión de odoo-tools lista) tiene dos respaldos. Su resultado se redacta a cualquier profundidad, igual que el de una lectura. Y sus argumentos se revisan con los metadatos de campos del modelo antes de enviarse: una cadena que es una ruta de campo del modelo se revisa como una lectura de esa ruta (se rechaza, por ejemplo, mapped("provider_id.name") hacia un proveedor de pago, o signup_token:count); un objeto cuyas claves son campos se revisa como valores de escritura (salvo en onchange, una simulación que no escribe); y un argumento con forma de dominio pasa por el análisis de rutas de lectura. Una cadena que no es un campo del modelo (el cuerpo de un mensaje, un asunto, el nombre de archivo de un adjunto, una palabra de estado) se deja pasar, así que una nota interna y un verbo de negocio con un argumento de texto siguen corriendo.

El archivo de política puede cambiar la lista: fields.withhold agrega nombres (modelo.campo o *.campo, para minimizar datos), y fields.allow libera nombres exactos modelo.campo, cada uno con una razón. withhold gana sobre allow, que gana sobre la lista integrada.

Precedencia

decide(op, model, method) corre estos pasos en orden, y el primero que decide, decide:

  1. invalid-name: el modelo o el método no pasa su revisión de nombre.
  2. never:<id>: coincide una entrada NUNCA.
  3. readonly: Read-only mode está activo y la operación no es read ni inspect.
  4. locked: la política no se pudo cargar.
  5. tool-disabled: la herramienta está en tools.disabled (se revisa por herramienta).
  6. file:<modelo>: una entrada exacta y vigente que fija la operación. Para call, false rechaza, una lista decide por pertenencia (y abre los niveles y los métodos de salida que lista), y true abre los niveles del modelo para call pero no los métodos de salida, que siguen necesitando una lista.
  7. tier:<nombre>: un nivel del modelo (el suyo, el de un alias o el de un padre de delegación) retiene la operación bajo el perfil.
  8. outbound-method: un call de un método de salida que no es una nota interna.
  9. file:<patrón>: el patrón más específico que fija la operación (primero el prefijo.* más largo, luego *).
  10. profile:<nombre>: el valor por defecto del perfil.

Los pasos 7 y 8 nunca ven lo que permite un patrón, así que un patrón nunca puede abrir un nivel ni un método de salida: solo puede hacerlo una entrada que nombre el modelo exacto, con una razón. Agregar un false nunca convierte un rechazo en un permiso.

Alias. Una regla sobre cualquiera de los dos nombres de un par renombrado o fusionado aplica a ambos, en todas las versiones: account.invoice y account.move, sus modelos .line, stock.quant.package y stock.package, base.automation y base.action.rule, y los demás renombres del mapa de compatibilidad. Un rechazo sobre cualquiera de los dos gana. En Odoo 10 a 12, una regla sobre account.move (asientos) restringe también account.invoice, y al revés.

El archivo de política

{
  "version": 1,
  "comment": "Acme production: confirm and post only, no imports",
  "models": {
    "*":                {"call": false},
    "sale.order":       {"call": ["action_confirm", "action_cancel"]},
    "account.move":     {"call": ["action_post", "message_post"],
                         "reason": "Invoices are sent from the chatter by the billing agent"},
    "res.partner.bank": {"create": true, "write": true,
                         "reason": "Ticket 4312: supplier onboarding", "expires": "2026-11-15"},
    "hr.*":             {"read": false, "create": false, "write": false, "call": false}
  },
  "fields": {
    "withhold": ["hr.employee.private_email", "*.vat"],
    "allow": [{"field": "sale.order.access_token", "reason": "Portal links for the sales agent"}]
  },
  "tools": {"disabled": ["odoo_import", "odoo_import_preview"]}
}

El archivo agrega reglas encima del perfil elegido en la opción y no lleva un perfil propio. El esquema es estricto: cualquier clave desconocida es un error, así que una errata no puede quitar un rechazo en silencio. Hay un esquema para editores en docs/policy.schema.json (el servidor valida en código).

Clave Regla
nivel superior version (requerida, 1), models, fields, tools, comment
models las claves son un modelo exacto, prefijo.* o *; máximo 1000 entradas
una entrada read, create, write, unlink, schema (true o false); call (true, false, o una lista de 1 a 200 nombres de métodos públicos); reason (de 10 a 200 caracteres); expires (AAAA-MM-DD); comment
fields withhold: una lista de modelo.campo o *.campo; allow: una lista de {"field": "modelo.campo", "reason": "..."}
tools {"disabled": [nombres de herramientas]}
lectura máximo 256 KiB, UTF-8 (se acepta un BOM inicial); una clave duplicada es un error
  • Una entrada fija solo las operaciones que nombra. Las listas de métodos no se combinan: decide la entrada más específica que fija call.
  • Las aperturas necesitan una razón. Una entrada exacta que abre una operación de un nivel, o que lista un método de salida, necesita un reason de al menos 10 caracteres (E15). reason y expires solo aplican a entradas exactas (E19).
  • La caducidad falla cerrada. Después de su fecha (fin del día, UTC), los permisos de una entrada (un valor true y una lista de métodos de call por igual) se ignoran y solo siguen aplicando sus rechazos false, así que la operación cae al perfil. Una lista de métodos caducada no se vuelve un rechazo general de call: los verbos de negocio que nombraba siguen funcionando bajo standard, y una entrada caducada nunca permite más que el mismo archivo sin sus permisos. Las aperturas aparecen en odoo_version (opened_sensitive), en --check-policy y en la línea de inicio de la auditoría.
  • El digest es un SHA-256 sobre el perfil, las reglas normalizadas, las reglas de campos, las herramientas desactivadas, la marca de Read-only mode y la versión de los datos de niveles, recortado a 12 caracteres hexadecimales. Los comentarios, las razones y el orden de las claves no lo cambian; cualquier valor de operación sí.

Dónde vive el archivo

  • Solo una ruta absoluta explícita. El servidor no busca en ninguna ubicación por defecto, porque un agente podría crear ahí un archivo y hacer que se cargara en silencio.
  • Recomendado: ~/.config/odoo-tools/policy.json en macOS y Linux, %APPDATA%\odoo-tools\policy.json en Windows. Mantenlo fuera de toda carpeta de proyecto y que solo tú puedas escribirlo (si no, advertencia W3).
  • En un contenedor: -v ~/.odoo-tools/policy.json:/policy/policy.json:ro -e ODOO_POLICY_FILE=/policy/policy.json (ver Despliegue local).

Proteger el archivo y las opciones

Nada de esto es obligatorio, y en stdio nada de esto frena a un agente con una shell sin restricciones (ver Qué protege y qué no); hace más difícil desabrochar el cinturón:

  • Reglas de permisos de Claude Code como {"permissions": {"deny": ["Edit(~/.config/odoo-tools/**)", "Write(~/.config/odoo-tools/**)"]}} cubren las herramientas de archivos de Claude Code y los comandos de archivos que reconoce en Bash, no subprocesos arbitrarios. Mantén activas las confirmaciones de Bash.
  • Las opciones de plugin de archivos de proyecto se ignoran: Claude Code lee las opciones de plugin solo de la configuración de usuario o administrada, así que un repositorio clonado no puede poner el perfil en full.
  • Las instrucciones del servidor le dicen al agente que un rechazo de la política es definitivo y que no debe buscar un rodeo por otra herramienta ni por la shell.

Errores de carga y advertencias

El servidor nunca se cae por una política mala. Un perfil desconocido, una ruta relativa o un archivo inválido lo bloquean (locked): tools/list lista solo odoo_version, que devuelve el bloque policy con el error sin contactar a Odoo; cualquier otra herramienta se rechaza con un texto que nombra el error y la opción que lo corrige, y dice que no se envió nada a Odoo; el log de arranque y la línea de instrucciones llevan el error, y la línea de inicio de la auditoría su código E. El diagnóstico local del servidor (server.jsonl) guarda también ese código E, y en Claude Code el hook SessionStart del plugin lo lee ahí: cuando el último arranque quedó bloqueado, te avisa con el código E como máximo una vez al día (no lee las opciones de la política ni el archivo). Ver Solución de problemas.

Los textos de error y de advertencia se muestran en inglés, tal como los emite el servidor:

Código Texto
E1 unknown permission profile '{value}' (use readonly, standard, builder, full)
E2 the policy file path must be absolute (got '{value}')
E3 policy file {path}: cannot be read ({strerror})
E4 policy file {path}: is larger than 262144 bytes
E5 policy file {path}: is not valid JSON (line {line}, column {column}: {msg})
E6 policy file {path}: duplicate key '{key}' (a repeated entry could hide a rule)
E7 policy file {path}: the top level must be an object with "version": 1
E8 policy file {path}: unknown key '{key}' at the top level (use version, models, fields, tools, comment)
E9 policy file {path}: models['{key}']: not a model name, 'prefix.*' or '*'
E10 policy file {path}: models['{key}']: unknown key '{op}' (use read, create, write, unlink, call, schema, reason, expires, comment)
E11 policy file {path}: models['{key}'].{op}: must be true or false
E12 policy file {path}: models['{key}'].call: must be true, false or a list of method names
E13 policy file {path}: models['{key}'].call: '{method}' is not a public method name
E14 policy file {path}: tools.disabled: unknown tool '{name}'
E15 policy file {path}: models['{key}'] opens {ops} on {article} {tier}-tier model and needs "reason" (at least 10 characters): it is shown in odoo_version and written to the audit log
E16 policy file {path}: models['{key}'].expires: '{value}' is not a date (YYYY-MM-DD)
E17 policy file {path}: fields.{list}[{index}]: '{value}' is not model.field (withhold also accepts *.field)
E18 policy file {path}: models['{key}'].{op}: cannot be granted: {reason}
E19 policy file {path}: models['{key}']: "reason" and "expires" only apply to an entry that names one model exactly
E20 policy file {path}: fields.allow[{index}] needs "reason" (at least 10 characters)

Un archivo cuyo contenido completo es el valor JSON null también se bloquea con E7.

Las advertencias no bloquean. Aparecen en odoo_version.policy.warnings, en --check-policy y en la línea de inicio de la auditoría. W10 la emite odoo_version en tiempo de ejecución, cuando el usuario de Odoo es administrador.

Código Texto
W1 models['{key}'] refuses create/write/unlink but allows call: business methods (e.g. action_post) can still change these records; set "call": false or list the allowed methods
W2 models['{model}'] opens {ops} on '{model}' ({tier} tier){until}: {reason}
W3 the policy file can be changed by other users of this machine (mode {octal})
W4 models['{key}'] expired on {date}: its allowances are ignored, its refusals still apply
W5 models['{key}'] sets {op} on a pattern; models of the {tiers} tiers are not affected: name a model exactly to open one
W6 profile 'builder' lets the agent create automations and server actions, which run Python on the Odoo server with sudo() available: for confidentiality it equals 'full'. Use it on sandboxes and staging.
W7 fields.allow releases {field}: {reason}
W10 the Odoo user of this connection is an administrator (base.group_system): Odoo's access rights narrow nothing, so this policy is the only narrowing layer. Prefer a dedicated user with the fewest groups that work.

Revisar un archivo sin conexión: --check-policy

python3 "<plugin>/server/run_stdio.py" --check-policy ARCHIVO [--profile standard] \
        [--explain MODELO:OP[:MÉTODO] ...] [--json]

Carga el archivo como lo haría el servidor, con Read-only mode apagado, nunca contacta a Odoo, e imprime el perfil y el digest, las reglas normalizadas, las aperturas con su razón y su caducidad, las advertencias, las herramientas ocultas y una línea por cada --explain:

explain: res.users write: REFUSED (tier:security)
explain: sale.order call action_confirm: ALLOWED (file:sale.order)
explain: account.move read: REFUSED (file:account.invoice, alias of account.move)
explain: website.page write: REFUSED (tier:customization, through ir.ui.view, which website.page delegates to)

Códigos de salida: 0 si el archivo es válido, 1 si tiene un error (el servidor arrancaría bloqueado), 2 por uso incorrecto. Un ARCHIVO relativo se resuelve contra la carpeta actual para la revisión (la opción misma debe ser absoluta), y un ARCHIVO vacío (--check-policy "") revisa solo el perfil. --json imprime el mismo reporte como JSON. El digest cubre la marca de Read-only mode, así que un servidor que corre con ODOO_READONLY=1 reporta otro digest para el mismo archivo y perfil. El repositorio de políticas de un equipo puede correrlo en su CI. Dentro del plugin, <plugin> es ${CLAUDE_PLUGIN_ROOT}.

Qué reportan las herramientas

odoo_version devuelve un bloque policy:

"policy": {"profile": "standard", "file": null, "digest": "21524d1933f3", "readonly": false,
           "hidden_tools": ["odoo_unlink", "odoo_add_field", "odoo_add_automation"],
           "opened_sensitive": [], "released_fields": [], "warnings": [],
           "error": null, "error_code": null}

Una entrada de opened_sensitive se ve así: {"model": "res.partner.bank", "ops": ["create", "write"], "tier": "security", "reason": "...", "expires": "2026-11-15"}. Cada advertencia es un objeto {"code": ..., "text": ...}. Con la política bloqueada, odoo_version devuelve solo el bloque plugin, el bloque policy con error y error_code, y una nota, sin contactar a Odoo.

Desde la v1.6.0, odoo_version devuelve también un bloque plugin: la versión de odoo-tools que respondió y, en Claude Code, su copia instalada (plugin@marketplace). Una respuesta sin policy viene de una copia anterior a la 1.5.0, que no tiene política de permisos: la ruta guiada y /odoo-tools:odoo-doctor se detienen ahí (ver Dos copias de odoo-tools).

  • odoo_access_check devuelve policy ({operación: true o false}) junto a la respuesta propia de Odoo, con una nota cuando Odoo permite lo que la política rechaza.
  • odoo_list_models deja fuera los modelos en los que la política no permite nada y dice cuántos son (hidden_by_policy).
  • odoo_online_profile reporta en unavailable una sección que la política retiene, en lugar de fallar.

Rechazos

Un rechazo es un error de herramienta. Nombra la operación, el modelo, el perfil y la regla que decidió; luego dice dónde se detuvo la llamada, da el remedio y le pide al agente no buscar el mismo resultado con otra herramienta. Solo se repiten nombres validados (modelo, campo, método, operación y clave de contexto), nunca el valor de un argumento.

  • Capa A, antes de iniciar sesión (la mayoría de los rechazos): se decide con los accesos declarados por la herramienta y los argumentos, antes de que exista una sesión. Termina con Nothing was sent to Odoo. y no se cuenta como llamada a herramienta. Aquí se rechazan una clave de contexto fuera de la lista y un default_<campo> que contiene una lista o un objeto (regla context).
  • Capa B, después de iniciar sesión (el guard): cada llamada a Odoo que hace una herramienta se revisa otra vez, con sus valores y rutas, al enviarse. Ese rechazo termina con The call was stopped before Odoo ran it: nothing was changed and no record data was returned. El servidor puede haber leído definiciones de campos para decidir. En Odoo 10 a 12, aquí se rechaza una clave de un objeto pasado por posición a un método de negocio que no es campo del modelo ni clave de contexto permitida (regla context, ver el límite conocido 10).
  • Una llamada fuera del mapeo declarado de una herramienta se detiene como un error de odoo-tools (regla envelope), en todos los perfiles; repórtalo a dev@transgenia.org.

Un rechazo empieza así, y sigue con el remedio (aquí, archivar con odoo_write {"active": false}):

the odoo-tools permission policy refuses 'unlink' on 'res.partner' (profile 'standard',
rule profile:standard). Nothing was sent to Odoo.

El agente debe tratar como definitivo un rechazo que nombra la política de permisos: te lo dice, y tú puedes cambiar el perfil o el archivo de política.

Registro de auditoría

Cada llamada a herramienta, rechazos incluidos, deja una línea JSON: la herramienta, los nombres de modelo y operación que tocó (op:modelo[:método]), la capa y la regla de un rechazo, conteos, la duración, el nombre de la clase de excepción de un error y el digest de la política. Nunca guarda valores de argumentos, dominios, valores de campos, ids, contenido de archivos, la URL, tokens, llaves, contraseñas ni textos de mensajes de error. La base de datos y el login de Odoo aparecen solo en la línea start que se escribe al arrancar el servidor, que también lleva el perfil, la ruta del archivo, las advertencias (códigos) y las aperturas con sus razones.

{"v":1,"ts":"2026-10-21T15:02:11Z","seq":42,"prev":"9c1e0b7d2a44f1e3","event":"call",
 "tool":"odoo_write","layer":"B","decision":"deny","rule":"relational",
 "touches":["write:res.partner"],"effective":["write:res.users"],"n":3,"status":"refused",
 "ms":41,"policy":"a4f1c0d2e9b7"}

Las claves son v, ts, seq, prev, event (start, call, chain_reset), tool, layer (tool, A o B), decision, rule, touches, effective, n, withheld, status (ok, refused, error, invalid), error, ms, policy, y en la línea de inicio profile, readonly, version, pid, source, db, login, opened y warnings.

ODOO_AUDIT_DIR A dónde van las líneas
una carpeta (el plugin fija ${CLAUDE_PLUGIN_DATA}/audit) un archivo por día UTC, odoo-tools-audit-AAAA-MM-DD.jsonl, carpeta 0700, archivos 0600, nunca a través de un enlace simbólico
sin definir o stderr el logger odoo_mcp.audit en stderr (docker logs en un contenedor)
off nada

ODOO_AUDIT_RETENTION_DAYS (90 por defecto; 0 conserva los archivos para siempre) borra los archivos diarios antiguos al arrancar el servidor y en la primera escritura después de la medianoche UTC; solo se borran archivos normales con nombre de archivo de auditoría. El plugin fija los 90 días. Claude Code borra la carpeta de datos del plugin cuando el plugin se desinstala de su último ámbito. Un fallo al escribir nunca rompe una llamada a herramienta.

Cada prev son los primeros 16 caracteres hexadecimales del SHA-256 de la línea anterior del mismo archivo (genesis en la primera). Para revisar esa cadena:

python3 "${CLAUDE_PLUGIN_ROOT}/server/run_stdio.py" --verify-audit ARCHIVO

Sale con 0 cuando la cadena se sostiene; con 1 en la primera línea cuyo contenido se editó, cuyo orden cambió o que se insertó, o cuando la última línea está incompleta (y la nombra); con 2 cuando el archivo no se puede leer. Como cada línea solo enlaza con la anterior, revisar un solo archivo no detecta por sí mismo líneas completas quitadas del final de un día, ni un archivo de un día borrado entero: el prefijo que sobrevive sigue encadenado. En local esto es evidencia, no prevención: quien puede escribir la carpeta puede reescribir los archivos y recalcular la cadena. Un destino que ancle la cadena entre archivos y reinicios es una adición del modo remoto, no incluida aquí.

Ejemplos

El repositorio trae cuatro archivos en docs/policy-examples/, y la CI carga cada uno:

Archivo Perfil Para
production-hardened.json standard producción con un usuario administrador, mientras no exista un usuario de mínimo privilegio: solo verbos seleccionados, sin datos de RR. HH., sin leer el chatter, sin cambios al sitio web, sin importaciones
sales-team.json standard un agente de ventas: CRM y cotizaciones, respuestas a clientes desde el chatter, sin cambios contables
read-only-analyst.json readonly reportes: agregados y exportaciones sin datos de RR. HH., chatter ni datos de contacto
sandbox-builder.json builder un sandbox de entrenamiento: cambios estilo Studio, borrados y usuarios de prueba

El archivo endurecido, para producción con un usuario administrador:

{
  "version": 1,
  "comment": "Production hardening: business reads and writes, curated verbs only, no imports",
  "models": {
    "*":            {"call": false},
    "res.partner":  {"call": ["message_post"], "reason": "Agents reply to customers from the chatter"},
    "crm.lead":     {"call": ["action_set_won", "action_set_lost"]},
    "sale.order":   {"call": ["action_confirm", "action_cancel"]},
    "account.move": {"call": ["action_post"]},
    "hr.*":         {"read": false, "create": false, "write": false, "call": false},
    "mail.message": {"read": false},
    "mail.activity":{"read": false},
    "website.*":    {"write": false, "create": false}
  },
  "fields": {"withhold": ["*.vat", "hr.employee.private_email"]},
  "tools": {"disabled": ["odoo_import", "odoo_import_preview"]}
}
  • "*": {"call": false} deja odoo_execute solo con los verbos seleccionados; sus métodos de lectura también se rechazan, porque necesitan call.
  • Los rechazos de hr.*, mail.message y mail.activity mantienen los datos de RR. HH. fuera de alcance también a través del chatter (ver los límites conocidos).
  • El rechazo de website.* cierra el canal del contenido publicado.

Para usarlo, copia el archivo fuera de todo proyecto (por ejemplo a ~/.config/odoo-tools/policy.json), revísalo con --check-policy y pon su ruta absoluta en Permission policy file.

Mapeo de herramientas

Cada herramienta declara lo que toca, y una herramienta sin mapeo no se puede registrar, listar ni llamar. La capa A decide lo declarado a partir de los argumentos, antes de que exista una sesión. Las llamadas mecánicas fijas que hace una herramienta son entradas internas, cada una anclada por un predicado sobre la llamada real y con una razón; el guard (capa B) rechaza cualquier llamada que no esté declarada ni sea una interna cuyo predicado se cumple.

Herramienta Solo lectura Declarado (capa A) Oculta en readonly / standard / builder
odoo_access_check sí inspect sobre el modelo pedido no / no / no
odoo_add_automation no schema sobre el modelo pedido; create base.automation; create ir.actions.server sí / sí / no
odoo_add_field no schema sobre el modelo pedido; inspect sobre el modelo de relation, si se da sí / sí / no
odoo_api_catalog sí inspect sobre el modelo pedido no / no / no
odoo_connections sí ningún modelo no / no / no
odoo_create no create sobre el modelo pedido sí / no / no
odoo_execute no call del método pedido, más su clase (por ejemplo unlink) sí / no / no
odoo_export_records_csv sí read sobre el modelo pedido no / no / no
odoo_export_records_json sí read sobre el modelo pedido no / no / no
odoo_fields_get sí inspect sobre el modelo pedido no / no / no
odoo_import no create y write sobre el modelo pedido; las rutas de las columnas fields sí / no / no
odoo_import_preview no create y write sobre el modelo pedido; las rutas de las columnas fields sí / no / no
odoo_list_models sí read ir.model no / no / no
odoo_module_info sí read ir.module.module no / no / no
odoo_online_profile sí read res.users, res.company, res.users.apikeys e ir.module.module; inspect res.partner; una sección rechazada se reporta y la herramienta sigue no / no / no
odoo_read sí read sobre el modelo pedido no / no / no
odoo_read_group sí read sobre el modelo pedido no / no / no
odoo_record_documents sí read sobre el modelo pedido; read ir.attachment no / no / no
odoo_report sí nunca llama a Odoo no / no / no
odoo_search sí read sobre el modelo pedido no / no / no
odoo_search_count sí read sobre el modelo pedido no / no / no
odoo_search_read sí read sobre el modelo pedido no / no / no
odoo_telemetry_preview sí ningún modelo no / no / no
odoo_translate_get sí read sobre el modelo pedido no / no / no
odoo_translate_set no write sobre el modelo pedido sí / no / no
odoo_unlink no unlink sobre el modelo pedido sí / sí / no
odoo_version sí ningún modelo no / no / no
odoo_write no write sobre el modelo pedido sí / no / no

Para agregar una herramienta al servidor, ver Contribuir.

Límites conocidos

  1. Efectos dentro de los métodos. action_confirm puede enviar un correo de confirmación cuando Odoo está configurado así, y las automatizaciones existentes corren con las escrituras permitidas. Odoo los gobierna para el usuario conectado. El archivo endurecido limita call a listas de métodos.
  2. Las cadenas que devuelven los métodos no se redactan, por ejemplo una URL de portal que lleva un token.
  3. Los modelos puntero distintos de ir.attachment (mail.message, mail.activity, mail.followers, rating.rating) importan cuando un archivo rechaza lecturas de modelos de negocio; el ejemplo endurecido los rechaza junto con hr.*.
  4. Campos relacionados y calculados con nombres inofensivos, y los nombres visibles, pueden llevar datos de un modelo retenido. Los grupos de campos de Odoo siguen siendo el control ahí.
  5. Los enlaces many2one no se revisan como toques. Fijar partner_bank_id a una cuenta bancaria existente es solo una escritura en el padre.
  6. Los campos públicos son un canal de salida por su efecto, por ejemplo la descripción publicada en el sitio web. Los archivos endurecidos pueden rechazar escrituras en website.* y product.template.
  7. La inferencia a partir de conteos y agregados no se impide.
  8. En stdio, un agente con shell puede rodear la política, y builder equivale a full en confidencialidad.
  9. Un adjunto anidado se puede ligar a un registro con un valor por defecto de contexto. default_res_model y default_res_id siguen permitidos, porque la programación de actividades y el redactor de correo los usan, así que una llamada que crea un ir.attachment anidado (directamente, o por un hijo de delegación como product.document) puede fijar el res_model y el res_id de ese adjunto desde el contexto. Una creación directa del adjunto con esos valores sí se revisa (el padre debe permitir write); solo se acepta el camino de la creación anidada, que ninguna revisión contra el modelo principal de la llamada puede seguir. public y res_field se rechazan como valores por defecto, así que esto liga un adjunto, no lo expone.
  10. Un contexto pasado por posición, en Odoo 10 a 12. Esas series también toman el contexto de una llamada del argumento posicional que sigue a los parámetros propios del método; desde la 13 solo se lee kwargs["context"]. La política lee ese argumento como contexto: en la capa A en todas las series, antes de enviar nada, por odoo_execute con o sin ids, y otra vez en la sesión vigilada en 10 a 12. Esas series construyen el contexto con dict(), así que una lista de pares [clave, valor] cuenta abajo como un objeto y se lee como el contexto que construye; una lista de nombres de campos, ids o términos de dominio no construye ninguno.

    • Con un método cuyos parámetros conoce la política (search_read, create, write y los demás métodos que lee), un objeto después de los parámetros, o en un parámetro que nunca lleva un objeto (el limit de search_count, que Odoo 10 a 12 no tienen), cumple todas las reglas de contexto. Una llamada con un argumento después de los parámetros se sigue rechazando como antes (regla shape) cuando las reglas de contexto la dejan pasar.
    • Con cualquier otro método (un método de negocio como action_confirm), la política no puede saber dónde terminan los parámetros, así que cualquier objeto pasado por posición puede ser el contexto o uno de los valores del método. Sus claves default_<campo> cumplen las reglas de contexto en todas las series (una lista o un objeto, un campo estructural en una llamada que puede crear, un campo retenido), y un objeto de valores como {"default_code": "A-1"} sigue corriendo. Desde la v1.6.0, en Odoo 10 a 12 la sesión vigilada decide también sus demás claves, con los campos del modelo: una clave que es campo del modelo es uno de los valores del método y se revisa como sus demás argumentos (ver Campos con aspecto de credencial); una clave de contexto permitida pasa; cualquier otra, como tracking_disable, solo puede ser una clave de contexto y se rechaza (regla context). Los campos son las definiciones que la sesión vigilada lee para sus otras revisiones (fields_get, con caché), y solo se leen cuando hay una clave así; si Odoo rechaza esa consulta, la llamada se rechaza (regla unverified). Una clave que no es texto, que solo puede traer una lista de pares (enlaces x2many como [4, 5]), no nombra ninguna clave de contexto que Odoo lea y no se toca. Por eso una lista de pares de texto cuyos primeros elementos no son campos del modelo ni claves de contexto permitidas (pares de una selección, una fila de dos columnas) también se rechaza ahí cuando se pasa por posición: pasa los argumentos del método por nombre.

    Los rechazos (regla context) nombran la clave, nunca su valor, y dicen que pases el contexto por nombre, en kwargs["context"], y los demás argumentos del método por nombre, o que pongas los valores en los values del registro. Lo que queda: en Odoo 10 a 12, una clave de un objeto así que es campo del modelo se lee como un valor, así que una clave de contexto con el nombre de uno de los campos del modelo no se somete a la lista permitida. Las claves que la lista deja fuera a propósito (tracking_disable, mail_notrack, mail_create_nolog) se rechazan salvo que el modelo tenga un campo con ese nombre. Pasa el contexto por nombre, en kwargs["context"].