Merge pull request #13905 from backstage/permissions/schema

Permissions - Add a required parameter schema
This commit is contained in:
Harrison Hogg
2022-10-11 14:59:24 +01:00
committed by GitHub
43 changed files with 755 additions and 294 deletions
+22 -12
View File
@@ -19,11 +19,13 @@ import { Permission } from '@backstage/plugin-permission-common';
import { PermissionCondition } from '@backstage/plugin-permission-common';
import { PermissionCriteria } from '@backstage/plugin-permission-common';
import { PermissionEvaluator } from '@backstage/plugin-permission-common';
import { PermissionRuleParams } from '@backstage/plugin-permission-common';
import { PluginEndpointDiscovery } from '@backstage/backend-common';
import { PolicyDecision } from '@backstage/plugin-permission-common';
import { QueryPermissionRequest } from '@backstage/plugin-permission-common';
import { ResourcePermission } from '@backstage/plugin-permission-common';
import { TokenManager } from '@backstage/backend-common';
import { z } from 'zod';
// @public
export type ApplyConditionsRequest = {
@@ -53,7 +55,9 @@ export type Condition<TRule> = TRule extends PermissionRule<
infer TResourceType,
infer TParams
>
? (...params: TParams) => PermissionCondition<TResourceType, TParams>
? undefined extends TParams
? () => PermissionCondition<TResourceType, TParams>
: (params: TParams) => PermissionCondition<TResourceType, TParams>
: never;
// @public
@@ -74,7 +78,7 @@ export const createConditionExports: <
TResource,
TRules extends Record<
string,
PermissionRule<TResource, any, TResourceType, unknown[]>
PermissionRule<TResource, any, TResourceType, PermissionRuleParams>
>,
>(options: {
pluginId: string;
@@ -85,7 +89,7 @@ export const createConditionExports: <
createConditionalDecision: (
permission: ResourcePermission<TResourceType>,
conditions: PermissionCriteria<
PermissionCondition<TResourceType, unknown[]>
PermissionCondition<TResourceType, PermissionRuleParams>
>,
) => ConditionalPolicyDecision;
};
@@ -93,15 +97,15 @@ export const createConditionExports: <
// @public
export const createConditionFactory: <
TResourceType extends string,
TParams extends any[],
TParams extends PermissionRuleParams = PermissionRuleParams,
>(
rule: PermissionRule<unknown, unknown, TResourceType, TParams>,
) => (...params: TParams) => PermissionCondition<TResourceType, TParams>;
) => (params: TParams) => PermissionCondition<TResourceType, TParams>;
// @public
export const createConditionTransformer: <
TQuery,
TRules extends PermissionRule<any, TQuery, string, unknown[]>[],
TRules extends PermissionRule<any, TQuery, string, PermissionRuleParams>[],
>(
permissionRules: [...TRules],
) => ConditionTransformer<TQuery>;
@@ -113,7 +117,12 @@ export const createPermissionIntegrationRouter: <
>(options: {
resourceType: TResourceType;
permissions?: Permission[] | undefined;
rules: PermissionRule<TResource, any, NoInfer<TResourceType>, unknown[]>[];
rules: PermissionRule<
TResource,
any,
NoInfer<TResourceType>,
PermissionRuleParams
>[];
getResources: (resourceRefs: string[]) => Promise<(TResource | undefined)[]>;
}) => express.Router;
@@ -122,7 +131,7 @@ export const createPermissionRule: <
TResource,
TQuery,
TResourceType extends string,
TParams extends unknown[],
TParams extends PermissionRuleParams = undefined,
>(
rule: PermissionRule<TResource, TQuery, TResourceType, TParams>,
) => PermissionRule<TResource, TQuery, TResourceType, TParams>;
@@ -147,7 +156,7 @@ export const makeCreatePermissionRule: <
TResource,
TQuery,
TResourceType extends string,
>() => <TParams extends unknown[]>(
>() => <TParams extends PermissionRuleParams = undefined>(
rule: PermissionRule<TResource, TQuery, TResourceType, TParams>,
) => PermissionRule<TResource, TQuery, TResourceType, TParams>;
@@ -165,13 +174,14 @@ export type PermissionRule<
TResource,
TQuery,
TResourceType extends string,
TParams extends unknown[] = unknown[],
TParams extends PermissionRuleParams = PermissionRuleParams,
> = {
name: string;
description: string;
resourceType: TResourceType;
apply(resource: TResource, ...params: TParams): boolean;
toQuery(...params: TParams): PermissionCriteria<TQuery>;
paramsSchema?: z.ZodSchema<TParams>;
apply(resource: TResource, params: NoInfer<TParams>): boolean;
toQuery(params: NoInfer<TParams>): PermissionCriteria<TQuery>;
};
// @public
+2 -1
View File
@@ -41,7 +41,8 @@
"@types/express": "^4.17.6",
"express": "^4.17.1",
"express-promise-router": "^4.1.0",
"zod": "^3.11.6"
"zod": "^3.11.6",
"zod-to-json-schema": "^3.18.1"
},
"devDependencies": {
"@backstage/backend-test-utils": "workspace:^",
@@ -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,23 +31,28 @@ const testIntegration = () =>
name: 'testRule1',
description: 'Test rule 1',
resourceType: 'test-resource',
apply: jest.fn(
(_resource: any, _firstParam: string, _secondParam: number) => true,
),
toQuery: jest.fn((firstParam: string, secondParam: number) => ({
paramsSchema: z.object({
foo: z.string(),
bar: z.number(),
}),
apply: (_resource: any, _params) => true,
toQuery: params => ({
query: 'testRule1',
params: [firstParam, secondParam],
})),
params,
}),
}),
testRule2: createPermissionRule({
name: 'testRule2',
description: 'Test rule 2',
resourceType: 'test-resource',
apply: jest.fn((_resource: any, _firstParam: object) => false),
toQuery: jest.fn((firstParam: object) => ({
paramsSchema: z.object({
foo: z.string().optional(),
}),
apply: (_resource: any) => false,
toQuery: params => ({
query: 'testRule2',
params: [firstParam],
})),
params,
}),
}),
},
});
@@ -56,16 +62,24 @@ describe('createConditionExports', () => {
it('creates condition factories for the supplied rules', () => {
const { conditions } = testIntegration();
expect(conditions.testRule1('a', 1)).toEqual({
expect(
conditions.testRule1({
foo: 'a',
bar: 1,
}),
).toEqual({
rule: 'testRule1',
resourceType: 'test-resource',
params: ['a', 1],
params: {
foo: 'a',
bar: 1,
},
});
expect(conditions.testRule2({ baz: 'quux' })).toEqual({
expect(conditions.testRule2({})).toEqual({
rule: 'testRule2',
resourceType: 'test-resource',
params: [{ baz: 'quux' }],
params: {},
});
});
});
@@ -85,7 +99,10 @@ describe('createConditionExports', () => {
{
rule: 'testRule1',
resourceType: 'test-resource',
params: ['a', 1],
params: {
foo: 'a',
bar: 1,
},
},
],
}),
@@ -98,7 +115,10 @@ describe('createConditionExports', () => {
{
rule: 'testRule1',
resourceType: 'test-resource',
params: ['a', 1],
params: {
foo: 'a',
bar: 1,
},
},
],
},
@@ -36,7 +36,9 @@ export type Condition<TRule> = TRule extends PermissionRule<
infer TResourceType,
infer TParams
>
? (...params: TParams) => PermissionCondition<TResourceType, TParams>
? undefined extends TParams
? () => PermissionCondition<TResourceType, TParams>
: (params: TParams) => PermissionCondition<TResourceType, TParams>
: never;
/**
@@ -14,6 +14,7 @@
* limitations under the License.
*/
import { z } from 'zod';
import { createConditionFactory } from './createConditionFactory';
import { createPermissionRule } from './createPermissionRule';
@@ -22,8 +23,11 @@ describe('createConditionFactory', () => {
name: 'test-rule',
description: 'test-description',
resourceType: 'test-resource',
apply: jest.fn(),
toQuery: jest.fn(),
paramsSchema: z.object({
foo: z.string(),
}),
apply: (_resource, _params) => true,
toQuery: _params => ({}),
});
it('returns a function', () => {
@@ -33,10 +37,17 @@ describe('createConditionFactory', () => {
describe('return value', () => {
it('constructs a condition with the rule name and supplied params', () => {
const conditionFactory = createConditionFactory(testRule);
expect(conditionFactory('a', 'b', 1, 2)).toEqual({
expect(
conditionFactory({
foo: 'bar',
}),
).toEqual({
rule: 'test-rule',
resourceType: 'test-resource',
params: ['a', 'b', 1, 2],
params: {
foo: 'bar',
},
});
});
});
@@ -14,7 +14,10 @@
* limitations under the License.
*/
import { PermissionCondition } from '@backstage/plugin-permission-common';
import {
PermissionCondition,
PermissionRuleParams,
} from '@backstage/plugin-permission-common';
import { PermissionRule } from '../types';
/**
@@ -33,12 +36,17 @@ import { PermissionRule } from '../types';
*
* @public
*/
export const createConditionFactory =
<TResourceType extends string, TParams extends any[]>(
rule: PermissionRule<unknown, unknown, TResourceType, TParams>,
) =>
(...params: TParams): PermissionCondition<TResourceType, TParams> => ({
rule: rule.name,
resourceType: rule.resourceType,
params,
});
export const createConditionFactory = <
TResourceType extends string,
TParams extends PermissionRuleParams = PermissionRuleParams,
>(
rule: PermissionRule<unknown, unknown, TResourceType, TParams>,
) => {
return (params: TParams): PermissionCondition<TResourceType, TParams> => {
return {
rule: rule.name,
resourceType: rule.resourceType,
params,
};
};
};
@@ -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,20 +27,22 @@ const transformConditions = createConditionTransformer([
name: 'test-rule-1',
description: 'Test rule 1',
resourceType: 'test-resource',
paramsSchema: z.object({
foo: z.string(),
bar: z.number(),
}),
apply: jest.fn(),
toQuery: jest.fn(
(firstParam: string, secondParam: number) =>
`test-rule-1:${firstParam}/${secondParam}`,
),
toQuery: jest.fn(({ foo, bar }) => `test-rule-1:${foo}/${bar}`),
}),
createPermissionRule({
name: 'test-rule-2',
description: 'Test rule 2',
resourceType: 'test-resource',
paramsSchema: z.object({
foo: z.string(),
}),
apply: jest.fn(),
toQuery: jest.fn(
(firstParam: object) => `test-rule-2:${JSON.stringify(firstParam)}`,
),
toQuery: jest.fn(({ foo }) => `test-rule-2:${foo}`),
}),
]);
@@ -52,7 +55,10 @@ describe('createConditionTransformer', () => {
conditions: {
rule: 'test-rule-1',
resourceType: 'test-resource',
params: ['abc', 123],
params: {
foo: 'abc',
bar: 123,
},
},
expectedResult: 'test-rule-1:abc/123',
},
@@ -60,9 +66,11 @@ describe('createConditionTransformer', () => {
conditions: {
rule: 'test-rule-2',
resourceType: 'test-resource',
params: [{ foo: 0 }],
params: {
foo: '0',
},
},
expectedResult: 'test-rule-2:{"foo":0}',
expectedResult: 'test-rule-2:0',
},
{
conditions: {
@@ -70,13 +78,22 @@ describe('createConditionTransformer', () => {
{
rule: 'test-rule-1',
resourceType: 'test-resource',
params: ['a', 1],
params: {
foo: 'a',
bar: 1,
},
},
{
rule: 'test-rule-2',
resourceType: 'test-resource',
params: {
foo: 'b',
},
},
{ rule: 'test-rule-2', resourceType: 'test-resource', params: [{}] },
],
},
expectedResult: {
anyOf: ['test-rule-1:a/1', 'test-rule-2:{}'],
anyOf: ['test-rule-1:a/1', 'test-rule-2:b'],
},
},
{
@@ -85,13 +102,22 @@ describe('createConditionTransformer', () => {
{
rule: 'test-rule-1',
resourceType: 'test-resource',
params: ['a', 1],
params: {
foo: 'a',
bar: 1,
},
},
{
rule: 'test-rule-2',
resourceType: 'test-resource',
params: {
foo: 'b',
},
},
{ rule: 'test-rule-2', resourceType: 'test-resource', params: [{}] },
],
},
expectedResult: {
allOf: ['test-rule-1:a/1', 'test-rule-2:{}'],
allOf: ['test-rule-1:a/1', 'test-rule-2:b'],
},
},
{
@@ -99,11 +125,13 @@ describe('createConditionTransformer', () => {
not: {
rule: 'test-rule-2',
resourceType: 'test-resource',
params: [{}],
params: {
foo: 'a',
},
},
},
expectedResult: {
not: 'test-rule-2:{}',
not: 'test-rule-2:a',
},
},
{
@@ -114,12 +142,17 @@ describe('createConditionTransformer', () => {
{
rule: 'test-rule-1',
resourceType: 'test-resource',
params: ['a', 1],
params: {
foo: 'a',
bar: 1,
},
},
{
rule: 'test-rule-2',
resourceType: 'test-resource',
params: [{}],
params: {
foo: 'b',
},
},
],
},
@@ -129,61 +162,16 @@ describe('createConditionTransformer', () => {
{
rule: 'test-rule-1',
resourceType: 'test-resource',
params: ['b', 2],
params: {
foo: 'b',
bar: 2,
},
},
{
rule: 'test-rule-2',
resourceType: 'test-resource',
params: [{ c: 3 }],
},
],
},
},
],
},
expectedResult: {
allOf: [
{
anyOf: ['test-rule-1:a/1', 'test-rule-2:{}'],
},
{
not: {
allOf: ['test-rule-1:b/2', 'test-rule-2:{"c":3}'],
},
},
],
},
},
{
conditions: {
allOf: [
{
anyOf: [
{
rule: 'test-rule-1',
resourceType: 'test-resource',
params: ['a', 1],
},
{
rule: 'test-rule-2',
resourceType: 'test-resource',
params: [{ b: 2 }],
},
],
},
{
not: {
allOf: [
{
rule: 'test-rule-1',
resourceType: 'test-resource',
params: ['c', 3],
},
{
not: {
rule: 'test-rule-2',
resourceType: 'test-resource',
params: [{ d: 4 }],
params: {
foo: 'c',
},
},
],
@@ -194,11 +182,71 @@ describe('createConditionTransformer', () => {
expectedResult: {
allOf: [
{
anyOf: ['test-rule-1:a/1', 'test-rule-2:{"b":2}'],
anyOf: ['test-rule-1:a/1', 'test-rule-2:b'],
},
{
not: {
allOf: ['test-rule-1:c/3', { not: 'test-rule-2:{"d":4}' }],
allOf: ['test-rule-1:b/2', 'test-rule-2:c'],
},
},
],
},
},
{
conditions: {
allOf: [
{
anyOf: [
{
rule: 'test-rule-1',
resourceType: 'test-resource',
params: {
foo: 'a',
bar: 1,
},
},
{
rule: 'test-rule-2',
resourceType: 'test-resource',
params: {
foo: 'b',
},
},
],
},
{
not: {
allOf: [
{
rule: 'test-rule-1',
resourceType: 'test-resource',
params: {
foo: 'c',
bar: 3,
},
},
{
not: {
rule: 'test-rule-2',
resourceType: 'test-resource',
params: {
foo: 'd',
},
},
},
],
},
},
],
},
expectedResult: {
allOf: [
{
anyOf: ['test-rule-1:a/1', 'test-rule-2:b'],
},
{
not: {
allOf: ['test-rule-1:c/3', { not: 'test-rule-2:d' }],
},
},
],
@@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { InputError } from '@backstage/errors';
import {
AllOfCriteria,
AnyOfCriteria,
@@ -45,7 +46,14 @@ const mapConditions = <TQuery>(
};
}
return getRule(criteria.rule).toQuery(...criteria.params);
const rule = getRule(criteria.rule);
const result = rule.paramsSchema?.safeParse(criteria.params);
if (result && !result.success) {
throw new InputError(`Parameters to rule are invalid`, result.error);
}
return rule.toQuery(criteria.params ?? {});
};
/**
@@ -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,16 +40,20 @@ const testRule1 = createPermissionRule({
name: 'test-rule-1',
description: 'Test rule 1',
resourceType: 'test-resource',
apply: (_resource: any, _firstParam: string, _secondParam: number) => true,
toQuery: (_firstParam: string, _secondParam: number) => ({}),
paramsSchema: z.object({
foo: z.string(),
bar: z.number().describe('bar'),
}),
apply: (_resource: any, _params) => true,
toQuery: _params => ({}),
});
const testRule2 = createPermissionRule({
name: 'test-rule-2',
description: 'Test rule 2',
resourceType: 'test-resource',
apply: (_resource: any, _firstParam: object) => false,
toQuery: (_firstParam: object) => ({}),
apply: (_resource: any) => false,
toQuery: () => ({}),
});
describe('createPermissionIntegrationRouter', () => {
@@ -79,23 +84,31 @@ describe('createPermissionIntegrationRouter', () => {
{
rule: 'test-rule-1',
resourceType: 'test-resource',
params: ['abc', 123],
params: {
foo: 'abc',
bar: 123,
},
},
{
anyOf: [
{
rule: 'test-rule-1',
resourceType: 'test-resource',
params: ['a', 1],
params: {
foo: 'a',
bar: 1,
},
},
{
rule: 'test-rule-2',
resourceType: 'test-resource',
},
{ rule: 'test-rule-2', resourceType: 'test-resource', params: [{}] },
],
},
{
not: {
rule: 'test-rule-2',
resourceType: 'test-resource',
params: [{}],
},
},
{
@@ -105,12 +118,14 @@ describe('createPermissionIntegrationRouter', () => {
{
rule: 'test-rule-1',
resourceType: 'test-resource',
params: ['a', 1],
params: {
foo: 'a',
bar: 1,
},
},
{
rule: 'test-rule-2',
resourceType: 'test-resource',
params: [{}],
},
],
},
@@ -120,12 +135,14 @@ describe('createPermissionIntegrationRouter', () => {
{
rule: 'test-rule-1',
resourceType: 'test-resource',
params: ['b', 2],
params: {
foo: 'b',
bar: 2,
},
},
{
rule: 'test-rule-2',
resourceType: 'test-resource',
params: [{ c: 3 }],
},
],
},
@@ -161,16 +178,21 @@ describe('createPermissionIntegrationRouter', () => {
{
rule: 'test-rule-2',
resourceType: 'test-resource',
params: [{ foo: 0 }],
},
{
allOf: [
{
rule: 'test-rule-1',
resourceType: 'test-resource',
params: ['a', 1],
params: {
foo: 'a',
bar: 1,
},
},
{
rule: 'test-rule-2',
resourceType: 'test-resource',
},
{ rule: 'test-rule-2', resourceType: 'test-resource', params: [{}] },
],
},
{
@@ -180,12 +202,14 @@ describe('createPermissionIntegrationRouter', () => {
{
rule: 'test-rule-1',
resourceType: 'test-resource',
params: ['a', 1],
params: {
foo: 'a',
bar: 1,
},
},
{
rule: 'test-rule-2',
resourceType: 'test-resource',
params: [{ b: 2 }],
},
],
},
@@ -195,13 +219,15 @@ describe('createPermissionIntegrationRouter', () => {
{
rule: 'test-rule-1',
resourceType: 'test-resource',
params: ['c', 3],
params: {
foo: 'c',
bar: 3,
},
},
{
not: {
rule: 'test-rule-2',
resourceType: 'test-resource',
params: [{ d: 4 }],
},
},
],
@@ -247,7 +273,10 @@ describe('createPermissionIntegrationRouter', () => {
conditions: {
rule: 'test-rule-1',
resourceType: 'test-resource',
params: [],
params: {
foo: 'a',
bar: 1,
},
},
},
{
@@ -257,7 +286,6 @@ describe('createPermissionIntegrationRouter', () => {
conditions: {
rule: 'test-rule-2',
resourceType: 'test-resource',
params: [],
},
},
{
@@ -268,7 +296,10 @@ describe('createPermissionIntegrationRouter', () => {
not: {
rule: 'test-rule-1',
resourceType: 'test-resource',
params: [],
params: {
foo: 'a',
bar: 1,
},
},
},
},
@@ -280,7 +311,6 @@ describe('createPermissionIntegrationRouter', () => {
not: {
rule: 'test-rule-2',
resourceType: 'test-resource',
params: [],
},
},
},
@@ -293,12 +323,14 @@ describe('createPermissionIntegrationRouter', () => {
{
rule: 'test-rule-1',
resourceType: 'test-resource',
params: [],
params: {
foo: 'a',
bar: 1,
},
},
{
rule: 'test-rule-2',
resourceType: 'test-resource',
params: [],
},
],
},
@@ -342,7 +374,9 @@ describe('createPermissionIntegrationRouter', () => {
conditions: {
rule: 'test-rule-1',
resourceType: 'test-incorrect-resource-1',
params: [{}],
params: {
foo: {},
},
},
},
{
@@ -352,7 +386,9 @@ describe('createPermissionIntegrationRouter', () => {
conditions: {
rule: 'test-rule-1',
resourceType: 'test-resource',
params: [{}],
params: {
foo: {},
},
},
},
{
@@ -362,7 +398,9 @@ describe('createPermissionIntegrationRouter', () => {
conditions: {
rule: 'test-rule-1',
resourceType: 'test-incorrect-resource-2',
params: [{}],
params: {
foo: {},
},
},
},
],
@@ -390,7 +428,7 @@ describe('createPermissionIntegrationRouter', () => {
conditions: {
rule: 'test-rule-1',
resourceType: 'test-resource',
params: [],
params: {},
},
},
],
@@ -427,7 +465,10 @@ describe('createPermissionIntegrationRouter', () => {
conditions: {
rule: 'test-rule-1',
resourceType: 'test-resource',
params: [],
params: {
foo: 'a',
bar: 1,
},
},
},
{
@@ -437,7 +478,10 @@ describe('createPermissionIntegrationRouter', () => {
conditions: {
rule: 'test-rule-1',
resourceType: 'test-resource',
params: [],
params: {
foo: 'a',
bar: 1,
},
},
},
{
@@ -447,7 +491,10 @@ describe('createPermissionIntegrationRouter', () => {
conditions: {
rule: 'test-rule-1',
resourceType: 'test-resource',
params: [],
params: {
foo: 'a',
bar: 1,
},
},
},
],
@@ -524,13 +571,32 @@ describe('createPermissionIntegrationRouter', () => {
name: testRule1.name,
description: testRule1.description,
resourceType: testRule1.resourceType,
parameters: { count: 2 },
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,
parameters: { count: 1 },
paramsSchema: {
$schema: 'http://json-schema.org/draft-07/schema#',
additionalProperties: false,
properties: {},
type: 'object',
},
},
],
});
@@ -17,6 +17,7 @@
import express, { Response } from 'express';
import Router from 'express-promise-router';
import { z } from 'zod';
import zodToJsonSchema from 'zod-to-json-schema';
import { InputError } from '@backstage/errors';
import { errorHandler } from '@backstage/backend-common';
import {
@@ -29,6 +30,7 @@ import {
} from '@backstage/plugin-permission-common';
import { PermissionRule } from '../types';
import {
NoInfer,
createGetRule,
isAndCriteria,
isNotCriteria,
@@ -45,7 +47,7 @@ const permissionCriteriaSchema: z.ZodSchema<
z.object({
rule: z.string(),
resourceType: z.string(),
params: z.array(z.unknown()),
params: z.record(z.any()).optional(),
}),
]),
);
@@ -124,16 +126,15 @@ const applyConditions = <TResourceType extends string, TResource>(
return !applyConditions(criteria.not, resource, getRule);
}
return getRule(criteria.rule).apply(resource, ...criteria.params);
};
const rule = getRule(criteria.rule);
const result = rule.paramsSchema?.safeParse(criteria.params);
/**
* Prevent use of type parameter from contributing to type inference.
*
* https://github.com/Microsoft/TypeScript/issues/14829#issuecomment-980401795
* @ignore
*/
type NoInfer<T> = T extends infer S ? S : never;
if (result && !result.success) {
throw new InputError(`Parameters to rule are invalid`, result.error);
}
return rule.apply(resource, criteria.params ?? {});
};
/**
* Create an express Router which provides an authorization route to allow
@@ -194,9 +195,7 @@ export const createPermissionIntegrationRouter = <
name: rule.name,
description: rule.description,
resourceType: rule.resourceType,
parameters: {
count: rule.toQuery.length,
},
paramsSchema: zodToJsonSchema(rule.paramsSchema ?? z.object({})),
}));
return res.json({ permissions, rules: serializableRules });
@@ -14,6 +14,7 @@
* limitations under the License.
*/
import { PermissionRuleParams } from '@backstage/plugin-permission-common';
import { PermissionRule } from '../types';
/**
@@ -25,7 +26,7 @@ export const createPermissionRule = <
TResource,
TQuery,
TResourceType extends string,
TParams extends unknown[],
TParams extends PermissionRuleParams = undefined,
>(
rule: PermissionRule<TResource, TQuery, TResourceType, TParams>,
) => rule;
@@ -40,7 +41,7 @@ export const createPermissionRule = <
*/
export const makeCreatePermissionRule =
<TResource, TQuery, TResourceType extends string>() =>
<TParams extends unknown[]>(
<TParams extends PermissionRuleParams = undefined>(
rule: PermissionRule<TResource, TQuery, TResourceType, TParams>,
) =>
createPermissionRule(rule);
@@ -22,6 +22,14 @@ import {
} from '@backstage/plugin-permission-common';
import { PermissionRule } from '../types';
/**
* Prevent use of type parameter from contributing to type inference.
*
* https://github.com/Microsoft/TypeScript/issues/14829#issuecomment-980401795
* @ignore
*/
export type NoInfer<T> = T extends infer S ? S : never;
/**
* Utility function used to parse a PermissionCriteria
* @param criteria - a PermissionCriteria
+14 -4
View File
@@ -14,7 +14,12 @@
* limitations under the License.
*/
import type { PermissionCriteria } from '@backstage/plugin-permission-common';
import type {
PermissionCriteria,
PermissionRuleParams,
} from '@backstage/plugin-permission-common';
import { z } from 'zod';
import { NoInfer } from './integration/util';
/**
* A conditional rule that can be provided in an
@@ -36,23 +41,28 @@ export type PermissionRule<
TResource,
TQuery,
TResourceType extends string,
TParams extends unknown[] = unknown[],
TParams extends PermissionRuleParams = PermissionRuleParams,
> = {
name: string;
description: string;
resourceType: TResourceType;
/**
* A ZodSchema that reflects the structure of the parameters that are passed to
*/
paramsSchema?: 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
* params.
*/
apply(resource: TResource, ...params: TParams): boolean;
apply(resource: TResource, params: NoInfer<TParams>): boolean;
/**
* Translate this rule to criteria suitable for use in querying a backing data store. The criteria
* can be used for loading a collection of resources efficiently with conditional criteria already
* applied.
*/
toQuery(...params: TParams): PermissionCriteria<TQuery>;
toQuery(params: NoInfer<TParams>): PermissionCriteria<TQuery>;
};