Files
Cyrene_v2/internal/user/admin_layer_test.go
T
AskaEth af8591b78a fix(channel,user): owner 车道只认白名单,堵住群主/伪造 role 冒充主人(裁决 06.4.2/06.4.3/06.4.4)
- 适配器只描述平台身份: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 条/分钟
2026-10-04 12:07:40 +08:00

97 lines
3.1 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package user
// 本文件覆盖裁决 06.4.2 的「每渠道账号配置的管理员 id 列表」白名单层:它是 channels.json
// 的 adminIds[](design/08 §1.1)在内存里的落点,与 owner.yaml 的账号白名单取并集后共同
// 决定 owner 车道,且**不写回 owner.yaml**。
import (
"os"
"path/filepath"
"strings"
"testing"
)
func TestOwnerProfileSetPlatformAdmins(t *testing.T) {
t.Parallel()
p := &OwnerProfile{ID: "owner", DisplayName: "主人"}
p.SetPlatformAdmins("qq-obv11", []string{"10001", " 10002 ", "10001", " "})
ids := p.PlatformAdmins("qq-obv11")
if len(ids) != 2 || ids[0] != "10001" || ids[1] != "10002" {
t.Fatalf("PlatformAdmins = %v, want [10001 10002](去空白 + 去重)", ids)
}
for _, id := range ids {
if !p.IsOwner("qq-obv11", id) {
t.Errorf("adminIds 命中的 %q 未判为主人", id)
}
}
if p.IsOwner("qq-obv11", "10003") {
t.Error("未声明的账号被判为主人")
}
if p.IsOwner("telegram", "10001") {
t.Error("管理员 id 层跨平台串台了(应按平台隔离)")
}
if p.IsOwner("qq-obv11", "") {
t.Error("空 accountId 不得命中白名单(fail-closed)")
}
// 整体替换:重新声明后旧 id 立即失效,channels.json 热重载不会留下悬挂管理员。
p.SetPlatformAdmins("qq-obv11", []string{"10002"})
if p.IsOwner("qq-obv11", "10001") {
t.Error("SetPlatformAdmins 未整体替换")
}
// 空列表(含只有空白的列表)清空该平台的这一层。
p.SetPlatformAdmins("qq-obv11", []string{" "})
if p.IsOwner("qq-obv11", "10002") || p.PlatformAdmins("qq-obv11") != nil {
t.Error("空列表应清空该平台的管理员层")
}
}
func TestOwnerProfileAdminLayerConcurrentWithOwnerYaml(t *testing.T) {
t.Parallel()
// 两层来源共存:owner.yaml 的账号白名单与 channels.json 的 adminIds 都能判主。
p := &OwnerProfile{
ID: "owner",
DisplayName: "主人",
Accounts: []OwnerAccount{{Platform: "kook", AccountID: "329284282"}},
}
p.SetPlatformAdmins("kook", []string{"555"})
if !p.IsOwner("kook", "329284282") || !p.IsOwner("kook", "555") {
t.Fatalf("两层白名单未取并集: yaml=%v adminIds=%v",
p.IsOwner("kook", "329284282"), p.IsOwner("kook", "555"))
}
}
func TestOwnerProfileAdminLayerNotPersisted(t *testing.T) {
t.Parallel()
path := filepath.Join(t.TempDir(), "owner.yaml")
p := &OwnerProfile{ID: "owner", DisplayName: "主人"}
p.RegisterAccount("kook", "329284282")
p.SetPlatformAdmins("qq-obv11", []string{"10001"})
if err := SaveOwnerProfile(path, p); err != nil {
t.Fatalf("SaveOwnerProfile: %v", err)
}
data, err := os.ReadFile(path)
if err != nil {
t.Fatalf("read %s: %v", path, err)
}
if strings.Contains(string(data), "10001") {
t.Error("adminIds 层被写回了 owner.yaml(应为只读来源 channels.json)")
}
reloaded, err := LoadOwnerProfile(path)
if err != nil {
t.Fatalf("LoadOwnerProfile: %v", err)
}
if !reloaded.IsOwner("kook", "329284282") {
t.Error("owner.yaml 的账号白名单应在重载后保留")
}
if reloaded.IsOwner("qq-obv11", "10001") {
t.Error("adminIds 层不应来自 owner.yaml")
}
}