限制数据访问

重要

本教程是 服务器框架入门 教程的扩展。确保您已完成该教程,并使用您构建的 estate 模块作为本教程练习的基础。

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

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

  • 如果安装了 estate_account,那么只有被允许处理发票的代理才能确认销售,因为这是 创建发票 所必需的。

然而:

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

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

  • 房地产代理不需要也不应该决定哪些房产类型或标签是 可用的

  • 房地产代理可以拥有 专属 房产,我们不希望一个代理能够管理另一个代理的专属房产。

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

注解

对于小型企业来说,我们实际上可能对其中的一些或大部分情况是可以接受的。

因为用户禁用不必要的安全规则比从零开始创建它们更容易,所以最好谨慎行事并限制访问:如果必要或方便,用户可以放宽这些限制。

参见

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

编码规范 文档记录了主数据项的格式和位置。

目标

在本节结束时,

  • 我们可以将员工设为 房地产代理房地产经理

  • 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

注解

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

小技巧

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

如果您前往 设置 ‣ 管理用户 并打开 admin 用户(“Mitchell Admin”),您应该会看到一个新部分:

../../_images/groups.png

将管理员用户设置为 房地产经理

Exercise

通过 Web 界面,创建一个仅具有“房地产代理”权限的新用户。该用户不应拥有任何发票或管理权限。

使用隐私标签页或窗口以新用户身份登录(记得设置密码)。作为房地产代理,您应该只能看到房地产应用,可能还能看到讨论(聊天)应用:

../../_images/agent.png

访问权限

参见

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

目标

在本节结束时,

  • 至少不是房地产代理的员工将无法看到房地产应用程序。

  • 房地产代理将无法更新房产类型或标签。

访问权限在 第 4 章:安全性(Security)- 简介 中首次引入。

访问权限是一种通过组为用户授予模型访问权限的方式:将访问权限与组关联后,拥有该组的所有用户都将获得访问权限。

例如,我们不希望房地产代理能够修改可用的房产类型,因此不会将此访问权限链接到“用户”组。

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

分组

创建

读取

更新

删除

A

X

X

B

X

C

X

拥有组 A 和 C 的用户将能够执行除删除对象之外的所有操作,而拥有组 B 和 C 的用户将能够读取和更新对象,但无法创建或删除它。

注解

  • 可以省略访问权限的组,这意味着 ACL 将适用于 所有用户 ,这是一种有用但有风险的回退方式,因为根据安装的应用程序,它甚至可能授予非用户对该模型的访问权限。

  • 如果没有适用的访问权限,则用户将被拒绝访问(默认拒绝)。

  • 如果菜单项指向用户无法访问的模型,并且没有用户可见的子菜单,则该菜单将不会显示。

Exercise

更新访问权限文件为:

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

  • 仅授予代理(房地产用户)对类型和标签的读取权限。

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

  • 检查您的代理用户是否无法更改类型或标签,也无法删除房产,但可以创建或更新房产。

警告

请记住为您的 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>

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

小技巧

由于规则往往相当复杂且不是批量创建的,因此它们通常使用 XML 创建,而不是用于访问权限的 CSV。

上面的规则:

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

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

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

注解

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

Exercise

定义一条规则,限制代理只能查看或修改没有销售员的房产,或者是他们作为销售员的房产。

您可能需要创建第二个房地产代理用户,或者创建一些销售员为经理或其他用户的房产。

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

estate_group_manager 组需要隐含 estate_group_user

安全覆盖

绕过安全检查

目标

在本节结束时,代理应能够确认房产销售而无需拥有发票访问权限。

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

../../_images/error.png

这是因为在流程中 estate_account 尝试创建发票,但创建发票需要拥有对所有发票管理的权限。

我们希望代理能够在没有完整发票访问权限的情况下确认销售,这意味着我们需要 绕过 Odoo 的常规安全检查,以便在当前用户没有相应权限的情况下仍然能够创建发票。

在 Odoo 中绕过现有安全检查主要有两种方式,无论是有意为之还是作为副作用:

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

  • 执行原生 SQL 查询会因为绕过 ORM 本身而附带地绕过访问权限和记录规则。

