写点什么

PingCode Wiki 权限设计之 ACL

作者:阿杰
  • 2022 年 2 月 11 日
  • 本文字数:4361 字

    阅读完需:约 14 分钟

PingCode Wiki 权限设计之ACL

2021 年 Wiki 加入了很多强硬的特性,其中包括协同编辑 、页面权限表情符号等,这些功能给用户带来了更好的体验。作为 Wiki 使用者兼开发者,今日来聊聊年终上线的页面权限,同时总结一下开发阶段涉及到的技术、遇到的问题以及解决方案,关于权限本人之前已经写过一篇Worktile 权限的文章了,Worktile 权限着重讲了 RBAC(基于角色的权限控制方案)的设计与实现,本文基于 Wiki 页面权限选择的另一个主流权限设计的方案:ACL


本文大致分为三部分:


  • 1. ACL 介绍

  • 2. 介绍我们的权限以及为什么选择它

  • 3.设计实现

一、ACL 介绍

1. 什么是 ACL?

ACL:Access Control List,权限控制列表,是对文件以及目录的权限控制方案。大名鼎鼎的 Linux 权限系统,它就是 ACL 的典型案例,本人在开发过程中也受到了 Linux 权限设计的一些启发。

2. ACL 的使用场景

使用场景也可以换个问法:为什么要使用 ACL ?关于这个问题我们还以 Linux 作为案例:Linux 本身只提供了 Owner(所有者)、Group(用户组)、Others(其他成员),也就是说其他成员或用户组是无法指定更细粒度的权限。


为了更好的解释,我们来举个简单的例子(场景):


有 4 个成员有 A、B、C、D,其中 A、B、C 是开发组 G 的成员,A 成员创建了一个代码仓库并把团队开发的代码放置到该目录中,其中这些代码主要是关于 G 组的,与其他成员无关,所以 A 把文件目录设置了权限,权限是组内可读可写,其他人没有任何权限。


现在来分析一下各个角色,A 是仓库的 Owner,G 是 Group(含 B、C),D 是与该文件无关的成员,所以是 Others。现在入职了一个用户 E,因为 E 是新人,所以不想让 E 去操作代码,只允许他查看熟悉代码。


面对这种场景,试想一下如何给 E 成员设置对应权限呢?答案是 oh no,因为 E 既不能按照 G 组权限,也不能按照 Others 权限,更不能是 Owner!所以面对这种鸡肋的权限,ACL 就作为了其补充,ACL 可以支持针对某一个用户或某个用户组做独立的权限,完美解决了类似场景。

二、PingCode Wiki 权限架构

OK,了解了什么是 ACL 以及使用场景,我们来聊一下 Wiki 的权限架构,基本架构见下图。





1. 基础权限 RBAC——角色对应的权限

1.1 权限配置





1.2 查看权限





2. 页面权限 ACL——针对用户和用户组的权限

页面权限同样是作为 RBAC 的补充,更加细粒度的权限划分,即给某部分人或某部分用户组分配对应权限。

2.1 页面权限设置入口

右上角菜单——「更多-权限设置」





2.2 独立权限与共享权限

独立权限——「权限设置-知识库成员」





共享权限——「权限设置-非知识库成员」





从我们的需求(针对用户和用户组部分人群)来看是符合 ACL 解决方案的。

三、设计与实现

到这里的同学相信已经对 Wiki 权限功能有了大致的了解,下面开始详细介绍一下开发中的设计细节,关键点、难点以及方案的选用取与舍。

1. 关系模型与数据库设计

了解基本需求,本人根据以往的开发经验简单的定了一个初步方案,见下图方案 A,后来对照原型和设计图调整了一些细节,产生了方案 B,见下图。

A 方案





B 方案(最终方案)





两种方案的区别:

A 方案是关系型数据库的常见设计,以用户/用户组为主导映射模块和权限,对应关系为:多对多 用户/用户组(user/group) ——> 资源(pageId)

B 方案,以模块为主导映射用户/用户组,一对多:资源(pageId)——> 用户/用户组(scopes)

考虑到权限是全量保存的,正是由于这种交互方式所以调整出了 B 方案,而且服务端数据库是 mongodb,支持数组的特性,非常的契合。

2. 对应的程序实现

资源或模块(pageId)——> 用户/用户组(scopes),实体对应关系





