diff --git a/plugins/rbac-backend/README.md b/plugins/rbac-backend/README.md index 26dfd190bc..b4767c53c2 100644 --- a/plugins/rbac-backend/README.md +++ b/plugins/rbac-backend/README.md @@ -175,3 +175,16 @@ The RBAC plugin offers the option to store policies in a database. It supports t - postgres: Recommended for production environments. Ensure that you have already configured the database backend for your Backstage instance, as the RBAC plugin utilizes the same database configuration. + +### Optional maximum depth + +The RBAC plugin also includes an option max depth feature for organizations with potentially complex group hierarchy, this configuration value will ensure that the RBAC plugin will stop at a certain depth when building user graphs. + +```YAML +permission: + enabled: true + rbac: + maxDepth: 1 +``` + +The maxDepth must be greater than 0 to ensure that the graphs are built correctly. Also the graph will be built with a hierarchy of 1 + maxDepth. diff --git a/plugins/rbac-backend/config.d.ts b/plugins/rbac-backend/config.d.ts index 863d6d62b2..f53c69e6db 100644 --- a/plugins/rbac-backend/config.d.ts +++ b/plugins/rbac-backend/config.d.ts @@ -38,6 +38,11 @@ export interface Config { * The RBAC plugin will handle access control for plugins included in this list. */ pluginsWithPermission?: string[]; + /** + * An optional value that limits the depth when building the hierarchy group graph + * @visibility frontend + */ + maxDepth?: number; }; }; } diff --git a/plugins/rbac-backend/src/file-permissions/csv.test.ts b/plugins/rbac-backend/src/file-permissions/csv.test.ts index 2c28a7d257..3cd0e7fb88 100644 --- a/plugins/rbac-backend/src/file-permissions/csv.test.ts +++ b/plugins/rbac-backend/src/file-permissions/csv.test.ts @@ -1,4 +1,5 @@ import { TokenManager } from '@backstage/backend-common'; +import { ConfigReader } from '@backstage/config'; import { Adapter, @@ -115,11 +116,14 @@ async function createEnforcer( const catalogDBClient = Knex.knex({ client: MockClient }); const enf = await newEnforcer(theModel, adapter); + const config = newConfigReader(); + const rm = new BackstageRoleManager( catalogApi, log, tokenManager, catalogDBClient, + config, ); enf.setRoleManager(rm); enf.enableAutoBuildRoleLinks(false); @@ -1005,3 +1009,34 @@ describe('CSV file', () => { }); }); }); + +function newConfigReader( + users?: Array<{ name: string }>, + superUsers?: Array<{ name: string }>, +): ConfigReader { + const testUsers = [ + { + name: 'user:default/guest', + }, + { + name: 'group:default/guests', + }, + ]; + + return new ConfigReader({ + permission: { + rbac: { + admin: { + users: users || testUsers, + superUsers: superUsers, + }, + }, + }, + backend: { + database: { + client: 'better-sqlite3', + connection: ':memory:', + }, + }, + }); +} diff --git a/plugins/rbac-backend/src/role-manager/ancestor-search-memo.test.ts b/plugins/rbac-backend/src/role-manager/ancestor-search-memo.test.ts index e95402dc28..dfcf197986 100644 --- a/plugins/rbac-backend/src/role-manager/ancestor-search-memo.test.ts +++ b/plugins/rbac-backend/src/role-manager/ancestor-search-memo.test.ts @@ -1,4 +1,4 @@ -import { Entity } from '@backstage/catalog-model'; +import { Entity, GroupEntity } from '@backstage/catalog-model'; import * as Knex from 'knex'; import { createTracker, MockClient, Tracker } from 'knex-mock-client'; @@ -41,28 +41,24 @@ describe('ancestor-search-memo', () => { ]; const testGroups = [ - createGroupEntity( - 'group:default/team-a', - 'group:default/team-b', - [], - ['adam'], - ), - createGroupEntity('group:default/team-b', 'group:default/team-c', [], []), - createGroupEntity('group:default/team-c', '', [], []), - createGroupEntity( - 'group:default/team-d', - 'group:default/team-e', - [], - ['george'], - ), - createGroupEntity('group:default/team-e', 'group:default/team-f', [], []), - createGroupEntity('group:default/team-f', '', [], []), + createGroupEntity('team-a', 'team-b', [], ['adam']), + createGroupEntity('team-b', 'team-c', [], []), + createGroupEntity('team-c', '', [], []), + createGroupEntity('team-d', 'team-e', [], ['george']), + createGroupEntity('team-e', 'team-f', [], []), + createGroupEntity('team-f', '', [], []), ]; + const testUserGroups = [createGroupEntity('team-a', 'team-b', [], ['adam'])]; + const catalogApiMock: any = { - getEntities: jest - .fn() - .mockImplementation(() => Promise.resolve({ items: testGroups })), + getEntities: jest.fn().mockImplementation((arg: any) => { + const hasMember = arg.filter['relations.hasMember']; + if (hasMember && hasMember === 'user:default/adam') { + return { items: testUserGroups }; + } + return { items: testGroups }; + }), }; const catalogDBClient = Knex.knex({ client: MockClient }); @@ -74,12 +70,16 @@ describe('ancestor-search-memo', () => { authenticate: jest.fn().mockImplementation(), }; - const asm = new AncestorSearchMemo( - 'user:default/adam', - tokenManagerMock, - catalogApiMock, - catalogDBClient, - ); + let asm: AncestorSearchMemo; + + beforeEach(() => { + asm = new AncestorSearchMemo( + 'user:default/adam', + tokenManagerMock, + catalogApiMock, + catalogDBClient, + ); + }); describe('getAllGroups and getAllRelations', () => { let tracker: Tracker; @@ -104,7 +104,6 @@ describe('ancestor-search-memo', () => { it('should return all groups', async () => { const allGroupsTest = await asm.getAllGroups(); - // @ts-ignore expect(allGroupsTest).toEqual(testGroups); }); @@ -137,14 +136,8 @@ describe('ancestor-search-memo', () => { }); it('should return all user groups', async () => { - tracker.on - .select( - /select "source_entity_ref", "target_entity_ref" from "relations" where "type" = ?/, - ) - .response(userRelations); - const relations = await asm.getUserRelations(); - - expect(relations).toEqual(userRelations); + const userGroups = await asm.getUserGroups(); + expect(userGroups).toEqual(testUserGroups); }); it('should fail to return anything when there is an error getting user relations', async () => { @@ -187,6 +180,7 @@ describe('ancestor-search-memo', () => { asm, relation as Relation, allRelationsTest as Relation[], + 0, ), ); @@ -196,6 +190,103 @@ describe('ancestor-search-memo', () => { expect(asm.hasEntityRef('group:default/team-c')).toBeTruthy(); expect(asm.hasEntityRef('group:default/team-d')).toBeFalsy(); }); + + // maxDepth of one stops here + // | + // user:default/adam -> group:default/team-a -> group:default/team-b -> group:default/team-c + it('should build the graph but stop based on the maxDepth', async () => { + const asmMaxDepth = new AncestorSearchMemo( + 'user:default/adam', + tokenManagerMock, + catalogApiMock, + catalogDBClient, + 1, + ); + + tracker.on + .select( + /select "source_entity_ref", "target_entity_ref" from "relations" where "type" = ?/, + ) + .response(userRelations); + const userRelationsTest = await asmMaxDepth.getUserRelations(); + + tracker.reset(); + tracker.on + .select( + /select "source_entity_ref", "target_entity_ref" from "relations" where "type" = ?/, + ) + .response(allRelations); + const allRelationsTest = await asmMaxDepth.getAllRelations(); + + userRelationsTest.forEach(relation => + asmMaxDepth.traverseRelations( + asmMaxDepth, + relation as Relation, + allRelationsTest as Relation[], + 0, + ), + ); + + expect(asmMaxDepth.hasEntityRef('user:default/adam')).toBeTruthy(); + expect(asmMaxDepth.hasEntityRef('group:default/team-a')).toBeTruthy(); + expect(asmMaxDepth.hasEntityRef('group:default/team-b')).toBeTruthy(); + expect(asmMaxDepth.hasEntityRef('group:default/team-c')).toBeFalsy(); + expect(asmMaxDepth.hasEntityRef('group:default/team-d')).toBeFalsy(); + }); + }); + + describe('traverseGroups', () => { + // user:default/adam -> group:default/team-a -> group:default/team-b -> group:default/team-c + it('should build a graph for a particular user', async () => { + const userGroupsTest = await asm.getUserGroups(); + + const allGroupsTest = await asm.getAllGroups(); + + userGroupsTest.forEach(group => + asm.traverseGroups( + asm, + group as GroupEntity, + allGroupsTest as GroupEntity[], + 0, + ), + ); + + expect(asm.hasEntityRef('group:default/team-a')).toBeTruthy(); + expect(asm.hasEntityRef('group:default/team-b')).toBeTruthy(); + expect(asm.hasEntityRef('group:default/team-c')).toBeTruthy(); + expect(asm.hasEntityRef('group:default/team-d')).toBeFalsy(); + }); + + // maxDepth of one stops here + // | + // user:default/adam -> group:default/team-a -> group:default/team-b -> group:default/team-c + it('should build the graph but stop based on the maxDepth', async () => { + const asmMaxDepth = new AncestorSearchMemo( + 'user:default/adam', + tokenManagerMock, + catalogApiMock, + catalogDBClient, + 1, + ); + + const userGroupsTest = await asmMaxDepth.getUserGroups(); + + const allGroupsTest = await asmMaxDepth.getAllGroups(); + + userGroupsTest.forEach(group => + asmMaxDepth.traverseGroups( + asmMaxDepth, + group as GroupEntity, + allGroupsTest as GroupEntity[], + 0, + ), + ); + + expect(asmMaxDepth.hasEntityRef('group:default/team-a')).toBeTruthy(); + expect(asmMaxDepth.hasEntityRef('group:default/team-b')).toBeTruthy(); + expect(asmMaxDepth.hasEntityRef('group:default/team-c')).toBeFalsy(); + expect(asmMaxDepth.hasEntityRef('group:default/team-d')).toBeFalsy(); + }); }); describe('buildUserGraph', () => { @@ -211,6 +302,12 @@ describe('ancestor-search-memo', () => { const asmDBSpy = jest .spyOn(asmUserGraph, 'doesRelationTableExist') .mockImplementation(() => Promise.resolve(true)); + const userRelationsSpy = jest + .spyOn(asmUserGraph, 'getUserRelations') + .mockImplementation(() => Promise.resolve(userRelations)); + const allRelationsSpy = jest + .spyOn(asmUserGraph, 'getAllRelations') + .mockImplementation(() => Promise.resolve(allRelations)); beforeAll(() => { tracker = createTracker(catalogDBClient); @@ -222,25 +319,16 @@ describe('ancestor-search-memo', () => { // user:default/adam -> group:default/team-a -> group:default/team-b -> group:default/team-c it('should build the user graph using relations table', async () => { - tracker.on - .select( - /select "source_entity_ref", "target_entity_ref" from "relations" where "type" = ?/, - ) - .response(userRelations); - tracker.reset(); - tracker.on - .select( - /select "source_entity_ref", "target_entity_ref" from "relations" where "type" = ?/, - ) - .response(allRelations); await asmUserGraph.buildUserGraph(asmUserGraph); expect(asmDBSpy).toHaveBeenCalled(); - expect(asm.hasEntityRef('user:default/adam')).toBeTruthy(); - expect(asm.hasEntityRef('group:default/team-a')).toBeTruthy(); - expect(asm.hasEntityRef('group:default/team-b')).toBeTruthy(); - expect(asm.hasEntityRef('group:default/team-c')).toBeTruthy(); - expect(asm.hasEntityRef('group:default/team-d')).toBeFalsy(); + expect(userRelationsSpy).toHaveBeenCalled(); + expect(allRelationsSpy).toHaveBeenCalled(); + expect(asmUserGraph.hasEntityRef('user:default/adam')).toBeTruthy(); + expect(asmUserGraph.hasEntityRef('group:default/team-a')).toBeTruthy(); + expect(asmUserGraph.hasEntityRef('group:default/team-b')).toBeTruthy(); + expect(asmUserGraph.hasEntityRef('group:default/team-c')).toBeTruthy(); + expect(asmUserGraph.hasEntityRef('group:default/team-d')).toBeFalsy(); }); }); diff --git a/plugins/rbac-backend/src/role-manager/ancestor-search-memo.ts b/plugins/rbac-backend/src/role-manager/ancestor-search-memo.ts index 5aede45cf4..351a117240 100644 --- a/plugins/rbac-backend/src/role-manager/ancestor-search-memo.ts +++ b/plugins/rbac-backend/src/role-manager/ancestor-search-memo.ts @@ -25,18 +25,21 @@ export class AncestorSearchMemo { private catalogDBClient: Knex; private userEntityRef: string; + private maxDepth?: number; constructor( userEntityRef: string, tokenManager: TokenManager, catalogApi: CatalogApi, catalogDBClient: Knex, + maxDepth?: number, ) { this.graph = new Graph({ directed: true }); this.userEntityRef = userEntityRef; this.tokenManager = tokenManager; this.catalogApi = catalogApi; this.catalogDBClient = catalogDBClient; + this.maxDepth = maxDepth; } isAcyclic(): boolean { @@ -126,7 +129,17 @@ export class AncestorSearchMemo { } } - traverseGroups(memo: AncestorSearchMemo, group: Entity, allGroups: Entity[]) { + traverseGroups( + memo: AncestorSearchMemo, + group: Entity, + allGroups: Entity[], + current_depth: number, + ) { + const depth = current_depth + 1; + if (this.maxDepth && current_depth >= this.maxDepth) { + return; + } + const groupsRefs = new Set(); const groupName = `group:${group.metadata.namespace?.toLocaleLowerCase( 'en-US', @@ -147,7 +160,7 @@ export class AncestorSearchMemo { } if (groupsRefs.size > 0 && memo.isAcyclic()) { - this.traverseGroups(memo, parentGroup!, allGroups); + this.traverseGroups(memo, parentGroup!, allGroups, depth); } } @@ -155,7 +168,14 @@ export class AncestorSearchMemo { memo: AncestorSearchMemo, relation: Relation, allRelations: Relation[], + current_depth: number, ) { + // We add one to the maxDepth here because the user is considered the starting node + if (this.maxDepth && current_depth >= this.maxDepth + 1) { + return; + } + const depth = current_depth + 1; + if (!memo.hasEntityRef(relation.source_entity_ref)) { memo.setNode(relation.source_entity_ref); } @@ -167,7 +187,7 @@ export class AncestorSearchMemo { ); if (parentGroup && memo.isAcyclic()) { - this.traverseRelations(memo, parentGroup!, allRelations); + this.traverseRelations(memo, parentGroup!, allRelations, depth); } } @@ -180,13 +200,14 @@ export class AncestorSearchMemo { memo, group as Relation, allRelations as Relation[], + 0, ), ); } else { const userGroups = await this.getUserGroups(); const allGroups = await this.getAllGroups(); userGroups.forEach(group => - this.traverseGroups(memo, group as Entity, allGroups as Entity[]), + this.traverseGroups(memo, group as Entity, allGroups as Entity[], 0), ); } } diff --git a/plugins/rbac-backend/src/role-manager/role-manager.test.ts b/plugins/rbac-backend/src/role-manager/role-manager.test.ts index 98847f4361..acfbf4dd14 100644 --- a/plugins/rbac-backend/src/role-manager/role-manager.test.ts +++ b/plugins/rbac-backend/src/role-manager/role-manager.test.ts @@ -1,6 +1,7 @@ import { TokenManager } from '@backstage/backend-common'; import { CatalogApi } from '@backstage/catalog-client'; import { Entity } from '@backstage/catalog-model'; +import { ConfigReader } from '@backstage/config'; import * as Knex from 'knex'; import { MockClient } from 'knex-mock-client'; @@ -31,11 +32,14 @@ describe('BackstageRoleManager', () => { let roleManager: BackstageRoleManager; beforeEach(() => { + const config = newConfigReader(); + roleManager = new BackstageRoleManager( catalogApiMock as CatalogApi, loggerMock as Logger, tokenManagerMock as TokenManager, catalogDBClient, + config, ); }); @@ -987,6 +991,107 @@ describe('BackstageRoleManager', () => { expect(result).toBeTruthy(); }); + // user:default/mike should inherits role from group:default/team-e, and we have a complex graph + // So return true on call hasLink. + // + // Hierarchy: + // role:default/team-e + // ↓ + // |----------------- group:default/team-e ---------| + // ↓ | + // | ----------------- group:default/team-f ----| | + // ↓ ↓ | + // group:default/team-a -> role:default/team-a group:default/team-b group:default/team-h -> role:default/team-h + // ↓ ↓ ↓ + // group:default/team-c -> role:default/team-c group:default/team-d group:default/team-g -> role:default/team-g + // ↓ ↓ ↓ + // user:default/mike -------------------|---------------------------------| + // + it('should return false for hasLink, when user:default/mike inherits role from group tree with group:default/team-e, complex tree, maxDepth of 3', async () => { + const config = newConfigReader(1); + + const roleManagerMaxDepth = new BackstageRoleManager( + catalogApiMock as CatalogApi, + loggerMock as Logger, + tokenManagerMock as TokenManager, + catalogDBClient, + config, + ); + + const groupCMock = createGroupEntity('team-c', 'team-a', [], ['mike']); + const groupDMock = createGroupEntity('team-d', 'team-b', [], ['mike']); + const groupAMock = createGroupEntity('team-a', 'team-f', ['team-c']); + const groupBMock = createGroupEntity('team-b', 'team-f', ['team-d']); + const groupFMock = createGroupEntity('team-f', 'team-e', [ + 'team-a', + 'team-b', + ]); + const groupEMock = createGroupEntity('team-e', undefined, [ + 'team-f', + 'team-g', + ]); + const groupGMock = createGroupEntity('team-g', 'team-h', [], ['mike']); + const groupHMock = createGroupEntity('team-h', 'team-e', ['team-g'], []); + + catalogApiMock.getEntities.mockImplementation((arg: any) => { + const hasMember = arg.filter['relations.hasMember']; + if (hasMember && hasMember === 'user:default/mike') { + return { items: [groupCMock, groupDMock, groupGMock] }; + } + return { + items: [ + groupCMock, + groupDMock, + groupAMock, + groupBMock, + groupFMock, + groupEMock, + groupGMock, + groupHMock, + ], + }; + }); + + roleManagerMaxDepth.addLink( + 'group:default/team-a', + 'role:default/team-a', + ); + roleManagerMaxDepth.addLink( + 'group:default/team-c', + 'role:default/team-c', + ); + roleManagerMaxDepth.addLink( + 'group:default/team-e', + 'role:default/team-e', + ); + roleManagerMaxDepth.addLink( + 'group:default/team-g', + 'role:default/team-g', + ); + + roleManagerMaxDepth.addLink( + 'group:default/team-h', + 'role:default/team-h', + ); + + const resultE = await roleManagerMaxDepth.hasLink( + 'user:default/mike', + 'role:default/team-e', + ); + const resultG = await roleManagerMaxDepth.hasLink( + 'user:default/mike', + 'role:default/team-g', + ); + const resultH = await roleManagerMaxDepth.hasLink( + 'user:default/mike', + 'role:default/team-h', + ); + + expect(resultE).toBeFalsy(); + expect(resultH).toBeTruthy(); + expect(resultG).toBeTruthy(); + }); + // user:default/mike should inherits from group:default/team-a, but we have cycle dependency: team-a -> team-c. // So return false on call hasLink. // @@ -1213,3 +1318,36 @@ describe('BackstageRoleManager', () => { return entity; } }); + +function newConfigReader( + maxDepth?: number, + users?: Array<{ name: string }>, + superUsers?: Array<{ name: string }>, +): ConfigReader { + const testUsers = [ + { + name: 'user:default/guest', + }, + { + name: 'group:default/guests', + }, + ]; + + return new ConfigReader({ + permission: { + rbac: { + admin: { + users: users || testUsers, + superUsers: superUsers, + }, + maxDepth, + }, + }, + backend: { + database: { + client: 'better-sqlite3', + connection: ':memory:', + }, + }, + }); +} diff --git a/plugins/rbac-backend/src/role-manager/role-manager.ts b/plugins/rbac-backend/src/role-manager/role-manager.ts index f5104321f2..de1d32df10 100644 --- a/plugins/rbac-backend/src/role-manager/role-manager.ts +++ b/plugins/rbac-backend/src/role-manager/role-manager.ts @@ -1,6 +1,7 @@ import { TokenManager } from '@backstage/backend-common'; import { CatalogApi } from '@backstage/catalog-client'; import { parseEntityRef } from '@backstage/catalog-model'; +import { Config } from '@backstage/config'; import { RoleManager } from 'casbin'; import { Knex } from 'knex'; @@ -11,13 +12,22 @@ import { RoleList } from './role-list'; export class BackstageRoleManager implements RoleManager { private allRoles: Map; + private maxDepth?: number; constructor( private readonly catalogApi: CatalogApi, private readonly log: Logger, private readonly tokenManager: TokenManager, private readonly catalogDBClient: Knex, + private readonly config: Config, ) { this.allRoles = new Map(); + const rbacConfig = this.config.getOptionalConfig('permission.rbac'); + this.maxDepth = rbacConfig?.getOptionalNumber('maxDepth'); + if (this.maxDepth !== undefined && this.maxDepth! <= 0) { + throw new Error( + 'Max Depth for RBAC group hierarchy must be greater than zero', + ); + } } /** @@ -99,6 +109,7 @@ export class BackstageRoleManager implements RoleManager { this.tokenManager, this.catalogApi, this.catalogDBClient, + this.maxDepth, ); await memo.buildUserGraph(memo); @@ -175,6 +186,7 @@ export class BackstageRoleManager implements RoleManager { this.tokenManager, this.catalogApi, this.catalogDBClient, + this.maxDepth, ); await memo.getAllGroups(); await memo.buildUserGraph(memo); diff --git a/plugins/rbac-backend/src/service/enforcer-delegate.test.ts b/plugins/rbac-backend/src/service/enforcer-delegate.test.ts index b2626c1d47..9ec3ef266b 100644 --- a/plugins/rbac-backend/src/service/enforcer-delegate.test.ts +++ b/plugins/rbac-backend/src/service/enforcer-delegate.test.ts @@ -173,6 +173,7 @@ describe('EnforcerDelegate', () => { logger, tokenManagerMock, catalogDBClient, + config, ); enf.setRoleManager(rm); enf.enableAutoBuildRoleLinks(false); diff --git a/plugins/rbac-backend/src/service/permission-policy.test.ts b/plugins/rbac-backend/src/service/permission-policy.test.ts index 30e1e3bf62..c528048497 100644 --- a/plugins/rbac-backend/src/service/permission-policy.test.ts +++ b/plugins/rbac-backend/src/service/permission-policy.test.ts @@ -133,7 +133,7 @@ describe('RBACPermissionPolicy Tests', () => { const stringPolicy = `p, user:default/known_user, test-resource, update, allow `; const config = newConfigReader(); const adapter = await newAdapter(config, stringPolicy); - const enfDelegate = await newEnforcerDelegate(adapter); + const enfDelegate = await newEnforcerDelegate(adapter, config); const policy = await newPermissionPolicy(config, enfDelegate); @@ -147,7 +147,7 @@ describe('RBACPermissionPolicy Tests', () => { beforeEach(async () => { const config = newConfigReader(); const adapter = await newAdapter(config); - enfDelegate = await newEnforcerDelegate(adapter); + enfDelegate = await newEnforcerDelegate(adapter, config); policy = await newPermissionPolicy(config, enfDelegate); catalogApi.getEntities.mockReturnValue({ items: [] }); @@ -305,6 +305,7 @@ describe('RBACPermissionPolicy Tests', () => { enforcerDelegate = await newEnforcerDelegate( adapter, + config, storedPolicies, storedGroupPolicies, policyMetadataStorage, @@ -379,6 +380,7 @@ describe('RBACPermissionPolicy Tests', () => { enforcerDelegate = await newEnforcerDelegate( adapter, + config, storedPolicies, storedGroupPolicies, policyMetadataStorage, @@ -480,6 +482,7 @@ describe('RBACPermissionPolicy Tests', () => { enforcerDelegate = await newEnforcerDelegate( adapter, + config, storedPolicies, storedGroupPolicies, policyMetadataStorage, @@ -571,6 +574,7 @@ describe('RBACPermissionPolicy Tests', () => { enforcerDelegate = await newEnforcerDelegate( adapter, + config, storedPolicies, storedGroupPolicies, policyMetadataStorage, @@ -645,6 +649,7 @@ describe('RBACPermissionPolicy Tests', () => { enforcerDelegate = await newEnforcerDelegate( adapter, + config, storedPolicies, storedGroupPolicies, policyMetadataStorage, @@ -734,6 +739,7 @@ describe('RBACPermissionPolicy Tests', () => { enforcerDelegate = await newEnforcerDelegate( adapter, + config, storedPolicies, storedGroupPolicies, policyMetadataStorage, @@ -842,7 +848,7 @@ describe('RBACPermissionPolicy Tests', () => { ); const config = newConfigReader(basicAndResourcePermissions); const adapter = await newAdapter(config); - enfDelegate = await newEnforcerDelegate(adapter); + enfDelegate = await newEnforcerDelegate(adapter, config); policy = await newPermissionPolicy( config, @@ -1110,7 +1116,7 @@ describe('RBACPermissionPolicy Tests', () => { const config = newConfigReader(csvPermFile, admins, superUser); const adapter = await newAdapter(config); - enfDelegate = await newEnforcerDelegate(adapter); + enfDelegate = await newEnforcerDelegate(adapter, config); await enfDelegate.addGroupingPolicy(oldGroupPolicy, { source: 'configuration', @@ -1230,7 +1236,7 @@ describe('Policy checks for resourced permissions defined by name', () => { beforeEach(async () => { const config = newConfigReader(); const adapter = await newAdapter(config); - enfDelegate = await newEnforcerDelegate(adapter); + enfDelegate = await newEnforcerDelegate(adapter, config); policy = await newPermissionPolicy( config, enfDelegate, @@ -1411,7 +1417,7 @@ describe('Policy checks for users and groups', () => { const config = newConfigReader(policyChecksCSV); const adapter = await newAdapter(config); - const enfDelegate = await newEnforcerDelegate(adapter); + const enfDelegate = await newEnforcerDelegate(adapter, config); policy = await newPermissionPolicy(config, enfDelegate); @@ -1801,6 +1807,7 @@ describe('Policy checks for conditional policies', () => { adapter, logger, tokenManagerMock, + config, ); const enfDelegate = new EnforcerDelegate( @@ -2123,6 +2130,7 @@ async function createEnforcer( adapter: Adapter, logger: Logger, tokenManager: TokenManager, + config: ConfigReader, ): Promise { const catalogDBClient = Knex.knex({ client: MockClient }); const enf = await newEnforcer(theModel, adapter); @@ -2132,6 +2140,7 @@ async function createEnforcer( logger, tokenManager, catalogDBClient, + config, ); enf.setRoleManager(rm); enf.enableAutoBuildRoleLinks(false); @@ -2142,6 +2151,7 @@ async function createEnforcer( async function newEnforcerDelegate( adapter: Adapter, + config: ConfigReader, storedPolicies?: string[][], storedGroupingPolicies?: string[][], policyMock?: PolicyMetadataStorage, @@ -2149,7 +2159,13 @@ async function newEnforcerDelegate( const theModel = newModelFromString(MODEL); const logger = getVoidLogger(); - const enf = await createEnforcer(theModel, adapter, logger, tokenManagerMock); + const enf = await createEnforcer( + theModel, + adapter, + logger, + tokenManagerMock, + config, + ); if (storedPolicies) { await enf.addPolicies(storedPolicies); diff --git a/plugins/rbac-backend/src/service/policy-builder.ts b/plugins/rbac-backend/src/service/policy-builder.ts index 08eded2075..8112b43438 100644 --- a/plugins/rbac-backend/src/service/policy-builder.ts +++ b/plugins/rbac-backend/src/service/policy-builder.ts @@ -60,6 +60,7 @@ export class PolicyBuilder { env.logger, env.tokenManager, catalogDBClient, + env.config, ); enf.setRoleManager(rm); enf.enableAutoBuildRoleLinks(false);