From f77a1d1f121ac20b5361e4e0051d01967487418c Mon Sep 17 00:00:00 2001 From: Anton Ganhammar Date: Wed, 30 Aug 2023 22:30:30 +0200 Subject: [PATCH 1/4] feat: include token when fetching entity Signed-off-by: Anton Ganhammar --- plugins/code-coverage-backend/package.json | 1 + .../code-coverage-backend/src/service/router.ts | 17 +++++++++++++---- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/plugins/code-coverage-backend/package.json b/plugins/code-coverage-backend/package.json index b53e958f2c..fc72d1854d 100644 --- a/plugins/code-coverage-backend/package.json +++ b/plugins/code-coverage-backend/package.json @@ -35,6 +35,7 @@ "@backstage/config": "workspace:^", "@backstage/errors": "workspace:^", "@backstage/integration": "workspace:^", + "@backstage/plugin-auth-node": "workspace:^", "@types/express": "^4.17.6", "body-parser": "^1.20.0", "body-parser-xml": "^2.0.5", diff --git a/plugins/code-coverage-backend/src/service/router.ts b/plugins/code-coverage-backend/src/service/router.ts index 55447dd5ad..06a108dd0e 100644 --- a/plugins/code-coverage-backend/src/service/router.ts +++ b/plugins/code-coverage-backend/src/service/router.ts @@ -33,6 +33,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'; /** * Options for {@link createRouter}. @@ -79,7 +80,9 @@ export const makeRouter = async ( */ router.get('/report', async (req, res) => { const { entity } = req.query; - const entityLookup = await catalogApi.getEntityByRef(entity as string); + const entityLookup = await catalogApi.getEntityByRef(entity as string, { + token: getBearerTokenFromAuthorizationHeader(req.headers.authorization), + }); if (!entityLookup) { throw new NotFoundError(`No entity found matching ${entity}`); } @@ -101,7 +104,9 @@ export const makeRouter = async ( */ router.get('/history', async (req, res) => { const { entity } = req.query; - const entityLookup = await catalogApi.getEntityByRef(entity as string); + const entityLookup = await catalogApi.getEntityByRef(entity as string, { + token: getBearerTokenFromAuthorizationHeader(req.headers.authorization), + }); if (!entityLookup) { throw new NotFoundError(`No entity found matching ${entity}`); } @@ -119,7 +124,9 @@ export const makeRouter = async ( */ router.get('/file-content', async (req, res) => { const { entity, path } = req.query; - const entityLookup = await catalogApi.getEntityByRef(entity as string); + const entityLookup = await catalogApi.getEntityByRef(entity as string, { + token: getBearerTokenFromAuthorizationHeader(req.headers.authorization), + }); if (!entityLookup) { throw new NotFoundError(`No entity found matching ${entity}`); } @@ -170,7 +177,9 @@ export const makeRouter = async ( */ router.post('/report', async (req, res) => { const { entity: entityRef, coverageType } = req.query; - const entity = await catalogApi.getEntityByRef(entityRef as string); + const entity = await catalogApi.getEntityByRef(entityRef as string, { + token: getBearerTokenFromAuthorizationHeader(req.headers.authorization), + }); if (!entity) { throw new NotFoundError(`No entity found matching ${entityRef}`); } From 33e606a797efd5ec4bbda90d9dbee26be0dd05f8 Mon Sep 17 00:00:00 2001 From: Anton Ganhammar Date: Thu, 31 Aug 2023 11:25:32 +0200 Subject: [PATCH 2/4] chore: add changeset Signed-off-by: Anton Ganhammar --- .changeset/proud-lamps-cross.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/proud-lamps-cross.md diff --git a/.changeset/proud-lamps-cross.md b/.changeset/proud-lamps-cross.md new file mode 100644 index 0000000000..cd2528a94c --- /dev/null +++ b/.changeset/proud-lamps-cross.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-code-coverage-backend': patch +--- + +Include auth token when fetching entity From 82141ae65a6fdb81819eea85c9cde77352b87303 Mon Sep 17 00:00:00 2001 From: Anton Ganhammar Date: Thu, 31 Aug 2023 11:39:37 +0200 Subject: [PATCH 3/4] fix: update yarn.lock Signed-off-by: Anton Ganhammar --- yarn.lock | 1 + 1 file changed, 1 insertion(+) diff --git a/yarn.lock b/yarn.lock index ed2fdd44f2..db143094fd 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6183,6 +6183,7 @@ __metadata: "@backstage/config": "workspace:^" "@backstage/errors": "workspace:^" "@backstage/integration": "workspace:^" + "@backstage/plugin-auth-node": "workspace:^" "@types/body-parser-xml": ^2.0.2 "@types/express": ^4.17.6 "@types/supertest": ^2.0.8 From f86328fee63654be639bae316f842833d1d22804 Mon Sep 17 00:00:00 2001 From: Anton Ganhammar Date: Thu, 31 Aug 2023 15:10:40 +0200 Subject: [PATCH 4/4] feat: add token related tests to report and history endpoints Signed-off-by: Anton Ganhammar --- .../src/service/router.test.ts | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/plugins/code-coverage-backend/src/service/router.test.ts b/plugins/code-coverage-backend/src/service/router.test.ts index 7d1825c25f..4f1a9ee2e5 100644 --- a/plugins/code-coverage-backend/src/service/router.test.ts +++ b/plugins/code-coverage-backend/src/service/router.test.ts @@ -25,6 +25,35 @@ import { } from '@backstage/backend-common'; import { ConfigReader } from '@backstage/config'; import { createRouter } from './router'; +import { CatalogRequestOptions } from '@backstage/catalog-client'; + +jest.mock('./CodeCoverageDatabase'); + +import { CodeCoverageDatabase } from './CodeCoverageDatabase'; + +CodeCoverageDatabase.create = jest.fn( + async () => + ({ + getCodeCoverage: async () => ({ + files: [], + metadata: { + generationTime: 1, + }, + }), + getHistory: async () => ({}), + } as any), +); + +let catalogRequestOptions: CatalogRequestOptions; + +jest.mock('@backstage/catalog-client', () => ({ + CatalogClient: jest.fn().mockImplementation(() => ({ + getEntityByRef: async (_: string, options: CatalogRequestOptions) => { + catalogRequestOptions = options; + return {}; + }, + })), +})); function createDatabase(): PluginDatabaseManager { return DatabaseManager.fromConfig( @@ -76,4 +105,28 @@ describe('createRouter', () => { expect(response.body).toEqual({ status: 'ok' }); }); }); + + [ + '/report?entity=component:default/mycomponent', + '/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'; + const response = await request(app) + .get(uri) + .set('Authorization', `Bearer ${token}`); + + expect(response.status).toEqual(200); + expect(catalogRequestOptions.token).toEqual(token); + }); + }); + }); });