权限值计算参考了 Linux,采用的是位运算:1、2、4、8...

  • 拥有的权限值累计取最大 mask 值:只读 1 + 编辑 2,最终数据库存 3。

  • 判断是否拥有某个权限值:权限值 & 预期的权限值 = 存在/预期的权限值。

3. 约定 API

还是由于保存交互决定了添加、修改、删除是一个 API。

  • 保存(添加/修改/移除):{put}/api/wiki/spaces/:spaceId/pages/:pageId/permission$ 

  • 获取:​{get} /api/wiki/spaces/:spaceId/pages/:pageId/permission/:scopeType$ 

4. 涉及的复杂场景

权限涉及的复杂场景非常的多,以下场景讨论和决定经历了很多波折,某些功能中途重做了两次,迭代向后推迟了一周,两次会议,共持续大约两个半小时,由于文章篇幅,只列举部分场景作为交流。

4.1 权限继承(复杂场景)

第一个难处理的点就是权限继承,确实很绕,相信很多产品都遇到过类似场景(具体是哪些这里不赘述了),本人从技术和产品的角度都参考过 Linux 的处理,但由于系统级别和应用还是有些许不同,做了一些调整,下面罗列的是我们做过的方案。

第一版:子页面完全继承父页面且不可更改

  • 父页面不可见,子页面不可见

  • 父页面只读,子页面编辑,最终子页面是只读

  • 父页面编辑,子页面只读,最终子页面是编辑

不满足的场景和逻辑的冲突点:

  • 父页面允许查看,预期某些子页面允许查看,某些子页面不可查看

  • 父页面允许查看但不许编辑,预期某些子页面开放编辑权限

  • 父页面允许编辑,但某些子页面不允许编辑

第二版:子页面允许独立权限,默认继承父权限

页面继承逻辑:子页面一旦设置,中断与父页面的继承关系,中断后权限类型可以改

  • 父级页面设置权限后,子级页面不设置权限,默认继承父级权限;

  • 子级页面设置权限后,不再继承父级权限,形成自己单独的页面权限

4.2 中间件逻辑

中间件涉及的场景也比较多,比如单页面详情查看,拖拽移动以及跨知识库移动,复制,删除等等,核心的验证逻辑如下图。





4.3 页面树展示及权限计算(难点)

4.3.1 展示逻辑

