Skip to main content

2. 添加基本权限检查

如果权限检查的结果不需要根据不同的情况而改变资源您可以使用基本权限检查对于这种检查,我们只需定义一个准许并调用authorize用它。

在本教程中,我们将使用基本权限检查来授权create这将允许 Backstage 集成商通过调整用户的许可政策.

我们先创建一个新权限,然后使用权限 api 调用authorize在创建待办事项时使用它。

创建新权限

让我们导航到文件plugins/todo-list-common/src/permissions.ts并添加我们的第一个权限:

plugins/todo-list-common/src/permissions.ts
import { createPermission } from '@backstage/plugin-permission-common';

export const tempExamplePermission = createPermission({
name: 'temp.example.noop',
attributes: {},
export const todoListCreatePermission = createPermission({
name: 'todo.list.create',
attributes: { action: 'create' },
});

export const todoListPermissions = [tempExamplePermission];
export const todoListPermissions = [todoListCreatePermission];

在本教程中,我们自动导出了该文件中的所有权限(请参阅plugins/todo-list-common/src/index.ts).

注:我们使用单独的 todo-list-common 包,因为您的插件授权的所有权限都应从"common-library" 包中导出。这允许 backstage 集成者在前端组件以及权限策略中引用它们。

使用新权限进行授权

安装以下模块

$ yarn workspace @internal/plugin-todo-list-backend \
add @backstage/plugin-permission-common @backstage/plugin-permission-node @internal/plugin-todo-list-common

编辑plugins/todo-list-backend/src/service/router.ts:

plugins/todo-list-backend/src/service/router.ts
import { InputError } from '@backstage/errors';
import { IdentityApi } from '@backstage/plugin-auth-node';
import { InputError, NotAllowedError } from '@backstage/errors';
import { getBearerTokenFromAuthorizationHeader, IdentityApi } from '@backstage/plugin-auth-node';
import { PermissionEvaluator, AuthorizeResult } from '@backstage/plugin-permission-common';
import { createPermissionIntegrationRouter } from '@backstage/plugin-permission-node';
import { todoListCreatePermission } from '@internal/plugin-todo-list-common';

export interface RouterOptions {
logger: Logger;
identity: IdentityApi;
permissions: PermissionEvaluator;
}

export async function createRouter(
options: RouterOptions,
): Promise<express.Router> {
const { logger, identity } = options;
const { logger, identity, permissions } = options;

const permissionIntegrationRouter = createPermissionIntegrationRouter({
permissions: [todoListCreatePermission],
});

const router = Router();
router.use(express.json());

router.get('/health', (_, response) => {
logger.info('PONG!');
response.json({ status: 'ok' });
});

router.use(permissionIntegrationRouter);

router.get('/todos', async (_req, res) => {
res.json(getAll());
});

router.post('/todos', async (req, res) => {
let author: string | undefined = undefined;

const user = await identity.getIdentity({ request: req });
author = user?.identity.userEntityRef;
const token = getBearerTokenFromAuthorizationHeader(
req.header('authorization'),
);
const decision = (
await permissions.authorize([{ permission: todoListCreatePermission }], {
token,
})
)[0];

if (decision.result === AuthorizeResult.DENY) {
throw new NotAllowedError('Unauthorized');
}

if (!isTodoCreateRequest(req.body)) {
throw new InputError('Invalid payload');
}

const todo = add({ title: req.body.title, author });
res.json(todo);
});

// ...

通过permissions对象的插件packages/backend/src/plugins/todolist.ts:

packages/backend/src/plugins/todolist.ts
import { DefaultIdentityClient } from '@backstage/plugin-auth-node';
import { createRouter } from '@internal/plugin-todo-list-backend';
import { Router } from 'express';
import { PluginEnvironment } from '../types';

export default async function createPlugin({
logger,
discovery,
permissions,
}: PluginEnvironment): Promise<Router> {
return await createRouter({
logger,
identity: DefaultIdentityClient.create({
discovery,
issuer: await discovery.getExternalBaseUrl('auth'),
}),
permissions,
});
}

现在,您的插件已配置完成。 让我们尝试通过拒绝权限来测试逻辑。

测试授权创建端点

在运行此步骤之前,请确保您已按照以下步骤进行了操作开始节。

为了测试上述逻辑,您的Backstage实例的集成者需要更改其权限策略,返回DENY为我们新创建的权限:

packages/backend/src/plugins/permission.ts
import {
BackstageIdentityResponse,
} from '@backstage/plugin-auth-node';
import {
PermissionPolicy,
PolicyQuery,
} from '@backstage/plugin-permission-node';
import { isPermission } from '@backstage/plugin-permission-common';
import { todoListCreatePermission } from '@internal/plugin-todo-list-common';

class TestPermissionPolicy implements PermissionPolicy {
async handle(): Promise<PolicyDecision> {
async handle(
request: PolicyQuery,
_user?: BackstageIdentityResponse,
): Promise<PolicyDecision> {
if (isPermission(request.permission, todoListCreatePermission)) {
return {
result: AuthorizeResult.DENY,
};
}

return {
result: AuthorizeResult.ALLOW,
};
}

现在,只要您尝试创建新的 Todo 项目,前台就会显示错误。

让我们把结果翻回到ALLOW然后才继续前进。

if (isPermission(request.permission, todoListCreatePermission)) {
return {
result: AuthorizeResult.DENY,
result: AuthorizeResult.ALLOW,
};
}

此时一切正常,但如果运行yarn tsc你会遇到一些错误,让我们来解决这些问题。

首先,我们将清理plugins/todo-list-backend/src/service/router.test.ts:

plugins/todo-list-backend/src/service/router.test.ts
import { getVoidLogger } from '@backstage/backend-common';
import { DefaultIdentityClient } from '@backstage/plugin-auth-node';
import { PermissionEvaluator } from '@backstage/plugin-permission-common';
import express from 'express';
import request from 'supertest';

import { createRouter } from './router';

const mockedAuthorize: jest.MockedFunction<PermissionEvaluator['authorize']> =
jest.fn();
const mockedPermissionQuery: jest.MockedFunction<
PermissionEvaluator['authorizeConditional']
> = jest.fn();

const permissionEvaluator: PermissionEvaluator = {
authorize: mockedAuthorize,
authorizeConditional: mockedPermissionQuery,
};

describe('createRouter', () => {
let app: express.Express;

beforeAll(async () => {
const router = await createRouter({
logger: getVoidLogger(),
identity: {} as DefaultIdentityClient,
permissions: permissionEvaluator,
});
app = express().use(router);
});

beforeEach(() => {
jest.resetAllMocks();
});

describe('GET /health', () => {
it('returns ok', async () => {
const response = await request(app).get('/health');

expect(response.status).toEqual(200);
expect(response.body).toEqual({ status: 'ok' });
});
});
});

然后,我们要更新plugins/todo-list-backend/src/service/standaloneServer.ts:

plugins/todo-list-backend/src/service/standaloneServer.ts
import {
createServiceBuilder,
loadBackendConfig,
SingleHostDiscovery,
ServerTokenManager,
} from '@backstage/backend-common';
import { DefaultIdentityClient } from '@backstage/plugin-auth-node';
import { ServerPermissionClient } from '@backstage/plugin-permission-node';
import { Server } from 'http';
import { Logger } from 'winston';
import { createRouter } from './router';

export interface ServerOptions {
port: number;
enableCors: boolean;
logger: Logger;
}

export async function startStandaloneServer(
options: ServerOptions,
): Promise<Server> {
const logger = options.logger.child({ service: 'todo-list-backend' });
logger.debug('Starting application server...');
const config = await loadBackendConfig({ logger, argv: process.argv });
const discovery = SingleHostDiscovery.fromConfig(config);
const tokenManager = ServerTokenManager.fromConfig(config, {
logger,
});
const permissions = ServerPermissionClient.fromConfig(config, {
discovery,
tokenManager,
});
const router = await createRouter({
logger,
identity: DefaultIdentityClient.create({
discovery,
issuer: await discovery.getExternalBaseUrl('auth'),
}),
permissions,
});

let service = createServiceBuilder(module)
.setPort(options.port)
.addRouter('/todo-list', router);
if (options.enableCors) {
service = service.enableCors({ origin: 'http://localhost:3000' });
}

return await service.start().catch(err => {
logger.error(err);
process.exit(1);
});
}

module.hot?.accept();

最后,我们需要更新plugins/todo-list-backend/src/plugin.ts:

plugins/todo-list-backend/src/plugin.ts
import { loggerToWinstonLogger } from '@backstage/backend-common';
import {
coreServices,
createBackendPlugin,
} from '@backstage/backend-plugin-api';
import { createRouter } from './service/router';

/**
* The example TODO list backend plugin.
*
* @public
*/
export const exampleTodoListPlugin = createBackendPlugin({
pluginId: 'exampleTodoList',
register(env) {
env.registerInit({
deps: {
identity: coreServices.identity,
logger: coreServices.logger,
httpRouter: coreServices.httpRouter,
permissions: coreServices.permissions,
},
async init({ identity, logger, httpRouter }) {
async init({ identity, logger, httpRouter, permissions }) {
httpRouter.use(
await createRouter({
identity,
logger: loggerToWinstonLogger(logger),
permissions,
}),
);
},
});
},
});

现在,当您运行yarn tsc就不会再出错了。