From 899d38ea68e23e6c0250c11182855f8c1a15ba5c Mon Sep 17 00:00:00 2001 From: Mike Lewis Date: Thu, 3 Mar 2022 18:05:23 +0000 Subject: [PATCH] permission-common: add utility types for creating and refining Permissions Signed-off-by: Mike Lewis --- .changeset/warm-mangos-compete.md | 4 +- .../permission-backend/src/service/router.ts | 3 +- plugins/permission-common/api-report.md | 19 +++++++ .../src/PermissionClient.test.ts | 20 ++++---- .../src/permissions/createPermission.ts | 50 +++++++++++++++++++ .../src/permissions/index.ts | 1 + .../permission-common/src/permissions/util.ts | 19 ++++++- .../src/ServerPermissionClient.test.ts | 7 ++- 8 files changed, 106 insertions(+), 17 deletions(-) create mode 100644 plugins/permission-common/src/permissions/createPermission.ts diff --git a/.changeset/warm-mangos-compete.md b/.changeset/warm-mangos-compete.md index 3ad688c372..3215b36d22 100644 --- a/.changeset/warm-mangos-compete.md +++ b/.changeset/warm-mangos-compete.md @@ -2,4 +2,6 @@ '@backstage/plugin-permission-common': patch --- -Add more specific `Permission` types +- Add more specific `Permission` types. +- Add `createPermission` helper to infer the appropriate type for some permission input. +- Add `isResourcePermission` helper to refine Permissions to ResourcePermissions. diff --git a/plugins/permission-backend/src/service/router.ts b/plugins/permission-backend/src/service/router.ts index 51e3ce960e..4b5b8f5abb 100644 --- a/plugins/permission-backend/src/service/router.ts +++ b/plugins/permission-backend/src/service/router.ts @@ -35,6 +35,7 @@ import { Identified, AuthorizeRequest, AuthorizeResponse, + isResourcePermission, } from '@backstage/plugin-permission-common'; import { ApplyConditionsRequestEntry, @@ -109,7 +110,7 @@ const handleRequest = async ( }; } - if (!('resourceType' in request.permission)) { + if (!isResourcePermission(request.permission)) { throw new Error( `Conditional decision returned from permission policy for non-resource permission ${request.permission.name}`, ); diff --git a/plugins/permission-common/api-report.md b/plugins/permission-common/api-report.md index fcd1e1251b..d58d89f9cc 100644 --- a/plugins/permission-common/api-report.md +++ b/plugins/permission-common/api-report.md @@ -59,6 +59,19 @@ export type BasicPermission = { attributes: PermissionAttributes; }; +// @public +export function createPermission(input: { + name: string; + attributes: PermissionAttributes; + resourceType: TResourceType; +}): ResourcePermission; + +// @public +export function createPermission(input: { + name: string; + attributes: PermissionAttributes; +}): BasicPermission; + // @public export type DiscoveryApi = { getBaseUrl(pluginId: string): Promise; @@ -78,6 +91,12 @@ export function isDeletePermission(permission: Permission): boolean; // @public export function isReadPermission(permission: Permission): boolean; +// @public +export function isResourcePermission( + permission: Permission, + resourceType?: T, +): permission is ResourcePermission; + // @public export function isUpdatePermission(permission: Permission): boolean; diff --git a/plugins/permission-common/src/PermissionClient.test.ts b/plugins/permission-common/src/PermissionClient.test.ts index 0e57f618b7..4f66bcffd3 100644 --- a/plugins/permission-common/src/PermissionClient.test.ts +++ b/plugins/permission-common/src/PermissionClient.test.ts @@ -20,7 +20,7 @@ import { ConfigReader } from '@backstage/config'; import { PermissionClient } from './PermissionClient'; import { AuthorizeQuery, AuthorizeResult, Identified } from './types/api'; import { DiscoveryApi } from './types/discovery'; -import { Permission } from './types/permission'; +import { createPermission } from './permissions'; const server = setupServer(); const token = 'fake-token'; @@ -36,16 +36,11 @@ const client: PermissionClient = new PermissionClient({ config: new ConfigReader({ permission: { enabled: true } }), }); -const mockPermission: Permission = { +const mockPermission = createPermission({ name: 'test.permission', attributes: {}, - resourceType: 'test-resource', -}; - -const mockAuthorizeQuery = { - permission: mockPermission, - resourceRef: 'foo', -}; + resourceType: 'foo', +}); describe('PermissionClient', () => { beforeAll(() => server.listen({ onUnhandledRequest: 'error' })); @@ -53,6 +48,11 @@ describe('PermissionClient', () => { afterEach(() => server.resetHandlers()); describe('authorize', () => { + const mockAuthorizeQuery = { + permission: mockPermission, + resourceRef: 'foo:bar', + }; + const mockAuthorizeHandler = jest.fn((req, res, { json }: RestContext) => { const responses = req.body.items.map((a: Identified) => ({ id: a.id, @@ -84,7 +84,7 @@ describe('PermissionClient', () => { items: [ expect.objectContaining({ permission: mockPermission, - resourceRef: 'foo', + resourceRef: 'foo:bar', }), ], }); diff --git a/plugins/permission-common/src/permissions/createPermission.ts b/plugins/permission-common/src/permissions/createPermission.ts new file mode 100644 index 0000000000..b3e918ac85 --- /dev/null +++ b/plugins/permission-common/src/permissions/createPermission.ts @@ -0,0 +1,50 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + BasicPermission, + Permission, + PermissionAttributes, + ResourcePermission, +} from '../types'; + +/** + * Utility function for creating a valid {@link ResourcePermission}, inferring + * the appropriate type and resource type parameter. + * + * @public + */ +export function createPermission(input: { + name: string; + attributes: PermissionAttributes; + resourceType: TResourceType; +}): ResourcePermission; +/** + * Utility function for creating a valid {@link BasicPermission}. + * + * @public + */ +export function createPermission(input: { + name: string; + attributes: PermissionAttributes; +}): BasicPermission; +export function createPermission(input: { + name: string; + attributes: PermissionAttributes; + resourceType?: string; +}): Permission { + return input; +} diff --git a/plugins/permission-common/src/permissions/index.ts b/plugins/permission-common/src/permissions/index.ts index 736d898539..acb15e45cf 100644 --- a/plugins/permission-common/src/permissions/index.ts +++ b/plugins/permission-common/src/permissions/index.ts @@ -15,3 +15,4 @@ */ export * from './util'; +export { createPermission } from './createPermission'; diff --git a/plugins/permission-common/src/permissions/util.ts b/plugins/permission-common/src/permissions/util.ts index 188609f4ae..2843d97419 100644 --- a/plugins/permission-common/src/permissions/util.ts +++ b/plugins/permission-common/src/permissions/util.ts @@ -14,7 +14,24 @@ * limitations under the License. */ -import { Permission } from '../types'; +import { Permission, ResourcePermission } from '../types'; + +/** + * Check if a given permission is a {@link ResourcePermission}. When + * `resourceType` is supplied as the second parameter, also checks if + * the permission has the specified resource type. + * @public + */ +export function isResourcePermission( + permission: Permission, + resourceType?: T, +): permission is ResourcePermission { + if (!('resourceType' in permission)) { + return false; + } + + return !resourceType || permission.resourceType === resourceType; +} /** * Check if a given permission is related to a create action. diff --git a/plugins/permission-node/src/ServerPermissionClient.test.ts b/plugins/permission-node/src/ServerPermissionClient.test.ts index 53300061a0..f2302f8f0d 100644 --- a/plugins/permission-node/src/ServerPermissionClient.test.ts +++ b/plugins/permission-node/src/ServerPermissionClient.test.ts @@ -16,10 +16,10 @@ import { ServerPermissionClient } from './ServerPermissionClient'; import { - Permission, Identified, AuthorizeQuery, AuthorizeResult, + createPermission, } from '@backstage/plugin-permission-common'; import { ConfigReader } from '@backstage/config'; import { @@ -48,11 +48,10 @@ const discovery: PluginEndpointDiscovery = { return mockBaseUrl; }, }; -const testPermission: Permission = { +const testPermission = createPermission({ name: 'test.permission', attributes: {}, - resourceType: 'test-resource', -}; +}); const config = new ConfigReader({ permission: { enabled: true }, backend: { auth: { keys: [{ secret: 'a-secret-key' }] } },