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
@@ -89,10 +89,11 @@ func (s *stubSessions) GetOrCreate(_ context.Context, msg adapter.UnifiedMessage
|
||||
return s.sess, nil
|
||||
}
|
||||
|
||||
// directMessage is an owner-private direct message: the sender is tagged as the
|
||||
// owner (structural, not from text), so it exercises the owner-private path. The
|
||||
// single-account stub session in newTestBridge reports Owner=true to match, since
|
||||
// the production SessionManager derives Owner from the resolved actor.
|
||||
// directMessage 是一条主人私聊消息:platform telegram / 发送者 100。它是否是主人由**白名单**
|
||||
// 决定——夹具的主人档案(testOwnerProfile("telegram", "100"))把这个账号结构性地写进去
|
||||
// (裁决 06.4.2:owner 车道只认白名单)。这里仍带上适配器上报的 role=owner 标记,用来断言
|
||||
// 「白名单命中时适配器标记不影响判主」;反过来,未进白名单的账号即使带该标记也不是主人。
|
||||
// newTestBridge 的单账号 stub session 据此报 Owner=true,与生产 SessionManager 的派生一致。
|
||||
func directMessage() adapter.UnifiedMessage {
|
||||
return adapter.UnifiedMessage{
|
||||
Platform: "telegram",
|
||||
@@ -111,7 +112,9 @@ func newTestBridge(t *testing.T) (*Bridge, *stubChatter, *stubSessions, *fakeAda
|
||||
sess := &stubSessions{sess: session.Session{SessionID: "sess-1", Owner: true, MemWritable: true}}
|
||||
ad := &fakeAdapter{name: "telegram"}
|
||||
|
||||
b, err := New(Options{Loop: chat, Sessions: sess, Owner: &user.OwnerProfile{}})
|
||||
// 主人私聊要进主会话,夹具必须把发送者结构性地写进白名单(owner 车道只认白名单,
|
||||
// 裁决 06.4.2);适配器上报的 RoleOwner 标记本身不再判主。
|
||||
b, err := New(Options{Loop: chat, Sessions: sess, Owner: testOwnerProfile("telegram", "100")})
|
||||
if err != nil {
|
||||
t.Fatalf("New: %v", err)
|
||||
}
|
||||
@@ -311,7 +314,7 @@ func TestOwnerPrivatePersistsToMainSessionWithPlatform(t *testing.T) {
|
||||
hist := &captureHistory{}
|
||||
var persisted []history.Message
|
||||
b, err := New(Options{
|
||||
Loop: chat, Sessions: sess, Owner: &user.OwnerProfile{},
|
||||
Loop: chat, Sessions: sess, Owner: testOwnerProfile("telegram", "100"),
|
||||
History: hist,
|
||||
OnPersisted: func(m history.Message) { persisted = append(persisted, m) },
|
||||
})
|
||||
@@ -358,7 +361,7 @@ func TestOwnerPrivatePersistsToMainWithDeviceID(t *testing.T) {
|
||||
sess := &stubSessions{sess: session.Session{SessionID: "sess-1", Owner: true, MemWritable: true}}
|
||||
hist := &captureHistory{}
|
||||
b, err := New(Options{
|
||||
Loop: chat, Sessions: sess, Owner: &user.OwnerProfile{},
|
||||
Loop: chat, Sessions: sess, Owner: testOwnerProfile("telegram", "100"),
|
||||
History: hist,
|
||||
})
|
||||
if err != nil {
|
||||
@@ -581,7 +584,7 @@ func TestOwnerInGroupIsIsolatedButNotLabeledThirdParty(t *testing.T) {
|
||||
// but the owner is never labeled as a third party (design/16 §4.6 §1-2).
|
||||
sess := &stubSessions{sess: session.Session{SessionID: "sess-g", IsGroup: true, Owner: true, MemWritable: false}}
|
||||
hist := &captureHistory{}
|
||||
b, err := New(Options{Loop: chat, Sessions: sess, Owner: &user.OwnerProfile{}, History: hist})
|
||||
b, err := New(Options{Loop: chat, Sessions: sess, Owner: testOwnerProfile("telegram", "owner_uid"), History: hist})
|
||||
if err != nil {
|
||||
t.Fatalf("New: %v", err)
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user