From 2dcf772e9d5ea0b0b30eeb0113a012a38a1400e2 Mon Sep 17 00:00:00 2001 From: Oleksandr Andriienko Date: Mon, 12 Feb 2024 10:25:40 +0200 Subject: [PATCH] fix(rbac): display resource typed permissions by name too Signed-off-by: Oleksandr Andriienko --- .../src/service/plugin-endpoint.test.ts | 106 ++++++++++++++++++ .../src/service/plugin-endpoints.ts | 21 ++-- 2 files changed, 119 insertions(+), 8 deletions(-) diff --git a/plugins/rbac-backend/src/service/plugin-endpoint.test.ts b/plugins/rbac-backend/src/service/plugin-endpoint.test.ts index 001b148992..8c602ce8c2 100644 --- a/plugins/rbac-backend/src/service/plugin-endpoint.test.ts +++ b/plugins/rbac-backend/src/service/plugin-endpoint.test.ts @@ -1,5 +1,6 @@ import { getVoidLogger, ReadUrlResponse } from '@backstage/backend-common'; import { ConfigReader } from '@backstage/config'; +import { NotFoundError } from '@backstage/errors'; import { PluginPermissionMetadataCollector } from './plugin-endpoints'; @@ -50,6 +51,10 @@ describe('plugin-endpoint', () => { }), }; + beforeEach(() => { + (mockUrlReaderService.readUrl as jest.Mock).mockReset(); + }); + describe('Test list plugin policies', () => { it('should return empty plugin policies list', async () => { const collector = new PluginPermissionMetadataCollector( @@ -86,7 +91,108 @@ describe('plugin-endpoint', () => { permission: 'policy-entity', policy: 'read', }, + { + permission: 'policy.entity.read', + policy: 'read', + }, + ]); + }); + + it('should return plugin policies list without resource type permissions', async () => { + backendPluginIDsProviderMock.getPluginIds.mockReturnValue(['permission']); + + mockUrlReaderService.readUrl.mockReturnValue(mockReadUrlResponse); + bufferMock.toString.mockReturnValueOnce( + '{"permissions":[{"type":"resource","name":"policy.entity.read","attributes":{"action":"read"}}]}', + ); + + const collector = new PluginPermissionMetadataCollector( + mockPluginEndpointDiscovery, + backendPluginIDsProviderMock, + config, + logger, + ); + const policiesMetadata = await collector.getPluginPolicies(); + + expect(policiesMetadata.length).toEqual(1); + expect(policiesMetadata[0].pluginId).toEqual('permission'); + expect(policiesMetadata[0].policies).toEqual([ + { + permission: 'policy.entity.read', + policy: 'read', + }, + ]); + }); + + it('should skip not found error for not found endpoint', async () => { + backendPluginIDsProviderMock.getPluginIds.mockReturnValue([ + 'permission', + 'unknown-plugin-id', + ]); + + mockUrlReaderService.readUrl = jest + .fn() + .mockImplementation(async (wellKnownURL: string) => { + if ( + wellKnownURL === + 'https://localhost:7007/api/permission/.well-known/backstage/permissions/metadata' + ) { + return mockReadUrlResponse; + } + throw new NotFoundError(); + }); + bufferMock.toString.mockReturnValueOnce( + '{"permissions":[{"type":"resource","name":"policy.entity.read","attributes":{"action":"read"}}]}', + ); + + const collector = new PluginPermissionMetadataCollector( + mockPluginEndpointDiscovery, + backendPluginIDsProviderMock, + config, + logger, + ); + const policiesMetadata = await collector.getPluginPolicies(); + + expect(policiesMetadata.length).toEqual(1); + expect(policiesMetadata[0].pluginId).toEqual('permission'); + expect(policiesMetadata[0].policies).toEqual([ + { + permission: 'policy.entity.read', + policy: 'read', + }, + ]); + }); + + it('should throw error when it is not possible to retrieve permission metadata for known endpoint', async () => { + backendPluginIDsProviderMock.getPluginIds.mockReturnValue([ + 'permission', + 'catalog', ]); + + mockUrlReaderService.readUrl = jest + .fn() + .mockImplementation(async (wellKnownURL: string) => { + if ( + wellKnownURL === + 'https://localhost:7007/api/permission/.well-known/backstage/permissions/metadata' + ) { + return mockReadUrlResponse; + } + throw new Error('Unexpected error'); + }); + bufferMock.toString.mockReturnValueOnce( + '{"permissions":[{"type":"resource","name":"policy.entity.read","attributes":{"action":"read"}}]}', + ); + + const collector = new PluginPermissionMetadataCollector( + mockPluginEndpointDiscovery, + backendPluginIDsProviderMock, + config, + logger, + ); + await expect(collector.getPluginPolicies()).rejects.toThrow( + 'Unexpected error', + ); }); }); diff --git a/plugins/rbac-backend/src/service/plugin-endpoints.ts b/plugins/rbac-backend/src/service/plugin-endpoints.ts index 4fa1069496..0666622f7b 100644 --- a/plugins/rbac-backend/src/service/plugin-endpoints.ts +++ b/plugins/rbac-backend/src/service/plugin-endpoints.ts @@ -119,13 +119,18 @@ export class PluginPermissionMetadataCollector { } function permissionsToCasbinPolicies(permissions: Permission[]): Policy[] { - return permissions.map(permission => { - const policy: Policy = { - permission: isResourcePermission(permission) - ? permission.resourceType - : permission.name, + const policies = []; + for (const permission of permissions) { + if (isResourcePermission(permission)) { + policies.push({ + permission: permission.resourceType, + policy: permission.attributes.action || 'use', + }); + } + policies.push({ + permission: permission.name, policy: permission.attributes.action || 'use', - }; - return policy; - }); + }); + } + return policies; }