Files
konabot/docs/permsys.md

6.6 KiB
Raw Blame History

权限系统 konabot.common.permsys

本文档面向维护者,说明 konabot/common/permsys 模块的职责、数据模型、权限解析规则,以及在插件中接入的推荐方式。

模块目标

permsys 提供了一套简单的、可继承的权限系统,用于回答两个问题:

  1. 某个事件对应的主体是谁。
  2. 该主体是否拥有某项权限。

它适合处理 bot 内部的功能开关、管理权限、平台级授权等场景。

当前模块由以下几部分组成:

  • konabot/common/permsys/__init__.py
    • 暴露 PermManagerDepPermManagerrequire_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 表示来源平台,如 sysob11discord
  • entity_type 表示主体类型,如 globalgroupuser
  • external_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. 权限键

权限键使用点分结构,例如:

  • admin
  • plugin.weather
  • plugin.weather.use

检查时会自动做前缀回退。以 plugin.weather.use 为例,查询顺序是:

  1. plugin.weather.use
  2. plugin.weather
  3. plugin
  4. *

因此,* 可以看作兜底总权限。

权限解析规则

PermManager.check_has_permission_info() 的逻辑可以概括为:

  1. 先把输入转换成实体链。
  2. 对权限键做逐级回退,同时追加 *
  3. 在数据库中批量查出链上所有实体、所有候选键的显式记录。
  4. 按“实体越具体越优先、权限键越具体越优先”的顺序,返回第一条命中的记录。

若没有任何显式记录:

  • check_has_permission_info() 返回 None
  • check_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.xxxplugin.xxx.action
  • 不推荐:含糊的单词或临时字符串

这样才能利用前缀回退机制做批量授权。

输入安全

虽然这个项目偏内部使用,但权限键、实体类型、外部 ID 仍然应视为不可信输入:

  • 不要把聊天输入直接拼到 SQL 中
  • 不要让任意用户可随意构造高权限写入
  • 对可写命令至少做权限保护和必要校验

改动兼容性

以下改动都可能影响全局权限行为,修改前应充分评估:

  • 更改实体链顺序
  • 更改默认兜底键 * 的语义
  • 更改 None 的处理方式
  • 更改启动时超级管理员注入逻辑

调试建议

  • 先用 konaperm get ... 确认某个权限最终命中了哪一层
  • 再用 konaperm list ... 查看该实体链上有哪些显式记录
  • 若表现异常,检查是否是更上层实体或更宽泛权限键提前命中

相关文件

  • konabot/common/permsys/__init__.py
  • konabot/common/permsys/entity.py
  • konabot/common/permsys/repo.py
  • konabot/plugins/perm_manage/__init__.py