refactor: share route-level group gating decisions
This commit is contained in:
@@ -73,7 +73,10 @@ export type { WizardPrompter } from "../wizard/prompts.js";
|
||||
export { resolveInboundRouteEnvelopeBuilderWithRuntime } from "./inbound-envelope.js";
|
||||
export { createScopedPairingAccess } from "./pairing-access.js";
|
||||
export { issuePairingChallenge } from "../pairing/pairing-challenge.js";
|
||||
export { resolveSenderScopedGroupPolicy } from "./group-access.js";
|
||||
export {
|
||||
evaluateGroupRouteAccessForPolicy,
|
||||
resolveSenderScopedGroupPolicy,
|
||||
} from "./group-access.js";
|
||||
export { extractToolSend } from "./tool-send.js";
|
||||
export { resolveWebhookPath } from "./webhook-path.js";
|
||||
export type { WebhookInFlightLimiter } from "./webhook-request-guards.js";
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import {
|
||||
evaluateGroupRouteAccessForPolicy,
|
||||
evaluateSenderGroupAccess,
|
||||
evaluateSenderGroupAccessForPolicy,
|
||||
resolveSenderScopedGroupPolicy,
|
||||
@@ -59,6 +60,66 @@ describe("evaluateSenderGroupAccessForPolicy", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("evaluateGroupRouteAccessForPolicy", () => {
|
||||
it("blocks disabled policy", () => {
|
||||
expect(
|
||||
evaluateGroupRouteAccessForPolicy({
|
||||
groupPolicy: "disabled",
|
||||
routeAllowlistConfigured: true,
|
||||
routeMatched: true,
|
||||
routeEnabled: true,
|
||||
}),
|
||||
).toEqual({
|
||||
allowed: false,
|
||||
groupPolicy: "disabled",
|
||||
reason: "disabled",
|
||||
});
|
||||
});
|
||||
|
||||
it("blocks allowlist without configured routes", () => {
|
||||
expect(
|
||||
evaluateGroupRouteAccessForPolicy({
|
||||
groupPolicy: "allowlist",
|
||||
routeAllowlistConfigured: false,
|
||||
routeMatched: false,
|
||||
}),
|
||||
).toEqual({
|
||||
allowed: false,
|
||||
groupPolicy: "allowlist",
|
||||
reason: "empty_allowlist",
|
||||
});
|
||||
});
|
||||
|
||||
it("blocks unmatched allowlist route", () => {
|
||||
expect(
|
||||
evaluateGroupRouteAccessForPolicy({
|
||||
groupPolicy: "allowlist",
|
||||
routeAllowlistConfigured: true,
|
||||
routeMatched: false,
|
||||
}),
|
||||
).toEqual({
|
||||
allowed: false,
|
||||
groupPolicy: "allowlist",
|
||||
reason: "route_not_allowlisted",
|
||||
});
|
||||
});
|
||||
|
||||
it("blocks disabled matched route even when group policy is open", () => {
|
||||
expect(
|
||||
evaluateGroupRouteAccessForPolicy({
|
||||
groupPolicy: "open",
|
||||
routeAllowlistConfigured: true,
|
||||
routeMatched: true,
|
||||
routeEnabled: false,
|
||||
}),
|
||||
).toEqual({
|
||||
allowed: false,
|
||||
groupPolicy: "open",
|
||||
reason: "route_disabled",
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("evaluateSenderGroupAccess", () => {
|
||||
it("defaults missing provider config to allowlist", () => {
|
||||
const decision = evaluateSenderGroupAccess({
|
||||
|
||||
@@ -14,6 +14,19 @@ export type SenderGroupAccessDecision = {
|
||||
reason: SenderGroupAccessReason;
|
||||
};
|
||||
|
||||
export type GroupRouteAccessReason =
|
||||
| "allowed"
|
||||
| "disabled"
|
||||
| "empty_allowlist"
|
||||
| "route_not_allowlisted"
|
||||
| "route_disabled";
|
||||
|
||||
export type GroupRouteAccessDecision = {
|
||||
allowed: boolean;
|
||||
groupPolicy: GroupPolicy;
|
||||
reason: GroupRouteAccessReason;
|
||||
};
|
||||
|
||||
export function resolveSenderScopedGroupPolicy(params: {
|
||||
groupPolicy: GroupPolicy;
|
||||
groupAllowFrom: string[];
|
||||
@@ -24,6 +37,52 @@ export function resolveSenderScopedGroupPolicy(params: {
|
||||
return params.groupAllowFrom.length > 0 ? "allowlist" : "open";
|
||||
}
|
||||
|
||||
export function evaluateGroupRouteAccessForPolicy(params: {
|
||||
groupPolicy: GroupPolicy;
|
||||
routeAllowlistConfigured: boolean;
|
||||
routeMatched: boolean;
|
||||
routeEnabled?: boolean;
|
||||
}): GroupRouteAccessDecision {
|
||||
if (params.groupPolicy === "disabled") {
|
||||
return {
|
||||
allowed: false,
|
||||
groupPolicy: params.groupPolicy,
|
||||
reason: "disabled",
|
||||
};
|
||||
}
|
||||
|
||||
if (params.routeMatched && params.routeEnabled === false) {
|
||||
return {
|
||||
allowed: false,
|
||||
groupPolicy: params.groupPolicy,
|
||||
reason: "route_disabled",
|
||||
};
|
||||
}
|
||||
|
||||
if (params.groupPolicy === "allowlist") {
|
||||
if (!params.routeAllowlistConfigured) {
|
||||
return {
|
||||
allowed: false,
|
||||
groupPolicy: params.groupPolicy,
|
||||
reason: "empty_allowlist",
|
||||
};
|
||||
}
|
||||
if (!params.routeMatched) {
|
||||
return {
|
||||
allowed: false,
|
||||
groupPolicy: params.groupPolicy,
|
||||
reason: "route_not_allowlisted",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
allowed: true,
|
||||
groupPolicy: params.groupPolicy,
|
||||
reason: "allowed",
|
||||
};
|
||||
}
|
||||
|
||||
export function evaluateSenderGroupAccessForPolicy(params: {
|
||||
groupPolicy: GroupPolicy;
|
||||
providerMissingFallbackApplied?: boolean;
|
||||
|
||||
@@ -278,9 +278,12 @@ export {
|
||||
isNormalizedSenderAllowed,
|
||||
} from "./allow-from.js";
|
||||
export {
|
||||
evaluateGroupRouteAccessForPolicy,
|
||||
evaluateSenderGroupAccess,
|
||||
evaluateSenderGroupAccessForPolicy,
|
||||
resolveSenderScopedGroupPolicy,
|
||||
type GroupRouteAccessDecision,
|
||||
type GroupRouteAccessReason,
|
||||
type SenderGroupAccessDecision,
|
||||
type SenderGroupAccessReason,
|
||||
} from "./group-access.js";
|
||||
|
||||
@@ -93,7 +93,10 @@ export {
|
||||
} from "../security/dm-policy-shared.js";
|
||||
export { formatDocsLink } from "../terminal/links.js";
|
||||
export type { WizardPrompter } from "../wizard/prompts.js";
|
||||
export { resolveSenderScopedGroupPolicy } from "./group-access.js";
|
||||
export {
|
||||
evaluateGroupRouteAccessForPolicy,
|
||||
resolveSenderScopedGroupPolicy,
|
||||
} from "./group-access.js";
|
||||
export { createScopedPairingAccess } from "./pairing-access.js";
|
||||
export { formatResolvedUnresolvedNote } from "./resolution-notes.js";
|
||||
export { runPluginCommandWithTimeout } from "./run-command.js";
|
||||
|
||||
@@ -59,6 +59,7 @@ export type { WizardPrompter } from "../wizard/prompts.js";
|
||||
export { formatAllowFromLowercase } from "./allow-from.js";
|
||||
export { resolveSenderCommandAuthorization } from "./command-auth.js";
|
||||
export { resolveChannelAccountConfigBasePath } from "./config-paths.js";
|
||||
export { evaluateGroupRouteAccessForPolicy } from "./group-access.js";
|
||||
export { loadOutboundMediaFromUrl } from "./outbound-media.js";
|
||||
export { createScopedPairingAccess } from "./pairing-access.js";
|
||||
export { issuePairingChallenge } from "../pairing/pairing-challenge.js";
|
||||
|
||||
Reference in New Issue
Block a user