mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-19 03:57:26 +00:00
refactor(channels): unify dm pairing policy flows
This commit is contained in:
127
extensions/matrix/src/matrix/monitor/access-policy.ts
Normal file
127
extensions/matrix/src/matrix/monitor/access-policy.ts
Normal file
@@ -0,0 +1,127 @@
|
||||
import {
|
||||
formatAllowlistMatchMeta,
|
||||
issuePairingChallenge,
|
||||
readStoreAllowFromForDmPolicy,
|
||||
resolveDmGroupAccessWithLists,
|
||||
} from "openclaw/plugin-sdk";
|
||||
import {
|
||||
normalizeMatrixAllowList,
|
||||
resolveMatrixAllowListMatch,
|
||||
resolveMatrixAllowListMatches,
|
||||
} from "./allowlist.js";
|
||||
|
||||
type MatrixDmPolicy = "open" | "pairing" | "allowlist" | "disabled";
|
||||
type MatrixGroupPolicy = "open" | "allowlist" | "disabled";
|
||||
|
||||
export async function resolveMatrixAccessState(params: {
|
||||
isDirectMessage: boolean;
|
||||
resolvedAccountId: string;
|
||||
dmPolicy: MatrixDmPolicy;
|
||||
groupPolicy: MatrixGroupPolicy;
|
||||
allowFrom: string[];
|
||||
groupAllowFrom: Array<string | number>;
|
||||
senderId: string;
|
||||
readStoreForDmPolicy: (provider: string, accountId: string) => Promise<string[]>;
|
||||
}) {
|
||||
const storeAllowFrom = params.isDirectMessage
|
||||
? await readStoreAllowFromForDmPolicy({
|
||||
provider: "matrix",
|
||||
accountId: params.resolvedAccountId,
|
||||
dmPolicy: params.dmPolicy,
|
||||
readStore: params.readStoreForDmPolicy,
|
||||
})
|
||||
: [];
|
||||
const normalizedGroupAllowFrom = normalizeMatrixAllowList(params.groupAllowFrom);
|
||||
const senderGroupPolicy =
|
||||
params.groupPolicy === "disabled"
|
||||
? "disabled"
|
||||
: normalizedGroupAllowFrom.length > 0
|
||||
? "allowlist"
|
||||
: "open";
|
||||
const access = resolveDmGroupAccessWithLists({
|
||||
isGroup: !params.isDirectMessage,
|
||||
dmPolicy: params.dmPolicy,
|
||||
groupPolicy: senderGroupPolicy,
|
||||
allowFrom: params.allowFrom,
|
||||
groupAllowFrom: normalizedGroupAllowFrom,
|
||||
storeAllowFrom,
|
||||
groupAllowFromFallbackToAllowFrom: false,
|
||||
isSenderAllowed: (allowFrom) =>
|
||||
resolveMatrixAllowListMatches({
|
||||
allowList: normalizeMatrixAllowList(allowFrom),
|
||||
userId: params.senderId,
|
||||
}),
|
||||
});
|
||||
const effectiveAllowFrom = normalizeMatrixAllowList(access.effectiveAllowFrom);
|
||||
const effectiveGroupAllowFrom = normalizeMatrixAllowList(access.effectiveGroupAllowFrom);
|
||||
return {
|
||||
access,
|
||||
effectiveAllowFrom,
|
||||
effectiveGroupAllowFrom,
|
||||
groupAllowConfigured: effectiveGroupAllowFrom.length > 0,
|
||||
};
|
||||
}
|
||||
|
||||
export async function enforceMatrixDirectMessageAccess(params: {
|
||||
dmEnabled: boolean;
|
||||
dmPolicy: MatrixDmPolicy;
|
||||
accessDecision: "allow" | "block" | "pairing";
|
||||
senderId: string;
|
||||
senderName: string;
|
||||
effectiveAllowFrom: string[];
|
||||
upsertPairingRequest: (input: {
|
||||
id: string;
|
||||
meta?: Record<string, string | undefined>;
|
||||
}) => Promise<{
|
||||
code: string;
|
||||
created: boolean;
|
||||
}>;
|
||||
sendPairingReply: (text: string) => Promise<void>;
|
||||
logVerboseMessage: (message: string) => void;
|
||||
}): Promise<boolean> {
|
||||
if (!params.dmEnabled) {
|
||||
return false;
|
||||
}
|
||||
if (params.accessDecision === "allow") {
|
||||
return true;
|
||||
}
|
||||
const allowMatch = resolveMatrixAllowListMatch({
|
||||
allowList: params.effectiveAllowFrom,
|
||||
userId: params.senderId,
|
||||
});
|
||||
const allowMatchMeta = formatAllowlistMatchMeta(allowMatch);
|
||||
if (params.accessDecision === "pairing") {
|
||||
await issuePairingChallenge({
|
||||
channel: "matrix",
|
||||
senderId: params.senderId,
|
||||
senderIdLine: `Matrix user id: ${params.senderId}`,
|
||||
meta: { name: params.senderName },
|
||||
upsertPairingRequest: params.upsertPairingRequest,
|
||||
buildReplyText: ({ code }) =>
|
||||
[
|
||||
"OpenClaw: access not configured.",
|
||||
"",
|
||||
`Pairing code: ${code}`,
|
||||
"",
|
||||
"Ask the bot owner to approve with:",
|
||||
"openclaw pairing approve matrix <code>",
|
||||
].join("\n"),
|
||||
sendPairingReply: params.sendPairingReply,
|
||||
onCreated: () => {
|
||||
params.logVerboseMessage(
|
||||
`matrix pairing request sender=${params.senderId} name=${params.senderName ?? "unknown"} (${allowMatchMeta})`,
|
||||
);
|
||||
},
|
||||
onReplyError: (err) => {
|
||||
params.logVerboseMessage(
|
||||
`matrix pairing reply failed for ${params.senderId}: ${String(err)}`,
|
||||
);
|
||||
},
|
||||
});
|
||||
return false;
|
||||
}
|
||||
params.logVerboseMessage(
|
||||
`matrix: blocked dm sender ${params.senderId} (dmPolicy=${params.dmPolicy}, ${allowMatchMeta})`,
|
||||
);
|
||||
return false;
|
||||
}
|
||||
@@ -7,9 +7,7 @@ import {
|
||||
formatAllowlistMatchMeta,
|
||||
logInboundDrop,
|
||||
logTypingFailure,
|
||||
readStoreAllowFromForDmPolicy,
|
||||
resolveControlCommandGate,
|
||||
resolveDmGroupAccessWithLists,
|
||||
type PluginRuntime,
|
||||
type RuntimeEnv,
|
||||
type RuntimeLogger,
|
||||
@@ -23,6 +21,7 @@ import {
|
||||
type PollStartContent,
|
||||
} from "../poll-types.js";
|
||||
import { reactMatrixMessage, sendMessageMatrix, sendTypingMatrix } from "../send.js";
|
||||
import { enforceMatrixDirectMessageAccess, resolveMatrixAccessState } from "./access-policy.js";
|
||||
import {
|
||||
normalizeMatrixAllowList,
|
||||
resolveMatrixAllowListMatch,
|
||||
@@ -234,81 +233,34 @@ export function createMatrixRoomMessageHandler(params: MatrixMonitorHandlerParam
|
||||
senderId,
|
||||
senderUsername,
|
||||
});
|
||||
const storeAllowFrom = isDirectMessage
|
||||
? await readStoreAllowFromForDmPolicy({
|
||||
provider: "matrix",
|
||||
accountId: resolvedAccountId,
|
||||
dmPolicy,
|
||||
readStore: pairing.readStoreForDmPolicy,
|
||||
})
|
||||
: [];
|
||||
const groupAllowFrom = cfg.channels?.matrix?.groupAllowFrom ?? [];
|
||||
const normalizedGroupAllowFrom = normalizeMatrixAllowList(groupAllowFrom);
|
||||
const senderGroupPolicy =
|
||||
groupPolicy === "disabled"
|
||||
? "disabled"
|
||||
: normalizedGroupAllowFrom.length > 0
|
||||
? "allowlist"
|
||||
: "open";
|
||||
const access = resolveDmGroupAccessWithLists({
|
||||
isGroup: isRoom,
|
||||
dmPolicy,
|
||||
groupPolicy: senderGroupPolicy,
|
||||
allowFrom,
|
||||
groupAllowFrom: normalizedGroupAllowFrom,
|
||||
storeAllowFrom,
|
||||
groupAllowFromFallbackToAllowFrom: false,
|
||||
isSenderAllowed: (allowFrom) =>
|
||||
resolveMatrixAllowListMatches({
|
||||
allowList: normalizeMatrixAllowList(allowFrom),
|
||||
userId: senderId,
|
||||
}),
|
||||
});
|
||||
const effectiveAllowFrom = normalizeMatrixAllowList(access.effectiveAllowFrom);
|
||||
const effectiveGroupAllowFrom = normalizeMatrixAllowList(access.effectiveGroupAllowFrom);
|
||||
const groupAllowConfigured = effectiveGroupAllowFrom.length > 0;
|
||||
const { access, effectiveAllowFrom, effectiveGroupAllowFrom, groupAllowConfigured } =
|
||||
await resolveMatrixAccessState({
|
||||
isDirectMessage,
|
||||
resolvedAccountId,
|
||||
dmPolicy,
|
||||
groupPolicy,
|
||||
allowFrom,
|
||||
groupAllowFrom,
|
||||
senderId,
|
||||
readStoreForDmPolicy: pairing.readStoreForDmPolicy,
|
||||
});
|
||||
|
||||
if (isDirectMessage) {
|
||||
if (!dmEnabled) {
|
||||
return;
|
||||
}
|
||||
if (access.decision !== "allow") {
|
||||
const allowMatch = resolveMatrixAllowListMatch({
|
||||
allowList: effectiveAllowFrom,
|
||||
userId: senderId,
|
||||
});
|
||||
const allowMatchMeta = formatAllowlistMatchMeta(allowMatch);
|
||||
if (access.decision === "pairing") {
|
||||
const { code, created } = await pairing.upsertPairingRequest({
|
||||
id: senderId,
|
||||
meta: { name: senderName },
|
||||
});
|
||||
if (created) {
|
||||
logVerboseMessage(
|
||||
`matrix pairing request sender=${senderId} name=${senderName ?? "unknown"} (${allowMatchMeta})`,
|
||||
);
|
||||
try {
|
||||
await sendMessageMatrix(
|
||||
`room:${roomId}`,
|
||||
[
|
||||
"OpenClaw: access not configured.",
|
||||
"",
|
||||
`Pairing code: ${code}`,
|
||||
"",
|
||||
"Ask the bot owner to approve with:",
|
||||
"openclaw pairing approve matrix <code>",
|
||||
].join("\n"),
|
||||
{ client },
|
||||
);
|
||||
} catch (err) {
|
||||
logVerboseMessage(`matrix pairing reply failed for ${senderId}: ${String(err)}`);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
logVerboseMessage(
|
||||
`matrix: blocked dm sender ${senderId} (dmPolicy=${dmPolicy}, ${allowMatchMeta})`,
|
||||
);
|
||||
}
|
||||
const allowedDirectMessage = await enforceMatrixDirectMessageAccess({
|
||||
dmEnabled,
|
||||
dmPolicy,
|
||||
accessDecision: access.decision,
|
||||
senderId,
|
||||
senderName,
|
||||
effectiveAllowFrom,
|
||||
upsertPairingRequest: pairing.upsertPairingRequest,
|
||||
sendPairingReply: async (text) => {
|
||||
await sendMessageMatrix(`room:${roomId}`, text, { client });
|
||||
},
|
||||
logVerboseMessage,
|
||||
});
|
||||
if (!allowedDirectMessage) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user