限制数据访问¶
重要
本教程是 服务器框架入门 教程的延伸。请确保你已完成该教程,并以你构建的 estate 模块作为本教程练习的基础。
到目前为止,我们主要关注的是实现有用的功能。然而在大多数业务场景中,安全性 很快就会成为一个问题:目前,
任何员工(
group_user所代表的就是员工)都可以创建、读取、更新或删除房产、房产类型或房产标签。如果安装了
estate_account,那么只有被允许操作开票的经纪人才能确认销售,因为这是 创建发票 所必需的。
然而:
我们不希望第三方能够直接访问房产。
并非所有员工都是房地产经纪人(例如行政人员、房产经理……),我们不希望非经纪人看到可售的房产。
房地产经纪人不需要、也无权决定哪些房产类型或标签是 可用的。
房地产经纪人可以有 独家 房产,我们不希望一个经纪人能够管理另一位经纪人的独家房产。
所有房地产经纪人都应能确认其可管理的房产的销售,但我们不希望他们能够验证系统中任何发票或将其标记为已付款。
注解
对于小型企业来说,其中部分或大部分做法其实是可以接受的。
因为让用户禁用不必要的安全规则,比从零开始创建规则要容易,所以宁可谨慎行事并限制访问:用户在必要时或方便时可以放宽这些限制。
群组¶
目标
在本节结束时,
我们可以把员工设为 房地产经纪人 或 房地产经理。
admin用户是一名房地产经理。我们新增了一名 房地产经纪人 员工,该员工没有开票或管理权限。
每次需要变更时都为员工单独挂载安全规则并不现实,因此由 用户组 把安全规则与用户关联起来。用户组对应可以分配给员工的角色。
对大多数 Odoo 应用 1 来说,一个不错的基线是设置 用户 和 经理 (或管理员)两种角色:经理可以更改应用的配置并监督其整体使用情况,而用户嘛,就是使用应用 2。
这个基线对我们来说似乎已经足够:
房地产经理可以配置系统(管理可用的类型和标签),并监督流程中的每一处房产。
房地产经纪人可以管理自己负责的房产,以及未专门指派给任何经纪人负责的房产。
与 Odoo 数据驱动的特性一致,用户组不过是 res.groups 模型的一条记录。它们通常属于模块的 主数据,定义在模块的某个数据文件中。
一个简单示例 可以在这里找到。
练习
在合适的文件夹中创建
security.xml文件,并将它添加到__manifest__.py文件中。如果还没有,请为
__manifest__.py添加'category'字段,值为Real Estate/Brokerage。添加一条记录,创建一个权限组,其 id 为
groups_privilege_real_estate,名称为“Real Estate”,类别为base.module_category_real_estate_brokerage。添加一条记录,创建一个用户组,其 id 为
estate_group_user,名称为“Agent”,权限组为groups_privilege_real_estate。在其下方,添加一条记录,创建一个用户组,其 id 为
estate_group_manager,名称为“Manager”,权限组为groups_privilege_real_estate。estate_group_manager用户组需要隐含estate_group_user。
注解
那个 类别 是从哪里来的?它是一个 模块类别。这里使用的类别 id base.module_category_real_estate_brokerage 是 Odoo 根据模块 __manifest__.py 中所设的 category 自动生成的。你也可以在这里找到 Odoo 提供的 默认模块类别 列表。
小技巧
由于我们修改了数据文件,记得重启 Odoo 并使用 -u estate 更新模块。
如果你进入 并打开 admin 用户(“Mitchell Admin”),应该会看到一个新的部分:
将 admin 用户设置为 房地产经理。
练习
通过 Web 界面创建一个仅具有“房地产经纪人”访问权限的新用户。该用户不应拥有任何开票或管理权限。
使用隐私标签页或隐私窗口以新用户登录(记得设置密码),作为房地产经纪人,你应该只能看到房地产应用,可能还有 Discuss(聊天)应用:
访问权限¶
更多内容
与本主题相关的文档可在 访问权限 中找到。
目标
在本节结束时,
不是房地产经纪人(或更高角色)的员工将看不到房地产应用。
房地产经纪人将无法更新房产类型或标签。
访问权限最早在 第 4 章:安全性 - 简要介绍 中介绍过。
访问权限是一种 通过 用户组授予用户模型访问权的方式:把访问权限关联到某个用户组,之后拥有该组的用户都会获得该访问权限。
例如,我们不希望房地产经纪人能够修改可用的房产类型,因此不会把该访问权限关联到“用户”组。
访问权限只能授予访问权:检查访问权限时,系统会查看与用户关联的 任何 访问权限(通过任何用户组)是否授予了该访问权。
group |
create |
读取 |
更新 |
delete |
A |
X |
X |
||
B |
X |
|||
C |
X |
同时属于用户组 A 和 C 的用户可以对该对象做任何操作,但不能删除它;而同时属于 B 和 C 的用户可以读取和更新该对象,但不能创建或删除它。
注解
要使某项访问权限应用于 每个用户,请使用用户组
base.group_everyone。请注意,这有一定风险:取决于所安装的应用,它甚至可能授予非用户对该模型的访问权限。如果没有访问权限适用于某个用户,则不会授予其访问权(默认拒绝)。
如果某个菜单项指向用户无权访问的模型,且该菜单项下没有用户可见的子菜单,那么该菜单不会显示。
练习
将访问权限文件更新为:
为房地产经理用户组授予所有对象的完整访问权限。
只给经纪人(房地产用户)授予类型和标签的读取权限。
不授予任何人删除房产的权限。
检查你的经纪人用户无法更改类型或标签、也无法删除房产,但在其他方面可以创建或更新房产。
警告
记得为你的 ir.access 记录设置不同的 xid,否则它们会互相覆盖。
由于“demo”用户没有被设为房地产经纪人或经理,他们甚至不应该能看到房地产应用。使用隐私标签页或隐私窗口来检查这一点(“demo”用户的密码是“demo”)。
细粒度访问权限¶
目标
在本节结束时,经纪人将无法看到同事的独家房产;但经理仍然可以看到所有内容。
访问权限可以授予整个模型的访问权,但通常我们需要更精细的控制:经纪人总体上可以操作房产,但我们可能不希望他们更新、甚至看到同事管理的房产。
访问权限的 域 提供了这种精确性:它们可以针对单条记录授予或拒绝访问:
id,name,model_id,group_id/id,operation,domain
access_read,access_read,test.model,base.group_user,r,
access_other,access_other,test.model,base.group_user,cud,"[('user_id', 'in', [user.id, False])]"
访问权限的管理基于 域:如果记录通过检查,则授予访问权,否则拒绝访问。
上面的访问权限 access_other:
仅适用于“创建”、“更新”(write)和“删除”(unlink)操作:这里我们希望每位员工都能看到其他用户的记录(见上文的
access_read),但只有作者/负责人可以更新记录。是 非全局 规则,因此我们可以为经理等提供额外的规则。
如果当前用户(
user.id)已设置在记录上(例如作为创建者或被分配者),或者记录完全没有关联用户,则允许该操作。
注解
如果某条访问权限在定义时没有域,且适用于某个模型和操作,则该模型的所有记录都允许该操作(默认允许)。如果访问权限设置不正确(过于宽松),这可能会产生奇怪的效果。
练习
定义或修改一条访问权限,使经纪人只能查看或修改没有销售员的房产,或者自己就是该房产销售员的房产。
你可能想创建第二个房地产经纪人用户,或者创建几处销售员为经理或其他用户的房产。
验证你的房地产经理是否仍能看到所有房产。如果看不到,为什么?记住:
estate_group_manager用户组需要隐含estate_group_user。
安全绕过¶
绕过安全性¶
目标
本节结束时,经纪人应能在无需开票权限的情况下确认房产销售。
如果你以房地产经纪人的身份尝试将房产标记为“已售出”,应当会收到访问错误:
出现这种情况是因为 estate_account 在该过程中尝试创建发票,而创建发票需要拥有发票管理的全部权限。
我们希望经纪人能够在没有完整开票权限的情况下确认销售,这意味着我们需要 绕过 Odoo 的常规安全检查,以便在 尽管 当前用户没有相应权限的情况下创建发票。
在 Odoo 中绕过现有安全检查主要有两种方式:主动为之,或作为副作用产生:
sudo()方法会创建一个处于“sudo 模式”的新记录集,这会忽略所有访问权限和记录规则(不过硬编码的用户组和用户检查可能仍然生效)。执行原生 SQL 查询会绕过访问权限和记录规则,这是绕过 ORM 本身所产生的副作用。
练习
更新 estate_account,使其在创建发票时绕过访问权限和规则。
危险
通常应避免使用这些特性,只有在确认当前用户和当前操作确实应当绕过常规访问权限校验之后,才可极其谨慎地使用。
在这种模式下执行的操作还应尽可能少地依赖用户输入,并尽最大可能对其进行校验。
以编程方式检查安全性¶
目标
本节结束时,无论 estate 如何变更,发票的创建都应能抵御安全问题。
在 Odoo 中,仅在 通过 ORM 执行数据访问 时才会检查访问权限,例如通过 ORM 方法创建、读取、搜索、写入或删除记录。其他方法 并不 一定检查任何形式的访问权限。
在上一节中,我们在 action_sold 中创建发票时绕过了记录规则。任何用户都可以触发这一绕过,而且不会进行任何访问权限检查:
在
estate_account的action_sold中,在创建发票之前添加一条 print 输出(因为创建发票会访问该房产,从而触发 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),这会验证当前用户是否有权对该集合中的 每一条 记录执行该操作。特殊情况下,当记录集为空时,它会验证当前用户对该模型整体上是否具有执行该操作的某些访问权限。
练习
在创建发票之前,使用 check_access 确保当前用户能够更新该发票所针对的房产。
重新运行绕过脚本,检查错误是否在 print 输出之前出现。
多公司安全性¶
目标
本节结束时,经纪人应当只能访问其所属机构(或各机构)的房产。
出于这样或那样的原因,我们可能需要以多家公司的形式管理房地产业务,例如我们可能拥有基本自治的多个机构、特许经营模式,或多个品牌(可能来自收购的其他房地产企业),它们在法律或财务上彼此独立。
Odoo 可以在同一系统内管理多家公司,但具体如何处理取决于各个模块:Odoo 本身提供了管理公司相关字段和 多公司规则 的工具,而后者正是我们将要关注的。
我们希望不同机构之间彼此“隔离”,房产归属于特定机构,而用户(无论是经纪人还是经理)只能看到与其机构关联的房产。
和之前一样,由于这基于较为复杂的记录,用户放宽规则比收紧规则更容易,因此默认采用相对更严格的安全模型是合理的。
多公司规则就是基于 company_ids 或 company_id 字段的访问权限:
company_ids是当前用户有权访问的所有公司company_id是当前活动的公司(即用户当前正在其中或为其工作的公司)。
多公司规则 通常 会使用前者,即检查记录是否与用户有权访问的 其中一家 公司关联:
id,name,model_id,group_id/id,operation,domain
hr_appraisal_plan_comp_rule,Appraisal Plan multi-company,hr.appraisal.plan,,crud,"[
'|', ('company_id', '=', False),
('company_id', 'in', company_ids)
]"
危险
多公司规则通常是 全局 规则,否则其他规则很可能会被用来绕过这些多公司规则。
练习
为
estate.property添加company_id字段,该字段应为必填(我们不希望出现没有归属机构的房产),且应默认取当前用户当前所在的公司。创建一家新公司,并在该公司中新建一名房地产经纪人。
经理应同时属于这两家公司。
原经纪人应只属于原公司。
在每家公司中创建一些房产(可以以经理身份使用公司选择器,或使用经纪人账号)。取消默认销售员,以避免触发 那条 规则。
所有经纪人都能看到所有公司,这并不可取,请添加限制该行为的记录规则。
警告
更改模块的模型或数据后,记得 --update 你的模块
可见性 != 安全性¶
目标
本节结束时,房地产经纪人不应看到房地产应用的设置菜单,但仍应能够设置房产类型或标签。
特定的 Odoo 模型可以直接与用户组(或公司、或用户)关联。在使用这种关联之前,重要的是要弄清它属于 安全 特性还是 可见性 特性:
可见性 特性意味着用户仍可通过其他方式访问该模型或记录,例如通过界面的其他部分,或通过 使用 RPC 远程执行操作,只是在某些情况下可能不会在 Web 界面中显示。
安全 特性意味着用户无法访问记录、字段或操作。
示例如下:
(Python 中) 模型字段 上的用户组属于安全特性:不在该组中的用户将无法获取该字段,甚至不知道它的存在。
例如:在服务器动作中,只有系统用户才能查看或更新 Python 代码。
视图元素 (XML 中)上的用户组属于可见性特性:不在该组中的用户将无法在表单中看到该元素及其内容,但仍可以通过其他方式与该对象(包括该字段)交互。
菜单和动作上的用户组属于可见性特性:菜单或动作不会显示在界面中,但这并不能阻止直接与底层对象交互。
练习
房地产经纪人无法添加房产类型或标签,但在创建房产时,可以在房产表单视图中看到这些选项。
设置菜单只会给他们的界面增添干扰,请将其设为仅对经理可见。
尽管经纪人已无法访问房产类型和房产标签菜单,但他们仍然可以访问底层对象,因为他们仍可选择要设置到其房产上的标签或类型。