Merge pull request #15798 from backstage/prfc/scaffolder-permissions

PRFC: Scaffolder permissions
This commit is contained in:
Ben Lambert
2023-03-22 11:06:39 +01:00
committed by GitHub
32 changed files with 1182 additions and 92 deletions
+5
View File
@@ -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.