限制数据访问

Important

本教程是 入门 教程的扩展。请确保您已完成该教程,并以您构建的 estate 模块为基础完成本教程的练习。如果想从干净的起点开始,请从 technical-training-solutions 仓库拉取 16.0-core 分支。

到目前为止,我们主要关注实现有用的功能。然而在大多数业务场景中,安全 很快就会成为一个问题:目前,

  • 任何员工(即 group_user 所代表的角色)都可以创建、读取、更新或删除房产、房产类型或房产标签。

  • 如果安装了 estate_account,那么只有被允许参与开票的经纪人才能确认销售,因为这是 创建发票 所必需的。

然而:

  • 我们不希望第三方能够直接访问房产。

  • 并非所有员工都是房地产经纪人(例如行政人员、物业经理等),我们不希望非经纪人看到可用的房产。

  • 房地产经纪人不需要、也不能决定哪些房产类型或标签是 可用的。

  • 房地产经纪人可能拥有 独享 房产,我们不希望某个经纪人能够管理其他经纪人的独享房产。

  • 所有房地产经纪人都应该能够确认其可管理的房产的销售,但我们不希望他们能够验证系统中的任何发票或将其标记为已支付。

Note

对于小型企业来说,上述某些或大部分情况我们或许可以接受。

由于对用户来说,禁用不必要的安全规则比从零开始创建它们更容易,因此宁可谨慎行事、限制访问:用户可以在必要时或方便时放宽这些访问限制。

群组

更多内容

与此主题相关的文档可以在 安全参考 中找到。

Odoo编码规范 文档说明了主数据项的格式和位置。

目标

在本节结束时,

  • 我们可以将员工设为 房地产经纪人 或 房地产经理。

  • admin 用户是一名房地产经理。

  • 我们有一名新的 房地产经纪人 员工,他没有发票或管理方面的访问权限。

每当需要变更时,为员工逐个附加安全规则并不现实,因此用 群组 将安全规则和用户关联起来。它们对应可以分配给员工的角色。

对于大多数 Odoo 应用 1 来说,一个良好的基线是设置 用户 和 经理 (或管理员)角色:经理可以更改应用的配置并监督其整体使用情况,而用户嘛,就使用该应用 2。

这个基线对我们来说似乎已经足够了:

  • 房地产经理可以配置系统(管理可用的类型和标签),并监督管道中的所有房产。

  • 房地产经纪人可以管理由其负责的房产,或未指定给任何经纪人负责的房产。

秉承 Odoo 数据驱动的特性,群组不过是 res.groups 模型的一条记录。它们通常是模块 主数据 的一部分,在模块的某个数据文件中定义。

可以在 这里 找到一个简单的示例。

Exercise

  1. 在适当的文件夹中创建 security.xml 文件,并将其添加到 __manifest__.py 文件中。

  2. 如果还没有添加,请在 __manifest__.py 中添加一个值为 Real Estate/Brokerage 的 'category' 字段。

  3. 添加一条记录,创建 id 为 estate_group_user、名称为“Agent”、类别为 base.module_category_real_estate_brokerage 的群组。

  4. 在其下方添加一条记录,创建 id 为 estate_group_manager、名称为“Manager”、类别为 base.module_category_real_estate_brokerage 的群组。estate_group_manager 群组需要隐含 estate_group_user。

Note

这个 类别 是从哪里来的?它是一个 模块类别。这里我们使用了类别 id base.module_category_real_estate_brokerage,它是 Odoo 根据模块 __manifest__.py 中设置的 category 自动生成的。您还可以在这里找到 Odoo 提供的 默认模块类别 列表。

Tip

由于我们修改了数据文件,请记得重启 Odoo 并使用 -u estate 更新模块。

如果您前往 Settings ‣ Manage Users 并打开 admin 用户(“Mitchell Admin”),您应该会看到一个新的部分:

../../_images/groups.png

将 admin 用户设为 房地产经理。

Exercise

