diff --git a/.changeset/curvy-icons-peel.md b/.changeset/curvy-icons-peel.md new file mode 100644 index 0000000000..bbadcb0a55 --- /dev/null +++ b/.changeset/curvy-icons-peel.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-badges-backend': patch +--- + +Updating the `authorization` middleware to call the Catalog to check that the requesting user has permission to see the Entity before generating the UUID. diff --git a/plugins/badges-backend/src/service/router-obfuscated.test.ts b/plugins/badges-backend/src/service/router-obfuscated.test.ts index 49d0368c7f..fd4a525234 100644 --- a/plugins/badges-backend/src/service/router-obfuscated.test.ts +++ b/plugins/badges-backend/src/service/router-obfuscated.test.ts @@ -212,20 +212,30 @@ describe('createRouter', () => { }); describe('GET /entity/:namespace/:kind/:name/obfuscated', () => { - catalog.getEntityByRef.mockResolvedValueOnce(entity); - catalog.getEntities.mockResolvedValueOnce({ items: entities }); + beforeEach(() => { + catalog.getEntityByRef = jest.fn().mockResolvedValueOnce(entity); + catalog.getEntities = jest + .fn() + .mockResolvedValueOnce({ items: entities }); + }); + + it('returns obfuscated 404 if the user token does not return a catalog entity', async () => { + catalog.getEntityByRef = jest.fn().mockResolvedValue(undefined); - it('returns obfuscated 401 if no auth', async () => { const obfuscatedEntity = await request(app).get( '/entity/default/component/test/obfuscated', ); - expect(obfuscatedEntity.status).toEqual(401); + + expect(obfuscatedEntity.status).toEqual(404); }); it('returns obfuscated entity and badges', async () => { + catalog.getEntityByRef = jest.fn().mockResolvedValue(entity); + const obfuscatedEntity = await request(app) .get('/entity/default/component/test/obfuscated') .set('Authorization', 'Bearer fakeToken'); + expect(obfuscatedEntity.status).toEqual(200); expect(obfuscatedEntity.body.uuid).toMatch( new RegExp( @@ -233,6 +243,11 @@ describe('createRouter', () => { ), ); + expect(catalog.getEntityByRef).toHaveBeenCalledWith( + { namespace: 'default', kind: 'component', name: 'test' }, + { token: 'fakeToken' }, + ); + const uuid = obfuscatedEntity.body.uuid; const url = `/entity/${uuid}/test-badge?format=json`; let response = await request(app).get(url); diff --git a/plugins/badges-backend/src/service/router.ts b/plugins/badges-backend/src/service/router.ts index 47bb8fcbe8..5a8fbb538a 100644 --- a/plugins/badges-backend/src/service/router.ts +++ b/plugins/badges-backend/src/service/router.ts @@ -24,7 +24,7 @@ import { } from '@backstage/backend-common'; import { CatalogApi, CatalogClient } from '@backstage/catalog-client'; import { Config } from '@backstage/config'; -import { AuthenticationError, NotFoundError } from '@backstage/errors'; +import { NotFoundError } from '@backstage/errors'; import { BadgeBuilder, DefaultBadgeBuilder } from '../lib/BadgeBuilder'; import { BadgeContext, BadgeFactories } from '../types'; import { isNil } from 'lodash'; @@ -60,7 +60,7 @@ export async function createRouter( ); const router = Router(); - const { config, logger, tokenManager, discovery, identity } = options; + const { config, logger, tokenManager, discovery } = options; const baseUrl = await discovery.getExternalBaseUrl('badges'); if (config.getOptionalBoolean('app.badges.obfuscate')) { @@ -72,7 +72,6 @@ export async function createRouter( logger, options, config, - identity, baseUrl, ); } @@ -94,7 +93,6 @@ async function obfuscatedRoute( logger: Logger, options: RouterOptions, config: Config, - identity: IdentityApi, baseUrl: string, ) { logger.info('Badges obfuscation is enabled'); @@ -130,6 +128,7 @@ async function obfuscatedRoute( }, token, ); + if (isNil(entity)) { throw new NotFoundError( `No ${kind} entity in ${namespace} named "${name}"`, @@ -217,21 +216,30 @@ async function obfuscatedRoute( router.get( '/entity/:namespace/:kind/:name/obfuscated', - function authenticate(req, _res, next) { - const token = - getBearerTokenFromAuthorizationHeader(req.headers.authorization) || - (req.cookies?.token as string | undefined); + async function authenticate(req, _res, next) { + const token = getBearerTokenFromAuthorizationHeader( + req.headers.authorization, + ); - if (!token) { - throw new AuthenticationError('Unauthorized'); - } + const { kind, namespace, name } = req.params; - try { - req.user = identity.getIdentity({ request: req }); + // check that the user has the correct permissions + // to view the catalog entity by forwarding the token + const entity = await catalog.getEntityByRef( + { + kind, + namespace, + name, + }, + { token }, + ); + + if (!entity) { + throw new NotFoundError( + `No ${kind} entity in ${namespace} named "${name}"`, + ); + } else { next(); - } catch (error) { - tokenManager.authenticate(token.toString()); - next(error); } }, async (req, res) => {