chore: add util to perform basic permission check

Signed-off-by: Frank Kong <frkong@redhat.com>
This commit is contained in:
Frank Kong
2024-05-02 10:16:25 -04:00
parent b1fbdebcc7
commit 83643ef091
2 changed files with 95 additions and 105 deletions
+42 -105
View File
@@ -103,6 +103,7 @@ import {
IdentityApiGetIdentityRequest,
} from '@backstage/plugin-auth-node';
import { InternalTaskSecrets } from '../scaffolder/tasks/types';
import { checkPermission } from '../util/checkPermissions';
/**
*
@@ -492,19 +493,11 @@ export async function createRouter(
)
.get('/v2/actions', async (req, res) => {
const credentials = await httpAuth.credentials(req);
if (permissions) {
const authorizationResponse = (
await permissions.authorizeConditional(
[{ permission: actionReadPermission }],
{ credentials: credentials },
)
)[0];
if (authorizationResponse.result === AuthorizeResult.DENY) {
throw new NotAllowedError();
}
}
await checkPermission({
credentials,
permissions: [actionReadPermission],
permissionService: permissions,
});
const actionsList = actionRegistry.list().map(action => {
return {
id: action.id,
@@ -522,18 +515,11 @@ export async function createRouter(
});
const credentials = await httpAuth.credentials(req);
if (permissions) {
const authorizationResponse = (
await permissions.authorizeConditional(
[{ permission: taskCreatePermission }],
{ credentials: credentials },
)
)[0];
if (authorizationResponse.result === AuthorizeResult.DENY) {
throw new NotAllowedError();
}
}
await checkPermission({
credentials,
permissions: [taskCreatePermission],
permissionService: permissions,
});
const { token } = await auth.getPluginRequestToken({
onBehalfOf: credentials,
@@ -612,22 +598,13 @@ export async function createRouter(
})
.get('/v2/tasks', async (req, res) => {
const credentials = await httpAuth.credentials(req);
if (permissions) {
const authorizationResponse = (
await permissions.authorizeConditional(
[{ permission: taskReadPermission }],
{ credentials: credentials },
)
)[0];
if (authorizationResponse.result === AuthorizeResult.DENY) {
throw new NotAllowedError();
}
}
await checkPermission({
credentials,
permissions: [taskReadPermission],
permissionService: permissions,
});
const [userEntityRef] = [req.query.createdBy].flat();
if (
typeof userEntityRef !== 'string' &&
typeof userEntityRef !== 'undefined'
@@ -649,19 +626,11 @@ export async function createRouter(
})
.get('/v2/tasks/:taskId', async (req, res) => {
const credentials = await httpAuth.credentials(req);
if (permissions) {
const authorizationResponse = (
await permissions.authorizeConditional(
[{ permission: taskReadPermission }],
{ credentials: credentials },
)
)[0];
if (authorizationResponse.result === AuthorizeResult.DENY) {
throw new NotAllowedError();
}
}
await checkPermission({
credentials,
permissions: [taskReadPermission],
permissionService: permissions,
});
const { taskId } = req.params;
const task = await taskBroker.get(taskId);
@@ -674,22 +643,12 @@ export async function createRouter(
})
.post('/v2/tasks/:taskId/cancel', async (req, res) => {
const credentials = await httpAuth.credentials(req);
if (permissions) {
const authorizationResponses = await permissions.authorizeConditional(
[
{ permission: taskCancelPermission },
{ permission: taskReadPermission },
],
{ credentials: credentials },
);
// Requires both read and cancel permissions
for (const response of authorizationResponses) {
if (response.result === AuthorizeResult.DENY) {
throw new NotAllowedError();
}
}
}
// Requires both read and cancel permissions
await checkPermission({
credentials,
permissions: [taskCancelPermission, taskReadPermission],
permissionService: permissions,
});
const { taskId } = req.params;
await taskBroker.cancel?.(taskId);
@@ -697,19 +656,12 @@ export async function createRouter(
})
.get('/v2/tasks/:taskId/eventstream', async (req, res) => {
const credentials = await httpAuth.credentials(req);
await checkPermission({
credentials,
permissions: [taskReadPermission],
permissionService: permissions,
});
if (permissions) {
const authorizationResponse = (
await permissions.authorizeConditional(
[{ permission: taskReadPermission }],
{ credentials: credentials },
)
)[0];
if (authorizationResponse.result === AuthorizeResult.DENY) {
throw new NotAllowedError();
}
}
const { taskId } = req.params;
const after =
req.query.after !== undefined ? Number(req.query.after) : undefined;
@@ -759,19 +711,12 @@ export async function createRouter(
})
.get('/v2/tasks/:taskId/events', async (req, res) => {
const credentials = await httpAuth.credentials(req);
await checkPermission({
credentials,
permissions: [taskReadPermission],
permissionService: permissions,
});
if (permissions) {
const authorizationResponse = (
await permissions.authorizeConditional(
[{ permission: taskReadPermission }],
{ credentials: credentials },
)
)[0];
if (authorizationResponse.result === AuthorizeResult.DENY) {
throw new NotAllowedError();
}
}
const { taskId } = req.params;
const after = Number(req.query.after) || undefined;
@@ -803,19 +748,11 @@ export async function createRouter(
})
.post('/v2/dry-run', async (req, res) => {
const credentials = await httpAuth.credentials(req);
if (permissions) {
const authorizationResponse = (
await permissions.authorizeConditional(
[{ permission: taskCreatePermission }],
{ credentials: credentials },
)
)[0];
if (authorizationResponse.result === AuthorizeResult.DENY) {
throw new NotAllowedError();
}
}
await checkPermission({
credentials,
permissions: [taskCreatePermission],
permissionService: permissions,
});
const bodySchema = z.object({
template: z.unknown(),
@@ -0,0 +1,53 @@
/*
* Copyright 2024 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 {
BackstageCredentials,
PermissionsService,
} from '@backstage/backend-plugin-api';
import { NotAllowedError } from '@backstage/errors';
import {
AuthorizeResult,
ResourcePermission,
} from '@backstage/plugin-permission-common';
export type checkPermissionOptions = {
credentials: BackstageCredentials;
permissions: ResourcePermission[];
permissionService?: PermissionsService;
};
/**
* Does a basic check on permissions. Throws 403 error if any permission responds with AuthorizeResult.DENY
* @public
*/
export async function checkPermission(options: checkPermissionOptions) {
const { permissions, permissionService, credentials } = options;
if (permissionService) {
const permissionRequest = permissions.map(resourcePermission => ({
permission: resourcePermission,
}));
const authorizationResponses = await permissionService.authorizeConditional(
permissionRequest,
{ credentials: credentials },
);
for (const response of authorizationResponses) {
if (response.result === AuthorizeResult.DENY) {
throw new NotAllowedError();
}
}
}
}