@@ -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: {},
|
||||
});
|
||||
@@ -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
|
||||
|
||||

|
||||
|
||||
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',
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user