Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore(release): backport keycloak fixes but without upgrade keycloak client #2429

Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
chore(release): backport keycloak-backend fixes for RHDH 1.3.1
Signed-off-by: Oleksandr Andriienko <oandriie@redhat.com>
  • Loading branch information
AndrienkoAleksandr committed Oct 23, 2024
commit 857af95415349652197e43634cc59579bf5fa868
7 changes: 1 addition & 6 deletions plugins/keycloak-backend/__fixtures__/data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,9 +90,4 @@ export const users = [
},
];

export const groupMembers = [
['jamesdoe'],
[],
[],
['jamesdoe', 'joedoe', 'johndoe'],
];
export const groupMembers = ['jamesdoe'];
31 changes: 27 additions & 4 deletions plugins/keycloak-backend/__fixtures__/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,10 +55,25 @@ export class KeycloakAdminClientMock {
count: jest.fn().mockResolvedValue(groups.length),
listMembers: jest
.fn()
.mockResolvedValueOnce(groupMembers[0].map(username => ({ username })))
.mockResolvedValueOnce(groupMembers[1].map(username => ({ username })))
.mockResolvedValueOnce(groupMembers[2].map(username => ({ username })))
.mockResolvedValueOnce(groupMembers[3].map(username => ({ username }))),
.mockImplementation(
async (payload?: {
id: string;
_max?: number;
_realm?: string;
first?: number;
}) => {
const { id, first } = payload || {};
if (id === '9cf51b5d-e066-4ed8-940c-dc6da77f81a5' && first === 0) {
// biggroup - first members page
return groupMembers.map(username => ({ username }));
}
if (id === 'bb10231b-2939-4b1a-b8bb-9249ed7b76f7' && first === 0) {
// testgroup - first members page
return groupMembers.map(username => ({ username }));
}
return [];
},
),
};

auth = authMock;
Expand All @@ -74,6 +89,14 @@ class FakeAbortSignal implements AbortSignal {
dispatchEvent() {
return true;
}
any(signals: Iterable<AbortSignal>): AbortSignal {
for (const signal of signals) {
if (signal.aborted) {
return signal;
}
}
throw new Error('No abort signal found');
}
}

