权限校验链

Odoo 权限到底按什么顺序卡你:ACL、记录规则、sudo 与多公司上下文讲透

从 Odoo 19 源码出发,讲清一次访问为什么会先过 ACL、再过记录规则,以及 sudo 和 allowed_company_ids 为什么会让同一段代码在不同环境下表现完全不同。

Odoo 开发 框架
进阶 开发者 2 分钟阅读
0 评论 0 点赞 0 收藏 5 阅读

先记住一句大白话

在 Odoo 里,一次“我能不能看 / 改这条记录”,通常不是只看用户组,也不是只看一条 ir.rule

真正的顺序更像这样:

  1. 先过模型级权限(ACL / ir.model.access
  2. 再过记录级权限(ir.rule
  3. 如果用了 sudo(),前两层大部分都直接绕开
  4. 如果没 sudo,多公司上下文还会参与记录规则的计算与缓存

所以很多权限问题,不是“规则写错了”,而是你根本在错误的层面排查。


第一层:ACL 先决定“你有没有资格碰这个模型”

/home/ubuntu/odoo-temp/odoo/orm/models.py 里,check_access() 最终会走到 _check_access()

这个方法一上来先看的是:

Access = self.env['ir.model.access']
if not Access.check(self._name, operation, raise_exception=False):
    return self, ...

这一步只回答一个问题:

你对这个模型,原则上有没有 create/read/write/unlink 的资格?

比如:

  • 你对 sale.order 没有 write ACL
  • 那你连“写销售订单”这件事都不成立
  • 根本还轮不到记录规则出场

这就是为什么有些同学一直盯着 ir.rule,结果怎么改都没用:

因为门卫还在第一层,第二层根本没开始查。


第二层:记录规则决定“这个模型里的哪些记录你能碰”

如果 ACL 通过,_check_access() 才会继续看 ir.rule

Rule = self.env['ir.rule']
domain = Rule._compute_domain(self._name, operation)
if domain and (forbidden := self - self.sudo().with_context(active_test=False).filtered_domain(domain)):
    return forbidden, ...

这里的关键心智模型是:

  • ACL 管的是 模型级入口
  • ir.rule 管的是 记录级过滤

也就是说:

  • ACL 说“你能不能进这个仓库”
  • Rule 说“进来以后,你能碰哪几排货架”

所以你会看到一种很常见的现象:

  • 用户能打开列表页
  • 但只能看到部分记录
  • 或者能看到记录却不能修改某几条

这类问题,大概率就是规则层,而不是 ACL 层。


第三层:_compute_domain() 不是简单拼接,而是“全局 AND,本地组 OR”

ir_rule.py 里,_compute_domain() 会把规则拆成两类:

  • 全局规则:不绑 groups 的规则
  • 组规则:绑了 groups 的规则

源码最后的组合方式很重要:

  • 全局规则之间:AND
  • 组规则之间:OR
  • 然后整体再并回全局域里

这意味着:

1)全局规则越多,通常越严

因为是 AND。

例如:

  • 规则 A:只能看自己公司
  • 规则 B:只能看自己负责的客户

最后不是“满足其一就行”,而是两个都要满足

2)组规则更像“给不同角色开不同门”

例如:

  • 销售组规则:看自己销售单
  • 经理组规则:看全团队销售单

如果同一用户命中了多个组规则,Odoo 会把它们做 OR。

所以调权限时,不能只看某一条 rule,要看:

  • 它是 global 还是 group rule
  • 用户到底命中了哪些组
  • 最终是被 AND 严格收紧,还是被 OR 放宽

第四层:sudo() 不是“方便调试”,而是“直接切到管理员视角”

ir_rule.py 里的 _get_rules() 有个特别关键的分支:

if self.env.su:
    return self.browse(())

也就是说,一旦你在 superuser 环境里:

  • 不是“把全部规则都拿出来继续算”
  • 而是规则集合直接变空

models.py 里的 check_access() / has_access() 也都把 env.su 当成快速放行条件。

所以 sudo() 的真实含义是:

我现在不是在观察普通用户的权限边界,我是在绕过它。

