先说结论
Odoo 的权限不是一层,而是至少四层:
- ACL(
ir.model.access):这个模型能不能读、写、建、删 - 记录规则(
ir.rule):在允许访问模型之后,哪些具体记录能看 - 视图 groups:界面上哪些按钮、字段、菜单该显示给谁
sudo():有意识地绕过权限检查,用于特定内部逻辑
最常见的错误就是把这四层混在一起。
比如:
- 以为 UI 看不到,就代表后端也访问不到
- 以为有 ACL,就不需要 record rule
- 以为
sudo()只是“少写点权限代码”,不会影响数据边界
实际上,它们解决的是不同问题。
第 1 层:ACL 决定“能不能碰这个模型”
在 odoo/addons/base/models/ir_model.py 里,ir.model.access 维护的是模型级权限。
它回答的是非常基础的问题:
这个用户对这个 model,是否有 read / write / create / unlink 权限?
源码里的 check() 会先判断当前用户是否在允许组里,如果没有,就直接抛错。
这意味着 ACL 是第一道闸门:
- 没有 ACL,连模型层的门都进不去
- 有 ACL,也不代表能看见所有记录
所以 ACL 是“模型访问许可”,不是“数据行访问许可”。
第 2 层:record rule 决定“能看到哪几行”
在 odoo/addons/base/models/ir_rule.py 里,_compute_domain() 会把所有适用的记录规则合成为一个 domain。
这里最关键的规则是:
- global rules:全局规则,彼此是 AND
- group rules:组规则,先 OR,再与 global rules AND
换句话说,记录规则不是“加一条就完事”,而是有合并语义的。
再加上 check_access() 在真正检查记录时,会把规则域套到实际记录集上,过滤出 forbidden records。
所以你看到的结果通常不是:
- “有没有权限”
而是:
- “这个记录集里,哪些能看,哪些不能看”
这也是为什么记录规则经常和多公司、仓库、团队、销售区域等边界一起出现。
第 3 层:视图 groups 只是 UI 可见性
在 ir.ui.view 的 postprocess 阶段,Odoo 会根据 groups 把一些节点从前端 arch 里移掉,或者调整按钮权限标记。
这很有用,但它只负责“界面展示”。
它不等于后端安全。
也就是说:
- 你把按钮藏起来,不代表 API 调不到
- 你把字段隐藏掉,不代表用户不能通过别的入口写入
真正的安全边界,还是要回到 ACL + record rule + 后端业务校验。
所以,groups 适合控制“看见什么”,不适合单独承担“能不能做”的责任。
第 4 层:sudo() 是明确的绕过,不是“普通快捷方式”
在 odoo/orm/models.py 里,check_access() 会先查 ACL,再查规则。
但如果环境已经是 env.su,很多检查会直接短路。
这意味着 sudo() 的意义非常强:
- 它不是“临时提高一点点权限”
- 它是“显式切换到超级用户语义”
因此,写代码时要问自己:
- 为什么这里必须绕过权限?
- 绕过以后,返回给前端的数据会不会过宽?
- 有没有必要在 sudo 后再做一次业务过滤?
如果不想清楚,sudo() 很容易成为数据泄漏的入口。
一条完整链路长什么样
odoo/orm/models.py 里的 _check_access() 很直白:
- 先查
ir.model.access - 再查
ir.rule - 只要任一层不通过,就报错
也就是说:
ACL 管“模型层”,record rule 管“记录层”。
这两个阶段不是二选一,而是串联关系。
常见误区:把视图 groups 当成安全边界
这个误区非常常见。
例如:
- 只在按钮上加
groups="base.group_system" - 就以为普通用户完全不可能触发对应逻辑
实际上,如果那个逻辑被别的 RPC、action、server action、导入工具或者自定义代码调用,UI 限制根本不够。
所以“前端看不见”只能算体验控制,不能算安全控制。
调试权限问题时,先按这个顺序看
- 先看 ACL:模型有没有基本读写权限
- 再看 record rule:是不是规则域把记录过滤掉了
- 再看
sudo():是不是某段逻辑绕过了检查 - 最后看 view groups:只是界面为什么没显示
这样排查,基本不会把“UI 问题”和“数据权限问题”混成一团。
一个实用的判断标准
如果你在设计权限边界,可以用一句话自检:
这个限制是“能不能碰模型”,还是“能不能看到记录”,还是“界面要不要显示”?
只要这个问题没分清,权限设计就很容易乱。
总结
Odoo 权限的正确理解方式是分层:
- ACL 先决定模型是否可访问
- record rule 再决定哪些记录可访问
- view groups 只负责 UI 呈现
sudo()是显式绕过,不是轻量优化
把这四层分开,你就不会再把“看不见”误当成“访问不到”。
English sidecar
The short version
Odoo permissions are layered. They are not one lock.
- ACL (
ir.model.access) decides whether a user can read, write, create, or delete a model - Record rules (
ir.rule) decide which specific records are visible after model access is granted - View groups decide what the UI should show
sudo()is an explicit bypass for special internal logic
A very common mistake is to mix these layers up.
Layer 1: ACL controls the model
ir.model.access answers the basic question:
Can this user touch this model at all?
If the answer is no, Odoo stops before record rules even matter.
So ACL is the model-level gate, not the row-level filter.
Layer 2: record rules control the records
ir.rule._compute_domain() merges all applicable rules into a domain.
The key behavior is:
- global rules are ANDed
- group rules are ORed together first, then ANDed with globals
That means record rules are not just “one more restriction”. They have merge semantics.
Layer 3: view groups are only UI visibility
ir.ui.view post-processing can remove or tweak nodes based on groups.
Useful? Absolutely.
But it only controls presentation.
Hiding a button does not protect the backend API.
Layer 4: sudo() is an explicit bypass
In odoo/orm/models.py, access checks first consult ACLs and then record rules.
But once the environment is sudoed, those checks short-circuit.
So sudo() is not a small convenience. It is a strong semantic change.
Always ask:
- Why is bypassing necessary here?
- Will the returned data be too broad?
- Should I still apply business filtering after sudo?
Practical debugging order
When something is denied, check in this order:
- ACL
- record rules
sudo()usage- view groups
That keeps UI problems and data-security problems separate.
Takeaway
Odoo permissions are easiest to understand if you keep the layers separate:
- ACL = model access
- record rules = record access
- view groups = UI visibility
- sudo = deliberate bypass
If you treat “not visible” as “not accessible”, you will eventually debug the wrong layer.
DISCUSSION
评论区