export class ManualTaskRunner implements TaskRunner {
Expand Down
23 changes: 16 additions & 7 deletions plugins/keycloak-backend/src/lib/read.test.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { mockServices } from '@backstage/backend-test-utils';

import KcAdminClient from '@keycloak/keycloak-admin-client';

import {
Expand All @@ -21,10 +23,12 @@ const config: KeycloakProviderConfig = {
baseUrl: 'http://mock-url',
};

const logger = mockServices.logger.mock();

describe('readKeycloakRealm', () => {
it('should return the correct number of users and groups', async () => {
const client = new KeycloakAdminClientMock() as unknown as KcAdminClient;
const { users, groups } = await readKeycloakRealm(client, config);
const { users, groups } = await readKeycloakRealm(client, config, logger);

expect(users).toHaveLength(3);
expect(groups).toHaveLength(4);
Expand All @@ -41,7 +45,7 @@ describe('readKeycloakRealm', () => {
};

const client = new KeycloakAdminClientMock() as unknown as KcAdminClient;
const { users, groups } = await readKeycloakRealm(client, config, {
const { users, groups } = await readKeycloakRealm(client, config, logger, {
userTransformer,
groupTransformer,
});
Expand Down Expand Up @@ -144,11 +148,15 @@ describe('getEntities', () => {
it('should fetch all users', async () => {
const client = new KeycloakAdminClientMock() as unknown as KcAdminClient;

const users = await getEntities(client.users, {
id: '',
baseUrl: '',
realm: '',
});
const users = await getEntities(
client.users,
{
id: '',
baseUrl: '',
realm: '',
},
logger,
);

expect(users).toHaveLength(3);
});
Expand All @@ -163,6 +171,7 @@ describe('getEntities', () => {
baseUrl: '',
realm: '',
},
logger,
1,
);

Expand Down
81 changes: 63 additions & 18 deletions plugins/keycloak-backend/src/lib/read.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
* limitations under the License.
*/

import { LoggerService } from '@backstage/backend-plugin-api';
import { GroupEntity, UserEntity } from '@backstage/catalog-model';

import KeycloakAdminClient from '@keycloak/keycloak-admin-client';
Expand Down Expand Up @@ -120,6 +121,7 @@ export function* traverseGroups(
export async function getEntities<T extends Users | Groups>(
entities: T,
config: KeycloakProviderConfig,
logger: LoggerService,
entityQuerySize: number = KEYCLOAK_ENTITY_QUERY_SIZE,
): Promise<Awaited<ReturnType<T['find']>>> {
const rawEntityCount = await entities.count({ realm: config.realm });
Expand All @@ -132,11 +134,15 @@ export async function getEntities<T extends Users | Groups>(
const entityPromises = Array.from(
{ length: pageCount },
(_, i) =>
entities.find({
realm: config.realm,
max: entityQuerySize,
first: i * entityQuerySize,
}) as ReturnType<T['find']>,
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<
Expand All @@ -146,9 +152,43 @@ export async function getEntities<T extends Users | Groups>(
return entityResults;
}

async function getAllGroupMembers<T extends Groups>(
groups: T,
groupId: string,
config: KeycloakProviderConfig,
options?: { userQuerySize?: number },
): Promise<string[]> {
const querySize = options?.userQuerySize || 100;

let allMembers: string[] = [];
let page = 0;
let totalMembers = 0;

do {
const members = await groups.listMembers({
id: groupId,
max: querySize,
realm: config.realm,
first: page * querySize,
});

if (members.length > 0) {
allMembers = allMembers.concat(members.map(m => m.username!));
totalMembers = members.length; // Get the number of members retrieved
} else {
totalMembers = 0; // No members retrieved
}

page++;
} while (totalMembers > 0);

return allMembers;
}

export const readKeycloakRealm = async (
client: KeycloakAdminClient,
config: KeycloakProviderConfig,
logger: LoggerService,
options?: {
userQuerySize?: number;
groupQuerySize?: number;
Expand All @@ -162,12 +202,14 @@ export const readKeycloakRealm = async (
const kUsers = await getEntities(
client.users,
config,
logger,
options?.userQuerySize,
);

const rawKGroups = await getEntities(
client.groups,
config,
logger,
options?.groupQuerySize,
);
const flatKGroups = rawKGroups.reduce((acc, g) => {
Expand All @@ -176,13 +218,12 @@ export const readKeycloakRealm = async (
}, [] as GroupRepresentationWithParent[]);
const kGroups = await Promise.all(
flatKGroups.map(async g => {
g.members = (
await client.groups.listMembers({
id: g.id!,
max: options?.userQuerySize,
realm: config.realm,
})
).map(m => m.username!);
g.members = await getAllGroupMembers(
client.groups as Groups,
g.id!,
config,
options,
);
return g;
}),
);
Expand Down Expand Up @@ -228,13 +269,17 @@ export const readKeycloakRealm = async (
const groups = parsedGroups.map(g => {
const entity = g.entity;
entity.spec.members =
g.entity.spec.members?.map(
m => parsedUsers.find(p => p.username === m)?.entity.metadata.name!,
) ?? [];
g.entity.spec.members?.flatMap(m => {
const name = parsedUsers.find(p => p.username === m)?.entity.metadata
.name;
return name ? [name] : [];
}) ?? [];
entity.spec.children =
g.entity.spec.children?.map(
c => parsedGroups.find(p => p.name === c)?.entity.metadata.name!,
) ?? [];
g.entity.spec.children?.flatMap(c => {
const child = parsedGroups.find(p => p.name === c)?.entity.metadata
.name;
return child ? [child] : [];
}) ?? [];
entity.spec.parent = parsedGroups.find(
p => p.name === entity.spec.parent,
)?.entity.metadata.name;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -217,12 +217,17 @@ export class KeycloakOrgEntityProvider implements EntityProvider {

await kcAdminClient.auth(credentials);

const { users, groups } = await readKeycloakRealm(kcAdminClient, provider, {
userQuerySize: provider.userQuerySize,
groupQuerySize: provider.groupQuerySize,
userTransformer: this.options.userTransformer,
groupTransformer: this.options.groupTransformer,
});
const { users, groups } = await readKeycloakRealm(
kcAdminClient,
provider,
logger,
{
userQuerySize: provider.userQuerySize,
groupQuerySize: provider.groupQuerySize,
userTransformer: this.options.userTransformer,
groupTransformer: this.options.groupTransformer,
},
);

const { markCommitComplete } = markReadComplete({ users, groups });

Expand Down