通过网页界面创建一个只具有“房地产经纪人”权限的新用户。该用户不应具有任何发票或管理权限。

使用隐私标签页或窗口以新用户登录(记得设置密码),作为房地产经纪人,您应该只能看到房地产应用,可能还有 Discuss(聊天)应用:

../../_images/agent.png

访问权限

更多内容

与此主题相关的文档可以在 访问权限 找到。

目标

在本节结束时,

  • 不是房地产经纪人的员工将看不到房地产应用。

  • 房地产经纪人将无法更新房产类型或标签。

访问权限最初在 第 5 章:安全 - 简要介绍 中引入。

访问权限是一种让用户 经由 群组访问模型的方式:将访问权限关联到某个群组,拥有该群组的所有用户都会获得该访问权限。

例如,我们不希望房地产经纪人能够修改可用的房产类型,因此不会将该访问权限关联到“用户”群组。

访问权限只能授予访问权限,而不能移除:在检查访问权限时,系统会查看与该用户关联(通过任何群组)的 任何 访问权限是否授予该访问权限。

group

create

读取

更新

delete

A

X

X

B

X

C

X

拥有群组 A 和 C 的用户可以做除删除该对象之外的任何操作,而拥有群组 B 和 C 的用户可以读取和更新它,但不能创建或删除它。

Note

  • 访问权限的群组可以省略,这意味着该 ACL 适用于 每个用户;这是一个有用但有风险的兜底方案,因为根据已安装的应用,它甚至可能授予非用户对该模型的访问权限。

  • 如果没有适用于某个用户的访问权限,则该用户不会获得访问权限(默认拒绝)。

  • 如果某个菜单项指向用户无权访问的模型,且没有该用户可以看到的子菜单,则该菜单将不会显示。

Exercise

将访问权限文件更新为:

  • 为您的房地产经理群组授予所有对象的完全访问权限。

  • 为经纪人(房地产用户)只授予类型和标签的读取权限。

  • 不给任何人删除房产的权限。

  • 检查您的经纪人用户无法更改类型或标签,也无法删除房产,但除此之外可以创建或更新房产。

Warning

记得为您的 ir.model.access 记录使用不同的 xid,否则它们会相互覆盖。

由于“demo”用户没有被设为房地产经纪人或经理,他们甚至应该无法看到房地产应用。使用隐私标签页或窗口检查这一点(“demo”用户的密码是“demo”)。

记录规则

更多内容

与此主题相关的文档可以在 记录规则 找到。

目标

在本节结束时,经纪人将无法看到其同事独享的房产;但经理仍能看到所有内容。

访问权限可以授予对整个模型的访问,但通常我们需要更精细的控制:虽然经纪人总体上可以操作房产,但我们可能不希望他们更新甚至查看由同事管理的房产。

记录 规则 提供了这种精细度:它们可以授予或拒绝对单条记录的访问:

<record id="rule_id" model="ir.rule">
    <field name="name">A description of the rule's role</field>
    <field name="model_id" ref="model_to_manage"/>
    <field name="perm_read" eval="False"/>
    <field name="groups" eval="[Command.link(ref('base.group_user'))]"/>
    <field name="domain_force">[
        '|', ('user_id', '=', user.id),
             ('user_id', '=', False)
    ]</field>
</record>

搜索域 就是管理访问权限的方式:如果记录通过检查则授予访问权限,否则拒绝访问。

Tip

由于规则往往相当复杂且不会批量创建,它们通常以 XML 创建,而不是像访问权限那样使用 CSV。

上面这条规则:

  • 仅适用于“创建”、“更新”(write)和“删除”(unlink)操作:这里我们希望每个员工都能看到其他用户的记录,但只有作者 / 负责人可以更新记录。

  • 是 非全局的,因此我们可以为例如经理提供额外的规则。

  • 如果当前用户(user.id)在记录上被设置(例如创建,或被指派),或者记录根本没有关联用户,则允许该操作。

Note

如果某个模型和操作没有定义或适用的规则,则该操作是被允许的(默认允许),如果访问权限设置不正确(过于宽松),这可能会产生奇怪的效果。

