权限系统
Lingchu Bot 采用分层权限系统,融合 Lingchu 全局 UID 身份、平台账号绑定、身份组成员关系,以及按命令的 allow-list 授权。本页描述运行时如何解析权限、超级用户如何配置,以及黑名单和受保护目标策略如何对破坏性命令设防。
| 层 | 文件 | 职责 |
|---|---|---|
| 启动引导 | permissions/bootstrap.py |
校验 LINGCHU_SUPERUSERS、种子默认身份组、启动时同步超级用户成员关系与命令授权 |
| 服务 | permissions/service.py |
解析 PermissionContext、执行 check_permission、展开运行时与成员组、查询授权 |
| 管理 API | permissions/admin.py |
仅超级用户可调用:创建/更新/删除身份组、添加/移除成员 |
| 目标策略 | permissions/subject_policy.py |
黑名单与受保护目标条目,带过期的活跃策略查询 |
| 平台集成 | permissions/platforms.py |
通过 PlatformProfile.permission_module 发现平台权限模块,解析运行时身份组 |
| 仓库 | repositories/permissions.py |
对 IdentityUser、PlatformAccount、PlatformIdentityGroup、IdentityMembership、PermissionGrant 的 ORM CRUD |
UID 超级用户与平台账号映射
Section titled “UID 超级用户与平台账号映射”超级用户通过 LINGCHU_SUPERUSERS 环境键(NoneBot 配置别名 lingchu_superusers)声明。值是从 Lingchu UID 到一个或多个平台账号绑定的 JSON 映射:
{ "alice": { "qq": 10001 }, "bob": { "qq": 10002 }}启动时,permissions/bootstrap.py 中的 validate_and_seed_permission_system() 会:
- 从
runtime_config.lingchu_superusers读取配置映射。 - 将每个 UID 和平台账号 ID 规范化为字符串(QQ 账号校验为正整数)。
- 校验每个
platform_id是否为已知平台 profile,且每个(platform_id, account_id)对未被重复绑定。 - 将每个 UID upsert 到
IdentityUser,添加到system.superusers组(source 为superusers_config),并通过bind_platform_account()绑定每个平台账号。 - 将所有
MENU_FEATURES.command_key授权给system.superusers组,使超级用户可执行所有命令。
如果 LINGCHU_SUPERUSERS 缺失或为空,启动会抛出 PermissionConfigError 并中止。
命令授权与 MENU_FEATURES.command_key
Section titled “命令授权与 MENU_FEATURES.command_key”每个命令由 handle/menu.py 中 MenuFeature 上声明的稳定 command_key 标识。同一个 command_key 是以下场景的共享标识:
- 权限检查(
check_permission(command_key, bot, event)) - 菜单过滤(
allowed_command_keys()隐藏当前身份无法执行的命令) - 处理器装饰器与触发词注册
lingchu_permission_grants表中的权限授权
permissions/service.py 中的 check_permission_for_context() 按以下短路顺序解析 PermissionDecision:
- 若
context.uid为None→allowed=False, reason="anonymous"。 - 若
repo.is_superuser(uid)→allowed=True, reason="superuser"。 - 否则,从运行时透传(启用时)加上匹配的
IdentityMembership行收集有效身份组,通过parent_group_id展开到祖先组,查询effect == "allow"的PermissionGrant行。 - 若任意授权匹配 →
allowed=True, reason="granted";否则 →allowed=False, reason="missing_grant"。
平台运行时角色透传
Section titled “平台运行时角色透传”permission_platform_runtime_passthrough 控制平台解析的运行时角色(例如 QQ 群主/管理员/成员)是否参与权限检查。它接受全局布尔值或按平台的映射:
permission_platform_runtime_passthrough = true# 或按平台:# permission_platform_runtime_passthrough = { qq = true }当当前 platform_id 启用透传时,平台模块(见 permissions/platforms.py::resolve_runtime_identity_groups)返回的运行时身份组会被加入直接组集合,再进行授权查询。禁用时,仅显式 IdentityMembership 行生效。默认值为 True;按平台条目缺失时也回退到 True。
permissions/service.py 中的 platform_runtime_passthrough_enabled() 是 check_permission_for_context() 与菜单可见性路径共用的唯一解析器。
黑名单与受保护目标拦截
Section titled “黑名单与受保护目标拦截”protected_subject_feature_keys 是必须先经过目标策略校验才能执行的 command_key 允许列表。默认覆盖破坏性操作:
protected_subject_feature_keys = [ "kick_member", "block_member", "global_block_member", "member_mute", "recall_message", "set_member_card", "set_member_title", "set_member_admin", "unset_member_admin", "remote_kick", "remote_block", "remote_mute",]目标策略存储在 lingchu_subject_policy 表中,通过 permissions/subject_policy.py 暴露:
| 策略类型 | 效果 |
|---|---|
blocked |
目标用户在黑名单中;受保护命令被拒绝 |
protected |
目标用户在保护名单中;针对其的破坏性命令被拒绝 |
find_active_subject_policy() 先查 global 作用域,再查 group 作用域,并惰性删除已过期条目。active_subject_policy_condition() 返回一个 SQLAlchemy 过滤器,排除 expires_at 已过期的行,供需要连接策略表的仓库查询使用。
受保护用户自动恢复(OneBot V11)
Section titled “受保护用户自动恢复(OneBot V11)”在 OneBot V11 适配器上,被禁言的受保护用户会被自动再次解禁。handle/qq/adapters/onebot11/default/protect_notice.py 监听 GroupBanNoticeEvent(sub_type == "ban" 且时长为正),当被禁言用户命中群级或全局 protected 策略时,调用 set_group_ban(group_id, user_id, duration=0)。这覆盖了人类管理员在 QQ 客户端发起的禁言,而不仅是 bot 自己的命令。监听器始终生效,不受开机/关机门禁影响。
例外情况:
- 全体禁言期间:群级全体禁言时无法单独解禁个人,因此当 bot 自身在该群也被禁言时跳过恢复。
- 超级用户覆盖:超级用户禁言非超级用户的受保护成员会强制生效、不自动恢复;禁言同为超级用户的受保护用户仍会恢复。
权限种子与平台角色树
Section titled “权限种子与平台角色树”默认身份组从各平台权限模块种子而来,但只种子已启用平台的模块。permissions/platforms.py::iter_default_identity_groups() 仅遍历 LINGCHUAdapter 选中(通过 iter_enabled_profiles())的 PlatformProfile.permission_module 条目,并调用其 get_default_identity_groups()。未配置适配器的平台既不会被导入也不会被种子,因此无需为不使用的平台安装适配器。若之后启用某平台,其身份组会在下次启动时补种。
对于 QQ,platforms/qq/permissions.py 声明 PLATFORM_ID = "qq" 并种子以下树:
| 组 ID | 父组 | 显示名 |
|---|---|---|
qq.group |
— | QQ群聊 |
qq.group.owner |
qq.group |
QQ群主 |
qq.group.admin |
qq.group |
QQ群管理员 |
qq.group.member |
qq.group |
QQ群成员 |
qq.friend |
— | QQ好友 |
qq.channel |
— | QQ频道 |
qq.bot |
— | QQ机器人 |
qq.device |
— | QQ设备 |
运行时,resolve_runtime_identity_groups() 将 OneBot V11 发送者角色(owner / admin / member)映射到对应 qq.group.* 组 ID。若事件中缺少角色,会回退到 bot.call_api("get_group_member_info", ...),API 失败时最终回退到 member 角色。
平台权限模块通过适配器注册表的 PlatformProfile.permission_module 字段动态发现——核心权限层中没有硬编码的模块路径。resolve_runtime_identity_groups() 只导入事件来源平台对应的权限模块,且仅在该平台适配器已启用时导入。
权限数据模型
Section titled “权限数据模型”ORM 模型位于 database/models/identity.py,使用 database/_dialect_compat.py 中的跨方言兼容类型:
| 表 | 模型 | 用途 |
|---|---|---|
lingchu_identity_users |
IdentityUser |
Lingchu 全局 UID,可选昵称 |
lingchu_platform_accounts |
PlatformAccount |
UID 与 (platform_id, account_id) 对的绑定 |
lingchu_platform_identity_groups |
PlatformIdentityGroup |
平台作用域组,可选 parent_group_id 与 builtin 标志 |
lingchu_identity_memberships |
IdentityMembership |
UID → 组成员关系,可按 scope_type / scope_id 限定作用域 |
lingchu_permission_grants |
PermissionGrant |
从组到 command_key 的 allow-list 授权 |
SUPERUSERS_GROUP_ID = "system.superusers" 是唯一不按平台作用域的内置组;它由 bootstrap.py 而非平台模块种子。
管理 API
Section titled “管理 API”permissions/admin.py 暴露仅超级用户可调用的变更操作。每个函数首先调用 assert_superuser(),若执行者非超级用户则抛出 PermissionDeniedError:
create_platform_identity_group()/update_platform_identity_group()/delete_platform_identity_group()add_identity_group_member()/remove_identity_group_member()/list_identity_group_members()
内置组(builtin=True)无法更新或删除;仍有成员关系或授权的组无法删除。