跳转到内容

权限系统

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 IdentityUserPlatformAccountPlatformIdentityGroupIdentityMembershipPermissionGrant 的 ORM CRUD

超级用户通过 LINGCHU_SUPERUSERS 环境键(NoneBot 配置别名 lingchu_superusers)声明。值是从 Lingchu UID 到一个或多个平台账号绑定的 JSON 映射:

{
"alice": { "qq": 10001 },
"bob": { "qq": 10002 }
}

启动时,permissions/bootstrap.py 中的 validate_and_seed_permission_system() 会:

  1. runtime_config.lingchu_superusers 读取配置映射。
  2. 将每个 UID 和平台账号 ID 规范化为字符串(QQ 账号校验为正整数)。
  3. 校验每个 platform_id 是否为已知平台 profile,且每个 (platform_id, account_id) 对未被重复绑定。
  4. 将每个 UID upsert 到 IdentityUser,添加到 system.superusers 组(source 为 superusers_config),并通过 bind_platform_account() 绑定每个平台账号。
  5. 将所有 MENU_FEATURES.command_key 授权给 system.superusers 组,使超级用户可执行所有命令。

如果 LINGCHU_SUPERUSERS 缺失或为空,启动会抛出 PermissionConfigError 并中止。

每个命令由 handle/menu.pyMenuFeature 上声明的稳定 command_key 标识。同一个 command_key 是以下场景的共享标识:

  • 权限检查(check_permission(command_key, bot, event)
  • 菜单过滤(allowed_command_keys() 隐藏当前身份无法执行的命令)
  • 处理器装饰器与触发词注册
  • lingchu_permission_grants 表中的权限授权

permissions/service.py 中的 check_permission_for_context() 按以下短路顺序解析 PermissionDecision

  1. context.uidNoneallowed=False, reason="anonymous"
  2. repo.is_superuser(uid)allowed=True, reason="superuser"
  3. 否则,从运行时透传(启用时)加上匹配的 IdentityMembership 行收集有效身份组,通过 parent_group_id 展开到祖先组,查询 effect == "allow"PermissionGrant 行。
  4. 若任意授权匹配 → 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 已过期的行,供需要连接策略表的仓库查询使用。

受保护用户自动恢复(OneBot V11)

Section titled “受保护用户自动恢复(OneBot V11)”

在 OneBot V11 适配器上,被禁言的受保护用户会被自动再次解禁。handle/qq/adapters/onebot11/default/protect_notice.py 监听 GroupBanNoticeEventsub_type == "ban" 且时长为正),当被禁言用户命中群级或全局 protected 策略时,调用 set_group_ban(group_id, user_id, duration=0)。这覆盖了人类管理员在 QQ 客户端发起的禁言,而不仅是 bot 自己的命令。监听器始终生效,不受开机/关机门禁影响。

例外情况:

  • 全体禁言期间:群级全体禁言时无法单独解禁个人,因此当 bot 自身在该群也被禁言时跳过恢复。
  • 超级用户覆盖:超级用户禁言非超级用户的受保护成员会强制生效、不自动恢复;禁言同为超级用户的受保护用户仍会恢复。

默认身份组从各平台权限模块种子而来,但只种子已启用平台的模块。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() 只导入事件来源平台对应的权限模块,且仅在该平台适配器已启用时导入。

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_idbuiltin 标志
lingchu_identity_memberships IdentityMembership UID → 组成员关系,可按 scope_type / scope_id 限定作用域
lingchu_permission_grants PermissionGrant 从组到 command_key 的 allow-list 授权

SUPERUSERS_GROUP_ID = "system.superusers" 是唯一不按平台作用域的内置组;它由 bootstrap.py 而非平台模块种子。

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)无法更新或删除;仍有成员关系或授权的组无法删除。