Exercise

定义一条规则,将经纪人限制为只能查看或修改没有销售员、或他们自己就是销售员的房产。

您可能需要再创建一个房地产经纪人用户,或者创建几条销售员是经理或其他用户的房产。

验证您的房地产经理仍能看到所有房产。如果不能,为什么?请记住:

estate_group_manager 群组需要隐含 estate_group_user。

安全覆盖

绕过安全机制

目标

在本节结束时,经纪人应该能够在不需要发票访问权限的情况下确认房产销售。

如果您以房地产经纪人身份尝试将房产标记为“已售出”,您应该会收到一个访问错误:

../../_images/error.png

这是因为 estate_account 在此过程中会尝试创建发票,而创建发票需要拥有全部发票管理权限。

我们希望经纪人能够确认销售而不必拥有完整的发票访问权限,这意味着我们需要 绕过 Odoo 的常规安全检查,以便 尽管 当前用户没有相应权限也能创建发票。

在 Odoo 中有两种主要的绕过现有安全检查的方式,可以是刻意的,也可以是作为副作用:

  • sudo() 方法会创建一个处于“sudo 模式”的新记录集,它会忽略所有访问权限和记录规则(尽管硬编码的群组和用户检查可能仍然适用)。

  • 执行原生 SQL 查询会绕过 ORM 本身,从而作为副作用绕过访问权限和记录规则。

Exercise

更新 estate_account,在创建发票时绕过访问权限和规则。

Danger

应尽量避免使用这些特性,只有在确认当前用户和操作应该能够绕过常规访问权限验证之后,才极其谨慎地使用。

在这种模式下执行的操作也应尽可能少地依赖用户输入,并应尽最大程度对其进行验证。

以编程方式检查安全

目标

在本节结束时,无论 estate 如何变更,发票的创建都应对安全问题具有弹性。

在 Odoo 中,访问权限和记录规则只在 通过 ORM 执行数据访问时 进行检查,例如通过 ORM 方法创建、读取、搜索、写入或删除记录。其他方法不一定检查任何类型的访问权限。

在上一节中,我们在 action_sold 中创建发票时绕过了记录规则。任何用户都可以触发这一绕过,而不会检查任何访问权限:

  • 在 estate_account 的 action_sold 中,在创建发票之前添加一条打印语句(因为创建发票会访问房产,从而触发 ACL 检查),例如:

    print(" reached ".center(100, '='))
    

您应该在 Odoo 日志中看到 reached,随后出现一个访问错误。

Danger

已经在 Python 代码中并不意味着任何访问权限或规则已经或将会被检查。

目前 ,访问权限是通过访问 self 上的数据以及调用 super() 其作用相同并会 更新 self)来隐式检查的,这会触发访问错误并取消事务,从而使我们的发票被”取消创建”。

然而 ,如果将来情况发生变化,或者我们给该方法添加副作用(例如向政府机构报告销售情况),或者 estate 中引入了 bug,……非中介人员就有可能触发他们本不应有权执行的操作。

因此,在执行非 CRUD 操作、合理地绕过 ORM 或安全机制,或触发其他副作用时,执行 显式安全检查 是极其重要的。

可以通过以下方式执行显式安全检查:

  • 检查当前用户是谁(self.env.user),并将其与特定模型或记录进行匹配。

  • 检查当前用户是否拥有特定群组,以硬编码方式允许或拒绝某项操作(self.env.user.has_group)。

  • 在记录集上调用 check_access_rights(operation) 方法,这会验证当前用户是否有权限访问该模型本身。

  • 在非空记录集上调用 check_access_rule(operations),这会验证当前用户被允许对该记录集中的*每一条*记录执行该操作。

Warning

检查访问权限和检查记录规则是两种独立的操作,如果你在检查记录规则,通常还需要事先检查访问权限。

Exercise

在创建发票之前,使用 check_access_rights 和 check_access_rule 来确保当前用户总体上可以更新房产,也可以更新该发票对应的那个特定房产。

