diff --git a/.changeset/angry-ears-wash.md b/.changeset/angry-ears-wash.md new file mode 100644 index 0000000000..e91693483c --- /dev/null +++ b/.changeset/angry-ears-wash.md @@ -0,0 +1,5 @@ +--- +"@janus-idp/backstage-plugin-rbac-backend": patch +--- + +Refactors the rbac backend plugin to move the admin role and admin permission creation to a separate file diff --git a/plugins/rbac-backend/src/__fixtures__/data/invalid-conditions/bad-conditions-yaml.yaml b/plugins/rbac-backend/__fixtures__/data/invalid-conditions/bad-conditions-yaml.yaml similarity index 100% rename from plugins/rbac-backend/src/__fixtures__/data/invalid-conditions/bad-conditions-yaml.yaml rename to plugins/rbac-backend/__fixtures__/data/invalid-conditions/bad-conditions-yaml.yaml diff --git a/plugins/rbac-backend/src/__fixtures__/data/invalid-conditions/invalid-yaml.yaml b/plugins/rbac-backend/__fixtures__/data/invalid-conditions/invalid-yaml.yaml similarity index 100% rename from plugins/rbac-backend/src/__fixtures__/data/invalid-conditions/invalid-yaml.yaml rename to plugins/rbac-backend/__fixtures__/data/invalid-conditions/invalid-yaml.yaml diff --git a/plugins/rbac-backend/src/__fixtures__/data/invalid-csv/duplicate-policy.csv b/plugins/rbac-backend/__fixtures__/data/invalid-csv/duplicate-policy.csv similarity index 100% rename from plugins/rbac-backend/src/__fixtures__/data/invalid-csv/duplicate-policy.csv rename to plugins/rbac-backend/__fixtures__/data/invalid-csv/duplicate-policy.csv diff --git a/plugins/rbac-backend/src/__fixtures__/data/invalid-csv/error-policy.csv b/plugins/rbac-backend/__fixtures__/data/invalid-csv/error-policy.csv similarity index 100% rename from plugins/rbac-backend/src/__fixtures__/data/invalid-csv/error-policy.csv rename to plugins/rbac-backend/__fixtures__/data/invalid-csv/error-policy.csv diff --git a/plugins/rbac-backend/src/__fixtures__/data/valid-conditions/conditions.yaml b/plugins/rbac-backend/__fixtures__/data/valid-conditions/conditions.yaml similarity index 100% rename from plugins/rbac-backend/src/__fixtures__/data/valid-conditions/conditions.yaml rename to plugins/rbac-backend/__fixtures__/data/valid-conditions/conditions.yaml diff --git a/plugins/rbac-backend/src/__fixtures__/data/valid-conditions/empty-conditions.yaml b/plugins/rbac-backend/__fixtures__/data/valid-conditions/empty-conditions.yaml similarity index 100% rename from plugins/rbac-backend/src/__fixtures__/data/valid-conditions/empty-conditions.yaml rename to plugins/rbac-backend/__fixtures__/data/valid-conditions/empty-conditions.yaml diff --git a/plugins/rbac-backend/src/__fixtures__/data/valid-csv/basic-and-resource-policies.csv b/plugins/rbac-backend/__fixtures__/data/valid-csv/basic-and-resource-policies.csv similarity index 100% rename from plugins/rbac-backend/src/__fixtures__/data/valid-csv/basic-and-resource-policies.csv rename to plugins/rbac-backend/__fixtures__/data/valid-csv/basic-and-resource-policies.csv diff --git a/plugins/rbac-backend/src/__fixtures__/data/valid-csv/policy-checks.csv b/plugins/rbac-backend/__fixtures__/data/valid-csv/policy-checks.csv similarity index 100% rename from plugins/rbac-backend/src/__fixtures__/data/valid-csv/policy-checks.csv rename to plugins/rbac-backend/__fixtures__/data/valid-csv/policy-checks.csv diff --git a/plugins/rbac-backend/src/__fixtures__/data/valid-csv/rbac-policy.csv b/plugins/rbac-backend/__fixtures__/data/valid-csv/rbac-policy.csv similarity index 100% rename from plugins/rbac-backend/src/__fixtures__/data/valid-csv/rbac-policy.csv rename to plugins/rbac-backend/__fixtures__/data/valid-csv/rbac-policy.csv diff --git a/plugins/rbac-backend/src/__fixtures__/data/valid-csv/simple-policy.csv b/plugins/rbac-backend/__fixtures__/data/valid-csv/simple-policy.csv similarity index 100% rename from plugins/rbac-backend/src/__fixtures__/data/valid-csv/simple-policy.csv rename to plugins/rbac-backend/__fixtures__/data/valid-csv/simple-policy.csv diff --git a/plugins/rbac-backend/__fixtures__/mock-utils.ts b/plugins/rbac-backend/__fixtures__/mock-utils.ts new file mode 100644 index 0000000000..a844933f0d --- /dev/null +++ b/plugins/rbac-backend/__fixtures__/mock-utils.ts @@ -0,0 +1,125 @@ +import { mockCredentials, mockServices } from '@backstage/backend-test-utils'; + +import type { Enforcer } from 'casbin'; +import * as Knex from 'knex'; +import { MockClient } from 'knex-mock-client'; +import type TypeORMAdapter from 'typeorm-adapter'; + +import type { RBACProvider } from '@janus-idp/backstage-plugin-rbac-node'; + +import { resolve } from 'path'; + +import { CasbinDBAdapterFactory } from '../src/database/casbin-adapter-factory'; +import { ConditionalStorage } from '../src/database/conditional-storage'; +import { RoleMetadataStorage } from '../src/database/role-metadata'; +import { + EnforcerDelegate, + RoleEventEmitter, + RoleEvents, +} from '../src/service/enforcer-delegate'; +import { PluginPermissionMetadataCollector } from '../src/service/plugin-endpoints'; + +// TODO: Move to 'catalogServiceMock' from '@backstage/plugin-catalog-node/testUtils' +// once '@backstage/plugin-catalog-node' is upgraded +export const catalogApiMock = { + getEntityAncestors: jest.fn().mockImplementation(), + getLocationById: jest.fn().mockImplementation(), + getEntities: jest.fn().mockImplementation(), + getEntitiesByRefs: jest.fn().mockImplementation(), + queryEntities: jest.fn().mockImplementation(), + getEntityByRef: jest.fn().mockImplementation(), + refreshEntity: jest.fn().mockImplementation(), + getEntityFacets: jest.fn().mockImplementation(), + addLocation: jest.fn().mockImplementation(), + getLocationByRef: jest.fn().mockImplementation(), + removeLocationById: jest.fn().mockImplementation(), + removeEntityByUid: jest.fn().mockImplementation(), + validateEntity: jest.fn().mockImplementation(), + getLocationByEntity: jest.fn().mockImplementation(), +}; + +export const conditionalStorageMock: ConditionalStorage = { + filterConditions: jest.fn().mockImplementation(() => []), + createCondition: jest.fn().mockImplementation(), + checkConflictedConditions: jest.fn().mockImplementation(), + getCondition: jest.fn().mockImplementation(), + deleteCondition: jest.fn().mockImplementation(), + updateCondition: jest.fn().mockImplementation(), +}; + +export const roleMetadataStorageMock: RoleMetadataStorage = { + filterRoleMetadata: jest.fn().mockImplementation(() => []), + findRoleMetadata: jest.fn().mockImplementation(), + createRoleMetadata: jest.fn().mockImplementation(), + updateRoleMetadata: jest.fn().mockImplementation(), + removeRoleMetadata: jest.fn().mockImplementation(), +}; + +export const auditLoggerMock = { + getActorId: jest.fn().mockImplementation(), + createAuditLogDetails: jest.fn().mockImplementation(), + auditLog: jest.fn().mockImplementation(), +}; + +export const pluginMetadataCollectorMock: Partial = + { + getPluginConditionRules: jest.fn().mockImplementation(), + getPluginPolicies: jest.fn().mockImplementation(), + getMetadataByPluginId: jest.fn().mockImplementation(), + }; + +export const roleEventEmitterMock: RoleEventEmitter = { + on: jest.fn().mockImplementation(), +}; + +export const enforcerMock: Partial = { + loadPolicy: jest.fn().mockImplementation(async () => {}), + enableAutoSave: jest.fn().mockImplementation(() => {}), + setRoleManager: jest.fn().mockImplementation(() => {}), + enableAutoBuildRoleLinks: jest.fn().mockImplementation(() => {}), + buildRoleLinks: jest.fn().mockImplementation(() => {}), +}; + +export const enforcerDelegateMock: Partial = { + hasPolicy: jest.fn().mockImplementation(), + hasGroupingPolicy: jest.fn().mockImplementation(), + getPolicy: jest.fn().mockImplementation(), + getGroupingPolicy: jest.fn().mockImplementation(), + getFilteredPolicy: jest.fn().mockImplementation(), + getFilteredGroupingPolicy: jest.fn().mockImplementation(), + addPolicy: jest.fn().mockImplementation(), + addPolicies: jest.fn().mockImplementation(), + addGroupingPolicies: jest.fn().mockImplementation(), + removePolicy: jest.fn().mockImplementation(), + removePolicies: jest.fn().mockImplementation(), + removeGroupingPolicy: jest.fn().mockImplementation(), + removeGroupingPolicies: jest.fn().mockImplementation(), + updatePolicies: jest.fn().mockImplementation(), + updateGroupingPolicies: jest.fn().mockImplementation(), +}; + +export const dataBaseAdapterFactoryMock: Partial = { + createAdapter: jest.fn((): Promise => { + return Promise.resolve({} as TypeORMAdapter); + }), +}; + +export const providerMock: RBACProvider = { + getProviderName: jest.fn().mockImplementation(), + connect: jest.fn().mockImplementation(), + refresh: jest.fn().mockImplementation(), +}; + +export const mockClientKnex = Knex.knex({ client: MockClient }); + +export const mockHttpAuth = mockServices.httpAuth(); +export const mockAuthService = mockServices.auth(); +export const credentials = mockCredentials.user(); +export const mockLoggerService = mockServices.logger.mock(); +export const mockUserInfoService = mockServices.userInfo(); +export const mockDiscovery = mockServices.discovery.mock(); + +export const csvPermFile = resolve( + __dirname, + './../__fixtures__/data/valid-csv/rbac-policy.csv', +); diff --git a/plugins/rbac-backend/__fixtures__/test-utils.ts b/plugins/rbac-backend/__fixtures__/test-utils.ts new file mode 100644 index 0000000000..a515cd1575 --- /dev/null +++ b/plugins/rbac-backend/__fixtures__/test-utils.ts @@ -0,0 +1,142 @@ +import type { LoggerService } from '@backstage/backend-plugin-api'; +import { mockServices } from '@backstage/backend-test-utils'; +import { Config } from '@backstage/config'; + +import { + Adapter, + Enforcer, + Model, + newEnforcer, + newModelFromString, +} from 'casbin'; +import * as Knex from 'knex'; +import { MockClient } from 'knex-mock-client'; + +import { CasbinDBAdapterFactory } from '../src/database/casbin-adapter-factory'; +import { RoleMetadataStorage } from '../src/database/role-metadata'; +import { BackstageRoleManager } from '../src/role-manager/role-manager'; +import { EnforcerDelegate } from '../src/service/enforcer-delegate'; +import { MODEL } from '../src/service/permission-model'; +import { RBACPermissionPolicy } from '../src/service/permission-policy'; +import { PluginPermissionMetadataCollector } from '../src/service/plugin-endpoints'; +import { + auditLoggerMock, + catalogApiMock, + conditionalStorageMock, + csvPermFile, + mockAuthService, + mockClientKnex, + pluginMetadataCollectorMock, + roleMetadataStorageMock, +} from './mock-utils'; + +export function newConfig( + permFile?: string, + users?: Array<{ name: string }>, + superUsers?: Array<{ name: string }>, +): Config { + const testUsers = [ + { + name: 'user:default/guest', + }, + { + name: 'group:default/guests', + }, + ]; + + return mockServices.rootConfig({ + data: { + permission: { + rbac: { + 'policies-csv-file': permFile || csvPermFile, + policyFileReload: true, + admin: { + users: users || testUsers, + superUsers: superUsers, + }, + }, + }, + backend: { + database: { + client: 'better-sqlite3', + connection: ':memory:', + }, + }, + }, + }); +} + +export async function newAdapter(config: Config): Promise { + return await new CasbinDBAdapterFactory( + config, + mockClientKnex, + ).createAdapter(); +} + +export async function createEnforcer( + theModel: Model, + adapter: Adapter, + logger: LoggerService, + config: Config, +): Promise { + const catalogDBClient = Knex.knex({ client: MockClient }); + const rbacDBClient = Knex.knex({ client: MockClient }); + const enf = await newEnforcer(theModel, adapter); + + const rm = new BackstageRoleManager( + catalogApiMock, + logger, + catalogDBClient, + rbacDBClient, + config, + mockAuthService, + ); + enf.setRoleManager(rm); + enf.enableAutoBuildRoleLinks(false); + await enf.buildRoleLinks(); + + return enf; +} + +export async function newEnforcerDelegate( + adapter: Adapter, + config: Config, + storedPolicies?: string[][], + storedGroupingPolicies?: string[][], +): Promise { + const theModel = newModelFromString(MODEL); + const logger = mockServices.logger.mock(); + + const enf = await createEnforcer(theModel, adapter, logger, config); + + if (storedPolicies) { + await enf.addPolicies(storedPolicies); + } + + if (storedGroupingPolicies) { + await enf.addGroupingPolicies(storedGroupingPolicies); + } + + return new EnforcerDelegate(enf, roleMetadataStorageMock, mockClientKnex); +} + +export async function newPermissionPolicy( + config: Config, + enfDelegate: EnforcerDelegate, + roleMock?: RoleMetadataStorage, +): Promise { + const logger = mockServices.logger.mock(); + const permissionPolicy = await RBACPermissionPolicy.build( + logger, + auditLoggerMock, + config, + conditionalStorageMock, + enfDelegate, + roleMock || roleMetadataStorageMock, + mockClientKnex, + pluginMetadataCollectorMock as PluginPermissionMetadataCollector, + mockAuthService, + ); + auditLoggerMock.auditLog.mockReset(); + return permissionPolicy; +} diff --git a/plugins/rbac-backend/src/admin-permissions/admin-creation.test.ts b/plugins/rbac-backend/src/admin-permissions/admin-creation.test.ts new file mode 100644 index 0000000000..909fb5a1d5 --- /dev/null +++ b/plugins/rbac-backend/src/admin-permissions/admin-creation.test.ts @@ -0,0 +1,177 @@ +import { mockServices } from '@backstage/backend-test-utils'; +import { Config } from '@backstage/config'; + +import * as Knex from 'knex'; + +import type { RoleMetadata } from '@janus-idp/backstage-plugin-rbac-common'; + +import { + auditLoggerMock, + catalogApiMock, + csvPermFile, + mockClientKnex, + roleMetadataStorageMock, +} from '../../__fixtures__/mock-utils'; +import { + newAdapter, + newConfig, + newEnforcerDelegate, + newPermissionPolicy, +} from '../../__fixtures__/test-utils'; +import { RoleMetadataDao } from '../database/role-metadata'; +import { EnforcerDelegate } from '../service/enforcer-delegate'; +import { + ADMIN_ROLE_NAME, + setAdminPermissions, + useAdminsFromConfig, +} from './admin-creation'; + +const modifiedBy = 'user:default/some-admin'; +const adminRole = 'role:default/rbac_admin'; +const groupPolicy = [['user:default/test_admin', 'role:default/rbac_admin']]; +const permissions = [ + ['role:default/rbac_admin', 'policy-entity', 'read', 'allow'], + ['role:default/rbac_admin', 'policy-entity', 'create', 'allow'], + ['role:default/rbac_admin', 'policy-entity', 'delete', 'allow'], + ['role:default/rbac_admin', 'policy-entity', 'update', 'allow'], + ['role:default/rbac_admin', 'catalog-entity', 'read', 'allow'], +]; +const oldGroupPolicy = ['user:default/old_admin', 'role:default/rbac_admin']; + +describe('Admin Creation', () => { + describe('Admin role and permission creation to a user', () => { + let enfDelegate: EnforcerDelegate; + let config: Config; + roleMetadataStorageMock.findRoleMetadata = jest + .fn() + .mockImplementation( + async ( + _roleEntityRef: string, + _trx: Knex.Knex.Transaction, + ): Promise => { + return { + roleEntityRef: 'role:default/catalog-writer', + source: 'legacy', + modifiedBy, + }; + }, + ); + + const admins = new Array<{ name: string }>(); + admins.push({ name: 'user:default/test_admin' }); + const superUser = new Array<{ name: string }>(); + superUser.push({ name: 'user:default/super_user' }); + + catalogApiMock.getEntities.mockReturnValue({ items: [] }); + + beforeEach(async () => { + roleMetadataStorageMock.findRoleMetadata = jest + .fn() + .mockImplementation( + async ( + _roleEntityRef: string, + _trx: Knex.Knex.Transaction, + ): Promise => { + return { + roleEntityRef: 'role:default/catalog-writer', + source: 'legacy', + modifiedBy, + }; + }, + ); + + config = newConfig(csvPermFile, admins, superUser); + const adapter = await newAdapter(config); + + enfDelegate = await newEnforcerDelegate(adapter, config); + + await enfDelegate.addGroupingPolicy(oldGroupPolicy, { + source: 'configuration', + roleEntityRef: ADMIN_ROLE_NAME, + modifiedBy: `user:default/tom`, + }); + + const adminUsers = config.getOptionalConfigArray( + 'permission.rbac.admin.users', + ); + await useAdminsFromConfig( + adminUsers || [], + enfDelegate, + auditLoggerMock, + roleMetadataStorageMock, + mockClientKnex, + ); + await setAdminPermissions(enfDelegate, auditLoggerMock); + }); + + it('should assign an admin to the admin role and permissions', async () => { + const enfRole = await enfDelegate.getFilteredGroupingPolicy(1, adminRole); + const enfPermission = await enfDelegate.getFilteredPolicy(0, adminRole); + expect(enfRole).toEqual(groupPolicy); + expect(enfPermission).toEqual(permissions); + }); + + it('should fail to build the admin permissions, problem with creating role metadata', async () => { + roleMetadataStorageMock.findRoleMetadata = jest + .fn() + .mockImplementation(async (): Promise => { + return undefined; + }); + + roleMetadataStorageMock.createRoleMetadata = jest + .fn() + .mockImplementation(async (): Promise => { + throw new Error(`Failed to create`); + }); + + config = mockServices.rootConfig({ + data: { + permission: { + rbac: { + 'policies-csv-file': csvPermFile, + policyFileReload: true, + }, + }, + backend: { + database: { + client: 'better-sqlite3', + connection: ':memory:', + }, + }, + }, + }); + + await expect( + newPermissionPolicy(config, enfDelegate, roleMetadataStorageMock), + ).rejects.toThrow('Failed to create'); + }); + + it('should build and update a legacy admin permission', async () => { + roleMetadataStorageMock.findRoleMetadata = jest + .fn() + .mockImplementationOnce( + async ( + _roleEntityRef: string, + _trx: Knex.Knex.Transaction, + ): Promise => { + return { source: 'legacy' }; + }, + ); + + const enfRole = await enfDelegate.getFilteredGroupingPolicy(1, adminRole); + const enfPermission = await enfDelegate.getFilteredPolicy(0, adminRole); + + expect(enfRole).toEqual(groupPolicy); + expect(enfPermission).toEqual(permissions); + expect(roleMetadataStorageMock.updateRoleMetadata).toHaveBeenCalled(); + }); + + it('should remove users that are no longer in the config file', async () => { + const enfRole = await enfDelegate.getFilteredGroupingPolicy(1, adminRole); + const enfPermission = await enfDelegate.getFilteredPolicy(0, adminRole); + expect(enfRole).toEqual(groupPolicy); + expect(enfRole).not.toContain(oldGroupPolicy); + expect(enfPermission).toEqual(permissions); + }); + }); +}); diff --git a/plugins/rbac-backend/src/admin-permissions/admin-creation.ts b/plugins/rbac-backend/src/admin-permissions/admin-creation.ts new file mode 100644 index 0000000000..38dc9dae5b --- /dev/null +++ b/plugins/rbac-backend/src/admin-permissions/admin-creation.ts @@ -0,0 +1,150 @@ +import type { Config } from '@backstage/config'; + +import { Knex } from 'knex'; + +import { AuditLogger } from '@janus-idp/backstage-plugin-audit-log-node'; + +import { + HANDLE_RBAC_DATA_STAGE, + PermissionAuditInfo, + PermissionEvents, + RBAC_BACKEND, + RoleAuditInfo, + RoleEvents, +} from '../audit-log/audit-logger'; +import { + RoleMetadataDao, + RoleMetadataStorage, +} from '../database/role-metadata'; +import { removeTheDifference } from '../helper'; +import { EnforcerDelegate } from '../service/enforcer-delegate'; +import { validateEntityReference } from '../validation/policies-validation'; + +export const ADMIN_ROLE_NAME = 'role:default/rbac_admin'; +export const ADMIN_ROLE_AUTHOR = 'application configuration'; +const DEF_ADMIN_ROLE_DESCRIPTION = + 'The default permission policy for the admin role allows for the creation, deletion, updating, and reading of roles and permission policies.'; + +const getAdminRoleMetadata = (): RoleMetadataDao => { + const currentDate: Date = new Date(); + return { + source: 'configuration', + roleEntityRef: ADMIN_ROLE_NAME, + description: DEF_ADMIN_ROLE_DESCRIPTION, + author: ADMIN_ROLE_AUTHOR, + modifiedBy: ADMIN_ROLE_AUTHOR, + lastModified: currentDate.toUTCString(), + createdAt: currentDate.toUTCString(), + }; +}; + +export const useAdminsFromConfig = async ( + admins: Config[], + enf: EnforcerDelegate, + auditLogger: AuditLogger, + roleMetadataStorage: RoleMetadataStorage, + knex: Knex, +) => { + const addedGroupPolicies = new Map(); + const newGroupPolicies = new Map(); + + for (const admin of admins) { + const entityRef = admin.getString('name'); + validateEntityReference(entityRef); + + addedGroupPolicies.set(entityRef, ADMIN_ROLE_NAME); + + if (!(await enf.hasGroupingPolicy(...[entityRef, ADMIN_ROLE_NAME]))) { + newGroupPolicies.set(entityRef, ADMIN_ROLE_NAME); + } + } + + const adminRoleMeta = + await roleMetadataStorage.findRoleMetadata(ADMIN_ROLE_NAME); + + const trx = await knex.transaction(); + let addedRoleMembers; + try { + if (!adminRoleMeta) { + // even if there are no user, we still create default role metadata for admins + await roleMetadataStorage.createRoleMetadata(getAdminRoleMetadata(), trx); + } else if (adminRoleMeta.source === 'legacy') { + await roleMetadataStorage.updateRoleMetadata( + getAdminRoleMetadata(), + ADMIN_ROLE_NAME, + trx, + ); + } + + addedRoleMembers = Array.from(newGroupPolicies.entries()); + await enf.addGroupingPolicies( + addedRoleMembers, + getAdminRoleMetadata(), + trx, + ); + + await trx.commit(); + } catch (error) { + await trx.rollback(error); + throw error; + } + + await auditLogger.auditLog({ + actorId: RBAC_BACKEND, + message: `Created or updated role`, + eventName: RoleEvents.CREATE_OR_UPDATE_ROLE, + metadata: { + ...getAdminRoleMetadata(), + members: addedRoleMembers.map(gp => gp[0]), + }, + stage: HANDLE_RBAC_DATA_STAGE, + status: 'succeeded', + }); + + const configGroupPolicies = await enf.getFilteredGroupingPolicy( + 1, + ADMIN_ROLE_NAME, + ); + + await removeTheDifference( + configGroupPolicies.map(gp => gp[0]), + Array.from(addedGroupPolicies.keys()), + 'configuration', + ADMIN_ROLE_NAME, + enf, + auditLogger, + ADMIN_ROLE_AUTHOR, + ); +}; + +const addAdminPermissions = async ( + policies: string[][], + enf: EnforcerDelegate, + auditLogger: AuditLogger, +) => { + await enf.addPolicies(policies); + + await auditLogger.auditLog({ + actorId: RBAC_BACKEND, + message: `Created RBAC admin permissions`, + eventName: PermissionEvents.CREATE_POLICY, + metadata: { policies: policies, source: 'configuration' }, + stage: HANDLE_RBAC_DATA_STAGE, + status: 'succeeded', + }); +}; + +export const setAdminPermissions = async ( + enf: EnforcerDelegate, + auditLogger: AuditLogger, +) => { + const adminPermissions = [ + [ADMIN_ROLE_NAME, 'policy-entity', 'read', 'allow'], + [ADMIN_ROLE_NAME, 'policy-entity', 'create', 'allow'], + [ADMIN_ROLE_NAME, 'policy-entity', 'delete', 'allow'], + [ADMIN_ROLE_NAME, 'policy-entity', 'update', 'allow'], + // Needed for the RBAC frontend plugin. + [ADMIN_ROLE_NAME, 'catalog-entity', 'read', 'allow'], + ]; + await addAdminPermissions(adminPermissions, enf, auditLogger); +}; diff --git a/plugins/rbac-backend/src/database/conditional.storage.test.ts b/plugins/rbac-backend/src/database/conditional-storage.test.ts similarity index 100% rename from plugins/rbac-backend/src/database/conditional.storage.test.ts rename to plugins/rbac-backend/src/database/conditional-storage.test.ts diff --git a/plugins/rbac-backend/src/file-permissions/csv-file-watcher.test.ts b/plugins/rbac-backend/src/file-permissions/csv-file-watcher.test.ts index 190bb9a46a..79afe666d3 100644 --- a/plugins/rbac-backend/src/file-permissions/csv-file-watcher.test.ts +++ b/plugins/rbac-backend/src/file-permissions/csv-file-watcher.test.ts @@ -16,6 +16,7 @@ import type { Source } from '@janus-idp/backstage-plugin-rbac-common'; import { resolve } from 'path'; +import { ADMIN_ROLE_AUTHOR } from '../admin-permissions/admin-creation'; import { CasbinDBAdapterFactory } from '../database/casbin-adapter-factory'; import { RoleMetadataDao, @@ -24,7 +25,6 @@ import { import { BackstageRoleManager } from '../role-manager/role-manager'; import { EnforcerDelegate } from '../service/enforcer-delegate'; import { MODEL } from '../service/permission-model'; -import { ADMIN_ROLE_AUTHOR } from '../service/permission-policy'; import { CSVFileWatcher } from './csv-file-watcher'; const legacyPermission = [ @@ -56,7 +56,7 @@ const configRole = ['user:default/guest', 'role:default/config']; // TODO: Move to 'catalogServiceMock' from '@backstage/plugin-catalog-node/testUtils' // once '@backstage/plugin-catalog-node' is upgraded -const catalogApi = { +const catalogApiMock = { getEntityAncestors: jest.fn().mockImplementation(), getLocationById: jest.fn().mockImplementation(), getEntities: jest.fn().mockImplementation(), @@ -73,7 +73,7 @@ const catalogApi = { getLocationByEntity: jest.fn().mockImplementation(), }; -const loggerMock = mockServices.logger.mock(); +const mockLoggerService = mockServices.logger.mock(); const modifiedBy = 'user:default/some-admin'; @@ -116,7 +116,7 @@ const roleMetadataStorageMock: RoleMetadataStorage = { removeRoleMetadata: jest.fn().mockImplementation(), }; -const dbManagerMock = Knex.knex({ client: MockClient }); +const mockClientKnex = Knex.knex({ client: MockClient }); const mockAuthService = mockServices.auth(); @@ -150,18 +150,18 @@ describe('CSVFileWatcher', () => { beforeEach(async () => { csvFileName = resolve( __dirname, - './../__fixtures__/data/valid-csv/rbac-policy.csv', + '../../__fixtures__/data/valid-csv/rbac-policy.csv', ); const config = newConfig(); const adapter = await new CasbinDBAdapterFactory( config, - dbManagerMock, + mockClientKnex, ).createAdapter(); const stringModel = newModelFromString(MODEL); - const enf = await createEnforcer(stringModel, adapter, loggerMock); + const enf = await createEnforcer(stringModel, adapter, mockLoggerService); const knex = Knex.knex({ client: MockClient }); @@ -172,7 +172,7 @@ describe('CSVFileWatcher', () => { }); afterEach(() => { - (loggerMock.warn as jest.Mock).mockReset(); + (mockLoggerService.warn as jest.Mock).mockReset(); (roleMetadataStorageMock.removeRoleMetadata as jest.Mock).mockReset(); }); @@ -180,7 +180,7 @@ describe('CSVFileWatcher', () => { return new CSVFileWatcher( fileName, false, - loggerMock, + mockLoggerService, enforcerDelegate, roleMetadataStorageMock, auditLoggerMock, @@ -293,7 +293,7 @@ describe('CSVFileWatcher', () => { it('should fail to add duplicate policies', async () => { csvFileName = resolve( __dirname, - './../__fixtures__/data/invalid-csv/duplicate-policy.csv', + '../../__fixtures__/data/invalid-csv/duplicate-policy.csv', ); const csvFileWatcher = createCSVFileWatcher(csvFileName); @@ -316,27 +316,27 @@ describe('CSVFileWatcher', () => { await csvFileWatcher.initialize(); - expect(loggerMock.warn).toHaveBeenNthCalledWith( + expect(mockLoggerService.warn).toHaveBeenNthCalledWith( 1, `Duplicate policy: ${duplicatePolicy} found in the file ${csvFileName}`, ); - expect(loggerMock.warn).toHaveBeenNthCalledWith( + expect(mockLoggerService.warn).toHaveBeenNthCalledWith( 2, `Duplicate policy: ${duplicatePolicy} found in the file ${csvFileName}`, ); - expect(loggerMock.warn).toHaveBeenNthCalledWith( + expect(mockLoggerService.warn).toHaveBeenNthCalledWith( 3, `Duplicate policy: ${duplicatePolicyWithDifferentEffect[0]}, ${duplicatePolicyWithDifferentEffect[1]}, ${duplicatePolicyWithDifferentEffect[2]} with different effect found in the file ${csvFileName}`, ); - expect(loggerMock.warn).toHaveBeenNthCalledWith( + expect(mockLoggerService.warn).toHaveBeenNthCalledWith( 4, `Duplicate policy: ${duplicatePolicyWithDifferentEffect[0]}, ${duplicatePolicyWithDifferentEffect[1]}, ${duplicatePolicyWithDifferentEffect[2]} with different effect found in the file ${csvFileName}`, ); - expect(loggerMock.warn).toHaveBeenNthCalledWith( + expect(mockLoggerService.warn).toHaveBeenNthCalledWith( 5, `Duplicate role: ${duplicateRole} found in the file ${csvFileName}`, ); - expect(loggerMock.warn).toHaveBeenNthCalledWith( + expect(mockLoggerService.warn).toHaveBeenNthCalledWith( 6, `Duplicate role: ${duplicateRole} found in the file ${csvFileName}`, ); @@ -345,7 +345,7 @@ describe('CSVFileWatcher', () => { it('should fail to add policies with errors', async () => { csvFileName = resolve( __dirname, - './../__fixtures__/data/invalid-csv/error-policy.csv', + '../../__fixtures__/data/invalid-csv/error-policy.csv', ); const csvFileWatcher = createCSVFileWatcher(csvFileName); @@ -367,35 +367,35 @@ describe('CSVFileWatcher', () => { await csvFileWatcher.initialize(); - expect(loggerMock.warn).toHaveBeenNthCalledWith( + expect(mockLoggerService.warn).toHaveBeenNthCalledWith( 1, `Failed to validate policy from file ${csvFileName}. Cause: Entity reference "${roleErrorPolicy[0]}" was not on the form [:][/]`, ); - expect(loggerMock.warn).toHaveBeenNthCalledWith( + expect(mockLoggerService.warn).toHaveBeenNthCalledWith( 2, `Failed to validate policy from file ${csvFileName}. Cause: 'effect' has invalid value: '${allowErrorPolicy[3]}'. It should be: 'allow' or 'deny'`, ); - expect(loggerMock.warn).toHaveBeenNthCalledWith( + expect(mockLoggerService.warn).toHaveBeenNthCalledWith( 3, `Unable to add policy ${restPermission} from file ${csvFileName}. Cause: source does not match originating role ${restPermission[0]}, consider making changes to the 'REST'`, ); - expect(loggerMock.warn).toHaveBeenNthCalledWith( + expect(mockLoggerService.warn).toHaveBeenNthCalledWith( 4, `Unable to add policy ${configPermission} from file ${csvFileName}. Cause: source does not match originating role ${configPermission[0]}, consider making changes to the 'CONFIGURATION'`, ); - expect(loggerMock.warn).toHaveBeenNthCalledWith( + expect(mockLoggerService.warn).toHaveBeenNthCalledWith( 5, `Failed to validate group policy ${entityRoleError}. Cause: Entity reference "${entityRoleError[0]}" was not on the form [:][/], error originates from file ${csvFileName}`, ); - expect(loggerMock.warn).toHaveBeenNthCalledWith( + expect(mockLoggerService.warn).toHaveBeenNthCalledWith( 6, `Failed to validate group policy ${roleError}. Cause: Entity reference "${roleError[1]}" was not on the form [:][/], error originates from file ${csvFileName}`, ); - expect(loggerMock.warn).toHaveBeenNthCalledWith( + expect(mockLoggerService.warn).toHaveBeenNthCalledWith( 7, `Unable to validate role ${restRole}. Cause: source does not match originating role ${restRole[1]}, consider making changes to the 'REST', error originates from file ${csvFileName}`, ); - expect(loggerMock.warn).toHaveBeenNthCalledWith( + expect(mockLoggerService.warn).toHaveBeenNthCalledWith( 8, `Unable to validate role ${configRole}. Cause: source does not match originating role ${configRole[1]}, consider making changes to the 'CONFIGURATION', error originates from file ${csvFileName}`, ); @@ -408,7 +408,7 @@ describe('CSVFileWatcher', () => { beforeEach(async () => { csvFileName = resolve( __dirname, - './../__fixtures__/data/valid-csv/simple-policy.csv', + '../../__fixtures__/data/valid-csv/simple-policy.csv', ); csvFileWatcher = createCSVFileWatcher(csvFileName); await csvFileWatcher.initialize(); @@ -511,11 +511,11 @@ describe('CSVFileWatcher', () => { expect(enfPolicies).toStrictEqual(policies); - expect(loggerMock.warn).toHaveBeenNthCalledWith( + expect(mockLoggerService.warn).toHaveBeenNthCalledWith( 1, `Unable to add policy ${configPermission} from file ${csvFileName}. Cause: source does not match originating role ${configPermission[0]}, consider making changes to the 'CONFIGURATION'`, ); - expect(loggerMock.warn).toHaveBeenNthCalledWith( + expect(mockLoggerService.warn).toHaveBeenNthCalledWith( 2, `Unable to add policy ${restPermission} from file ${csvFileName}. Cause: source does not match originating role ${restPermission[0]}, consider making changes to the 'REST'`, ); @@ -555,11 +555,11 @@ describe('CSVFileWatcher', () => { expect(enfRoles).toStrictEqual(roles); - expect(loggerMock.warn).toHaveBeenNthCalledWith( + expect(mockLoggerService.warn).toHaveBeenNthCalledWith( 1, `Unable to validate role ${restRole}. Cause: source does not match originating role ${restRole[1]}, consider making changes to the 'REST', error originates from file ${csvFileName}`, ); - expect(loggerMock.warn).toHaveBeenNthCalledWith( + expect(mockLoggerService.warn).toHaveBeenNthCalledWith( 2, `Unable to validate role ${configRole}. Cause: source does not match originating role ${configRole[1]}, consider making changes to the 'CONFIGURATION', error originates from file ${csvFileName}`, ); @@ -698,7 +698,7 @@ async function createEnforcer( const config = newConfig(); const rm = new BackstageRoleManager( - catalogApi, + catalogApiMock, logger, catalogDBClient, rbacDBClient, diff --git a/plugins/rbac-backend/src/file-permissions/yaml-conditional-file-watcher.test.ts b/plugins/rbac-backend/src/file-permissions/yaml-conditional-file-watcher.test.ts index 606eb3a133..1ed618e125 100644 --- a/plugins/rbac-backend/src/file-permissions/yaml-conditional-file-watcher.test.ts +++ b/plugins/rbac-backend/src/file-permissions/yaml-conditional-file-watcher.test.ts @@ -14,7 +14,7 @@ import { RoleEventEmitter, RoleEvents } from '../service/enforcer-delegate'; import { PluginPermissionMetadataCollector } from '../service/plugin-endpoints'; import { YamlConditinalPoliciesFileWatcher } from './yaml-conditional-file-watcher'; // Adjust the import path as necessary -const loggerMock = mockServices.logger.mock(); +const mockLoggerService = mockServices.logger.mock(); let loggerWarnSpy: jest.SpyInstance; @@ -180,10 +180,10 @@ describe('YamlConditionalFileWatcher', () => { beforeEach(() => { csvFileName = resolve( __dirname, - './../__fixtures__/data/valid-conditions/conditions.yaml', + '../../__fixtures__/data/valid-conditions/conditions.yaml', ); - loggerWarnSpy = jest.spyOn(loggerMock, 'warn'); + loggerWarnSpy = jest.spyOn(mockLoggerService, 'warn'); auditLoggerMock.auditLog.mockClear(); conditionalStorageMock.createCondition = jest.fn().mockImplementation(); @@ -195,7 +195,7 @@ describe('YamlConditionalFileWatcher', () => { return new YamlConditinalPoliciesFileWatcher( filePath, false, - loggerMock, + mockLoggerService, conditionalStorageMock as DataBaseConditionalStorage, auditLoggerMock, mockAuthService, @@ -220,7 +220,7 @@ describe('YamlConditionalFileWatcher', () => { test('handles error on parse invalid yaml file', async () => { const invalidFilePath = resolve( __dirname, - './../__fixtures__/data/invalid-conditions/invalid-yaml.yaml', + '../../__fixtures__/data/invalid-conditions/invalid-yaml.yaml', ); const watcher = createWatcher(invalidFilePath); await watcher.initialize(); @@ -294,7 +294,7 @@ describe('YamlConditionalFileWatcher', () => { csvFileName = resolve( __dirname, - './../__fixtures__/data/valid-conditions/empty-conditions.yaml', + '../../__fixtures__/data/valid-conditions/empty-conditions.yaml', ); const watcher = createWatcher(csvFileName); await watcher.initialize(); diff --git a/plugins/rbac-backend/src/helper.test.ts b/plugins/rbac-backend/src/helper.test.ts index 80d84f4153..62e8f91d79 100644 --- a/plugins/rbac-backend/src/helper.test.ts +++ b/plugins/rbac-backend/src/helper.test.ts @@ -1,3 +1,4 @@ +import { ADMIN_ROLE_AUTHOR } from './admin-permissions/admin-creation'; import { RoleMetadataDao } from './database/role-metadata'; import { deepSortedEqual, @@ -13,7 +14,6 @@ import { } from './helper'; // Import the function to test import { EnforcerDelegate } from './service/enforcer-delegate'; -import { ADMIN_ROLE_AUTHOR } from './service/permission-policy'; const modifiedBy = 'user:default/some-user'; diff --git a/plugins/rbac-backend/src/providers/connect-providers.test.ts b/plugins/rbac-backend/src/providers/connect-providers.test.ts index 8d9ff65d1d..e9b1e55da3 100644 --- a/plugins/rbac-backend/src/providers/connect-providers.test.ts +++ b/plugins/rbac-backend/src/providers/connect-providers.test.ts @@ -26,7 +26,7 @@ import { EnforcerDelegate } from '../service/enforcer-delegate'; import { MODEL } from '../service/permission-model'; import { Connection, connectRBACProviders } from './connect-providers'; -const loggerMock = mockServices.logger.mock(); +const mockLoggerService = mockServices.logger.mock(); const roleMetadataStorageMock: RoleMetadataStorage = { filterRoleMetadata: jest @@ -92,7 +92,7 @@ const auditLoggerMock = { // TODO: Move to 'catalogServiceMock' from '@backstage/plugin-catalog-node/testUtils' // once '@backstage/plugin-catalog-node' is upgraded -const catalogApi = { +const catalogApiMock = { getEntityAncestors: jest.fn().mockImplementation(), getLocationById: jest.fn().mockImplementation(), getEntities: jest.fn().mockImplementation(), @@ -111,7 +111,7 @@ const catalogApi = { const mockAuthService = mockServices.auth(); -const dbManagerMock = Knex.knex({ client: MockClient }); +const mockClientKnex = Knex.knex({ client: MockClient }); const providerMock: RBACProvider = { getProviderName: jest.fn().mockImplementation(), @@ -161,11 +161,11 @@ describe('Connection', () => { const id = 'test'; const adapter = await new CasbinDBAdapterFactory( config, - dbManagerMock, + mockClientKnex, ).createAdapter(); const stringModel = newModelFromString(MODEL); - const enf = await createEnforcer(stringModel, adapter, loggerMock); + const enf = await createEnforcer(stringModel, adapter, mockLoggerService); const knex = Knex.knex({ client: MockClient }); @@ -187,7 +187,7 @@ describe('Connection', () => { id, enforcerDelegate, roleMetadataStorageMock, - loggerMock, + mockLoggerService, auditLoggerMock, ); }); @@ -218,7 +218,7 @@ describe('Connection', () => { >; afterEach(() => { - (loggerMock.warn as jest.Mock).mockReset(); + (mockLoggerService.warn as jest.Mock).mockReset(); }); it('should add the new roles', async () => { @@ -325,7 +325,7 @@ describe('Connection', () => { const roleToAdd = `user:default/test,role:default/`; await provider.applyRoles(roles); - expect(loggerMock.warn).toHaveBeenCalledWith( + expect(mockLoggerService.warn).toHaveBeenCalledWith( `Failed to validate group policy ${roleToAdd}. Cause: Entity reference "role:default/" was not on the form [:][/]`, ); }); @@ -354,7 +354,7 @@ describe('Connection', () => { }; await provider.applyRoles(roles); - expect(loggerMock.warn).toHaveBeenCalledWith( + expect(mockLoggerService.warn).toHaveBeenCalledWith( `Failed to validate group policy ${failingRoleToAdd}. Cause: Entity reference "role:default/" was not on the form [:][/]`, ); expect(enfAddGroupingPolicySpy).toHaveBeenCalledWith( @@ -383,7 +383,7 @@ describe('Connection', () => { >; afterEach(() => { - (loggerMock.warn as jest.Mock).mockReset(); + (mockLoggerService.warn as jest.Mock).mockReset(); }); it('should add new permissions', async () => { @@ -416,7 +416,7 @@ describe('Connection', () => { ]; await provider.applyPermissions(policies); - expect(loggerMock.warn).toHaveBeenCalledWith( + expect(mockLoggerService.warn).toHaveBeenCalledWith( `Invalid permission policy, Error: 'effect' has invalid value: 'temp'. It should be: 'allow' or 'deny'`, ); }); @@ -429,7 +429,7 @@ describe('Connection', () => { ]; await provider.applyPermissions(policies); - expect(loggerMock.warn).toHaveBeenCalledWith( + expect(mockLoggerService.warn).toHaveBeenCalledWith( `Unable to add policy ${policies[0].toString()}. Cause: source does not match originating role ${policies[0][0]}, consider making changes to the 'CSV-FILE'`, ); }); @@ -451,11 +451,11 @@ describe('connectRBACProviders', () => { const adapter = await new CasbinDBAdapterFactory( config, - dbManagerMock, + mockClientKnex, ).createAdapter(); const stringModel = newModelFromString(MODEL); - const enf = await createEnforcer(stringModel, adapter, loggerMock); + const enf = await createEnforcer(stringModel, adapter, mockLoggerService); const knex = Knex.knex({ client: MockClient }); @@ -469,7 +469,7 @@ describe('connectRBACProviders', () => { [providerMock], enforcerDelegate, roleMetadataStorageMock, - loggerMock, + mockLoggerService, auditLoggerMock, ); @@ -487,7 +487,7 @@ async function createEnforcer( const enf = await newEnforcer(theModel, adapter); const rm = new BackstageRoleManager( - catalogApi, + catalogApiMock, logger, catalogDBClient, rbacDBClient, 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 48874390e8..d4822a3cfb 100644 --- a/plugins/rbac-backend/src/role-manager/role-manager.test.ts +++ b/plugins/rbac-backend/src/role-manager/role-manager.test.ts @@ -18,7 +18,7 @@ describe('BackstageRoleManager', () => { getEntities: jest.fn().mockImplementation(), }; - const loggerMock = mockServices.logger.mock(); + const mockLoggerService = mockServices.logger.mock(); const mockAuthService = mockServices.auth(); @@ -32,7 +32,7 @@ describe('BackstageRoleManager', () => { roleManager = new BackstageRoleManager( catalogApiMock as CatalogApi, - loggerMock as LoggerService, + mockLoggerService as LoggerService, catalogDBClient, rbacDBClient, config, @@ -53,7 +53,7 @@ describe('BackstageRoleManager', () => { try { errorRoleManager = new BackstageRoleManager( catalogApiMock as CatalogApi, - loggerMock as LoggerService, + mockLoggerService as LoggerService, catalogDBClient, rbacDBClient, config, @@ -109,7 +109,7 @@ describe('BackstageRoleManager', () => { describe('hasLink tests', () => { afterEach(() => { - (loggerMock.warn as jest.Mock).mockReset(); + (mockLoggerService.warn as jest.Mock).mockReset(); (catalogApiMock.getEntities as jest.Mock).mockReset(); }); @@ -343,7 +343,7 @@ describe('BackstageRoleManager', () => { const config = newConfig(0); const rm = new BackstageRoleManager( catalogApiMock as CatalogApi, - loggerMock as LoggerService, + mockLoggerService as LoggerService, catalogDBClient, rbacDBClient, config, @@ -693,7 +693,7 @@ describe('BackstageRoleManager', () => { 'group:default/team-b', ); expect(result).toBeFalsy(); - expect(loggerMock.warn).toHaveBeenCalledWith( + expect(mockLoggerService.warn).toHaveBeenCalledWith( 'Detected cycle dependencies in the Group graph: [["group:default/team-a","group:default/team-b"]]. Admin/(catalog owner) have to fix it to make RBAC permission evaluation correct for groups: [["group:default/team-a","group:default/team-b"]]', ); @@ -702,7 +702,7 @@ describe('BackstageRoleManager', () => { 'group:default/team-a', ); expect(result).toBeFalsy(); - expect(loggerMock.warn).toHaveBeenCalledWith( + expect(mockLoggerService.warn).toHaveBeenCalledWith( 'Detected cycle dependencies in the Group graph: [["group:default/team-a","group:default/team-b"]]. Admin/(catalog owner) have to fix it to make RBAC permission evaluation correct for groups: [["group:default/team-a","group:default/team-b"]]', ); }); @@ -738,7 +738,7 @@ describe('BackstageRoleManager', () => { 'role:default/team-b', ); expect(result).toBeFalsy(); - expect(loggerMock.warn).toHaveBeenCalledWith( + expect(mockLoggerService.warn).toHaveBeenCalledWith( 'Detected cycle dependencies in the Group graph: [["group:default/team-a","group:default/team-b"]]. Admin/(catalog owner) have to fix it to make RBAC permission evaluation correct for groups: [["group:default/team-a","group:default/team-b"]]', ); @@ -747,7 +747,7 @@ describe('BackstageRoleManager', () => { 'role:default/team-a', ); expect(result).toBeFalsy(); - expect(loggerMock.warn).toHaveBeenCalledWith( + expect(mockLoggerService.warn).toHaveBeenCalledWith( 'Detected cycle dependencies in the Group graph: [["group:default/team-a","group:default/team-b"]]. Admin/(catalog owner) have to fix it to make RBAC permission evaluation correct for groups: [["group:default/team-a","group:default/team-b"]]', ); }); @@ -783,7 +783,7 @@ describe('BackstageRoleManager', () => { 'group:default/team-c', ); expect(result).toBeFalsy(); - expect(loggerMock.warn).toHaveBeenCalledWith( + expect(mockLoggerService.warn).toHaveBeenCalledWith( 'Detected cycle dependencies in the Group graph: [["group:default/team-a","group:default/team-b"]]. Admin/(catalog owner) have to fix it to make RBAC permission evaluation correct for groups: [["group:default/team-a","group:default/team-b"]]', ); @@ -792,7 +792,7 @@ describe('BackstageRoleManager', () => { 'group:default/team-b', ); expect(result).toBeFalsy(); - expect(loggerMock.warn).toHaveBeenCalledWith( + expect(mockLoggerService.warn).toHaveBeenCalledWith( 'Detected cycle dependencies in the Group graph: [["group:default/team-a","group:default/team-b"]]. Admin/(catalog owner) have to fix it to make RBAC permission evaluation correct for groups: [["group:default/team-a","group:default/team-b"]]', ); @@ -801,7 +801,7 @@ describe('BackstageRoleManager', () => { 'group:default/team-a', ); expect(result).toBeFalsy(); - expect(loggerMock.warn).toHaveBeenCalledWith( + expect(mockLoggerService.warn).toHaveBeenCalledWith( 'Detected cycle dependencies in the Group graph: [["group:default/team-a","group:default/team-b"]]. Admin/(catalog owner) have to fix it to make RBAC permission evaluation correct for groups: [["group:default/team-a","group:default/team-b"]]', ); }); @@ -841,7 +841,7 @@ describe('BackstageRoleManager', () => { 'role:default/team-c', ); expect(result).toBeFalsy(); - expect(loggerMock.warn).toHaveBeenCalledWith( + expect(mockLoggerService.warn).toHaveBeenCalledWith( 'Detected cycle dependencies in the Group graph: [["group:default/team-a","group:default/team-b"]]. Admin/(catalog owner) have to fix it to make RBAC permission evaluation correct for groups: [["group:default/team-a","group:default/team-b"]]', ); @@ -850,7 +850,7 @@ describe('BackstageRoleManager', () => { 'role:default/team-b', ); expect(result).toBeFalsy(); - expect(loggerMock.warn).toHaveBeenCalledWith( + expect(mockLoggerService.warn).toHaveBeenCalledWith( 'Detected cycle dependencies in the Group graph: [["group:default/team-a","group:default/team-b"]]. Admin/(catalog owner) have to fix it to make RBAC permission evaluation correct for groups: [["group:default/team-a","group:default/team-b"]]', ); @@ -859,7 +859,7 @@ describe('BackstageRoleManager', () => { 'role:default/team-a', ); expect(result).toBeFalsy(); - expect(loggerMock.warn).toHaveBeenCalledWith( + expect(mockLoggerService.warn).toHaveBeenCalledWith( 'Detected cycle dependencies in the Group graph: [["group:default/team-a","group:default/team-b"]]. Admin/(catalog owner) have to fix it to make RBAC permission evaluation correct for groups: [["group:default/team-a","group:default/team-b"]]', ); }); @@ -894,7 +894,7 @@ describe('BackstageRoleManager', () => { 'group:default/team-a', ); expect(result).toBeFalsy(); - expect(loggerMock.warn).toHaveBeenCalledWith( + expect(mockLoggerService.warn).toHaveBeenCalledWith( 'Detected cycle dependencies in the Group graph: [["group:default/team-a","group:default/team-c"]]. Admin/(catalog owner) have to fix it to make RBAC permission evaluation correct for groups: [["group:default/team-a","group:default/team-c"]]', ); }); @@ -932,7 +932,7 @@ describe('BackstageRoleManager', () => { 'role:default/team-a', ); expect(result).toBeFalsy(); - expect(loggerMock.warn).toHaveBeenCalledWith( + expect(mockLoggerService.warn).toHaveBeenCalledWith( 'Detected cycle dependencies in the Group graph: [["group:default/team-a","group:default/team-c"]]. Admin/(catalog owner) have to fix it to make RBAC permission evaluation correct for groups: [["group:default/team-a","group:default/team-c"]]', ); }); @@ -996,7 +996,7 @@ describe('BackstageRoleManager', () => { 'role:default/team-e', ); expect(result).toBeFalsy(); - expect(loggerMock.warn).toHaveBeenCalledWith( + expect(mockLoggerService.warn).toHaveBeenCalledWith( 'Detected cycle dependencies in the Group graph: [["group:default/team-a","group:default/team-c"]]. Admin/(catalog owner) have to fix it to make RBAC permission evaluation correct for groups: [["group:default/team-a","group:default/team-c"]]', ); @@ -1089,7 +1089,7 @@ describe('BackstageRoleManager', () => { const roleManagerMaxDepth = new BackstageRoleManager( catalogApiMock as CatalogApi, - loggerMock as LoggerService, + mockLoggerService as LoggerService, catalogDBClient, rbacDBClient, config, @@ -1225,7 +1225,7 @@ describe('BackstageRoleManager', () => { 'group:default/team-a', ); expect(result).toBeFalsy(); - expect(loggerMock.warn).toHaveBeenCalledWith( + expect(mockLoggerService.warn).toHaveBeenCalledWith( 'Detected cycle dependencies in the Group graph: [["group:default/team-a","group:default/team-c"]]. Admin/(catalog owner) have to fix it to make RBAC permission evaluation correct for groups: [["group:default/team-a","group:default/team-c"]]', ); @@ -1293,7 +1293,7 @@ describe('BackstageRoleManager', () => { 'role:default/team-a', ); expect(result).toBeFalsy(); - expect(loggerMock.warn).toHaveBeenCalledWith( + expect(mockLoggerService.warn).toHaveBeenCalledWith( 'Detected cycle dependencies in the Group graph: [["group:default/team-a","group:default/team-c"]]. Admin/(catalog owner) have to fix it to make RBAC permission evaluation correct for groups: [["group:default/team-a","group:default/team-c"]]', ); @@ -1480,7 +1480,7 @@ describe('BackstageRoleManager', () => { 'role:default/team-a', ); expect(result).toBeFalsy(); - expect(loggerMock.warn).toHaveBeenCalledWith( + expect(mockLoggerService.warn).toHaveBeenCalledWith( 'Detected cycle dependencies in the Group graph: [["group:default/team-a","group:default/team-c"]]. Admin/(catalog owner) have to fix it to make RBAC permission evaluation correct for groups: [["group:default/team-a","group:default/team-c"]]', ); }); diff --git a/plugins/rbac-backend/src/service/enforcer-delegate.test.ts b/plugins/rbac-backend/src/service/enforcer-delegate.test.ts index d810b7e680..72c4e058c9 100644 --- a/plugins/rbac-backend/src/service/enforcer-delegate.test.ts +++ b/plugins/rbac-backend/src/service/enforcer-delegate.test.ts @@ -15,7 +15,7 @@ import { MODEL } from './permission-model'; // TODO: Move to 'catalogServiceMock' from '@backstage/plugin-catalog-node/testUtils' // once '@backstage/plugin-catalog-node' is upgraded -const catalogApi = { +const catalogApiMock = { getEntityAncestors: jest.fn().mockImplementation(), getLocationById: jest.fn().mockImplementation(), getEntities: jest.fn().mockImplementation(), @@ -40,7 +40,7 @@ const roleMetadataStorageMock: RoleMetadataStorage = { removeRoleMetadata: jest.fn().mockImplementation(), }; -const dbManagerMock = Knex.knex({ client: MockClient }); +const mockClientKnex = Knex.knex({ client: MockClient }); const mockAuthService = mockServices.auth(); @@ -126,7 +126,7 @@ describe('EnforcerDelegate', () => { const sqliteInMemoryAdapter = await new CasbinDBAdapterFactory( config, - dbManagerMock, + mockClientKnex, ).createAdapter(); const catalogDBClient = Knex.knex({ client: MockClient }); @@ -143,7 +143,7 @@ describe('EnforcerDelegate', () => { enfAddPoliciesSpy = jest.spyOn(enf, 'addPolicies'); const rm = new BackstageRoleManager( - catalogApi, + catalogApiMock, logger, catalogDBClient, rbacDBClient, diff --git a/plugins/rbac-backend/src/service/enforcer-delegate.ts b/plugins/rbac-backend/src/service/enforcer-delegate.ts index f434a1cc36..0bcdfd47d4 100644 --- a/plugins/rbac-backend/src/service/enforcer-delegate.ts +++ b/plugins/rbac-backend/src/service/enforcer-delegate.ts @@ -3,13 +3,13 @@ import { Knex } from 'knex'; import EventEmitter from 'events'; +import { ADMIN_ROLE_NAME } from '../admin-permissions/admin-creation'; import { RoleMetadataDao, RoleMetadataStorage, } from '../database/role-metadata'; import { mergeRoleMetadata, policiesToString, policyToString } from '../helper'; import { MODEL } from './permission-model'; -import { ADMIN_ROLE_NAME } from './permission-policy'; export type RoleEvents = 'roleAdded'; export interface RoleEventEmitter { diff --git a/plugins/rbac-backend/src/service/permission-policy.test.ts b/plugins/rbac-backend/src/service/permission-policy.test.ts index 5f646c5977..811956d155 100644 --- a/plugins/rbac-backend/src/service/permission-policy.test.ts +++ b/plugins/rbac-backend/src/service/permission-policy.test.ts @@ -17,7 +17,6 @@ import { Model, newEnforcer, newModelFromString, - StringAdapter, } from 'casbin'; import * as Knex from 'knex'; import { MockClient } from 'knex-mock-client'; @@ -26,6 +25,7 @@ import type { RoleMetadata } from '@janus-idp/backstage-plugin-rbac-common'; import { resolve } from 'path'; +import { ADMIN_ROLE_NAME } from '../admin-permissions/admin-creation'; import { CasbinDBAdapterFactory } from '../database/casbin-adapter-factory'; import { ConditionalStorage } from '../database/conditional-storage'; import { @@ -35,14 +35,14 @@ import { import { BackstageRoleManager } from '../role-manager/role-manager'; import { EnforcerDelegate } from './enforcer-delegate'; import { MODEL } from './permission-model'; -import { ADMIN_ROLE_NAME, RBACPermissionPolicy } from './permission-policy'; +import { RBACPermissionPolicy } from './permission-policy'; import { PluginPermissionMetadataCollector } from './plugin-endpoints'; type PermissionAction = 'create' | 'read' | 'update' | 'delete'; // TODO: Move to 'catalogServiceMock' from '@backstage/plugin-catalog-node/testUtils' // once '@backstage/plugin-catalog-node' is upgraded -const catalogApi = { +const catalogApiMock = { getEntityAncestors: jest.fn().mockImplementation(), getLocationById: jest.fn().mockImplementation(), getEntities: jest.fn().mockImplementation(), @@ -59,7 +59,7 @@ const catalogApi = { getLocationByEntity: jest.fn().mockImplementation(), }; -const conditionalStorage: ConditionalStorage = { +const conditionalStorageMock: ConditionalStorage = { filterConditions: jest.fn().mockImplementation(() => []), createCondition: jest.fn().mockImplementation(), checkConflictedConditions: jest.fn().mockImplementation(), @@ -85,14 +85,12 @@ const roleMetadataStorageMock: RoleMetadataStorage = { removeRoleMetadata: jest.fn().mockImplementation(), }; -const dbManagerMock = Knex.knex({ client: MockClient }); - const csvPermFile = resolve( __dirname, - './../__fixtures__/data/valid-csv/rbac-policy.csv', + '../../__fixtures__/data/valid-csv/rbac-policy.csv', ); -const knex = Knex.knex({ client: MockClient }); +const mockClientKnex = Knex.knex({ client: MockClient }); const mockAuthService = mockServices.auth(); @@ -109,8 +107,6 @@ const pluginMetadataCollectorMock: Partial = getMetadataByPluginId: jest.fn().mockImplementation(), }; -const mockAuth = mockServices.auth(); - const modifiedBy = 'user:default/some-admin'; describe('RBACPermissionPolicy Tests', () => { @@ -135,10 +131,15 @@ describe('RBACPermissionPolicy Tests', () => { throw new Error(`Failed to create`); }); - const stringPolicy = `p, user:default/known_user, test-resource, update, allow `; const config = newConfig(); - const adapter = await newAdapter(config, stringPolicy); + const adapter = await newAdapter(config); const enfDelegate = await newEnforcerDelegate(adapter, config); + await enfDelegate.addPolicy([ + 'user:default/known_user', + 'test-resource', + 'update', + 'allow', + ]); await expect(newPermissionPolicy(config, enfDelegate)).rejects.toThrow( 'Failed to create', @@ -155,7 +156,7 @@ describe('RBACPermissionPolicy Tests', () => { enfDelegate = await newEnforcerDelegate(adapter, config); policy = await newPermissionPolicy(config, enfDelegate); - catalogApi.getEntities.mockReturnValue({ items: [] }); + catalogApiMock.getEntities.mockReturnValue({ items: [] }); }); // case1 @@ -295,7 +296,7 @@ describe('RBACPermissionPolicy Tests', () => { config = newConfig(); adapter = await newAdapter(config); - catalogApi.getEntities.mockReturnValue({ items: [] }); + catalogApiMock.getEntities.mockReturnValue({ items: [] }); }); it('should cleanup old group policies and metadata after re-attach policy file', async () => { @@ -607,7 +608,7 @@ describe('RBACPermissionPolicy Tests', () => { beforeEach(async () => { const basicAndResourcePermissions = resolve( __dirname, - './../__fixtures__/data/valid-csv/basic-and-resource-policies.csv', + '../../__fixtures__/data/valid-csv/basic-and-resource-policies.csv', ); const config = newConfig(basicAndResourcePermissions); const adapter = await newAdapter(config); @@ -619,7 +620,7 @@ describe('RBACPermissionPolicy Tests', () => { roleMetadataStorageTest, ); - catalogApi.getEntities.mockReturnValue({ items: [] }); + catalogApiMock.getEntities.mockReturnValue({ items: [] }); }); // +-------+------+------------------------------------+ // | allow | deny | result | | @@ -898,7 +899,7 @@ describe('RBACPermissionPolicy Tests', () => { const superUser = new Array<{ name: string }>(); superUser.push({ name: 'user:default/super_user' }); - catalogApi.getEntities.mockReturnValue({ items: [] }); + catalogApiMock.getEntities.mockReturnValue({ items: [] }); beforeEach(async () => { roleMetadataStorageMock.findRoleMetadata = jest @@ -934,68 +935,6 @@ describe('RBACPermissionPolicy Tests', () => { ); }); - it('should build the admin permissions', async () => { - const enfRole = await enfDelegate.getFilteredGroupingPolicy(1, adminRole); - const enfPermission = await enfDelegate.getFilteredPolicy(0, adminRole); - expect(enfRole).toEqual(groupPolicy); - expect(enfPermission).toEqual(permissions); - }); - - it('should fail to build the admin permissions, problem with creating role metadata', async () => { - roleMetadataStorageMock.findRoleMetadata = jest - .fn() - .mockImplementation(async (): Promise => { - return undefined; - }); - - roleMetadataStorageMock.createRoleMetadata = jest - .fn() - .mockImplementation(async (): Promise => { - throw new Error(`Failed to create`); - }); - - const config = mockServices.rootConfig({ - data: { - permission: { - rbac: { - 'policies-csv-file': csvPermFile, - policyFileReload: true, - }, - }, - backend: { - database: { - client: 'better-sqlite3', - connection: ':memory:', - }, - }, - }, - }); - - await expect( - newPermissionPolicy(config, enfDelegate, roleMetadataStorageMock), - ).rejects.toThrow('Failed to create'); - }); - - it('should build and update a legacy admin permission', async () => { - roleMetadataStorageTest.findRoleMetadata = jest - .fn() - .mockImplementationOnce( - async ( - _roleEntityRef: string, - _trx: Knex.Knex.Transaction, - ): Promise => { - return { source: 'legacy' }; - }, - ); - - const enfRole = await enfDelegate.getFilteredGroupingPolicy(1, adminRole); - const enfPermission = await enfDelegate.getFilteredPolicy(0, adminRole); - - expect(enfRole).toEqual(groupPolicy); - expect(enfPermission).toEqual(permissions); - expect(roleMetadataStorageTest.updateRoleMetadata).toHaveBeenCalled(); - }); - it('should allow read access to resource permission for user from config file', async () => { const decision = await policy.handle( newPolicyQueryWithResourcePermission( @@ -1099,7 +1038,7 @@ describe('Policy checks for resourced permissions defined by name', () => { }); it('should allow access to resourced permission assigned by name', async () => { - catalogApi.getEntities.mockReturnValue({ items: [] }); + catalogApiMock.getEntities.mockReturnValue({ items: [] }); await enfDelegate.addGroupingPolicy( ['user:default/tor', 'role:default/catalog_reader'], @@ -1128,7 +1067,7 @@ describe('Policy checks for resourced permissions defined by name', () => { }); it('should allow access to resourced permission assigned by name, because it has higher priority then permission for the same resource assigned by resource type', async () => { - catalogApi.getEntities.mockReturnValue({ items: [] }); + catalogApiMock.getEntities.mockReturnValue({ items: [] }); await enfDelegate.addGroupingPolicy( ['user:default/tor', 'role:default/catalog_reader'], @@ -1155,7 +1094,7 @@ describe('Policy checks for resourced permissions defined by name', () => { }); it('should deny access to resourced permission assigned by name, because it has higher priority then permission for the same resource assigned by resource type', async () => { - catalogApi.getEntities.mockReturnValue({ items: [] }); + catalogApiMock.getEntities.mockReturnValue({ items: [] }); await enfDelegate.addGroupingPolicy( ['user:default/tor', 'role:default/catalog_reader'], @@ -1201,7 +1140,7 @@ describe('Policy checks for resourced permissions defined by name', () => { members: ['tor'], }, }; - catalogApi.getEntities.mockImplementation(_arg => { + catalogApiMock.getEntities.mockImplementation(_arg => { return { items: [groupEntityMock] }; }); @@ -1257,7 +1196,7 @@ describe('Policy checks for resourced permissions defined by name', () => { namespace: 'default', }, }; - catalogApi.getEntities.mockImplementation(_arg => { + catalogApiMock.getEntities.mockImplementation(_arg => { return { items: [groupParentMock, groupEntityMock] }; }); @@ -1307,7 +1246,7 @@ describe('Policy checks for users and groups', () => { beforeEach(async () => { const policyChecksCSV = resolve( __dirname, - './../__fixtures__/data/valid-csv/policy-checks.csv', + '../../__fixtures__/data/valid-csv/policy-checks.csv', ); const config = newConfig(policyChecksCSV); const adapter = await newAdapter(config); @@ -1316,7 +1255,7 @@ describe('Policy checks for users and groups', () => { policy = await newPermissionPolicy(config, enfDelegate); - catalogApi.getEntities.mockReset(); + catalogApiMock.getEntities.mockReset(); }); // User inherits permissions from groups and their parent groups. @@ -1350,7 +1289,7 @@ describe('Policy checks for users and groups', () => { members: ['alice'], }, }; - catalogApi.getEntities.mockReturnValue({ items: [entityMock] }); + catalogApiMock.getEntities.mockReturnValue({ items: [entityMock] }); const decision = await policy.handle( newPolicyQueryWithBasicPermission('test.resource'), @@ -1379,7 +1318,7 @@ describe('Policy checks for users and groups', () => { members: ['akira'], }, }; - catalogApi.getEntities.mockReturnValue({ items: [entityMock] }); + catalogApiMock.getEntities.mockReturnValue({ items: [entityMock] }); const decision = await policy.handle( newPolicyQueryWithBasicPermission('test.resource'), newPolicyQueryUser('user:default/akira'), @@ -1407,7 +1346,7 @@ describe('Policy checks for users and groups', () => { members: ['antey'], }, }; - catalogApi.getEntities.mockReturnValue({ items: [entityMock] }); + catalogApiMock.getEntities.mockReturnValue({ items: [entityMock] }); const decision = await policy.handle( newPolicyQueryWithBasicPermission('test.resource'), newPolicyQueryUser('user:default/antey'), @@ -1432,7 +1371,7 @@ describe('Policy checks for users and groups', () => { namespace: 'default', }, }; - catalogApi.getEntities.mockReturnValue({ items: [entityMock] }); + catalogApiMock.getEntities.mockReturnValue({ items: [entityMock] }); const decision = await policy.handle( newPolicyQueryWithBasicPermission('test.resource'), @@ -1461,7 +1400,7 @@ describe('Policy checks for users and groups', () => { members: ['mike'], }, }; - catalogApi.getEntities.mockReturnValue({ items: [entityMock] }); + catalogApiMock.getEntities.mockReturnValue({ items: [entityMock] }); const decision = await policy.handle( newPolicyQueryWithBasicPermission('test.resource'), newPolicyQueryUser('user:default/mike'), @@ -1489,7 +1428,7 @@ describe('Policy checks for users and groups', () => { members: ['tom'], }, }; - catalogApi.getEntities.mockReturnValue({ items: [entityMock] }); + catalogApiMock.getEntities.mockReturnValue({ items: [entityMock] }); const decision = await policy.handle( newPolicyQueryWithBasicPermission('test.resource'), newPolicyQueryUser('user:default/tom'), @@ -1528,7 +1467,7 @@ describe('Policy checks for users and groups', () => { }, }; - catalogApi.getEntities.mockImplementation(_arg => { + catalogApiMock.getEntities.mockImplementation(_arg => { return { items: [groupMock, groupParentMock] }; }); @@ -1561,7 +1500,7 @@ describe('Policy checks for users and groups', () => { members: ['alice'], }, }; - catalogApi.getEntities.mockReturnValue({ items: [entityMock] }); + catalogApiMock.getEntities.mockReturnValue({ items: [entityMock] }); const decision = await policy.handle( newPolicyQueryWithResourcePermission( @@ -1591,7 +1530,7 @@ describe('Policy checks for users and groups', () => { namespace: 'default', }, }; - catalogApi.getEntities.mockReturnValue({ items: [entityMock] }); + catalogApiMock.getEntities.mockReturnValue({ items: [entityMock] }); const decision = await policy.handle( newPolicyQueryWithResourcePermission( 'test.resource.read', @@ -1620,7 +1559,7 @@ describe('Policy checks for users and groups', () => { namespace: 'default', }, }; - catalogApi.getEntities.mockReturnValue({ items: [entityMock] }); + catalogApiMock.getEntities.mockReturnValue({ items: [entityMock] }); const decision = await policy.handle( newPolicyQueryWithResourcePermission( 'test.resource.read', @@ -1649,7 +1588,7 @@ describe('Policy checks for users and groups', () => { namespace: 'default', }, }; - catalogApi.getEntities.mockReturnValue({ items: [entityMock] }); + catalogApiMock.getEntities.mockReturnValue({ items: [entityMock] }); const decision = await policy.handle( newPolicyQueryWithResourcePermission( @@ -1682,7 +1621,7 @@ describe('Policy checks for users and groups', () => { members: ['mike'], }, }; - catalogApi.getEntities.mockReturnValue({ items: [entityMock] }); + catalogApiMock.getEntities.mockReturnValue({ items: [entityMock] }); const decision = await policy.handle( newPolicyQueryWithResourcePermission( 'test.resource.read', @@ -1714,7 +1653,7 @@ describe('Policy checks for users and groups', () => { members: ['tom'], }, }; - catalogApi.getEntities.mockReturnValue({ items: [entityMock] }); + catalogApiMock.getEntities.mockReturnValue({ items: [entityMock] }); const decision = await policy.handle( newPolicyQueryWithResourcePermission( 'test.resource.read', @@ -1757,7 +1696,7 @@ describe('Policy checks for users and groups', () => { }, }; - catalogApi.getEntities.mockImplementation(_arg => { + catalogApiMock.getEntities.mockImplementation(_arg => { return { items: [groupParentMock, groupMock] }; }); @@ -1784,38 +1723,38 @@ describe('Policy checks for conditional policies', () => { let policy: RBACPermissionPolicy; beforeEach(async () => { - const adapter = new StringAdapter( - ` - p, role:default/test, catalog-entity, read, allow - - g, group:default/test-group, role:default/test - g, group:default/qa, role:default/qa - `, - ); const config = newConfig(undefined, []); + const adapter = await newAdapter(config); const theModel = newModelFromString(MODEL); const logger = mockServices.logger.mock(); const enf = await createEnforcer(theModel, adapter, logger, config); + const policies = [['role:default/test', 'catalog-entity', 'read', 'allow']]; + const groupPolicies = [ + ['group:default/test-group', 'role:default/test'], + ['group:default/qa', 'role:default/qa'], + ]; + await enf.addPolicies(policies); + await enf.addGroupingPolicies(groupPolicies); const enfDelegate = new EnforcerDelegate( enf, roleMetadataStorageMock, - knex, + mockClientKnex, ); policy = await RBACPermissionPolicy.build( logger, auditLoggerMock, config, - conditionalStorage, + conditionalStorageMock, enfDelegate, roleMetadataStorageMock, - knex, + mockClientKnex, pluginMetadataCollectorMock as PluginPermissionMetadataCollector, - mockAuth, + mockAuthService, ); - catalogApi.getEntities.mockReset(); + catalogApiMock.getEntities.mockReset(); }); it('should execute condition policy', async () => { @@ -1830,8 +1769,8 @@ describe('Policy checks for conditional policies', () => { members: ['mike'], }, }; - catalogApi.getEntities.mockReturnValue({ items: [entityMock] }); - (conditionalStorage.filterConditions as jest.Mock).mockReturnValueOnce([ + catalogApiMock.getEntities.mockReturnValue({ items: [entityMock] }); + (conditionalStorageMock.filterConditions as jest.Mock).mockReturnValueOnce([ { id: 1, pluginId: 'catalog', @@ -1887,8 +1826,8 @@ describe('Policy checks for conditional policies', () => { members: ['mike'], }, }; - catalogApi.getEntities.mockReturnValue({ items: [entityMock] }); - (conditionalStorage.filterConditions as jest.Mock).mockReturnValueOnce([ + catalogApiMock.getEntities.mockReturnValue({ items: [entityMock] }); + (conditionalStorageMock.filterConditions as jest.Mock).mockReturnValueOnce([ { id: 1, pluginId: 'catalog', @@ -1958,10 +1897,10 @@ describe('Policy checks for conditional policies', () => { members: ['mike'], }, }; - catalogApi.getEntities.mockReturnValue({ + catalogApiMock.getEntities.mockReturnValue({ items: [entityMock, qaGroupMock], }); - (conditionalStorage.filterConditions as jest.Mock) + (conditionalStorageMock.filterConditions as jest.Mock) .mockReturnValueOnce([ { id: 1, @@ -2037,8 +1976,8 @@ describe('Policy checks for conditional policies', () => { members: ['mike'], }, }; - catalogApi.getEntities.mockReturnValue({ items: [entityMock] }); - (conditionalStorage.filterConditions as jest.Mock).mockReturnValueOnce([ + catalogApiMock.getEntities.mockReturnValue({ items: [entityMock] }); + (conditionalStorageMock.filterConditions as jest.Mock).mockReturnValueOnce([ { id: 1, pluginId: 'catalog', @@ -2171,16 +2110,10 @@ function newConfig( }); } -async function newAdapter( - config: Config, - stringPolicy?: string, -): Promise { - if (stringPolicy) { - return new StringAdapter(stringPolicy); - } +async function newAdapter(config: Config): Promise { return await new CasbinDBAdapterFactory( config, - dbManagerMock, + mockClientKnex, ).createAdapter(); } @@ -2195,7 +2128,7 @@ async function createEnforcer( const enf = await newEnforcer(theModel, adapter); const rm = new BackstageRoleManager( - catalogApi, + catalogApiMock, logger, catalogDBClient, rbacDBClient, @@ -2228,7 +2161,7 @@ async function newEnforcerDelegate( await enf.addGroupingPolicies(storedGroupingPolicies); } - return new EnforcerDelegate(enf, roleMetadataStorageMock, knex); + return new EnforcerDelegate(enf, roleMetadataStorageMock, mockClientKnex); } async function newPermissionPolicy( @@ -2241,12 +2174,12 @@ async function newPermissionPolicy( logger, auditLoggerMock, config, - conditionalStorage, + conditionalStorageMock, enfDelegate, roleMock || roleMetadataStorageMock, - knex, + mockClientKnex, pluginMetadataCollectorMock as PluginPermissionMetadataCollector, - mockAuth, + mockAuthService, ); auditLoggerMock.auditLog.mockReset(); return permissionPolicy; diff --git a/plugins/rbac-backend/src/service/permission-policy.ts b/plugins/rbac-backend/src/service/permission-policy.ts index 228cf0c64b..c33eb90553 100644 --- a/plugins/rbac-backend/src/service/permission-policy.ts +++ b/plugins/rbac-backend/src/service/permission-policy.ts @@ -3,7 +3,6 @@ import type { BackstageUserInfo, LoggerService, } from '@backstage/backend-plugin-api'; -import type { Config } from '@backstage/config'; import type { ConfigApi } from '@backstage/core-plugin-api'; import { AuthorizeResult, @@ -30,176 +29,23 @@ import { toPermissionAction, } from '@janus-idp/backstage-plugin-rbac-common'; +import { + setAdminPermissions, + useAdminsFromConfig, +} from '../admin-permissions/admin-creation'; import { createPermissionEvaluationOptions, EVALUATE_PERMISSION_ACCESS_STAGE, EvaluationEvents, - HANDLE_RBAC_DATA_STAGE, - PermissionAuditInfo, - PermissionEvents, - RBAC_BACKEND, - RoleAuditInfo, - RoleEvents, } from '../audit-log/audit-logger'; import { replaceAliases } from '../conditional-aliases/alias-resolver'; import { ConditionalStorage } from '../database/conditional-storage'; -import { - RoleMetadataDao, - RoleMetadataStorage, -} from '../database/role-metadata'; +import { RoleMetadataStorage } from '../database/role-metadata'; import { CSVFileWatcher } from '../file-permissions/csv-file-watcher'; import { YamlConditinalPoliciesFileWatcher } from '../file-permissions/yaml-conditional-file-watcher'; -import { removeTheDifference } from '../helper'; -import { validateEntityReference } from '../validation/policies-validation'; import { EnforcerDelegate } from './enforcer-delegate'; import { PluginPermissionMetadataCollector } from './plugin-endpoints'; -export const ADMIN_ROLE_NAME = 'role:default/rbac_admin'; -export const ADMIN_ROLE_AUTHOR = 'application configuration'; -const DEF_ADMIN_ROLE_DESCRIPTION = - 'The default permission policy for the admin role allows for the creation, deletion, updating, and reading of roles and permission policies.'; - -const getAdminRoleMetadata = (): RoleMetadataDao => { - const currentDate: Date = new Date(); - return { - source: 'configuration', - roleEntityRef: ADMIN_ROLE_NAME, - description: DEF_ADMIN_ROLE_DESCRIPTION, - author: ADMIN_ROLE_AUTHOR, - modifiedBy: ADMIN_ROLE_AUTHOR, - lastModified: currentDate.toUTCString(), - createdAt: currentDate.toUTCString(), - }; -}; - -const useAdminsFromConfig = async ( - admins: Config[], - enf: EnforcerDelegate, - auditLogger: AuditLogger, - roleMetadataStorage: RoleMetadataStorage, - knex: Knex, -) => { - const addedGroupPolicies = new Map(); - const newGroupPolicies = new Map(); - - for (const admin of admins) { - const entityRef = admin.getString('name'); - validateEntityReference(entityRef); - - addedGroupPolicies.set(entityRef, ADMIN_ROLE_NAME); - - if (!(await enf.hasGroupingPolicy(...[entityRef, ADMIN_ROLE_NAME]))) { - newGroupPolicies.set(entityRef, ADMIN_ROLE_NAME); - } - } - - const adminRoleMeta = - await roleMetadataStorage.findRoleMetadata(ADMIN_ROLE_NAME); - - const trx = await knex.transaction(); - let addedRoleMembers; - try { - if (!adminRoleMeta) { - // even if there are no user, we still create default role metadata for admins - await roleMetadataStorage.createRoleMetadata(getAdminRoleMetadata(), trx); - } else if (adminRoleMeta.source === 'legacy') { - await roleMetadataStorage.updateRoleMetadata( - getAdminRoleMetadata(), - ADMIN_ROLE_NAME, - trx, - ); - } - - addedRoleMembers = Array.from(newGroupPolicies.entries()); - await enf.addGroupingPolicies( - addedRoleMembers, - getAdminRoleMetadata(), - trx, - ); - - await trx.commit(); - } catch (error) { - await trx.rollback(error); - throw error; - } - - await auditLogger.auditLog({ - actorId: RBAC_BACKEND, - message: `Created or updated role`, - eventName: RoleEvents.CREATE_OR_UPDATE_ROLE, - metadata: { - ...getAdminRoleMetadata(), - members: addedRoleMembers.map(gp => gp[0]), - }, - stage: HANDLE_RBAC_DATA_STAGE, - status: 'succeeded', - }); - - const configGroupPolicies = await enf.getFilteredGroupingPolicy( - 1, - ADMIN_ROLE_NAME, - ); - - await removeTheDifference( - configGroupPolicies.map(gp => gp[0]), - Array.from(addedGroupPolicies.keys()), - 'configuration', - ADMIN_ROLE_NAME, - enf, - auditLogger, - ADMIN_ROLE_AUTHOR, - ); -}; - -const addAdminPermission = async ( - policy: string[], - enf: EnforcerDelegate, - auditLogger: AuditLogger, -) => { - await enf.addPolicy(policy); - - await auditLogger.auditLog({ - actorId: RBAC_BACKEND, - message: `Created policy`, - eventName: PermissionEvents.CREATE_POLICY, - metadata: { policies: [policy], source: 'configuration' }, - stage: HANDLE_RBAC_DATA_STAGE, - status: 'succeeded', - }); -}; - -const setAdminPermissions = async ( - enf: EnforcerDelegate, - auditLogger: AuditLogger, -) => { - await addAdminPermission( - [ADMIN_ROLE_NAME, 'policy-entity', 'read', 'allow'], - enf, - auditLogger, - ); - await addAdminPermission( - [ADMIN_ROLE_NAME, 'policy-entity', 'create', 'allow'], - enf, - auditLogger, - ); - await addAdminPermission( - [ADMIN_ROLE_NAME, 'policy-entity', 'delete', 'allow'], - enf, - auditLogger, - ); - await addAdminPermission( - [ADMIN_ROLE_NAME, 'policy-entity', 'update', 'allow'], - enf, - auditLogger, - ); - // needed for rbac frontend. - await addAdminPermission( - [ADMIN_ROLE_NAME, 'catalog-entity', 'read', 'allow'], - enf, - auditLogger, - ); -}; - const evaluatePermMsg = ( userEntityRef: string | undefined, result: AuthorizeResult, diff --git a/plugins/rbac-backend/src/service/policies-rest-api.test.ts b/plugins/rbac-backend/src/service/policies-rest-api.test.ts index 2bcfb698dd..580c67c55b 100644 --- a/plugins/rbac-backend/src/service/policies-rest-api.test.ts +++ b/plugins/rbac-backend/src/service/policies-rest-api.test.ts @@ -49,7 +49,7 @@ jest.mock('@backstage/plugin-auth-node', () => ({ getBearerTokenFromAuthorizationHeader: () => 'token', })); -const mockEnforcer: Partial = { +const enforcerDelegateMock: Partial = { hasPolicy: jest.fn().mockImplementation(), hasGroupingPolicy: jest.fn().mockImplementation(), @@ -92,7 +92,7 @@ const roleMetadataStorageMock: RoleMetadataStorage = { removeRoleMetadata: jest.fn().mockImplementation(), }; -const conditionalStorage = { +const conditionalStorageMock = { filterConditions: jest.fn().mockImplementation(), createCondition: jest.fn().mockImplementation(), checkConflictedConditions: jest.fn().mockImplementation(), @@ -124,7 +124,7 @@ const mockHttpAuth = mockServices.httpAuth({ pluginId: 'permission', defaultCredentials: mockCredentials.user('user:default/guest'), }); -const mockAuth = mockServices.auth(); +const mockAuthService = mockServices.auth(); const credentials = mockCredentials.user('user:default/guest'); const conditions: RoleConditionalPolicyDecision[] = [ @@ -209,23 +209,23 @@ describe('REST policies api', () => { let server: PoliciesServer; beforeEach(async () => { - conditionalStorage.filterConditions = jest + conditionalStorageMock.filterConditions = jest .fn() .mockImplementation(async () => { return conditions; }); - mockEnforcer.hasPolicy = jest + enforcerDelegateMock.hasPolicy = jest .fn() .mockImplementation(async (..._param: string[]): Promise => { return false; }); - mockEnforcer.hasGroupingPolicy = jest + enforcerDelegateMock.hasGroupingPolicy = jest .fn() .mockImplementation(async (..._param: string[]): Promise => { return false; }); - mockEnforcer.getFilteredPolicy = jest + enforcerDelegateMock.getFilteredPolicy = jest .fn() .mockImplementation( async (_fieldIndex: number, ..._fieldValues: string[]) => { @@ -239,19 +239,19 @@ describe('REST policies api', () => { ]; }, ); - mockEnforcer.getFilteredGroupingPolicy = jest + enforcerDelegateMock.getFilteredGroupingPolicy = jest .fn() .mockImplementation( async (_fieldIndex: number, ..._fieldValues: string[]) => { return [['user:default/permission_admin', 'role:default/rbac_admin']]; }, ); - mockEnforcer.removeGroupingPolicies = jest + enforcerDelegateMock.removeGroupingPolicies = jest .fn() .mockImplementation(async (..._param: string[]): Promise => { return true; }); - mockEnforcer.addGroupingPolicies = jest.fn().mockImplementation(); + enforcerDelegateMock.addGroupingPolicies = jest.fn().mockImplementation(); roleMetadataStorageMock.findRoleMetadata = jest .fn() @@ -272,28 +272,28 @@ describe('REST policies api', () => { logger, discovery: mockDiscovery, httpAuth: mockHttpAuth, - auth: mockAuth, + auth: mockAuthService, policy: await RBACPermissionPolicy.build( logger, auditLoggerMock, config, - conditionalStorage, - mockEnforcer as EnforcerDelegate, + conditionalStorageMock, + enforcerDelegateMock as EnforcerDelegate, roleMetadataStorageMock, knex, pluginPermissionMetadataCollectorMock as PluginPermissionMetadataCollector, - mockAuth, + mockAuthService, ), }; server = new PoliciesServer( mockPermissionEvaluator, options, - mockEnforcer as EnforcerDelegate, + enforcerDelegateMock as EnforcerDelegate, config, mockHttpAuth, - mockAuth, - conditionalStorage, + mockAuthService, + conditionalStorageMock, pluginPermissionMetadataCollectorMock as PluginPermissionMetadataCollector, roleMetadataStorageMock, auditLoggerMock, @@ -301,7 +301,7 @@ describe('REST policies api', () => { const router = await server.serve(); app = express().use(router); app.use(MiddlewareFactory.create({ logger, config }).error()); - conditionalStorage.getCondition.mockReset(); + conditionalStorageMock.getCondition.mockReset(); validateRoleConditionMock.mockReset(); auditLoggerMock.auditLog.mockClear(); jest.clearAllMocks(); @@ -345,6 +345,10 @@ describe('REST policies api', () => { }); describe('POST /policies', () => { + afterEach(() => { + (enforcerDelegateMock.addPolicies as jest.Mock).mockReset(); + }); + it('should return a status of Unauthorized', async () => { mockedAuthorize.mockImplementationOnce(async () => [ { result: AuthorizeResult.DENY }, @@ -557,7 +561,7 @@ describe('REST policies api', () => { }); it('should not be created permission policy, because it is has been already present', async () => { - mockEnforcer.hasPolicy = jest + enforcerDelegateMock.hasPolicy = jest .fn() .mockImplementation(async (...param: string[]): Promise => { if (param.at(2) === 'read') { @@ -587,7 +591,7 @@ describe('REST policies api', () => { }); it('should not be created permission policy caused some unexpected error', async () => { - mockEnforcer.addPolicies = jest + enforcerDelegateMock.addPolicies = jest .fn() .mockImplementation(async (): Promise => { throw new Error(`Failed to add policies`); @@ -661,7 +665,7 @@ describe('REST policies api', () => { }); it('should be returned permission policies by user reference', async () => { - mockEnforcer.getFilteredPolicy = jest + enforcerDelegateMock.getFilteredPolicy = jest .fn() .mockImplementation( async (_fieldIndex: number, ..._fieldValues: string[]) => { @@ -692,7 +696,7 @@ describe('REST policies api', () => { ]); }); it('should be returned policies by user reference not found', async () => { - mockEnforcer.getFilteredPolicy = jest + enforcerDelegateMock.getFilteredPolicy = jest .fn() .mockImplementation( async (_fieldIndex: number, ..._fieldValues: string[]) => { @@ -741,18 +745,20 @@ describe('REST policies api', () => { }); it('should be returned list all policies', async () => { - mockEnforcer.getPolicy = jest.fn().mockImplementation(async () => { - return [ - [ - 'user:default/permission_admin', - 'policy-entity', - 'create', - 'allow', - 'rest', - ], - ['user:default/guest', 'policy-entity', 'read', 'allow', 'rest'], - ]; - }); + enforcerDelegateMock.getPolicy = jest + .fn() + .mockImplementation(async () => { + return [ + [ + 'user:default/permission_admin', + 'policy-entity', + 'create', + 'allow', + 'rest', + ], + ['user:default/guest', 'policy-entity', 'read', 'allow', 'rest'], + ]; + }); const result = await request(app).get('/policies').send(); expect(result.statusCode).toBe(200); expect(result.body).toEqual([ @@ -777,7 +783,7 @@ describe('REST policies api', () => { ]); }); it('should be returned list filtered policies', async () => { - mockEnforcer.getFilteredPolicy = jest + enforcerDelegateMock.getFilteredPolicy = jest .fn() .mockImplementation( async (_fieldIndex: number, ..._fieldValues: string[]) => { @@ -891,7 +897,7 @@ describe('REST policies api', () => { }); it('should fail to delete, because policy not found', async () => { - mockEnforcer.hasPolicy = jest + enforcerDelegateMock.hasPolicy = jest .fn() .mockImplementation(async (..._param: string[]): Promise => { return false; @@ -915,12 +921,12 @@ describe('REST policies api', () => { }); it('should fail to delete, because unexpected error', async () => { - mockEnforcer.hasPolicy = jest + enforcerDelegateMock.hasPolicy = jest .fn() .mockImplementation(async (..._param: string[]): Promise => { return true; }); - mockEnforcer.removePolicies = jest + enforcerDelegateMock.removePolicies = jest .fn() .mockImplementation(async (..._param: string[]): Promise => { throw new Error('Fail to delete policy'); @@ -1005,12 +1011,12 @@ describe('REST policies api', () => { }, ); - mockEnforcer.hasPolicy = jest + enforcerDelegateMock.hasPolicy = jest .fn() .mockImplementation(async (..._param: string[]): Promise => { return true; }); - mockEnforcer.removePolicies = jest + enforcerDelegateMock.removePolicies = jest .fn() .mockImplementation(async (..._param: string[]): Promise => { return true; @@ -1042,12 +1048,12 @@ describe('REST policies api', () => { }); it('should delete policy', async () => { - mockEnforcer.hasPolicy = jest + enforcerDelegateMock.hasPolicy = jest .fn() .mockImplementation(async (..._param: string[]): Promise => { return true; }); - mockEnforcer.removePolicies = jest + enforcerDelegateMock.removePolicies = jest .fn() .mockImplementation(async (..._param: string[]): Promise => { return true; @@ -1163,7 +1169,7 @@ describe('REST policies api', () => { }); it('should fail to update policy - newPolicy permission is absent', async () => { - mockEnforcer.hasPolicy = jest + enforcerDelegateMock.hasPolicy = jest .fn() .mockImplementation(async (..._param: string[]): Promise => { return true; @@ -1189,7 +1195,7 @@ describe('REST policies api', () => { }); it('should fail to update policy - newPolicy policy is absent', async () => { - mockEnforcer.hasPolicy = jest + enforcerDelegateMock.hasPolicy = jest .fn() .mockImplementation(async (..._param: string[]): Promise => { return true; @@ -1215,7 +1221,7 @@ describe('REST policies api', () => { }); it('should fail to update policy - newPolicy effect is absent', async () => { - mockEnforcer.hasPolicy = jest + enforcerDelegateMock.hasPolicy = jest .fn() .mockImplementation(async (..._param: string[]): Promise => { return true; @@ -1316,7 +1322,7 @@ describe('REST policies api', () => { }); it('should fail to update policy - newPolicy is already present', async () => { - mockEnforcer.hasPolicy = jest + enforcerDelegateMock.hasPolicy = jest .fn() .mockImplementation(async (..._param: string[]): Promise => { return true; @@ -1348,7 +1354,7 @@ describe('REST policies api', () => { }); it('should nothing to update', async () => { - mockEnforcer.hasPolicy = jest + enforcerDelegateMock.hasPolicy = jest .fn() .mockImplementation(async (..._param: string[]): Promise => { return true; @@ -1376,7 +1382,7 @@ describe('REST policies api', () => { }); it('should nothing to update - same permissions with different policy in a different order', async () => { - mockEnforcer.hasPolicy = jest + enforcerDelegateMock.hasPolicy = jest .fn() .mockImplementation(async (..._param: string[]): Promise => { return true; @@ -1414,7 +1420,7 @@ describe('REST policies api', () => { }); it('should nothing to update - same permissions with different permission type in a different order', async () => { - mockEnforcer.hasPolicy = jest + enforcerDelegateMock.hasPolicy = jest .fn() .mockImplementation(async (..._param: string[]): Promise => { return true; @@ -1452,7 +1458,7 @@ describe('REST policies api', () => { }); it('should fail to update policy - unable to remove oldPolicy', async () => { - mockEnforcer.hasPolicy = jest + enforcerDelegateMock.hasPolicy = jest .fn() .mockImplementation(async (...param: string[]): Promise => { if (param[2] === 'create') { @@ -1460,7 +1466,7 @@ describe('REST policies api', () => { } return true; }); - mockEnforcer.updatePolicies = jest + enforcerDelegateMock.updatePolicies = jest .fn() .mockImplementation(async (): Promise => { throw new Error('Fail to remove policy'); @@ -1493,7 +1499,7 @@ describe('REST policies api', () => { }); it('should fail to update policy - unable to add newPolicy', async () => { - mockEnforcer.hasPolicy = jest + enforcerDelegateMock.hasPolicy = jest .fn() .mockImplementation(async (...param: string[]): Promise => { if (param[2] === 'create') { @@ -1501,7 +1507,7 @@ describe('REST policies api', () => { } return true; }); - mockEnforcer.updatePolicies = jest + enforcerDelegateMock.updatePolicies = jest .fn() .mockImplementation( async (_param: string[][], _source: Source): Promise => { @@ -1536,7 +1542,7 @@ describe('REST policies api', () => { }); it('should update policy', async () => { - mockEnforcer.hasPolicy = jest + enforcerDelegateMock.hasPolicy = jest .fn() .mockImplementation(async (...param: string[]): Promise => { if (param[2] === 'create') { @@ -1544,7 +1550,7 @@ describe('REST policies api', () => { } return true; }); - mockEnforcer.updatePolicies = jest.fn().mockImplementation(); + enforcerDelegateMock.updatePolicies = jest.fn().mockImplementation(); const result = await request(app) .put('/policies/user/default/permission_admin') @@ -1569,7 +1575,7 @@ describe('REST policies api', () => { }); it('should fail to update permission policy - duplication in old policy', async () => { - mockEnforcer.hasPolicy = jest + enforcerDelegateMock.hasPolicy = jest .fn() .mockImplementation(async (...param: string[]): Promise => { if (param[2] === 'create') { @@ -1615,7 +1621,7 @@ describe('REST policies api', () => { }); it('should fail to update permission policy - duplication in new policy', async () => { - mockEnforcer.hasPolicy = jest + enforcerDelegateMock.hasPolicy = jest .fn() .mockImplementation(async (...param: string[]): Promise => { if (param[2] === 'update') { @@ -1661,7 +1667,7 @@ describe('REST policies api', () => { }); it('should fail to update permission policy - oldPolicy has an additional permission', async () => { - mockEnforcer.hasPolicy = jest + enforcerDelegateMock.hasPolicy = jest .fn() .mockImplementation(async (..._param: string[]): Promise => { return true; @@ -1773,7 +1779,7 @@ describe('REST policies api', () => { }, ); - mockEnforcer.hasPolicy = jest + enforcerDelegateMock.hasPolicy = jest .fn() .mockImplementation(async (...param: string[]): Promise => { if (param[2] === 'delete') { @@ -1781,7 +1787,7 @@ describe('REST policies api', () => { } return true; }); - mockEnforcer.updatePolicies = jest.fn().mockImplementation(); + enforcerDelegateMock.updatePolicies = jest.fn().mockImplementation(); const result = await request(app) .put('/policies/user/default/permission_admin') @@ -1845,7 +1851,7 @@ describe('REST policies api', () => { }); it('should be returned list all roles', async () => { - mockEnforcer.getGroupingPolicy = jest + enforcerDelegateMock.getGroupingPolicy = jest .fn() .mockImplementation(async () => { return [ @@ -1932,7 +1938,7 @@ describe('REST policies api', () => { }); it('should be returned not found error by role reference', async () => { - mockEnforcer.getFilteredGroupingPolicy = jest + enforcerDelegateMock.getFilteredGroupingPolicy = jest .fn() .mockImplementation( async (_fieldIndex: number, ..._fieldValues: string[]) => { @@ -2073,7 +2079,7 @@ describe('REST policies api', () => { }); expect(result.statusCode).toBe(201); - expect(mockEnforcer.addGroupingPolicies).toHaveBeenCalledWith( + expect(enforcerDelegateMock.addGroupingPolicies).toHaveBeenCalledWith( [['user:default/permission_admin', 'role:default/rbac_admin']], { author: 'user:default/guest', @@ -2097,7 +2103,7 @@ describe('REST policies api', () => { }); expect(result.statusCode).toBe(201); - expect(mockEnforcer.addGroupingPolicies).toHaveBeenCalledWith( + expect(enforcerDelegateMock.addGroupingPolicies).toHaveBeenCalledWith( [['user:default/permission_admin', 'role:default/rbac_admin']], { roleEntityRef: 'role:default/rbac_admin', @@ -2110,7 +2116,7 @@ describe('REST policies api', () => { }); it('should not be created role, because it is has been already present', async () => { - mockEnforcer.hasGroupingPolicy = jest + enforcerDelegateMock.hasGroupingPolicy = jest .fn() .mockImplementation(async (..._param: string[]): Promise => { return true; @@ -2127,7 +2133,7 @@ describe('REST policies api', () => { }); it('should not be created role caused some unexpected error', async () => { - mockEnforcer.addGroupingPolicies = jest + enforcerDelegateMock.addGroupingPolicies = jest .fn() .mockImplementation(async (): Promise => { throw new Error('Fail to create new policy'); @@ -2279,7 +2285,7 @@ describe('REST policies api', () => { }); it('should fail to update role - old role not found', async () => { - mockEnforcer.hasPolicy = jest + enforcerDelegateMock.hasPolicy = jest .fn() .mockImplementation(async (..._policy: string[]): Promise => { return false; @@ -2305,7 +2311,7 @@ describe('REST policies api', () => { }); it('should fail to update role - newRole is already present', async () => { - mockEnforcer.hasGroupingPolicy = jest + enforcerDelegateMock.hasGroupingPolicy = jest .fn() .mockImplementation(async (..._param: string[]): Promise => { return true; @@ -2330,7 +2336,7 @@ describe('REST policies api', () => { }); it('should nothing to update', async () => { - mockEnforcer.hasGroupingPolicy = jest + enforcerDelegateMock.hasGroupingPolicy = jest .fn() .mockImplementation(async (..._param: string[]): Promise => { return true; @@ -2351,7 +2357,7 @@ describe('REST policies api', () => { }); it('should nothing to update, because role and metadata are the same', async () => { - mockEnforcer.hasGroupingPolicy = jest + enforcerDelegateMock.hasGroupingPolicy = jest .fn() .mockImplementation(async (..._param: string[]): Promise => { return true; @@ -2378,7 +2384,7 @@ describe('REST policies api', () => { }); it('should nothing to update, because role and metadata are the same, but old role metadata was not send', async () => { - mockEnforcer.hasGroupingPolicy = jest + enforcerDelegateMock.hasGroupingPolicy = jest .fn() .mockImplementation(async (..._param: string[]): Promise => { return true; @@ -2402,7 +2408,7 @@ describe('REST policies api', () => { }); it('should update description and set author', async () => { - mockEnforcer.hasGroupingPolicy = jest + enforcerDelegateMock.hasGroupingPolicy = jest .fn() .mockImplementation(async (..._param: string[]): Promise => { return true; @@ -2424,7 +2430,7 @@ describe('REST policies api', () => { }); expect(result.statusCode).toEqual(200); - expect(mockEnforcer.updateGroupingPolicies).toHaveBeenCalledWith( + expect(enforcerDelegateMock.updateGroupingPolicies).toHaveBeenCalledWith( [['user:default/permission_admin', 'role:default/rbac_admin']], [['user:default/permission_admin', 'role:default/rbac_admin']], { @@ -2437,7 +2443,7 @@ describe('REST policies api', () => { }); it('should update role and role description', async () => { - mockEnforcer.hasGroupingPolicy = jest + enforcerDelegateMock.hasGroupingPolicy = jest .fn() .mockImplementation(async (...param: string[]): Promise => { if (param[0] === 'user:default/permission_admin') { @@ -2464,7 +2470,7 @@ describe('REST policies api', () => { expect(result.statusCode).toEqual(200); - expect(mockEnforcer.updateGroupingPolicies).toHaveBeenCalledWith( + expect(enforcerDelegateMock.updateGroupingPolicies).toHaveBeenCalledWith( [['user:default/permission_admin', 'role:default/rbac_admin']], [ ['user:default/test', 'role:default/rbac_admin'], @@ -2480,7 +2486,7 @@ describe('REST policies api', () => { }); it('should fail to update policy - role metadata could not be found', async () => { - mockEnforcer.hasGroupingPolicy = jest + enforcerDelegateMock.hasGroupingPolicy = jest .fn() .mockImplementation(async (...param: string[]): Promise => { if (param[0] === 'user:default/test') { @@ -2513,7 +2519,7 @@ describe('REST policies api', () => { }); it('should fail to update role - unable to remove oldRole', async () => { - mockEnforcer.hasGroupingPolicy = jest + enforcerDelegateMock.hasGroupingPolicy = jest .fn() .mockImplementation(async (...param: string[]): Promise => { if (param[0] === 'user:default/test') { @@ -2521,7 +2527,7 @@ describe('REST policies api', () => { } return true; }); - mockEnforcer.updateGroupingPolicies = jest + enforcerDelegateMock.updateGroupingPolicies = jest .fn() .mockImplementation(async (): Promise => { throw new Error('Unexpected error'); @@ -2547,7 +2553,7 @@ describe('REST policies api', () => { }); it('should fail to update role - unable to add newRole', async () => { - mockEnforcer.hasGroupingPolicy = jest + enforcerDelegateMock.hasGroupingPolicy = jest .fn() .mockImplementation(async (...param: string[]): Promise => { if (param[0] === 'user:default/test') { @@ -2555,7 +2561,7 @@ describe('REST policies api', () => { } return true; }); - mockEnforcer.updateGroupingPolicies = jest + enforcerDelegateMock.updateGroupingPolicies = jest .fn() .mockImplementation( async (_param: string[][], _source: Source): Promise => { @@ -2583,7 +2589,7 @@ describe('REST policies api', () => { }); it('should update role', async () => { - mockEnforcer.hasGroupingPolicy = jest + enforcerDelegateMock.hasGroupingPolicy = jest .fn() .mockImplementation(async (...param: string[]): Promise => { if (param[0] === 'user:default/test') { @@ -2591,7 +2597,9 @@ describe('REST policies api', () => { } return true; }); - mockEnforcer.updateGroupingPolicies = jest.fn().mockImplementation(); + enforcerDelegateMock.updateGroupingPolicies = jest + .fn() + .mockImplementation(); const result = await request(app) .put('/roles/role/default/rbac_admin') @@ -2609,7 +2617,7 @@ describe('REST policies api', () => { }); it('should update role where newRole has multiple roles', async () => { - mockEnforcer.hasGroupingPolicy = jest + enforcerDelegateMock.hasGroupingPolicy = jest .fn() .mockImplementation(async (...param: string[]): Promise => { if ( @@ -2620,7 +2628,9 @@ describe('REST policies api', () => { } return true; }); - mockEnforcer.updateGroupingPolicies = jest.fn().mockImplementation(); + enforcerDelegateMock.updateGroupingPolicies = jest + .fn() + .mockImplementation(); const result = await request(app) .put('/roles/role/default/rbac_admin') @@ -2638,7 +2648,7 @@ describe('REST policies api', () => { }); it('should update role where newRole has multiple roles with one being from oldRole', async () => { - mockEnforcer.hasGroupingPolicy = jest + enforcerDelegateMock.hasGroupingPolicy = jest .fn() .mockImplementation(async (...param: string[]): Promise => { if (param[0] === 'user:default/test') { @@ -2646,7 +2656,9 @@ describe('REST policies api', () => { } return true; }); - mockEnforcer.updateGroupingPolicies = jest.fn().mockImplementation(); + enforcerDelegateMock.updateGroupingPolicies = jest + .fn() + .mockImplementation(); const result = await request(app) .put('/roles/role/default/rbac_admin') @@ -2667,7 +2679,7 @@ describe('REST policies api', () => { }); it('should update role name', async () => { - mockEnforcer.hasGroupingPolicy = jest + enforcerDelegateMock.hasGroupingPolicy = jest .fn() .mockImplementation(async (...param: string[]): Promise => { if (param[0] === 'user:default/test') { @@ -2675,7 +2687,9 @@ describe('REST policies api', () => { } return true; }); - mockEnforcer.updateGroupingPolicies = jest.fn().mockImplementation(); + enforcerDelegateMock.updateGroupingPolicies = jest + .fn() + .mockImplementation(); const result = await request(app) .put('/roles/role/default/rbac_admin') @@ -2693,7 +2707,7 @@ describe('REST policies api', () => { }); it('should fail to update role - duplicate roles in oldRole', async () => { - mockEnforcer.hasGroupingPolicy = jest + enforcerDelegateMock.hasGroupingPolicy = jest .fn() .mockImplementation(async (...param: string[]): Promise => { if (param[0] === 'user:default/test') { @@ -2852,19 +2866,19 @@ describe('REST policies api', () => { }); it('should fail to delete, because unexpected error', async () => { - mockEnforcer.hasGroupingPolicy = jest + enforcerDelegateMock.hasGroupingPolicy = jest .fn() .mockImplementation(async (..._param: string[]): Promise => { return true; }); - mockEnforcer.removeGroupingPolicies = jest + enforcerDelegateMock.removeGroupingPolicies = jest .fn() .mockImplementation( async (_param: string[][], _source: Source): Promise => { throw new Error('Unexpected error'); }, ); - mockEnforcer.getFilteredGroupingPolicy = jest + enforcerDelegateMock.getFilteredGroupingPolicy = jest .fn() .mockImplementation( async (_index: number, ..._filter: string[]): Promise => { @@ -2886,12 +2900,12 @@ describe('REST policies api', () => { }); it('should fail to delete, because not found error', async () => { - mockEnforcer.hasGroupingPolicy = jest + enforcerDelegateMock.hasGroupingPolicy = jest .fn() .mockImplementation(async (..._param: string[]): Promise => { return false; }); - mockEnforcer.getFilteredGroupingPolicy = jest + enforcerDelegateMock.getFilteredGroupingPolicy = jest .fn() .mockImplementation( async (_index: number, ..._filter: string[]): Promise => { @@ -2913,17 +2927,17 @@ describe('REST policies api', () => { }); it('should delete a user / group from a role', async () => { - mockEnforcer.hasGroupingPolicy = jest + enforcerDelegateMock.hasGroupingPolicy = jest .fn() .mockImplementation(async (..._param: string[]): Promise => { return true; }); - mockEnforcer.removeGroupingPolicies = jest + enforcerDelegateMock.removeGroupingPolicies = jest .fn() .mockImplementation(async (..._param: string[]): Promise => { return true; }); - mockEnforcer.getFilteredGroupingPolicy = jest + enforcerDelegateMock.getFilteredGroupingPolicy = jest .fn() .mockImplementation( async (_index: number, ..._filter: string[]): Promise => { @@ -2941,12 +2955,12 @@ describe('REST policies api', () => { }); it('should delete a role', async () => { - mockEnforcer.hasGroupingPolicy = jest + enforcerDelegateMock.hasGroupingPolicy = jest .fn() .mockImplementation(async (..._param: string[]): Promise => { return true; }); - mockEnforcer.removeGroupingPolicies = jest + enforcerDelegateMock.removeGroupingPolicies = jest .fn() .mockImplementation(async (..._param: string[]): Promise => { return true; @@ -2976,14 +2990,14 @@ describe('REST policies api', () => { return { source: 'rest', roleEntityRef: roleEntityRef, modifiedBy }; }, ); - mockEnforcer.getFilteredGroupingPolicy = jest + enforcerDelegateMock.getFilteredGroupingPolicy = jest .fn() .mockImplementation( async (_index: number, ..._filter: string[]): Promise => { return ['group:default/test', 'role/default/rbac_admin', 'rest']; }, ); - mockEnforcer.hasGroupingPolicy = jest + enforcerDelegateMock.hasGroupingPolicy = jest .fn() .mockImplementation(async (..._param: string[]): Promise => { return true; @@ -3097,7 +3111,7 @@ describe('REST policies api', () => { }); it('should be returned condition decision by pluginId', async () => { - conditionalStorage.filterConditions = jest + conditionalStorageMock.filterConditions = jest .fn() .mockImplementation( async ( @@ -3119,7 +3133,7 @@ describe('REST policies api', () => { }); it('should be returned empty condition decision list by pluginId', async () => { - conditionalStorage.filterConditions = jest + conditionalStorageMock.filterConditions = jest .fn() .mockImplementation( async ( @@ -3141,7 +3155,7 @@ describe('REST policies api', () => { }); it('should be returned condition decision by resourceType', async () => { - conditionalStorage.filterConditions = jest + conditionalStorageMock.filterConditions = jest .fn() .mockImplementation( async ( @@ -3165,7 +3179,7 @@ describe('REST policies api', () => { describe('DELETE /roles/conditions/:id', () => { beforeEach(() => { - conditionalStorage.getCondition = jest + conditionalStorageMock.getCondition = jest .fn() .mockImplementation(async () => { return expectedConditions[0]; @@ -3203,11 +3217,11 @@ describe('REST policies api', () => { expect(result.statusCode).toEqual(204); expect(mockHttpAuth.credentials).toHaveBeenCalledTimes(1); - expect(conditionalStorage.deleteCondition).toHaveBeenCalled(); + expect(conditionalStorageMock.deleteCondition).toHaveBeenCalled(); }); it('should fail to delete condition decision by id', async () => { - conditionalStorage.deleteCondition = jest.fn(() => { + conditionalStorageMock.deleteCondition = jest.fn(() => { throw new Error('Failed to delete condition decision by id'); }); @@ -3260,7 +3274,7 @@ describe('REST policies api', () => { }); it('should return condition decision by id', async () => { - conditionalStorage.getCondition = jest + conditionalStorageMock.getCondition = jest .fn() .mockImplementation(async (id: number) => { if (id === 1) { @@ -3325,9 +3339,11 @@ describe('REST policies api', () => { }); it('should be created condition', async () => { - conditionalStorage.createCondition = jest.fn().mockImplementation(() => { - return 1; - }); + conditionalStorageMock.createCondition = jest + .fn() + .mockImplementation(() => { + return 1; + }); pluginPermissionMetadataCollectorMock.getMetadataByPluginId = jest .fn() .mockImplementation(() => { @@ -3446,7 +3462,7 @@ describe('REST policies api', () => { expect(validateRoleConditionMock).toHaveBeenCalledWith(conditionDecision); expect(result.statusCode).toBe(200); - expect(conditionalStorage.updateCondition).toHaveBeenCalledWith(1, { + expect(conditionalStorageMock.updateCondition).toHaveBeenCalledWith(1, { id: 1, pluginId: 'catalog', roleEntityRef: 'role:default/test', @@ -3481,28 +3497,28 @@ describe('REST policies api', () => { logger, discovery: mockDiscovery, httpAuth: mockHttpAuth, - auth: mockAuth, + auth: mockAuthService, policy: await RBACPermissionPolicy.build( logger, auditLoggerMock, config, - conditionalStorage, - mockEnforcer as EnforcerDelegate, + conditionalStorageMock, + enforcerDelegateMock as EnforcerDelegate, roleMetadataStorageMock, knex, pluginPermissionMetadataCollectorMock as PluginPermissionMetadataCollector, - mockAuth, + mockAuthService, ), }; server = new PoliciesServer( mockPermissionEvaluator, options, - mockEnforcer as EnforcerDelegate, + enforcerDelegateMock as EnforcerDelegate, config, mockHttpAuth, - mockAuth, - conditionalStorage, + mockAuthService, + conditionalStorageMock, pluginPermissionMetadataCollectorMock as PluginPermissionMetadataCollector, roleMetadataStorageMock, auditLoggerMock, @@ -3691,12 +3707,12 @@ describe('REST policies api', () => { }); it('should not delete policy, because permission framework was disabled', async () => { - mockEnforcer.hasPolicy = jest + enforcerDelegateMock.hasPolicy = jest .fn() .mockImplementation(async (..._param: string[]): Promise => { return true; }); - mockEnforcer.removePolicies = jest + enforcerDelegateMock.removePolicies = jest .fn() .mockImplementation(async (..._param: string[]): Promise => { return true; @@ -3735,7 +3751,7 @@ describe('REST policies api', () => { }); it('should not update policy, because permission framework was disabled', async () => { - mockEnforcer.hasPolicy = jest + enforcerDelegateMock.hasPolicy = jest .fn() .mockImplementation(async (...param: string[]): Promise => { if (param[2] === 'create') { @@ -3743,7 +3759,7 @@ describe('REST policies api', () => { } return true; }); - mockEnforcer.updatePolicies = jest.fn().mockImplementation(); + enforcerDelegateMock.updatePolicies = jest.fn().mockImplementation(); const result = await request(app) .put('/policies/user/default/permission_admin') @@ -3769,12 +3785,19 @@ describe('REST policies api', () => { }); it('should not return list all policies, because permission framework was disabled', async () => { - mockEnforcer.getPolicy = jest.fn().mockImplementation(async () => { - return [ - ['user:default/permission_admin', 'policy-entity', 'create', 'allow'], - ['user:default/guest', 'policy-entity', 'read', 'allow'], - ]; - }); + enforcerDelegateMock.getPolicy = jest + .fn() + .mockImplementation(async () => { + return [ + [ + 'user:default/permission_admin', + 'policy-entity', + 'create', + 'allow', + ], + ['user:default/guest', 'policy-entity', 'read', 'allow'], + ]; + }); const result = await request(app).get('/policies').send(); expect(result.statusCode).toBe(404); @@ -3782,7 +3805,7 @@ describe('REST policies api', () => { }); it('should not return list all roles, because permission framework was disabled', async () => { - mockEnforcer.getGroupingPolicy = jest + enforcerDelegateMock.getGroupingPolicy = jest .fn() .mockImplementation(async () => { return [ @@ -3819,7 +3842,7 @@ describe('REST policies api', () => { }); it('should not update role, because permission framework was disabled', async () => { - mockEnforcer.hasGroupingPolicy = jest + enforcerDelegateMock.hasGroupingPolicy = jest .fn() .mockImplementation(async (...param: string[]): Promise => { if (param[0] === 'user:default/permission_admin') { @@ -3849,12 +3872,12 @@ describe('REST policies api', () => { }); it('should not delete a role, because permission framework was disabled', async () => { - mockEnforcer.hasGroupingPolicy = jest + enforcerDelegateMock.hasGroupingPolicy = jest .fn() .mockImplementation(async (..._param: string[]): Promise => { return true; }); - mockEnforcer.removeGroupingPolicies = jest + enforcerDelegateMock.removeGroupingPolicies = jest .fn() .mockImplementation(async (..._param: string[]): Promise => { return true; @@ -3882,7 +3905,7 @@ describe('REST policies api', () => { }); it('should not return condition decision by id, because permission framework was disabled', async () => { - conditionalStorage.getCondition = jest + conditionalStorageMock.getCondition = jest .fn() .mockImplementation(async (id: number) => { if (id === 1) { @@ -3898,9 +3921,11 @@ describe('REST policies api', () => { }); it('should not create condition, because permission framework was disabled', async () => { - conditionalStorage.createCondition = jest.fn().mockImplementation(() => { - return 1; - }); + conditionalStorageMock.createCondition = jest + .fn() + .mockImplementation(() => { + return 1; + }); pluginPermissionMetadataCollectorMock.getMetadataByPluginId = jest .fn() .mockImplementation(() => { diff --git a/plugins/rbac-backend/src/service/policy-builder.test.ts b/plugins/rbac-backend/src/service/policy-builder.test.ts index 3710c4d375..4e8f7296b6 100644 --- a/plugins/rbac-backend/src/service/policy-builder.test.ts +++ b/plugins/rbac-backend/src/service/policy-builder.test.ts @@ -15,7 +15,7 @@ import { PluginPermissionMetadataCollector } from './plugin-endpoints'; import { PoliciesServer } from './policies-rest-api'; import { PolicyBuilder } from './policy-builder'; -const mockEnforcer: Partial = { +const enforcerMock: Partial = { loadPolicy: jest.fn().mockImplementation(async () => {}), enableAutoSave: jest.fn().mockImplementation(() => {}), setRoleManager: jest.fn().mockImplementation(() => {}), @@ -28,7 +28,7 @@ jest.mock('casbin', () => { return { ...actualCasbin, newEnforcer: jest.fn((): Promise> => { - return Promise.resolve(mockEnforcer); + return Promise.resolve(enforcerMock); }), FileAdapter: jest.fn((): Adapter => { return {} as Adapter; @@ -36,7 +36,7 @@ jest.mock('casbin', () => { }; }); -const mockDataBaseAdapterFactory: Partial = { +const dataBaseAdapterFactoryMock: Partial = { createAdapter: jest.fn((): Promise => { return Promise.resolve({} as TypeORMAdapter); }), @@ -45,12 +45,12 @@ const mockDataBaseAdapterFactory: Partial = { jest.mock('../database/casbin-adapter-factory', () => { return { CasbinDBAdapterFactory: jest.fn((): Partial => { - return mockDataBaseAdapterFactory; + return dataBaseAdapterFactoryMock; }), }; }); -const mockPluginMetadataCollector: Partial = +const pluginMetadataCollectorMock: Partial = { getPluginConditionRules: jest.fn().mockImplementation(), getPluginPolicies: jest.fn().mockImplementation(), @@ -61,12 +61,12 @@ jest.mock('./plugin-endpoints', () => { return { PluginPermissionMetadataCollector: jest .fn() - .mockImplementation(() => mockPluginMetadataCollector), + .mockImplementation(() => pluginMetadataCollectorMock), }; }); const mockRouter: Router = {} as Router; -const mockPoliciesServer: Partial = { +const policiesServerMock: Partial = { serve: jest.fn().mockImplementation(async () => { return mockRouter; }), @@ -74,7 +74,7 @@ const mockPoliciesServer: Partial = { jest.mock('./policies-rest-api', () => { return { - PoliciesServer: jest.fn().mockImplementation(() => mockPoliciesServer), + PoliciesServer: jest.fn().mockImplementation(() => policiesServerMock), }; }); @@ -101,7 +101,7 @@ describe('PolicyBuilder', () => { }), }; - const logger = mockServices.logger.mock(); + const mockLoggerService = mockServices.logger.mock(); beforeEach(async () => { jest.clearAllMocks(); @@ -124,7 +124,7 @@ describe('PolicyBuilder', () => { }, }, }), - logger, + logger: mockLoggerService, discovery: mockServices.discovery.mock(), permissions: mockServices.permissions.mock(), userInfo: mockServices.userInfo.mock(), @@ -135,15 +135,17 @@ describe('PolicyBuilder', () => { backendPluginIDsProviderMock, ); expect(CasbinDBAdapterFactory).toHaveBeenCalled(); - expect(mockEnforcer.loadPolicy).toHaveBeenCalled(); - expect(mockEnforcer.enableAutoSave).toHaveBeenCalled(); + expect(enforcerMock.loadPolicy).toHaveBeenCalled(); + expect(enforcerMock.enableAutoSave).toHaveBeenCalled(); expect(RBACPermissionPolicy.build).toHaveBeenCalled(); expect(PoliciesServer).toHaveBeenCalled(); - expect(mockPoliciesServer.serve).toHaveBeenCalled(); + expect(policiesServerMock.serve).toHaveBeenCalled(); expect(router).toBeTruthy(); expect(router).toBe(mockRouter); - expect(logger.info).toHaveBeenCalledWith('RBAC backend plugin was enabled'); + expect(mockLoggerService.info).toHaveBeenCalledWith( + 'RBAC backend plugin was enabled', + ); }); it('should build policy server with rbac providers', async () => { @@ -163,7 +165,7 @@ describe('PolicyBuilder', () => { }, }, }), - logger, + logger: mockLoggerService, discovery: mockServices.discovery.mock(), permissions: mockServices.permissions.mock(), userInfo: mockServices.userInfo.mock(), @@ -175,16 +177,18 @@ describe('PolicyBuilder', () => { [providerMock], ); expect(CasbinDBAdapterFactory).toHaveBeenCalled(); - expect(mockEnforcer.loadPolicy).toHaveBeenCalled(); - expect(mockEnforcer.enableAutoSave).toHaveBeenCalled(); + expect(enforcerMock.loadPolicy).toHaveBeenCalled(); + expect(enforcerMock.enableAutoSave).toHaveBeenCalled(); expect(RBACPermissionPolicy.build).toHaveBeenCalled(); expect(providerMock.connect).toHaveBeenCalled(); expect(PoliciesServer).toHaveBeenCalled(); - expect(mockPoliciesServer.serve).toHaveBeenCalled(); + expect(policiesServerMock.serve).toHaveBeenCalled(); expect(router).toBeTruthy(); expect(router).toBe(mockRouter); - expect(logger.info).toHaveBeenCalledWith('RBAC backend plugin was enabled'); + expect(mockLoggerService.info).toHaveBeenCalledWith( + 'RBAC backend plugin was enabled', + ); }); it('should build policy server, but log warning that permission framework disabled', async () => { @@ -204,7 +208,7 @@ describe('PolicyBuilder', () => { }, }, }), - logger, + logger: mockLoggerService, discovery: mockServices.discovery.mock(), permissions: mockServices.permissions.mock(), userInfo: mockServices.userInfo.mock(), @@ -215,15 +219,15 @@ describe('PolicyBuilder', () => { backendPluginIDsProviderMock, ); expect(CasbinDBAdapterFactory).toHaveBeenCalled(); - expect(mockEnforcer.loadPolicy).toHaveBeenCalled(); - expect(mockEnforcer.enableAutoSave).toHaveBeenCalled(); + expect(enforcerMock.loadPolicy).toHaveBeenCalled(); + expect(enforcerMock.enableAutoSave).toHaveBeenCalled(); expect(RBACPermissionPolicy.build).toHaveBeenCalled(); expect(PoliciesServer).toHaveBeenCalled(); - expect(mockPoliciesServer.serve).toHaveBeenCalled(); + expect(policiesServerMock.serve).toHaveBeenCalled(); expect(router).toBeTruthy(); expect(router).toBe(mockRouter); - expect(logger.warn).toHaveBeenCalledWith( + expect(mockLoggerService.warn).toHaveBeenCalledWith( 'RBAC backend plugin was disabled by application config permission.enabled: false', ); }); @@ -248,7 +252,7 @@ describe('PolicyBuilder', () => { }, }, }), - logger, + logger: mockLoggerService, discovery: mockServices.discovery.mock(), permissions: mockServices.permissions.mock(), userInfo: mockServices.userInfo.mock(), @@ -259,15 +263,17 @@ describe('PolicyBuilder', () => { pluginIdProvider, ); expect(CasbinDBAdapterFactory).toHaveBeenCalled(); - expect(mockEnforcer.loadPolicy).toHaveBeenCalled(); - expect(mockEnforcer.enableAutoSave).toHaveBeenCalled(); + expect(enforcerMock.loadPolicy).toHaveBeenCalled(); + expect(enforcerMock.enableAutoSave).toHaveBeenCalled(); expect(RBACPermissionPolicy.build).toHaveBeenCalled(); expect(PoliciesServer).toHaveBeenCalled(); - expect(mockPoliciesServer.serve).toHaveBeenCalled(); + expect(policiesServerMock.serve).toHaveBeenCalled(); expect(router).toBeTruthy(); expect(router).toBe(mockRouter); - expect(logger.info).toHaveBeenCalledWith('RBAC backend plugin was enabled'); + expect(mockLoggerService.info).toHaveBeenCalledWith( + 'RBAC backend plugin was enabled', + ); expect(pluginIdProvider.getPluginIds()).toEqual(['catalog']); }); @@ -292,7 +298,7 @@ describe('PolicyBuilder', () => { }, }, }), - logger, + logger: mockLoggerService, discovery: mockServices.discovery.mock(), permissions: mockServices.permissions.mock(), userInfo: mockServices.userInfo.mock(), @@ -303,15 +309,17 @@ describe('PolicyBuilder', () => { pluginIdProvider, ); expect(CasbinDBAdapterFactory).toHaveBeenCalled(); - expect(mockEnforcer.loadPolicy).toHaveBeenCalled(); - expect(mockEnforcer.enableAutoSave).toHaveBeenCalled(); + expect(enforcerMock.loadPolicy).toHaveBeenCalled(); + expect(enforcerMock.enableAutoSave).toHaveBeenCalled(); expect(RBACPermissionPolicy.build).toHaveBeenCalled(); expect(PoliciesServer).toHaveBeenCalled(); - expect(mockPoliciesServer.serve).toHaveBeenCalled(); + expect(policiesServerMock.serve).toHaveBeenCalled(); expect(router).toBeTruthy(); expect(router).toBe(mockRouter); - expect(logger.info).toHaveBeenCalledWith('RBAC backend plugin was enabled'); + expect(mockLoggerService.info).toHaveBeenCalledWith( + 'RBAC backend plugin was enabled', + ); expect(pluginIdProvider.getPluginIds()).toEqual(['catalog', 'rbac']); }); @@ -334,7 +342,7 @@ describe('PolicyBuilder', () => { }, }, }), - logger, + logger: mockLoggerService, discovery: mockServices.discovery.mock(), permissions: mockServices.permissions.mock(), userInfo: mockServices.userInfo.mock(), @@ -343,14 +351,16 @@ describe('PolicyBuilder', () => { lifecycle: mockServices.lifecycle.mock(), }); expect(CasbinDBAdapterFactory).toHaveBeenCalled(); - expect(mockEnforcer.loadPolicy).toHaveBeenCalled(); - expect(mockEnforcer.enableAutoSave).toHaveBeenCalled(); + expect(enforcerMock.loadPolicy).toHaveBeenCalled(); + expect(enforcerMock.enableAutoSave).toHaveBeenCalled(); expect(RBACPermissionPolicy.build).toHaveBeenCalled(); - expect(mockPoliciesServer.serve).toHaveBeenCalled(); + expect(policiesServerMock.serve).toHaveBeenCalled(); expect(router).toBeTruthy(); expect(router).toBe(mockRouter); - expect(logger.info).toHaveBeenCalledWith('RBAC backend plugin was enabled'); + expect(mockLoggerService.info).toHaveBeenCalledWith( + 'RBAC backend plugin was enabled', + ); const pIdProvider = ( PluginPermissionMetadataCollector as unknown as jest.Mock ).mock.calls[0][0].deps.pluginIdProvider;