Merge pull request #16164 from backstage/permissions-tutorial-fixes
docs: miscellaneous permissions plugin tutorial fixes
This commit is contained in:
@@ -48,7 +48,7 @@ The source code is available here:
|
||||
|
||||
Create a new `packages/backend/src/plugins/todolist.ts` with the following content:
|
||||
|
||||
```javascript
|
||||
```typescript
|
||||
import { DefaultIdentityClient } from '@backstage/plugin-auth-node';
|
||||
import { createRouter } from '@internal/plugin-todo-list-backend';
|
||||
import { Router } from 'express';
|
||||
|
||||
@@ -146,7 +146,7 @@ In order to test the logic above, the integrators of your backstage instance nee
|
||||
- async handle(): Promise<PolicyDecision> {
|
||||
+ async handle(
|
||||
+ request: PolicyQuery,
|
||||
+ user?: BackstageIdentityResponse,
|
||||
+ _user?: BackstageIdentityResponse,
|
||||
+ ): Promise<PolicyDecision> {
|
||||
+ if (isPermission(request.permission, todoListCreatePermission)) {
|
||||
+ return {
|
||||
@@ -204,7 +204,7 @@ First we'll clean up the `plugins/todo-list-backend/src/service/router.test.ts`:
|
||||
const router = await createRouter({
|
||||
logger: getVoidLogger(),
|
||||
identity: {} as DefaultIdentityClient,
|
||||
+ permissions: toPermissionEvaluator,
|
||||
+ permissions: permissionEvaluator,
|
||||
});
|
||||
app = express().use(router);
|
||||
});
|
||||
@@ -235,7 +235,7 @@ Then we want to update the `plugins/todo-list-backend/src/service/standaloneServ
|
||||
+ ServerTokenManager,
|
||||
} from '@backstage/backend-common';
|
||||
import { DefaultIdentityClient } from '@backstage/plugin-auth-node';
|
||||
import { ServerPermissionClient } from '@backstage/plugin-permission-node';
|
||||
+ import { ServerPermissionClient } from '@backstage/plugin-permission-node';
|
||||
import { Server } from 'http';
|
||||
import { Logger } from 'winston';
|
||||
import { createRouter } from './router';
|
||||
@@ -285,4 +285,44 @@ Then we want to update the `plugins/todo-list-backend/src/service/standaloneServ
|
||||
module.hot?.accept();
|
||||
```
|
||||
|
||||
Finally, we need to update `plugins/todo-list-backend/src/plugin.ts`:
|
||||
|
||||
```diff
|
||||
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.
|
||||
*
|
||||
* @alpha
|
||||
*/
|
||||
export const exampleTodoListPlugin = createBackendPlugin({
|
||||
id: '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,
|
||||
}),
|
||||
);
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
Now when you run `yarn tsc` you should have no more errors.
|
||||
|
||||
@@ -76,7 +76,7 @@ This enables decisions based on characteristics of the resource, but it's import
|
||||
Install the missing module:
|
||||
|
||||
```
|
||||
$ yarn workspace @internal/plugin-todo-list-backend add @backstage/plugin-permission-node
|
||||
$ yarn workspace @internal/plugin-todo-list-backend add @backstage/plugin-permission-node zod
|
||||
```
|
||||
|
||||
Create a new `plugins/todo-list-backend/src/service/rules.ts` file and append the following code:
|
||||
@@ -89,7 +89,8 @@ import { Todo, TodoFilter } from './todos';
|
||||
|
||||
export const createTodoListPermissionRule = makeCreatePermissionRule<
|
||||
Todo,
|
||||
TodoFilter
|
||||
TodoFilter,
|
||||
typeof TODO_LIST_RESOURCE_TYPE
|
||||
>();
|
||||
|
||||
export const isOwner = createTodoListPermissionRule({
|
||||
@@ -97,8 +98,8 @@ export const isOwner = createTodoListPermissionRule({
|
||||
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')
|
||||
})
|
||||
userId: z.string().describe('User ID to match on the resource'),
|
||||
}),
|
||||
apply: (resource: Todo, { userId }) => {
|
||||
return resource.author === userId;
|
||||
},
|
||||
@@ -187,6 +188,7 @@ Make sure `todoListConditions` and `createTodoListConditionalDecision` are expor
|
||||
```diff
|
||||
export * from './service/router';
|
||||
+ export * from './conditionExports';
|
||||
export { exampleTodoListPlugin } from './plugin';
|
||||
```
|
||||
|
||||
## Test the authorized update endpoint
|
||||
@@ -209,7 +211,6 @@ Let's go back to the permission policy's handle function and try to authorize ou
|
||||
+ import {
|
||||
+ todoListCreatePermission,
|
||||
+ todoListUpdatePermission,
|
||||
+ TODO_LIST_RESOURCE_TYPE,
|
||||
+ } from '@internal/plugin-todo-list-common';
|
||||
+ import {
|
||||
+ todoListConditions,
|
||||
@@ -217,7 +218,11 @@ Let's go back to the permission policy's handle function and try to authorize ou
|
||||
+ } 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,
|
||||
@@ -236,6 +241,7 @@ Let's go back to the permission policy's handle function and try to authorize ou
|
||||
return {
|
||||
result: AuthorizeResult.ALLOW,
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
For any incoming update requests, we now return a _Conditional Decision_. We are saying:
|
||||
|
||||
@@ -15,7 +15,7 @@ One possible solution may leverage the batching functionality to authorize all o
|
||||
- res.json(getAll())
|
||||
+ const items = getAll();
|
||||
+ const decisions = await permissions.authorize(
|
||||
+ items.map(({ id }) => ({ permission: todosListRead, resourceRef: id })),
|
||||
+ items.map(({ id }) => ({ permission: todoListReadPermission, resourceRef: id })),
|
||||
+ );
|
||||
|
||||
+ const filteredItems = decisions.filter(
|
||||
@@ -53,7 +53,7 @@ Let's add another permission to the plugin.
|
||||
resourceType: TODO_LIST_RESOURCE_TYPE,
|
||||
});
|
||||
+
|
||||
+ export const todosListRead = createPermission({
|
||||
+ export const todoListReadPermission = createPermission({
|
||||
+ name: 'todos.list.read',
|
||||
+ attributes: { action: 'read' },
|
||||
+ resourceType: TODO_LIST_RESOURCE_TYPE,
|
||||
@@ -81,10 +81,9 @@ So far we've only used the `PermissionEvaluator.authorize` method, which will ev
|
||||
import {
|
||||
todosListCreate,
|
||||
todosListUpdate,
|
||||
+ todosListRead,
|
||||
+ todoListReadPermission,
|
||||
TODO_LIST_RESOURCE_TYPE,
|
||||
} from './permissions';
|
||||
+ import { rules } from './rules';
|
||||
|
||||
+ const transformConditions: ConditionTransformer<TodoFilter> = createConditionTransformer(Object.values(rules));
|
||||
|
||||
@@ -95,7 +94,7 @@ So far we've only used the `PermissionEvaluator.authorize` method, which will ev
|
||||
+ );
|
||||
+
|
||||
+ const decision = (
|
||||
+ await permissions.authorizeConditional([{ permission: todosListRead }], {
|
||||
+ await permissions.authorizeConditional([{ permission: todoListReadPermission }], {
|
||||
+ token,
|
||||
+ })
|
||||
+ )[0];
|
||||
@@ -110,7 +109,6 @@ So far we've only used the `PermissionEvaluator.authorize` method, which will ev
|
||||
+ } else {
|
||||
+ res.json(getAll());
|
||||
+ }
|
||||
+ }
|
||||
- res.json(getAll());
|
||||
});
|
||||
```
|
||||
@@ -121,7 +119,7 @@ Since `TodoFilter` used in our plugin matches the structure of the conditions ob
|
||||
|
||||
## Test the authorized read endpoint
|
||||
|
||||
Let's update our permission policy to return a conditional result whenever a `todosListRead` permission is received. In this case, we can reuse the decision returned for the `todosListCreate` permission.
|
||||
Let's update our permission policy to return a conditional result whenever a `todoListReadPermission` permission is received. In this case, we can reuse the decision returned for the `todosListCreate` permission.
|
||||
|
||||
```diff
|
||||
// packages/backend/src/plugins/permission.ts
|
||||
@@ -132,7 +130,6 @@ import {
|
||||
todoListCreatePermission,
|
||||
todoListUpdatePermission,
|
||||
+ todoListReadPermission,
|
||||
TODO_LIST_RESOURCE_TYPE,
|
||||
} from '@internal/plugin-todo-list-common';
|
||||
|
||||
...
|
||||
|
||||
@@ -56,7 +56,11 @@ Let's make the following changes in `plugins/todo-list/src/components/TodoListPa
|
||||
- Add
|
||||
- </Button>
|
||||
+ {!loadingPermission && (
|
||||
+ <Button disabled={!canAddTodo} variant="contained" onClick={handleAdd}>
|
||||
+ <Button
|
||||
+ disabled={!canAddTodo}
|
||||
+ variant="contained"
|
||||
+ onClick={() => onAdd(title.current)}
|
||||
+ >
|
||||
+ Add
|
||||
+ </Button>
|
||||
+ )}
|
||||
@@ -118,7 +122,10 @@ Providing a disabled state can be a helpful signal to users, but there may be ca
|
||||
- <Grid item>
|
||||
- <AddTodo onAdd={handleAdd} />
|
||||
- </Grid>
|
||||
+ <RequirePermission permission={todoListCreatePermission} errorPage={<></>}>
|
||||
+ <RequirePermission
|
||||
+ permission={todoListCreatePermission}
|
||||
+ errorPage={<></>}
|
||||
+ >
|
||||
+ <Grid item>
|
||||
+ <AddTodo onAdd={handleAdd} />
|
||||
+ </Grid>
|
||||
@@ -149,11 +156,15 @@ Providing a disabled state can be a helpful signal to users, but there may be ca
|
||||
onChange={e => (title.current = e.target.value)}
|
||||
/>
|
||||
- {!loadingPermission && (
|
||||
- <Button disabled={!canAddTodo} variant="contained" onClick={handleAdd}>
|
||||
- <Button
|
||||
- disabled={!canAddTodo}
|
||||
- variant="contained"
|
||||
- onClick={() => onAdd(title.current)}
|
||||
- >
|
||||
- Add
|
||||
- </Button>
|
||||
- )}
|
||||
+ <Button variant="contained" onClick={handleAdd}>
|
||||
+ <Button variant="contained" onClick={() => onAdd(title.current)}>
|
||||
+ Add
|
||||
+ </Button>
|
||||
</Box>
|
||||
|
||||
Reference in New Issue
Block a user