6.6 KiB
权限系统 konabot.common.permsys
本文档面向维护者,说明 konabot/common/permsys 模块的职责、数据模型、权限解析规则,以及在插件中接入的推荐方式。
模块目标
permsys 提供了一套简单的、可继承的权限系统,用于回答两个问题:
- 某个事件对应的主体是谁。
- 该主体是否拥有某项权限。
它适合处理 bot 内部的功能开关、管理权限、平台级授权等场景。
当前模块由以下几部分组成:
konabot/common/permsys/__init__.py- 暴露
PermManager、DepPermManager、require_permission - 负责数据库初始化、启动迁移、超级管理员默认授权
- 暴露
konabot/common/permsys/entity.py- 定义
PermEntity - 将事件转换为可查询的实体链
- 定义
konabot/common/permsys/repo.py- 封装 SQLite 读写
konabot/common/permsys/migrates/- 存放迁移 SQL
konabot/common/permsys/sql/- 存放查询与更新 SQL
核心概念
1. PermEntity
PermEntity 是权限系统中的最小主体标识:
PermEntity(platform: str, entity_type: str, external_id: str)
示例:
PermEntity("sys", "global", "global")PermEntity("ob11", "group", "123456")PermEntity("ob11", "user", "987654")
其中:
platform表示来源平台,如sys、ob11、discordentity_type表示主体类型,如global、group、userexternal_id表示平台侧的外部标识
2. 实体链
权限判断不是只看单个实体,而是看一条“实体链”。
以 get_entity_chain_of_entity() 为例,传入一个具体实体时,返回的链为:
[
PermEntity(platform, entity_type, external_id),
PermEntity(platform, "global", "global"),
PermEntity("sys", "global", "global"),
]
这意味着权限会优先读取更具体的主体,再回退到平台全局,最后回退到系统全局。
get_entity_chain(event) 则会根据事件类型自动构造链。例如:
- OneBot V11 群消息:用户 -> 群 -> 平台全局 -> 系统全局
- OneBot V11 私聊:用户 -> 平台全局 -> 系统全局
- Discord 频道消息:用户/频道/服务器 -> 平台全局 -> 系统全局
- Console:控制台用户/频道 -> 平台全局 -> 系统全局
注意:当前 entity.py 中的具体链顺序与字段命名应以实现为准;修改这里时要评估现有权限继承是否会被破坏。
3. 权限键
权限键使用点分结构,例如:
adminplugin.weatherplugin.weather.use
检查时会自动做前缀回退。以 plugin.weather.use 为例,查询顺序是:
plugin.weather.useplugin.weatherplugin*
因此,* 可以看作兜底总权限。
权限解析规则
PermManager.check_has_permission_info() 的逻辑可以概括为:
- 先把输入转换成实体链。
- 对权限键做逐级回退,同时追加
*。 - 在数据库中批量查出链上所有实体、所有候选键的显式记录。
- 按“实体越具体越优先、权限键越具体越优先”的顺序,返回第一条命中的记录。
若没有任何显式记录:
check_has_permission_info()返回Nonecheck_has_permission()返回False
这表示本系统默认是“未授权即拒绝”。
数据存储
模块使用 SQLite,默认数据库文件位于:
data/perm.sqlite3
启动时会执行迁移:
create_startup()在 NoneBot 启动事件中调用execute_migration()
权限值支持三态:
True:显式允许False:显式拒绝None:删除/清空该层的显式设置,让判断重新回退到继承链
repo.py 中的 update_perm_info() 会将这个三态直接写入数据库。
超级管理员注入
在启动阶段,create_startup() 会读取 konabot.common.nb.is_admin.cfg.admin_qq_account,并为这些 QQ 账号写入:
PermEntity("ob11", "user", str(account)), "*", True
也就是说,配置中的超级管理员会直接拥有全部权限。
这属于启动时自动灌入的保底策略,不依赖手工授权命令。
在插件中使用
1. 直接做权限检查
from konabot.common.permsys import DepPermManager
async def handler(pm: DepPermManager, event):
ok = await pm.check_has_permission(event, "plugin.example.use")
if not ok:
return
适合需要在处理流程中动态决定权限键的场景。
2. 挂到 Rule 上做准入控制
from nonebot_plugin_alconna import Alconna, on_alconna
from konabot.common.permsys import require_permission
cmd = on_alconna(
Alconna("example"),
rule=require_permission("plugin.example.use"),
)
适合命令入口明确、未通过时直接拦截的场景。
3. 更新权限
from konabot.common.permsys import DepPermManager
from konabot.common.permsys.entity import PermEntity
await pm.update_permission(
PermEntity("ob11", "group", "123456"),
"plugin.example.use",
True,
)
建议只在专门的管理插件中开放写权限,避免普通功能插件到处分散改表。
perm_manage 插件与本模块的关系
konabot/plugins/perm_manage/__init__.py 是本模块当前的管理入口,提供:
konaperm list:列出实体链上已有的显式权限记录konaperm get:查看某个权限最终命中的记录konaperm set:写入 allow/deny/null
这个插件本身使用 require_permission("admin") 保护,因此只有拥有 admin 权限的主体才能管理权限。
接入建议
权限键命名
建议使用稳定、可扩展的分层键名:
- 推荐:
plugin.xxx、plugin.xxx.action - 不推荐:含糊的单词或临时字符串
这样才能利用前缀回退机制做批量授权。
输入安全
虽然这个项目偏内部使用,但权限键、实体类型、外部 ID 仍然应视为不可信输入:
- 不要把聊天输入直接拼到 SQL 中
- 不要让任意用户可随意构造高权限写入
- 对可写命令至少做权限保护和必要校验
改动兼容性
以下改动都可能影响全局权限行为,修改前应充分评估:
- 更改实体链顺序
- 更改默认兜底键
*的语义 - 更改
None的处理方式 - 更改启动时超级管理员注入逻辑
调试建议
- 先用
konaperm get ...确认某个权限最终命中了哪一层 - 再用
konaperm list ...查看该实体链上有哪些显式记录 - 若表现异常,检查是否是更上层实体或更宽泛权限键提前命中
相关文件
konabot/common/permsys/__init__.pykonabot/common/permsys/entity.pykonabot/common/permsys/repo.pykonabot/plugins/perm_manage/__init__.py