diff --git a/.changeset/friendly-coats-travel.md b/.changeset/friendly-coats-travel.md new file mode 100644 index 0000000000..70d684a3da --- /dev/null +++ b/.changeset/friendly-coats-travel.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-code-coverage-backend': patch +--- + +Migrated to support new auth services. diff --git a/plugins/code-coverage-backend/api-report.md b/plugins/code-coverage-backend/api-report.md index 8a64361391..fef869a302 100644 --- a/plugins/code-coverage-backend/api-report.md +++ b/plugins/code-coverage-backend/api-report.md @@ -3,10 +3,12 @@ > 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 express from 'express'; +import { HttpAuthService } from '@backstage/backend-plugin-api'; import { Logger } from 'winston'; import { PluginDatabaseManager } from '@backstage/backend-common'; import { PluginEndpointDiscovery } from '@backstage/backend-common'; @@ -21,6 +23,8 @@ export function createRouter(options: RouterOptions): Promise; // @public export interface RouterOptions { + // (undocumented) + auth?: AuthService; // (undocumented) catalogApi?: CatalogApi; // (undocumented) @@ -30,6 +34,8 @@ export interface RouterOptions { // (undocumented) discovery: PluginEndpointDiscovery; // (undocumented) + httpAuth?: HttpAuthService; + // (undocumented) logger: Logger; // (undocumented) urlReader: UrlReader; diff --git a/plugins/code-coverage-backend/package.json b/plugins/code-coverage-backend/package.json index b3c3aae2d7..acdeab5a23 100644 --- a/plugins/code-coverage-backend/package.json +++ b/plugins/code-coverage-backend/package.json @@ -48,6 +48,7 @@ "yn": "^4.0.0" }, "devDependencies": { + "@backstage/backend-test-utils": "workspace:^", "@backstage/cli": "workspace:^", "@types/body-parser-xml": "^2.0.2", "@types/supertest": "^2.0.8", diff --git a/plugins/code-coverage-backend/src/service/router.test.ts b/plugins/code-coverage-backend/src/service/router.test.ts index 2032a5470d..64ea515ac0 100644 --- a/plugins/code-coverage-backend/src/service/router.test.ts +++ b/plugins/code-coverage-backend/src/service/router.test.ts @@ -26,6 +26,7 @@ import { import { ConfigReader } from '@backstage/config'; import { createRouter } from './router'; import { CatalogRequestOptions } from '@backstage/catalog-client'; +import { mockCredentials, mockServices } from '@backstage/backend-test-utils'; jest.mock('./CodeCoverageDatabase'); @@ -96,6 +97,8 @@ describe('createRouter', () => { discovery: testDiscovery, urlReader: mockUrlReader, logger: getVoidLogger(), + auth: mockServices.auth(), + httpAuth: mockServices.httpAuth(), }); app = express().use(router); }); @@ -118,21 +121,21 @@ describe('createRouter', () => { '/history?entity=component:default/mycomponent', ].forEach(uri => { describe(`GET ${uri}`, () => { - it('does not send token when calling catalog api and request is unauthenticated', async () => { - const response = await request(app).get(uri); - - expect(response.status).toEqual(200); - expect(catalogRequestOptions.token).toBeUndefined(); - }); - - it('includes auth token when calling catalog api', async () => { - const token = 'my-auth-token'; + it('forwards request credentials to the catalog api call', async () => { const response = await request(app) .get(uri) - .set('Authorization', `Bearer ${token}`); + .set( + 'Authorization', + mockCredentials.user.header('user:default/other'), + ); expect(response.status).toEqual(200); - expect(catalogRequestOptions.token).toEqual(token); + expect(catalogRequestOptions.token).toEqual( + mockCredentials.service.token({ + onBehalfOf: mockCredentials.user('user:default/other'), + targetPluginId: 'catalog', + }), + ); }); }); }); diff --git a/plugins/code-coverage-backend/src/service/router.ts b/plugins/code-coverage-backend/src/service/router.ts index 47bc285d62..d1a0a1703c 100644 --- a/plugins/code-coverage-backend/src/service/router.ts +++ b/plugins/code-coverage-backend/src/service/router.ts @@ -21,6 +21,7 @@ import BodyParser from 'body-parser'; import bodyParserXml from 'body-parser-xml'; import { CatalogApi, CatalogClient } from '@backstage/catalog-client'; import { + createLegacyAuthAdapters, errorHandler, PluginDatabaseManager, PluginEndpointDiscovery, @@ -33,7 +34,7 @@ import { CodeCoverageDatabase } from './CodeCoverageDatabase'; import { aggregateCoverage, CoverageUtils } from './CoverageUtils'; import { Converter, Jacoco, Cobertura, Lcov } from './converter'; import { getEntitySourceLocation } from '@backstage/catalog-model'; -import { getBearerTokenFromAuthorizationHeader } from '@backstage/plugin-auth-node'; +import { AuthService, HttpAuthService } from '@backstage/backend-plugin-api'; /** * Options for {@link createRouter}. @@ -47,6 +48,8 @@ export interface RouterOptions { urlReader: UrlReader; logger: Logger; catalogApi?: CatalogApi; + auth?: AuthService; + httpAuth?: HttpAuthService; } export interface CodeCoverageApi { @@ -63,6 +66,7 @@ export const makeRouter = async ( const catalogApi = options.catalogApi ?? new CatalogClient({ discoveryApi: discovery }); const scm = ScmIntegrations.fromConfig(config); + const { auth, httpAuth } = createLegacyAuthAdapters(options); const bodySizeLimit = config.getOptionalString('codeCoverage.bodySizeLimit') ?? '100kb'; @@ -92,9 +96,13 @@ export const makeRouter = async ( */ router.get('/report', async (req, res) => { const { entity } = req.query; - const entityLookup = await catalogApi.getEntityByRef(entity as string, { - token: getBearerTokenFromAuthorizationHeader(req.headers.authorization), - }); + const entityLookup = await catalogApi.getEntityByRef( + entity as string, + await auth.getPluginRequestToken({ + onBehalfOf: await httpAuth.credentials(req), + targetPluginId: 'catalog', + }), + ); if (!entityLookup) { throw new NotFoundError(`No entity found matching ${entity}`); } @@ -116,9 +124,13 @@ export const makeRouter = async ( */ router.get('/history', async (req, res) => { const { entity } = req.query; - const entityLookup = await catalogApi.getEntityByRef(entity as string, { - token: getBearerTokenFromAuthorizationHeader(req.headers.authorization), - }); + const entityLookup = await catalogApi.getEntityByRef( + entity as string, + await auth.getPluginRequestToken({ + onBehalfOf: await httpAuth.credentials(req), + targetPluginId: 'catalog', + }), + ); if (!entityLookup) { throw new NotFoundError(`No entity found matching ${entity}`); } @@ -136,9 +148,13 @@ export const makeRouter = async ( */ router.get('/file-content', async (req, res) => { const { entity, path } = req.query; - const entityLookup = await catalogApi.getEntityByRef(entity as string, { - token: getBearerTokenFromAuthorizationHeader(req.headers.authorization), - }); + const entityLookup = await catalogApi.getEntityByRef( + entity as string, + await auth.getPluginRequestToken({ + onBehalfOf: await httpAuth.credentials(req), + targetPluginId: 'catalog', + }), + ); if (!entityLookup) { throw new NotFoundError(`No entity found matching ${entity}`); } @@ -189,9 +205,13 @@ export const makeRouter = async ( */ router.post('/report', async (req, res) => { const { entity: entityRef, coverageType } = req.query; - const entity = await catalogApi.getEntityByRef(entityRef as string, { - token: getBearerTokenFromAuthorizationHeader(req.headers.authorization), - }); + const entity = await catalogApi.getEntityByRef( + entityRef as string, + await auth.getPluginRequestToken({ + onBehalfOf: await httpAuth.credentials(req), + targetPluginId: 'catalog', + }), + ); if (!entity) { throw new NotFoundError(`No entity found matching ${entityRef}`); } diff --git a/yarn.lock b/yarn.lock index 5aa82df2e2..08887fd8d8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6014,6 +6014,7 @@ __metadata: dependencies: "@backstage/backend-common": "workspace:^" "@backstage/backend-plugin-api": "workspace:^" + "@backstage/backend-test-utils": "workspace:^" "@backstage/catalog-client": "workspace:^" "@backstage/catalog-model": "workspace:^" "@backstage/cli": "workspace:^"