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 条/分钟
This commit is contained in:
1 parent
7e7f53bb41
commit
af8591b78a
30 files changed
+1289
-144
No files matched your search
@@ -2,21 +2,24 @@ package qq_obv11
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
const rlKeyA = "qq-obv11|100|100"
|
||||
|
||||
func TestRateLimiterAllowWithinWindow(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
rl := newRateLimiter(3, 10*time.Second)
|
||||
now := time.Now()
|
||||
for i := 0; i < 3; i++ {
|
||||
if !rl.allow(now) {
|
||||
if !rl.allow(rlKeyA, now) {
|
||||
t.Fatalf("allow #%d denied, want allowed", i)
|
||||
}
|
||||
}
|
||||
if rl.allow(now) {
|
||||
if rl.allow(rlKeyA, now) {
|
||||
t.Error("4th allow in window accepted, want denied")
|
||||
}
|
||||
}
|
||||
@@ -26,24 +29,78 @@ func TestRateLimiterDeniedOverWindow(t *testing.T) {
|
||||
|
||||
rl := newRateLimiter(2, 10*time.Second)
|
||||
base := time.Now()
|
||||
if !rl.allow(base) || !rl.allow(base) {
|
||||
if !rl.allow(rlKeyA, base) || !rl.allow(rlKeyA, base) {
|
||||
t.Fatal("first two sends should be allowed")
|
||||
}
|
||||
if rl.allow(base) {
|
||||
if rl.allow(rlKeyA, base) {
|
||||
t.Error("third send within window allowed, want denied")
|
||||
}
|
||||
// After the window elapses, the budget frees up.
|
||||
if !rl.allow(base.Add(11 * time.Second)) {
|
||||
if !rl.allow(rlKeyA, base.Add(11*time.Second)) {
|
||||
t.Error("send after window elapsed denied, want allowed")
|
||||
}
|
||||
}
|
||||
|
||||
// TestRateLimiterSessionsIndependent 是裁决 06.4.3 的断言:额度按会话(群聊/私聊)
|
||||
// 独立计数,一个群用尽自己的额度不会挤占另一个群,也不影响私聊。
|
||||
func TestRateLimiterSessionsIndependent(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
rl := newRateLimiter(2, 10*time.Second)
|
||||
now := time.Now()
|
||||
const (
|
||||
groupB = "qq-obv11|200|200"
|
||||
peer = "qq-obv11|900|900"
|
||||
)
|
||||
|
||||
for i := 0; i < 2; i++ {
|
||||
if !rl.allow(rlKeyA, now) {
|
||||
t.Fatalf("group A send #%d denied, want allowed", i+1)
|
||||
}
|
||||
}
|
||||
if rl.allow(rlKeyA, now) {
|
||||
t.Fatal("group A exceeded its own quota, want denied")
|
||||
}
|
||||
for i := 0; i < 2; i++ {
|
||||
if !rl.allow(groupB, now) {
|
||||
t.Fatalf("group B send #%d denied by group A's quota — 会话计数未隔离", i+1)
|
||||
}
|
||||
if !rl.allow(peer, now) {
|
||||
t.Fatalf("peer send #%d denied by another session's quota — 会话计数未隔离", i+1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestRateLimiterPrunesIdleSessions 保证长跑进程里会话键不会无界增长:键数超过阈值后,
|
||||
// 窗口内已无记录的会话会被回收,新会话照常放行。
|
||||
func TestRateLimiterPrunesIdleSessions(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
rl := newRateLimiter(1, 10*time.Second)
|
||||
base := time.Now()
|
||||
for i := 0; i < sendSessionsGCThreshold+1; i++ {
|
||||
if !rl.allow("sess-"+strconv.Itoa(i), base) {
|
||||
t.Fatalf("session %d denied, want allowed", i)
|
||||
}
|
||||
}
|
||||
later := base.Add(time.Minute)
|
||||
if !rl.allow("fresh", later) {
|
||||
t.Fatal("fresh session denied after window roll")
|
||||
}
|
||||
rl.mu.Lock()
|
||||
keys := len(rl.times)
|
||||
rl.mu.Unlock()
|
||||
if keys > sendSessionsGCThreshold {
|
||||
t.Fatalf("idle sessions not pruned: %d keys remain", keys)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRateLimiterDisabledAllowsAll(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
rl := newRateLimiter(0, 10*time.Second)
|
||||
for i := 0; i < 100; i++ {
|
||||
if !rl.allow(time.Now()) {
|
||||
if !rl.allow(rlKeyA, time.Now()) {
|
||||
t.Fatalf("disabled limiter denied send #%d", i)
|
||||
}
|
||||
}
|
||||
@@ -53,7 +110,7 @@ func TestRateLimiterNilAllows(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var rl *rateLimiter
|
||||
if !rl.allow(time.Now()) {
|
||||
if !rl.allow(rlKeyA, time.Now()) {
|
||||
t.Error("nil limiter denied send, want allowed")
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user