即使一个人,也要活得像军队一样!

Odoo13-- Odoo权限机制

Odoo权限机制

除手动通过自定义模式管理权限外,Odoo还提供了两种数据驱动的机制来管理或限制对数据的访问。

这两种机制都通过组来与具体用户相关联:一个用户可属于多个组,安全机制与组进行关联,进而应用对用户应用安全机制。

1
2
3
用户---拥有----> 1个或多个组 ----拥有---->多个权限

通过组,用户既可以拥有权限

Odoo的权限控制方式有以下:
一是访问权限(模型ir.model.access)即表级权限
二是记录规则(模型ir.rule)即行级权限
三是字段级权限,一个对象或表上的某些字段的访问权限。(通过group实现)
四是视图界面级权限,不属于指定视图元素所包含组的用户看不到。(通过group实现)

群组定义

组的模型为res.groups,只需要在xml文件中定义就行。

如下示例,定义一个销售管理员组

1
2
3
4
5
6
7
<record id="group_sale_manager" model="res.groups">
<field name="name">Administrator</field>
<field name="comment">the user will have an access to the sales configuration as well as statistic reports.</field>
<field name="category_id" ref="base.module_category_sales_sales"/>
<field name="implied_ids" eval="[(4, ref('group_sale_salesman_all_leads'))]"/>
<field name="users" eval="[(4, ref('base.user_root')), (4, ref('base.user_admin'))]"/>
</record>

其中:
name:这是组的显示名称。
category_id:这是对应用分类的引用,用于在用户表单中组织分组。
implied_ids:它们是所要继承权限的其它组。
users:这是属于该组的用户。在新的插件模块中,我们通常希望admin用户属于应用的管理员组。

再做一些解释:
category_id的作用就是分类。比如销售应用的组,采购应用的组,技术设置类的组。模型是ir.module.category,odoo翻译为应用。也是在xml中定义。

1
2
3
4
<record model="ir.module.category" id="module_category_sales">
<field name="name">Sales</field>
<field name="sequence">6</field>
</record>

implied_ids很多人翻译为继承的,可能有些不太好理解,实际上这是一个自关联字段。举个例子:封建社会中有 金钱/权力/女人/马车/豪宅/奴隶。 若是个奴隶,就只有苟活的权利,只能眼巴巴的望着这纸醉金迷的色会。若是个大官,金钱/权力/女人…都会主动靠近,自己拥有。 说白了,implied_ids相当于附加的。有老婆就会有老婆饼,你只有老婆饼就只能很遗憾的想着小金莲。

1
2
3
4
5
6
组   implied_ids   组
A B、C、D
B C、D

用户属于A组,就自动拥有B、C、D组的权限
用户属于B组,就自动拥有C、D组的权限

访问权限

模型权限访问管理:模型级的权限控制,该模型的所有记录,对于群组内用户(如无定义,则对所有用户)的读写改删权限控制。
access rule是通过security文件夹下的ir.model.access.csv文件来控制的:
这个文件第一行指明了需要控制的内容:

1
2
3
4
5
6
7
8
id,name,model_id,group_id,perm_read,perm_write,perm_create,perm_unlink

分别对应:
id:记录的外部标识符 (也称为 XML ID)。在我们的模块中它应该是唯一。
name:描述标题。官方模块通常使用模型名称和组的圆点分隔的字符串。如: todo.task.user
model_id :模型的外部标识符。todo.task对应该标识符是model_todo_task
group_id:权限组,在第一步中通过groups.xml。最重要的一点是供定义它的模块名前缀。比如员工组,它的标识符为base.group_user。
perm_XX:字段标记授予 读, 写, 创建,删除 权限。

举例

1
2
3
4
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_crm_team,crm.team,model_crm_team,base.group_user,1,0,0,0
access_crm_team_user,crm.team.user,model_crm_team,sales_team.group_sale_salesman,1,0,0,0
access_crm_team_manager,crm.team.manager,model_crm_team,sales_team.group_sale_manager,1,1,1,1

记录规则

记录规则也是通过xml文件定义,定义方式如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="模块名_对象_rule" model="ir.rule"> //为某对象添加记录规则
<field name="name">规则名</field>
<field name="model_id" ref="model_对象"/>
<field name="domain_force">[('字段名','操作符',值)]</field> //记录过滤表达式:符合该表达式的记录才能被访问
<field name="groups" eval="[(值,ref('访问组id'))]"/> //访问组id在第一步创建时指定,用于指定本规则作用于哪些组
<field name="perm_read" eval="0/1"/>
<field name="perm_write" eval="0/1"/>
<field name="perm_create" eval="0/1"/>
<field name="perm_unlink" eval="0/1" />
</record>
</odoo>

举例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

