message: Verify access to related catalog entities for all techdocs api routes
diff: @@ -18,7 +18,7 @@ import {
  PluginCacheManager,
  } from '@backstage/backend-common';
  import { CatalogClient } from '@backstage/catalog-client';
  -import { Entity, stringifyEntityRef } from '@backstage/catalog-model';
  +import { stringifyEntityRef } from '@backstage/catalog-model';
  import { Config } from '@backstage/config';
  import { NotFoundError, NotModifiedError } from '@backstage/errors';
  import {
  @@ -27,7 +27,6 @@ import {
  PreparerBuilder,
  PublisherBase,
  } from '@backstage/techdocs-common';
  -import fetch from 'node-fetch';
  import express, { Response } from 'express';
  import Router from 'express-promise-router';
  import { Knex } from 'knex';
  @@ -107,6 +106,16 @@ export async function createRouter(
  router.get('/metadata/techdocs/:namespace/:kind/:name', async (req, res) => {
  const { kind, namespace, name } = req.params;
  const entityName = { kind, namespace, name };
  + const token = getBearerToken(req.headers.authorization);
  +
  + // Verify that the related entity exists and the current user has permission to view it.
  + const entity = await catalogClient.getEntityByName(entityName, { token });
  +
  + if (!entity) {
  + throw new NotFoundError(
  + `Unable to get metadata for '${stringifyEntityRef(entityName)}'`,
  + );
  + }
  try {
  const techdocsMetadata = await publisher.fetchTechDocsMetadata(
  @@ -128,23 +137,19 @@ export async function createRouter(
  });
  router.get('/metadata/entity/:namespace/:kind/:name', async (req, res) => {
  - const catalogUrl = await discovery.getBaseUrl('catalog');
  -
  const { kind, namespace, name } = req.params;
  const entityName = { kind, namespace, name };
  -
  - try {
  const token = getBearerToken(req.headers.authorization);
  - // TODO: Consider using the catalog client here
  - const entity = (await (
  - await fetch(
  - `${catalogUrl}/entities/by-name/${kind}/${namespace}/${name}`,
  - {
  - headers: token ? { Authorization: `Bearer ${token}` } : {},
  - },
  - )
  - ).json()) as Entity;
  + const entity = await catalogClient.getEntityByName(entityName, { token });
  +
  + if (!entity) {
  + throw new NotFoundError(
  + `Unable to get metadata for '${stringifyEntityRef(entityName)}'`,
  + );
  + }
  +
  + try {
  const locationMetadata = getLocationForEntity(entity, scmIntegrations);
  res.json({ ...entity, locationMetadata });
  } catch (err) {
  @@ -226,6 +231,23 @@ export async function createRouter(
  );
  });
  + // Ensures that the related entity exists and the current user has permission to view it.
  + router.use('/static/docs/:namespace/:kind/:name', async (req, _res, next) => {
  + const { kind, namespace, name } = req.params;
  + const entityName = { kind, namespace, name };
  + const token = getBearerToken(req.headers.authorization);
  +
  + const entity = await catalogClient.getEntityByName(entityName, { token });
  +
  + if (!entity) {
  + throw new NotFoundError(
  + `Entity not found for ${stringifyEntityRef(entityName)}`,
  + );
  + }
  +
  + next();
  + });
  +
  // If a cache manager was provided, attach the cache middleware.
  if (cache) {
  router.use(createCacheMiddleware({ logger, cache }));
  