refactor: share matched group policy evaluation

This commit is contained in:
Peter Steinberger
2026-03-07 23:39:04 +00:00
parent f319ec2dac
commit b0d9246768
8 changed files with 293 additions and 28 deletions

View File

@@ -1,5 +1,5 @@
import { describe, expect, it } from "vitest";
import { resolveNextcloudTalkAllowlistMatch } from "./policy.js";
import { resolveNextcloudTalkAllowlistMatch, resolveNextcloudTalkGroupAllow } from "./policy.js";
describe("nextcloud-talk policy", () => {
describe("resolveNextcloudTalkAllowlistMatch", () => {
@@ -30,4 +30,109 @@ describe("nextcloud-talk policy", () => {
).toBe(false);
});
});
describe("resolveNextcloudTalkGroupAllow", () => {
it("blocks disabled policy", () => {
expect(
resolveNextcloudTalkGroupAllow({
groupPolicy: "disabled",
outerAllowFrom: ["owner"],
innerAllowFrom: ["room-user"],
senderId: "owner",
}),
).toEqual({
allowed: false,
outerMatch: { allowed: false },
innerMatch: { allowed: false },
});
});
it("allows open policy", () => {
expect(
resolveNextcloudTalkGroupAllow({
groupPolicy: "open",
outerAllowFrom: [],
innerAllowFrom: [],
senderId: "owner",
}),
).toEqual({
allowed: true,
outerMatch: { allowed: true },
innerMatch: { allowed: true },
});
});
it("blocks allowlist mode when both outer and inner allowlists are empty", () => {
expect(
resolveNextcloudTalkGroupAllow({
groupPolicy: "allowlist",
outerAllowFrom: [],
innerAllowFrom: [],
senderId: "owner",
}),
).toEqual({
allowed: false,
outerMatch: { allowed: false },
innerMatch: { allowed: false },
});
});
it("requires inner match when only room-specific allowlist is configured", () => {
expect(
resolveNextcloudTalkGroupAllow({
groupPolicy: "allowlist",
outerAllowFrom: [],
innerAllowFrom: ["room-user"],
senderId: "room-user",
}),
).toEqual({
allowed: true,
outerMatch: { allowed: false },
innerMatch: { allowed: true, matchKey: "room-user", matchSource: "id" },
});
});
it("blocks when outer allowlist misses even if inner allowlist matches", () => {
expect(
resolveNextcloudTalkGroupAllow({
groupPolicy: "allowlist",
outerAllowFrom: ["team-owner"],
innerAllowFrom: ["room-user"],
senderId: "room-user",
}),
).toEqual({
allowed: false,
outerMatch: { allowed: false },
innerMatch: { allowed: true, matchKey: "room-user", matchSource: "id" },
});
});
it("allows when both outer and inner allowlists match", () => {
expect(
resolveNextcloudTalkGroupAllow({
groupPolicy: "allowlist",
outerAllowFrom: ["team-owner"],
innerAllowFrom: ["room-user"],
senderId: "team-owner",
}),
).toEqual({
allowed: false,
outerMatch: { allowed: true, matchKey: "team-owner", matchSource: "id" },
innerMatch: { allowed: false },
});
expect(
resolveNextcloudTalkGroupAllow({
groupPolicy: "allowlist",
outerAllowFrom: ["shared-user"],
innerAllowFrom: ["shared-user"],
senderId: "shared-user",
}),
).toEqual({
allowed: true,
outerMatch: { allowed: true, matchKey: "shared-user", matchSource: "id" },
innerMatch: { allowed: true, matchKey: "shared-user", matchSource: "id" },
});
});
});
});

View File

@@ -6,6 +6,7 @@ import type {
} from "openclaw/plugin-sdk/nextcloud-talk";
import {
buildChannelKeyCandidates,
evaluateMatchedGroupAccessForPolicy,
normalizeChannelSlug,
resolveChannelEntryMatchWithFallback,
resolveMentionGatingWithBypass,
@@ -128,19 +129,8 @@ export function resolveNextcloudTalkGroupAllow(params: {
innerAllowFrom: Array<string | number> | undefined;
senderId: string;
}): { allowed: boolean; outerMatch: AllowlistMatch; innerMatch: AllowlistMatch } {
if (params.groupPolicy === "disabled") {
return { allowed: false, outerMatch: { allowed: false }, innerMatch: { allowed: false } };
}
if (params.groupPolicy === "open") {
return { allowed: true, outerMatch: { allowed: true }, innerMatch: { allowed: true } };
}
const outerAllow = normalizeNextcloudTalkAllowlist(params.outerAllowFrom);
const innerAllow = normalizeNextcloudTalkAllowlist(params.innerAllowFrom);
if (outerAllow.length === 0 && innerAllow.length === 0) {
return { allowed: false, outerMatch: { allowed: false }, innerMatch: { allowed: false } };
}
const outerMatch = resolveNextcloudTalkAllowlistMatch({
allowFrom: params.outerAllowFrom,
senderId: params.senderId,
@@ -149,14 +139,32 @@ export function resolveNextcloudTalkGroupAllow(params: {
allowFrom: params.innerAllowFrom,
senderId: params.senderId,
});
const allowed = resolveNestedAllowlistDecision({
outerConfigured: outerAllow.length > 0 || innerAllow.length > 0,
outerMatched: outerAllow.length > 0 ? outerMatch.allowed : true,
innerConfigured: innerAllow.length > 0,
innerMatched: innerMatch.allowed,
const access = evaluateMatchedGroupAccessForPolicy({
groupPolicy: params.groupPolicy,
allowlistConfigured: outerAllow.length > 0 || innerAllow.length > 0,
allowlistMatched: resolveNestedAllowlistDecision({
outerConfigured: outerAllow.length > 0 || innerAllow.length > 0,
outerMatched: outerAllow.length > 0 ? outerMatch.allowed : true,
innerConfigured: innerAllow.length > 0,
innerMatched: innerMatch.allowed,
}),
});
return { allowed, outerMatch, innerMatch };
return {
allowed: access.allowed,
outerMatch:
params.groupPolicy === "open"
? { allowed: true }
: params.groupPolicy === "disabled"
? { allowed: false }
: outerMatch,
innerMatch:
params.groupPolicy === "open"
? { allowed: true }
: params.groupPolicy === "disabled"
? { allowed: false }
: innerMatch,
};
}
export function resolveNextcloudTalkMentionGate(params: {