chore(badges): improve error handling, improve visibility splitting if else block to functions, move db test file beside the tested file

Signed-off-by: Rbillon59 <r.billon@celonis.com>
This commit is contained in:
Rbillon59
2023-04-27 11:45:47 +02:00
parent 6263ef7ce9
commit 2fbf5300c5
2 changed files with 231 additions and 221 deletions
@@ -14,7 +14,7 @@
* limitations under the License.
*/
import { DatabaseBadgesStore } from '../database/badgesStore';
import { DatabaseBadgesStore } from './badgesStore';
import { TestDatabaseId, TestDatabases } from '@backstage/backend-test-utils';
import { Knex } from 'knex';
import { Entity } from '@backstage/catalog-model';
+230 -220
View File
@@ -24,7 +24,7 @@ import {
} from '@backstage/backend-common';
import { CatalogApi, CatalogClient } from '@backstage/catalog-client';
import { Config } from '@backstage/config';
import { NotFoundError } from '@backstage/errors';
import { AuthenticationError, NotFoundError } from '@backstage/errors';
import { BadgeBuilder, DefaultBadgeBuilder } from '../lib/BadgeBuilder';
import { BadgeContext, BadgeFactories } from '../types';
import { isNil } from 'lodash';
@@ -61,97 +61,252 @@ export async function createRouter(
const baseUrl = await discovery.getExternalBaseUrl('badges');
if (config.getOptionalBoolean('app.badges.obfuscate')) {
logger.info('Badges obfuscation is enabled');
return obfuscatedRoute(
router,
catalog,
badgeBuilder,
tokenManager,
logger,
options,
config,
identity,
baseUrl,
);
}
return nonObfuscatedRoute(
router,
catalog,
badgeBuilder,
tokenManager,
config,
baseUrl,
);
}
const store = options.badgeStore
? options.badgeStore
: await DatabaseBadgesStore.create({
database: await DatabaseManager.fromConfig(config).forPlugin(
'badges',
),
});
async function obfuscatedRoute(
router: express.Router,
catalog: CatalogApi,
badgeBuilder: BadgeBuilder,
tokenManager: TokenManager,
logger: Logger,
options: RouterOptions,
config: Config,
identity: IdentityApi,
baseUrl: string,
) {
logger.info('Badges obfuscation is enabled');
router.get('/entity/:entityUuid/badge-specs', async (req, res) => {
const { entityUuid } = req.params;
const store = options.badgeStore
? options.badgeStore
: await DatabaseBadgesStore.create({
database: await DatabaseManager.fromConfig(config).forPlugin('badges'),
});
// Retrieve the badge info from the database
const badgeInfos = await store.getBadgeFromUuid(entityUuid);
router.get('/entity/:entityUuid/badge-specs', async (req, res) => {
const { entityUuid } = req.params;
if (isNil(badgeInfos)) {
throw new NotFoundError(
`No badge found for entity uuid "${entityUuid}"`,
);
}
// Retrieve the badge info from the database
const badgeInfos = await store.getBadgeFromUuid(entityUuid);
// If a mapping is found, map name, namespace and kind
const name = badgeInfos.name;
const namespace = badgeInfos.namespace;
const kind = badgeInfos.kind;
const token = await tokenManager.getToken();
if (isNil(badgeInfos)) {
throw new NotFoundError(`No badge found for entity uuid "${entityUuid}"`);
}
// Query the catalog with the name, namespace, kind to get the entity informations
const entity = await catalog.getEntityByRef(
{
namespace,
kind,
name,
},
token,
// If a mapping is found, map name, namespace and kind
const name = badgeInfos.name;
const namespace = badgeInfos.namespace;
const kind = badgeInfos.kind;
const token = await tokenManager.getToken();
// Query the catalog with the name, namespace, kind to get the entity informations
const entity = await catalog.getEntityByRef(
{
namespace,
kind,
name,
},
token,
);
if (isNil(entity)) {
throw new NotFoundError(
`No ${kind} entity in ${namespace} named "${name}"`,
);
if (isNil(entity)) {
throw new NotFoundError(
`No ${kind} entity in ${namespace} named "${name}"`,
);
}
// Create the badge specs
const specs = [];
for (const badgeInfo of await badgeBuilder.getBadges()) {
const context: BadgeContext = {
badgeUrl: `${baseUrl}/entity/${entityUuid}/${badgeInfo.id}`,
config: config,
entity,
};
const badge = await badgeBuilder.createBadgeJson({
badgeInfo,
context,
});
specs.push(badge);
}
res.status(200).json(specs);
});
router.get('/entity/:entityUuid/:badgeId', async (req, res) => {
const { entityUuid, badgeId } = req.params;
// Retrieve the badge info from the database
const badgeInfo = await store.getBadgeFromUuid(entityUuid);
if (isNil(badgeInfo)) {
throw new NotFoundError(`No badge found for entity uuid "${entityUuid}"`);
}
// If a mapping is found, map name, namespace and kind
const name = badgeInfo.name;
const namespace = badgeInfo.namespace;
const kind = badgeInfo.kind;
const token = await tokenManager.getToken();
const entity = await catalog.getEntityByRef(
{
namespace,
kind,
name,
},
token,
);
if (isNil(entity)) {
throw new NotFoundError(
`No ${kind} entity in ${namespace} named "${name}"`,
res.sendStatus(404),
);
}
let format =
req.accepts(['image/svg+xml', 'application/json']) || 'image/svg+xml';
if (req.query.format === 'json') {
format = 'application/json';
}
const badgeOptions = {
badgeInfo: { id: badgeId },
context: {
badgeUrl: `${baseUrl}/entity/${entityUuid}/${badgeId}`,
config: config,
entity,
},
};
let data: string;
if (format === 'application/json') {
data = JSON.stringify(
await badgeBuilder.createBadgeJson(badgeOptions),
null,
2,
);
} else {
data = await badgeBuilder.createBadgeSvg(badgeOptions);
}
res.setHeader('Content-Type', format);
res.status(200).send(data);
});
router.get(
'/entity/:namespace/:kind/:name/obfuscated',
function authenticate(req, _res, next) {
const token =
getBearerTokenFromAuthorizationHeader(req.headers.authorization) ||
(req.cookies?.token as string | undefined);
if (!token) {
throw new AuthenticationError('Unauthorized');
}
// Create the badge specs
const specs = [];
for (const badgeInfo of await badgeBuilder.getBadges()) {
const context: BadgeContext = {
badgeUrl: await getBadgeObfuscatedUrl(entityUuid, badgeInfo.id),
config: config,
entity,
};
try {
req.user = identity.getIdentity({ request: req });
next();
} catch (error) {
tokenManager.authenticate(token.toString());
next(error);
}
},
async (req, res) => {
const { namespace, kind, name } = req.params;
let storedEntityUuid: { uuid: string } | undefined =
await store.getUuidFromEntityMetadata(name, namespace, kind);
const badge = await badgeBuilder.createBadgeJson({
badgeInfo,
context,
});
specs.push(badge);
if (isNil(storedEntityUuid)) {
storedEntityUuid = await store.addBadge(name, namespace, kind);
if (isNil(storedEntityUuid)) {
throw new NotFoundError(
`No uuid found for entity "${namespace}/${kind}/${name}"`,
);
}
}
res.status(200).json(specs);
});
return res.status(200).json(storedEntityUuid);
},
);
router.get('/entity/:entityUuid/:badgeId', async (req, res) => {
const { entityUuid, badgeId } = req.params;
router.use(errorHandler());
// Retrieve the badge info from the database
const badgeInfo = await store.getBadgeFromUuid(entityUuid);
return router;
}
if (isNil(badgeInfo)) {
throw new NotFoundError(
`No badge found for entity uuid "${entityUuid}"`,
);
}
async function nonObfuscatedRoute(
router: express.Router,
catalog: CatalogApi,
badgeBuilder: BadgeBuilder,
tokenManager: TokenManager,
config: Config,
baseUrl: string,
) {
router.get('/entity/:namespace/:kind/:name/badge-specs', async (req, res) => {
const token = await tokenManager.getToken();
const { namespace, kind, name } = req.params;
const entity = await catalog.getEntityByRef(
{ namespace, kind, name },
token,
);
if (!entity) {
throw new NotFoundError(
`No ${kind} entity in ${namespace} named "${name}"`,
);
}
// If a mapping is found, map name, namespace and kind
const name = badgeInfo.name;
const namespace = badgeInfo.namespace;
const kind = badgeInfo.kind;
const specs = [];
for (const badgeInfo of await badgeBuilder.getBadges()) {
const badgeId = badgeInfo.id;
const context: BadgeContext = {
badgeUrl: `${baseUrl}/entity/${namespace}/${kind}/${name}/badge/${badgeId}`,
config: config,
entity,
};
const badge = await badgeBuilder.createBadgeJson({
badgeInfo,
context,
});
specs.push(badge);
}
res.status(200).json(specs);
});
router.get(
'/entity/:namespace/:kind/:name/badge/:badgeId',
async (req, res) => {
const { namespace, kind, name, badgeId } = req.params;
const token = await tokenManager.getToken();
const entity = await catalog.getEntityByRef(
{
namespace,
kind,
name,
},
{ namespace, kind, name },
token,
);
if (isNil(entity)) {
if (!entity) {
throw new NotFoundError(
`No ${kind} entity in ${namespace} named "${name}"`,
res.sendStatus(404),
);
}
@@ -161,11 +316,10 @@ export async function createRouter(
format = 'application/json';
}
// Generate the badge URL for the different types of badgeId
const badgeOptions = {
badgeInfo: { id: badgeId },
context: {
badgeUrl: await getBadgeObfuscatedUrl(entityUuid, badgeId),
badgeUrl: `${baseUrl}/entity/${namespace}/${kind}/${name}/badge/${badgeId}`,
config: config,
entity,
},
@@ -184,154 +338,10 @@ export async function createRouter(
res.setHeader('Content-Type', format);
res.status(200).send(data);
});
},
);
router.get(
'/entity/:namespace/:kind/:name/obfuscated',
function authenticate(req, res, next) {
const token =
getBearerTokenFromAuthorizationHeader(req.headers.authorization) ||
(req.cookies?.token as string | undefined);
router.use(errorHandler());
if (!token) {
res.status(401).send('Unauthorized');
return;
}
try {
req.user = identity.getIdentity({ request: req });
next();
} catch (error) {
tokenManager.authenticate(token.toString());
next(error);
}
},
async (req, res) => {
const { namespace, kind, name } = req.params;
let storedEntityUuid: { uuid: string } | undefined =
await store.getUuidFromEntityMetadata(name, namespace, kind);
if (isNil(storedEntityUuid)) {
storedEntityUuid = await store.addBadge(name, namespace, kind);
if (isNil(storedEntityUuid)) {
throw new NotFoundError(
`No uuid found for entity "${namespace}/${kind}/${name}"`,
);
}
}
return res.status(200).json(storedEntityUuid);
},
);
router.use(errorHandler());
return router;
// If the obfuscation is disabled, use the previously implemented routes
// eslint-disable-next-line no-else-return
} else {
router.get(
'/entity/:namespace/:kind/:name/badge-specs',
async (req, res) => {
const token = await tokenManager.getToken();
const { namespace, kind, name } = req.params;
const entity = await catalog.getEntityByRef(
{ namespace, kind, name },
token,
);
if (!entity) {
throw new NotFoundError(
`No ${kind} entity in ${namespace} named "${name}"`,
);
}
const specs = [];
for (const badgeInfo of await badgeBuilder.getBadges()) {
const context: BadgeContext = {
badgeUrl: await getBadgeUrl(namespace, kind, name, badgeInfo.id),
config: config,
entity,
};
const badge = await badgeBuilder.createBadgeJson({
badgeInfo,
context,
});
specs.push(badge);
}
res.status(200).json(specs);
},
);
router.get(
'/entity/:namespace/:kind/:name/badge/:badgeId',
async (req, res) => {
const { namespace, kind, name, badgeId } = req.params;
const token = await tokenManager.getToken();
const entity = await catalog.getEntityByRef(
{ namespace, kind, name },
token,
);
if (!entity) {
throw new NotFoundError(
`No ${kind} entity in ${namespace} named "${name}"`,
);
}
let format =
req.accepts(['image/svg+xml', 'application/json']) || 'image/svg+xml';
if (req.query.format === 'json') {
format = 'application/json';
}
const badgeOptions = {
badgeInfo: { id: badgeId },
context: {
badgeUrl: await getBadgeUrl(namespace, kind, name, badgeId),
config: config,
entity,
},
};
let data: string;
if (format === 'application/json') {
data = JSON.stringify(
await badgeBuilder.createBadgeJson(badgeOptions),
null,
2,
);
} else {
data = await badgeBuilder.createBadgeSvg(badgeOptions);
}
res.setHeader('Content-Type', format);
res.status(200).send(data);
},
);
router.use(errorHandler());
return router;
}
// This function return the obfuscated badge url based on the namespace/kind/name triplet
async function getBadgeObfuscatedUrl(
uuid: string,
badgeId: string,
): Promise<string> {
return `${baseUrl}/entity/${uuid}/${badgeId}`;
}
// This function return the badge url based on the namespace/kind/name triplet
async function getBadgeUrl(
namespace: string,
kind: string,
name: string,
badgeId: string,
): Promise<string> {
return `${baseUrl}/entity/${namespace}/${kind}/${name}/badge/${badgeId}`;
}
return router;
}