diff --git a/docs/permissions/getting-started.md b/docs/permissions/getting-started.md index 6e6bcd2d03..d2c467ff08 100644 --- a/docs/permissions/getting-started.md +++ b/docs/permissions/getting-started.md @@ -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, diff --git a/docs/permissions/plugin-authors/02-adding-a-basic-permission-check.md b/docs/permissions/plugin-authors/02-adding-a-basic-permission-check.md index 124605cb66..241b175987 100644 --- a/docs/permissions/plugin-authors/02-adding-a-basic-permission-check.md +++ b/docs/permissions/plugin-authors/02-adding-a-basic-permission-check.md @@ -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.getIdentity({ request: req }); 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 = ++ 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 { + 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. diff --git a/docs/permissions/plugin-authors/05-frontend-authorization.md b/docs/permissions/plugin-authors/05-frontend-authorization.md index dc4df7839c..d494ea60ba 100644 --- a/docs/permissions/plugin-authors/05-frontend-authorization.md +++ b/docs/permissions/plugin-authors/05-frontend-authorization.md @@ -118,7 +118,7 @@ Providing a disabled state can be a helpful signal to users, but there may be ca - - - -+ ++ }> + + + @@ -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'; + +... + + }> + {searchPage} + + } /> ++ ++ ++ + +``` + +Now if you try to navigate to `https://localhost:3000/todo-list` you'll get and error page if you do not have permission.