本教材附带一个可安装的最小模块:
myaddons/odoo_permission_lab/
├── __manifest__.py
├── models/lab_document.py
├── security/permission_lab_groups.xml
├── security/ir.model.access.csv
├── security/permission_lab_rules.xml
├── views/lab_document_views.xml
└── tests/test_permission_lab.py
它不是生产权限模块,而是用最少代码把本文公式变成可重复实验。只应安装在专用学习数据库。
10.1 安装与测试
请使用你当前环境中真实的配置文件、addons path 和专用学习库名:
./odoo-bin -c <学习环境配置> -d <专用学习库> \
--addons-path=myaddons,addons -i odoo_permission_lab --stop-after-init
在全新专用测试库中运行模块测试,要用 -i 安装:
./odoo-bin -c <学习环境配置> -d <专用测试库> \
--addons-path=myaddons,addons -i odoo_permission_lab \
--test-enable --test-tags /odoo_permission_lab --stop-after-init
只有模块已经安装在该专用库中时,才把 -i 换成 -u 做升级测试。不要把 -i、-u 或测试命令直接指向生产数据库。若现有配置已经声明完整 addons path,应以现有配置为准。
10.2 模型设计
实验模型为 permission.lab.document:
| 字段 | 用途 | 要观察的权限点 |
|---|---|---|
name |
标题 | 普通字段 |
owner_id |
负责人 | 本人范围规则 |
member_ids |
共享成员 | OR 条件 |
company_id |
公司 | 全局规则 |
related_document_id |
关联文档 | check_company=True 关联一致性 |
amount |
金额 | 普通可写字段 |
state |
工作流状态 | UI readonly + 服务端专用 action |
confidential_note |
经理备注 | Python 字段 groups= |
模型设置 _check_company_auto = True,并在 related_document_id 上设置 check_company=True,用于把关联一致性与记录可见性分开实验。状态不能通过普通 write() 改,只能通过 action_submit() 和 action_approve();后者显式检查组、记录 write、状态和“不能自批”。
10.3 组与 ACL
Permission Lab / User
implied -> base.group_user
Permission Lab / Manager
implied -> Permission Lab / User
ACL:
| 组 | Read | Write | Create | Unlink |
|---|---|---|---|---|
| User | 1 | 1 | 1 | 0 |
| Manager | 1 | 1 | 1 | 1 |
经理继承用户组,因此两条 ACL 都适用,最终做并集。用户没有任何 unlink grant,所以即使某条记录规则匹配也不能删除。
10.4 三条规则如何组合
实验模块配置:
全局 Company:company_id in company_ids
组 User:owner_id = user.id OR member_ids 包含 user.id
组 Manager:TRUE
最终:
普通用户 = Company AND (OwnOrShared)
经理 = Company AND (OwnOrShared OR TRUE)
= Company
经理可以看当前启用公司的全部实验文档,但经理的 TRUE 组规则不能突破全局公司规则。这是设计强制边界时最重要的组合模式之一。
User 规则对四种操作都生效,所以共享成员不仅能读,也能写整条记录,包括 owner_id、member_ids 和 company_id。这是为了集中演示宽规则和“写前检查”的故意设计,不是推荐的生产策略;真实模块通常应拆分 read 与 write/create 规则,并在 write() 中保护负责人、成员、公司等边界字段。
10.5 建议建立的测试用户
| 用户 | 组 | 用途 |
|---|---|---|
| A | Permission Lab / User | 创建自己的文档 |
| B | Permission Lab / User | 创建另一条文档并共享给 A |
| M | Permission Lab / Manager | 看全量、审批、访问经理字段 |
| N | 仅 Internal User | 验证没有实验模型 ACL |
准备记录:
| 记录 | Owner | Members | Company |
|---|---|---|---|
| D1 | A | 空 | C1 |
| D2 | B | 空 | C1 |
| D3 | B | A | C1 |
| D4 | B | 空 | C2 |
在只启用 C1 时,预期 read:
| 用户 | D1 | D2 | D3 | D4 |
|---|---|---|---|---|
| A | 可见 | 不可见 | 可见 | 不可见 |
| B | 不可见 | 可见 | 可见 | 不可见 |
| M | 可见 | 可见 | 可见 | 不可见 |
| N | 无模型 read ACL | 无 | 无 | 无 |
10.6 十个动手实验
实验 1:没有 ACL 时默认拒绝
让 N 直接 search 模型,观察 ACL AccessError。然后不要改规则,只给 N 加实验 User 组,确认 ACL grant 后才能进入规则层。
实验 2:ACL 做并集
临时创建一个只给 unlink 的额外组并授予 A,观察 A 的最终权限变成原 User ACL 加 unlink,而不是新组覆盖旧组。
实验 3:search 静默过滤与 browse 抛错
以 A 搜索 D2,结果应为空;再以 A browse(D2.id).read(['name']),观察显式访问错误。
实验 4:组规则 OR
给 A 加 Manager 组,A 同时匹配 User 和 Manager 规则。观察 Manager 的 TRUE 规则把 C1 内范围放宽到全部。
实验 5:全局规则不可被经理放宽
只启用 C1 时,以 M 搜索 D4。即使经理组规则为 TRUE,D4 仍被全局 Company 规则拒绝。
再把 C1 文档的 related_document_id 指向 D4,观察 _check_company_auto + check_company=True 即使在 sudo 下也拒绝跨公司关联。前一个实验回答“能否看到记录”,后一个回答“能否建立关联”,二者不能互相替代。
实验 6:perm_* 不是拒绝
复制 User 规则,取消 perm_write,然后停用原 User 规则。观察这条规则不再限制 write,而不是禁止 write。规则数据位于 noupdate="1" 区域,手工改动后普通模块升级不会可靠恢复原值;实验完成后应手工恢复并核对,最稳妥的做法是重建专用学习库。
实验 7:字段组
以 A 显式 read/write confidential_note,应抛 AccessError;以 M 操作应成功。再尝试 A 在 create payload 中传该字段,实验模型的 create 加固同样应拒绝。
实验 8:按钮隐藏不能保护方法
以 A 直接调用 action_approve(),即使不经过 UI 按钮,也会进入公开方法。确认服务端组校验拒绝 A。然后临时注释方法内组校验,仅依赖按钮 groups,观察为何会形成漏洞。
实验 9:write 规则检查旧状态
A 对自己的 D1 有 write。让 A 直接把 owner_id 改为 B;当前实验模型故意没有禁止负责人转移。写入可能成功,随后 D1 从 A 的 search 结果中消失。这正是“记录规则不是写后业务约束”的演示。
生产代码若不允许转移,应在 write 中限制 owner_id,而不是只依赖 own rule。
实验 10:职责分离
A 提交 D1,M 审批应成功。再让 M 创建、提交自己的文档并尝试自批,方法应拒绝。确认这是业务方法的规则,而不是 ACL 或 record rule 自动提供的能力。
10.7 实验与自动化测试的对应关系
| 实验 | 当前自动化覆盖 | 仍需手工观察 |
|---|---|---|
| 1 | outsider 无 ACL 被拒绝 | 运行时加组及 cache 失效 |
| 2 | User/Manager ACL 正向行为 | 临时额外 unlink grant 的并集 |
| 3 | search 过滤、显式 read 抛错 | 错误提示的界面差异 |
| 4 | Manager TRUE 规则放宽组规则范围 | 给现有 A 动态加组 |
| 5 | 全局公司规则、显式读取、非法 context 与跨公司关联 | 公司切换器中的交互 |
| 6 | 无 | 克隆库中改 perm_write 并重建库 |
| 7 | read/write/create 字段限制 | fields_get 和表单裁剪 |
| 8 | 直接方法调用和直接 state write 被拒绝 | 浏览器中按钮裁剪、真实 JSON-RPC |
| 9 | owner 写前规则实验 | search 结果变化的界面观察 |
| 10 | 正常审批、自批/重复审批拒绝、非草稿删除拒绝 | 错误展示与批量边界 |
测试模块主要验证服务端安全边界,不等于已经覆盖菜单、动作、视图裁剪和完整 HTTP 路由。学习时保留手工实验;生产模块还应补 HttpCase、浏览器流程、组撤销、缓存和性能回归。
10.8 延伸练习
完成基础实验后,可以逐项扩展,但每次只改一个变量:
- 将 User 的 read 范围设为本人或成员,write 范围改为仅本人。
- 增加“已提交记录普通用户只读”的策略,并分析组规则 OR 是否意外放宽。
- 增加明细行模型,分别保护主单和行。
- 增加报表动作,测试隐藏 Print 入口后能否直达 URL。
- 增加 portal 用户和 token controller,实践先非 sudo、后 token、再最小 sudo。
- 增加部门树
parent_path,比较child_of与预计算部门范围的 SQL。 - 给角色增加到期时间,验证只靠 cron 回收为什么存在时间窗口。
- 模拟 sudo 预取后
with_user(),写缓存回归测试。
老赵解读
实验模块的价值不在于展示一套标准答案,而在于把抽象规则变成可重复的观察。建议每次只改变一个变量:换用户、换公司、换记录状态或增减一条规则,然后同时观察界面、ORM 结果、异常和 SQL。最终把每个手工实验固化为自动化测试,学习成果才会变成可复用的工程资产。
目前没有任何评论。