社区类应用的权限设计以及管理 —— 以 NodeBB 为例

Node.js

笔者近期在开发淘宝开发者社区时需要对 NodeBB 的安全架构进行评估,由于 NodeBB 使用了 NoSQL 且无论是官方还是社区对其数据库设计都没有相关的介绍说明,因此整理了 NodeBB 的权限设计及管理方式的说明文档,在此分享供有需要的同学进行参考。

权限概念

首先需要搞清楚的第一个问题是,应用内有哪些用户角色?

用户角色

NodeBB 内有如下用户角色:

用户群组

其次是,应用内的角色控制是通过怎样的方式实现的?

NodeBB 内的用户角色权限控制是由“群组”来实现的。其默认群组是在应用数据库初始化时(./nodebb setup)创建的,有如下群组:

相关 SQL:

群组的设置

然后我们需要了解,群组内包含了哪些设置?即群组“表”(NodeBB 使用 Mongodb,本身没有表的概念)包含了哪些字段。

群组的设置

主要关注以下几个字段:

群组的管理权限

群组的管理者有对群组的管理权限,即:

用户角色权限控制

这些用户角色权限控制如何实现?

权限类型

权限授予的维度

权限维度的关系:用户权限继承群组权限。

例如,有权限 p_1、用户 u_1、和群组 g_1,用户 u_1 属于群组 g_1,则有:

实现

一个示例:

最终:

权限创建与授予

  1. 用户 u_especial/u_global/u_ban_c1/u_other/u_admin 注册时自动加入到 registered-users 群组(代码实现);

  2. 创建版块 c1:

    • 为版块的相关权限创建“权限群组”,其中为 版块 c_1 的 “删除主题权限” 创建两个权限群组:

      • cid:1:privileges:groups:topics:delete:群组维度;
      • cid:1:privileges:topics:delete:用户维度。
    • 授予指定群组该权限(代码实现),默认授予了 administratorsregistered-users 群组该权限,即是将它们加入到了 cid:1:privileges:groups:topics:delete 的成员:

      {_key: "group:cid:1:privileges:groups:topics:delete"} 权限群组

  3. 设定 registered-users 群组无版块 c_1 的删除主题的权限,即是将 registered-users 群组从权限群组 cid:1:privileges:groups:topics:delete 的成员内移除:

    权限群组

  4. 将 u_admin 加入到群组 administrators,可以看到 administrators 群组成员里已有 u_admin(value=12):

    admin群组

  5. 设定 u_especial 用户有版块 c_1 的删除主题的权限,即是将 u_especial 加入到权限群组 cid:1:privileges:topics:delete 的成员:

    {_key: "group:cid:1:privileges:topics:delete:members"} 权限群组

  6. 设定 u_ban_c1 设置为 c_1 的版主,即是将 u_ban_c1 加入到权限群组 cid:1:privileges:moderate

    {_key: "group:cid:1:privileges:moderate:members"} 权限群组

  7. 设定 u_global 设置为论坛总版主,即是将 u_global 加入到群组 Global Moderators

    {_key: "group:Global Moderators:members"} Global群组

权限验证

在前台用户发起删除帖子时,调用的是 posts.delete 接口,该接口调用栈如下:

同样的,其他操作也会遵循这一个流程进行权限验证:

接口层 -> 模型层 -> 权限层

多重权限如何进行管理

增加角色

增加角色可以通过新建群组的方式来实现。

例如新建群组 g_test,然后授予其权限,比如:

增加权限点

增加权限点通过编码的方式进行。以下是一个示例。

当前在应用内没有对“关注”进行权限控制。如果需要,得如何增加?

  1. 在全局权限列表中添加 follow 字段:

    代码 关注

  2. 在 user 模型的 follow 方法中添加权限控制流程

    代码

  3. 在权限管理层添加判断条件

    代码

系统及用户设置

系统及用户级亦有限制和隐私的设置,例如:

  1. 系统设置:
    • 实现:Config 类
    • 获取系统设置:{_key: "config"}
  2. 用户设置:
    • 实现:User.settings
    • 获取某个用户的设置:{_key: "user:{id}:settings"}

参考