Added parameter scheamas to permission rules

Signed-off-by: Harry Hogg <hhogg@spotify.com>
This commit is contained in:
Harry Hogg
2022-09-28 15:07:28 +01:00
parent 4c480aeefb
commit e9b4191071
16 changed files with 47 additions and 1 deletions
@@ -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(),
});
+6
View File
@@ -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
+2 -1
View File
@@ -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] }),
});
+1
View File
@@ -6427,6 +6427,7 @@ __metadata:
uuid: ^8.2.0
winston: ^3.2.1
yn: ^4.0.0
zod: ^3.11.6
languageName: unknown
linkType: soft