这就是最常见的调试误区:

  • sudo().search(...)
  • 发现所有记录都能看到
  • 然后断言“规则没生效”

其实不是规则没生效,而是你已经不在规则适用的世界里了


第五层:多公司不是附加条件,而是规则上下文的一部分

ir_rule.py_eval_context() 会给 rule 提供:

  • user
  • company_ids
  • company_id

其中 company_ids 就来自当前激活公司的集合,也就是用户切公司后那组 allowed_company_ids

同时,_compute_domain_keys() 默认返回:

['allowed_company_ids']

_compute_domain() 又挂了 ormcache

这表示:

  • 规则不仅按用户和模型缓存
  • 还按 当前公司上下文 缓存

所以你会遇到这些现象:

  • 同一个用户,切一下公司,搜索结果就变了
  • 同一段代码,在不同 cron / request / shell 上下文里表现不同
  • 某次你觉得“缓存有问题”,其实是 allowed_company_ids 变了

这不是 Odoo 在抽风,而是它明确把多公司当成权限求值的一部分。


为什么有时“能读不能写”

很多人第一次碰到这个现象会困惑:

  • 列表里能搜到记录
  • 表单也能打开
  • 一点保存就报 AccessError

原因通常有两类:

1)ACL 的 read / write 分开

模型访问本来就是按操作拆的。

你可能有 read ACL,但没有 write ACL。

2)记录规则也是按操作拆的

ir.rule 本身有:

  • perm_read
  • perm_write
  • perm_create
  • perm_unlink

所以完全可能出现:

  • 读规则放得宽
  • 写规则收得紧

从业务角度也很合理:

  • 允许客服看全部工单
  • 但只允许负责人修改自己的工单

所以“能看到但不能改”不是矛盾,而是 Odoo 权限模型本来就支持的设计。


一个最实用的排查顺序

以后再遇到权限问题,建议按这个顺序排:

第 1 步:先确认有没有 sudo() / with_user()

先搞清楚你看到的是:

  • 普通用户上下文
  • 还是管理员上下文

这个判断错了,后面全白看。

第 2 步:查 ACL

确认这个用户对该模型的 create/read/write/unlink 是否本来就有权限。

第 3 步:查 rule 是 global 还是 group

重点看:

  • rule 绑定了哪些 group
  • 用户实际命中了哪些 group
  • 是读失败还是写失败

第 4 步:查 allowed_company_ids

尤其是多公司场景:

  • shell context
  • HTTP request context
  • cron context
  • 测试用例 context

很可能并不一致。

第 5 步:最后再看 domain 具体内容

很多时候问题根本不在 domain_force 语法,而在:

  • 规则没被命中
  • 缓存上下文不一致
  • 你在 sudo 环境下调试

开发时最容易踩的 4 个坑

坑 1:用 sudo() 写业务逻辑,结果把本该受控的写操作全放飞了

sudo() 很适合:

  • 系统级维护
  • 后台修复
  • 框架层内部动作

但如果你把它随手塞进普通业务逻辑,常见后果就是:

  • 用户本来不该改的数据也改了
  • 记录规则完全失效
  • 多公司隔离被无意穿透

坑 2:只测 read,不测 write

很多权限 bug 其实只在 write / unlink 才暴露。

坑 3:切了公司但没意识到缓存键变了

你以为在复现“同一个问题”,实际上环境已经不是同一个了。

坑 4:把“看不到记录”和“字段只读”混为一谈

记录规则管的是记录集合;字段 readonly、groups、view attrs 又是另一层。

不要把 UI 层限制和 ORM 安全边界混成一锅。


一句话总结

Odoo 的权限不是“用户组打勾”这么简单,而是 ACL 先过模型门槛,Record Rule 再筛记录集合,sudo 负责整体绕过,而多公司上下文又会进入规则求值与缓存键 的分层系统。

把这四层分清,你排权限问题会快很多,也不容易被 sudo 和多公司假象带偏。

DISCUSSION

评论区

想参与讨论?先 登录 再发表评论。
还没有评论,你可以成为第一个留言的人。