mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-28 05:16:23 -06:00
fix(msteams): mark external system events as non-owner
Marks skipped and supplemental Microsoft Teams system events as non-owner/untrusted while preserving active primary message dispatch behavior.
Verified before merge:
- PR was open, not draft, mergeable, and clean against main
- Matched head: 4f79f46205
- GitHub checks passed, including Real behavior proof, auto-response, build artifacts, type/lint checks, channel/runtime critical quality checks, and security-fast
- ClawSweeper marked proof sufficient with no concrete contributor-facing blocker remaining
Co-authored-by: GuoJiaming <804436395@qq.com>
This commit is contained in:
@@ -606,6 +606,98 @@ describe("msteams monitor handler authz", () => {
|
||||
expect(runtimeApiMockState.dispatchReplyFromConfigWithSettledDispatcher).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("marks skipped channel message system events as non-owner", async () => {
|
||||
resetThreadMocks();
|
||||
const { deps, enqueueSystemEvent } = createDeps({
|
||||
channels: {
|
||||
msteams: {
|
||||
groupPolicy: "open",
|
||||
requireMention: true,
|
||||
},
|
||||
},
|
||||
} as OpenClawConfig);
|
||||
|
||||
const handler = createMSTeamsMessageHandler(deps);
|
||||
await handler(
|
||||
createMessageActivity({
|
||||
id: "msg-skip-mention",
|
||||
text: "please run the deployment",
|
||||
from: {
|
||||
id: "member-id",
|
||||
aadObjectId: "member-aad",
|
||||
name: "Member",
|
||||
},
|
||||
conversation: {
|
||||
id: "19:channel@thread.tacv2",
|
||||
conversationType: "channel",
|
||||
},
|
||||
channelData: {
|
||||
team: { id: "team123", name: "Team 123" },
|
||||
channel: { name: "General" },
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
expect(runtimeApiMockState.dispatchReplyFromConfigWithSettledDispatcher).not.toHaveBeenCalled();
|
||||
const systemEventCall = enqueueSystemEvent.mock.calls.find(
|
||||
([text]) => typeof text === "string" && text.includes("please run the deployment"),
|
||||
);
|
||||
if (!systemEventCall) {
|
||||
throw new Error("expected skipped Teams message system event");
|
||||
}
|
||||
expect(systemEventCall[1]).toMatchObject({
|
||||
forceSenderIsOwnerFalse: true,
|
||||
trusted: false,
|
||||
});
|
||||
});
|
||||
|
||||
it("keeps dispatched primary message system events owner-neutral", async () => {
|
||||
resetThreadMocks();
|
||||
const { deps, enqueueSystemEvent } = createDeps({
|
||||
channels: {
|
||||
msteams: {
|
||||
groupPolicy: "open",
|
||||
requireMention: false,
|
||||
},
|
||||
},
|
||||
} as OpenClawConfig);
|
||||
|
||||
const handler = createMSTeamsMessageHandler(deps);
|
||||
await handler(
|
||||
createMessageActivity({
|
||||
id: "msg-active",
|
||||
text: "please check the build",
|
||||
from: {
|
||||
id: "member-id",
|
||||
aadObjectId: "member-aad",
|
||||
name: "Member",
|
||||
},
|
||||
conversation: {
|
||||
id: "19:channel@thread.tacv2",
|
||||
conversationType: "channel",
|
||||
},
|
||||
channelData: {
|
||||
team: { id: "team123", name: "Team 123" },
|
||||
channel: { name: "General" },
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
expect(runtimeApiMockState.dispatchReplyFromConfigWithSettledDispatcher).toHaveBeenCalled();
|
||||
const systemEventCall = enqueueSystemEvent.mock.calls.find(
|
||||
([text]) => typeof text === "string" && text.includes("please check the build"),
|
||||
);
|
||||
if (!systemEventCall) {
|
||||
throw new Error("expected active Teams message system event");
|
||||
}
|
||||
expect(systemEventCall[1]).not.toMatchObject({
|
||||
forceSenderIsOwnerFalse: true,
|
||||
});
|
||||
expect(systemEventCall[1]).not.toMatchObject({
|
||||
trusted: false,
|
||||
});
|
||||
});
|
||||
|
||||
it("authorizes text control commands from static access groups", async () => {
|
||||
resetThreadMocks();
|
||||
const hasControlCommand = vi.fn(() => true);
|
||||
|
||||
@@ -38,11 +38,20 @@ vi.mock("../graph-thread.js", () => {
|
||||
|
||||
describe("msteams thread parent context injection", () => {
|
||||
type MessageHandler = ReturnType<typeof createMSTeamsMessageHandler>;
|
||||
type ParentSystemEventCall = [
|
||||
string,
|
||||
{
|
||||
sessionKey: string;
|
||||
contextKey?: string;
|
||||
forceSenderIsOwnerFalse?: boolean;
|
||||
trusted?: boolean;
|
||||
},
|
||||
];
|
||||
|
||||
function findParentSystemEventCall(
|
||||
mock: ReturnType<typeof vi.fn>,
|
||||
): [string, { sessionKey: string; contextKey?: string }] | undefined {
|
||||
const calls = mock.mock.calls as Array<[string, { sessionKey: string; contextKey?: string }]>;
|
||||
): ParentSystemEventCall | undefined {
|
||||
const calls = mock.mock.calls as ParentSystemEventCall[];
|
||||
return calls.find(([text]) => text.startsWith("Replying to @"));
|
||||
}
|
||||
|
||||
@@ -93,6 +102,10 @@ describe("msteams thread parent context injection", () => {
|
||||
expect(parentCall[0]).toBe("Replying to @Alice: Can someone investigate the latency spike?");
|
||||
expect(parentCall[1]?.contextKey).toContain("msteams:thread-parent:");
|
||||
expect(parentCall[1]?.contextKey).toContain("thread-root-123");
|
||||
expect(parentCall[1]).toMatchObject({
|
||||
forceSenderIsOwnerFalse: true,
|
||||
trusted: false,
|
||||
});
|
||||
});
|
||||
|
||||
it("caches parent fetches across thread replies in the same session", async () => {
|
||||
|
||||
@@ -498,10 +498,15 @@ export function createMSTeamsMessageHandler(deps: MSTeamsMessageHandlerDeps) {
|
||||
? `Teams DM from ${senderName}`
|
||||
: `Teams message in ${conversationType} from ${senderName}`;
|
||||
|
||||
core.system.enqueueSystemEvent(`${inboundLabel}: ${preview}`, {
|
||||
sessionKey: route.sessionKey,
|
||||
contextKey: `msteams:message:${conversationId}:${activity.id ?? "unknown"}`,
|
||||
});
|
||||
const enqueuePrimaryMessageSystemEvent = (opts?: {
|
||||
forceSenderIsOwnerFalse?: boolean;
|
||||
trusted?: boolean;
|
||||
}) =>
|
||||
core.system.enqueueSystemEvent(`${inboundLabel}: ${preview}`, {
|
||||
sessionKey: route.sessionKey,
|
||||
contextKey: `msteams:message:${conversationId}:${activity.id ?? "unknown"}`,
|
||||
...opts,
|
||||
});
|
||||
|
||||
const channelId = conversationId;
|
||||
const { teamConfig, channelConfig } = channelGate;
|
||||
@@ -536,6 +541,10 @@ export function createMSTeamsMessageHandler(deps: MSTeamsMessageHandlerDeps) {
|
||||
requireMention,
|
||||
mentioned,
|
||||
});
|
||||
enqueuePrimaryMessageSystemEvent({
|
||||
forceSenderIsOwnerFalse: true,
|
||||
trusted: false,
|
||||
});
|
||||
createChannelHistoryWindow({ historyMap: conversationHistories }).record({
|
||||
historyKey: conversationId,
|
||||
limit: historyLimit,
|
||||
@@ -549,6 +558,7 @@ export function createMSTeamsMessageHandler(deps: MSTeamsMessageHandlerDeps) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
enqueuePrimaryMessageSystemEvent();
|
||||
let graphConversationId = translateMSTeamsDmConversationIdForGraph({
|
||||
isDirectMessage,
|
||||
conversationId,
|
||||
@@ -665,6 +675,8 @@ export function createMSTeamsMessageHandler(deps: MSTeamsMessageHandlerDeps) {
|
||||
core.system.enqueueSystemEvent(formatParentContextEvent(parentSummary), {
|
||||
sessionKey: route.sessionKey,
|
||||
contextKey: `msteams:thread-parent:${conversationId}:${activity.replyToId}`,
|
||||
forceSenderIsOwnerFalse: true,
|
||||
trusted: false,
|
||||
});
|
||||
markParentContextInjected(route.sessionKey, activity.replyToId);
|
||||
}
|
||||
|
||||
@@ -207,6 +207,8 @@ describe("createMSTeamsReactionHandler", () => {
|
||||
expect(label).toContain("added");
|
||||
expect(meta.sessionKey).toBe("test-session");
|
||||
expect(meta.contextKey).toContain("added");
|
||||
expect(meta.forceSenderIsOwnerFalse).toBe(true);
|
||||
expect(meta.trusted).toBe(false);
|
||||
});
|
||||
|
||||
it("enqueues system event for reactionsRemoved", async () => {
|
||||
|
||||
@@ -116,6 +116,8 @@ export function createMSTeamsReactionHandler(deps: MSTeamsMessageHandlerDeps) {
|
||||
core.system.enqueueSystemEvent(label, {
|
||||
sessionKey: route.sessionKey,
|
||||
contextKey: `msteams:reaction:${conversationId}:${targetMessageId}:${senderId}:${reactionType}:${direction}`,
|
||||
forceSenderIsOwnerFalse: true,
|
||||
trusted: false,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user