Utility for detecting loops in a list of roles.

This commit is contained in:
mike12345567 2024-09-26 18:26:35 +01:00
parent 10e187014a
commit fa9bb030c9
2 changed files with 108 additions and 0 deletions

View File

@ -0,0 +1,47 @@
import { Role } from "@budibase/types"
// Function to detect loops in roles
export function checkForRoleInheritanceLoops(roles: Role[]): boolean {
const roleMap = new Map<string, Role>()
roles.forEach(role => {
roleMap.set(role._id!, role)
})
const checked = new Set<string>()
const checking = new Set<string>()
function hasLoop(roleId: string): boolean {
if (checking.has(roleId)) {
return true
}
if (checked.has(roleId)) {
return false
}
checking.add(roleId)
const role = roleMap.get(roleId)
if (!role) {
// role not found - ignore
checking.delete(roleId)
return false
}
const inherits = Array.isArray(role.inherits)
? role.inherits
: [role.inherits]
for (const inheritedId of inherits) {
if (inheritedId && hasLoop(inheritedId)) {
return true
}
}
// mark this role has been fully checked
checking.delete(roleId)
checked.add(roleId)
return false
}
return !!roles.find(role => hasLoop(role._id!))
}

View File

@ -0,0 +1,61 @@
import { checkForRoleInheritanceLoops } from "../roles"
import { Role } from "@budibase/types"
/**
* This unit test exists as this utility will be used in the frontend and backend, confirmation
* of its API and expected results is useful since the backend tests won't confirm it works
* exactly as the frontend needs it to - easy to add specific test cases here that the frontend
* might need to check/cover.
*/
interface TestRole extends Omit<Role, "_id"> {
_id: string
}
let allRoles: TestRole[] = []
function role(id: string, inherits: string | string[]): TestRole {
const role = {
_id: id,
inherits: inherits,
name: "ROLE",
permissionId: "PERMISSION",
permissions: {}, // not needed for this test
}
allRoles.push(role)
return role
}
describe("role utilities", () => {
let role1: TestRole, role2: TestRole
beforeEach(() => {
role1 = role("role_1", [])
role2 = role("role_2", [role1._id])
})
afterEach(() => {
allRoles = []
})
function check(hasLoop: boolean) {
const result = checkForRoleInheritanceLoops(allRoles)
expect(result).toBe(hasLoop)
}
describe("checkForRoleInheritanceLoops", () => {
it("should confirm no loops", () => {
check(false)
})
it("should confirm there is a loop", () => {
const role3 = role("role_3", [role2._id])
const role4 = role("role_4", [role3._id, role2._id, role1._id])
role3.inherits = [
...(Array.isArray(role3.inherits) ? role3.inherits : []),
role4._id,
]
check(true)
})
})
})