diff --git a/examples/rbac_with_role_hierarchy_domains_model.conf b/examples/rbac_with_role_hierarchy_domains_model.conf new file mode 100644 index 0000000..060dd64 --- /dev/null +++ b/examples/rbac_with_role_hierarchy_domains_model.conf @@ -0,0 +1,14 @@ +[request_definition] +r = sub, dom, obj, act + +[policy_definition] +p = sub, dom, obj, act + +[role_definition] +g = _, _, _ + +[policy_effect] +e = some(where (p.eft == allow)) + +[matchers] +m = (g(r.sub, p.sub, r.dom) || g(r.sub, p.sub, '*')) && (p.dom == '*' || r.dom == p.dom) && r.obj == p.obj && r.act == p.act diff --git a/examples/rbac_with_role_hierarchy_domains_policy.csv b/examples/rbac_with_role_hierarchy_domains_policy.csv new file mode 100644 index 0000000..71b0936 --- /dev/null +++ b/examples/rbac_with_role_hierarchy_domains_policy.csv @@ -0,0 +1,25 @@ +p, abstract_role1, *, devis, read +p, abstract_role1, *, devis, create + +p, abstract_role2, *, devis, read +p, abstract_role2, *, organization, read +p, abstract_role2, *, organization, write + +g, role1, abstract_role1, tenant1 +g, role1, abstract_role1, tenant2 +g, role1, abstract_role1, tenant3 + +g, role2, abstract_role2, tenant1 +g, role2, abstract_role2, tenant2 +g, role2, abstract_role2, tenant3 + +g, super_user, abstract_role2, * + +g, michael, role1, tenant1 +g, antoine, role1, tenant2 +g, kevin, role1, tenant3 + +g, thomas, role2, tenant1 +g, lucie, role2, tenant3 + +g, theo, super_user, * diff --git a/src/enforcer.ts b/src/enforcer.ts index f784c13..2555e54 100644 --- a/src/enforcer.ts +++ b/src/enforcer.ts @@ -305,6 +305,7 @@ export class Enforcer extends ManagementEnforcer { let n: string | undefined; while ((n = q.shift()) !== undefined) { for (const rm of this.rmMap.values()) { + // Get roles for the specific domain const role = await rm.getRoles(n, ...domain); role.forEach((r) => { if (!res.has(r)) { @@ -312,6 +313,19 @@ export class Enforcer extends ManagementEnforcer { q.push(r); } }); + + // Also get roles with wildcard domain '*' if domain is provided and not already '*' + if (domain && domain.length > 0 && domain[0] !== '*') { + const wildcardDomain = [...domain]; + wildcardDomain[0] = '*'; + const wildcardRole = await rm.getRoles(n, ...wildcardDomain); + wildcardRole.forEach((r) => { + if (!res.has(r)) { + res.add(r); + q.push(r); + } + }); + } } } @@ -337,8 +351,17 @@ export class Enforcer extends ManagementEnforcer { for (const n of roles) { if (withDomain) { + // Get policies matching the specific domain const p = await this.getFilteredPolicy(0, n, ...domain); res.push(...p); + + // Also get policies with wildcard domain '*' if the domain is not already '*' + if (domain[0] !== '*') { + const wildcardDomain = [...domain]; + wildcardDomain[0] = '*'; + const wildcardP = await this.getFilteredPolicy(0, n, ...wildcardDomain); + res.push(...wildcardP); + } } else { const p = await this.getPermissionsForUser(n); res.push(...p); diff --git a/test/rbacRoleHierarchyDomains.test.ts b/test/rbacRoleHierarchyDomains.test.ts new file mode 100644 index 0000000..c6536a9 --- /dev/null +++ b/test/rbacRoleHierarchyDomains.test.ts @@ -0,0 +1,62 @@ +import { newEnforcer } from '../src'; + +describe('Test Role Hierarchy with Domains and Wildcards', () => { + test('getImplicitPermissionsForUser should handle wildcard domains', async () => { + const e = await newEnforcer( + 'examples/rbac_with_role_hierarchy_domains_model.conf', + 'examples/rbac_with_role_hierarchy_domains_policy.csv' + ); + + // Test michael in tenant1 - should get permissions from abstract_role1 with domain * + const michaelPerms = await e.getImplicitPermissionsForUser('michael', 'tenant1'); + + // Michael should have: + // - abstract_role1 permissions (devis read/create) with domain * + expect(michaelPerms).toContainEqual(['abstract_role1', '*', 'devis', 'read']); + expect(michaelPerms).toContainEqual(['abstract_role1', '*', 'devis', 'create']); + + // Test thomas in tenant1 - should get permissions from abstract_role2 with domain * + const thomasPerms = await e.getImplicitPermissionsForUser('thomas', 'tenant1'); + + // Thomas should have: + // - abstract_role2 permissions (devis read, organization read/write) with domain * + expect(thomasPerms).toContainEqual(['abstract_role2', '*', 'devis', 'read']); + expect(thomasPerms).toContainEqual(['abstract_role2', '*', 'organization', 'read']); + expect(thomasPerms).toContainEqual(['abstract_role2', '*', 'organization', 'write']); + + // Test theo with super_user - should get permissions from abstract_role2 with domain * + const theoPerms = await e.getImplicitPermissionsForUser('theo', 'tenant1'); + + // Theo should have: + // - abstract_role2 permissions (devis read, organization read/write) with domain * + expect(theoPerms).toContainEqual(['abstract_role2', '*', 'devis', 'read']); + expect(theoPerms).toContainEqual(['abstract_role2', '*', 'organization', 'read']); + expect(theoPerms).toContainEqual(['abstract_role2', '*', 'organization', 'write']); + }); + + test('enforce should work with wildcard domains in role hierarchy', async () => { + const e = await newEnforcer( + 'examples/rbac_with_role_hierarchy_domains_model.conf', + 'examples/rbac_with_role_hierarchy_domains_policy.csv' + ); + + // Michael in tenant1 should be able to read devis + expect(await e.enforce('michael', 'tenant1', 'devis', 'read')).toBe(true); + expect(await e.enforce('michael', 'tenant1', 'devis', 'create')).toBe(true); + + // Michael in tenant2 should NOT have access (not assigned to tenant2) + expect(await e.enforce('michael', 'tenant2', 'devis', 'read')).toBe(false); + + // Antoine in tenant2 should be able to read devis + expect(await e.enforce('antoine', 'tenant2', 'devis', 'read')).toBe(true); + + // Thomas in tenant1 should have organization permissions + expect(await e.enforce('thomas', 'tenant1', 'organization', 'read')).toBe(true); + expect(await e.enforce('thomas', 'tenant1', 'organization', 'write')).toBe(true); + + // Theo with super_user should have access to any tenant + expect(await e.enforce('theo', 'tenant1', 'organization', 'read')).toBe(true); + expect(await e.enforce('theo', 'tenant2', 'organization', 'read')).toBe(true); + expect(await e.enforce('theo', 'tenant3', 'organization', 'read')).toBe(true); + }); +});