一、问题是怎么冒出来的
大家在做 PowerApps 的时候,应该都碰过这种窝火事:明明给张三分配了“编辑者”的角色,结果他打开应用,编辑按钮压根不显示;反过来,李四只是个普通访客,却能看到删除按钮,而且点了还真的能删。这种权限控制“神经错乱”的情况,大多数时候不是 PowerApps 本身坏了,而是我们自己把角色和权限的对应关系搞拧了。
我举一个真实的例子。有个同事做了一个请假审批应用,他设想得很美好:普通员工只能看到“提交请假”按钮,部门主管可以看到“审批”按钮,HR 看到“导出”按钮。可是应用上线后,员工登录居然看到了“审批”按钮,点进去还能改别人的请假状态。查了半天,最后发现他把角色名称和权限判断条件里的字符串写错了一个字——代码里判断的是“部门经理”,而角色表里存的是“部门主管”。就这么一个小小不一致,让整个权限体系全乱套了。所以说,角色和权限的映射,是这类应用里最不能含糊的一环。
二、先弄明白权限控制的基本套路
2.1 角色和权限到底是个啥
你可以这么理解:角色是给“人”贴的标签,权限是给“事情”定的规矩。比如“人事专员”是个角色,而“查看所有员工信息”是一条权限。一个应用里可以有很多角色,每个角色又能拥有多条权限。角色和权限之间的关系,就是所谓的“映射”。
在实际开发中,我们通常不会直接判断某个用户能不能编辑,而是先判断他属于哪个角色,再根据角色去查他有哪些权限。这样设计的好处是,当一个人换了岗位,只需要把他的角色改一下,所有权限都会自动跟着变,不需要四处找代码去改。
2.2 常见的映射方式
最简单的映射方式,就是“如果用户是管理员,那么他拥有全部权限;如果是编辑者,他拥有部分权限;如果是访客,只拥有查看权限”。这种判断在 PowerApps 里通常用“如果”函数来实现。
稍微复杂一点的做法,是把角色和权限做成一张表,表里每一行写着某个角色能做什么、不能做什么。应用启动时读取这张表,之后每次要判断权限,就去查表。这种方法的好处是,以后增减权限不用修改公式,只需要改表里的数据就行了。
2.3 为什么容易出错
出错的原因往往不是权限逻辑本身有多复杂,而是因为我们要在两个地方维护角色信息:一处是应用里的判断公式,另一处是存放用户身份的数据源。两边一旦不同步,就会出现“A 处写的是角色编号,B 处写的是角色名称”这样的错位。再加上前端按钮的可见性、后端数据源的筛选条件,各自都要写一遍判断,任何一个地方写岔了,都会引发权限失控。
三、几种典型的角色与权限映射错误
3.1 错误一:把角色名硬编码错了
这是最常见的一种。比如你在数据源里存的角色值是“admin”,但是公式里判断的是“administrator”。明明都是“管理员”,可机器不认,它只认字符串是否完全相等。这种错误很难靠肉眼发现,因为代码看起来挺正常,而且一般测试时只用默认账号,不会特意去换一个不同角色名的账号登录。
3.2 错误二:判断条件写反了
有时候我们为了省事,会写这样的逻辑:“如果不是访客,就允许编辑”。看起来没问题,可一旦角色表里多了一个“临时工”,这个临时工不是访客,于是也被当成编辑者了。这种“排除法”式的权限判断,在角色数量少的时候还好,角色一多,就会漏掉很多边界情况,导致某些不怀好意的人获得了不该有的权限。
3.3 错误三:忘了数据源层的权限
很多时候,前端按钮隐藏了,但数据源依然可以被直接访问。比如你用 SharePoint 列表作为数据源,列表本身的权限设置很宽松,那么用户即使不通过 PowerApps,也能直接去列表里改数据。所以,权限控制不能只看应用界面,数据源那一层也得把好门。PowerApps 里的“委托”和筛选条件,如果没写好,也容易绕过前端的限制,把不该显示的数据查出来。
四、一套容易理解的修复方案
这一节我们用一个具体的技术栈来演示修复过程:技术栈采用 Power Fx(PowerApps 的公式语言)。所有示例代码都是 Power Fx。下面我们一步步来改造一个权限控制有问题的应用。
4.1 设计思路:把映射表集中管理
我们不再把权限判断散落在各个按钮属性里,而是集中定义一张“角色权限映射表”,放在应用启动时。这样要改权限,只需要改这一处。另外再定义一张“用户角色表”,专门记录每个用户属于哪个角色。每次判断权限时,先通过当前登录用户的邮箱,到他对应的角色,然后去权限映射表里查这个角色能干什么。
4.2 在应用启动时建立角色权限表
在 App 的 OnStart 属性里,我们清空旧的映射表,再重新收集新数据。示例代码如下:
// 清空原有角色权限表,防止脏数据残留
Clear(colRolePermissions);
// 往角色权限表里插入三条记录,每条记录包含三个权限布尔值
Collect(
colRolePermissions,
{
Role: "管理员",
CanView: true, // 管理员可以看所有数据
CanEdit: true, // 管理员可以编辑所有数据
CanDelete: true // 管理员可以删除所有数据
},
{
Role: "编辑者",
CanView: true, // 编辑者可以查看数据
CanEdit: true, // 编辑者可以编辑数据
CanDelete: false // 编辑者没有删除权限
},
{
Role: "访客",
CanView: true, // 访客只能查看数据
CanEdit: false, // 访客不能编辑
CanDelete: false // 访客也不能删除
}
);
// 清空用户角色表,重新建立用户与角色的对应关系
Clear(colUserRoles);
// 这里为了示例,我们手动模拟两条用户数据
// 实际项目中,这些数据通常来自 SharePoint 列表或者 SQL 数据库
Collect(
colUserRoles,
{
UserEmail: "zhangsan@contoso.com", // 张三的邮箱
Role: "管理员" // 张三的角色是管理员
},
{
UserEmail: "lisi@contoso.com", // 李四的邮箱
Role: "访客" // 李四的角色是访客
}
);
这里把映射表集中到了两处,逻辑一目了然。以后要新增一个“部门主管”角色,只需要在 colRolePermissions 里加一行,同时在 colUserRoles 里把对应的用户角色改过来就行。
4.3 获取当前用户角色并计算权限
我们在应用加载后,要拿到当前登录用户的邮箱,然后从 colUserRoles 中找出他对应的角色,再去 colRolePermissions 里查出他有哪些权限。示例代码如下:
// 获取当前登录用户的邮箱地址,注意这个函数在浏览器环境也能工作
Set(gblCurrentEmail, User().Email);
// 根据当前用户邮箱在用户角色表中查找他的角色
// 如果找不到,默认设置为“访客”,防止没有角色的人获得越权权限
Set(
gblCurrentRole,
LookUp(
colUserRoles,
UserEmail = gblCurrentEmail,
Role
)
);
// 如果上面查找的结果是空白,那就把它当成访客处理
If(
IsBlank(gblCurrentRole),
Set(gblCurrentRole, "访客")
);
// 根据当前角色,从权限表里取出“可以编辑”这个布尔值
Set(
gblCanEdit,
LookUp(
colRolePermissions,
Role = gblCurrentRole,
CanEdit
)
);
// 取出“可以删除”的布尔值
Set(
gblCanDelete,
LookUp(
colRolePermissions,
Role = gblCurrentRole,
CanDelete
)
);
注意代码里我故意把创建用户角色表和判断逻辑分开写,这样万一出问题,你可以先检查 gblCurrentRole 是不是正确,再检查 gblCanEdit 是否正确,排查起来会舒服很多。
4.4 控制按钮的可见性
假设界面上有一个“提交请假”按钮和一个“删除记录”按钮。现在我们直接控制它们的 Visible 属性。
“提交请假”按钮的 Visible 属性设置为:
// 见:只有有编辑权限的人才能提交请假
gblCanEdit
“删除记录”按钮的 Visible 属性设置为:
// 删除权限比较特殊,只有管理员能删除
gblCanDelete
这样做的好处是,用户界面上不会再出现“点了没反应”或者“点了报错”的怪事。按钮显示不显示,跟数据源的权限完全一致。
4.5 控制数据操作权限
除了按钮可见性,我们还要确保就算有人绕过界面,直接通过其他途径操作数据,也不会越权。比如数据的筛选条件,我们可以写成这样:
// 假设数据源叫 spLeaveRequests
// 最终列表控件的数据源属性:Filter 函数根据当前角色动态过滤
Filter(
spLeaveRequests,
// 如果是管理员,看全部数据;否则只能看自己提交的请假单
If(
gblCurrentRole = "管理员",
true,
RequestorEmail = gblCurrentEmail
)
)
这条公式的含义很直白:管理员登录后,可以看所有请假申请;非管理员登录后,列表里只显示自己的请假申请。这样一来,就算有人在数据源里乱翻,也很难看到别人提交的内容。
对于编辑表单的开启权限,可以用属性判断:
// 编辑表单的 DefaultMode 属性只有两种取值
// 当没有权限时,强制设为只读;有权限时,设为编辑
If(
gblCanEdit,
FormMode.Edit, // 允许编辑
FormMode.View // 只能查看
)
这个设置的好处是,无权限用户就算能打开表单,也只能看,不能改。相比只隐藏按钮,这个方式更安全。
4.6 运行效果和验证方法
当你把上面的代码都布置好以后,可以按照下面步骤来验证效果。
首先,用张三的账号登录,也就是示例里的“zhangsan@contoso.com”。你会看到“提交请假”按钮是显示的,“删除记录”按钮也是显示的。再去打开请假列表,能看到所有人的记录。因为张三的角色是管理员,他拥有所有权限。
然后,用李四的账号登录,也就是“lisi@contoso.com”。你会看到“提交请假”按钮不见了,“删除记录”按钮也不见了。在请假列表里,只能看到李四自己提交的记录。因为李四是访客,什么编辑和删除操作都做不了。
如果实际场景中李四应该是编辑者,那你只需要修改 colUserRoles 里的记录,把李四的角色从“访客”改成“编辑者”,重启应用后,他的按钮和列表内容就会跟着变,不需要去改任何按钮属性。这就是集中映射表的魔力。
五、几点注意事项
第一,角色名称一定要全应用统一。不要在用户角色表里写“admin”,却在权限判断里写“管理员”。最好在文档里列一个角色命名字典,开发的时候照着写。
第二,别把权限判断逻辑到处复制。今天在一个按钮上写“如果用户邮箱等于某某”,明天在另一个按钮上又写“如果用户角色不等于是某某”,很容易写乱。不如集中管理,哪怕多花一点时间设计映射表,长期收益也是划算的。
第三,数据源本身的权限不能忽略。PowerApps 里的按钮隐藏并不等于数据安全。一定要检查你的 SharePoint 列表、SQL 表或者 Dataverse 表的底层权限设置,确保没有给所有人开放“完全控制”权限。
第四,注意 Power Fx 中的委托特性。当你使用 Filter 过滤 SharePoint 大列表时,有些函数可能无法委托,导致只能拿到前 500 条数据后再在本地筛选,这本身又是一个权限漏洞。建议把“请求者邮箱”字段单独建索引,并且用支持委托的函数来写筛选条件。
第五,测试时不要只用管理员账号。一定要准备低权限的测试账号,专门用来验证越权访问。最好把常见角色的测试账号列成一张表格,每次发布新版本前,逐个角色登录,把所有敏感操作都点一遍。
六、总结
PowerApps 权限控制看似不起眼,但是一出错,轻则功能失效,重则数据泄露。今天我们聊到的“角色与权限映射错误”,根源往往是角色名称不一致、判断逻辑写反、或者分散管理导致的遗漏。集中管理角色权限映射表,用 Power Fx 在应用启动时一次性建立映射关系,然后再用统一的变量去控制按钮和数据源,是一个简单又靠谱的修复思路。希望这个方案能帮你在今后的开发中少掉几根头发。遇到权限乱套的时候,先别急着到处找补,坐下来把角色和权限的关系重新捋一遍,你会发现很多问题其实没那么复杂。
评论
围绕“PowerApps应用权限控制异常:角色与权限映射错误的修复方案”参与讨论