From 327dc42070c28bb4a379a49aab6e4fb9327353df Mon Sep 17 00:00:00 2001 From: Gustavo Madeira Santana Date: Mon, 9 Mar 2026 04:06:05 -0400 Subject: [PATCH] Matrix: extract monitor access state --- .../src/matrix/monitor/access-state.test.ts | 45 +++++++++ .../matrix/src/matrix/monitor/access-state.ts | 77 +++++++++++++++ .../matrix/src/matrix/monitor/handler.ts | 96 ++++++++----------- extensions/matrix/src/matrix/monitor/index.ts | 1 + 4 files changed, 162 insertions(+), 57 deletions(-) create mode 100644 extensions/matrix/src/matrix/monitor/access-state.test.ts create mode 100644 extensions/matrix/src/matrix/monitor/access-state.ts diff --git a/extensions/matrix/src/matrix/monitor/access-state.test.ts b/extensions/matrix/src/matrix/monitor/access-state.test.ts new file mode 100644 index 00000000000..46f22e2c957 --- /dev/null +++ b/extensions/matrix/src/matrix/monitor/access-state.test.ts @@ -0,0 +1,45 @@ +import { describe, expect, it } from "vitest"; +import { resolveMatrixMonitorAccessState } from "./access-state.js"; + +describe("resolveMatrixMonitorAccessState", () => { + it("normalizes effective allowlists once and exposes reusable matches", () => { + const state = resolveMatrixMonitorAccessState({ + allowFrom: ["matrix:@Alice:Example.org"], + storeAllowFrom: ["user:@bob:example.org"], + groupAllowFrom: ["@Carol:Example.org"], + roomUsers: ["user:@Dana:Example.org"], + senderId: "@dana:example.org", + isRoom: true, + }); + + expect(state.effectiveAllowFrom).toEqual([ + "matrix:@alice:example.org", + "user:@bob:example.org", + ]); + expect(state.effectiveGroupAllowFrom).toEqual(["@carol:example.org"]); + expect(state.effectiveRoomUsers).toEqual(["user:@dana:example.org"]); + expect(state.directAllowMatch.allowed).toBe(false); + expect(state.roomUserMatch?.allowed).toBe(true); + expect(state.groupAllowMatch?.allowed).toBe(false); + expect(state.commandAuthorizers).toEqual([ + { configured: true, allowed: false }, + { configured: true, allowed: true }, + { configured: true, allowed: false }, + ]); + }); + + it("keeps room-user matching disabled for dm traffic", () => { + const state = resolveMatrixMonitorAccessState({ + allowFrom: [], + storeAllowFrom: [], + groupAllowFrom: ["@carol:example.org"], + roomUsers: ["@dana:example.org"], + senderId: "@dana:example.org", + isRoom: false, + }); + + expect(state.roomUserMatch).toBeNull(); + expect(state.commandAuthorizers[1]).toEqual({ configured: true, allowed: false }); + expect(state.commandAuthorizers[2]).toEqual({ configured: true, allowed: false }); + }); +}); diff --git a/extensions/matrix/src/matrix/monitor/access-state.ts b/extensions/matrix/src/matrix/monitor/access-state.ts new file mode 100644 index 00000000000..8677b57d749 --- /dev/null +++ b/extensions/matrix/src/matrix/monitor/access-state.ts @@ -0,0 +1,77 @@ +import { normalizeMatrixAllowList, resolveMatrixAllowListMatch } from "./allowlist.js"; +import type { MatrixAllowListMatch } from "./allowlist.js"; + +type MatrixCommandAuthorizer = { + configured: boolean; + allowed: boolean; +}; + +export type MatrixMonitorAccessState = { + effectiveAllowFrom: string[]; + effectiveGroupAllowFrom: string[]; + effectiveRoomUsers: string[]; + groupAllowConfigured: boolean; + directAllowMatch: MatrixAllowListMatch; + roomUserMatch: MatrixAllowListMatch | null; + groupAllowMatch: MatrixAllowListMatch | null; + commandAuthorizers: [MatrixCommandAuthorizer, MatrixCommandAuthorizer, MatrixCommandAuthorizer]; +}; + +export function resolveMatrixMonitorAccessState(params: { + allowFrom: Array; + storeAllowFrom: Array; + groupAllowFrom: Array; + roomUsers: Array; + senderId: string; + isRoom: boolean; +}): MatrixMonitorAccessState { + const effectiveAllowFrom = normalizeMatrixAllowList([ + ...params.allowFrom, + ...params.storeAllowFrom, + ]); + const effectiveGroupAllowFrom = normalizeMatrixAllowList(params.groupAllowFrom); + const effectiveRoomUsers = normalizeMatrixAllowList(params.roomUsers); + + const directAllowMatch = resolveMatrixAllowListMatch({ + allowList: effectiveAllowFrom, + userId: params.senderId, + }); + const roomUserMatch = + params.isRoom && effectiveRoomUsers.length > 0 + ? resolveMatrixAllowListMatch({ + allowList: effectiveRoomUsers, + userId: params.senderId, + }) + : null; + const groupAllowMatch = + effectiveGroupAllowFrom.length > 0 + ? resolveMatrixAllowListMatch({ + allowList: effectiveGroupAllowFrom, + userId: params.senderId, + }) + : null; + + return { + effectiveAllowFrom, + effectiveGroupAllowFrom, + effectiveRoomUsers, + groupAllowConfigured: effectiveGroupAllowFrom.length > 0, + directAllowMatch, + roomUserMatch, + groupAllowMatch, + commandAuthorizers: [ + { + configured: effectiveAllowFrom.length > 0, + allowed: directAllowMatch.allowed, + }, + { + configured: effectiveRoomUsers.length > 0, + allowed: roomUserMatch?.allowed ?? false, + }, + { + configured: effectiveGroupAllowFrom.length > 0, + allowed: groupAllowMatch?.allowed ?? false, + }, + ], + }; +} diff --git a/extensions/matrix/src/matrix/monitor/handler.ts b/extensions/matrix/src/matrix/monitor/handler.ts index e294c253dd2..9586a76736d 100644 --- a/extensions/matrix/src/matrix/monitor/handler.ts +++ b/extensions/matrix/src/matrix/monitor/handler.ts @@ -24,12 +24,8 @@ import { sendReadReceiptMatrix, sendTypingMatrix, } from "../send.js"; +import { resolveMatrixMonitorAccessState } from "./access-state.js"; import { resolveMatrixAckReactionConfig } from "./ack-config.js"; -import { - normalizeMatrixAllowList, - resolveMatrixAllowListMatch, - resolveMatrixAllowListMatches, -} from "./allowlist.js"; import { resolveMatrixLocation, type MatrixLocationPayload } from "./location.js"; import { downloadMatrixMedia } from "./media.js"; import { resolveMentions } from "./mentions.js"; @@ -55,6 +51,7 @@ export type MatrixMonitorHandlerParams = { logger: RuntimeLogger; logVerboseMessage: (message: string) => void; allowFrom: string[]; + groupAllowFrom?: string[]; roomsConfig?: Record; mentionRegexes: ReturnType; groupPolicy: "open" | "allowlist" | "disabled"; @@ -89,6 +86,7 @@ export function createMatrixRoomMessageHandler(params: MatrixMonitorHandlerParam logger, logVerboseMessage, allowFrom, + groupAllowFrom = [], roomsConfig, mentionRegexes, groupPolicy, @@ -290,22 +288,33 @@ export function createMatrixRoomMessageHandler(params: MatrixMonitorHandlerParam const senderName = await getMemberDisplayName(roomId, senderId); const storeAllowFrom = await readStoreAllowFrom(); - const effectiveAllowFrom = normalizeMatrixAllowList([...allowFrom, ...storeAllowFrom]); - const groupAllowFrom = cfg.channels?.["matrix"]?.groupAllowFrom ?? []; - const effectiveGroupAllowFrom = normalizeMatrixAllowList(groupAllowFrom); - const groupAllowConfigured = effectiveGroupAllowFrom.length > 0; + const roomUsers = roomConfig?.users ?? []; + const accessState = resolveMatrixMonitorAccessState({ + allowFrom, + storeAllowFrom, + groupAllowFrom, + roomUsers, + senderId, + isRoom, + }); + const { + effectiveAllowFrom, + effectiveGroupAllowFrom, + effectiveRoomUsers, + groupAllowConfigured, + directAllowMatch, + roomUserMatch, + groupAllowMatch, + commandAuthorizers, + } = accessState; if (isDirectMessage) { if (!dmEnabled || dmPolicy === "disabled") { return; } if (dmPolicy !== "open") { - const allowMatch = resolveMatrixAllowListMatch({ - allowList: effectiveAllowFrom, - userId: senderId, - }); - const allowMatchMeta = formatAllowlistMatchMeta(allowMatch); - if (!allowMatch.allowed) { + const allowMatchMeta = formatAllowlistMatchMeta(directAllowMatch); + if (!directAllowMatch.allowed) { if (!isReactionEvent && dmPolicy === "pairing") { const { code, created } = await core.channel.pairing.upsertPairingRequest({ channel: "matrix", @@ -351,27 +360,21 @@ export function createMatrixRoomMessageHandler(params: MatrixMonitorHandlerParam } } - const roomUsers = roomConfig?.users ?? []; - if (isRoom && roomUsers.length > 0) { - const userMatch = resolveMatrixAllowListMatch({ - allowList: normalizeMatrixAllowList(roomUsers), - userId: senderId, - }); - if (!userMatch.allowed) { - logVerboseMessage( - `matrix: blocked sender ${senderId} (room users allowlist, ${roomMatchMeta}, ${formatAllowlistMatchMeta( - userMatch, - )})`, - ); - return; - } + if (isRoom && roomUserMatch && !roomUserMatch.allowed) { + logVerboseMessage( + `matrix: blocked sender ${senderId} (room users allowlist, ${roomMatchMeta}, ${formatAllowlistMatchMeta( + roomUserMatch, + )})`, + ); + return; } - if (isRoom && groupPolicy === "allowlist" && roomUsers.length === 0 && groupAllowConfigured) { - const groupAllowMatch = resolveMatrixAllowListMatch({ - allowList: effectiveGroupAllowFrom, - userId: senderId, - }); - if (!groupAllowMatch.allowed) { + if ( + isRoom && + groupPolicy === "allowlist" && + effectiveRoomUsers.length === 0 && + groupAllowConfigured + ) { + if (groupAllowMatch && !groupAllowMatch.allowed) { logVerboseMessage( `matrix: blocked sender ${senderId} (groupAllowFrom, ${roomMatchMeta}, ${formatAllowlistMatchMeta( groupAllowMatch, @@ -456,31 +459,10 @@ export function createMatrixRoomMessageHandler(params: MatrixMonitorHandlerParam surface: "matrix", }); const useAccessGroups = cfg.commands?.useAccessGroups !== false; - const senderAllowedForCommands = resolveMatrixAllowListMatches({ - allowList: effectiveAllowFrom, - userId: senderId, - }); - const senderAllowedForGroup = groupAllowConfigured - ? resolveMatrixAllowListMatches({ - allowList: effectiveGroupAllowFrom, - userId: senderId, - }) - : false; - const senderAllowedForRoomUsers = - isRoom && roomUsers.length > 0 - ? resolveMatrixAllowListMatches({ - allowList: normalizeMatrixAllowList(roomUsers), - userId: senderId, - }) - : false; const hasControlCommandInMessage = core.channel.text.hasControlCommand(bodyText, cfg); const commandGate = resolveControlCommandGate({ useAccessGroups, - authorizers: [ - { configured: effectiveAllowFrom.length > 0, allowed: senderAllowedForCommands }, - { configured: roomUsers.length > 0, allowed: senderAllowedForRoomUsers }, - { configured: groupAllowConfigured, allowed: senderAllowedForGroup }, - ], + authorizers: commandAuthorizers, allowTextCommands, hasControlCommand: hasControlCommandInMessage, }); diff --git a/extensions/matrix/src/matrix/monitor/index.ts b/extensions/matrix/src/matrix/monitor/index.ts index d19b056164f..e91aae25e2c 100644 --- a/extensions/matrix/src/matrix/monitor/index.ts +++ b/extensions/matrix/src/matrix/monitor/index.ts @@ -316,6 +316,7 @@ export async function monitorMatrixProvider(opts: MonitorMatrixOpts = {}): Promi logger, logVerboseMessage, allowFrom, + groupAllowFrom, roomsConfig, mentionRegexes, groupPolicy,