diff --git a/plugins/permission-node/src/integration/createPermissionIntegrationRouter.test.ts b/plugins/permission-node/src/integration/createPermissionIntegrationRouter.test.ts index 6e28c5ddc4..759c8a8e81 100644 --- a/plugins/permission-node/src/integration/createPermissionIntegrationRouter.test.ts +++ b/plugins/permission-node/src/integration/createPermissionIntegrationRouter.test.ts @@ -34,6 +34,11 @@ const testPermission: Permission = createPermission({ attributes: {}, }); +const testPermission2: Permission = createPermission({ + name: 'test.permission2', + attributes: {}, +}); + const mockTestRule1Apply = jest .fn() .mockImplementation((_resource: any, _params) => true); @@ -60,6 +65,17 @@ const testRule2 = createPermissionRule({ toQuery: () => ({}), }); +const mockTestRule3Apply = jest + .fn() + .mockImplementation((_resource: any) => false); +const testRule3 = createPermissionRule({ + name: 'test-rule-3', + description: 'Test rule 3', + resourceType: 'test-resource-2', + apply: mockTestRule2Apply, + toQuery: () => ({}), +}); + const defaultMockedGetResources: CreatePermissionIntegrationRouterResourceOptions< string, { id: string } @@ -69,8 +85,7 @@ const defaultMockedGetResources: CreatePermissionIntegrationRouterResourceOption const createApp = ( mockedGetResources: - | typeof defaultMockedGetResources - | null = defaultMockedGetResources, + | typeof defaultMockedGetResources = defaultMockedGetResources, ) => { const router = mockedGetResources ? createPermissionIntegrationRouter({ @@ -84,6 +99,16 @@ const createApp = ( return express().use(router); }; +const createAppWithResources = ( + resourceOptions: CreatePermissionIntegrationRouterResourceOptions< + string, + any + >, +) => { + const router = createPermissionIntegrationRouter(resourceOptions); + return express().use(router); +}; + describe('createPermissionIntegrationRouter', () => { afterEach(() => { jest.clearAllMocks(); @@ -573,15 +598,35 @@ describe('createPermissionIntegrationRouter', () => { }); it('returns 501 with no getResources implementation', async () => { - const response = await request(createApp(null)) + const response = await request( + createAppWithResources({ + resourceType: 'test-resource', + permissions: [testPermission], + rules: [testRule1, testRule2], + }), + ) .post('/.well-known/backstage/permissions/apply-conditions') .send({ - items: [], + items: [ + { + id: '345', + resourceRef: 'default:test/resource-2', + resourceType: 'test-resource', + conditions: { + rule: 'test-rule-1', + resourceType: 'test-resource', + params: { + foo: 'a', + bar: 1, + }, + }, + }, + ], }); expect(response.status).toEqual(501); expect(response.body.error.message).toEqual( - `This plugin does not expose any permission rule or can't evaluate conditional decisions`, + `This plugin does not expose any permission rule or can't evaluate the conditions request for test-resource`, ); }); }); @@ -630,6 +675,73 @@ describe('createPermissionIntegrationRouter', () => { ], }); }); + it.skip('returns a list of permissions and rules used by a given backend that was created with an array of resource options', async () => { + const mockedResourceOptions = [ + { + resourceType: 'test-resource', + permissions: [testPermission], + rules: [testRule1, testRule2], + }, + { + resourceType: 'test-resource-2', + permissions: [testPermission2], + rules: [testRule3], + }, + ]; + + const response = await request( + createApp({ resourceOptions: mockedResourceOptions }), + ).get('/.well-known/backstage/permissions/metadata'); + + expect(response.status).toEqual(200); + expect(response.body).toEqual({ + permissions: [testPermission, testPermission2], + rules: [ + { + name: testRule1.name, + description: testRule1.description, + resourceType: testRule1.resourceType, + paramsSchema: { + $schema: 'http://json-schema.org/draft-07/schema#', + additionalProperties: false, + properties: { + foo: { + type: 'string', + }, + bar: { + description: 'bar', + type: 'number', + }, + }, + required: ['foo', 'bar'], + type: 'object', + }, + }, + { + name: testRule2.name, + description: testRule2.description, + resourceType: testRule2.resourceType, + paramsSchema: { + $schema: 'http://json-schema.org/draft-07/schema#', + additionalProperties: false, + properties: {}, + type: 'object', + }, + }, + { + name: testRule3.name, + description: testRule3.description, + resourceType: testRule3.resourceType, + paramsSchema: { + $schema: 'http://json-schema.org/draft-07/schema#', + additionalProperties: false, + properties: {}, + type: 'object', + }, + }, + ], + }); + }); }); }); diff --git a/plugins/permission-node/src/integration/createPermissionIntegrationRouter.ts b/plugins/permission-node/src/integration/createPermissionIntegrationRouter.ts index adb323c557..0a66e2d3f2 100644 --- a/plugins/permission-node/src/integration/createPermissionIntegrationRouter.ts +++ b/plugins/permission-node/src/integration/createPermissionIntegrationRouter.ts @@ -346,12 +346,7 @@ export function createPermissionIntegrationRouter< options: | { permissions: Array } | CreatePermissionIntegrationRouterResourceOptions - | Array< - CreatePermissionIntegrationRouterResourceOptions< - TResourceType, - TResource - > - >, + | Array>, ): express.Router { const allOptions = [options].flat(); const allRules = allOptions.flatMap( @@ -366,6 +361,12 @@ export function createPermissionIntegrationRouter< const allPermissions = allOptions .flatMap(option => option.permissions) .filter((p): p is Permission => !!p); + const allResourceTypes = allOptions.reduce((acc, option) => { + if (isCreatePermissionIntegrationRouterResourceOptions(option)) { + acc.push(option.resourceType); + } + return acc; + }, [] as string[]); const router = Router(); router.use(express.json()); @@ -391,15 +392,6 @@ export function createPermissionIntegrationRouter< router.post( '/.well-known/backstage/permissions/apply-conditions', async (req, res: Response) => { - // if ( - // !isCreatePermissionIntegrationRouterResourceOptions(options) || - // options.getResources === undefined - // ) { - // throw new NotImplementedError( - // `This plugin does not expose any permission rule or can't evaluate conditional decisions`, - // ); - // } - const ruleMapByResourceType: Record< string, ReturnType @@ -422,15 +414,11 @@ export function createPermissionIntegrationRouter< } } - // const { resourceType, getResources } = options; - - // const getRule = createGetRule(rules); - const assertValidResourceTypes = ( requests: ApplyConditionsRequestEntry[], ) => { const invalidResourceTypes = requests - .filter(request => request.resourceType !== resourceType) + .filter(request => !allResourceTypes.includes(request.resourceType)) .map(request => request.resourceType); if (invalidResourceTypes.length) { @@ -461,11 +449,9 @@ export function createPermissionIntegrationRouter< }, {}); const resourcesByResourceType: Record> = {}; - Object.keys(resourceRefsByResourceType).forEach(async resourceType => { - if ( - !getResourcesByResourceType || - !getResourcesByResourceType[resourceType] - ) { + for (const resourceType of Object.keys(resourceRefsByResourceType)) { + const getResources = getResourcesByResourceType[resourceType]; + if (!getResources) { throw new NotImplementedError( `This plugin does not expose any permission rule or can't evaluate the conditions request for ${resourceType}`, ); @@ -473,30 +459,22 @@ export function createPermissionIntegrationRouter< const resourceRefs = Array.from( resourceRefsByResourceType[resourceType], ); - const resources = await getResourcesByResourceType[resourceType]( - resourceRefs, - ); + const resources = await getResources(resourceRefs); resourceRefs.forEach((resourceRef, index) => { + if (!resourcesByResourceType[resourceType]) { + resourcesByResourceType[resourceType] = {}; + } resourcesByResourceType[resourceType][resourceRef] = resources[index]; }); - }); - - /* - const resourceArray = await getResources(resourceRefs); - const resources = resourceRefs.reduce((acc, resourceRef, index) => { - acc[resourceRef] = resourceArray[index]; - - return acc; - }, {} as Record); -*/ + } return res.json({ items: body.items.map(request => ({ id: request.id, result: applyConditions( request.conditions, - resources[request.resourceRef], - getRule, + resourcesByResourceType[request.resourceType][request.resourceRef], + ruleMapByResourceType[request.resourceType], ) ? AuthorizeResult.ALLOW : AuthorizeResult.DENY, @@ -516,12 +494,9 @@ function isCreatePermissionIntegrationRouterResourceOptions< >( options: | { permissions: Array } - | CreatePermissionIntegrationRouterResourceOptions - | Array< - CreatePermissionIntegrationRouterResourceOptions< - TResourceType, - TResource - > + | CreatePermissionIntegrationRouterResourceOptions< + TResourceType, + TResource >, ): options is CreatePermissionIntegrationRouterResourceOptions< TResourceType,