Add todo-list-common

Signed-off-by: Joon Park <joonp@spotify.com>
This commit is contained in:
Joon Park
2022-04-14 16:33:56 +01:00
parent cad2ccf728
commit 56a8d3a603
9 changed files with 153 additions and 45 deletions
+8 -4
View File
@@ -10,30 +10,34 @@ The rest of this page is focused on adding the `todo-list` and `todo-list-backen
## Setup for the Tutorial
We will use a "Todo list" feature, composed of the `todo-list` and `todo-list-backend` plugins.
We will use a "Todo list" feature, composed of the `todo-list` and `todo-list-backend` plugins, as well as their dependency, `todo-list-common`.
The source code is available here:
- [todo-list](https://github.com/backstage/backstage/blob/master/contrib/plugins/todo-list)
- [todo-list-backend](https://github.com/backstage/backstage/blob/master/contrib/plugins/todo-list-backend)
- [todo-list-common](https://github.com/backstage/backstage/blob/master/contrib/plugins/todo-list-common)
1. Copy-paste the two folders into the plugins folder of your backstage application repository or run the following script from the root of your backstage application:
1. Copy-paste the three folders into the plugins folder of your backstage application repository or run the following script from the root of your backstage application:
```bash
$ curl https://codeload.github.com/backstage/backstage/zip/refs/heads/master | \
tar -C plugins --strip-components=1 -xv \
backstage-master/contrib/plugins/todo-list \
backstage-master/contrib/plugins/todo-list-backend
backstage-master/contrib/plugins/todo-list-backend \
backstage-master/contrib/plugins/todo-list-common
```
Your application structure should look something like this:
// TODO: UPDATE THIS IMAGE
![backstage application files structure](../../assets/permission/permission-tutorial-backstage-application-initial-structure.png)
2. add the frontend and backend plugins as dependencies of your Backstage app and backend respectively:
```
$ yarn workspace backend add @internal/plugin-todo-list-backend@^1.0.0
$ yarn workspace backend add @internal/plugin-todo-list-backend@^1.0.0 @internal/plugin-todo-list-common@^1.0.0
$ yarn workspace app add @internal/plugin-todo-list@^1.0.0
```
@@ -10,32 +10,33 @@ For this tutorial, we'll use a basic permission check to authorize the `create`
We'll start by creating a new permission, and then we'll use the permission api to call `authorize` with it during todo creation.
## Setup
## Creating a new permission
Let's navigate to the file `plugins/todo-list-common/src/permissions.ts` and add our first permission:
```diff
import { createPermission } from '@backstage/plugin-permission-common';
- export const tempExamplePermission = createPermission({
- name: 'temp.example.noop',
- attributes: {},
+ export const todoListCreate = createPermission({
+ name: 'todo.list.create',
+ attributes: { action: 'create' },
});
```
For this tutorial, we've automatically exported all permissions from this file (see `plugins/todo-list-common/src/index.ts`). For the actual plugins that you author, we recommend exporting all permissions from your plugin, so that Backstage integrators can import them when writing policies.
## Authorizing using the new permission
Install the following module:
```
$ yarn workspace @internal/plugin-todo-list-backend \
add @backstage/plugin-permission-common
add @backstage/plugin-permission-common @internal/plugin-todo-list-common
```
## Creating a new permission
Let's create a new file `plugins/todo-list-backend/src/service/permissions.ts` with the following content:
```typescript
import { createPermission } from '@backstage/plugin-permission-common';
export const todosListCreate = createPermission({
name: 'todos.list.create',
attributes: { action: 'create' },
});
```
We recommend exporting all permissions from your plugin, so that Backstage integrators can import them when writing policies.
## Authorizing using the new permission
Edit `plugins/todo-list-backend/src/service/router.ts`:
```diff
@@ -44,7 +45,7 @@ Edit `plugins/todo-list-backend/src/service/router.ts`:
- import { InputError } from '@backstage/errors';
+ import { InputError, NotAllowedError } from '@backstage/errors';
+ import { PermissionEvaluator, AuthorizeResult } from '@backstage/plugin-permission-common';
+ import { todosListCreate } from './permissions';
+ import { todoListCreate } from '@internal/plugin-todo-list-common';
...
@@ -69,7 +70,7 @@ Edit `plugins/todo-list-backend/src/service/router.ts`:
const user = token ? await identity.authenticate(token) : undefined;
author = user?.identity.userEntityRef;
+ const decision = (
+ await permissions.authorize([{ permission: todosListCreate }], {
+ await permissions.authorize([{ permission: todoListCreate }], {
+ token,
+ })
+ )[0];
@@ -132,7 +133,7 @@ In order to test the logic above, the integrators of your backstage instance nee
+ PolicyQuery,
} from '@backstage/plugin-permission-node';
+ import { isPermission } from '@backstage/plugin-permission-common';
+ import { todosListCreate } from '@internal/plugin-todo-list-backend';
+ import { todoListCreate } from '@internal/plugin-todo-list-common';
class TestPermissionPolicy implements PermissionPolicy {
- async handle(): Promise<PolicyDecision> {
@@ -140,7 +141,7 @@ In order to test the logic above, the integrators of your backstage instance nee
+ request: PolicyQuery,
+ user?: BackstageIdentityResponse,
+ ): Promise<PolicyDecision> {
+ if (isPermission(request.permission, todosListCreate)) {
+ if (isPermission(request.permission, todoListCreate)) {
+ return {
+ result: AuthorizeResult.DENY,
+ };
@@ -11,34 +11,34 @@ When performing updates (or other operations) on specific [resources](../concept
## Creating the update permission
Let's add a new permission to the file `plugins/todo-list-backend/src/service/permissions.ts` from [the previous section](./02-adding-a-basic-permission-check.md).
Let's add a new permission to the file `plugins/todo-list-common/src/permissions.ts` from [the previous section](./02-adding-a-basic-permission-check.md).
```diff
import { createPermission } from '@backstage/plugin-permission-common';
+ export const TODO_LIST_RESOURCE_TYPE = 'todo-item';
+
export const todosListCreate = createPermission({
name: 'todos.list.create',
export const todoListCreate = createPermission({
name: 'todo.list.create',
attributes: { action: 'create' },
});
+
+ export const todosListUpdate = createPermission({
+ name: 'todos.list.update',
+ export const todoListUpdate = createPermission({
+ name: 'todo.list.update',
+ attributes: { action: 'update' },
+ resourceType: TODO_LIST_RESOURCE_TYPE,
+ });
```
Notice that unlike `todosListCreate`, the `todosListUpdate` permission contains a `resourceType` field. This field indicates to the permission framework that this permission is intended to be authorized in the context of a resource with type `'todo-item'`. You can use whatever string you like as the resource type, as long as you use the same value consistently for each type of resource.
Notice that unlike `todoListCreate`, the `todoListUpdate` permission contains a `resourceType` field. This field indicates to the permission framework that this permission is intended to be authorized in the context of a resource with type `'todo-item'`. You can use whatever string you like as the resource type, as long as you use the same value consistently for each type of resource.
## Setting up authorization for the update permission
To start, let's edit `plugins/todo-list-backend/src/service/router.ts` in the same manner as we did in the previous section:
```diff
- import { todosListCreate } from './permissions';
+ import { todosListCreate, todosListUpdate } from './permissions';
- import { todoListCreate } from '@internal/plugin-todo-list-common';
+ import { todoListCreate, todoListUpdate } from '@internal/plugin-todo-list-common';
...
@@ -52,7 +52,7 @@ To start, let's edit `plugins/todo-list-backend/src/service/router.ts` in the sa
}
+ const decision = (
+ await permissions.authorize(
+ [{ permission: todosListUpdate, resourceRef: req.body.id }],
+ [{ permission: todoListUpdate, resourceRef: req.body.id }],
+ {
+ token,
+ },
@@ -83,6 +83,7 @@ Create a new `plugins/todo-list-backend/src/service/rules.ts` file and append th
```typescript
import { makeCreatePermissionRule } from '@backstage/plugin-permission-node';
import { TODO_LIST_RESOURCE_TYPE } from '@internal/plugin-todo-list-common';
import { Todo, TodoFilter } from './todos';
const createTodoListPermissionRule = makeCreatePermissionRule<
@@ -93,7 +94,7 @@ const createTodoListPermissionRule = makeCreatePermissionRule<
export const isOwner = createTodoListPermissionRule({
name: 'IS_OWNER',
description: 'Should allow only if the todo belongs to the user',
resourceType: 'todo-item',
resourceType: TODO_LIST_RESOURCE_TYPE,
apply: (resource: Todo, userId: string) => {
return resource.author === userId;
},
@@ -122,10 +123,12 @@ Now, let's create the new endpoint by editing `plugins/todo-list-backend/src/ser
- `rules`: an array of all the permission rules you want to support in conditional decisions.
```diff
+ import { createPermissionIntegrationRouter } from '@backstage/plugin-permission-node';
...
- import { add, getAll, update } from './todos';
+ import { add, getAll, getTodo, update } from './todos';
+ import { TODO_LIST_RESOURCE_TYPE } from './permissions';
+ import { createPermissionIntegrationRouter } from '@backstage/plugin-permission-node';
+ import { TODO_LIST_RESOURCE_TYPE } from '@internal/plugin-todo-list-common';
+ import { rules } from './rules';
export async function createRouter(
@@ -165,21 +168,21 @@ Let's edit `packages/backend/src/plugins/permission.ts`:
PolicyQuery,
} from '@backstage/plugin-permission-node';
import { isPermission } from '@backstage/plugin-permission-common';
- import { todosListCreate } from '@internal/plugin-todo-list';
- import { todoListCreate } from '@internal/plugin-todo-list-common';
+ import {
+ todosListCreate,
+ todosListUpdate,
+ todoListCreate,
+ todoListUpdate,
+ TODO_LIST_RESOURCE_TYPE,
+ } from '@internal/plugin-todo-list';
+ } from '@internal/plugin-todo-list-common';
...
if (isPermission(request.permission, todosListCreate)) {
if (isPermission(request.permission, todoListCreate)) {
return {
result: AuthorizeResult.DENY,
};
}
+ if (isPermission(request.permission, todosListUpdate)) {
+ if (isPermission(request.permission, todoListUpdate)) {
+ return {
+ result: AuthorizeResult.CONDITIONAL,
+ pluginId: 'todolist',