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
@@ -0,0 +1 @@
module.exports = require('@backstage/cli/config/eslint-factory')(__dirname);
@@ -0,0 +1,13 @@
# todo-list-common
Welcome to the todo-list-common plugin!
_This plugin was created through the Backstage CLI_
## Getting started
Your plugin has been added to the example app in this repository, meaning you'll be able to access it by running `yarn start` in the root directory, and then navigating to [/todo-list-common](http://localhost:3000/todo-list-common).
You can also serve the plugin in isolation by running `yarn start` in the plugin directory.
This method of serving the plugin provides quicker iteration speed and a faster startup and hot reloads.
It is only meant for local development, and the setup for it can be found inside the [/dev](./dev) directory.
@@ -0,0 +1,46 @@
{
"name": "@internal/plugin-todo-list-common",
"version": "1.0.0",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
"private": true,
"publishConfig": {
"access": "public",
"main": "dist/index.esm.js",
"types": "dist/index.d.ts"
},
"backstage": {
"role": "frontend-plugin"
},
"scripts": {
"start": "backstage-cli package start",
"build": "backstage-cli package build",
"lint": "backstage-cli package lint",
"test": "backstage-cli package test",
"clean": "backstage-cli package clean",
"prepack": "backstage-cli package prepack",
"postpack": "backstage-cli package postpack"
},
"dependencies": {
"@backstage/core-components": "^0.9.3-next.0",
"@backstage/core-plugin-api": "^1.0.0",
"@backstage/plugin-permission-common": "^0.6.0-next.0",
"@backstage/theme": "^0.2.15"
},
"peerDependencies": {
"react": "^16.13.1 || ^17.0.0"
},
"devDependencies": {
"@backstage/cli": "^0.17.0-next.1",
"@backstage/core-app-api": "^1.0.1-next.0",
"@backstage/dev-utils": "^1.0.1-next.0",
"@backstage/test-utils": "^1.0.1-next.1",
"@types/node": "*",
"msw": "^0.35.0",
"cross-fetch": "^3.1.5"
},
"files": [
"dist"
]
}
@@ -0,0 +1,16 @@
/*
* 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.
*/
export * from './permissions';
@@ -0,0 +1,22 @@
/*
* 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 { createPermission } from '@backstage/plugin-permission-common';
export const tempExamplePermission = createPermission({
name: 'temp.example.noop',
attributes: {},
});
+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',
+2
View File
@@ -94,6 +94,7 @@ import { techDocsPage } from './components/techdocs/TechDocsPage';
import { ApacheAirflowPage } from '@backstage/plugin-apache-airflow';
import { PermissionedRoute } from '@backstage/plugin-permission-react';
import { catalogEntityCreatePermission } from '@backstage/plugin-catalog-common';
import { TodoListCommonPage } from 'plugin-todo-list-common';
const app = createApp({
apis,
@@ -233,6 +234,7 @@ const routes = (
</Route>
<Route path="/azure-pull-requests" element={<AzurePullRequestsPage />} />
<Route path="/apache-airflow" element={<ApacheAirflowPage />} />
<Route path="/todo-list-common" element={<TodoListCommonPage />} />
</FlatRoutes>
);