# 权限系统 `konabot.common.permsys` 本文档面向维护者,说明 `konabot/common/permsys` 模块的职责、数据模型、权限解析规则,以及在插件中接入的推荐方式。 ## 模块目标 `permsys` 提供了一套简单的、可继承的权限系统,用于回答两个问题: 1. 某个事件对应的主体是谁。 2. 该主体是否拥有某项权限。 它适合处理 bot 内部的功能开关、管理权限、平台级授权等场景。 当前模块由以下几部分组成: - `konabot/common/permsys/__init__.py` - 暴露 `PermManager`、`DepPermManager`、`require_permission` - 负责数据库初始化、启动迁移、超级管理员默认授权 - 提供 `register_default_allow_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` 是权限系统中的最小主体标识: ```python PermEntity(platform: str, entity_type: str, external_id: str) ``` 示例: - `PermEntity("sys", "global", "global")` - `PermEntity("ob11", "group", "123456")` - `PermEntity("ob11", "user", "987654")` 其中: - `platform` 表示来源平台,如 `sys`、`ob11`、`discord` - `entity_type` 表示主体类型,如 `global`、`group`、`user` - `external_id` 表示平台侧的外部标识 ### 2. 实体链 权限判断不是只看单个实体,而是看一条“实体链”。 以 `get_entity_chain_of_entity()` 为例,传入一个具体实体时,返回的链为: ```python [ 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 账号写入: ```python PermEntity("ob11", "user", str(account)), "*", True ``` 也就是说,配置中的超级管理员会直接拥有全部权限。 此外,模块也支持插件在导入阶段通过 `register_default_allow_permission("some.key")` 注册默认放行的权限键;这些键会在启动时被写入到: ```python PermEntity("sys", "global", "global"), "some.key", True ``` 这适合“默认所有人可用,但仍希望后续能被权限系统单独关闭”的功能。 这属于启动时自动灌入的保底策略,不依赖手工授权命令。 ## 在插件中使用 ### 1. 直接做权限检查 ```python 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 上做准入控制 ```python 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. 更新权限 ```python 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__.py` - `konabot/common/permsys/entity.py` - `konabot/common/permsys/repo.py` - `konabot/plugins/perm_manage/__init__.py`