4.14 引入了新版权限系统,但这部分功能尚未稳定,也暂时没有进入文档。本话题将介绍新版权限系统的概念、API、相关插件。同时接受有关新版权限系统的问题交流。
TL;DR
4.14 引入的新版权限系统会包含以下能力:
- 新的服务
ctx.permissions
- 新的内置用户字段
permissions
,类型为string[]
- 官方插件 @koishijs/plugin-admin 新增用户组功能
- 指令依赖某些 API 或其他指令时,能够在调用前进行权限检查
- 权限组、用户组、用户组路线
- 基于权限系统的服务端 API(WIP)
弃用特性和迁移计划:
- 用户字段和指令配置中的
authority
将会暂时进入弃用状态 - 我们目前没有移除
authority
属性的计划,开发者不需要迁移到 Permission API - 未来的某个版本中可能会将
authority
连同 Permission API 一起挪到独立的插件中- 这项改动目前暂时没有具体方案,只是作为一种可能的计划
- 用户需要安装新的插件以完成迁移,不完成迁移不会影响可用性,只会丢失权限等级相关能力
- 开发者需要将新插件声明为
peerDependency
权限是什么?
权限其实就是执行某些操作的能力。权限具有唯一的标识符,通常由小写英文字母、数字、短横线和点构成。下面是一些你可能会见到的权限名称:
- user.514:ID 为 514 的用户的权限
- group.233:ID 为 114 的用户组的权限
- authority.3:权限等级 3 的权限
- command.foo:指令 foo 的权限
- command.foo.option.bar:指令 foo 选项 bar 的权限
- onebot:onebot 平台的权限
- onebot.admin:onebot 平台下群管理员的权限
- bot.channel.mute:能够禁言频道的机器人的权限
- custom:自定义权限组的权限
我们不区分权限与权限组的概念。权限组只是继承了其他权限的权限。用户和用户组本质上也只是一种特殊的权限组。
权限的继承关系
权限可以继承其他权限。它的基本写法如下:
ctx.permissions.inherit(A, B)
如果权限 A 继承了权限 B,那么拥有权限 A 的主体将被视为同时拥有权限 B(换句话说就是 A 比 B 大)。
例 1:ID 为 514 的用户拥有权限等级 3,指令 foo 所需要的权限等级是 2。这种情况下该用户显然应该可以调用该指令。那么这种调用关系具体是如何体现的呢?
user.514 > authority.3 > authority.2 > command.foo
这里出现了三个继承关系:
-
user.114 > authority.3
,因为 ID 为 514 的用户拥有权限等级 3 -
authority.3 > authority.2
,因为权限等级 3 天生比权限等级 2 大(内置逻辑) -
authority.2 > command.foo
,因为指令 foo 被权限等级 2 继承
由此,权限的继承关系能够顺利表达已有的权限等级机制,并且具备更强的表达能力。
权限的多继承
权限继承除了不能循环外,几乎没有任何限制。因此,任何一个权限既可以被多个权限继承,也可以继承多个权限。下面分别展示一些使用例。
例 2:ID 为 514 的用户同时继承了权限等级 1,而 command.foo 所需权限等级 2,此时该用户并不能调用 foo 指令。但如果现在我们让该用户直接继承 foo 指令的调用权限,会发生什么呢?
user.514 > authority.1
> command.foo
在这张图中,user.514
并不能经由 authority.1
到达 command.foo
,但是添加第二条边后又可以直接到达 command.foo
了。因此该用户此时就又可以调用 foo 指令了。
可以看到,权限的继承为我们提供了一种能力,可以允许特定低等级用户去调用高级权限的指令,这种能力是过去的权限等级所不具有的。
例 3:我们希望某个管理型指令 foo 既可以被权限等级 2 的用户调用,又可以被 QQ 群的管理员调用。此时我们可以对 foo 指令进行以下配置:
authority.2 >
onebot.admin > command.foo
这样一来,一个用户只需满足上述两个条件之一就可以调用此指令了。
可以看到,判断指令是否可以被用户调用,本质上是判断用户自身的权限是否可以顺着权限继承的关系到达指令的权限。同时,权限的继承允许我们给指令的调用设置多个不同条件。
用户组
用户组并非内置功能,可以在安装插件 @koishijs/plugin-permissions 后,于控制台中配置或使用指令配置。
用户组也是一种权限组,以 group.xxx
命名。它可以由管理员用户进行配置,能够批量管理一系列用户。
例 4:你的机器人提供了绘图指令 nai,但是只希望特定用户有权限去调用它,同时这些用户的特权又不适用于其他指令(因此不能简单地配置权限等级)。这个时候,你可以将这些用户放入用户组 VIP(假设 ID = 233):
user.514 >
user.810 > group.233 > command.nai
user.1919 >
进一步,你希望即使是 VIP 用户组也不能配置 --step
等选项,因此你又设置了一个 VVIP 用户组(ID = 666):
user.514 > group.666 > command.nai.option.step
此时,用户 514, 810, 1919 都属于 VIP 用户组,因此可以调用 nai 指令;用户 514 同时属于 VVIP 用户组,因此可以调用 nai 指令的 --step
选项。
请注意,如果用户 514 不属于 VIP 用户组,那么它仅仅拥有权限 command.nai.option.step
,并不拥有权限 command.nai
,因此仍然无法调用 nai 指令。这显得有些不合理,如何解决呢?
- 让 VVIP 用户组也继承
command.nai
- 让 VVIP 用户组直接继承 VIP 用户组,就像这样:
user.514 > group.666 > group.233 > command.nai
第二种做法的好处在于,相当于 VVIP 用户组获得了 VIP 用户组的所有权限,这对于 VIP 用户组本身提供多个权限的情况下配置变得更方便了。
用户组路线
用户组路径并非内置功能,可以在安装插件 @koishijs/plugin-permissions 后,于控制台中配置或使用指令配置。
在过去我们使用权限等级,此时每个用户的权限实际上是一个正整数。因此管理员实际上只是在「提升」或「降低」用户的权限。这比使用用户组更加简单方便。那么有没有办法对用户组引入类似的机制呢?答案就是用户组路线。
例 5:回忆我们刚刚介绍的 VIP 和 VVIP 用户组,它们之间存在的继承关系,与权限等级是类似的:
nai: group.666 > group.223
auth: authority.2 > authority.1
因此,我们可以给这种继承路径起一个名称。这样一来管理员就可以直接使用 promote 来将 VIP 用户升级为 VVIP 了:
track set nai vip vvip
promote @user nai
demote @user nai
promote / demote 的第一个参数是用户或者用户组,第二个参数是用户组路径名(缺省时表示 auth)。
权限的依赖关系
Koishi 中的权限不仅存在继承关系,还存在依赖关系。它的基本写法如下:
ctx.permissions.depend(A, B)
如果权限 A 依赖了权限 B,那么要执行权限 A 的操作时必须同时检查权限 B(换句话说就是 A 需要 B)。
例 6:foo 指令的代码中调用了 bar 指令,因此 foo 指令依赖 bar 指令。
command.foo -> command.bar
如果用户只拥有 foo 的权限,没有调用 bar 的权限,他依然无法调用 foo 指令。
例 7:foo 指令的代码中使用了 bot.muteChannel()
。
command.foo -> bot.channel.mute
如果机器人没有实现此 API,用户就无法在该机器人上调用 foo 指令。
由此可见,权限的依赖与继承不同。继承更多的是一种管理上的考虑,而依赖则关乎具体的功能实现。
权限访问器
在上面的介绍中,如果要定义新的权限,就必须手动分配给用户或用户组后才能使用。有没有方法自动为一个会话分配权限呢?这就是权限访问器的功能。
ctx.permissions.provide('onebot.admin', async (session) => {
return session.onebot?.sender?.role === 'admin'
})
上面的代码的作用是:当某个会话处于 onebot 平台,并且发送者是群内管理员时,自动附加一个 onebot.admin
权限。利用这种技术,我们就可以为特定平台提供权限能力了。
每个权限可以定义多个访问器函数。在运行时必须通过每一个访问器函数的检查才能视为拥有权限。
一个权限要么是普通权限,要么是访问器权限。下面是一些区别:
普通权限 | 访问器权限 |
---|---|
手动分配给用户 (组) | 自动分配给会话 |
可以被其他权限继承 | 不能被其他权限继承 |
权限国际化
普通权限要被用于指令和控制台中显示,因此需要做国际化。具体的做法也很简单:
- 通过 API 定义:使用
permission.{name}
提供翻译文本 - 通过指令定义:定义时提供文本 (自动视为当前用户语言),或通过
--locale
指定特定语言的文本 - 通过控制台定义:可以在控制台「用户管理」界面中配置用户组文本
访问器权限由于其不能被其他权限继承,因此不需要做国际化。
提供网络服务
这部分功能可能不会在 4.14 实装,但是作为权限系统的一部分,这里一并介绍。
未来将会提供基于权限系统的 Router API,允许部分网络服务经过权限认证。应用场景包括:
- 将 Koishi 直接提供网络服务,其他应用可以调用 Koishi 的功能
- 用户可以独立配置每一个控制台页面、功能的访问权限
- 用户可以为自己生成具有细粒度权限的个人令牌 (PAT),用于网络调用
到底是什么版本
超时空是 Koishi 传统艺能,不可不品尝(其实是 typo
好好好!!!
好好好!!
快进到 KoishiLang KoishiOS MineKoi
万物基于 Koishi (x
补充:条件权限
权限的继承和依赖关系都可以是有条件的,可以传入第三个参数:
ctx.permissions.inherit(A, B, condition)
ctx.permissions.extend(A, B, condition)
这里的 condition 可以是一个接受会话对象,返回布尔值的同步函数,也可以通过 Schema.computed()
让用户提供,实现过滤器与权限的联动。
在控制台中,commands 插件同样支持指令配置条件权限,用法与其他要用到过滤器的地方差不多。
目前暂时不支持用户和用户组条件继承其他权限。
补充:频道权限
同用户一样,频道和群组也是可以有权限的。因此你可以限制某些功能只能在特定频道和群组中触发(当然过滤器也可以实现这一点)。
而过滤器不能实现的在于,权限系统实际上允许你将频道编组(类似用户组)。当我们需要发送广播消息的时候就可以只对部分频道发送了。例如,你可以利用权限系统设置若干广播频道,并只在这些频道中发送订阅信息。
看完之后 赶去重写了权限分配功能 看来以后要多逛逛论坛了