Skip to content

Commit

Permalink
fix(keycloak): serialize the calls to get groups and users
Browse files Browse the repository at this point in the history
Signed-off-by: Patrick <[email protected]>
  • Loading branch information
JohannesWill authored and PatAKnight committed Nov 20, 2024
1 parent ba75c57 commit 23e94e6
Show file tree
Hide file tree
Showing 2 changed files with 78 additions and 73 deletions.
5 changes: 5 additions & 0 deletions workspaces/keycloak/.changeset/tender-ears-judge.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@backstage-community/plugin-catalog-backend-module-keycloak': patch
---

Serialize the calls to get groups and users from Keycloak. In cases where there was a large number of users and / or groups, originally the keycloak would make many async calls that would lead to DOS of the keycloak server.
Original file line number Diff line number Diff line change
Expand Up @@ -120,25 +120,21 @@ export async function getEntities<T extends Users | Groups>(
const pageCount = Math.ceil(entityCount / entityQuerySize);

// The next line acts like range in python
const entityPromises = Array.from(
{ length: pageCount },
(_, i) =>
entities
.find({
realm: config.realm,
max: entityQuerySize,
first: i * entityQuerySize,
})
.catch(err =>
logger.warn('Failed to retieve Keycloak entities.', err),
) as ReturnType<T['find']>,
);

const entityResults = (await Promise.all(entityPromises)).flat() as Awaited<
ReturnType<T['find']>
>;
const entityResults = [];
for (let i = 0; i < pageCount; i) {
try {
const entitiesPage = await entities.find({
realm: config.realm,
max: entityQuerySize,
first: i * entityQuerySize,
});
entityResults.push(...entitiesPage);
} catch (err) {
logger.warn(`Failed to retrieve Keycloak entities: ${err}`);
}
}

return entityResults;
return entityResults as Awaited<ReturnType<T['find']>>;
}

async function getAllGroupMembers<T extends Groups>(
Expand Down Expand Up @@ -269,65 +265,69 @@ export const readKeycloakRealm = async (
[] as GroupRepresentationWithParent[],
);
}
const kGroups = await Promise.all(
rawKGroups.map(async g => {
g.members = await getAllGroupMembers(
client.groups as Groups,
g.id!,
config,
options,
);

if (isVersion23orHigher) {
if (g.subGroupCount! > 0) {
g.subGroups = await client.groups.listSubGroups({
parentId: g.id!,
first: 0,
max: g.subGroupCount,
briefRepresentation: false,
realm: config.realm,
});
}
if (g.parentId) {
const groupParent = await client.groups.findOne({
id: g.parentId,
realm: config.realm,
});
g.parent = groupParent?.name;
}
const kGroups = [];
for (const g of rawKGroups) {
g.members = await getAllGroupMembers(client.groups, g.id!, config, options);

if (isVersion23orHigher) {
if (g.subGroupCount! > 0) {
g.subGroups = await client.groups.listSubGroups({
parentId: g.id!,
first: 0,
max: g.subGroupCount,
briefRepresentation: false,
realm: config.realm,
});
}
if (g.parentId) {
const groupParent = await client.groups.findOne({
id: g.parentId,
realm: config.realm,
});
g.parent = groupParent?.name;
}
}

return g;
}),
);
kGroups.push(g);
}

const parsedGroups = await kGroups.reduce(async (promise, g) => {
const partial = await promise;
const entity = await parseGroup(g, config.realm, options?.groupTransformer);
if (entity) {
const group = {
...g,
entity,
} as GroupRepresentationWithParentAndEntity;
partial.push(group);
}
return partial;
}, Promise.resolve([] as GroupRepresentationWithParentAndEntity[]));
const parsedGroups = await kGroups.reduce(
async (promise, g) => {
const partial = await promise;
const entity = await parseGroup(
g,
config.realm,
options?.groupTransformer,
);
if (entity) {
const group = {
...g,
entity,
} as GroupRepresentationWithParentAndEntity;
partial.push(group);
}
return partial;
},
Promise.resolve([] as GroupRepresentationWithParentAndEntity[]),
);

const parsedUsers = await kUsers.reduce(async (promise, u) => {
const partial = await promise;
const entity = await parseUser(
u,
config.realm,
parsedGroups,
options?.userTransformer,
);
if (entity) {
const user = { ...u, entity } as UserRepresentationWithEntity;
partial.push(user);
}
return partial;
}, Promise.resolve([] as UserRepresentationWithEntity[]));
const parsedUsers = await kUsers.reduce(
async (promise, u) => {
const partial = await promise;
const entity = await parseUser(
u,
config.realm,
parsedGroups,
options?.userTransformer,
);
if (entity) {
const user = { ...u, entity } as UserRepresentationWithEntity;
partial.push(user);
}
return partial;
},
Promise.resolve([] as UserRepresentationWithEntity[]),
);

const groups = parsedGroups.map(g => {
const entity = g.entity;
Expand Down

0 comments on commit 23e94e6

Please sign in to comment.