Create permission aggregation endpoints (#11695)
* Create permission aggregation endpoints Signed-off-by: Joon Park <joonp@spotify.com> * Spelling Signed-off-by: Joon Park <joonp@spotify.com> * Refactor permission metadata aggregation into one endpoint Signed-off-by: Joe Porpeglia <josephp@spotify.com> * Change parameter field shape Signed-off-by: Joon Park <joonp@spotify.com> Co-authored-by: Joe Porpeglia <josephp@spotify.com>
This commit is contained in:
@@ -0,0 +1,7 @@
|
||||
---
|
||||
'@backstage/plugin-permission-node': patch
|
||||
---
|
||||
|
||||
Added a new endpoint for aggregating permission metadata from a plugin backend: `/.well-known/backstage/permissions/metadata`
|
||||
|
||||
By default, the metadata endpoint will return information about the permission rules supported by the plugin. Plugin authors can also provide an optional `permissions` parameter to `createPermissionIntegrationRouter`. If provided, these `Permission` objects will be included in the metadata returned by this endpoint. The `permissions` parameter will eventually be required in a future breaking change.
|
||||
@@ -275,6 +275,7 @@ scrollbar
|
||||
seb
|
||||
semlas
|
||||
semver
|
||||
serializable
|
||||
Serverless
|
||||
shoutout
|
||||
siloed
|
||||
|
||||
@@ -112,6 +112,7 @@ export const createPermissionIntegrationRouter: <
|
||||
TResource,
|
||||
>(options: {
|
||||
resourceType: TResourceType;
|
||||
permissions?: Permission[] | undefined;
|
||||
rules: PermissionRule<TResource, any, NoInfer<TResourceType>, unknown[]>[];
|
||||
getResources: (resourceRefs: string[]) => Promise<(TResource | undefined)[]>;
|
||||
}) => express.Router;
|
||||
|
||||
@@ -14,7 +14,11 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { AuthorizeResult } from '@backstage/plugin-permission-common';
|
||||
import {
|
||||
AuthorizeResult,
|
||||
createPermission,
|
||||
Permission,
|
||||
} from '@backstage/plugin-permission-common';
|
||||
import express, { Express, Router } from 'express';
|
||||
import request, { Response } from 'supertest';
|
||||
import { createPermissionIntegrationRouter } from './createPermissionIntegrationRouter';
|
||||
@@ -26,22 +30,25 @@ const mockGetResources: jest.MockedFunction<
|
||||
resourceRefs.map(resourceRef => ({ id: resourceRef })),
|
||||
);
|
||||
|
||||
const testPermission: Permission = createPermission({
|
||||
name: 'test.permission',
|
||||
attributes: {},
|
||||
});
|
||||
|
||||
const testRule1 = createPermissionRule({
|
||||
name: 'test-rule-1',
|
||||
description: 'Test rule 1',
|
||||
resourceType: 'test-resource',
|
||||
apply: jest.fn(
|
||||
(_resource: any, _firstParam: string, _secondParam: number) => true,
|
||||
),
|
||||
toQuery: jest.fn(),
|
||||
apply: (_resource: any, _firstParam: string, _secondParam: number) => true,
|
||||
toQuery: (_firstParam: string, _secondParam: number) => ({}),
|
||||
});
|
||||
|
||||
const testRule2 = createPermissionRule({
|
||||
name: 'test-rule-2',
|
||||
description: 'Test rule 2',
|
||||
resourceType: 'test-resource',
|
||||
apply: jest.fn((_resource: any, _firstParam: object) => false),
|
||||
toQuery: jest.fn(),
|
||||
apply: (_resource: any, _firstParam: object) => false,
|
||||
toQuery: (_firstParam: object) => ({}),
|
||||
});
|
||||
|
||||
describe('createPermissionIntegrationRouter', () => {
|
||||
@@ -51,6 +58,7 @@ describe('createPermissionIntegrationRouter', () => {
|
||||
beforeAll(() => {
|
||||
router = createPermissionIntegrationRouter({
|
||||
resourceType: 'test-resource',
|
||||
permissions: [testPermission],
|
||||
getResources: mockGetResources,
|
||||
rules: [testRule1, testRule2],
|
||||
});
|
||||
@@ -501,4 +509,31 @@ describe('createPermissionIntegrationRouter', () => {
|
||||
expect(response.error && response.error.text).toMatch(/invalid/i);
|
||||
});
|
||||
});
|
||||
|
||||
describe('GET /.well-known/backstage/permissions/metadata', () => {
|
||||
it('returns a list of permissions and rules used by a given backend', async () => {
|
||||
const response = await request(app).get(
|
||||
'/.well-known/backstage/permissions/metadata',
|
||||
);
|
||||
|
||||
expect(response.status).toEqual(200);
|
||||
expect(response.body).toEqual({
|
||||
permissions: [testPermission],
|
||||
rules: [
|
||||
{
|
||||
name: testRule1.name,
|
||||
description: testRule1.description,
|
||||
resourceType: testRule1.resourceType,
|
||||
parameters: { count: 2 },
|
||||
},
|
||||
{
|
||||
name: testRule2.name,
|
||||
description: testRule2.description,
|
||||
resourceType: testRule2.resourceType,
|
||||
parameters: { count: 1 },
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -23,6 +23,7 @@ import {
|
||||
AuthorizeResult,
|
||||
DefinitivePolicyDecision,
|
||||
IdentifiedPermissionMessage,
|
||||
Permission,
|
||||
PermissionCondition,
|
||||
PermissionCriteria,
|
||||
} from '@backstage/plugin-permission-common';
|
||||
@@ -174,6 +175,7 @@ export const createPermissionIntegrationRouter = <
|
||||
TResource,
|
||||
>(options: {
|
||||
resourceType: TResourceType;
|
||||
permissions?: Array<Permission>;
|
||||
// Do not infer value of TResourceType from supplied rules.
|
||||
// instead only consider the resourceType parameter, and
|
||||
// consider any rules whose resource type does not match
|
||||
@@ -183,8 +185,22 @@ export const createPermissionIntegrationRouter = <
|
||||
resourceRefs: string[],
|
||||
) => Promise<Array<TResource | undefined>>;
|
||||
}): express.Router => {
|
||||
const { resourceType, rules, getResources } = options;
|
||||
const { resourceType, permissions, rules, getResources } = options;
|
||||
const router = Router();
|
||||
router.use(express.json());
|
||||
|
||||
router.get('/.well-known/backstage/permissions/metadata', (_, res) => {
|
||||
const serializableRules = rules.map(rule => ({
|
||||
name: rule.name,
|
||||
description: rule.description,
|
||||
resourceType: rule.resourceType,
|
||||
parameters: {
|
||||
count: rule.toQuery.length,
|
||||
},
|
||||
}));
|
||||
|
||||
return res.json({ permissions, rules: serializableRules });
|
||||
});
|
||||
|
||||
const getRule = createGetRule(rules);
|
||||
|
||||
@@ -202,8 +218,6 @@ export const createPermissionIntegrationRouter = <
|
||||
}
|
||||
};
|
||||
|
||||
router.use(express.json());
|
||||
|
||||
router.post(
|
||||
'/.well-known/backstage/permissions/apply-conditions',
|
||||
async (req, res: Response<ApplyConditionsResponse | string>) => {
|
||||
@@ -227,7 +241,7 @@ export const createPermissionIntegrationRouter = <
|
||||
return acc;
|
||||
}, {} as Record<string, TResource | undefined>);
|
||||
|
||||
return res.status(200).json({
|
||||
return res.json({
|
||||
items: body.items.map(request => ({
|
||||
id: request.id,
|
||||
result: applyConditions(
|
||||
|
||||
Reference in New Issue
Block a user