限制对数据的访问¶
重要
本教程是 服务器框架 101 教程的延伸。请确保您已完成该教程,并使用您之前构建的 estate
模块作为本教程中练习的基础。
到目前为止,我们主要关注了实现有用的功能。然而在大多数业务场景中,*安全*很快会成为一个关注点:目前,
任何员工(即
group_user
所代表的)都可以创建、读取、更新或删除房产、房产类型或房产标签。如果安装了
estate_account
模块,则只有代理人员可以与开票功能进行交互,以便确认销售,因为这是 创建发票 所必需的。
然而:
我们不希望第三方能够直接访问属性。
并非所有员工都是房地产经纪人(例如行政人员、物业经理等),我们不希望非经纪人员查看可用的房产。
房地产经纪人不需要也不参与决定哪些房产类型或标签是**可用的**。
房地产经纪人可以拥有*独家*房产,我们不希望一个经纪人能够管理另一个经纪人的独家房产。
所有房地产经纪人应该能够确认他们可以管理的房产销售,但我们不希望他们能够验证或在系统中将发票标记为已付款。
注解
我们可能对于小型企业来说,这些内容中的某些或大部分都是可以接受的。
因为对于用户来说,禁用不必要的安全规则比从零开始创建它们更容易,因此最好采取谨慎的态度,限制访问权限:如果有必要或方便,用户可以放松这些访问限制。
组¶
目标
本节结尾处,
我们可以将员工设置为 房地产经纪人 或 房地产经理。
“admin” 用户是房地产管理员。
我们有一位新的 房地产经纪人 员工,没有发票或管理的访问权限。
这在需要更改时,每次为员工单独添加安全规则是不现实的,因此使用 组 来将安全规则与用户关联起来。它们对应于可以分配给员工的角色。
对于大多数 Odoo 应用 1 来说,一个良好的基础是设置 用户 和 *经理*(或管理员)角色:经理可以更改应用的配置并监督其全部使用情况,而用户则可以正常使用该应用 2。
这个基准对我们来说似乎已经足够:
房地产经理可以配置系统(管理可用类型和标签),并监督管道中的每处房产。
房地产经纪人可以管理他们负责的房产,或者未明确由任何经纪人负责的房产。
按照 Odoo 的数据驱动特性,用户组只不过是 res.groups
模型的一条记录。它们通常属于模块的 主数据,定义在模块的一个数据文件中。
如简单示例可在此处找到 <https://github.com/odoo/odoo/blob/532c083cbbe0ee6e7a940e2bdc9c677bd56b62fa/addons/hr/security/hr_security.xml#L9-L14>。
Exercise
在适当的文件夹中创建
security.xml
文件,并将其添加到__manifest__.py
文件中。如果没有的话,请将一个
'category'
字段添加到您的__manifest__.py
中,值设为Real Estate/Brokerage
。添加一条记录,创建一个 ID 为
estate_group_user
、名称为“代理人”且类别为base.module_category_real_estate_brokerage
的用户组。在下方,添加一条记录,创建一个 ID 为
estate_group_manager
、名称为“管理员”、类别为base.module_category_real_estate_brokerage
的用户组。该estate_group_manager
用户组需要包含estate_group_user
。
注解
那里这个 类别 是从哪里来的?它是一个 模块类别。在这里我们使用了类别 ID base.module_category_real_estate_brokerage
,该 ID 是由 Odoo 根据模块的 __manifest__.py
中设置的 category
自动生成的。您也可以在这里找到 Odoo 提供的 默认模块类别列表。
小技巧
由于我们修改了数据文件,请记得重启 Odoo 并使用 -u estate
更新模块。
如果你进入 admin
用户(”Mitchell Admin”),你应该会看到一个新部分:

将管理员用户设置为 房地产经理。
Exercise
通过网页界面,创建一个仅具有“房地产经纪人”访问权限的新用户。该用户不应具有任何开票或管理访问权限。
使用私有标签或窗口以新用户身份登录(记得设置密码),作为房地产经纪人,您只能看到房地产应用,以及可能的讨论(聊天)应用:

访问权限¶
另请参见
与该主题相关的文档可以在 访问权限 中找到。
目标
本节结尾处,
不是房地产代理的员工将看不到房地产应用。
房地产经纪人将无法更新房产类型或标签。
访问权限最初在 Chapter 4: Security - A Brief Introduction 中引入。
访问权限是一种通过用户组来赋予用户对模型的访问方式:将访问权限关联到一个用户组,然后该用户组中的所有用户都将拥有该访问权限。
例如,我们不希望房地产经纪人能够修改可用的房产类型,因此我们不会将该访问权限关联到“用户”组。
访问权限只能授予访问,不能撤销:当检查访问权限时,系统会查看用户(通过任何用户组关联的)任何访问权限是否授予该访问。
用户组 |
创建 |
读取 |
更新 |
删除 |
一个 |
X |
X |
||
B |
X |
|||
C |
X |
具有组 A 和 C 的用户可以执行任何操作,但不能删除对象;而具有组 B 和 C 的用户可以读取和更新对象,但不能创建或删除它。
注解
该访问权限的用户组可以省略,这意味着 ACL 将适用于 所有用户,这是一种有用但具有风险的默认选项,因为根据安装的应用程序不同,它可能会让非用户也能访问该模型。
如果没有任何访问权限适用于用户,他们将不被授予访问权限(默认拒绝)。
如果一个菜单项指向的模型用户没有访问权限,并且也没有用户可见的子菜单,该菜单将不会被显示。
Exercise
更新访问权限文件为:
将所有对象的完整访问权限授予您的房地产经理用户组。
让代理(房地产用户)仅对类型和标签拥有只读访问权限。
不要给予任何人删除属性的权限。
检查您的代理用户是否无法修改类型或标签,也无法删除属性,但可以正常创建或更新属性。
警告
请记住,为您的 ir.model.access
记录分配不同的 xid,否则它们会互相覆盖。
由于 “demo” 用户未被设置为房地产经纪人或经理,他们甚至不应该能够看到房地产应用。请使用私有标签或窗口进行检查(”demo” 用户的密码是 “demo”)。
记录规则¶
另请参见
与该主题相关的文档可以在 参考/security/rules 中找到。
目标
在本节结束时,代理将无法看到仅属于其同事的属性;但经理仍然可以查看所有内容。
访问权限可以授予对整个模型的访问权限,但通常我们需要更具体一些:虽然代理可以与一般属性进行交互,但我们可能不希望他们更新甚至查看由其同事管理的属性。
记录 规则 提供了这种精确性:它们可以授予或拒绝对单个记录的访问权限:
<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
用户组。
安全覆盖¶
绕过安全措施¶
目标
在本节结束时,代理应能够确认房产销售,而无需发票访问权限。
如果你作为房地产经纪人尝试将一个房产标记为“已售”,你应该会收到一个访问错误:

这是因为 estate_account
模块在过程中尝试创建一张发票,但创建发票需要拥有所有发票管理的权限。
我们希望代理能够在不拥有完整开票权限的情况下确认销售,这意味着我们需要*绕过* Odoo 的正常安全检查,以便*即使*当前用户没有相应权限,也能创建发票。
有两种主要方式可以绕过 Odoo 中现有的安全检查,无论是有意为之还是作为副作用:
sudo()
方法将以 “超级用户模式” 创建一个新的记录集,这将忽略所有访问权限和记录规则(尽管硬编码的用户组和用户检查可能仍然适用)。执行原始 SQL 查询会绕过访问权限和记录规则,这是由于绕过了 ORM 本身所导致的副作用。
Exercise
将 estate_account
更新为在创建发票时绕过访问权限和规则。
危险
这些功能通常应避免使用,只有在确认当前用户和操作有权绕过正常的访问权限验证后,才应谨慎使用。
在这些模式下执行的操作也应尽可能减少对用户输入的依赖,并尽可能全面地进行验证。
通过程序检查安全权限¶
目标
在本节结束时,无论对 estate
进行何种更改,发票的创建都应具备抵御安全问题的能力。
在 Odoo 中,访问权限和记录规则仅在通过 ORM 执行数据访问时进行检查,例如通过 ORM 方法创建、读取、搜索、写入或删除一条记录。其他方法**不一定**会检查任何类型的访问权限。
在上一节中,我们在创建发票时绕过了记录规则,即在 action_sold
方法中。这种绕过方式可以被任何用户使用,而无需检查任何访问权限:
在
estate_account
模块中的action_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_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>
危险
多公司规则通常是 全局 的,否则可能会有很高的风险,导致额外的规则绕过多公司规则。
Exercise
在
estate.property
中添加一个company_id
字段,该字段应为必填项(我们不希望有无代理的房产),并且默认值应设置为当前用户的当前公司。创建一个新公司,并在该公司中添加一名新的房产经纪人。
经理应该是两家公司的成员。
旧代理只能是旧公司的成员。
在每家公司中创建一些属性(可以使用公司选择器作为管理者,或者使用代理)。取消默认销售员以避免触发*该*规则。
所有代理都可以查看所有公司,这并不理想,添加一条限制此行为的记录规则。
警告
记得在更改模块的模型或数据时使用 --update
更新你的模块
可见性 ≠ 安全¶
目标
在本节结束时,房地产经纪人不应看到房地产应用的”设置”菜单,但仍应能够设置房产类型或标签。
特定的 Odoo 模型可以直接与组(或公司,或用户)相关联。在使用之前,需要弄清楚这种关联是*安全*功能还是*可见性*功能:
可见性 功能意味着用户仍然可以访问模型或记录,无论通过界面的其他部分,还是通过 使用 RPC 远程执行操作,在某些上下文中,这些内容可能不会在网页界面上显示。
安全 功能意味着用户无法访问记录、字段或操作。
以下是一些示例:
用户组对 *模型字段*(在 Python 中)是一种安全功能,组外的用户将无法检索该字段,甚至无法知道它的存在。
示例:在服务器操作中,只有系统用户可以看到或更新 Python 代码。
用户组对 *视图元素*(在 XML 中)是一种可见性功能,组外的用户将无法在表单中看到该元素及其内容,但他们仍然可以与其他对象进行交互(包括该字段)。
菜单和动作上的组是可见性功能,菜单或动作在界面上不会显示,但这并不妨碍直接与底层对象进行交互。
Exercise
房地产经纪人不能添加房产类型或标签,但在创建房产时可以从房产表单视图中查看其选项。
设置菜单只是对他们界面的一种干扰,仅对管理人员可见。
尽管代理不再能够访问“房产类型”和“房产标签”菜单,但他们仍然可以访问底层对象,因为他们仍然可以选择标签或类型来设置在自己的房产上。