会议上,讨论页面树的展示的可选方案如下:

  • 展示全部层级页面,根据权限可见内容(存在标题私密性问题,层级过多后,会看到多个不可见的标题,抛弃

  • 只展示有权限的页面,把子页面提出来(层级结构打乱了,​抛弃

  • 落地方案:只展示有权限的页面,当父页面不可见时,子页面可见时,把父页面展示出来,如果没有可见的子页面时父页面也不可见(方案二)---父页面不可见的标题用灰色展示(点击效果保留),点击后右侧无权限页面

  • 直接隐藏父与子(已实现方案一,仅能通过通知查看,入口少,改变继承关系后,不太适用,抛弃

最终确定的是一个折中方案,如果子页面有权限,那么会把父页面也展示出来,保证树结构的完整性,若当前页面和子页面都没有权限,那么就过滤掉。如下图





4.3.2 权限计算(技术难点)

难点:

  1. 页面树涉及到继承

  2. 全量的计算(弄不好需要递归处理),所以程序计算复杂度要考虑

  3. 过滤逻辑(子有查看权限,父一定展示)

解决手段

  1. 数据库存储 parent 和 parent_ids 字段,parent_ids 按照树的层级拍平排序,比如树的层级是 A->B->C,parent_ids 储存的是 [A,B,C]。

  2. 把列表数据分割成两组,一组是所有的页面,另一组是所有作为了父页面的,具体怎么查询只需要验证 parent_id 是否存在即可,属于数据库操作。

  3. 把 List 数据结构转换成 Map 或 Set,减少迭代次数来降低复杂度。

具体处理逻辑

准备工作:

  1. 从数据库中取出当前知识库的页面列表数据和页面对应的 ACL 权限列表

  2. 将页面列表拍平转换为 Map(为了下面减少复杂度)

  3. 将权限列表拍平转换为 Map(目的同上)

  4. 取出 RBAC 的知识库权限

  5. 用来处理子页面可见,父页面不可见,而需要展示的父页面集合 Set(在下面会详细讲解具体用处)

  6. 最终要 页面列表(含权限值)

如下图(为了方便后续的讲解,特标注序号)





处理流程,如下图:





  1. 迭代页面列表

  2. 取出一个页面,找对应权限

  3. 如果找到了设置的对应权限,那么合并 RBAC 权限放入最终的结果集,没有找到执行下面逻辑 jjjjjjjjjj a. 通过当前页面的 parent_ids 按倒序迭代(上面解决手段已讲解 parent_ids 的排列顺序) jjjjjjjjjjb. 如果没有父级或所有父级均没有权限,则继续最外层迭代,反之取出最近有权限的父级页面,并且将父级页面与当前页面之间的页面放入 ⑥ (Set)中,这块大家可能有疑问,举个例子:假设有 3 个页面,A、B、C,C 的父级是 B,B 的父级是 A,当前迭代的是 C,C 没有权限,B 也没有权限,A 有权限,那么最终将 A、B 推入 Set 中,C 由于继承逻辑 也拥有权限。

  4. 将 Set 中的父级页面补充到最终的结果集

  5. 继续迭代,直至完毕

最后发现 ⑥ 是为了补充缺失的一部分数据:子页面可见,父页面不可见,但要展示的父页面

大家可能从上面环节还有一个疑问就是为什么要合并 RBAC 知识库权限,请接着看。

4.4 权限相关特殊处理

上面谈到了已经验证出页面权限,但还是合并了知识库权限,是因为产品逻辑中有以下规定:

  1. 知识库没有编辑权限,页面权限有,那么最终有编辑权限,所以要覆盖知识库的编辑权限。

  2. 拥有只读权限的成员不能执行的操作:

  • 不能创建子页面

  • 不能删除当前页面

  • 不能移动当前页面

  • 不能移动至只读的页面下(对目标页面来说实际上是添加页面)

  • 不能复制页面

  • 不能复制至只读页面下

  • 不能发布页面

  • 不能重命名页面

  • 不能进入编辑页面

注意的是切勿漏掉场景。

4.4.1 个人对后续的设想

针对于这些特殊处理,个人感觉终究不是解决方案,而且后续新加或移除权限,都要兼顾多处,技术角度维护起来困难,也容易漏掉,所以设想页面也加入更多权限点,让用户来主动勾选所需要和禁止的权限,技术上就可以统一一套逻辑。

五、写在最后

上面只是列举了一些通用和些许复杂的场景,实际开发中还有很多的细节,所以开发期间很忙,经历了头脑风暴,也有走误区的及时调整和反思,这些都是宝贵的经验。

最后谈谈开发至今的一些感悟:

  • 团队之间及时沟通,充分理解需求,建立统一认知,避免把烟囱做成水井的糗事,也减少互相甩锅的情况,对团队有良好的作用,值得一提的是 Wiki 团队秉承着这一优良习惯,与 Wiki 这款产品的价值观相契合 。

  • 技术人员要积极考虑需求,优先站在用户和产品角度思考,其次再考虑技术,切勿过于执着技术,却忽略了用户和产品的初衷。

  • 技术人员对每一行代码负责,别把技术债留给自己或别人,代码终将会回馈团队和你自身。

  • 要做好细节,作为还是技术都要做好细节,“刚”到可能是细节决定的成败。

到这里结束了,非常感谢每个看到这里的同学 ,有任何疑问欢迎讨论交流。

欢迎关注 Wiki,关注 PingCode 研发中心。


最后,推荐我们的智能化研发管理工具 PingCode 给大家。

PingCode官网

关于 PingCode

PingCode 是由国内老牌 SaaS 厂商 Worktile 打造的智能化研发管理工具,围绕企业研发管理需求推出了 Agile(敏捷开发)、Testhub(测试管理)、Wiki(知识库)、Plan(项目集)、Goals(目标管理)、Flow(自动化管理)、Access (目录管理)七大子产品以及应用市场,实现了对项目、任务、需求、缺陷、迭代规划、测试、目标管理等研发管理全流程的覆盖以及代码托管工具、CI/CD 流水线、自动化测试等众多主流开发工具的打通。


自正式发布以来,以酷狗音乐、商汤科技、电银信息、51 社保、万国数据、金鹰卡通、用友、国汽智控、智齿客服、易快报等知名企业为代表,已经有超过 13 个行业的众多企业选择 PingCode 落地研发管理。

发布于: 刚刚阅读数: 4
用户头像

阿杰

关注

还未添加个人签名 2021.04.06 加入

还未添加个人简介

评论

发布
暂无评论
PingCode Wiki 权限设计之ACL