Merge pull request #15798 from backstage/prfc/scaffolder-permissions
PRFC: Scaffolder permissions
This commit is contained in:
@@ -73,6 +73,11 @@ export type ConditionTransformer<TQuery> = (
|
||||
conditions: PermissionCriteria<PermissionCondition>,
|
||||
) => PermissionCriteria<TQuery>;
|
||||
|
||||
// @public
|
||||
export const createConditionAuthorizer: <TResource, TQuery>(
|
||||
rules: PermissionRule<TResource, TQuery, string, PermissionRuleParams>[],
|
||||
) => (decision: PolicyDecision, resource: TResource | undefined) => boolean;
|
||||
|
||||
// @public
|
||||
export const createConditionExports: <
|
||||
TResourceType extends string,
|
||||
|
||||
@@ -25,6 +25,7 @@ import { z } from 'zod';
|
||||
import {
|
||||
createPermissionIntegrationRouter,
|
||||
CreatePermissionIntegrationRouterResourceOptions,
|
||||
createConditionAuthorizer,
|
||||
} from './createPermissionIntegrationRouter';
|
||||
import { createPermissionRule } from './createPermissionRule';
|
||||
|
||||
@@ -33,6 +34,9 @@ const testPermission: Permission = createPermission({
|
||||
attributes: {},
|
||||
});
|
||||
|
||||
const mockTestRule1Apply = jest
|
||||
.fn()
|
||||
.mockImplementation((_resource: any, _params) => true);
|
||||
const testRule1 = createPermissionRule({
|
||||
name: 'test-rule-1',
|
||||
description: 'Test rule 1',
|
||||
@@ -41,15 +45,18 @@ const testRule1 = createPermissionRule({
|
||||
foo: z.string(),
|
||||
bar: z.number().describe('bar'),
|
||||
}),
|
||||
apply: (_resource: any, _params) => true,
|
||||
apply: mockTestRule1Apply,
|
||||
toQuery: _params => ({}),
|
||||
});
|
||||
|
||||
const mockTestRule2Apply = jest
|
||||
.fn()
|
||||
.mockImplementation((_resource: any) => false);
|
||||
const testRule2 = createPermissionRule({
|
||||
name: 'test-rule-2',
|
||||
description: 'Test rule 2',
|
||||
resourceType: 'test-resource',
|
||||
apply: (_resource: any) => false,
|
||||
apply: mockTestRule2Apply,
|
||||
toQuery: () => ({}),
|
||||
});
|
||||
|
||||
@@ -625,3 +632,72 @@ describe('createPermissionIntegrationRouter', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('createConditionAuthorizer', () => {
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should return true in case of allowed decision', () => {
|
||||
const isAuthorized = createConditionAuthorizer([testRule1, testRule2]);
|
||||
expect(
|
||||
isAuthorized(
|
||||
{
|
||||
result: AuthorizeResult.ALLOW,
|
||||
},
|
||||
{},
|
||||
),
|
||||
).toBe(true);
|
||||
|
||||
expect(mockTestRule1Apply).not.toHaveBeenCalled();
|
||||
expect(mockTestRule2Apply).not.toHaveBeenCalled();
|
||||
});
|
||||
it('should return false in case of denied decision', () => {
|
||||
const isAuthorized = createConditionAuthorizer([testRule1, testRule2]);
|
||||
expect(
|
||||
isAuthorized(
|
||||
{
|
||||
result: AuthorizeResult.DENY,
|
||||
},
|
||||
{},
|
||||
),
|
||||
).toBe(false);
|
||||
|
||||
expect(mockTestRule1Apply).not.toHaveBeenCalled();
|
||||
expect(mockTestRule2Apply).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should apply conditions to a resource in case of conditional decision', () => {
|
||||
const isAuthorized = createConditionAuthorizer([testRule1, testRule2]);
|
||||
expect(
|
||||
isAuthorized(
|
||||
{
|
||||
pluginId: 'plugin',
|
||||
resourceType: 'test-resource',
|
||||
result: AuthorizeResult.CONDITIONAL,
|
||||
conditions: {
|
||||
allOf: [
|
||||
{
|
||||
rule: 'test-rule-1',
|
||||
resourceType: 'test-resource',
|
||||
params: {
|
||||
foo: 'a',
|
||||
bar: 1,
|
||||
},
|
||||
},
|
||||
{
|
||||
rule: 'test-rule-2',
|
||||
resourceType: 'test-resource',
|
||||
params: {},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{},
|
||||
),
|
||||
).toBe(false);
|
||||
|
||||
expect(mockTestRule1Apply).toHaveBeenCalledTimes(1);
|
||||
expect(mockTestRule2Apply).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -27,6 +27,7 @@ import {
|
||||
Permission,
|
||||
PermissionCondition,
|
||||
PermissionCriteria,
|
||||
PolicyDecision,
|
||||
} from '@backstage/plugin-permission-common';
|
||||
import { PermissionRule } from '../types';
|
||||
import {
|
||||
@@ -160,6 +161,30 @@ const applyConditions = <TResourceType extends string, TResource>(
|
||||
return rule.apply(resource, criteria.params ?? {});
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
* Takes some permission conditions and returns a definitive authorization result
|
||||
* on the resource to which they apply.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export const createConditionAuthorizer = <TResource, TQuery>(
|
||||
rules: PermissionRule<TResource, TQuery, string>[],
|
||||
) => {
|
||||
const getRule = createGetRule(rules);
|
||||
|
||||
return (
|
||||
decision: PolicyDecision,
|
||||
resource: TResource | undefined,
|
||||
): boolean => {
|
||||
if (decision.result === AuthorizeResult.CONDITIONAL) {
|
||||
return applyConditions(decision.conditions, resource, getRule);
|
||||
}
|
||||
|
||||
return decision.result === AuthorizeResult.ALLOW;
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Options for creating a permission integration router specific
|
||||
* for a particular resource type.
|
||||
|
||||
Reference in New Issue
Block a user