catalog-backend: update permission integration to support batching
Signed-off-by: MT Lewis <mtlewis@users.noreply.github.com>
This commit is contained in:
@@ -25,6 +25,7 @@ import {
|
||||
NoForeignRootFieldsEntityPolicy,
|
||||
parseEntityRef,
|
||||
SchemaValidEntityPolicy,
|
||||
stringifyEntityRef,
|
||||
Validators,
|
||||
} from '@backstage/catalog-model';
|
||||
import {
|
||||
@@ -34,7 +35,7 @@ import {
|
||||
} from '@backstage/integration';
|
||||
import { createHash } from 'crypto';
|
||||
import { Router } from 'express';
|
||||
import lodash from 'lodash';
|
||||
import lodash, { keyBy } from 'lodash';
|
||||
import { EntitiesCatalog, EntitiesSearchFilter } from '../catalog';
|
||||
import {
|
||||
DatabaseLocationsCatalog,
|
||||
@@ -415,18 +416,27 @@ export class NextCatalogBuilder {
|
||||
);
|
||||
const permissionIntegrationRouter = createPermissionIntegrationRouter({
|
||||
resourceType: RESOURCE_TYPE_CATALOG_ENTITY,
|
||||
getResource: async (resourceRef: string) => {
|
||||
const parsed = parseEntityRef(resourceRef);
|
||||
getResources: async (resourceRefs: string[]) => {
|
||||
const { entities } = await entitiesCatalog.entities({
|
||||
filter: {
|
||||
anyOf: resourceRefs.map(resourceRef => {
|
||||
const { kind, namespace, name } = parseEntityRef(resourceRef);
|
||||
|
||||
const { entities } = await unauthorizedEntitiesCatalog.entities({
|
||||
filter: basicEntityFilter({
|
||||
kind: parsed.kind,
|
||||
'metadata.namespace': parsed.namespace,
|
||||
'metadata.name': parsed.name,
|
||||
}),
|
||||
return basicEntityFilter({
|
||||
kind,
|
||||
'metadata.namespace': namespace,
|
||||
'metadata.name': name,
|
||||
});
|
||||
}),
|
||||
},
|
||||
});
|
||||
|
||||
return entities[0];
|
||||
const entitiesByRef = keyBy(entities, stringifyEntityRef);
|
||||
|
||||
return resourceRefs.map(
|
||||
resourceRef =>
|
||||
entitiesByRef[stringifyEntityRef(parseEntityRef(resourceRef))],
|
||||
);
|
||||
},
|
||||
rules: this.permissionRules,
|
||||
});
|
||||
|
||||
@@ -24,6 +24,9 @@ import { EntitiesCatalog } from '../catalog';
|
||||
import { LocationService, RefreshService } from './types';
|
||||
import { basicEntityFilter } from './request';
|
||||
import { createNextRouter } from './NextRouter';
|
||||
import { AuthorizeResult } from '@backstage/plugin-permission-common';
|
||||
import { createPermissionIntegrationRouter } from '@backstage/plugin-permission-node';
|
||||
import { RESOURCE_TYPE_CATALOG_ENTITY } from '@backstage/plugin-catalog-common';
|
||||
|
||||
describe('createNextRouter readonly disabled', () => {
|
||||
let entitiesCatalog: jest.Mocked<EntitiesCatalog>;
|
||||
@@ -433,3 +436,87 @@ describe('createNextRouter readonly enabled', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('NextRouter permissioning', () => {
|
||||
let entitiesCatalog: jest.Mocked<EntitiesCatalog>;
|
||||
let locationService: jest.Mocked<LocationService>;
|
||||
let app: express.Express;
|
||||
let refreshService: RefreshService;
|
||||
|
||||
const fakeRule = {
|
||||
name: 'FAKE_RULE',
|
||||
description: 'fake rule',
|
||||
apply: () => true,
|
||||
toQuery: () => ({ key: '', values: [] }),
|
||||
};
|
||||
|
||||
beforeAll(async () => {
|
||||
entitiesCatalog = {
|
||||
entities: jest.fn(),
|
||||
removeEntityByUid: jest.fn(),
|
||||
batchAddOrUpdateEntities: jest.fn(),
|
||||
entityAncestry: jest.fn(),
|
||||
};
|
||||
locationService = {
|
||||
getLocation: jest.fn(),
|
||||
createLocation: jest.fn(),
|
||||
listLocations: jest.fn(),
|
||||
deleteLocation: jest.fn(),
|
||||
};
|
||||
refreshService = { refresh: jest.fn() };
|
||||
const router = await createNextRouter({
|
||||
entitiesCatalog,
|
||||
locationService,
|
||||
logger: getVoidLogger(),
|
||||
refreshService,
|
||||
config: new ConfigReader(undefined),
|
||||
permissionIntegrationRouter: createPermissionIntegrationRouter({
|
||||
resourceType: RESOURCE_TYPE_CATALOG_ENTITY,
|
||||
rules: [fakeRule],
|
||||
getResources: jest.fn((resourceRefs: string[]) =>
|
||||
Promise.resolve(
|
||||
resourceRefs.map(resourceRef => ({ id: resourceRef })),
|
||||
),
|
||||
),
|
||||
}),
|
||||
});
|
||||
app = express().use(router);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.resetAllMocks();
|
||||
});
|
||||
|
||||
it('accepts and evaluates conditions at the apply-conditions endpoint', async () => {
|
||||
const spideySense: Entity = {
|
||||
apiVersion: 'a',
|
||||
kind: 'component',
|
||||
metadata: {
|
||||
name: 'spidey-sense',
|
||||
},
|
||||
};
|
||||
entitiesCatalog.entities.mockResolvedValueOnce({
|
||||
entities: [spideySense],
|
||||
pageInfo: { hasNextPage: false },
|
||||
});
|
||||
|
||||
const requestBody = {
|
||||
items: [
|
||||
{
|
||||
id: '123',
|
||||
resourceType: 'catalog-entity',
|
||||
resourceRef: 'component:default/spidey-sense',
|
||||
conditions: { rule: 'FAKE_RULE', params: ['user:default/spiderman'] },
|
||||
},
|
||||
],
|
||||
};
|
||||
const response = await request(app)
|
||||
.post('/.well-known/backstage/permissions/apply-conditions')
|
||||
.send(requestBody);
|
||||
|
||||
expect(response.status).toBe(200);
|
||||
expect(response.body).toEqual({
|
||||
items: [{ id: '123', result: AuthorizeResult.ALLOW }],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user