From 1b53918d4fd6fc4c7bb6be42ffc133d2855188ea Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Sun, 22 Mar 2026 18:23:36 -0700 Subject: [PATCH] test(mattermost): cover monitor auth gates --- .../src/mattermost/monitor-auth.test.ts | 151 ++++++++++++++++++ .../src/mattermost/monitor-gating.test.ts | 84 ++++++++++ 2 files changed, 235 insertions(+) create mode 100644 extensions/mattermost/src/mattermost/monitor-auth.test.ts create mode 100644 extensions/mattermost/src/mattermost/monitor-gating.test.ts diff --git a/extensions/mattermost/src/mattermost/monitor-auth.test.ts b/extensions/mattermost/src/mattermost/monitor-auth.test.ts new file mode 100644 index 00000000000..45f857e9021 --- /dev/null +++ b/extensions/mattermost/src/mattermost/monitor-auth.test.ts @@ -0,0 +1,151 @@ +import { describe, expect, it, vi } from "vitest"; + +const evaluateSenderGroupAccessForPolicy = vi.hoisted(() => vi.fn()); +const isDangerousNameMatchingEnabled = vi.hoisted(() => vi.fn()); +const resolveAllowlistMatchSimple = vi.hoisted(() => vi.fn()); +const resolveControlCommandGate = vi.hoisted(() => vi.fn()); +const resolveEffectiveAllowFromLists = vi.hoisted(() => vi.fn()); + +vi.mock("../runtime-api.js", () => ({ + evaluateSenderGroupAccessForPolicy, + isDangerousNameMatchingEnabled, + resolveAllowlistMatchSimple, + resolveControlCommandGate, + resolveEffectiveAllowFromLists, +})); + +describe("mattermost monitor auth", () => { + it("normalizes allowlist entries and resolves effective lists", async () => { + resolveEffectiveAllowFromLists.mockReturnValue({ + effectiveAllowFrom: ["alice"], + effectiveGroupAllowFrom: ["team"], + }); + + const { + normalizeMattermostAllowEntry, + normalizeMattermostAllowList, + resolveMattermostEffectiveAllowFromLists, + } = await import("./monitor-auth.js"); + + expect(normalizeMattermostAllowEntry(" @Alice ")).toBe("alice"); + expect(normalizeMattermostAllowEntry("mattermost:Bob")).toBe("bob"); + expect(normalizeMattermostAllowEntry("*")).toBe("*"); + expect(normalizeMattermostAllowList([" Alice ", "user:alice", "ALICE", "*"])).toEqual([ + "alice", + "*", + ]); + expect( + resolveMattermostEffectiveAllowFromLists({ + allowFrom: [" Alice "], + groupAllowFrom: [" Team "], + storeAllowFrom: ["Store"], + dmPolicy: "pairing", + }), + ).toEqual({ + effectiveAllowFrom: ["alice"], + effectiveGroupAllowFrom: ["team"], + }); + expect(resolveEffectiveAllowFromLists).toHaveBeenCalledWith({ + allowFrom: ["alice"], + groupAllowFrom: ["team"], + storeAllowFrom: ["store"], + dmPolicy: "pairing", + }); + }); + + it("checks sender allowlists against normalized ids and names", async () => { + resolveAllowlistMatchSimple.mockReturnValue({ allowed: true }); + + const { isMattermostSenderAllowed } = await import("./monitor-auth.js"); + expect( + isMattermostSenderAllowed({ + senderId: "@Alice", + senderName: "Alice", + allowFrom: [" mattermost:alice "], + allowNameMatching: true, + }), + ).toBe(true); + expect(resolveAllowlistMatchSimple).toHaveBeenCalledWith({ + allowFrom: ["alice"], + senderId: "alice", + senderName: "alice", + allowNameMatching: true, + }); + }); + + it("authorizes direct messages in open mode and blocks disabled/group-restricted channels", async () => { + isDangerousNameMatchingEnabled.mockReturnValue(false); + resolveEffectiveAllowFromLists.mockReturnValue({ + effectiveAllowFrom: [], + effectiveGroupAllowFrom: [], + }); + resolveControlCommandGate.mockReturnValue({ + commandAuthorized: false, + shouldBlock: false, + }); + evaluateSenderGroupAccessForPolicy.mockReturnValue({ + allowed: false, + reason: "empty_allowlist", + }); + resolveAllowlistMatchSimple.mockReturnValue({ allowed: false }); + + const { authorizeMattermostCommandInvocation } = await import("./monitor-auth.js"); + + expect( + authorizeMattermostCommandInvocation({ + account: { + config: { dmPolicy: "open" }, + } as never, + cfg: {} as never, + senderId: "alice", + senderName: "Alice", + channelId: "dm-1", + channelInfo: { type: "D", name: "alice", display_name: "Alice" } as never, + allowTextCommands: false, + hasControlCommand: false, + }), + ).toMatchObject({ + ok: true, + commandAuthorized: true, + kind: "direct", + roomLabel: "#alice", + }); + + expect( + authorizeMattermostCommandInvocation({ + account: { + config: { dmPolicy: "disabled" }, + } as never, + cfg: {} as never, + senderId: "alice", + senderName: "Alice", + channelId: "dm-1", + channelInfo: { type: "D", name: "alice", display_name: "Alice" } as never, + allowTextCommands: false, + hasControlCommand: false, + }), + ).toMatchObject({ + ok: false, + denyReason: "dm-disabled", + }); + + expect( + authorizeMattermostCommandInvocation({ + account: { + config: { groupPolicy: "allowlist" }, + } as never, + cfg: {} as never, + senderId: "alice", + senderName: "Alice", + channelId: "chan-1", + channelInfo: { type: "O", name: "town-square", display_name: "Town Square" } as never, + allowTextCommands: true, + hasControlCommand: false, + }), + ).toMatchObject({ + ok: false, + denyReason: "channel-no-allowlist", + kind: "channel", + }); + }); +}); diff --git a/extensions/mattermost/src/mattermost/monitor-gating.test.ts b/extensions/mattermost/src/mattermost/monitor-gating.test.ts new file mode 100644 index 00000000000..e818467c626 --- /dev/null +++ b/extensions/mattermost/src/mattermost/monitor-gating.test.ts @@ -0,0 +1,84 @@ +import { describe, expect, it, vi } from "vitest"; +import { + evaluateMattermostMentionGate, + mapMattermostChannelTypeToChatType, +} from "./monitor-gating.js"; + +describe("mattermost monitor gating", () => { + it("maps mattermost channel types to chat types", () => { + expect(mapMattermostChannelTypeToChatType("D")).toBe("direct"); + expect(mapMattermostChannelTypeToChatType("G")).toBe("group"); + expect(mapMattermostChannelTypeToChatType("P")).toBe("group"); + expect(mapMattermostChannelTypeToChatType("O")).toBe("channel"); + expect(mapMattermostChannelTypeToChatType(undefined)).toBe("channel"); + }); + + it("drops non-mentioned traffic when onchar is enabled but not triggered", () => { + const resolveRequireMention = vi.fn(() => true); + + expect( + evaluateMattermostMentionGate({ + kind: "channel", + cfg: {} as never, + accountId: "default", + channelId: "chan-1", + resolveRequireMention, + wasMentioned: false, + isControlCommand: false, + commandAuthorized: false, + oncharEnabled: true, + oncharTriggered: false, + canDetectMention: true, + }), + ).toEqual({ + shouldRequireMention: true, + shouldBypassMention: false, + effectiveWasMentioned: false, + dropReason: "onchar-not-triggered", + }); + }); + + it("bypasses mention for authorized control commands and allows direct chats", () => { + const resolveRequireMention = vi.fn(() => true); + + expect( + evaluateMattermostMentionGate({ + kind: "channel", + cfg: {} as never, + accountId: "default", + channelId: "chan-1", + resolveRequireMention, + wasMentioned: false, + isControlCommand: true, + commandAuthorized: true, + oncharEnabled: false, + oncharTriggered: false, + canDetectMention: true, + }), + ).toEqual({ + shouldRequireMention: true, + shouldBypassMention: true, + effectiveWasMentioned: true, + dropReason: null, + }); + + expect( + evaluateMattermostMentionGate({ + kind: "direct", + cfg: {} as never, + accountId: "default", + channelId: "chan-1", + resolveRequireMention, + wasMentioned: false, + isControlCommand: false, + commandAuthorized: false, + oncharEnabled: false, + oncharTriggered: false, + canDetectMention: true, + }), + ).toMatchObject({ + shouldRequireMention: false, + dropReason: null, + }); + }); +});