权限系统
UID 超级用户、身份组、命令授权与受保护目标拦截。
权限系统
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 超级用户与平台账号映射
超级用户通过 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
每个命令由 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"。
平台运行时角色透传
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() 与菜单可见性路径共用的唯一解析器。
黑名单与受保护目标拦截
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 已过期的行,供需要连接策略表的仓库查询使用。
权限种子与平台角色树
默认身份组从各平台权限模块种子而来。permissions/platforms.py::iter_default_identity_groups() 遍历每个 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 字段动态发现——核心权限层中没有硬编码的模块路径。
权限数据模型
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
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)无法更新或删除;仍有成员关系或授权的组无法删除。
最后更新于