重新运行绕过脚本,检查错误是否发生在打印语句之前。

多公司安全

更多内容

多公司指南 概述了多公司功能的一般情况,尤其是 多公司安全规则。

有关规则的一般文档同样可以在 记录规则 找到。

目标

在本节结束时,中介人员应当只能访问其所属中介(或多个中介)的房产。

出于这样或那样的原因,我们可能需要以多个公司的形式管理房地产业务,例如我们可能拥有大体自治的中介机构、加盟模式,或多个品牌(可能源自收购了其他房地产业务),它们在法律或财务上彼此独立。

Odoo 可以在同一系统中管理多个公司,但具体处理由各个模块自行负责:Odoo 本身提供了用于管理公司相关字段和 多公司规则 问题的工具,这正是我们要关注的内容。

我们希望不同的中介机构彼此”隔离”,房产归属于特定的中介机构,而用户(无论是中介人员还是经理)只能看到与其中介机构关联的房产。

和之前一样,由于这基于非平凡的记录,用户放宽规则比收紧规则更容易,因此默认采用相对更强的安全模型是合理的。

多公司规则其实就是基于 company_ids 或 company_id 字段的记录规则:

  • company_ids 是当前用户有权访问的所有公司

  • company_id 是当前处于活动状态的公司(用户当前正在为之工作的公司)。

多公司规则 通常 会使用前者,即检查记录是否与用户有权访问的公司中的 某一家 相关联:

<record model="ir.rule" id="hr_appraisal_plan_comp_rule">
    <field name="name">Appraisal Plan multi-company</field>
    <field name="model_id" ref="model_hr_appraisal_plan"/>
    <field name="domain_force">[
        '|', ('company_id', '=', False),
             ('company_id', 'in', company_ids)
    ]</field>
</record>

Danger

多公司规则通常是 全局的,否则额外添加的规则很有可能允许绕过多公司规则。

Exercise

  • 为 estate.property 添加一个 company_id 字段,它应为必填(我们不想要没有中介机构的房产),并且应默认为当前用户当前所属的公司。

  • 创建一个新公司,并在该公司中创建一名新的房产中介人员。

  • 经理应当是这两家公司的成员。

  • 原有的中介人员应当只是原公司的成员。

  • 在每个公司中创建几条房产(可以以经理身份使用公司选择器,也可以使用中介人员账号)。取消默认销售人员,以避免触发 那条 规则。

  • 所有中介人员都能看到所有公司,这并不理想,请添加记录规则来限制这种行为。

Warning

在更改模块的模型或数据时,记得对模块执行 --update

可见性 != 安全

目标

在本节结束时,房产中介人员不应看到房产应用的设置菜单,但仍应能够设置房产类型或标签。

特定的 Odoo 模型可以直接与群组(或公司、用户)关联。在使用之前,重要的是弄清这种关联是 安全 特性还是 可见性 特性:

  • 可见性 特性意味着用户仍然可以通过其他方式访问模型或记录,例如通过界面的其他部分,或通过 使用 RPC 远程执行操作,只是在某些上下文中这些内容可能不会显示在网页界面上。

  • 安全 特性意味着用户无法访问记录、字段或操作。

以下是一些示例:

Exercise

房产中介人员无法添加房产类型或标签,但在创建房产时可以从房产表单视图中看到它们的选项。

设置菜单只会给他们的界面增添干扰,请将其设置为仅对经理可见。

尽管不再能访问房产类型和房产标签菜单,中介人员仍然可以访问底层对象,因为他们仍能为自己的房产选择标签或类型。

1

Odoo 应用是覆盖某一业务领域的一组相关模块,通常由一个基础模块以及在该基础上的若干扩展组成,用于添加可选或特定功能,或与其他业务领域建立关联。

2

对于大多数或所有员工都会使用的应用,可以取消”应用用户”角色,并将其权限直接授予所有员工,例如通常所有员工都可以提交报销或休假。