- 适配器只描述平台身份:qq_obv11 的 owner/admin 一律上报 RoleMember,不再上报 RoleOwner - session 层唯一判主点只认白名单(owner.yaml Accounts ∪ 每渠道 adminIds),nil 档案 fail-closed 拒绝 - 新增「每渠道管理员层」:channels.json ChannelAccount.adminIds[] → runtimeconfig.AdminIDs → OwnerProfile.SetPlatformAdmins(整体替换语义,热重载后移除的 id 立即失效) - 组合根接线:启动即播种、面板保存先灌白名单再重载适配器(cmd/cyrene/wire_channels.go + 4 条端到端单测) - 06.4.3 节流与参与度状态按会话独立计数(ConversationKey 三处统一,空会话 GC);06.4.4 未知平台主动发言保守默认 1 条/分钟
99 lines
3.8 KiB
Go
99 lines
3.8 KiB
Go
package session
|
||
|
||
import (
|
||
"git.yeij.top/AskaEth/Cyrene_v2/internal/capability/restrict"
|
||
"git.yeij.top/AskaEth/Cyrene_v2/internal/channel/adapter"
|
||
"git.yeij.top/AskaEth/Cyrene_v2/internal/user"
|
||
)
|
||
|
||
// ResolveActor 把一条归一化入站消息解析成下游权限决策所用的强类型 actor 元数据。
|
||
// 这是 design/11 §0 的结构化主人识别规则(裁决 06.4.2):**owner 车道只认白名单**,
|
||
// 判主依据只有结构化来源——主人档案的账号白名单(owner.yaml,internal/user)与每渠道
|
||
// 账号配置的 adminIds[](channels.json,design/08 §1.1)。
|
||
//
|
||
// **适配器上报的角色标记一律不得单独判主**:平台消息里的 role 字段(OneBot 的群主
|
||
// owner / 群管理员 admin,或私聊帧里伪造的 role=owner)只能当作非主人侧提示,命中
|
||
// owner 也会被降级为 RoleMember。判主永远不看消息正文——正文声称「我是主人」同样无效。
|
||
func ResolveActor(msg adapter.UnifiedMessage, owner *user.OwnerProfile) adapter.Actor {
|
||
actor := adapter.Actor{
|
||
Role: msg.Sender.Role,
|
||
UserID: firstNonEmpty(msg.Sender.UserID, msg.SenderID),
|
||
DisplayName: firstNonEmpty(msg.Sender.DisplayName, msg.SenderName),
|
||
IsGroup: msg.Sender.IsGroup || msg.IsGroup(),
|
||
GroupID: firstNonEmpty(msg.Sender.GroupID, msg.GroupID),
|
||
GroupName: firstNonEmpty(msg.Sender.GroupName, msg.GroupName),
|
||
}
|
||
|
||
// 白名单命中即主人;未命中则不管适配器上报了什么角色,都留在非主人车道
|
||
// (owner.IsOwner 对 nil 档案安全返回 false)。
|
||
if owner.IsOwner(msg.Platform, msg.SenderID) {
|
||
actor.Role = adapter.RoleOwner
|
||
return actor.Resolve()
|
||
}
|
||
actor.Role = nonOwnerRole(actor.Role)
|
||
return actor.Resolve()
|
||
}
|
||
|
||
// nonOwnerRole 把适配器上报的角色收口到非主人车道:owner(群主 / 群管理员 / 伪造
|
||
// 字段)一律降级为 RoleMember;member / system / unknown 原样保留;非法值保守回落
|
||
// RoleMember,避免留下含糊角色。
|
||
func nonOwnerRole(role adapter.Role) adapter.Role {
|
||
switch role {
|
||
case adapter.RoleOwner:
|
||
return adapter.RoleMember
|
||
case adapter.RoleMember, adapter.RoleSystem, adapter.RoleUnknown:
|
||
return role
|
||
default:
|
||
return adapter.RoleMember
|
||
}
|
||
}
|
||
|
||
// EnvFor maps a resolved actor to the restrict.ActorEnv used to gate tools.
|
||
// Group chats always map to group_member (isolated, restricted) even when the
|
||
// owner is the speaker (design/02 §3: owner group speech stays in the isolated
|
||
// group context). A non-group owner maps to owner (full access); a non-group
|
||
// non-owner maps to stranger.
|
||
func EnvFor(actor adapter.Actor) restrict.ActorEnv {
|
||
if actor.IsGroup {
|
||
return restrict.ActorGroupMember
|
||
}
|
||
if actor.IsOwner() {
|
||
return restrict.ActorOwner
|
||
}
|
||
return restrict.ActorStranger
|
||
}
|
||
|
||
// IdentifyContext 仅凭消息自带的渠道元数据推断 restrict.ActorEnv:群聊一律
|
||
// group_member,其余一律 stranger。它拿不到白名单,因此**不能授予 owner 车道**——
|
||
// 适配器上报的 role=owner 在此被降级为 member(裁决 06.4.2,owner 只认白名单)。
|
||
// 真正的判主走 ResolveActor(msg, owner) + EnvFor。
|
||
func IdentifyContext(msg adapter.UnifiedMessage) restrict.ActorEnv {
|
||
actor := msg.Sender.Resolve()
|
||
if msg.IsGroup() {
|
||
actor.IsGroup = true
|
||
}
|
||
actor.Role = nonOwnerRole(actor.Role)
|
||
return EnvFor(actor)
|
||
}
|
||
|
||
// Env returns the restrict actor environment for the session. It mirrors
|
||
// EnvFor: group chats are group_member regardless of membership, owner private
|
||
// is owner, and anything else is stranger.
|
||
func (s Session) Env() restrict.ActorEnv {
|
||
if s.IsGroup {
|
||
return restrict.ActorGroupMember
|
||
}
|
||
if s.Owner {
|
||
return restrict.ActorOwner
|
||
}
|
||
return restrict.ActorStranger
|
||
}
|
||
|
||
// firstNonEmpty returns a when a is non-empty, otherwise b.
|
||
func firstNonEmpty(a, b string) string {
|
||
if a != "" {
|
||
return a
|
||
}
|
||
return b
|
||
}
|