Slack: move group policy behind plugin boundary
This commit is contained in:
parent
889011c08c
commit
9e556f75f5
@ -6,6 +6,7 @@ export * from "./src/blocks-render.js";
|
||||
export * from "./src/http/index.js";
|
||||
export * from "./src/interactive-replies.js";
|
||||
export * from "./src/message-actions.js";
|
||||
export * from "./src/group-policy.js";
|
||||
export * from "./src/sent-thread-cache.js";
|
||||
export * from "./src/targets.js";
|
||||
export * from "./src/threading-tool-context.js";
|
||||
|
||||
@ -20,8 +20,6 @@ import {
|
||||
PAIRING_APPROVED_MESSAGE,
|
||||
projectCredentialSnapshotFields,
|
||||
resolveConfiguredFromRequiredCredentialStatuses,
|
||||
resolveSlackGroupRequireMention,
|
||||
resolveSlackGroupToolPolicy,
|
||||
type ChannelPlugin,
|
||||
type OpenClawConfig,
|
||||
type SlackActionContext,
|
||||
@ -36,6 +34,7 @@ import {
|
||||
import { parseSlackBlocksInput } from "./blocks-input.js";
|
||||
import { createSlackActions } from "./channel-actions.js";
|
||||
import { createSlackWebClient } from "./client.js";
|
||||
import { resolveSlackGroupRequireMention, resolveSlackGroupToolPolicy } from "./group-policy.js";
|
||||
import { isSlackInteractiveRepliesEnabled } from "./interactive-replies.js";
|
||||
import { normalizeAllowListLower } from "./monitor/allow-list.js";
|
||||
import type { SlackProbe } from "./probe.js";
|
||||
|
||||
55
extensions/slack/src/group-policy.test.ts
Normal file
55
extensions/slack/src/group-policy.test.ts
Normal file
@ -0,0 +1,55 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { resolveSlackGroupRequireMention, resolveSlackGroupToolPolicy } from "./group-policy.js";
|
||||
|
||||
const cfg = {
|
||||
channels: {
|
||||
slack: {
|
||||
botToken: "xoxb-test",
|
||||
appToken: "xapp-test",
|
||||
channels: {
|
||||
alerts: {
|
||||
requireMention: false,
|
||||
tools: { allow: ["message.send"] },
|
||||
toolsBySender: {
|
||||
"id:user:alice": { allow: ["sessions.list"] },
|
||||
},
|
||||
},
|
||||
"*": {
|
||||
requireMention: true,
|
||||
tools: { deny: ["exec"] },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
// oxlint-disable-next-line typescript/no-explicit-any
|
||||
} as any;
|
||||
|
||||
describe("slack group policy", () => {
|
||||
it("uses matched channel requireMention and wildcard fallback", () => {
|
||||
expect(resolveSlackGroupRequireMention({ cfg, groupChannel: "#alerts" })).toBe(false);
|
||||
expect(resolveSlackGroupRequireMention({ cfg, groupChannel: "#missing" })).toBe(true);
|
||||
});
|
||||
|
||||
it("resolves sender override, then channel tools, then wildcard tools", () => {
|
||||
const senderOverride = resolveSlackGroupToolPolicy({
|
||||
cfg,
|
||||
groupChannel: "#alerts",
|
||||
senderId: "user:alice",
|
||||
});
|
||||
expect(senderOverride).toEqual({ allow: ["sessions.list"] });
|
||||
|
||||
const channelTools = resolveSlackGroupToolPolicy({
|
||||
cfg,
|
||||
groupChannel: "#alerts",
|
||||
senderId: "user:bob",
|
||||
});
|
||||
expect(channelTools).toEqual({ allow: ["message.send"] });
|
||||
|
||||
const wildcardTools = resolveSlackGroupToolPolicy({
|
||||
cfg,
|
||||
groupChannel: "#missing",
|
||||
senderId: "user:bob",
|
||||
});
|
||||
expect(wildcardTools).toEqual({ deny: ["exec"] });
|
||||
});
|
||||
});
|
||||
74
extensions/slack/src/group-policy.ts
Normal file
74
extensions/slack/src/group-policy.ts
Normal file
@ -0,0 +1,74 @@
|
||||
import {
|
||||
resolveToolsBySender,
|
||||
type GroupToolPolicyBySenderConfig,
|
||||
type GroupToolPolicyConfig,
|
||||
} from "openclaw/plugin-sdk/channel-policy";
|
||||
import { type ChannelGroupContext } from "openclaw/plugin-sdk/channel-runtime";
|
||||
import { normalizeHyphenSlug } from "openclaw/plugin-sdk/core";
|
||||
import { inspectSlackAccount } from "./account-inspect.js";
|
||||
|
||||
type SlackChannelPolicyEntry = {
|
||||
requireMention?: boolean;
|
||||
tools?: GroupToolPolicyConfig;
|
||||
toolsBySender?: GroupToolPolicyBySenderConfig;
|
||||
};
|
||||
|
||||
function resolveSlackChannelPolicyEntry(
|
||||
params: ChannelGroupContext,
|
||||
): SlackChannelPolicyEntry | undefined {
|
||||
const account = inspectSlackAccount({
|
||||
cfg: params.cfg,
|
||||
accountId: params.accountId,
|
||||
});
|
||||
const channels = (account.channels ?? {}) as Record<string, SlackChannelPolicyEntry>;
|
||||
if (Object.keys(channels).length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
const channelId = params.groupId?.trim();
|
||||
const groupChannel = params.groupChannel;
|
||||
const channelName = groupChannel?.replace(/^#/, "");
|
||||
const normalizedName = normalizeHyphenSlug(channelName);
|
||||
const candidates = [
|
||||
channelId ?? "",
|
||||
channelName ? `#${channelName}` : "",
|
||||
channelName ?? "",
|
||||
normalizedName,
|
||||
].filter(Boolean);
|
||||
for (const candidate of candidates) {
|
||||
if (candidate && channels[candidate]) {
|
||||
return channels[candidate];
|
||||
}
|
||||
}
|
||||
return channels["*"];
|
||||
}
|
||||
|
||||
function resolveSenderToolsEntry(
|
||||
entry: SlackChannelPolicyEntry | undefined,
|
||||
params: ChannelGroupContext,
|
||||
): GroupToolPolicyConfig | undefined {
|
||||
if (!entry) {
|
||||
return undefined;
|
||||
}
|
||||
const senderPolicy = resolveToolsBySender({
|
||||
toolsBySender: entry.toolsBySender,
|
||||
senderId: params.senderId,
|
||||
senderName: params.senderName,
|
||||
senderUsername: params.senderUsername,
|
||||
senderE164: params.senderE164,
|
||||
});
|
||||
return senderPolicy ?? entry.tools;
|
||||
}
|
||||
|
||||
export function resolveSlackGroupRequireMention(params: ChannelGroupContext): boolean {
|
||||
const resolved = resolveSlackChannelPolicyEntry(params);
|
||||
if (typeof resolved?.requireMention === "boolean") {
|
||||
return resolved.requireMention;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
export function resolveSlackGroupToolPolicy(
|
||||
params: ChannelGroupContext,
|
||||
): GroupToolPolicyConfig | undefined {
|
||||
return resolveSenderToolsEntry(resolveSlackChannelPolicyEntry(params), params);
|
||||
}
|
||||
@ -22,4 +22,5 @@ export {
|
||||
export { monitorSlackProvider } from "./monitor.js";
|
||||
export { probeSlack } from "./probe.js";
|
||||
export { sendMessageSlack } from "./send.js";
|
||||
export { resolveSlackGroupRequireMention, resolveSlackGroupToolPolicy } from "./group-policy.js";
|
||||
export { resolveSlackAppToken, resolveSlackBotToken } from "./token.js";
|
||||
|
||||
@ -6,65 +6,10 @@ import {
|
||||
resolveDiscordGroupToolPolicy,
|
||||
resolveLineGroupRequireMention,
|
||||
resolveLineGroupToolPolicy,
|
||||
resolveSlackGroupRequireMention,
|
||||
resolveSlackGroupToolPolicy,
|
||||
resolveTelegramGroupRequireMention,
|
||||
resolveTelegramGroupToolPolicy,
|
||||
} from "./group-mentions.js";
|
||||
|
||||
const cfg = {
|
||||
channels: {
|
||||
slack: {
|
||||
botToken: "xoxb-test",
|
||||
appToken: "xapp-test",
|
||||
channels: {
|
||||
alerts: {
|
||||
requireMention: false,
|
||||
tools: { allow: ["message.send"] },
|
||||
toolsBySender: {
|
||||
"id:user:alice": { allow: ["sessions.list"] },
|
||||
},
|
||||
},
|
||||
"*": {
|
||||
requireMention: true,
|
||||
tools: { deny: ["exec"] },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
// oxlint-disable-next-line typescript/no-explicit-any
|
||||
} as any;
|
||||
|
||||
describe("group mentions (slack)", () => {
|
||||
it("uses matched channel requireMention and wildcard fallback", () => {
|
||||
expect(resolveSlackGroupRequireMention({ cfg, groupChannel: "#alerts" })).toBe(false);
|
||||
expect(resolveSlackGroupRequireMention({ cfg, groupChannel: "#missing" })).toBe(true);
|
||||
});
|
||||
|
||||
it("resolves sender override, then channel tools, then wildcard tools", () => {
|
||||
const senderOverride = resolveSlackGroupToolPolicy({
|
||||
cfg,
|
||||
groupChannel: "#alerts",
|
||||
senderId: "user:alice",
|
||||
});
|
||||
expect(senderOverride).toEqual({ allow: ["sessions.list"] });
|
||||
|
||||
const channelTools = resolveSlackGroupToolPolicy({
|
||||
cfg,
|
||||
groupChannel: "#alerts",
|
||||
senderId: "user:bob",
|
||||
});
|
||||
expect(channelTools).toEqual({ allow: ["message.send"] });
|
||||
|
||||
const wildcardTools = resolveSlackGroupToolPolicy({
|
||||
cfg,
|
||||
groupChannel: "#missing",
|
||||
senderId: "user:bob",
|
||||
});
|
||||
expect(wildcardTools).toEqual({ deny: ["exec"] });
|
||||
});
|
||||
});
|
||||
|
||||
describe("group mentions (telegram)", () => {
|
||||
it("resolves topic-level requireMention and chat-level tools for topic ids", () => {
|
||||
const telegramCfg = {
|
||||
|
||||
@ -10,8 +10,7 @@ import type {
|
||||
GroupToolPolicyConfig,
|
||||
} from "../../config/types.tools.js";
|
||||
import { resolveExactLineGroupConfigKey } from "../../line/group-keys.js";
|
||||
import { inspectSlackAccount } from "../../plugin-sdk/slack.js";
|
||||
import { normalizeAtHashSlug, normalizeHyphenSlug } from "../../shared/string-normalization.js";
|
||||
import { normalizeAtHashSlug } from "../../shared/string-normalization.js";
|
||||
import type { ChannelGroupContext } from "./types.js";
|
||||
|
||||
type GroupMentionParams = ChannelGroupContext;
|
||||
@ -110,12 +109,6 @@ function resolveDiscordChannelEntry<TEntry>(
|
||||
);
|
||||
}
|
||||
|
||||
type SlackChannelPolicyEntry = {
|
||||
requireMention?: boolean;
|
||||
tools?: GroupToolPolicyConfig;
|
||||
toolsBySender?: GroupToolPolicyBySenderConfig;
|
||||
};
|
||||
|
||||
type SenderScopedToolsEntry = {
|
||||
tools?: GroupToolPolicyConfig;
|
||||
toolsBySender?: GroupToolPolicyBySenderConfig;
|
||||
@ -129,35 +122,6 @@ type ChannelGroupPolicyChannel =
|
||||
| "bluebubbles"
|
||||
| "line";
|
||||
|
||||
function resolveSlackChannelPolicyEntry(
|
||||
params: GroupMentionParams,
|
||||
): SlackChannelPolicyEntry | undefined {
|
||||
const account = inspectSlackAccount({
|
||||
cfg: params.cfg,
|
||||
accountId: params.accountId,
|
||||
});
|
||||
const channels = (account.channels ?? {}) as Record<string, SlackChannelPolicyEntry>;
|
||||
if (Object.keys(channels).length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
const channelId = params.groupId?.trim();
|
||||
const groupChannel = params.groupChannel;
|
||||
const channelName = groupChannel?.replace(/^#/, "");
|
||||
const normalizedName = normalizeHyphenSlug(channelName);
|
||||
const candidates = [
|
||||
channelId ?? "",
|
||||
channelName ? `#${channelName}` : "",
|
||||
channelName ?? "",
|
||||
normalizedName,
|
||||
].filter(Boolean);
|
||||
for (const candidate of candidates) {
|
||||
if (candidate && channels[candidate]) {
|
||||
return channels[candidate];
|
||||
}
|
||||
}
|
||||
return channels["*"];
|
||||
}
|
||||
|
||||
function resolveChannelRequireMention(
|
||||
params: GroupMentionParams,
|
||||
channel: ChannelGroupPolicyChannel,
|
||||
@ -270,14 +234,6 @@ export function resolveGoogleChatGroupToolPolicy(
|
||||
return resolveChannelToolPolicyForSender(params, "googlechat");
|
||||
}
|
||||
|
||||
export function resolveSlackGroupRequireMention(params: GroupMentionParams): boolean {
|
||||
const resolved = resolveSlackChannelPolicyEntry(params);
|
||||
if (typeof resolved?.requireMention === "boolean") {
|
||||
return resolved.requireMention;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
export function resolveBlueBubblesGroupRequireMention(params: GroupMentionParams): boolean {
|
||||
return resolveChannelRequireMention(params, "bluebubbles");
|
||||
}
|
||||
@ -312,13 +268,6 @@ export function resolveDiscordGroupToolPolicy(
|
||||
return resolveSenderToolsEntry(context.guildEntry, params);
|
||||
}
|
||||
|
||||
export function resolveSlackGroupToolPolicy(
|
||||
params: GroupMentionParams,
|
||||
): GroupToolPolicyConfig | undefined {
|
||||
const resolved = resolveSlackChannelPolicyEntry(params);
|
||||
return resolveSenderToolsEntry(resolved, params);
|
||||
}
|
||||
|
||||
export function resolveBlueBubblesGroupToolPolicy(
|
||||
params: GroupMentionParams,
|
||||
): GroupToolPolicyConfig | undefined {
|
||||
|
||||
@ -1,4 +1,8 @@
|
||||
/** Shared policy warnings and DM/group policy helpers for channel plugins. */
|
||||
export type {
|
||||
GroupToolPolicyBySenderConfig,
|
||||
GroupToolPolicyConfig,
|
||||
} from "../config/types.tools.js";
|
||||
export {
|
||||
buildOpenGroupPolicyConfigureRouteAllowlistWarning,
|
||||
buildOpenGroupPolicyRestrictSendersWarning,
|
||||
@ -10,7 +14,7 @@ export {
|
||||
collectOpenProviderGroupPolicyWarnings,
|
||||
} from "../channels/plugins/group-policy-warnings.js";
|
||||
export { buildAccountScopedDmSecurityPolicy } from "../channels/plugins/helpers.js";
|
||||
export { resolveChannelGroupRequireMention } from "../config/group-policy.js";
|
||||
export { resolveChannelGroupRequireMention, resolveToolsBySender } from "../config/group-policy.js";
|
||||
export {
|
||||
DM_GROUP_ACCESS_REASON,
|
||||
readStoreAllowFromForDmPolicy,
|
||||
|
||||
@ -89,6 +89,7 @@ export type { SecretFileReadOptions, SecretFileReadResult } from "../infra/secre
|
||||
|
||||
export { resolveGatewayBindUrl } from "../shared/gateway-bind-url.js";
|
||||
export type { GatewayBindUrlResult } from "../shared/gateway-bind-url.js";
|
||||
export { normalizeHyphenSlug } from "../shared/string-normalization.js";
|
||||
|
||||
export { resolveTailnetHostWithRunner } from "../shared/tailscale-status.js";
|
||||
export type {
|
||||
|
||||
@ -43,7 +43,7 @@ export {
|
||||
export {
|
||||
resolveSlackGroupRequireMention,
|
||||
resolveSlackGroupToolPolicy,
|
||||
} from "../channels/plugins/group-mentions.js";
|
||||
} from "../../extensions/slack/src/group-policy.js";
|
||||
export { SlackConfigSchema } from "../config/zod-schema.providers-core.js";
|
||||
export { buildComputedAccountStatusSnapshot } from "./status-helpers.js";
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user