permission-node: accept batched requests in /apply-conditions
Signed-off-by: MT Lewis <mtlewis@users.noreply.github.com>
This commit is contained in:
@@ -9,24 +9,29 @@ import { AuthorizeResponse } from '@backstage/plugin-permission-common';
|
||||
import { AuthorizeResult } from '@backstage/plugin-permission-common';
|
||||
import { BackstageIdentityResponse } from '@backstage/plugin-auth-backend';
|
||||
import { Config } from '@backstage/config';
|
||||
import express from 'express';
|
||||
import { Identified } from '@backstage/plugin-permission-common';
|
||||
import { PermissionAuthorizer } from '@backstage/plugin-permission-common';
|
||||
import { PermissionCondition } from '@backstage/plugin-permission-common';
|
||||
import { PermissionCriteria } from '@backstage/plugin-permission-common';
|
||||
import { PluginEndpointDiscovery } from '@backstage/backend-common';
|
||||
import { Router } from 'express';
|
||||
import { TokenManager } from '@backstage/backend-common';
|
||||
|
||||
// @public
|
||||
export type ApplyConditionsRequest = {
|
||||
export type ApplyConditionsRequest = ApplyConditionsRequestEntry[];
|
||||
|
||||
// @public
|
||||
export type ApplyConditionsRequestEntry = Identified<{
|
||||
resourceRef: string;
|
||||
resourceType: string;
|
||||
conditions: PermissionCriteria<PermissionCondition>;
|
||||
};
|
||||
}>;
|
||||
|
||||
// @public
|
||||
export type ApplyConditionsResponse = {
|
||||
result: AuthorizeResult.ALLOW | AuthorizeResult.DENY;
|
||||
};
|
||||
export type ApplyConditionsResponse = ApplyConditionsResponseEntry[];
|
||||
|
||||
// @public
|
||||
export type ApplyConditionsResponseEntry = Identified<DefinitivePolicyDecision>;
|
||||
|
||||
// @public
|
||||
export type Condition<TRule> = TRule extends PermissionRule<
|
||||
@@ -93,7 +98,12 @@ export const createPermissionIntegrationRouter: <TResource>(options: {
|
||||
resourceType: string;
|
||||
rules: PermissionRule<TResource, any, unknown[]>[];
|
||||
getResource: (resourceRef: string) => Promise<TResource | undefined>;
|
||||
}) => Router;
|
||||
}) => express.Router;
|
||||
|
||||
// @public
|
||||
export type DefinitivePolicyDecision = {
|
||||
result: AuthorizeResult.ALLOW | AuthorizeResult.DENY;
|
||||
};
|
||||
|
||||
// @public
|
||||
export const createPermissionRule: <
|
||||
@@ -137,9 +147,7 @@ export type PolicyAuthorizeRequest = Omit<AuthorizeRequest, 'resourceRef'>;
|
||||
|
||||
// @public
|
||||
export type PolicyDecision =
|
||||
| {
|
||||
result: AuthorizeResult.ALLOW | AuthorizeResult.DENY;
|
||||
}
|
||||
| DefinitivePolicyDecision
|
||||
| ConditionalPolicyDecision;
|
||||
|
||||
// @public
|
||||
|
||||
@@ -31,10 +31,12 @@
|
||||
"dependencies": {
|
||||
"@backstage/backend-common": "^0.10.1",
|
||||
"@backstage/config": "^0.1.11",
|
||||
"@backstage/errors": "^0.1.5",
|
||||
"@backstage/plugin-auth-backend": "^0.6.0",
|
||||
"@backstage/plugin-permission-common": "^0.3.0",
|
||||
"@types/express": "^4.17.6",
|
||||
"express": "^4.17.1",
|
||||
"express-promise-router": "^4.1.0",
|
||||
"zod": "^3.11.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
+125
-50
@@ -16,16 +16,12 @@
|
||||
|
||||
import { AuthorizeResult } from '@backstage/plugin-permission-common';
|
||||
import express, { Express, Router } from 'express';
|
||||
import request from 'supertest';
|
||||
import request, { Response } from 'supertest';
|
||||
import { createPermissionIntegrationRouter } from './createPermissionIntegrationRouter';
|
||||
|
||||
const mockGetResource: jest.MockedFunction<
|
||||
(resourceRef: string) => Promise<any>
|
||||
> = jest.fn((resourceRef: string) =>
|
||||
Promise.resolve({
|
||||
resourceRef,
|
||||
}),
|
||||
);
|
||||
Parameters<typeof createPermissionIntegrationRouter>[0]['getResource']
|
||||
> = jest.fn(async resourceRef => ({ id: resourceRef }));
|
||||
|
||||
const testRule1 = {
|
||||
name: 'test-rule-1',
|
||||
@@ -47,7 +43,7 @@ describe('createPermissionIntegrationRouter', () => {
|
||||
let app: Express;
|
||||
let router: Router;
|
||||
|
||||
beforeEach(() => {
|
||||
beforeAll(() => {
|
||||
router = createPermissionIntegrationRouter({
|
||||
resourceType: 'test-resource',
|
||||
getResource: mockGetResource,
|
||||
@@ -57,6 +53,10 @@ describe('createPermissionIntegrationRouter', () => {
|
||||
app = express().use(router);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('works', async () => {
|
||||
expect(router).toBeDefined();
|
||||
});
|
||||
@@ -70,7 +70,6 @@ describe('createPermissionIntegrationRouter', () => {
|
||||
{ rule: 'test-rule-2', params: [{}] },
|
||||
],
|
||||
},
|
||||
|
||||
{
|
||||
not: { rule: 'test-rule-2', params: [{}] },
|
||||
},
|
||||
@@ -95,14 +94,22 @@ describe('createPermissionIntegrationRouter', () => {
|
||||
])('returns 200/ALLOW when criteria match (case %#)', async conditions => {
|
||||
const response = await request(app)
|
||||
.post('/.well-known/backstage/permissions/apply-conditions')
|
||||
.send({
|
||||
resourceRef: 'default:test/resource',
|
||||
resourceType: 'test-resource',
|
||||
conditions,
|
||||
});
|
||||
.send([
|
||||
{
|
||||
id: '123',
|
||||
resourceRef: 'default:test/resource',
|
||||
resourceType: 'test-resource',
|
||||
conditions,
|
||||
},
|
||||
]);
|
||||
|
||||
expect(response.status).toEqual(200);
|
||||
expect(response.body).toEqual({ result: AuthorizeResult.ALLOW });
|
||||
expect(response.body).toEqual([
|
||||
{
|
||||
id: '123',
|
||||
result: AuthorizeResult.ALLOW,
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it.each([
|
||||
@@ -136,27 +143,92 @@ describe('createPermissionIntegrationRouter', () => {
|
||||
async conditions => {
|
||||
const response = await request(app)
|
||||
.post('/.well-known/backstage/permissions/apply-conditions')
|
||||
.send({
|
||||
resourceRef: 'default:test/resource',
|
||||
resourceType: 'test-resource',
|
||||
conditions,
|
||||
});
|
||||
.send([
|
||||
{
|
||||
id: '123',
|
||||
resourceRef: 'default:test/resource',
|
||||
resourceType: 'test-resource',
|
||||
conditions,
|
||||
},
|
||||
]);
|
||||
|
||||
expect(response.status).toEqual(200);
|
||||
expect(response.body).toEqual({ result: AuthorizeResult.DENY });
|
||||
expect(response.body).toEqual([
|
||||
{ id: '123', result: AuthorizeResult.DENY },
|
||||
]);
|
||||
},
|
||||
);
|
||||
|
||||
describe('batched requests', () => {
|
||||
let response: Response;
|
||||
|
||||
beforeEach(async () => {
|
||||
response = await request(app)
|
||||
.post('/.well-known/backstage/permissions/apply-conditions')
|
||||
.send([
|
||||
{
|
||||
id: '123',
|
||||
resourceRef: 'default:test/resource-1',
|
||||
resourceType: 'test-resource',
|
||||
conditions: { rule: 'test-rule-1', params: [] },
|
||||
},
|
||||
{
|
||||
id: '234',
|
||||
resourceRef: 'default:test/resource-1',
|
||||
resourceType: 'test-resource',
|
||||
conditions: { rule: 'test-rule-2', params: [] },
|
||||
},
|
||||
{
|
||||
id: '345',
|
||||
resourceRef: 'default:test/resource-2',
|
||||
resourceType: 'test-resource',
|
||||
conditions: { not: { rule: 'test-rule-1', params: [] } },
|
||||
},
|
||||
{
|
||||
id: '456',
|
||||
resourceRef: 'default:test/resource-3',
|
||||
resourceType: 'test-resource',
|
||||
conditions: { not: { rule: 'test-rule-2', params: [] } },
|
||||
},
|
||||
{
|
||||
id: '567',
|
||||
resourceRef: 'default:test/resource-4',
|
||||
resourceType: 'test-resource',
|
||||
conditions: {
|
||||
anyOf: [
|
||||
{ rule: 'test-rule-1', params: [] },
|
||||
{ rule: 'test-rule-2', params: [] },
|
||||
],
|
||||
},
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('processes batched requests', () => {
|
||||
expect(response.status).toEqual(200);
|
||||
expect(response.body).toEqual([
|
||||
{ id: '123', result: AuthorizeResult.ALLOW },
|
||||
{ id: '234', result: AuthorizeResult.DENY },
|
||||
{ id: '345', result: AuthorizeResult.DENY },
|
||||
{ id: '456', result: AuthorizeResult.ALLOW },
|
||||
{ id: '567', result: AuthorizeResult.ALLOW },
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
it('returns 400 when called with incorrect resource type', async () => {
|
||||
const response = await request(app)
|
||||
.post('/.well-known/backstage/permissions/apply-conditions')
|
||||
.send({
|
||||
resourceRef: 'default:test/resource',
|
||||
resourceType: 'test-incorrect-resource',
|
||||
conditions: {
|
||||
anyOf: [],
|
||||
.send([
|
||||
{
|
||||
id: '123',
|
||||
resourceRef: 'default:test/resource',
|
||||
resourceType: 'test-incorrect-resource',
|
||||
conditions: {
|
||||
anyOf: [],
|
||||
},
|
||||
},
|
||||
});
|
||||
]);
|
||||
|
||||
expect(response.status).toEqual(400);
|
||||
expect(response.error && response.error.text).toMatch(
|
||||
@@ -164,47 +236,50 @@ describe('createPermissionIntegrationRouter', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('returns 400 when resource is not found', async () => {
|
||||
it('returns 200/DENY when resource is not found', async () => {
|
||||
mockGetResource.mockReturnValueOnce(Promise.resolve(undefined));
|
||||
|
||||
const response = await request(app)
|
||||
.post('/.well-known/backstage/permissions/apply-conditions')
|
||||
.send({
|
||||
resourceRef: 'default:test/resource',
|
||||
resourceType: 'test-resource',
|
||||
conditions: {
|
||||
not: {
|
||||
rule: 'testRule1',
|
||||
params: ['a', 1],
|
||||
},
|
||||
.send([
|
||||
{
|
||||
id: '123',
|
||||
resourceRef: 'default:test/resource',
|
||||
resourceType: 'test-resource',
|
||||
conditions: { rule: 'testRule1', params: [] },
|
||||
},
|
||||
});
|
||||
]);
|
||||
|
||||
expect(response.status).toEqual(400);
|
||||
expect(response.error && response.error.text).toMatch(
|
||||
/resource for ref default:test\/resource not found/i,
|
||||
);
|
||||
expect(response.status).toEqual(200);
|
||||
expect(response.body).toEqual([
|
||||
{
|
||||
id: '123',
|
||||
result: AuthorizeResult.DENY,
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it.each([
|
||||
undefined,
|
||||
'',
|
||||
{},
|
||||
{ resourceType: 'test-resource-type' },
|
||||
{ resourceRef: 'test/resource-ref' },
|
||||
{
|
||||
resourceType: 'test-resource-type',
|
||||
resourceRef: 'test/resource-ref',
|
||||
},
|
||||
{ conditions: { anyOf: [] } },
|
||||
[{ resourceType: 'test-resource-type' }],
|
||||
[{ resourceRef: 'test/resource-ref' }],
|
||||
[
|
||||
{
|
||||
resourceType: 'test-resource-type',
|
||||
resourceRef: 'test/resource-ref',
|
||||
},
|
||||
],
|
||||
[{ conditions: { anyOf: [] } }],
|
||||
])(`returns 400 for invalid input %#`, async input => {
|
||||
const response = await request(app)
|
||||
.post('/.well-known/backstage/permissions/apply-conditions')
|
||||
.send(input);
|
||||
|
||||
expect(response.status).toEqual(400);
|
||||
expect(response.error && response.error.text).toMatch(
|
||||
/invalid request body/i,
|
||||
);
|
||||
expect(response.error && response.error.text).toMatch(/invalid/i);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -14,10 +14,14 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import express, { Response, Router } from 'express';
|
||||
import express, { Response } from 'express';
|
||||
import Router from 'express-promise-router';
|
||||
import { z } from 'zod';
|
||||
import { InputError } from '@backstage/errors';
|
||||
import { errorHandler } from '@backstage/backend-common';
|
||||
import {
|
||||
AuthorizeResult,
|
||||
Identified,
|
||||
PermissionCondition,
|
||||
PermissionCriteria,
|
||||
} from '@backstage/plugin-permission-common';
|
||||
@@ -28,6 +32,7 @@ import {
|
||||
isNotCriteria,
|
||||
isOrCriteria,
|
||||
} from './util';
|
||||
import { DefinitivePolicyDecision } from '../policy/types';
|
||||
|
||||
const permissionCriteriaSchema: z.ZodSchema<
|
||||
PermissionCriteria<PermissionCondition>
|
||||
@@ -43,11 +48,14 @@ const permissionCriteriaSchema: z.ZodSchema<
|
||||
]),
|
||||
);
|
||||
|
||||
const applyConditionsRequestSchema = z.object({
|
||||
resourceRef: z.string(),
|
||||
resourceType: z.string(),
|
||||
conditions: permissionCriteriaSchema,
|
||||
});
|
||||
const applyConditionsRequestSchema = z.array(
|
||||
z.object({
|
||||
id: z.string(),
|
||||
resourceRef: z.string(),
|
||||
resourceType: z.string(),
|
||||
conditions: permissionCriteriaSchema,
|
||||
}),
|
||||
);
|
||||
|
||||
/**
|
||||
* A request to load the referenced resource and apply conditions in order to
|
||||
@@ -55,11 +63,18 @@ const applyConditionsRequestSchema = z.object({
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export type ApplyConditionsRequest = {
|
||||
export type ApplyConditionsRequestEntry = Identified<{
|
||||
resourceRef: string;
|
||||
resourceType: string;
|
||||
conditions: PermissionCriteria<PermissionCondition>;
|
||||
};
|
||||
}>;
|
||||
|
||||
/**
|
||||
* A batch of {@link ApplyConditionsRequestEntry} objects.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export type ApplyConditionsRequest = ApplyConditionsRequestEntry[];
|
||||
|
||||
/**
|
||||
* The result of applying the conditions, expressed as a definitive authorize
|
||||
@@ -67,15 +82,27 @@ export type ApplyConditionsRequest = {
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export type ApplyConditionsResponse = {
|
||||
result: AuthorizeResult.ALLOW | AuthorizeResult.DENY;
|
||||
};
|
||||
export type ApplyConditionsResponseEntry = Identified<DefinitivePolicyDecision>;
|
||||
|
||||
/**
|
||||
* A batch of {@link ApplyConditionsResponseEntry} objects.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export type ApplyConditionsResponse = ApplyConditionsResponseEntry[];
|
||||
|
||||
const applyConditions = <TResource>(
|
||||
criteria: PermissionCriteria<PermissionCondition>,
|
||||
resource: TResource,
|
||||
getRule: (name: string) => PermissionRule<TResource, unknown>,
|
||||
): boolean => {
|
||||
// If resource was not found, deny. This avoids leaking information from the
|
||||
// apply-conditions API which would allow a user to differentiate between
|
||||
// non-existent resources and resources to which they do not have access.
|
||||
if (typeof resource === 'undefined') {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isAndCriteria(criteria)) {
|
||||
return criteria.allOf.every(child =>
|
||||
applyConditions(child, resource, getRule),
|
||||
@@ -92,30 +119,37 @@ const applyConditions = <TResource>(
|
||||
};
|
||||
|
||||
/**
|
||||
* Create an express Router which provides an authorization route to allow integration between the
|
||||
* permission backend and other Backstage backend plugins. Plugin owners that wish to support
|
||||
* conditional authorization for their resources should add the router created by this function
|
||||
* to their express app inside their `createRouter` implementation.
|
||||
* Create an express Router which provides an authorization route to allow
|
||||
* integration between the permission backend and other Backstage backend
|
||||
* plugins. Plugin owners that wish to support conditional authorization for
|
||||
* their resources should add the router created by this function to their
|
||||
* express app inside their `createRouter` implementation.
|
||||
*
|
||||
* @remarks
|
||||
*
|
||||
* To make this concrete, we can use the Backstage software catalog as an example. The catalog has
|
||||
* conditional rules around access to specific _entities_ in the catalog. The _type_ of resource is
|
||||
* captured here as `resourceType`, a string identifier (`catalog-entity` in this example) that can
|
||||
* be provided with permission definitions. This is merely a _type_ to verify that conditions in an
|
||||
* authorization policy are constructed correctly, not a reference to a specific resource.
|
||||
* To make this concrete, we can use the Backstage software catalog as an
|
||||
* example. The catalog has conditional rules around access to specific
|
||||
* _entities_ in the catalog. The _type_ of resource is captured here as
|
||||
* `resourceType`, a string identifier (`catalog-entity` in this example) that
|
||||
* can be provided with permission definitions. This is merely a _type_ to
|
||||
* verify that conditions in an authorization policy are constructed correctly,
|
||||
* not a reference to a specific resource.
|
||||
*
|
||||
* The `rules` parameter is an array of {@link PermissionRule}s that introduce conditional
|
||||
* filtering logic for resources; for the catalog, these are things like `isEntityOwner` or
|
||||
* `hasAnnotation`. Rules describe how to filter a list of resources, and the `conditions` returned
|
||||
* allow these rules to be applied with specific parameters (such as 'group:default/team-a', or
|
||||
* The `rules` parameter is an array of {@link PermissionRule}s that introduce
|
||||
* conditional filtering logic for resources; for the catalog, these are things
|
||||
* like `isEntityOwner` or `hasAnnotation`. Rules describe how to filter a list
|
||||
* of resources, and the `conditions` returned allow these rules to be applied
|
||||
* with specific parameters (such as 'group:default/team-a', or
|
||||
* 'backstage.io/edit-url').
|
||||
*
|
||||
* The `getResource` argument should load a resource by reference. For the catalog, this is an
|
||||
* {@link @backstage/catalog-model#EntityRef}. For other plugins, this can be any serialized format.
|
||||
* This is used to construct the `createPermissionIntegrationRouter`, a function to add an
|
||||
* authorization route to your backend plugin. This route will be called by the `permission-backend`
|
||||
* when authorization conditions relating to this plugin need to be evaluated.
|
||||
* The `getResources` argument should load resources based on a reference
|
||||
* identifier. For the catalog, this is an
|
||||
* {@link @backstage/catalog-model#EntityRef}. For other plugins, this can be
|
||||
* any serialized format. This is used to construct the
|
||||
* `createPermissionIntegrationRouter`, a function to add an authorization route
|
||||
* to your backend plugin. This function will be called by the
|
||||
* `permission-backend` when authorization conditions relating to this plugin
|
||||
* need to be evaluated.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
@@ -123,53 +157,60 @@ export const createPermissionIntegrationRouter = <TResource>(options: {
|
||||
resourceType: string;
|
||||
rules: PermissionRule<TResource, any>[];
|
||||
getResource: (resourceRef: string) => Promise<TResource | undefined>;
|
||||
}): Router => {
|
||||
}): express.Router => {
|
||||
const { resourceType, rules, getResource } = options;
|
||||
const router = Router();
|
||||
|
||||
const getRule = createGetRule(rules);
|
||||
|
||||
const assertValidResourceTypes = (requests: ApplyConditionsRequest) => {
|
||||
const invalidResourceType = requests.find(
|
||||
request => request.resourceType !== resourceType,
|
||||
)?.resourceType;
|
||||
|
||||
if (invalidResourceType) {
|
||||
throw new InputError(`Unexpected resource type: ${invalidResourceType}.`);
|
||||
}
|
||||
};
|
||||
|
||||
router.use(express.json());
|
||||
|
||||
router.post(
|
||||
'/.well-known/backstage/permissions/apply-conditions',
|
||||
express.json(),
|
||||
async (
|
||||
req,
|
||||
res: Response<
|
||||
| {
|
||||
result: Omit<AuthorizeResult, AuthorizeResult.CONDITIONAL>;
|
||||
}
|
||||
| string
|
||||
>,
|
||||
) => {
|
||||
async (req, res: Response<ApplyConditionsResponse | string>) => {
|
||||
const parseResult = applyConditionsRequestSchema.safeParse(req.body);
|
||||
|
||||
if (!parseResult.success) {
|
||||
return res.status(400).send(`Invalid request body.`);
|
||||
throw new InputError(parseResult.error.toString());
|
||||
}
|
||||
|
||||
const { data: body } = parseResult;
|
||||
const body = parseResult.data;
|
||||
|
||||
if (body.resourceType !== resourceType) {
|
||||
return res
|
||||
.status(400)
|
||||
.send(`Unexpected resource type: ${body.resourceType}.`);
|
||||
assertValidResourceTypes(body);
|
||||
|
||||
const resources = {} as Record<string, TResource | undefined>;
|
||||
for (const { resourceRef } of body) {
|
||||
if (!resources[resourceRef]) {
|
||||
resources[resourceRef] = await getResource(resourceRef);
|
||||
}
|
||||
}
|
||||
|
||||
const resource = await getResource(body.resourceRef);
|
||||
|
||||
if (!resource) {
|
||||
return res
|
||||
.status(400)
|
||||
.send(`Resource for ref ${body.resourceRef} not found.`);
|
||||
}
|
||||
|
||||
return res.status(200).json({
|
||||
result: applyConditions(body.conditions, resource, getRule)
|
||||
? AuthorizeResult.ALLOW
|
||||
: AuthorizeResult.DENY,
|
||||
});
|
||||
return res.status(200).json(
|
||||
body.map(request => ({
|
||||
id: request.id,
|
||||
result: applyConditions(
|
||||
request.conditions,
|
||||
resources[request.resourceRef],
|
||||
getRule,
|
||||
)
|
||||
? AuthorizeResult.ALLOW
|
||||
: AuthorizeResult.DENY,
|
||||
})),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
router.use(errorHandler());
|
||||
|
||||
return router;
|
||||
};
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
export type {
|
||||
ConditionalPolicyDecision,
|
||||
DefinitivePolicyDecision,
|
||||
PermissionPolicy,
|
||||
PolicyAuthorizeRequest,
|
||||
PolicyDecision,
|
||||
|
||||
@@ -35,6 +35,19 @@ import { BackstageIdentityResponse } from '@backstage/plugin-auth-backend';
|
||||
*/
|
||||
export type PolicyAuthorizeRequest = Omit<AuthorizeRequest, 'resourceRef'>;
|
||||
|
||||
/**
|
||||
* A definitive result to an authorization request, returned by the {@link PermissionPolicy}.
|
||||
*
|
||||
* @remarks
|
||||
*
|
||||
* This indicates that the policy unconditionally allows (or denies) the request.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export type DefinitivePolicyDecision = {
|
||||
result: AuthorizeResult.ALLOW | AuthorizeResult.DENY;
|
||||
};
|
||||
|
||||
/**
|
||||
* A conditional result to an authorization request, returned by the {@link PermissionPolicy}.
|
||||
*
|
||||
@@ -61,7 +74,7 @@ export type ConditionalPolicyDecision = {
|
||||
* @public
|
||||
*/
|
||||
export type PolicyDecision =
|
||||
| { result: AuthorizeResult.ALLOW | AuthorizeResult.DENY }
|
||||
| DefinitivePolicyDecision
|
||||
| ConditionalPolicyDecision;
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user