Odoo 18 权限系统源码学习
已完成
6.1 三种容易混淆的写法
# 1. 服务端字段访问限制
secret = fields.Char(groups="demo.group_manager")
# 2. 只影响 UI 是否可编辑
amount = fields.Float(readonly=True)
<!-- 3. 只控制这个视图节点是否返回给用户 -->
<field name="amount" groups="demo.group_manager" readonly="1"/>
只有 Python 字段定义的 groups= 是字段级服务端访问边界。readonly=True 的源码说明明确写着只影响 UI,代码赋值仍可工作,见 odoo/fields.py:152-175。
6.2 Python 字段 groups= 如何生效
Field.is_accessible(env) 根据当前用户组判断,见 odoo/fields.py:950-956。ORM 会:
- 在
fields_get()中移除不可访问字段:odoo/models.py:3693-3717。 - 从最终视图架构中移除或合并字段组条件。
- 显式 read/fetch 不可访问字段时抛
AccessError。 - write 不可访问字段时抛
AccessError。 - 在搜索 domain 使用不可访问字段时检查并拒绝。
字段组采用与 has_groups() 一致的表达方式:多个正组是 OR,负组以 !xml_id 表示排除。核心敏感字段建议使用简单、稳定的正组,避免难以解释的复杂组合。
6.3 Odoo 18 create 路径的源码注意事项
当前快照中:
write()明确调用check_field_access_rights('write', vals.keys()),见models.py:4661-4666。BaseModel.create()在models.py:4895-5041检查模型 create ACL,但没有调用check_field_access_rights('create', vals.keys())。
这是基于本版本源码得出的重要结论,不应泛化到未检查的其他版本。对于能影响金额、审批状态、密钥、公司归属或其他完整性的受限字段,建议在模型中加固:
@api.model_create_multi
def create(self, vals_list):
supplied = {name for vals in vals_list for name in vals}
self.check_field_access_rights("create", supplied)
return super().create(vals_list)
这段加固只覆盖调用方显式提供的 key;默认值、precompute、inverse、委托父字段以及业务不变量仍需分别验证。配套实验模块采用了显式 key 检查,并另外约束负责人和初始状态。
6.4 原生字段组的表达力边界
字段只有一套 groups,无法直接表达:
- A 组可读不可写;
- B 组只能在草稿时写;
- 金额十万元以下可写,以上只读;
- 只能修改自己的备注字段;
- 字段值从 X 改到 Y 需要审批。
这些策略应在 write()、专用 action 或增强策略服务中实现。要保护工作流状态,不能只把状态字段在视图里设成 readonly,因为 RPC 可以直接调用 write({'state': ...})。
6.5 安全的字段与状态写入模式
def write(self, vals):
if "state" in vals:
raise AccessError(_("Use a workflow action to change the state."))
return super().write(vals)
def _set_state(self, state):
# 下划线方法不能通过普通 RPC 调用;BaseModel.write 仍会检查记录访问。
return super(MyModel, self).write({"state": state})
def action_approve(self):
self.check_access("write")
if not self.env.user.has_group("my_module.group_approver"):
raise AccessError(_("Only approvers can approve."))
if any(record.state != "submitted" for record in self):
raise UserError(_("Only submitted records can be approved."))
if any(record.owner_id == self.env.user for record in self):
raise AccessError(_("You cannot approve your own request."))
return self._set_state("approved")
这里同时保护了组、记录访问、当前状态和职责分离。按钮上的 groups 只负责让普通用户看不到入口。
老赵解读
字段权限适合保护稳定、敏感的字段,例如成本、薪资或内部控制字段;涉及状态、金额阈值、本人回避等动态条件时,应回到业务方法逐项校验。一个可靠的审批按钮至少要同时检查能力、记录范围、当前状态、关键字段和职责分离,字段 groups 与按钮 groups 都只能承担其中一部分。
目前没有任何评论。