管理员向导
华炎魔方提供灵活的多维度数据权限架构,这允许管理员控制用户对数据的访问权限。通过仅显示与用户相关的数据,管理数据访问权限会增强安全性。使用权限集、权限集组和简档,控制用户可以访问的对象和字段。使用组织范围的共享设置、用户角色和共享规则,以指定用户可以查看并编辑的单个记录。
华炎魔方基于权限集为每一个对象设置权限,支持对象级、分部级和字段级的权限控制,还可以通过在对象上配置共享规则和限制规则,或者编写 beforeFind 触发器 来实现记录级的数据查看权限。
华炎魔方通过给每个用户配置“权限集”的方式来标识用户所属权限范围;只要基于权限集为每一个对象设置权限,就可以给每个用户分配不同的查看、创建、编辑、删除记录的权限;
简档是一种特殊的权限集,它与其他权限集一样,都是一种基于权限来对用户进行归类的标识。两者的区别在于,简档好比是每个用户的基本身份,每个用户必须且只能属于一个简档,比如一个用户可能是管理员或普通用户,但不可能同时有两种身份。而权限集是用于标识某种特殊权限的集体,能够为用户额外叠加更多的权限身份,一个用户只能属于一个简档,但是可以属于多个权限集,比如可以为某个财务人员分配一个财务管理员的简档,同时把他加到合同管理员权限集中,这样这个财务人员就额外拥有了管理合同的权限。
我们可以在“设置”应用的“公司设置→权限集/简档”界面查看目前系统中已经存在的”权限集/简档“列表,以下是可以为”权限集”配置的属性:
我们可以且必须为每个用户分配一个简档来标识其基本身份。
华炎魔方内置了以下几个标准简档,如果需要,可以另外添加自定义简档来扩展它们。
API 名称 | 名称 | 备注 |
---|---|---|
admin | 管理员 | 工作区/公司管理员 |
user | 用户 | 普通用户 |
supplier | 供应商 | 外部供应商用户 |
customer | 客户 | 外部客户用户 |
可以在“设置”应用的“公司设置→简档”界面,点击简档名称进入某个简档详细页,然后点击页面右上角的“自定义”按钮来自定义某个标准简档的属性配置。
除了可以按实际需求通过自定义按钮来修改某个标准简档的属性配置外,还可以新建一个自定义简档来扩展已有简档,比如可以新建一个财务管理员,该简档下的用户默认被分配了财务管理相关功能。
请在“设置”应用的“公司设置→用户”界面,新建或修改人员信息时,为每个人员配置“简档”属性来配置用户的简档。
在华炎魔方中可以为简档配置以下密码策略来极大提高相关简档下用户账户的安全性:
当因为密码策略用户被锁定后,一般可以等待一定时间后让账户自动解锁,也可以求助管理员来为用户解除锁定。
管理员在“设置”应用的“公司设置→用户”界面,进入要锁定或解锁的用户详情页面,点击右上角“锁定”或“解除锁定”按钮,即可变更相关用户的锁定状态。
除了密码策略外,我们还可以在华炎魔方中为简档配置以下登录策略来进一步提高相关简档下用户账户的安全性:
权限集,与简档相似,是另一种权限集合,也是授予某一类的用户对各种对象和功能的访问权限的集合。但是,每个用户可以属于多个权限集,这是与简档的主要区别。
一般来说,简档是为大类用户配置基本权限,而权限集则可为小类用户扩展更多的功能访问权限。 华炎魔方里的每个用户,都会属于1个简档,也可能同时再属于1个或多个权限集。对系统管理员而言,再用户创建之后,用户属于哪个简档一般无需调整,更多的是对用户属于哪些权限集作适时的调整。
假设您在贵组织中拥有“库存”自定义对象。许多用户需要此对象的“读取”访问权限,而少数用户则需要“编辑”访问权限。您可以创建授予“读取”访问权限的权限集,并将其分配到适用用户。然后,您可以创建另一个权限集,可授予“库存”对象的“编辑”访问权限,并将其分配到人数较少的用户组。
华炎魔方内置了以下几个标准权限集,如果需要,可以另外添加自定义权限集来扩展它们。
API 名称 | 名称 | 备注 |
---|---|---|
organization_admin | 分部管理员 | 用于管理分部下组织及用户信息。 |
workflow_admin | 流程管理员 | 用于管理审批王应用下的审批流程。 |
master_admin | 主数据管理员 | 用于管理系统基础主数据。 |
可以在“设置”应用的“公司设置→权限集”界面,点击权限集名称进入某个权限集详细页,然后点击页面右上角的“自定义”按钮来自定义某个标准权限集的属性配置。
除了可以按实际需求通过自定义按钮来修改某个标准权限集的属性配置外,还可以新建一个自定义权限集来扩展已有权限集,比如可以新建一个合同管理员,以分配合同管理的相关功能给该权限集下的用户。
与简档不一样,简档的成员是通过给相关人员设置其简档属性来配置成员的,要配置权限集的成员,可在“设置”应用的“公司设置→权限集”界面,当新建或修改权限集的时候直接设置其“成员”属性即可。
需要注意的是,一个用户可以属于多个权限集,当用户同时属于多个权限集时,最终用户的权限为其在各权限集中的权限叠加。
虽然每个用户只能属于一个简档,但是可能同时属于多个权限集,当一个用户属于多个权限集时,实际拥有的权限为各权限集权限的叠加。
权限叠加的意思是取各个权限集的最大权限,然后把它们叠加到一起,比如用户A属于1和3两个权限集,虽然他在权限集1中没有创建权限,但是在权限集3中有,取两个权限集中最大权限叠加后最终用户A是有创建权限的,相关规则请参考下图:
华炎魔方权限引擎是基于权限集来计算用户对每一个对象的相关权限的,所以我们首先需要配置的就是对象权限以便实现对象级的权限控制。
在简档和权限集里,都可以设置对象权限,还可以直接在对象设置界面直接配置该对象的对象权限。 我们推荐在”设置“应用中进入”公司设置→简档/权限集“界面,找到并进入要设置对象权限的简档或权限集记录详细界面,在”对象权限“子表中新建或编辑某条对象权限记录来配置对象权限。
以下权限功能描述将基于我们假设已经创建了一个名为”合同管理员“的权限集,并且需要为该权限集下的用户配置”合同“对象权限。
在对象权限记录的新建或编辑界面可以设置指定简档或权限集下的用户对该对象的基本操作权限。
用户创建记录时,记录所有者字段值默认为当前登录用户,即默认情况下记录创建人就是记录所有者,也就是说默认情况下,除非用户有查看或修改所有记录权限,否则他最多只能查看或编辑自己创建的记录。
在对象权限记录的新建或编辑界面可以设置指定简档或权限集下的用户对该对象的分部级操作权限。
假设在某个魔方项目中有以下几个分部,我们按南部和北部的地理位置来划分业务区域:
在该项目中需要通过权限配置来实现以下业务需求:
按以上业务需求,我们首先需要在“设置”应用中进入“公司设置→权限集”页面新建以下权限集:
接下来我们分别在上面新建的几个权限集的详细页面配置合同对象的对象权限:
按以上配置方法配置完各个权限集的对象权限后,我们就完整实现了上面提到的分部级权限相关需求。
在对象权限记录的新建或编辑界面可以设置指定简档或权限集下的用户禁止查看该对象下的某些列表视图。
我们可以在对象设置界面勾选”允许上传附件“开关来放开对象的上传附件功能,该功能放开后,用户就可以在合同记录详细界面的附件子表上上传附件了。
在较早版本的华炎魔方中,放开合同对象的附件功能后,用户是否可以在合同记录详细界面上传、修改及删除附件取决于用户是否有附件所属主表记录,也就是当前合同记录的修改权限。
现在我们增强了这部分权限功能,可以在对象权限记录的新建或编辑界面配置更多与附件功能相关的权限。
之前我们提到华炎魔方权限引擎是基于权限集来计算用户对每一个对象的相关权限的,华炎魔方权限引擎还进一步实现了不同权限集下的用户对每一个对象下的不同字段的权限计算,现在我们来看看如何为合同对象配置字段级的权限控制。
假设我们需要禁止公司的业务人员查看合同记录中与财务相关的字段,我们可以先新建一个名为”业务员“的简档,然后为该简档配置合同对象下的字段级权限。
要配置字段权限,请在”设置“应用中进入”公司设置→简档/权限集“界面,找到并进入要设置对象权限的简档或权限集记录详细界面,在”对象权限“子表中点击要设置字段权限的记录左侧序号链接来进入对象权限记录详细界面。
在对象权限记录详细界面我们可以看到底部有一个名为”字段权限“的子表,请按以下步骤来配置字段权限:
我们只要把与财务相关的字段的”允许编辑“和”允许查看“勾选框去除即可实现业务人员查看合同记录时隐藏财务相关的字段。
在华炎魔方中查询数据时,这里配置的字段权限也会被华炎魔方权限引擎识别并叠加到最终查询条件中,整个查询计算过程请参考该文档顶部提到的 权限计算 - 查询 示意图。
华炎魔方支持基于权限集来授权应用,它可以用来限制某个权限集/简档下的用户只能看到部分应用。
请在“设置”应用的“公司设置→权限集/简档”界面编辑要限制查看应用的权限集或简档,然后在”授权应用“字段中选择允许其查看的应用,这里未选择的应用其他应用即是被限制查看的应用了,如果设置授权应用为空则表示授权全部应用。
需要注意的是,应用权限也是适用于之前提到的权限叠加规则的,即如果某个用户属于多个权限集的话,他最终能看到的应用是每个权限集能看到的应用叠加在一起的。
因为权限叠加规则的存在,而”用户“简档代表的是整个系统中所有账户,所以如果要限制某个权限集/简档下的用户只能看到部分应用的话,可能需要先限制”用户“这个简档,只授权其最少的应用权限。
假设系统中有合同、财务、办公、审批等应用,对应的有以下两个权限集,同时合同管理员下的用户被分配到”用户“简档中:
如果我们需要做到让合同管理员只能看到合同应用而看不到另外两个应用,同时财务管理员能看到所有应用,我们可以按以下步骤来配置相关应用权限:
上面第三个步骤比较重要,不可以省略,因为合同管理员下的用户属于”用户“这个简档,如果不配置第三步的话,叠加权限后合同管理员是可以看到所有应用的。
如果想省略第三步,我们可以把合同管理员这个权限集的类型改为简档,然后把所有相关人员的简档也配置为”合同管理员“而不是”用户“即可。
通过前面的介绍我们了解到华炎魔方权限引擎基于权限集可以实现对象级、分部级及字段级的权限控制,但是还缺少了记录级的权限控制的描述。
与之前提到的权限控制功能不同,华炎魔方并不是基于权限集来实现记录级权限控制的,而是通过在对象上配置共享规则和限制规则来实现查看权限在特定条件下的共享和的收缩。
要配置共享规则或限制规则,请在”设置“应用中进入”对象设置→对象“界面,点击要设置相关规则的对象名称进入对象设置界面,然后在底部可以看到”限制规则“和”共享规则“子表,在这里新建相关规则即可:
{{$user.profile !='user'}}
这种公式表达式,当条件成立时该规则才生效。给共享/限制规则设置的”指定条目条件“是一个公式表达式,华炎魔方权限引擎会执行该表达式并根据执行结果来判断是否要把规则中配置的”记录过滤器“时配置的过滤条件叠加到最终查询结果中。
{{ javascript语法的表达式 }}
必须以 ‘{{
‘ 开头,以 ’}}
’ 结尾,其包裹的是一个标准的javascript
表达式。
公式表达式中支持以下变量,可以直接在表达式中引用它们:
Dictionary<any>
userSession,当前登录用户信息,详细请参考后续附录。Dictionary<any>
全局变量,目前只支持now变量。公式表达式global.now
会输出当前时间值。给共享/限制规则设置的”记录过滤器“是要共享或限制查看的数据的过滤条件,其语法规则请参考该文档底部 查询过滤条件详解 小节。
这里输入的内容也是支持公式表达式语法的,其表达式语法与上面提到的“指定条目条件”是一样的。
只要在华炎魔方中查询数据,华炎魔方权限引擎就会把满足”指定条目条件“的共享/限制规则中的”记录过滤器“里配置的过滤条件叠加到最终查询条件中,整个查询计算过程请参考该文档顶部提到的 权限计算 - 查询 示意图。
OR
的方式叠加到最终的查询过滤条件中。AND
的方式叠加到最终的查询过滤条件中。假设在某个魔方项目中有三个分部,分别表示上海总公司、南京分公司和杭州分公司,在该项目中需要通过权限配置来实现公司内部的业务管理人员可以查看本公司所有外部客户创建的合同数据。
这里说的外部客户指的是系统中简档为”customer“的用户,在不配置共享规则的情况下,我们可以按需求给”业务管理员 salesman
“这个权限集下的用户配置”合同“对象权限,勾选”查看本分部“来允许用户查看本分部的所有合同数据。
细想我们会发现需求中提的要求是业务管理员只能看到其本人创建以及外部客户创建的合同数据,并不允许其查看其他内部人员创建的合同数据。
要实现该需求,我们可以通过给合同对象配置限制规则来缩小业务管理员的查看数据权限,也可以配置共享规则来放大业务管理员的查看数据权限,下面我们详细讲解下这两种方式的配置方法:
要通过缩小业务管理员的查看合同数据权限这种方式来实现需求的话,我们在给”业务管理员“这个权限集下配置的”合同“对象权限中,需要勾选”查看本分部“属性来允许用户查看本分部的所有合同数据,这样的话业务管理员比需求要求的权限就偏大了,我们可以给合同对象配置一个限制规则来缩小其权限:
{{$user.roles.indexOf("salesman") > -1}}
{{[["profile__c", "=", "customer"], "or", ["owner", "=", $user.userId]]}}
["company_id", "=", $user.company_id]
是因为对象权限中勾选了”查看本分部“属性会自动带上。要通过放大业务管理员的查看合同数据权限这种方式来实现需求的话,我们在给”业务管理员“这个权限集下配置的”合同“对象权限中,不能勾选”查看本分部“属性以确保业务管理员只有查看自己创建的合同数据这种最基本的数据查看权限,这样的话业务管理员比需求要求的权限就偏小了,我们可以给合同对象配置一个共享规则来放大其权限:
{{$user.roles.indexOf("salesman") > -1}}
{{[["company_id", "=", $user.company_id],["profile__c", "=", "customer"]]}}
我们看到上面无论是配置限制规则还是共享规则的方式,在过滤条件中都用到了一个名为profile__c
的字段,它是用于标识合同记录创建人的简档的自定义字段,当其简档值为customer
时就表示该合同记录是外部客户创建的。
我们需要为合同对象创建一个名为简档的相关表字段来保存合同记录创建人的简档值:
profile__c
{{global.user.profile}}
这里默认值是一个表单公式,详情请参考文档 表单公式。
我们可以通过给部门对象和人员对象配置共享/限制规则,来改变这两个对象默认的权限规则。
默认情况下普通用户是可以查看所有部门信息的,我们可以给部门对象配置限制规则来实现部门对象的分部级数据查看权限功能。
{{$user.roles.indexOf('user') > -1}}
{{[["_id", "=", $user.companies.map(function(n){return n.organization;})], "or", ["parents", "=",$user.companies.map(function(n){return n.organization;})]]}}
PS: 这里如果通过给部门对象配置自定义对象权限,去掉“查看所有记录”勾选框,勾上“查看本分部”,而不是配置限制规则的话也能实现部门对象的分部级数据查看权限功能,但是这种配置实现的是“让user简档下的用户只能查看自己所属分部的部门信息,且不能查看其子分部下的部门信息。”
默认情况下普通用户只能看到自己所属分部下的人员,但是不能查看自己所属分部的子分部下的人员,我们可以给人员对象配置共享规则来让普通用户可以额外查看自己所属分部的子分部下的人员信息。
{{$user.roles.indexOf('user') > -1}}
{{[["organizations_parents", "=", $user.companies.map(function(n){return n.organization;})]]}}
虽然在华炎魔方中使用上面介绍过的各种权限配置方法已经能轻松实现精确到字段和记录级的权限控制,但在千奇百怪的实际项目场景中还是会有些权限需求未能企及,所以华炎魔方权限引擎还结合了 对象触发器,允许通过编写代码的方式来解决各种个性化的更高级别的权限问题。
华炎魔方中的对象触发器,按触发的先后时间次序有两种,一种是在操作执行前触发,一种是在操作执行后触发,并且前者是以before
为前缀来命名,后者是以after
为前缀来命名,比如beforeUpdate
和afterUpdate
分别会在执行数据修改前和数据修改成功后触发。
要通过触发器来实现权限控制,我们只要在相关对象上编写操作执行前的触发器,即编写以before
为前缀命名的增、删、改、查触发器,在触发器函数中判断到当前登录用户没有权限时通过有意throw Error
或返回false
值的方式来中断执行相关操作就可以达到权限控制的目的了。
我们可以在beforeInsert/beforeUpdate/beforeDelete
触发器中根据当前用户的身份来决定是否拒绝执行相关操作,当需要拒绝时只要在触发器函数中有意throw Error
或返回false
值就可以中断执行相关操作。
在触发器函数中可以通过表达式this.doc
变量来获取要修改哪些字段值,我们可以移除该变量中的字段来拒绝保存当前用户没有权限保存的字段值,以下示例代码只允许管理员变更合同记录的owner
字段值。
module.exports = {
listenTo: 'contracts',
beforeInsert: async function () {
const userId = this.userId;
const spaceId = this.spaceId;
if (userId && spaceId) {
const userSession = await auth.getSessionByUserId(userId, spaceId);
var isAdmin = userSession.is_space_admin;
if(!isAdmin && this.doc.owner){
throw new Error("只有系统管理员才能变更合同所有者!");
//return false // 把throw error换成这行,将会新建失败,但是不提示错误信息。
//delete this.doc.owner //把throw error换成这行,将会新建成功,但是新建后的记录owner值为空。
}
}
},
beforeUpdate: async function () {
const userId = this.userId;
const spaceId = this.spaceId;
if (userId && spaceId) {
const userSession = await auth.getSessionByUserId(userId, spaceId);
var isAdmin = userSession.is_space_admin;
if(!isAdmin && this.doc.owner){
throw new Error("只有系统管理员才能变更合同所有者!");
//return false // 把throw error换成这行,将会修改失败,但是不提示错误信息。
//delete this.doc.owner //把throw error换成这行,将会修改成功,但是记录owner值不会被修改。
}
}
}
}
我们可以在beforeFind
触发器中根据当前用户的身份来决定是否额外叠加特定的过滤条件来限制数据查看范围。
在beforeFind
触发器函数中通过表达式this.query.filters
变量可以获取原始的查询条件,然后可以变更该变量值来给原始查询条件额外叠加其他的过滤条件来收缩或放大用户的数据查看权限。
假设有一个文档集对象,要实现以下权限规则:
上述需求中需要把权限落实到具体的用户,所以无法通过分部级权限来实现该需求,我们可以给该对象添加两个额外的字段来保存相关权限配置:
然后编写以下代码为文档集document_collections
对象添加beforeFind
触发器逻辑实现相关权限控制需求。
const Filters = require('@steedos/filters');
module.exports = {
listenTo: 'document_collections',
beforeFind: async function(){
const query = this.query;
const userId = this.userId;
const spaceId = this.spaceId;
if (userId && spaceId) {
//获取原始的查询条件
let filters = query.filters;
//自定义生成用户权限查询条件
let permissionsFilters;
const userSession = await auth.getSessionByUserId(userId, spaceId);
var isAdmin = userSession.is_space_admin;
if (!isAdmin) {
var organizations = userSession.organizations.map((org) => { return org._id });
permissionsFilters = [
["owner", "=", userId],
"or",
["allow_read_organizations", "=", organizations],
"or",
["allow_read_users", "=", userId]
];
}
if(permissionsFilters){
//修改查询条件; formatFiltersToODataQuery函数将数组形式的filters转换为字符串filters
query.filters = `(${filters}) and (${Filters.formatFiltersToODataQuery(permissionsFilters)})`;
}
}
}
}
在触发器中编写权限控制相关代码时,我们经常需要会判断当前登录用户是否有相关权限,而要判断权限首先需要获取的是当前登录用户的身份信息,在华炎魔方中通过可以编写以下代码来获取用户身份。
可以通过以下代码来判断当前用户是否是系统管理员。
const userSession = await auth.getSessionByUserId(userId, spaceId);
var isAdmin = userSession.is_space_admin;
if (!isAdmin) {
//当前登录用户不是管理员
}
可以通过以下代码来获取当前用户所属组织,一般用于在过滤条件中按组织来过滤数据。
const userSession = await auth.getSessionByUserId(userId, spaceId);
var organizations = userSession.organizations.map((org) => { return org._id });
还可以通过以下代码来获取当前登录用户的userSession
来获取更多用户信息。
const auth = require("@steedos/auth");
const userSession = await auth.getSessionByUserId(userId, spaceId);
userSession
中所包含的用户信息详情请查阅本文档底部的 userSession详解 小节。
华炎魔方自动为业务对象生成的 GraphQL API,ODATA API都自带身份验证并与华炎魔方权限引擎集成,实现数据权限控制。
另外在微服务中对数据执行增删改查等操作,或是在触发器中调用 ObjectQL 数据查询或操作函数,都会经过华炎魔方权限引擎校验,只有校验通过的操作才会被执行。
华炎魔方使用数组格式定义过滤条件。
运算符为”=“时,条件自动按”or”裂变连接成多个筛选条件,类似实现了”in”操作功能,所以下两种写法结果相同:
[["status", "in", ["closed","open"]]]
[ [ "status", "=", "closed" ], "or", [ "status", "=", "open" ] ]
运算符为”!=“时,条件自动按”and”裂变连接成多个筛选条件,所以下两种写法结果相同:
[["status", "not in", ["closed","open"]]]
[ [ "status", "!=", "closed" ], "and", [ "status", "!=", "open" ] ]
运算符为”between”时,条件自动转换成”>=“及”<=“运算符对应的筛选条件,以下各组效果相同:
[["age", "between", [20,30]]] 等效于 [ [ "age", ">=", 20 ], "and", [ "age", "<=", 30 ] ]
[["age", "between", [null,30]]] 等效于 [ [ "age", "<=", 30 ] ]
[["age", "between", [20,null]]] 等效于 [ [ "age", ">=", 20 ] ]
between只支持数值及日期时间类型,且过滤值必须是两个元素的数组格式
其他情况一律自动按”or”裂变连接成多个筛选条件
[["tag", "contains", ["start","end"]]] 等效于 [ [ "tag", "contains", "start" ], "or", [ "tag", "contains", "end" ] ]
多个过滤器可以通过“与(and)”、“或(or)”操作进行组合,例如:
[ [ "value", ">", 3 ], "and", [ "value", "<", 7 ] ]
[ [ "value", ">", 7 ], "or", [ "value", "<", 3 ] ]
如果不指定“与(and)”、“或(or)”操作,系统默认按照“与(and)”操作执行过滤。所以下两种写法结果相同:
[ [ "value", ">", 3 ], "and", [ "value", "<", 7 ] ]
[ [ "value", ">", 3 ], [ "value", "<", 7 ] ]
userSession中包含了当前登录用户信息,下面列出了其主要变量清单:
属性名 | 值类型 | 属性含义 |
---|---|---|
authToken | string | 登录信息 |
companies | array | 所属分部 |
company | object | 主分部 |
company_id | string | 主分部id |
company_ids | array | 所属分部id |
string | 邮箱 | |
is_space_admin | boolean | 是否是工作区管理员 |
language | string | 语言 |
locale | string | 本地语言 |
mobile | string | 手机号 |
name | string | 用户名 |
organization | object | 主部门 |
organizations | array | 所属部门 |
password_expired | boolean | 登录密码过期 |
permission_shares | array | 共享规则 |
profile | string | 简档 |
roles | array | 用户角色 |
space | object | 当前工作区 |
spaceId | string | 当前工作区id |
spaces | array | 用户所属工作区集合 |
userId | string | 用户id |
utcOffset | number | 国际化UTC时间偏差;北京时间时该值为 8。 |