From 3ab95a8654e8f678e6380c8e8681ce19f7e29174 Mon Sep 17 00:00:00 2001 From: ElderMatt <18527012+ElderMatt@users.noreply.github.com> Date: Wed, 4 Jun 2025 15:43:50 +0200 Subject: [PATCH 1/3] fix: change order of managing groups and rolemapping --- src/operator/keycloak.ts | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/src/operator/keycloak.ts b/src/operator/keycloak.ts index 641408a..3931607 100644 --- a/src/operator/keycloak.ts +++ b/src/operator/keycloak.ts @@ -175,6 +175,8 @@ async function runKeycloakUpdater() { const api = setupKeycloakApi(connection) await keycloakConfigMapChanges(api) await manageGroups(api) + if (env.FEAT_EXTERNAL_IDP === 'true') await externalIDP(api) + else await internalIdp(api) if (!JSON.parse(env.FEAT_EXTERNAL_IDP)) { await manageUsers(api, env.USERS as Record[]) } @@ -329,8 +331,6 @@ if (typeof require !== 'undefined' && require.main === module) { } async function keycloakConfigMapChanges(api: KeycloakApi) { await keycloakRealmProviderConfigurer(api) - if (env.FEAT_EXTERNAL_IDP === 'true') await externalIDP(api) - else await internalIdp(api) } async function createKeycloakConnection(): Promise { @@ -461,7 +461,8 @@ async function keycloakRealmProviderConfigurer(api: KeycloakApi) { } console.info('Getting client email claim mapper') - const allClaims = (await api.protocols.adminRealmsRealmClientsClientUuidProtocolMappersModelsGet(keycloakRealm, client.id!)).body || + const allClaims = + (await api.protocols.adminRealmsRealmClientsClientUuidProtocolMappersModelsGet(keycloakRealm, client.id!)).body || [] if (!allClaims.some((el) => el.name === 'email')) { const mapper = createClientEmailClaimMapper() @@ -509,9 +510,7 @@ async function externalIDP(api: KeycloakApi) { try { await Promise.all( idpMappers.map((idpMapper) => { - const existingMapper = existingMappers.find( - (m) => m.name === idpMapper.name, - ) + const existingMapper = existingMappers.find((m) => m.name === idpMapper.name) if (existingMapper) { console.info(`Updating mapper ${idpMapper.name!}`) return api.providers.adminRealmsRealmIdentityProviderInstancesAliasMappersIdPut( @@ -525,7 +524,11 @@ async function externalIDP(api: KeycloakApi) { ) } console.info(`Creating mapper ${idpMapper.name!}`) - return api.providers.adminRealmsRealmIdentityProviderInstancesAliasMappersPost(keycloakRealm, env.IDP_ALIAS, idpMapper) + return api.providers.adminRealmsRealmIdentityProviderInstancesAliasMappersPost( + keycloakRealm, + env.IDP_ALIAS, + idpMapper, + ) }), ) console.info('Finished external IDP') @@ -546,9 +549,7 @@ async function internalIdp(api: KeycloakApi) { // get clients for access roles console.info(`Getting client realm-management from realm ${keycloakRealm}`) const realmManagementClients = (await api.clients.adminRealmsRealmClientsGet(keycloakRealm, 'realm-management')).body - const realmManagementClient = realmManagementClients.find( - (el) => el.clientId === 'realm-management', - )! + const realmManagementClient = realmManagementClients.find((el) => el.clientId === 'realm-management')! console.info(`Getting realm-management roles from realm ${keycloakRealm}`) const realmManagementRoles = ( From c0af8c8d56ac93b40312d3496b508f6c2589b6ac Mon Sep 17 00:00:00 2001 From: ElderMatt <18527012+ElderMatt@users.noreply.github.com> Date: Tue, 10 Jun 2025 13:40:34 +0200 Subject: [PATCH 2/3] feat: clean up code --- src/operator/keycloak.ts | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/operator/keycloak.ts b/src/operator/keycloak.ts index 3931607..f1c52b7 100644 --- a/src/operator/keycloak.ts +++ b/src/operator/keycloak.ts @@ -175,11 +175,7 @@ async function runKeycloakUpdater() { const api = setupKeycloakApi(connection) await keycloakConfigMapChanges(api) await manageGroups(api) - if (env.FEAT_EXTERNAL_IDP === 'true') await externalIDP(api) - else await internalIdp(api) - if (!JSON.parse(env.FEAT_EXTERNAL_IDP)) { - await manageUsers(api, env.USERS as Record[]) - } + await IDPManager(api) }, 'update from config') console.info('Updated Config') } @@ -615,6 +611,14 @@ async function internalIdp(api: KeycloakApi) { await createUpdateUser(api, createAdminUser(env.KEYCLOAK_ADMIN, env.KEYCLOAK_ADMIN_PASSWORD)) } +async function IDPManager(api: KeycloakApi) { + if (env.FEAT_EXTERNAL_IDP === 'true') await externalIDP(api) + else { + await internalIdp(api) + await manageUsers(api, env.USERS as Record[]) + } +} + async function manageGroups(api: KeycloakApi) { const teamGroups = createGroups(env.TEAM_IDS) console.info('Getting realm groups') From 50389813f9840cae60df906952219ba660bb2083 Mon Sep 17 00:00:00 2001 From: ElderMatt <18527012+ElderMatt@users.noreply.github.com> Date: Mon, 16 Jun 2025 10:39:33 +0200 Subject: [PATCH 3/3] feat: added tests --- src/operators/keycloak/keycloak.test.ts | 59 +++++++++++++++++++++++++ src/operators/keycloak/keycloak.ts | 16 +++---- 2 files changed, 67 insertions(+), 8 deletions(-) diff --git a/src/operators/keycloak/keycloak.test.ts b/src/operators/keycloak/keycloak.test.ts index d3d9ec5..8513432 100644 --- a/src/operators/keycloak/keycloak.test.ts +++ b/src/operators/keycloak/keycloak.test.ts @@ -1,3 +1,4 @@ +import * as keycloak from './keycloak' import { updateUserGroups } from './keycloak' describe('Keycloak User Group Management', () => { @@ -87,4 +88,62 @@ describe('Keycloak User Group Management', () => { ) }) }) +describe('IDPManager', () => { + let api: any + + beforeEach(() => { + jest.mock('./keycloak', () => ({ + internalIDP: jest.fn(), + externalIDP: jest.fn(), + manageUsers: jest.fn(), + IDPManager: jest.fn(async (api, externalIdp) => { + if (externalIdp) { + await keycloak.externalIDP(api) + } else { + await keycloak.internalIDP(api) + await keycloak.manageUsers(api, []) + } + }, + )})) + + api = { + providers: {}, + clientScope: {}, + roles: {}, + clientRoleMappings: {}, + roleMapper: {}, + clients: {}, + protocols: {}, + realms: {}, + users: {}, + groups: {}, + } + }) + + afterEach(() => { + jest.restoreAllMocks() + }) + + it('should call externalIDP when FEAT_EXTERNAL_IDP is "true"', async () => { + const { IDPManager } = await import('./keycloak') + + jest.spyOn(keycloak, 'externalIDP').mockImplementation(jest.fn()) + + await IDPManager(api, true) + + expect(keycloak.externalIDP).toHaveBeenCalled() + }) + + it('should call internalIdp and manageUsers when FEAT_EXTERNAL_IDP is not "true"', async () => { + const { IDPManager } = await import('./keycloak') + + jest.spyOn(keycloak, 'internalIDP').mockImplementation(jest.fn()) + jest.spyOn(keycloak, 'manageUsers').mockImplementation(jest.fn()) + + await IDPManager(api, false) + + expect(keycloak.internalIDP).toHaveBeenCalled() + expect(keycloak.manageUsers).toHaveBeenCalled() + }) +}) }) diff --git a/src/operators/keycloak/keycloak.ts b/src/operators/keycloak/keycloak.ts index f3f727e..63942c0 100644 --- a/src/operators/keycloak/keycloak.ts +++ b/src/operators/keycloak/keycloak.ts @@ -176,7 +176,7 @@ async function runKeycloakUpdater() { const api = setupKeycloakApi(connection) await keycloakConfigMapChanges(api) await manageGroups(api) - await IDPManager(api) + await IDPManager(api, env.FEAT_EXTERNAL_IDP === 'true') }, 'update from config') console.info('Updated Config') } @@ -481,7 +481,7 @@ async function keycloakRealmProviderConfigurer(api: KeycloakApi) { await api.realms.adminRealmsRealmPut(env.KEYCLOAK_REALM, createLoginThemeConfig('APL')) } -async function externalIDP(api: KeycloakApi) { +export async function externalIDP(api: KeycloakApi) { // Keycloak acts as broker // Create Identity Provider const idp = await createIdProvider(env.IDP_CLIENT_ID, env.IDP_ALIAS, env.IDP_CLIENT_SECRET, env.IDP_OIDC_URL) @@ -543,7 +543,7 @@ async function externalIDP(api: KeycloakApi) { } } -async function internalIdp(api: KeycloakApi) { +export async function internalIDP(api: KeycloakApi) { // IDP instead of broker console.info('Getting realm groups') const updatedExistingGroups = (await api.groups.adminRealmsRealmGroupsGet(keycloakRealm)).body @@ -621,15 +621,15 @@ async function internalIdp(api: KeycloakApi) { await createUpdateUser(api, createAdminUser(env.KEYCLOAK_ADMIN, env.KEYCLOAK_ADMIN_PASSWORD)) } -async function IDPManager(api: KeycloakApi) { - if (env.FEAT_EXTERNAL_IDP === 'true') await externalIDP(api) +export async function IDPManager(api: KeycloakApi, isExternalIdp: boolean) { + if (isExternalIdp) await externalIDP(api) else { - await internalIdp(api) + await internalIDP(api) await manageUsers(api, env.USERS as Record[]) } } -async function manageGroups(api: KeycloakApi) { +export async function manageGroups(api: KeycloakApi) { const teamGroups = createGroups(env.TEAM_IDS) console.info('Getting realm groups') try { @@ -748,7 +748,7 @@ async function deleteUsers(api: any, users: any[]) { ) } -async function manageUsers(api: KeycloakApi, users: Record[]) { +export async function manageUsers(api: KeycloakApi, users: Record[]) { // Create/Update users in realm 'otomi' await Promise.all( users.map((user) =>