mirror of https://github.com/openclaw/openclaw.git
269 lines
6.7 KiB
TypeScript
269 lines
6.7 KiB
TypeScript
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
import type { OpenClawConfig } from "./config.js";
|
|
import { resolveChannelGroupPolicy, resolveToolsBySender } from "./group-policy.js";
|
|
|
|
describe("resolveChannelGroupPolicy", () => {
|
|
it("fails closed when groupPolicy=allowlist and groups are missing", () => {
|
|
const cfg = {
|
|
channels: {
|
|
whatsapp: {
|
|
groupPolicy: "allowlist",
|
|
},
|
|
},
|
|
} as OpenClawConfig;
|
|
|
|
const policy = resolveChannelGroupPolicy({
|
|
cfg,
|
|
channel: "whatsapp",
|
|
groupId: "123@g.us",
|
|
});
|
|
|
|
expect(policy.allowlistEnabled).toBe(true);
|
|
expect(policy.allowed).toBe(false);
|
|
});
|
|
|
|
it("allows configured groups when groupPolicy=allowlist", () => {
|
|
const cfg = {
|
|
channels: {
|
|
whatsapp: {
|
|
groupPolicy: "allowlist",
|
|
groups: {
|
|
"123@g.us": { requireMention: true },
|
|
},
|
|
},
|
|
},
|
|
} as OpenClawConfig;
|
|
|
|
const policy = resolveChannelGroupPolicy({
|
|
cfg,
|
|
channel: "whatsapp",
|
|
groupId: "123@g.us",
|
|
});
|
|
|
|
expect(policy.allowlistEnabled).toBe(true);
|
|
expect(policy.allowed).toBe(true);
|
|
});
|
|
|
|
it("blocks all groups when groupPolicy=disabled", () => {
|
|
const cfg = {
|
|
channels: {
|
|
whatsapp: {
|
|
groupPolicy: "disabled",
|
|
groups: {
|
|
"*": { requireMention: false },
|
|
},
|
|
},
|
|
},
|
|
} as OpenClawConfig;
|
|
|
|
const policy = resolveChannelGroupPolicy({
|
|
cfg,
|
|
channel: "whatsapp",
|
|
groupId: "123@g.us",
|
|
});
|
|
|
|
expect(policy.allowed).toBe(false);
|
|
});
|
|
|
|
it("respects account-scoped groupPolicy overrides", () => {
|
|
const cfg = {
|
|
channels: {
|
|
whatsapp: {
|
|
groupPolicy: "open",
|
|
accounts: {
|
|
work: {
|
|
groupPolicy: "allowlist",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
} as OpenClawConfig;
|
|
|
|
const policy = resolveChannelGroupPolicy({
|
|
cfg,
|
|
channel: "whatsapp",
|
|
accountId: "work",
|
|
groupId: "123@g.us",
|
|
});
|
|
|
|
expect(policy.allowlistEnabled).toBe(true);
|
|
expect(policy.allowed).toBe(false);
|
|
});
|
|
|
|
it("allows groups when groupPolicy=allowlist with hasGroupAllowFrom but no groups", () => {
|
|
const cfg = {
|
|
channels: {
|
|
whatsapp: {
|
|
groupPolicy: "allowlist",
|
|
},
|
|
},
|
|
} as OpenClawConfig;
|
|
|
|
const policy = resolveChannelGroupPolicy({
|
|
cfg,
|
|
channel: "whatsapp",
|
|
groupId: "123@g.us",
|
|
hasGroupAllowFrom: true,
|
|
});
|
|
|
|
expect(policy.allowlistEnabled).toBe(true);
|
|
expect(policy.allowed).toBe(true);
|
|
});
|
|
|
|
it("still fails closed when groupPolicy=allowlist without groups or groupAllowFrom", () => {
|
|
const cfg = {
|
|
channels: {
|
|
whatsapp: {
|
|
groupPolicy: "allowlist",
|
|
},
|
|
},
|
|
} as OpenClawConfig;
|
|
|
|
const policy = resolveChannelGroupPolicy({
|
|
cfg,
|
|
channel: "whatsapp",
|
|
groupId: "123@g.us",
|
|
hasGroupAllowFrom: false,
|
|
});
|
|
|
|
expect(policy.allowlistEnabled).toBe(true);
|
|
expect(policy.allowed).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe("resolveToolsBySender", () => {
|
|
afterEach(() => {
|
|
vi.restoreAllMocks();
|
|
});
|
|
|
|
it("matches typed sender IDs", () => {
|
|
expect(
|
|
resolveToolsBySender({
|
|
toolsBySender: {
|
|
"id:user:alice": { allow: ["exec"] },
|
|
"*": { deny: ["exec"] },
|
|
},
|
|
senderId: "user:alice",
|
|
}),
|
|
).toEqual({ allow: ["exec"] });
|
|
});
|
|
|
|
it("does not allow senderName collisions to match id keys", () => {
|
|
const victimId = "f4ce8a7d-1111-2222-3333-444455556666";
|
|
expect(
|
|
resolveToolsBySender({
|
|
toolsBySender: {
|
|
[`id:${victimId}`]: { allow: ["exec", "fs.read"] },
|
|
"*": { deny: ["exec"] },
|
|
},
|
|
senderId: "attacker-real-id",
|
|
senderName: victimId,
|
|
senderUsername: "attacker",
|
|
}),
|
|
).toEqual({ deny: ["exec"] });
|
|
});
|
|
|
|
it("treats untyped legacy keys as senderId only", () => {
|
|
const warningSpy = vi.spyOn(process, "emitWarning").mockImplementation(() => undefined);
|
|
const victimId = "legacy-owner-id";
|
|
expect(
|
|
resolveToolsBySender({
|
|
toolsBySender: {
|
|
[victimId]: { allow: ["exec"] },
|
|
"*": { deny: ["exec"] },
|
|
},
|
|
senderId: "attacker-real-id",
|
|
senderName: victimId,
|
|
}),
|
|
).toEqual({ deny: ["exec"] });
|
|
|
|
expect(
|
|
resolveToolsBySender({
|
|
toolsBySender: {
|
|
[victimId]: { allow: ["exec"] },
|
|
"*": { deny: ["exec"] },
|
|
},
|
|
senderId: victimId,
|
|
senderName: "attacker",
|
|
}),
|
|
).toEqual({ allow: ["exec"] });
|
|
expect(warningSpy).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
it("matches username keys only against senderUsername", () => {
|
|
expect(
|
|
resolveToolsBySender({
|
|
toolsBySender: {
|
|
"username:alice": { allow: ["exec"] },
|
|
"*": { deny: ["exec"] },
|
|
},
|
|
senderId: "alice",
|
|
senderUsername: "other-user",
|
|
}),
|
|
).toEqual({ deny: ["exec"] });
|
|
|
|
expect(
|
|
resolveToolsBySender({
|
|
toolsBySender: {
|
|
"username:alice": { allow: ["exec"] },
|
|
"*": { deny: ["exec"] },
|
|
},
|
|
senderId: "other-id",
|
|
senderUsername: "@alice",
|
|
}),
|
|
).toEqual({ allow: ["exec"] });
|
|
});
|
|
|
|
it("matches e164 and name only when explicitly typed", () => {
|
|
expect(
|
|
resolveToolsBySender({
|
|
toolsBySender: {
|
|
"e164:+15550001111": { allow: ["exec"] },
|
|
"name:owner": { deny: ["exec"] },
|
|
},
|
|
senderE164: "+15550001111",
|
|
senderName: "owner",
|
|
}),
|
|
).toEqual({ allow: ["exec"] });
|
|
});
|
|
|
|
it("prefers id over username over name", () => {
|
|
expect(
|
|
resolveToolsBySender({
|
|
toolsBySender: {
|
|
"id:alice": { deny: ["exec"] },
|
|
"username:alice": { allow: ["exec"] },
|
|
"name:alice": { allow: ["read"] },
|
|
},
|
|
senderId: "alice",
|
|
senderUsername: "alice",
|
|
senderName: "alice",
|
|
}),
|
|
).toEqual({ deny: ["exec"] });
|
|
});
|
|
|
|
it("emits one deprecation warning per legacy key", () => {
|
|
const warningSpy = vi.spyOn(process, "emitWarning").mockImplementation(() => undefined);
|
|
const legacyKey = "legacy-warning-key";
|
|
const policy = {
|
|
[legacyKey]: { allow: ["exec"] },
|
|
"*": { deny: ["exec"] },
|
|
};
|
|
|
|
resolveToolsBySender({
|
|
toolsBySender: policy,
|
|
senderId: "other-id",
|
|
});
|
|
resolveToolsBySender({
|
|
toolsBySender: policy,
|
|
senderId: "other-id",
|
|
});
|
|
|
|
expect(warningSpy).toHaveBeenCalledTimes(1);
|
|
expect(String(warningSpy.mock.calls[0]?.[0])).toContain(`toolsBySender key "${legacyKey}"`);
|
|
expect(warningSpy.mock.calls[0]?.[1]).toMatchObject({
|
|
code: "OPENCLAW_TOOLS_BY_SENDER_UNTYPED_KEY",
|
|
});
|
|
});
|
|
});
|