Skip to main content

3 添加资源权限检查

在对特定的资源权限框架允许根据资源本身的特征做出决定,这意味着可以编写策略,例如允许拥有资源的用户进行操作,否则拒绝操作。

创建更新权限

让我们为文件添加一个新权限plugins/todo-list-common/src/permissions.ts前一节.

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

export const TODO_LIST_RESOURCE_TYPE = 'todo-item';

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

export const todoListUpdatePermission = createPermission({
name: 'todo.list.update',
attributes: { action: 'update' },
resourceType: TODO_LIST_RESOURCE_TYPE,
});

export const todoListPermissions = [todoListCreatePermission];
export const todoListPermissions = [
todoListCreatePermission,
todoListUpdatePermission,
];

请注意,与todoListCreatePermission,"......todoListUpdatePermission权限包含一个resourceType该字段向权限框架表明,该权限是在资源类型为'todo-item'您可以使用任何您喜欢的字符串作为资源类型,只要您对每种类型的资源都使用相同的值。

为更新权限设置授权

首先,让我们编辑plugins/todo-list-backend/src/service/router.ts的方法与上一节相同:

plugins/todo-list-backend/src/service/router.ts
import { todoListCreatePermission } from '@internal/plugin-todo-list-common';
import {
todoListCreatePermission,
todoListUpdatePermission,
} from '@internal/plugin-todo-list-common';

// ...

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

// ...

router.put('/todos', async (req, res) => {
const token = getBearerTokenFromAuthorizationHeader(
req.header('authorization'),
);

if (!isTodoUpdateRequest(req.body)) {
throw new InputError('Invalid payload');
}
const decision = (
await permissions.authorize(
[{ permission: todoListUpdatePermission, resourceRef: req.body.id }],
{
token,
},
)
)[0];

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

res.json(update(req.body));
});

**重要:**请注意,我们传递了一个额外的resourceRef字段,用id的值。

这样就可以根据资源的特征做出决定,但需要注意的是,策略作者将无法访问其权限策略中的资源 ref。 相反,策略将返回条件决定,我们现在需要在插件中支持这一点。

添加对条件决定的支持

安装缺失的模块:

$ yarn workspace @internal/plugin-todo-list-backend add zod

创建一个新的plugins/todo-list-backend/src/service/rules.ts文件,并添加以下代码:

plugins/todo-list-backend/src/service/rules.ts
import { makeCreatePermissionRule } from '@backstage/plugin-permission-node';
import { TODO_LIST_RESOURCE_TYPE } from '@internal/plugin-todo-list-common';
import { z } from 'zod';
import { Todo, TodoFilter } from './todos';

export const createTodoListPermissionRule = makeCreatePermissionRule<
Todo,
TodoFilter,
typeof TODO_LIST_RESOURCE_TYPE
>();

export const isOwner = createTodoListPermissionRule({
name: 'IS_OWNER',
description: 'Should allow only if the todo belongs to the user',
resourceType: TODO_LIST_RESOURCE_TYPE,
paramsSchema: z.object({
userId: z.string().describe('User ID to match on the resource'),
}),
apply: (resource: Todo, { userId }) => {
return resource.author === userId;
},
toQuery: ({ userId }) => {
return {
property: 'author',
values: [userId],
};
},
});

export const rules = { isOwner };

makeCreatePermissionRule是一个辅助工具,用于确保为该插件创建的规则使用一致的资源和查询类型。

注意:要支持由 backstage 集成者定义的自定义规则,必须从后端(backend)软件包中导出 createTodoListPermissionRule 并提供某种方式让自定义规则在后端启动前传入,很可能是通过 createRouter 来实现。

我们创建了一个新的isOwner规则,权限框架将自动使用该规则,每当返回的条件响应是对授权请求的响应,并附有resourceRef具体而言apply函数用于了解传递的资源是否应被授权。

让我们跳过toQuery我们将在下一节再讨论这个问题。

现在,让我们通过编辑plugins/todo-list-backend/src/service/router.ts这使用了createPermissionIntegrationRouter助手,将权限框架所需的 API 添加到您的插件中。 您需要提供

  • getResources: 一个函数,接受与传给 authorize 的格式相同的 resourceRefs 数组,并返回相应资源的数组。 resourceType: 与上述权限规则中使用的值相同。 permissions: 您的插件接受的权限列表。 rules: 您希望在条件决定中支持的所有权限规则的数组。
plugins/todo-list-backend/src/service/router.ts
// ...
import {
TODO_LIST_RESOURCE_TYPE,
todoListCreatePermission,
todoListUpdatePermission,
} from '@internal/plugin-todo-list-common';
import { add, getAll, update } from './todos';
import { add, getAll, getTodo, update } from './todos';
import { rules } from './rules';

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

const permissionIntegrationRouter = createPermissionIntegrationRouter({
permissions: [todoListCreatePermission, todoListUpdatePermission],
getResources: async resourceRefs => {
return resourceRefs.map(getTodo);
},
resourceType: TODO_LIST_RESOURCE_TYPE,
rules: Object.values(rules),
});

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

// ...
}

为政策制定者提供实用工具

现在我们有了新的资源类型和相应的规则,我们需要导出一些实用程序,供策略作者引用。

创建一个新的plugins/todo-list-backend/src/conditionExports.ts文件,并添加以下代码:

plugins/todo-list-backend/src/conditionExports.ts
import { TODO_LIST_RESOURCE_TYPE } from '@internal/plugin-todo-list-common';
import { createConditionExports } from '@backstage/plugin-permission-node';
import { rules } from './service/rules';

const { conditions, createConditionalDecision } = createConditionExports({
pluginId: 'todolist',
resourceType: TODO_LIST_RESOURCE_TYPE,
rules,
});

export const todoListConditions = conditions;

export const createTodoListConditionalDecision = createConditionalDecision;

确保todoListConditionscreateTodoListConditionalDecisiontodo-list-backend通过编辑plugins/todo-list-backend/src/index.ts:

plugins/todo-list-backend/src/index.ts
export * from './service/router';
export * from './conditionExports';
export { exampleTodoListPlugin } from './plugin';

测试授权更新端点

让我们回到权限策略的处理函数,尝试用一个isOwner状况

packages/backend/src/plugins/permission.ts
import {
BackstageIdentityResponse,
IdentityClient
} 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';
import {
todoListCreatePermission,
todoListUpdatePermission,
} from '@internal/plugin-todo-list-common';
import {
todoListConditions,
createTodoListConditionalDecision,
} from '@internal/plugin-todo-list-backend';

async handle(
request: PolicyQuery,
_user?: BackstageIdentityResponse,
user?: BackstageIdentityResponse,
): Promise<PolicyDecision> {
if (isPermission(request.permission, todoListCreatePermission)) {
return {
result: AuthorizeResult.ALLOW,
};
}
if (isPermission(request.permission, todoListUpdatePermission)) {
return createTodoListConditionalDecision(
request.permission,
todoListConditions.isOwner({
userId: user?.identity.userEntityRef ?? '',
}),
);
}

return {
result: AuthorizeResult.ALLOW,
};
}

对于传入的更新请求,我们现在会返回一个我们在说

嘿,权限框架,我一个人做不了决定。 请转到 id 为 todolist 的插件,要求它应用这些条件。

为了检查一切是否按预期运行,每当您尝试编辑不是由您创建的项目时,您都会在用户界面上看到一个错误。 成功!