diff --git a/.changeset/two-snails-fry.md b/.changeset/two-snails-fry.md new file mode 100644 index 0000000000..979134f98e --- /dev/null +++ b/.changeset/two-snails-fry.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-badges-backend': patch +--- + +Migrated to support new auth services. diff --git a/plugins/badges-backend/api-report.md b/plugins/badges-backend/api-report.md index ab5dbd9720..fb0c71dd75 100644 --- a/plugins/badges-backend/api-report.md +++ b/plugins/badges-backend/api-report.md @@ -3,11 +3,13 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts +import { AuthService } from '@backstage/backend-plugin-api'; import { BackendFeature } from '@backstage/backend-plugin-api'; import { CatalogApi } from '@backstage/catalog-client'; import { Config } from '@backstage/config'; import { Entity } from '@backstage/catalog-model'; import express from 'express'; +import { HttpAuthService } from '@backstage/backend-plugin-api'; import { IdentityApi } from '@backstage/plugin-auth-node'; import { Logger } from 'winston'; import { PluginEndpointDiscovery } from '@backstage/backend-common'; @@ -131,6 +133,8 @@ export class DefaultBadgeBuilder implements BadgeBuilder { // @public (undocumented) export interface RouterOptions { + // (undocumented) + auth?: AuthService; // (undocumented) badgeBuilder?: BadgeBuilder; // (undocumented) @@ -144,6 +148,8 @@ export interface RouterOptions { // (undocumented) discovery: PluginEndpointDiscovery; // (undocumented) + httpAuth?: HttpAuthService; + // (undocumented) identity: IdentityApi; // (undocumented) logger: Logger; diff --git a/plugins/badges-backend/src/plugin.ts b/plugins/badges-backend/src/plugin.ts index e5a51d763f..be965ae4a6 100644 --- a/plugins/badges-backend/src/plugin.ts +++ b/plugins/badges-backend/src/plugin.ts @@ -38,6 +38,8 @@ export const badgesPlugin = createBackendPlugin({ tokenManager: coreServices.tokenManager, identity: coreServices.identity, httpRouter: coreServices.httpRouter, + httpAuth: coreServices.httpAuth, + auth: coreServices.auth, }, async init({ config, @@ -46,6 +48,8 @@ export const badgesPlugin = createBackendPlugin({ tokenManager, identity, httpRouter, + httpAuth, + auth, }) { httpRouter.use( await createRouter({ @@ -55,6 +59,8 @@ export const badgesPlugin = createBackendPlugin({ discovery, tokenManager, identity, + httpAuth, + auth, }), ); }, diff --git a/plugins/badges-backend/src/service/router-obfuscated.test.ts b/plugins/badges-backend/src/service/router-obfuscated.test.ts index fd4a525234..10029dddd9 100644 --- a/plugins/badges-backend/src/service/router-obfuscated.test.ts +++ b/plugins/badges-backend/src/service/router-obfuscated.test.ts @@ -32,6 +32,7 @@ import { IdentityApiGetIdentityRequest, } from '@backstage/plugin-auth-node'; import { BadgesStore } from '../database/badgesStore'; +import { mockCredentials, mockServices } from '@backstage/backend-test-utils'; describe('createRouter', () => { let app: express.Express; @@ -148,6 +149,8 @@ describe('createRouter', () => { tokenManager, logger: getVoidLogger(), identity: { getIdentity }, + auth: mockServices.auth(), + httpAuth: mockServices.httpAuth(), }); app = express().use(router); }); @@ -167,6 +170,8 @@ describe('createRouter', () => { logger: getVoidLogger(), identity: { getIdentity }, badgeStore: badgeStore, + auth: mockServices.auth(), + httpAuth: mockServices.httpAuth(), }); expect(router).toBeDefined(); }); @@ -232,9 +237,9 @@ describe('createRouter', () => { 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'); + const obfuscatedEntity = await request(app).get( + '/entity/default/component/test/obfuscated', + ); expect(obfuscatedEntity.status).toEqual(200); expect(obfuscatedEntity.body.uuid).toMatch( @@ -245,7 +250,12 @@ describe('createRouter', () => { expect(catalog.getEntityByRef).toHaveBeenCalledWith( { namespace: 'default', kind: 'component', name: 'test' }, - { token: 'fakeToken' }, + { + token: mockCredentials.service.token({ + onBehalfOf: mockCredentials.user(), + targetPluginId: 'catalog', + }), + }, ); const uuid = obfuscatedEntity.body.uuid; diff --git a/plugins/badges-backend/src/service/router.ts b/plugins/badges-backend/src/service/router.ts index 5a8fbb538a..d508a02b2a 100644 --- a/plugins/badges-backend/src/service/router.ts +++ b/plugins/badges-backend/src/service/router.ts @@ -17,6 +17,7 @@ import express from 'express'; import Router from 'express-promise-router'; import { + createLegacyAuthAdapters, DatabaseManager, errorHandler, PluginEndpointDiscovery, @@ -30,9 +31,9 @@ import { BadgeContext, BadgeFactories } from '../types'; import { isNil } from 'lodash'; import { Logger } from 'winston'; import { IdentityApi } from '@backstage/plugin-auth-node'; -import { getBearerTokenFromAuthorizationHeader } from '@backstage/plugin-auth-node'; import { BadgesStore, DatabaseBadgesStore } from '../database/badgesStore'; import { createDefaultBadgeFactories } from '../badges'; +import { AuthService, HttpAuthService } from '@backstage/backend-plugin-api'; /** @public */ export interface RouterOptions { @@ -42,6 +43,8 @@ export interface RouterOptions { config: Config; discovery: PluginEndpointDiscovery; tokenManager: TokenManager; + auth?: AuthService; + httpAuth?: HttpAuthService; logger: Logger; identity: IdentityApi; badgeStore?: BadgesStore; @@ -60,28 +63,31 @@ export async function createRouter( ); const router = Router(); - const { config, logger, tokenManager, discovery } = options; + const { config, logger, discovery } = options; const baseUrl = await discovery.getExternalBaseUrl('badges'); + const { auth, httpAuth } = createLegacyAuthAdapters(options); + if (config.getOptionalBoolean('app.badges.obfuscate')) { return obfuscatedRoute( router, catalog, badgeBuilder, - tokenManager, logger, options, config, baseUrl, + auth, + httpAuth, ); } return nonObfuscatedRoute( router, catalog, badgeBuilder, - tokenManager, config, baseUrl, + auth, ); } @@ -89,18 +95,19 @@ async function obfuscatedRoute( router: express.Router, catalog: CatalogApi, badgeBuilder: BadgeBuilder, - tokenManager: TokenManager, logger: Logger, options: RouterOptions, config: Config, baseUrl: string, + auth: AuthService, + httpAuth: HttpAuthService, ) { logger.info('Badges obfuscation is enabled'); const store = options.badgeStore ? options.badgeStore : await DatabaseBadgesStore.create({ - database: await DatabaseManager.fromConfig(config).forPlugin('badges'), + database: DatabaseManager.fromConfig(config).forPlugin('badges'), }); router.get('/entity/:entityUuid/badge-specs', async (req, res) => { @@ -117,16 +124,15 @@ async function obfuscatedRoute( const name = badgeInfos.name; const namespace = badgeInfos.namespace; const kind = badgeInfos.kind; - const token = await tokenManager.getToken(); + const { token } = await auth.getPluginRequestToken({ + onBehalfOf: await auth.getOwnServiceCredentials(), + targetPluginId: 'catalog', + }); - // Query the catalog with the name, namespace, kind to get the entity informations + // Query the catalog with the name, namespace, kind to get the entity information const entity = await catalog.getEntityByRef( - { - namespace, - kind, - name, - }, - token, + { namespace, kind, name }, + { token }, ); if (isNil(entity)) { @@ -168,14 +174,15 @@ async function obfuscatedRoute( const name = badgeInfo.name; const namespace = badgeInfo.namespace; const kind = badgeInfo.kind; - const token = await tokenManager.getToken(); + + const { token } = await auth.getPluginRequestToken({ + onBehalfOf: await auth.getOwnServiceCredentials(), + targetPluginId: 'catalog', + }); + const entity = await catalog.getEntityByRef( - { - namespace, - kind, - name, - }, - token, + { namespace, kind, name }, + { token }, ); if (isNil(entity)) { throw new NotFoundError( @@ -217,20 +224,17 @@ async function obfuscatedRoute( router.get( '/entity/:namespace/:kind/:name/obfuscated', async function authenticate(req, _res, next) { - const token = getBearerTokenFromAuthorizationHeader( - req.headers.authorization, - ); - const { kind, namespace, name } = req.params; + const { token } = await auth.getPluginRequestToken({ + onBehalfOf: await httpAuth.credentials(req), + targetPluginId: 'catalog', + }); + // 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, - }, + { kind, namespace, name }, { token }, ); @@ -266,16 +270,20 @@ async function nonObfuscatedRoute( router: express.Router, catalog: CatalogApi, badgeBuilder: BadgeBuilder, - tokenManager: TokenManager, config: Config, baseUrl: string, + auth: AuthService, ) { router.get('/entity/:namespace/:kind/:name/badge-specs', async (req, res) => { - const token = await tokenManager.getToken(); + const { token } = await auth.getPluginRequestToken({ + onBehalfOf: await auth.getOwnServiceCredentials(), + targetPluginId: 'catalog', + }); + const { namespace, kind, name } = req.params; const entity = await catalog.getEntityByRef( { namespace, kind, name }, - token, + { token }, ); if (!entity) { throw new NotFoundError( @@ -306,11 +314,16 @@ async function nonObfuscatedRoute( '/entity/:namespace/:kind/:name/badge/:badgeId', async (req, res) => { const { namespace, kind, name, badgeId } = req.params; - const token = await tokenManager.getToken(); + const { token } = await auth.getPluginRequestToken({ + onBehalfOf: await auth.getOwnServiceCredentials(), + targetPluginId: 'catalog', + }); + const entity = await catalog.getEntityByRef( { namespace, kind, name }, - token, + { token }, ); + if (!entity) { throw new NotFoundError( `No ${kind} entity in ${namespace} named "${name}"`,