Exercise

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

危险

这些功能通常应避免使用,并且仅在确认当前用户和操作确实需要绕过常规访问权限验证后,才可谨慎使用。

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

以编程方式检查安全性

目标

在本节结束时,无论对 estate 进行何种更改,发票的创建都应对安全问题具有鲁棒性。

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

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

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

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

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

危险

仅仅因为您已经处于 Python 代码中,并不意味着任何访问权限或规则已经被或将被检查。

目前 ,访问权限是通过在 self 上访问数据以及调用 super()``(它执行相同操作并 *更新* ``self)隐式检查的,这会触发访问错误并取消事务,从而“撤销”我们的发票创建。

然而,如果未来这种情况发生变化,或者我们为方法添加了副作用(例如向政府机构报告销售情况),或者在 estate 中引入了错误……非代理可能能够触发他们不应访问的操作。

因此,在执行非 CRUD 操作、合法绕过 ORM 或安全性时,或触发其他副作用时,进行 显式安全检查 是非常重要的。

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

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

  • 检查当前用户是否具有硬编码的特定组以允许或拒绝操作(self.env.user.has_group)。

  • 在记录集上调用 check_access(operations) ,这将验证当前用户是否被允许对集合中的 每个 记录执行操作。作为一种特殊情况,当记录集为空时,它会验证当前用户是否对该模型具有某些访问权限以执行操作。

Exercise

在创建发票之前,使用 check_access 确保当前用户可以更新发票的属性。

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

多公司安全

参见

多公司指南 提供了多公司功能的概述,而 多公司安全规则 则特别针对相关内容进行了说明。

关于规则的一般文档可以再次在 记录规则 中找到。

目标

在本节结束时,代理应只能访问其所属机构(或多个机构)的房产。

出于某种原因,我们可能需要将房地产业务作为多家公司来管理,例如,我们可能拥有高度自主的机构、特许经营模式或多个品牌(可能是通过收购其他房地产企业而来的),这些公司在法律或财务上彼此独立。

Odoo 可以用于在同一个系统中管理多家公司,但实际处理取决于各个模块:Odoo 本身提供了管理公司依赖字段和 多公司规则 的工具,这正是我们将要关注的内容。

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

如前所述,由于这是基于非平凡记录的,用户放松规则比收紧规则更容易,因此默认采用相对更强的安全模型是有意义的。

多公司规则只是基于 company_idscompany_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>

危险

多公司规则通常是 全局的 ,否则存在高风险,即额外规则可能会允许绕过多公司规则。

Exercise

  • estate.property 添加一个 company_id 字段,该字段应为必填项(我们不希望有无归属机构的房产),并默认设置为当前用户的当前公司。

  • 创建一家新公司,并在该公司中添加一名新的房地产代理。

  • 经理应同时属于两家公司。

  • 旧代理应仅属于旧公司。

  • 在每家公司中创建一些房产(可以使用公司选择器作为经理,或使用代理)。取消设置默认销售员以避免触发 规则。

  • 所有代理都能看到所有公司,这是不可取的,请添加记录规则限制此行为。

警告

当您更改模块的模型或数据时,请记得使用 --update 更新您的模块

可见性 ≠ 安全性

目标

在本节结束时,房地产代理应看不到房地产应用程序的设置菜单,但仍应能够设置房产类型或标签。

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

  • 可见性 功能意味着用户仍然可以通过界面的另一部分或通过 使用 RPC 远程执行操作 来访问模型或记录,只是在某些情况下它们可能不会显示在 Web 界面中。

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

以下是一些示例:

Exercise

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

设置菜单只会给他们的界面增加噪音,请将其仅对经理可见。

尽管代理不再有权访问房产类型和房产标签菜单,但他们仍然可以访问底层对象,因为他们仍然可以选择标签或类型来设置他们的房产。

1

Odoo 应用程序是一组相关模块的集合,涵盖某个业务领域或领域,通常由一个基础模块和多个扩展模块组成,用于添加可选或特定功能,或与其他业务领域建立联系。

2

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