Files
openclaw/src/gateway/server-methods.authorization.test.ts
wangmiao0668000666 27f702d68f fix(gateway): authorize plugin methods from attached registry (#94343)
Authorize plugin gateway methods against the exact registry attached to dispatch, preserving fallback behavior for dynamic methods and deleting one-off repro scripts.

Fixes #92044.

Co-authored-by: wangmiao0668000666 <wang.miao86@xydigit.com>
2026-06-19 11:56:24 +01:00

66 lines
2.2 KiB
TypeScript

import { afterEach, describe, expect, it, vi } from "vitest";
import { createEmptyPluginRegistry } from "../plugins/registry-empty.js";
import { setActivePluginRegistry } from "../plugins/runtime.js";
import {
createGatewayMethodRegistry,
createPluginGatewayMethodDescriptor,
} from "./methods/registry.js";
import { handleGatewayRequest } from "./server-methods.js";
import type { GatewayRequestHandler } from "./server-methods/types.js";
const METHOD = "workboard.cards.dispatch";
afterEach(() => {
setActivePluginRegistry(createEmptyPluginRegistry());
});
describe("gateway method authorization", () => {
async function dispatch(scopes: string[]) {
const handler: GatewayRequestHandler = ({ respond }) => respond(true, { ok: true });
const methodRegistry = createGatewayMethodRegistry([
createPluginGatewayMethodDescriptor({
pluginId: "workboard",
name: METHOD,
handler,
scope: "operator.write",
}),
]);
const respond = vi.fn();
// Reproduce a request whose attached dispatch registry is newer than the global runtime state.
setActivePluginRegistry(createEmptyPluginRegistry());
await handleGatewayRequest({
req: { type: "req", id: "req-1", method: METHOD },
respond,
client: {
connId: "conn-1",
connect: {
role: "operator",
scopes,
client: { id: "test", version: "1", platform: "test", mode: "test" },
minProtocol: 1,
maxProtocol: 1,
},
} as Parameters<typeof handleGatewayRequest>[0]["client"],
isWebchatConnect: () => false,
context: { logGateway: { warn: vi.fn() } } as unknown as Parameters<
typeof handleGatewayRequest
>[0]["context"],
methodRegistry,
});
return respond;
}
it("authorizes from the attached registry used for dispatch", async () => {
const allowed = await dispatch(["operator.write"]);
const denied = await dispatch(["operator.read"]);
expect(allowed).toHaveBeenCalledWith(true, { ok: true });
expect(denied).toHaveBeenCalledWith(
false,
undefined,
expect.objectContaining({ message: "missing scope: operator.write" }),
);
});
});