Enforce the permission-name rule at discovery, for every plugin

This commit is contained in:
2026-08-05 13:00:23 +02:00
parent 90cbc47607
commit fb4382be9d
14 changed files with 153 additions and 54 deletions
@@ -12,7 +12,6 @@ import {
buildPermissionsListModel,
expandToEffectiveUsers,
isPermissionPathSegment,
isValidPermissionName,
permissionGrantTuple,
} from "./admin-permissions.ts";
import type { ExpandTree, RelationTuple } from "#plugin-api";
@@ -23,14 +22,6 @@ const userTuple = (permission: string, n: number): RelationTuple =>
const groupTuple = (permission: string, group: string): RelationTuple =>
({ namespace: "Permission", object: permission, relation: "granted", subject_set: { namespace: "Group", object: group, relation: "members" } });
test("isValidPermissionName requires <resource>:<action> so the convention holds for anything created here", () => {
for (const ok of ["users:read", "scheduling:write", "oauth2-clients:read", "team-a:a1_b9"]) assert.equal(isValidPermissionName(ok), true, ok);
// A bare word is what this rule exists to stop — "admin" says who you are, not what you may do.
for (const bad of ["admin", "", "Users:read", "users:", ":read", "users:read:extra", "a b:read", "-bad:read", `${"a".repeat(60)}:read`]) {
assert.equal(isValidPermissionName(bad), false, bad);
}
});
test("isPermissionPathSegment stays loose enough to address a permission that predates the rule", () => {
// Addressing is not creating: an "admin" tuple left in Keto must still open and delete, or it is
// stranded. It only has to be a safe URL/Keto object name.