mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-01 23:00:22 +00:00
refactor: compose shared channel security adapters
This commit is contained in:
@@ -20,10 +20,9 @@ import {
|
||||
import { getIMessageRuntime } from "./runtime.js";
|
||||
import { imessageSetupAdapter } from "./setup-core.js";
|
||||
import {
|
||||
collectIMessageSecurityWarnings,
|
||||
createIMessagePluginBase,
|
||||
imessageConfigAdapter,
|
||||
imessageResolveDmPolicy,
|
||||
imessageSecurityAdapter,
|
||||
imessageSetupWizard,
|
||||
} from "./shared.js";
|
||||
import {
|
||||
@@ -124,10 +123,7 @@ export const imessagePlugin: ChannelPlugin<ResolvedIMessageAccount> = {
|
||||
resolveDmPolicy: (account) => account.config.dmPolicy,
|
||||
resolveGroupPolicy: (account) => account.config.groupPolicy,
|
||||
}),
|
||||
security: {
|
||||
resolveDmPolicy: imessageResolveDmPolicy,
|
||||
collectWarnings: collectIMessageSecurityWarnings,
|
||||
},
|
||||
security: imessageSecurityAdapter,
|
||||
groups: {
|
||||
resolveRequireMention: resolveIMessageGroupRequireMention,
|
||||
resolveToolPolicy: resolveIMessageGroupToolPolicy,
|
||||
|
||||
@@ -2,10 +2,9 @@ import { describeAccountSnapshot } from "openclaw/plugin-sdk/account-helpers";
|
||||
import {
|
||||
adaptScopedAccountAccessor,
|
||||
createScopedChannelConfigAdapter,
|
||||
createScopedDmSecurityResolver,
|
||||
formatTrimmedAllowFromEntries,
|
||||
} from "openclaw/plugin-sdk/channel-config-helpers";
|
||||
import { createAllowlistProviderRestrictSendersWarningCollector } from "openclaw/plugin-sdk/channel-policy";
|
||||
import { createRestrictSendersChannelSecurity } from "openclaw/plugin-sdk/channel-policy";
|
||||
import { createChannelPluginBase } from "openclaw/plugin-sdk/core";
|
||||
import {
|
||||
buildChannelConfigSchema,
|
||||
@@ -42,22 +41,18 @@ export const imessageConfigAdapter = createScopedChannelConfigAdapter<ResolvedIM
|
||||
resolveDefaultTo: (account: ResolvedIMessageAccount) => account.config.defaultTo,
|
||||
});
|
||||
|
||||
export const imessageResolveDmPolicy = createScopedDmSecurityResolver<ResolvedIMessageAccount>({
|
||||
channelKey: IMESSAGE_CHANNEL,
|
||||
resolvePolicy: (account) => account.config.dmPolicy,
|
||||
resolveAllowFrom: (account) => account.config.allowFrom,
|
||||
policyPathSuffix: "dmPolicy",
|
||||
});
|
||||
|
||||
export const collectIMessageSecurityWarnings =
|
||||
createAllowlistProviderRestrictSendersWarningCollector<ResolvedIMessageAccount>({
|
||||
providerConfigPresent: (cfg) => cfg.channels?.imessage !== undefined,
|
||||
export const imessageSecurityAdapter =
|
||||
createRestrictSendersChannelSecurity<ResolvedIMessageAccount>({
|
||||
channelKey: IMESSAGE_CHANNEL,
|
||||
resolveDmPolicy: (account) => account.config.dmPolicy,
|
||||
resolveDmAllowFrom: (account) => account.config.allowFrom,
|
||||
resolveGroupPolicy: (account) => account.config.groupPolicy,
|
||||
surface: "iMessage groups",
|
||||
openScope: "any member",
|
||||
groupPolicyPath: "channels.imessage.groupPolicy",
|
||||
groupAllowFromPath: "channels.imessage.groupAllowFrom",
|
||||
mentionGated: false,
|
||||
policyPathSuffix: "dmPolicy",
|
||||
});
|
||||
|
||||
export function createIMessagePluginBase(params: {
|
||||
@@ -98,10 +93,7 @@ export function createIMessagePluginBase(params: {
|
||||
configured: account.configured,
|
||||
}),
|
||||
},
|
||||
security: {
|
||||
resolveDmPolicy: imessageResolveDmPolicy,
|
||||
collectWarnings: collectIMessageSecurityWarnings,
|
||||
},
|
||||
security: imessageSecurityAdapter,
|
||||
setup: params.setup,
|
||||
}) as Pick<
|
||||
ChannelPlugin<ResolvedIMessageAccount>,
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import { createScopedDmSecurityResolver } from "openclaw/plugin-sdk/channel-config-helpers";
|
||||
import {
|
||||
createPairingPrefixStripper,
|
||||
createTextPairingAdapter,
|
||||
} from "openclaw/plugin-sdk/channel-pairing";
|
||||
import { createAllowlistProviderRestrictSendersWarningCollector } from "openclaw/plugin-sdk/channel-policy";
|
||||
import { createRestrictSendersChannelSecurity } from "openclaw/plugin-sdk/channel-policy";
|
||||
import {
|
||||
createAttachedChannelResultAdapter,
|
||||
createEmptyChannelResult,
|
||||
@@ -29,26 +28,21 @@ import { getLineRuntime } from "./runtime.js";
|
||||
import { lineSetupAdapter } from "./setup-core.js";
|
||||
import { lineSetupWizard } from "./setup-surface.js";
|
||||
|
||||
const resolveLineDmPolicy = createScopedDmSecurityResolver<ResolvedLineAccount>({
|
||||
const lineSecurityAdapter = createRestrictSendersChannelSecurity<ResolvedLineAccount>({
|
||||
channelKey: "line",
|
||||
resolvePolicy: (account) => account.config.dmPolicy,
|
||||
resolveAllowFrom: (account) => account.config.allowFrom,
|
||||
resolveDmPolicy: (account) => account.config.dmPolicy,
|
||||
resolveDmAllowFrom: (account) => account.config.allowFrom,
|
||||
resolveGroupPolicy: (account) => account.config.groupPolicy,
|
||||
surface: "LINE groups",
|
||||
openScope: "any member in groups",
|
||||
groupPolicyPath: "channels.line.groupPolicy",
|
||||
groupAllowFromPath: "channels.line.groupAllowFrom",
|
||||
mentionGated: false,
|
||||
policyPathSuffix: "dmPolicy",
|
||||
approveHint: "openclaw pairing approve line <code>",
|
||||
normalizeEntry: (raw) => raw.replace(/^line:(?:user:)?/i, ""),
|
||||
normalizeDmEntry: (raw) => raw.replace(/^line:(?:user:)?/i, ""),
|
||||
});
|
||||
|
||||
const collectLineSecurityWarnings =
|
||||
createAllowlistProviderRestrictSendersWarningCollector<ResolvedLineAccount>({
|
||||
providerConfigPresent: (cfg) => cfg.channels?.line !== undefined,
|
||||
resolveGroupPolicy: (account) => account.config.groupPolicy,
|
||||
surface: "LINE groups",
|
||||
openScope: "any member in groups",
|
||||
groupPolicyPath: "channels.line.groupPolicy",
|
||||
groupAllowFromPath: "channels.line.groupAllowFrom",
|
||||
mentionGated: false,
|
||||
});
|
||||
|
||||
export const linePlugin: ChannelPlugin<ResolvedLineAccount> = {
|
||||
id: "line",
|
||||
...lineChannelPluginCommon,
|
||||
@@ -69,10 +63,7 @@ export const linePlugin: ChannelPlugin<ResolvedLineAccount> = {
|
||||
},
|
||||
}),
|
||||
setupWizard: lineSetupWizard,
|
||||
security: {
|
||||
resolveDmPolicy: resolveLineDmPolicy,
|
||||
collectWarnings: collectLineSecurityWarnings,
|
||||
},
|
||||
security: lineSecurityAdapter,
|
||||
groups: {
|
||||
resolveRequireMention: resolveLineGroupRequireMention,
|
||||
},
|
||||
|
||||
@@ -4,7 +4,6 @@ import { createMessageToolButtonsSchema } from "openclaw/plugin-sdk/channel-acti
|
||||
import {
|
||||
adaptScopedAccountAccessor,
|
||||
createScopedChannelConfigAdapter,
|
||||
createScopedDmSecurityResolver,
|
||||
} from "openclaw/plugin-sdk/channel-config-helpers";
|
||||
import type {
|
||||
ChannelMessageActionAdapter,
|
||||
@@ -12,7 +11,7 @@ import type {
|
||||
ChannelMessageToolDiscovery,
|
||||
} from "openclaw/plugin-sdk/channel-contract";
|
||||
import { createLoggedPairingApprovalNotifier } from "openclaw/plugin-sdk/channel-pairing";
|
||||
import { createAllowlistProviderRestrictSendersWarningCollector } from "openclaw/plugin-sdk/channel-policy";
|
||||
import { createRestrictSendersChannelSecurity } from "openclaw/plugin-sdk/channel-policy";
|
||||
import { createAttachedChannelResultAdapter } from "openclaw/plugin-sdk/channel-send-result";
|
||||
import { createScopedAccountReplyToModeResolver } from "openclaw/plugin-sdk/conversation-runtime";
|
||||
import { createChannelDirectoryAdapter } from "openclaw/plugin-sdk/directory-runtime";
|
||||
@@ -50,15 +49,18 @@ import { resolveMattermostOutboundSessionRoute } from "./session-route.js";
|
||||
import { mattermostSetupAdapter } from "./setup-core.js";
|
||||
import { mattermostSetupWizard } from "./setup-surface.js";
|
||||
|
||||
const collectMattermostSecurityWarnings =
|
||||
createAllowlistProviderRestrictSendersWarningCollector<ResolvedMattermostAccount>({
|
||||
providerConfigPresent: (cfg) => cfg.channels?.mattermost !== undefined,
|
||||
resolveGroupPolicy: (account) => account.config.groupPolicy,
|
||||
surface: "Mattermost channels",
|
||||
openScope: "any member",
|
||||
groupPolicyPath: "channels.mattermost.groupPolicy",
|
||||
groupAllowFromPath: "channels.mattermost.groupAllowFrom",
|
||||
});
|
||||
const mattermostSecurityAdapter = createRestrictSendersChannelSecurity<ResolvedMattermostAccount>({
|
||||
channelKey: "mattermost",
|
||||
resolveDmPolicy: (account) => account.config.dmPolicy,
|
||||
resolveDmAllowFrom: (account) => account.config.allowFrom,
|
||||
resolveGroupPolicy: (account) => account.config.groupPolicy,
|
||||
surface: "Mattermost channels",
|
||||
openScope: "any member",
|
||||
groupPolicyPath: "channels.mattermost.groupPolicy",
|
||||
groupAllowFromPath: "channels.mattermost.groupAllowFrom",
|
||||
policyPathSuffix: "dmPolicy",
|
||||
normalizeDmEntry: (raw) => normalizeAllowEntry(raw),
|
||||
});
|
||||
|
||||
function describeMattermostMessageTool({
|
||||
cfg,
|
||||
@@ -279,14 +281,6 @@ const mattermostConfigAdapter = createScopedChannelConfigAdapter<ResolvedMatterm
|
||||
}),
|
||||
});
|
||||
|
||||
const resolveMattermostDmPolicy = createScopedDmSecurityResolver<ResolvedMattermostAccount>({
|
||||
channelKey: "mattermost",
|
||||
resolvePolicy: (account) => account.config.dmPolicy,
|
||||
resolveAllowFrom: (account) => account.config.allowFrom,
|
||||
policyPathSuffix: "dmPolicy",
|
||||
normalizeEntry: (raw) => normalizeAllowEntry(raw),
|
||||
});
|
||||
|
||||
export const mattermostPlugin: ChannelPlugin<ResolvedMattermostAccount> = {
|
||||
id: "mattermost",
|
||||
meta: {
|
||||
@@ -339,10 +333,7 @@ export const mattermostPlugin: ChannelPlugin<ResolvedMattermostAccount> = {
|
||||
},
|
||||
}),
|
||||
},
|
||||
security: {
|
||||
resolveDmPolicy: resolveMattermostDmPolicy,
|
||||
collectWarnings: collectMattermostSecurityWarnings,
|
||||
},
|
||||
security: mattermostSecurityAdapter,
|
||||
groups: {
|
||||
resolveRequireMention: resolveMattermostGroupRequireMention,
|
||||
},
|
||||
|
||||
@@ -38,10 +38,9 @@ import {
|
||||
import { getSignalRuntime } from "./runtime.js";
|
||||
import { signalSetupAdapter } from "./setup-core.js";
|
||||
import {
|
||||
collectSignalSecurityWarnings,
|
||||
signalConfigAdapter,
|
||||
createSignalPluginBase,
|
||||
signalResolveDmPolicy,
|
||||
signalSecurityAdapter,
|
||||
signalSetupWizard,
|
||||
} from "./shared.js";
|
||||
type SignalSendFn = ReturnType<typeof getSignalRuntime>["channel"]["signal"]["sendMessageSignal"];
|
||||
@@ -295,10 +294,7 @@ export const signalPlugin: ChannelPlugin<ResolvedSignalAccount> = {
|
||||
resolveDmPolicy: (account) => account.config.dmPolicy,
|
||||
resolveGroupPolicy: (account) => account.config.groupPolicy,
|
||||
}),
|
||||
security: {
|
||||
resolveDmPolicy: signalResolveDmPolicy,
|
||||
collectWarnings: collectSignalSecurityWarnings,
|
||||
},
|
||||
security: signalSecurityAdapter,
|
||||
messaging: {
|
||||
normalizeTarget: normalizeSignalMessagingTarget,
|
||||
parseExplicitTarget: ({ raw }) => parseSignalExplicitTarget(raw),
|
||||
|
||||
@@ -2,9 +2,8 @@ import { describeAccountSnapshot } from "openclaw/plugin-sdk/account-helpers";
|
||||
import {
|
||||
adaptScopedAccountAccessor,
|
||||
createScopedChannelConfigAdapter,
|
||||
createScopedDmSecurityResolver,
|
||||
} from "openclaw/plugin-sdk/channel-config-helpers";
|
||||
import { createAllowlistProviderRestrictSendersWarningCollector } from "openclaw/plugin-sdk/channel-policy";
|
||||
import { createRestrictSendersChannelSecurity } from "openclaw/plugin-sdk/channel-policy";
|
||||
import { createChannelPluginBase } from "openclaw/plugin-sdk/core";
|
||||
import {
|
||||
listSignalAccountIds,
|
||||
@@ -47,25 +46,20 @@ export const signalConfigAdapter = createScopedChannelConfigAdapter<ResolvedSign
|
||||
resolveDefaultTo: (account: ResolvedSignalAccount) => account.config.defaultTo,
|
||||
});
|
||||
|
||||
export const signalResolveDmPolicy = createScopedDmSecurityResolver<ResolvedSignalAccount>({
|
||||
export const signalSecurityAdapter = createRestrictSendersChannelSecurity<ResolvedSignalAccount>({
|
||||
channelKey: SIGNAL_CHANNEL,
|
||||
resolvePolicy: (account) => account.config.dmPolicy,
|
||||
resolveAllowFrom: (account) => account.config.allowFrom,
|
||||
resolveDmPolicy: (account) => account.config.dmPolicy,
|
||||
resolveDmAllowFrom: (account) => account.config.allowFrom,
|
||||
resolveGroupPolicy: (account) => account.config.groupPolicy,
|
||||
surface: "Signal groups",
|
||||
openScope: "any member",
|
||||
groupPolicyPath: "channels.signal.groupPolicy",
|
||||
groupAllowFromPath: "channels.signal.groupAllowFrom",
|
||||
mentionGated: false,
|
||||
policyPathSuffix: "dmPolicy",
|
||||
normalizeEntry: (raw) => normalizeE164(raw.replace(/^signal:/i, "").trim()),
|
||||
normalizeDmEntry: (raw) => normalizeE164(raw.replace(/^signal:/i, "").trim()),
|
||||
});
|
||||
|
||||
export const collectSignalSecurityWarnings =
|
||||
createAllowlistProviderRestrictSendersWarningCollector<ResolvedSignalAccount>({
|
||||
providerConfigPresent: (cfg) => cfg.channels?.signal !== undefined,
|
||||
resolveGroupPolicy: (account) => account.config.groupPolicy,
|
||||
surface: "Signal groups",
|
||||
openScope: "any member",
|
||||
groupPolicyPath: "channels.signal.groupPolicy",
|
||||
groupAllowFromPath: "channels.signal.groupAllowFrom",
|
||||
mentionGated: false,
|
||||
});
|
||||
|
||||
export function createSignalPluginBase(params: {
|
||||
setupWizard?: NonNullable<ChannelPlugin<ResolvedSignalAccount>["setupWizard"]>;
|
||||
setup: NonNullable<ChannelPlugin<ResolvedSignalAccount>["setup"]>;
|
||||
@@ -110,10 +104,7 @@ export function createSignalPluginBase(params: {
|
||||
},
|
||||
}),
|
||||
},
|
||||
security: {
|
||||
resolveDmPolicy: signalResolveDmPolicy,
|
||||
collectWarnings: collectSignalSecurityWarnings,
|
||||
},
|
||||
security: signalSecurityAdapter,
|
||||
setup: params.setup,
|
||||
}) as Pick<
|
||||
ChannelPlugin<ResolvedSignalAccount>,
|
||||
|
||||
57
src/plugin-sdk/channel-policy.test.ts
Normal file
57
src/plugin-sdk/channel-policy.test.ts
Normal file
@@ -0,0 +1,57 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import type { GroupPolicy } from "../config/types.base.js";
|
||||
import { createRestrictSendersChannelSecurity } from "./channel-policy.js";
|
||||
|
||||
describe("createRestrictSendersChannelSecurity", () => {
|
||||
it("builds dm policy resolution and open-group warnings from one descriptor", async () => {
|
||||
const security = createRestrictSendersChannelSecurity<{
|
||||
accountId: string;
|
||||
allowFrom?: string[];
|
||||
dmPolicy?: string;
|
||||
groupPolicy?: GroupPolicy;
|
||||
}>({
|
||||
channelKey: "line",
|
||||
resolveDmPolicy: (account) => account.dmPolicy,
|
||||
resolveDmAllowFrom: (account) => account.allowFrom,
|
||||
resolveGroupPolicy: (account) => account.groupPolicy,
|
||||
surface: "LINE groups",
|
||||
openScope: "any member in groups",
|
||||
groupPolicyPath: "channels.line.groupPolicy",
|
||||
groupAllowFromPath: "channels.line.groupAllowFrom",
|
||||
mentionGated: false,
|
||||
policyPathSuffix: "dmPolicy",
|
||||
});
|
||||
|
||||
expect(
|
||||
security.resolveDmPolicy?.({
|
||||
cfg: { channels: {} } as never,
|
||||
accountId: "default",
|
||||
account: {
|
||||
accountId: "default",
|
||||
dmPolicy: "allowlist",
|
||||
allowFrom: ["line:user:abc"],
|
||||
},
|
||||
}),
|
||||
).toEqual({
|
||||
policy: "allowlist",
|
||||
allowFrom: ["line:user:abc"],
|
||||
policyPath: "channels.line.dmPolicy",
|
||||
allowFromPath: "channels.line.",
|
||||
approveHint: "Approve via: openclaw pairing list line / openclaw pairing approve line <code>",
|
||||
normalizeEntry: undefined,
|
||||
});
|
||||
|
||||
expect(
|
||||
security.collectWarnings?.({
|
||||
cfg: { channels: { line: {} } } as never,
|
||||
accountId: "default",
|
||||
account: {
|
||||
accountId: "default",
|
||||
groupPolicy: "open",
|
||||
},
|
||||
}),
|
||||
).toEqual([
|
||||
'- LINE groups: groupPolicy="open" allows any member in groups to trigger. Set channels.line.groupPolicy="allowlist" + channels.line.groupAllowFrom to restrict senders.',
|
||||
]);
|
||||
});
|
||||
});
|
||||
@@ -1,3 +1,8 @@
|
||||
import { createAllowlistProviderRestrictSendersWarningCollector } from "../channels/plugins/group-policy-warnings.js";
|
||||
import type { ChannelSecurityAdapter } from "../channels/plugins/types.adapters.js";
|
||||
import type { OpenClawConfig } from "../config/config.js";
|
||||
import type { GroupPolicy } from "../config/types.base.js";
|
||||
import { createScopedDmSecurityResolver } from "./channel-config-helpers.js";
|
||||
/** Shared policy warnings and DM/group policy helpers for channel plugins. */
|
||||
export type {
|
||||
GroupToolPolicyBySenderConfig,
|
||||
@@ -9,7 +14,6 @@ export {
|
||||
createAllowlistProviderGroupPolicyWarningCollector,
|
||||
createConditionalWarningCollector,
|
||||
createAllowlistProviderOpenWarningCollector,
|
||||
createAllowlistProviderRestrictSendersWarningCollector,
|
||||
createAllowlistProviderRouteAllowlistWarningCollector,
|
||||
createOpenGroupPolicyRestrictSendersWarningCollector,
|
||||
createOpenProviderGroupPolicyWarningCollector,
|
||||
@@ -35,3 +39,52 @@ export {
|
||||
resolveDmGroupAccessWithLists,
|
||||
resolveEffectiveAllowFromLists,
|
||||
} from "../security/dm-policy-shared.js";
|
||||
export { createAllowlistProviderRestrictSendersWarningCollector };
|
||||
|
||||
/** Compose the common DM policy resolver with restrict-senders group warnings. */
|
||||
export function createRestrictSendersChannelSecurity<
|
||||
ResolvedAccount extends { accountId?: string | null },
|
||||
>(params: {
|
||||
channelKey: string;
|
||||
resolveDmPolicy: (account: ResolvedAccount) => string | null | undefined;
|
||||
resolveDmAllowFrom: (account: ResolvedAccount) => Array<string | number> | null | undefined;
|
||||
resolveGroupPolicy: (account: ResolvedAccount) => GroupPolicy | null | undefined;
|
||||
surface: string;
|
||||
openScope: string;
|
||||
groupPolicyPath: string;
|
||||
groupAllowFromPath: string;
|
||||
mentionGated?: boolean;
|
||||
providerConfigPresent?: (cfg: OpenClawConfig) => boolean;
|
||||
resolveFallbackAccountId?: (account: ResolvedAccount) => string | null | undefined;
|
||||
defaultDmPolicy?: string;
|
||||
allowFromPathSuffix?: string;
|
||||
policyPathSuffix?: string;
|
||||
approveChannelId?: string;
|
||||
approveHint?: string;
|
||||
normalizeDmEntry?: (raw: string) => string;
|
||||
}): ChannelSecurityAdapter<ResolvedAccount> {
|
||||
return {
|
||||
resolveDmPolicy: createScopedDmSecurityResolver<ResolvedAccount>({
|
||||
channelKey: params.channelKey,
|
||||
resolvePolicy: params.resolveDmPolicy,
|
||||
resolveAllowFrom: params.resolveDmAllowFrom,
|
||||
resolveFallbackAccountId: params.resolveFallbackAccountId,
|
||||
defaultPolicy: params.defaultDmPolicy,
|
||||
allowFromPathSuffix: params.allowFromPathSuffix,
|
||||
policyPathSuffix: params.policyPathSuffix,
|
||||
approveChannelId: params.approveChannelId,
|
||||
approveHint: params.approveHint,
|
||||
normalizeEntry: params.normalizeDmEntry,
|
||||
}),
|
||||
collectWarnings: createAllowlistProviderRestrictSendersWarningCollector<ResolvedAccount>({
|
||||
providerConfigPresent:
|
||||
params.providerConfigPresent ?? ((cfg) => cfg.channels?.[params.channelKey] !== undefined),
|
||||
resolveGroupPolicy: params.resolveGroupPolicy,
|
||||
surface: params.surface,
|
||||
openScope: params.openScope,
|
||||
groupPolicyPath: params.groupPolicyPath,
|
||||
groupAllowFromPath: params.groupAllowFromPath,
|
||||
mentionGated: params.mentionGated,
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user