Updates to the To Do List permission tutorial
Signed-off-by: Andre Wanlin <67169551+awanlin@users.noreply.github.com>
This commit is contained in:
@@ -53,7 +53,6 @@ $ yarn workspace backend add @backstage/plugin-permission-backend
|
||||
2. Add the following to a new file, `packages/backend/src/plugins/permission.ts`. This adds the permission-backend router, and configures it with a policy which allows everything.
|
||||
|
||||
```typescript
|
||||
import { IdentityClient } from '@backstage/plugin-auth-node';
|
||||
import { createRouter } from '@backstage/plugin-permission-backend';
|
||||
import {
|
||||
AuthorizeResult,
|
||||
@@ -119,7 +118,6 @@ permission:
|
||||
2. Update the PermissionPolicy in `packages/backend/src/plugins/permission.ts` to disable a permission that’s easy for us to test. This policy rejects any attempt to delete a catalog entity:
|
||||
|
||||
```diff
|
||||
import { IdentityClient } from '@backstage/plugin-auth-node';
|
||||
import { createRouter } from '@backstage/plugin-permission-backend';
|
||||
import {
|
||||
AuthorizeResult,
|
||||
|
||||
@@ -48,7 +48,9 @@ Edit `plugins/todo-list-backend/src/service/router.ts`:
|
||||
...
|
||||
|
||||
- import { InputError } from '@backstage/errors';
|
||||
- import { IdentityApi } from '@backstage/plugin-auth-node';
|
||||
+ import { InputError, NotAllowedError } from '@backstage/errors';
|
||||
+ import { getBearerTokenFromAuthorizationHeader, IdentityApi } from '@backstage/plugin-auth-node';
|
||||
+ import { PermissionEvaluator, AuthorizeResult } from '@backstage/plugin-permission-common';
|
||||
+ import { todoListCreatePermission } from '@internal/plugin-todo-list-common';
|
||||
|
||||
@@ -56,7 +58,7 @@ Edit `plugins/todo-list-backend/src/service/router.ts`:
|
||||
|
||||
export interface RouterOptions {
|
||||
logger: Logger;
|
||||
identity: IdentityClient;
|
||||
identity: IdentityApi;
|
||||
+ permissions: PermissionEvaluator;
|
||||
}
|
||||
|
||||
@@ -69,11 +71,13 @@ Edit `plugins/todo-list-backend/src/service/router.ts`:
|
||||
...
|
||||
|
||||
router.post('/todos', async (req, res) => {
|
||||
const token = IdentityClient.getBearerToken(req.header('authorization'));
|
||||
let author: string | undefined = undefined;
|
||||
|
||||
const user = token ? await identity.authenticate(token) : undefined;
|
||||
const user = await identity.authenticate(token) : undefined;
|
||||
author = user?.identity.userEntityRef;
|
||||
+ const token = getBearerTokenFromAuthorizationHeader(
|
||||
+ req.header('authorization'),
|
||||
+ );
|
||||
+ const decision = (
|
||||
+ await permissions.authorize([{ permission: todoListCreatePermission }], {
|
||||
+ token,
|
||||
@@ -128,10 +132,8 @@ In order to test the logic above, the integrators of your backstage instance nee
|
||||
```diff
|
||||
// packages/backend/src/plugins/permission.ts
|
||||
|
||||
- import { IdentityClient } from '@backstage/plugin-auth-node';
|
||||
+ import {
|
||||
+ BackstageIdentityResponse,
|
||||
+ IdentityClient
|
||||
+ } from '@backstage/plugin-auth-node';
|
||||
import {
|
||||
PermissionPolicy,
|
||||
@@ -170,3 +172,117 @@ Let's flip the result back to `ALLOW` before moving on.
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
At this point everything is working but if you run `yarn tsc` you'll get some errors, let's fix those up.
|
||||
|
||||
First we'll clean up the `plugins/todo-list-backend/src/service/router.test.ts`:
|
||||
|
||||
```diff
|
||||
import { getVoidLogger } from '@backstage/backend-common';
|
||||
import { DefaultIdentityClient } from '@backstage/plugin-auth-node';
|
||||
+ import { PermissionEvaluator } from '@backstage/plugin-permission-common';
|
||||
import express from 'express';
|
||||
import request from 'supertest';
|
||||
|
||||
import { createRouter } from './router';
|
||||
|
||||
+ const mockedAuthorize: jest.MockedFunction<PermissionEvaluator['authorize']> =
|
||||
+ jest.fn();
|
||||
+ const mockedPermissionQuery: jest.MockedFunction<
|
||||
+ PermissionEvaluator['authorizeConditional']
|
||||
+> = jest.fn();
|
||||
|
||||
+ const permissionEvaluator: PermissionEvaluator = {
|
||||
+ authorize: mockedAuthorize,
|
||||
+ authorizeConditional: mockedPermissionQuery,
|
||||
+};
|
||||
|
||||
describe('createRouter', () => {
|
||||
let app: express.Express;
|
||||
|
||||
beforeAll(async () => {
|
||||
const router = await createRouter({
|
||||
logger: getVoidLogger(),
|
||||
identity: {} as DefaultIdentityClient,
|
||||
+ permissions: toPermissionEvaluator,
|
||||
});
|
||||
app = express().use(router);
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
jest.resetAllMocks();
|
||||
});
|
||||
|
||||
describe('GET /health', () => {
|
||||
it('returns ok', async () => {
|
||||
const response = await request(app).get('/health');
|
||||
|
||||
expect(response.status).toEqual(200);
|
||||
expect(response.body).toEqual({ status: 'ok' });
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
```
|
||||
|
||||
Then we want to update the `plugins/todo-list-backend/src/service/standaloneServer.ts`, first we need to add the `@backstage/plugin-permission-node` package to `plugins/todo-list-backend/package.json` and then we can make the following edits:
|
||||
|
||||
```diff
|
||||
import {
|
||||
createServiceBuilder,
|
||||
loadBackendConfig,
|
||||
SingleHostDiscovery,
|
||||
+ ServerTokenManager,
|
||||
} from '@backstage/backend-common';
|
||||
import { DefaultIdentityClient } from '@backstage/plugin-auth-node';
|
||||
import { ServerPermissionClient } from '@backstage/plugin-permission-node';
|
||||
import { Server } from 'http';
|
||||
import { Logger } from 'winston';
|
||||
import { createRouter } from './router';
|
||||
|
||||
export interface ServerOptions {
|
||||
port: number;
|
||||
enableCors: boolean;
|
||||
logger: Logger;
|
||||
}
|
||||
|
||||
export async function startStandaloneServer(
|
||||
options: ServerOptions,
|
||||
): Promise<Server> {
|
||||
const logger = options.logger.child({ service: 'todo-list-backend' });
|
||||
logger.debug('Starting application server...');
|
||||
const config = await loadBackendConfig({ logger, argv: process.argv });
|
||||
const discovery = SingleHostDiscovery.fromConfig(config);
|
||||
+ const tokenManager = ServerTokenManager.fromConfig(config, {
|
||||
+ logger,
|
||||
+ });
|
||||
+ const permissions = ServerPermissionClient.fromConfig(config, {
|
||||
+ discovery,
|
||||
+ tokenManager,
|
||||
+ });
|
||||
const router = await createRouter({
|
||||
logger,
|
||||
identity: DefaultIdentityClient.create({
|
||||
discovery,
|
||||
issuer: await discovery.getExternalBaseUrl('auth'),
|
||||
}),
|
||||
+ permissions,
|
||||
});
|
||||
|
||||
let service = createServiceBuilder(module)
|
||||
.setPort(options.port)
|
||||
.addRouter('/todo-list', router);
|
||||
if (options.enableCors) {
|
||||
service = service.enableCors({ origin: 'http://localhost:3000' });
|
||||
}
|
||||
|
||||
return await service.start().catch(err => {
|
||||
logger.error(err);
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
||||
|
||||
module.hot?.accept();
|
||||
```
|
||||
|
||||
Now when you run `yarn tsc` you should have no more errors.
|
||||
|
||||
@@ -118,7 +118,7 @@ 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}>
|
||||
+ <RequirePermission permission={todoListCreatePermission} errorPage={<></>}>
|
||||
+ <Grid item>
|
||||
+ <AddTodo onAdd={handleAdd} />
|
||||
+ </Grid>
|
||||
@@ -165,3 +165,25 @@ Providing a disabled state can be a helpful signal to users, but there may be ca
|
||||
```
|
||||
|
||||
Now you should find that the component for adding a todo list item does not render at all. Success!
|
||||
|
||||
You can also use `RequirePermission` to prevent access to routes as well, here's how that would look in your `packages/app/src/App.tsx`:
|
||||
|
||||
```diff
|
||||
+ import { RequirePermission } from '@backstage/plugin-permission-react';
|
||||
+ import { todoListCreatePermission } from '@internal/plugin-todo-list-common';
|
||||
|
||||
...
|
||||
|
||||
<Route path="/search" element={<SearchPage />}>
|
||||
{searchPage}
|
||||
</Route>
|
||||
<Route path="/settings" element={<UserSettingsPage />} />
|
||||
+ <Route path="/todo-list" element={
|
||||
// You might want to create a "read" permission for this, we are just using this one as an example
|
||||
+ <RequirePermission permission={todoListCreatePermission}>
|
||||
+ <TodoListPage />} />
|
||||
+ </RequirePermission>
|
||||
</FlatRoutes>
|
||||
```
|
||||
|
||||
Now if you try to navigate to `https://localhost:3000/todo-list` you'll get and error page if you do not have permission.
|
||||
|
||||
Reference in New Issue
Block a user