限制数据访问¶
Important
本教程是 入门 教程的扩展。请确保您已完成该教程,并以您构建的 estate 模块为基础完成本教程的练习。如果想从干净的起点开始,请从 technical-training-solutions 仓库拉取 16.0-core 分支。
到目前为止,我们主要关注实现有用的功能。然而在大多数业务场景中,安全 很快就会成为一个问题:目前,
任何员工(即
group_user所代表的角色)都可以创建、读取、更新或删除房产、房产类型或房产标签。如果安装了
estate_account,那么只有被允许参与开票的经纪人才能确认销售,因为这是 创建发票 所必需的。
然而:
我们不希望第三方能够直接访问房产。
并非所有员工都是房地产经纪人(例如行政人员、物业经理等),我们不希望非经纪人看到可用的房产。
房地产经纪人不需要、也不能决定哪些房产类型或标签是 可用的。
房地产经纪人可能拥有 独享 房产,我们不希望某个经纪人能够管理其他经纪人的独享房产。
所有房地产经纪人都应该能够确认其可管理的房产的销售,但我们不希望他们能够验证系统中的任何发票或将其标记为已支付。
Note
对于小型企业来说,上述某些或大部分情况我们或许可以接受。
由于对用户来说,禁用不必要的安全规则比从零开始创建它们更容易,因此宁可谨慎行事、限制访问:用户可以在必要时或方便时放宽这些访问限制。
群组¶
目标
在本节结束时,
我们可以将员工设为 房地产经纪人 或 房地产经理。
admin用户是一名房地产经理。我们有一名新的 房地产经纪人 员工,他没有发票或管理方面的访问权限。
每当需要变更时,为员工逐个附加安全规则并不现实,因此用 群组 将安全规则和用户关联起来。它们对应可以分配给员工的角色。
对于大多数 Odoo 应用 1 来说,一个良好的基线是设置 用户 和 经理 (或管理员)角色:经理可以更改应用的配置并监督其整体使用情况,而用户嘛,就使用该应用 2。
这个基线对我们来说似乎已经足够了:
房地产经理可以配置系统(管理可用的类型和标签),并监督管道中的所有房产。
房地产经纪人可以管理由其负责的房产,或未指定给任何经纪人负责的房产。
秉承 Odoo 数据驱动的特性,群组不过是 res.groups 模型的一条记录。它们通常是模块 主数据 的一部分,在模块的某个数据文件中定义。
可以在 这里 找到一个简单的示例。
Exercise
在适当的文件夹中创建
security.xml文件,并将其添加到__manifest__.py文件中。如果还没有添加,请在
__manifest__.py中添加一个值为Real Estate/Brokerage的'category'字段。添加一条记录,创建 id 为
estate_group_user、名称为“Agent”、类别为base.module_category_real_estate_brokerage的群组。在其下方添加一条记录,创建 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 更新模块。
如果您前往 并打开 admin 用户(“Mitchell Admin”),您应该会看到一个新的部分:
将 admin 用户设为 房地产经理。
Exercise
通过网页界面创建一个只具有“房地产经纪人”权限的新用户。该用户不应具有任何发票或管理权限。
使用隐私标签页或窗口以新用户登录(记得设置密码),作为房地产经纪人,您应该只能看到房地产应用,可能还有 Discuss(聊天)应用:
访问权限¶
更多内容
与此主题相关的文档可以在 访问权限 找到。
目标
在本节结束时,
不是房地产经纪人的员工将看不到房地产应用。
房地产经纪人将无法更新房产类型或标签。
访问权限最初在 第 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。
安全覆盖¶
绕过安全机制¶
目标
在本节结束时,经纪人应该能够在不需要发票访问权限的情况下确认房产销售。
如果您以房地产经纪人身份尝试将房产标记为“已售出”,您应该会收到一个访问错误:
这是因为 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 远程执行操作,只是在某些上下文中这些内容可能不会显示在网页界面上。
安全 特性意味着用户无法访问记录、字段或操作。
以下是一些示例:
模型字段 上的群组(在 Python 中)是安全特性,群组之外的用户将无法获取该字段,甚至不知道它存在。
示例:在服务器动作中,仅系统用户可以查看或更新 Python 代码。
视图元素 上的群组(在 XML 中)是可见性特性,群组之外的用户将无法在表单中看到该元素或其内容,但他们仍可以通过其他方式与对象(包括该字段)交互。
菜单和动作上的群组是可见性特性,菜单或动作不会显示在界面中,但这并不妨碍直接与底层对象交互。
Exercise
房产中介人员无法添加房产类型或标签,但在创建房产时可以从房产表单视图中看到它们的选项。
设置菜单只会给他们的界面增添干扰,请将其设置为仅对经理可见。
尽管不再能访问房产类型和房产标签菜单,中介人员仍然可以访问底层对象,因为他们仍能为自己的房产选择标签或类型。