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:
+1
-1
@@ -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';
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user