权限分层

Odoo 的 ACL、记录规则和 sudo:权限到底在哪一层被拦住

Odoo 的权限不是一把锁,而是多层门禁:模型访问、记录规则、组继承和 sudo 各管一段,缺一层就会误判。

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

Odoo 的权限系统最容易让人误会的一点,就是把它当成“有权限 / 没权限”的单层开关。

实际上它更像三层门禁:

  1. 模型访问控制(ACL):这个人能不能碰这个模型;
  2. 记录规则(record rule):这个人能碰这个模型里的哪几条记录;
  3. 组和视图可见性:界面上能不能看到、代码里是不是被当成有效成员。

只看其中一层,很容易把问题判断错。

一、组不是简单标签,而是一套“集合”

res.groups 里,implied_idsall_implied_ids 说明了一件事:

一个组不仅包含自己,还会继承它隐含的组。

这也是为什么 Odoo 里有 all_group_ids 这个概念。它不是“手工选中的组”,而是用户的有效组集合

res.users.has_group()_get_group_ids() 也反映了这个设计:

  • has_group() 只适合判断当前用户的有效成员关系;
  • _get_group_ids() 被缓存起来,避免每次都重新算;
  • 非内部用户去 RPC 读别人的组信息会被拦住,避免泄漏。

这说明“属于哪个组”本身就是一项有边界的敏感判断。

二、记录规则由 ir.rule 统一编译

ir.rule 的核心字段很直接:

  • model_id:作用在哪个模型;
  • domain_force:规则域;
  • perm_read/write/create/unlink:在哪些动作上生效;
  • groups:哪些组可见。

_get_rules() 会根据模型名、操作类型、当前用户组,找出可用规则。

这里有两个重要点:

  • env.su 会让规则查询直接绕开;
  • 规则不是“每条都 AND”,而是要分 global 和 group 两种语义来合并。

也就是说,权限不是一张平铺的表,而是先筛选、再组合、再计算。

三、_compute_domain() 才是最终的“有效规则”

_compute_domain() 会把规则编译成最终域:

  • 全局规则用 AND 叠加;
  • 组规则先按组做 OR,再合并进总域;
  • 还会把继承模型的限制一起带进去。

它还把 allowed_company_ids 放进缓存键里。这个细节很关键,因为多公司场景下,同一个规则域在不同公司上下文里可能完全不同。

所以如果你遇到“切一下公司,权限表现就变了”,通常不是随机 bug,而是缓存键和上下文正在按设计工作。

四、sudo() 不是“更安全”,只是“换成超级用户视角”

很多开发者会把 sudo() 当成一个万能补丁:

  • 读不到就 sudo;
  • 写不进去就 sudo;
  • 规则太复杂也 sudo。

这会让调试变得很痛苦。

因为 sudo() 改变的不是“业务事实”,而是校验视角。你在 sudo 视角里看到的结果,可能并不是普通用户真正会看到的结果。

所以 sudo() 应该是手术刀,不是胶带:

  • 在框架代码里,它有明确用途;
  • 在业务代码里,它应该少而准;
  • 如果只是为了绕过错误的 domain,那通常是在掩盖设计问题。

五、排查权限问题的顺序

一个实战上比较稳的顺序是:

  1. 先看 ACL:模型层面有没有 ir.model.access
  2. 再看 record rule:当前用户的规则域是什么;
  3. 再看 group inheritance:用户是不是通过 implied groups 得到了更大的权限;
  4. 最后看 company context:是不是多公司把缓存和规则分支都切换了。

这么排,比一上来就改 sudo() 靠谱得多。

结论

Odoo 的权限校验不是一层,而是分层:

  • ACL 负责“能不能碰模型”;
  • record rule 负责“能碰哪条记录”;
  • group 继承和上下文负责“以什么身份看世界”。

只要把这三层分开想,很多“权限不对”的问题其实会立刻清楚。

DISCUSSION

评论区

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