Added parameter scheamas to permission rules
Signed-off-by: Harry Hogg <hhogg@spotify.com>
This commit is contained in:
@@ -18,12 +18,17 @@ import { get } from 'lodash';
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import { RESOURCE_TYPE_CATALOG_ENTITY } from '@backstage/plugin-catalog-common';
|
||||
import { createCatalogPermissionRule } from './util';
|
||||
import { z } from 'zod';
|
||||
|
||||
export const createPropertyRule = (propertyType: 'metadata' | 'spec') =>
|
||||
createCatalogPermissionRule({
|
||||
name: `HAS_${propertyType.toUpperCase()}`,
|
||||
description: `Allow entities which have the specified ${propertyType} subfield.`,
|
||||
resourceType: RESOURCE_TYPE_CATALOG_ENTITY,
|
||||
schema: z.tuple([
|
||||
z.string().describe('Property name'),
|
||||
z.string().optional().describe('Property value'),
|
||||
]),
|
||||
apply: (resource: Entity, key: string, value?: string) => {
|
||||
const foundValue = get(resource[propertyType], key);
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import { RESOURCE_TYPE_CATALOG_ENTITY } from '@backstage/plugin-catalog-common';
|
||||
import { z } from 'zod';
|
||||
import { createCatalogPermissionRule } from './util';
|
||||
|
||||
/**
|
||||
@@ -31,6 +32,10 @@ export const hasAnnotation = createCatalogPermissionRule({
|
||||
description:
|
||||
'Allow entities which are annotated with the specified annotation',
|
||||
resourceType: RESOURCE_TYPE_CATALOG_ENTITY,
|
||||
schema: z.tuple([
|
||||
z.string().describe('Annotation name'),
|
||||
z.string().optional().describe('Annotation value'),
|
||||
]),
|
||||
apply: (resource: Entity, annotation: string, value?: string) =>
|
||||
!!resource.metadata.annotations?.hasOwnProperty(annotation) &&
|
||||
(value === undefined
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import { RESOURCE_TYPE_CATALOG_ENTITY } from '@backstage/plugin-catalog-common';
|
||||
import { z } from 'zod';
|
||||
import { createCatalogPermissionRule } from './util';
|
||||
|
||||
/**
|
||||
@@ -27,6 +28,7 @@ export const hasLabel = createCatalogPermissionRule({
|
||||
name: 'HAS_LABEL',
|
||||
description: 'Allow entities which have the specified label metadata.',
|
||||
resourceType: RESOURCE_TYPE_CATALOG_ENTITY,
|
||||
schema: z.tuple([z.string().describe('Label name')]),
|
||||
apply: (resource: Entity, label: string) =>
|
||||
!!resource.metadata.labels?.hasOwnProperty(label),
|
||||
toQuery: (label: string) => ({
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import { RESOURCE_TYPE_CATALOG_ENTITY } from '@backstage/plugin-catalog-common';
|
||||
import { z } from 'zod';
|
||||
import { EntitiesSearchFilter } from '../../catalog/types';
|
||||
import { createCatalogPermissionRule } from './util';
|
||||
|
||||
@@ -27,6 +28,7 @@ export const isEntityKind = createCatalogPermissionRule({
|
||||
name: 'IS_ENTITY_KIND',
|
||||
description: 'Allow entities with the specified kind',
|
||||
resourceType: RESOURCE_TYPE_CATALOG_ENTITY,
|
||||
schema: z.tuple([z.array(z.string().describe('List of entity kinds'))]),
|
||||
apply(resource: Entity, kinds: string[]) {
|
||||
const resourceKind = resource.kind.toLocaleLowerCase('en-US');
|
||||
return kinds.some(kind => kind.toLocaleLowerCase('en-US') === resourceKind);
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
import { Entity, RELATION_OWNED_BY } from '@backstage/catalog-model';
|
||||
import { RESOURCE_TYPE_CATALOG_ENTITY } from '@backstage/plugin-catalog-common';
|
||||
import { z } from 'zod';
|
||||
import { createCatalogPermissionRule } from './util';
|
||||
|
||||
/**
|
||||
@@ -28,6 +29,7 @@ export const isEntityOwner = createCatalogPermissionRule({
|
||||
name: 'IS_ENTITY_OWNER',
|
||||
description: 'Allow entities owned by the current user',
|
||||
resourceType: RESOURCE_TYPE_CATALOG_ENTITY,
|
||||
schema: z.tuple([z.array(z.string().describe('List of owners'))]),
|
||||
apply: (resource: Entity, claims: string[]) => {
|
||||
if (!resource.relations) {
|
||||
return false;
|
||||
|
||||
@@ -36,6 +36,7 @@ import {
|
||||
} from '@backstage/plugin-permission-node';
|
||||
import { RESOURCE_TYPE_CATALOG_ENTITY } from '@backstage/plugin-catalog-common';
|
||||
import { CatalogProcessingOrchestrator } from '../processing/types';
|
||||
import { z } from 'zod';
|
||||
|
||||
describe('createRouter readonly disabled', () => {
|
||||
let entitiesCatalog: jest.Mocked<EntitiesCatalog>;
|
||||
@@ -695,6 +696,7 @@ describe('NextRouter permissioning', () => {
|
||||
name: 'FAKE_RULE',
|
||||
description: 'fake rule',
|
||||
resourceType: RESOURCE_TYPE_CATALOG_ENTITY,
|
||||
schema: z.tuple([]),
|
||||
apply: () => true,
|
||||
toQuery: () => ({ key: '', values: [] }),
|
||||
});
|
||||
|
||||
@@ -30,6 +30,7 @@ import {
|
||||
createPermissionRule,
|
||||
} from '@backstage/plugin-permission-node';
|
||||
import { PermissionIntegrationClient } from './PermissionIntegrationClient';
|
||||
import { z } from 'zod';
|
||||
|
||||
describe('PermissionIntegrationClient', () => {
|
||||
describe('applyConditions', () => {
|
||||
@@ -279,6 +280,7 @@ describe('PermissionIntegrationClient', () => {
|
||||
name: 'RULE_1',
|
||||
description: 'Test rule 1',
|
||||
resourceType: 'test-resource',
|
||||
schema: z.tuple([z.enum(['yes', 'no'])]),
|
||||
apply: (_resource: any, input: 'yes' | 'no') => input === 'yes',
|
||||
toQuery: () => {
|
||||
throw new Error('Not implemented');
|
||||
@@ -288,6 +290,7 @@ describe('PermissionIntegrationClient', () => {
|
||||
name: 'RULE_2',
|
||||
description: 'Test rule 2',
|
||||
resourceType: 'test-resource',
|
||||
schema: z.tuple([z.enum(['yes', 'no'])]),
|
||||
apply: (_resource: any, input: 'yes' | 'no') => input === 'yes',
|
||||
toQuery: () => {
|
||||
throw new Error('Not implemented');
|
||||
|
||||
@@ -18,6 +18,7 @@ import {
|
||||
AuthorizeResult,
|
||||
createPermission,
|
||||
} from '@backstage/plugin-permission-common';
|
||||
import { z } from 'zod';
|
||||
import { createConditionExports } from './createConditionExports';
|
||||
import { createPermissionRule } from './createPermissionRule';
|
||||
|
||||
@@ -30,6 +31,7 @@ const testIntegration = () =>
|
||||
name: 'testRule1',
|
||||
description: 'Test rule 1',
|
||||
resourceType: 'test-resource',
|
||||
schema: z.tuple([z.string(), z.number()]),
|
||||
apply: jest.fn(
|
||||
(_resource: any, _firstParam: string, _secondParam: number) => true,
|
||||
),
|
||||
@@ -42,6 +44,7 @@ const testIntegration = () =>
|
||||
name: 'testRule2',
|
||||
description: 'Test rule 2',
|
||||
resourceType: 'test-resource',
|
||||
schema: z.tuple([z.object({})]),
|
||||
apply: jest.fn((_resource: any, _firstParam: object) => false),
|
||||
toQuery: jest.fn((firstParam: object) => ({
|
||||
query: 'testRule2',
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { z } from 'zod';
|
||||
import { createConditionFactory } from './createConditionFactory';
|
||||
import { createPermissionRule } from './createPermissionRule';
|
||||
|
||||
@@ -22,6 +23,7 @@ describe('createConditionFactory', () => {
|
||||
name: 'test-rule',
|
||||
description: 'test-description',
|
||||
resourceType: 'test-resource',
|
||||
schema: z.tuple([]),
|
||||
apply: jest.fn(),
|
||||
toQuery: jest.fn(),
|
||||
});
|
||||
|
||||
@@ -18,6 +18,7 @@ import {
|
||||
PermissionCondition,
|
||||
PermissionCriteria,
|
||||
} from '@backstage/plugin-permission-common';
|
||||
import { z } from 'zod';
|
||||
import { createConditionTransformer } from './createConditionTransformer';
|
||||
import { createPermissionRule } from './createPermissionRule';
|
||||
|
||||
@@ -26,6 +27,7 @@ const transformConditions = createConditionTransformer([
|
||||
name: 'test-rule-1',
|
||||
description: 'Test rule 1',
|
||||
resourceType: 'test-resource',
|
||||
schema: z.tuple([]),
|
||||
apply: jest.fn(),
|
||||
toQuery: jest.fn(
|
||||
(firstParam: string, secondParam: number) =>
|
||||
@@ -36,6 +38,7 @@ const transformConditions = createConditionTransformer([
|
||||
name: 'test-rule-2',
|
||||
description: 'Test rule 2',
|
||||
resourceType: 'test-resource',
|
||||
schema: z.tuple([]),
|
||||
apply: jest.fn(),
|
||||
toQuery: jest.fn(
|
||||
(firstParam: object) => `test-rule-2:${JSON.stringify(firstParam)}`,
|
||||
|
||||
@@ -21,6 +21,7 @@ import {
|
||||
} from '@backstage/plugin-permission-common';
|
||||
import express, { Express, Router } from 'express';
|
||||
import request, { Response } from 'supertest';
|
||||
import { z } from 'zod';
|
||||
import { createPermissionIntegrationRouter } from './createPermissionIntegrationRouter';
|
||||
import { createPermissionRule } from './createPermissionRule';
|
||||
|
||||
@@ -39,6 +40,7 @@ const testRule1 = createPermissionRule({
|
||||
name: 'test-rule-1',
|
||||
description: 'Test rule 1',
|
||||
resourceType: 'test-resource',
|
||||
schema: z.tuple([z.string(), z.number()]),
|
||||
apply: (_resource: any, _firstParam: string, _secondParam: number) => true,
|
||||
toQuery: (_firstParam: string, _secondParam: number) => ({}),
|
||||
});
|
||||
@@ -47,6 +49,7 @@ const testRule2 = createPermissionRule({
|
||||
name: 'test-rule-2',
|
||||
description: 'Test rule 2',
|
||||
resourceType: 'test-resource',
|
||||
schema: z.tuple([z.object({})]),
|
||||
apply: (_resource: any, _firstParam: object) => false,
|
||||
toQuery: (_firstParam: object) => ({}),
|
||||
});
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { z } from 'zod';
|
||||
import { createPermissionRule } from './createPermissionRule';
|
||||
import {
|
||||
createGetRule,
|
||||
@@ -30,6 +31,7 @@ describe('permission integration utils', () => {
|
||||
name: 'test-rule-1',
|
||||
description: 'Test rule 1',
|
||||
resourceType: 'test-resource',
|
||||
schema: z.tuple([]),
|
||||
apply: jest.fn(),
|
||||
toQuery: jest.fn(),
|
||||
});
|
||||
@@ -38,6 +40,7 @@ describe('permission integration utils', () => {
|
||||
name: 'test-rule-2',
|
||||
description: 'Test rule 2',
|
||||
resourceType: 'test-resource',
|
||||
schema: z.tuple([]),
|
||||
apply: jest.fn(),
|
||||
toQuery: jest.fn(),
|
||||
});
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
|
||||
import type { PermissionCriteria } from '@backstage/plugin-permission-common';
|
||||
import { z } from 'zod';
|
||||
|
||||
/**
|
||||
* A conditional rule that can be provided in an
|
||||
@@ -42,6 +43,11 @@ export type PermissionRule<
|
||||
description: string;
|
||||
resourceType: TResourceType;
|
||||
|
||||
/**
|
||||
* A ZodSchema that documents the parameters that this rule accepts.
|
||||
*/
|
||||
schema: z.ZodSchema<TParams>;
|
||||
|
||||
/**
|
||||
* Apply this rule to a resource already loaded from a backing data source. The params are
|
||||
* arguments supplied for the rule; for example, a rule could be `isOwner` with entityRefs as the
|
||||
|
||||
@@ -39,7 +39,8 @@
|
||||
"node-fetch": "^2.6.7",
|
||||
"uuid": "^8.2.0",
|
||||
"winston": "^3.2.1",
|
||||
"yn": "^4.0.0"
|
||||
"yn": "^4.0.0",
|
||||
"zod": "^3.11.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "workspace:^",
|
||||
|
||||
@@ -19,6 +19,7 @@ import {
|
||||
PLAYLIST_LIST_RESOURCE_TYPE,
|
||||
PlaylistMetadata,
|
||||
} from '@backstage/plugin-playlist-common';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { ListPlaylistsFilter } from '../service';
|
||||
|
||||
@@ -32,6 +33,7 @@ const isOwner = createPlaylistPermissionRule({
|
||||
name: 'IS_OWNER',
|
||||
description: 'Should allow only if the playlist belongs to the user',
|
||||
resourceType: PLAYLIST_LIST_RESOURCE_TYPE,
|
||||
schema: z.tuple([z.array(z.string().describe('List of owners'))]),
|
||||
apply: (list: PlaylistMetadata, userOwnershipRefs: string[]) =>
|
||||
userOwnershipRefs.includes(list.owner),
|
||||
toQuery: (userOwnershipRefs: string[]) => ({
|
||||
@@ -44,6 +46,7 @@ const isPublic = createPlaylistPermissionRule({
|
||||
name: 'IS_PUBLIC',
|
||||
description: 'Should allow only if the playlist is public',
|
||||
resourceType: PLAYLIST_LIST_RESOURCE_TYPE,
|
||||
schema: z.tuple([]),
|
||||
apply: (list: PlaylistMetadata) => list.public,
|
||||
toQuery: () => ({ key: 'public', values: [true] }),
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user