//只看自己的
<record id="sale_order_personal_rule" model="ir.rule">
<field name="name">Personal Orders</field>
<field ref="model_sale_order" name="model_id"/>
<field name="domain_force">['|',('user_id','=',user.id),('user_id','=',False)]</field>
<field name="groups" eval="[(4, ref('sales_team.group_sale_salesman'))]"/>
</record>

//看所有的
<record id="sale_order_see_all" model="ir.rule">
<field name="name">All Orders</field>
<field ref="model_sale_order" name="model_id"/>
<field name="domain_force">[(1,'=',1)]</field>
<field name="groups" eval="[(4, ref('sales_team.group_sale_salesman_all_leads'))]"/>
</record>

在记录规则未分配给任何安全组时,它被标记为全局并由不同的规则来进行处理。
规则适用的记录行,用domain来定义,符合domain规则的可访问,不符合的则不能访问。这里的domain有两个上下文可以使用:user是当前用户,time是时间。
全局规则(规则中未指定组)和组规则(规则中指定了组)的使用方式截然不同:
全局规则是减法的,必须全部匹配才能访问记录
组规则是加法的,其中任何一个匹配(并且全部的全局规则都匹配)就能访问记录
这意味着第一个组规则限制记录的访问,但是其它的所有组规则都在扩展访问。而全局规则是每一个规则都在限制记录的访问(或者不起作用)。

字段级权限

在某些情况下,我们可能会需要更精准控制的访问控制,可能会需要限制对模型具体字段的访问。
在定义字段时候,可以使用groups属性来让字段仅能由指定的安全组所访问。

1
phone = fields.Char(string="Private Phone", groups="hr.group_hr_user")

带有groups属性的字段进行了特殊处理,它检查用户是否属于属性中所指定的任意安全组。如果用户不属于指定的某一组,Odoo会在用户界面中删除该字段并限制对该字段的ORM操作。
注意这并不仅仅是表面工作。该字段不仅在用户界面中会隐藏,同时在用户的其它ORM操作中也无法使用它,比如读和写。对于XML-RPC或JSON-RPC调用同样如此。
在业务逻辑或用户界面on-change事件(@api.onchange方法)中使用这些字段时需要小心,在用户没有该字段访问权限时会抛出错误。一种避免的方式是使用提权,比如sudo()模型方法或计算字段的compute_sudo字段属性。

视图界面级权限

就是给视图界面元素定义时,添加groups属性,同时还有div标签、button标签同样适用

1
2
3
4
5
6
7
8
9
10
11
12
13
<menuitem
id="menu_human_resources_configuration"
name="Configuration"
parent="menu_hr_root"
groups="group_hr_manager"
sequence="100"/>


<div class="app_settings_block" data-string="Employees" string="Employees" data-key="hr" groups="hr.group_hr_manager">
...
</div>

<button name="set_recruit" string="Start Recruitment" states="open" type="object" class="oe_highlight" groups="base.group_user"/>

启用禁用功能

如Odoo中的启用多组织、多计量,就是利用安全组的策略实现的。

第一步,定义一个安全组

1
2
3
4
5
6
7
8
9
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data noupdate="0">
<record id="group_uom" model="res.groups">
<field name="name">Manage Multiple Units of Measure</field>
<field name="category_id" ref="base.module_category_hidden"/>
</record>
</data>
</odoo>

第二步,继承res.config.settings模型,增加字段,并在xml中添加字段用以展示

1
2
3
4
class ResConfigSettings(models.TransientModel):
_inherit = 'res.config.settings'

group_uom = fields.Boolean("Units of Measure", group='base.group_user', implied_group='uom.group_uom')

注意: 字段必须以group_开头, implied_group属性就是我们第一步创建的组。group属性可以不写,默认为base.group_user

第三步, 给我们需要启用的功能添加组

1
<field name="product_uom" groups="uom.group_uom"/>

第四步,在设置界面对字段group_uom进行勾选保存,就可以看到product_uom字段已经启用。

原理很简单,你只要记得我上面说的老婆饼和小金莲就懂了,源码位于\base\models\res_config.py 567行。
就是让implied_group成为group的附加的就可以。

1
2
3
4
5
6
7
8
9
10
11
12
13
# group fields: modify group / implied groups
current_settings = self.default_get(list(self.fields_get()))
with self.env.norecompute():
for name, groups, implied_group in sorted(classified['group'], key=lambda k: self[k[0]]):
groups = groups.sudo()
implied_group = implied_group.sudo()
if self[name] == current_settings[name]:
continue
if int(self[name]):
groups.write({'implied_ids': [(4, implied_group.id)]})
else:
groups.write({'implied_ids': [(3, implied_group.id)]})
implied_group.sudo().write({'users': [(5,)]})

权限控制代码层面原理

原理就是在加载视图时会进行权限校验,受限制的字段将在fields_get()请求中被移除。
具体代码:models.py中check_access_rightscheck_access_rule两个校验方法
此外:使用 user_has_groups方法可以判断当前用户是否拥有组

-------------本文结束感谢您的阅读-------------