跳至内容

第11章 调试、测试与性能检查

11.1 固定的排障顺序

权限问题不要从“再加一条规则试试”开始。按这个顺序查:

1. 当前 env:uid、su、allowed_company_ids
2. 用户类型:Internal / Portal / Public
3. 有效组:包括物化的 implied groups
4. ACL:目标模型和 operation 是否至少一条 grant
5. 全局规则:每一条是否匹配目标记录
6. 组规则:当前用户匹配哪些,OR 后是否通过
7. 字段 groups:错误是否其实来自字段
8. 业务方法:状态、额度、职责分离、公司校验
9. UI 入口:菜单/按钮为什么显示或隐藏
10. sudo、SQL、controller、缓存:是否绕过或复用旧值

先确认失败在哪一层,再修改最小范围。盲目增加一条 TRUE 组规则常常会修好一个用户,却意外放宽同组所有人。

11.2 在 Odoo shell 中观察

以下仅用于专用开发/测试库:

user = env.ref("base.user_demo")
Model = env["permission.lab.document"].with_user(user)

user.groups_id.mapped("full_name")
Model.browse().has_access("read")
Model.browse().has_access("write")

domain = env["ir.rule"].with_user(user)._compute_domain(
    "permission.lab.document", "read"
)
domain

Model.search([]).ids

env['ir.rule']._compute_domain() 是内部方法,适合源码学习和调试,不应成为业务模块长期依赖的公开 API。观察错误时注意普通用户和 debug 内部用户看到的规则细节不同;源码会避免向 portal/public 泄露敏感记录名与规则信息。

11.3 有效权限矩阵不要只列 CRUD

建议用下面的列设计测试和未来权限驾驶舱:

角色 入口 模型 CRUD 数据范围 字段读 字段写 业务能力 公司 有效期 来源
销售员 销售订单 R/W/C 本人及未分配 报价字段 草稿字段 确认限额内订单 C1 长期 岗位
销售经理 销售订单 R/W/C/U 本部门及下级 全部 除审计字段 审批,不可自批 C1/C2 兼岗三个月 授权单

只列“有/无菜单”或四个 CRUD,会漏掉最有业务价值的差异。

11.4 自动化测试的基本结构

推荐使用 new_test_user()with_user()with_context() 和明确的 AccessError 断言。配套测试见 myaddons/odoo_permission_lab/tests/test_permission_lab.py

from odoo.exceptions import AccessError
from odoo.tests.common import TransactionCase, new_test_user


class TestSecurity(TransactionCase):
    @classmethod
    def setUpClass(cls):
        super().setUpClass()
        cls.user = new_test_user(
            cls.env,
            login="security_user",
            groups="my_module.group_user",
        )

    def test_user_cannot_read_other_record(self):
        record = self.env["my.model"].sudo().create({...})
        with self.assertRaises(AccessError):
            record.with_user(self.user).read(["name"])

    def test_public_method_checks_group(self):
        record = self.env["my.model"].sudo().create({...})
        with self.assertRaises(AccessError):
            record.with_user(self.user).action_approve()

不要只断言按钮不存在。模型方法测试更接近恶意或误用的直接 RPC。对自定义 controller 再补 HttpCase,验证真实路由、token、状态码和响应脱敏。

11.5 必须覆盖的测试矩阵

身份和组

  • 内部、门户、公共用户。
  • 直接组、隐含组、多条继承路径。
  • 管理员组与真正 sudo 的差异。
  • 增加、撤销和用户降级。

CRUD 和记录范围

  • read、write、create、unlink 分开测。
  • search 静默过滤与显式 browse 抛错。
  • 单记录和混合允许/禁止的批量 recordset。
  • 全局规则 AND、组规则 OR、无适用组规则。
  • create 后验检查、write 前态检查。

字段与业务动作

  • fields_get、read、write、create payload、search domain。
  • 直接调用公开方法,不通过按钮。
  • 状态迁移、金额边界、自己审批自己、重复审批。
  • import/export/report/attachment 等旁路。

多公司

  • 单公司、启用多公司、只允许但未启用的公司。
  • 非法 allowed_company_ids
  • company_id=False 的共享语义。
  • _check_company 的跨公司关联。
  • sudo 与非 sudo 回归。

缓存与生命周期

  • 同事务改组后立即生效。
  • 新请求和跨 worker 生效。
  • 用户调部门、换岗位。
  • 临时授权刚到期的边界。
  • 规则修改和模块升级。
  • sudo 预取后降权。

11.6 权限测试应包含“允许”和“拒绝”两面

只测拒绝容易把业务全部锁死仍然通过;只测允许容易遗漏越权。每条策略至少写:

一个应允许的用户 × 一个应允许的记录/操作
一个应拒绝的用户 × 同一记录/操作
一个相同用户 × 边界外记录/状态

例如“部门经理可审批下属费用”至少要测:直属下属、下级部门、其他部门、经理本人、已审批状态和超额度。

11.7 性能分析

记录规则最终进入业务 SQL,测试数据量必须接近真实规模。重点观察:

  • 负责人、公司、部门、状态字段是否有索引。
  • child_of 是否依赖正确的 parent_path 和索引。
  • 多层关系 domain 是否产生大量 JOIN/EXISTS。
  • 多条组规则 OR 是否破坏索引选择。
  • 是否把数万个部门/用户 ID 永久展开成 IN (...)
  • search、read_group、报表和导出是否一致变慢。

在脱敏的测试环境使用 SQL 日志、查询计数和:

EXPLAIN (ANALYZE, BUFFERS) ...

不要在繁忙生产库随意运行带 ANALYZE 的重查询。先用 EXPLAIN,再在隔离环境复现。

11.8 原生审计能力的边界

_log_access 主要保留:

  • create_uid / create_date
  • 最后一次 write_uid / write_date

它不是完整变更历史。res.users.log 还会清理为每用户最近登录记录,见 res_users.py:299-315。原生用户-组 M2M 也没有“谁因为什么授权、审批单、前后值、何时到期”的不可变事件链。

权限增强模块若要满足内部控制与追责,需要独立审计事件,并考虑失败操作随事务回滚的问题。拒绝事件可写结构化服务器日志或外部审计接收端,不能简单在同一失败事务里 create 一条日志后再抛异常。

老赵解读

权限排障最有效的方法是固定顺序,不凭感觉乱改规则:先复现身份和公司上下文,再查 ACL、字段、记录规则、业务方法,最后检查 sudo、控制器和缓存。测试也必须成对出现,既证明该放行的人能完成工作,也证明不该放行的人确实被拒绝;只有成功用例的权限测试几乎没有安全价值。

额定值
0 0

目前没有任何评论。

成为第一个发表评论的人。