Complete code in router and fix tests

Signed-off-by: Ainhoa Larumbe <ainhoaL@users.noreply.github.com>
Co-authored-by: Vincenzo Scamporlino <vincenzos@spotify.com>
This commit is contained in:
Ainhoa Larumbe
2023-03-22 15:26:22 +00:00
parent d524bf467b
commit 16c725e939
2 changed files with 138 additions and 51 deletions
@@ -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',
},
},
],
});
});
});
});
@@ -346,12 +346,7 @@ export function createPermissionIntegrationRouter<
options:
| { permissions: Array<Permission> }
| CreatePermissionIntegrationRouterResourceOptions<TResourceType, TResource>
| Array<
CreatePermissionIntegrationRouterResourceOptions<
TResourceType,
TResource
>
>,
| Array<CreatePermissionIntegrationRouterResourceOptions<string, any>>,
): 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<ApplyConditionsResponse | string>) => {
// 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<typeof createGetRule>
@@ -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<string, Record<string, any>> = {};
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<string, TResource | undefined>);
*/
}
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<Permission> }
| CreatePermissionIntegrationRouterResourceOptions<TResourceType, TResource>
| Array<
CreatePermissionIntegrationRouterResourceOptions<
TResourceType,
TResource
>
| CreatePermissionIntegrationRouterResourceOptions<
TResourceType,
TResource
>,
): options is CreatePermissionIntegrationRouterResourceOptions<
TResourceType,