diff --git a/shortcuts/vc/vc_meeting_events.go b/shortcuts/vc/vc_meeting_events.go index b8d2eec65..e1c4dbe53 100644 --- a/shortcuts/vc/vc_meeting_events.go +++ b/shortcuts/vc/vc_meeting_events.go @@ -52,7 +52,8 @@ var VCMeetingEvents = common.Shortcut{ Command: "+meeting-events", Description: "List meeting events by meeting ID", Risk: "read", - Scopes: []string{"vc:meeting.meetingevent:read"}, + UserScopes: []string{"vc:meeting.meetingevent:read"}, + BotScopes: []string{"vc:meeting.bot.join:write"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, Flags: []common.Flag{ diff --git a/shortcuts/vc/vc_meeting_events_test.go b/shortcuts/vc/vc_meeting_events_test.go index 65edfa23e..2360a03c1 100644 --- a/shortcuts/vc/vc_meeting_events_test.go +++ b/shortcuts/vc/vc_meeting_events_test.go @@ -418,6 +418,24 @@ func TestMeetingEvents_Validation_PageAllIgnoresInvalidPageSize(t *testing.T) { } } +func TestMeetingEvents_ScopesMatchIdentityPermissions(t *testing.T) { + userScopes := []string{"vc:meeting.meetingevent:read"} + botScopes := []string{"vc:meeting.bot.join:write"} + + if got := VCMeetingEvents.ScopesForIdentity("user"); !reflect.DeepEqual(got, userScopes) { + t.Fatalf("ScopesForIdentity(user) = %v, want %v", got, userScopes) + } + if got := VCMeetingEvents.ScopesForIdentity("bot"); !reflect.DeepEqual(got, botScopes) { + t.Fatalf("ScopesForIdentity(bot) = %v, want %v", got, botScopes) + } + if got := VCMeetingEvents.DeclaredScopesForIdentity("user"); !reflect.DeepEqual(got, userScopes) { + t.Fatalf("DeclaredScopesForIdentity(user) = %v, want %v", got, userScopes) + } + if got := VCMeetingEvents.DeclaredScopesForIdentity("bot"); !reflect.DeepEqual(got, botScopes) { + t.Fatalf("DeclaredScopesForIdentity(bot) = %v, want %v", got, botScopes) + } +} + func TestMeetingEvents_Validation_InvalidPageSizeReturnsFlagError(t *testing.T) { runtime := newMeetingEventsRuntime() mustSetMeetingEventsFlag(t, runtime, "meeting-id", "7628568141510692381") diff --git a/shortcuts/vc/vc_meeting_list_active.go b/shortcuts/vc/vc_meeting_list_active.go index 0e612523d..143115f7e 100644 --- a/shortcuts/vc/vc_meeting_list_active.go +++ b/shortcuts/vc/vc_meeting_list_active.go @@ -23,7 +23,8 @@ var VCMeetingListActive = common.Shortcut{ Command: "+meeting-list-active", Description: "List active meetings for the current identity or target user", Risk: "read", - Scopes: []string{"vc:meeting.meetingevent:read"}, + UserScopes: []string{"vc:meeting.meetingevent:read"}, + BotScopes: []string{"vc:meeting.bot.join:write"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, Flags: []common.Flag{ diff --git a/shortcuts/vc/vc_meeting_test.go b/shortcuts/vc/vc_meeting_test.go index 60286bd90..b5f9be04c 100644 --- a/shortcuts/vc/vc_meeting_test.go +++ b/shortcuts/vc/vc_meeting_test.go @@ -9,6 +9,7 @@ import ( "encoding/json" "errors" "net/http" + "reflect" "strings" "testing" @@ -608,9 +609,21 @@ func TestMeetingListActive_DryRun_UserIdentity(t *testing.T) { } } -func TestMeetingListActive_ScopeMatchesEventReadPermission(t *testing.T) { - if len(VCMeetingListActive.Scopes) != 1 || VCMeetingListActive.Scopes[0] != "vc:meeting.meetingevent:read" { - t.Fatalf("scopes = %#v, want [vc:meeting.meetingevent:read]", VCMeetingListActive.Scopes) +func TestMeetingListActive_ScopesMatchIdentityPermissions(t *testing.T) { + userScopes := []string{"vc:meeting.meetingevent:read"} + botScopes := []string{"vc:meeting.bot.join:write"} + + if got := VCMeetingListActive.ScopesForIdentity("user"); !reflect.DeepEqual(got, userScopes) { + t.Fatalf("ScopesForIdentity(user) = %v, want %v", got, userScopes) + } + if got := VCMeetingListActive.ScopesForIdentity("bot"); !reflect.DeepEqual(got, botScopes) { + t.Fatalf("ScopesForIdentity(bot) = %v, want %v", got, botScopes) + } + if got := VCMeetingListActive.DeclaredScopesForIdentity("user"); !reflect.DeepEqual(got, userScopes) { + t.Fatalf("DeclaredScopesForIdentity(user) = %v, want %v", got, userScopes) + } + if got := VCMeetingListActive.DeclaredScopesForIdentity("bot"); !reflect.DeepEqual(got, botScopes) { + t.Fatalf("DeclaredScopesForIdentity(bot) = %v, want %v", got, botScopes) } } diff --git a/skills/lark-vc-agent/references/lark-vc-agent-meeting-events.md b/skills/lark-vc-agent/references/lark-vc-agent-meeting-events.md index 2dce99734..35460b0ba 100644 --- a/skills/lark-vc-agent/references/lark-vc-agent-meeting-events.md +++ b/skills/lark-vc-agent/references/lark-vc-agent-meeting-events.md @@ -11,6 +11,21 @@ - `meeting_id` 来自 `+meeting-list-active --as bot --user-id ` 或 `+meeting-join --as bot`:后续读取事件继续 `--as bot`。 - 应用身份下,应用机器人必须在该会中或参会过;应用身份 active meeting 返回的是“目标用户在会中且应用机器人也在会中”的会议,不表示可以读取任意 `meeting_id`。 +## 权限建议 + +本命令当前按身份声明不同权限: + +| 场景 | 推荐权限 | +|------|----------| +| UAT / 用户身份 `--as user` | `vc:meeting.meetingevent:read` | +| TAT / 应用身份 `--as bot` | `vc:meeting.bot.join:write` | + +注意: + +- 这里的 TAT 建议只说明 CLI 当前为应用身份声明了 `vc:meeting.bot.join:write` 这条窄版权限路径,不代表服务端已经把会中事件读取降成“无数据范围风险”的纯读点位。 +- 应用身份是否能真正读到事件,仍取决于应用机器人是否在会中或曾在会中、租户安装、权限可访问的数据范围,以及后端对该会议的可见性判断。 +- 如果你的目标是最小化读权限,优先使用用户身份并开通 `vc:meeting.meetingevent:read`。 + ## 命令 ```bash diff --git a/skills/lark-vc-agent/references/lark-vc-agent-meeting-list-active.md b/skills/lark-vc-agent/references/lark-vc-agent-meeting-list-active.md index f0cd9be08..357b444a1 100644 --- a/skills/lark-vc-agent/references/lark-vc-agent-meeting-list-active.md +++ b/skills/lark-vc-agent/references/lark-vc-agent-meeting-list-active.md @@ -4,6 +4,21 @@ 本 skill 对应 shortcut:`lark-cli vc +meeting-list-active`(调用 `GET /open-apis/vc/v1/bots/user_active_meeting`)。 +## 权限建议 + +本命令当前按身份声明不同权限: + +| 场景 | 推荐权限 | +|------|----------| +| UAT / 用户身份 `--as user` | `vc:meeting.meetingevent:read` | +| TAT / 应用身份 `--as bot` | `vc:meeting.bot.join:write` | + +注意: + +- TAT 这里的 `vc:meeting.bot.join:write` 是窄版 CLI 权限点位支持,不表示服务端已经把 active meeting 可见性变成纯读无数据范围风险模型。 +- 应用身份依然只返回“目标用户在会中且应用机器人也在会中”的会议;如果机器人没进会、数据范围没放开、应用没安装,结果仍可能为空。 +- 如果只是查询当前登录用户自己正在参加的会议,优先用用户身份和 `vc:meeting.meetingevent:read`。 + ## 命令 ```bash