openclaw/extensions/msteams/src/monitor-handler/access.ts

127 lines
4.3 KiB
TypeScript

import {
DEFAULT_ACCOUNT_ID,
createChannelPairingController,
evaluateSenderGroupAccessForPolicy,
isDangerousNameMatchingEnabled,
readStoreAllowFromForDmPolicy,
resolveDefaultGroupPolicy,
resolveDmGroupAccessWithLists,
resolveEffectiveAllowFromLists,
resolveSenderScopedGroupPolicy,
type OpenClawConfig,
} from "../../runtime-api.js";
import { normalizeMSTeamsConversationId } from "../inbound.js";
import { resolveMSTeamsAllowlistMatch, resolveMSTeamsRouteConfig } from "../policy.js";
import { getMSTeamsRuntime } from "../runtime.js";
import type { MSTeamsTurnContext } from "../sdk-types.js";
export type MSTeamsResolvedSenderAccess = Awaited<ReturnType<typeof resolveMSTeamsSenderAccess>>;
export async function resolveMSTeamsSenderAccess(params: {
cfg: OpenClawConfig;
activity: MSTeamsTurnContext["activity"];
}) {
const activity = params.activity;
const msteamsCfg = params.cfg.channels?.msteams;
const conversationId = normalizeMSTeamsConversationId(activity.conversation?.id ?? "unknown");
const convType = activity.conversation?.conversationType?.toLowerCase();
const isDirectMessage = convType === "personal" || (!convType && !activity.conversation?.isGroup);
const senderId = activity.from?.aadObjectId ?? activity.from?.id ?? "unknown";
const senderName = activity.from?.name ?? activity.from?.id ?? senderId;
const core = getMSTeamsRuntime();
const pairing = createChannelPairingController({
core,
channel: "msteams",
accountId: DEFAULT_ACCOUNT_ID,
});
const dmPolicy = msteamsCfg?.dmPolicy ?? "pairing";
const storedAllowFrom = await readStoreAllowFromForDmPolicy({
provider: "msteams",
accountId: pairing.accountId,
dmPolicy,
readStore: pairing.readStoreForDmPolicy,
});
const configuredDmAllowFrom = (msteamsCfg?.allowFrom ?? []).map((entry) => String(entry));
const groupAllowFrom = msteamsCfg?.groupAllowFrom;
const resolvedAllowFromLists = resolveEffectiveAllowFromLists({
allowFrom: configuredDmAllowFrom,
groupAllowFrom,
storeAllowFrom: storedAllowFrom,
dmPolicy,
});
const defaultGroupPolicy = resolveDefaultGroupPolicy(params.cfg);
const groupPolicy =
!isDirectMessage && msteamsCfg
? (msteamsCfg.groupPolicy ?? defaultGroupPolicy ?? "allowlist")
: "disabled";
const effectiveGroupAllowFrom = resolvedAllowFromLists.effectiveGroupAllowFrom;
const allowNameMatching = isDangerousNameMatchingEnabled(msteamsCfg);
const channelGate = resolveMSTeamsRouteConfig({
cfg: msteamsCfg,
teamId: activity.channelData?.team?.id,
teamName: activity.channelData?.team?.name,
conversationId,
channelName: activity.channelData?.channel?.name,
allowNameMatching,
});
// When a route-level (team/channel) allowlist is configured but the sender allowlist is
// empty, resolveSenderScopedGroupPolicy would otherwise downgrade the policy to "open",
// allowing any sender. To close this bypass (GHSA-g7cr-9h7q-4qxq), treat an empty sender
// allowlist as deny-all whenever the route allowlist is active.
const senderGroupPolicy =
channelGate.allowlistConfigured && effectiveGroupAllowFrom.length === 0
? groupPolicy
: resolveSenderScopedGroupPolicy({
groupPolicy,
groupAllowFrom: effectiveGroupAllowFrom,
});
const access = resolveDmGroupAccessWithLists({
isGroup: !isDirectMessage,
dmPolicy,
groupPolicy: senderGroupPolicy,
allowFrom: configuredDmAllowFrom,
groupAllowFrom,
storeAllowFrom: storedAllowFrom,
groupAllowFromFallbackToAllowFrom: false,
isSenderAllowed: (allowFrom) =>
resolveMSTeamsAllowlistMatch({
allowFrom,
senderId,
senderName,
allowNameMatching,
}).allowed,
});
const senderGroupAccess = evaluateSenderGroupAccessForPolicy({
groupPolicy,
groupAllowFrom: effectiveGroupAllowFrom,
senderId,
isSenderAllowed: (_senderId, allowFrom) =>
resolveMSTeamsAllowlistMatch({
allowFrom,
senderId,
senderName,
allowNameMatching,
}).allowed,
});
return {
msteamsCfg,
pairing,
isDirectMessage,
conversationId,
senderId,
senderName,
dmPolicy,
channelGate,
access,
senderGroupAccess,
configuredDmAllowFrom,
effectiveDmAllowFrom: access.effectiveAllowFrom,
effectiveGroupAllowFrom,
allowNameMatching,
groupPolicy,
};
}