diff --git a/src/main/java/org/tkit/onecx/iam/kc/client/operator/service/KeycloakAdminService.java b/src/main/java/org/tkit/onecx/iam/kc/client/operator/service/KeycloakAdminService.java index cab6e3c..f686553 100644 --- a/src/main/java/org/tkit/onecx/iam/kc/client/operator/service/KeycloakAdminService.java +++ b/src/main/java/org/tkit/onecx/iam/kc/client/operator/service/KeycloakAdminService.java @@ -55,8 +55,7 @@ public int createClient(KeycloakClient keycloakClient) { } // check scopes if they exist and create them if necessary - checkAndCreateScopes(client, realm); - + var scopeNametoIdMap = checkAndCreateScopes(client, realm); if (Boolean.TRUE.equals(clientDefaultConfig.addDefaultScopes())) { var defaultScopesKC = keycloak.realm(realm).getDefaultDefaultClientScopes().stream() .filter(csr -> csr.getProtocol().equals(PROTOCOL_OPENID_CONNECT)).map(ClientScopeRepresentation::getName) @@ -77,25 +76,27 @@ public int createClient(KeycloakClient keycloakClient) { } } else { // do update - var defaultClientScopes = client.getDefaultClientScopes(); - var optionalClientScopes = client.getOptionalClientScopes(); + var defaultClientScopeNames = client.getDefaultClientScopes(); + var optionalClientScopeNames = client.getOptionalClientScopes(); var clientToUpdate = keycloak.realm(realm).clients().get(clients.get(0).getId()); clientToUpdate.update(client); // update default client scopes var toRemove = clientToUpdate.getDefaultClientScopes().stream() - .filter(rep -> !defaultClientScopes.contains(rep.getName())).map(ClientScopeRepresentation::getId) + .filter(rep -> !defaultClientScopeNames.contains(rep.getName())).map(ClientScopeRepresentation::getId) .collect(Collectors.toSet()); - var toAdd = new ArrayList<>(client.getDefaultClientScopes()); - toAdd.removeAll(clientToUpdate.getDefaultClientScopes().stream().map(ClientScopeRepresentation::getName) + var toAdd = client.getDefaultClientScopes().stream().map(scopeNametoIdMap::get) + .collect(Collectors.toCollection(ArrayList::new)); + toAdd.removeAll(clientToUpdate.getDefaultClientScopes().stream().map(ClientScopeRepresentation::getId) .collect(Collectors.toSet())); toRemove.forEach(scope -> removeDefaultClientScope(clientToUpdate, scope)); toAdd.forEach(scope -> addDefaultClientScope(clientToUpdate, scope)); // update optional client scopes var toRemoveOpt = clientToUpdate.getOptionalClientScopes().stream() - .filter(rep -> !optionalClientScopes.contains(rep.getName())).map(ClientScopeRepresentation::getId) + .filter(rep -> !optionalClientScopeNames.contains(rep.getName())).map(ClientScopeRepresentation::getId) .collect(Collectors.toSet()); - var toAddOpt = new ArrayList<>(client.getOptionalClientScopes()); - toAddOpt.removeAll(clientToUpdate.getOptionalClientScopes().stream().map(ClientScopeRepresentation::getName) + var toAddOpt = client.getOptionalClientScopes().stream().map(scopeNametoIdMap::get) + .collect(Collectors.toCollection(ArrayList::new)); + toAddOpt.removeAll(clientToUpdate.getOptionalClientScopes().stream().map(ClientScopeRepresentation::getId) .collect(Collectors.toSet())); toRemoveOpt.forEach(scope -> removeOptClientScope(clientToUpdate, scope)); toAddOpt.forEach(scope -> addOptClientScope(clientToUpdate, scope)); @@ -148,8 +149,10 @@ void removeOptClientScope(ClientResource cr, String clientScope) { } } - private void checkAndCreateScopes(ClientRepresentation clientRepresentation, String realm) { + private Map checkAndCreateScopes(ClientRepresentation clientRepresentation, String realm) { var kcScopes = keycloak.realm(realm).clientScopes().findAll(); + var kcScopesMap = kcScopes.stream() + .collect(Collectors.toMap(ClientScopeRepresentation::getName, ClientScopeRepresentation::getId)); var scopeNames = kcScopes.stream().map(ClientScopeRepresentation::getName).collect(Collectors.toSet()); var scopesToAdd = new HashSet(); @@ -165,8 +168,14 @@ private void checkAndCreateScopes(ClientRepresentation clientRepresentation, Str // add all missing scopes to keycloak if (!scopesToAdd.isEmpty()) { - scopesToAdd.forEach(scopeName -> createClientScope(scopeName, realm)); + scopesToAdd.forEach(scopeName -> { + createClientScope(scopeName, realm); + kcScopesMap.put(scopeName, scopeName); + }); + } + + return kcScopesMap; } private void createClientScope(String clientScopeName, String realm) { diff --git a/src/test/java/org/tkit/onecx/iam/kc/client/operator/KeycloakClientControllerTest.java b/src/test/java/org/tkit/onecx/iam/kc/client/operator/KeycloakClientControllerTest.java index 1944194..9412f7e 100644 --- a/src/test/java/org/tkit/onecx/iam/kc/client/operator/KeycloakClientControllerTest.java +++ b/src/test/java/org/tkit/onecx/iam/kc/client/operator/KeycloakClientControllerTest.java @@ -5,15 +5,19 @@ import static org.awaitility.Awaitility.await; import java.util.Base64; +import java.util.HashMap; import java.util.List; import java.util.Map; import jakarta.inject.Inject; +import jakarta.ws.rs.core.Response; import org.apache.groovy.util.Maps; import org.awaitility.Awaitility; import org.junit.jupiter.api.*; import org.keycloak.admin.client.Keycloak; +import org.keycloak.representations.idm.ClientScopeRepresentation; +import org.keycloak.representations.idm.ProtocolMapperRepresentation; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.tkit.onecx.iam.kc.client.operator.service.KeycloakAdminService; @@ -87,6 +91,8 @@ void createUIClient() { assertThat(clientRep.getDescription()).isEqualTo(kcConfig.getDescription()); // validate that attributes are all in assertThat(clientRep.getAttributes()).containsAllEntriesOf(kcConfig.getAttributes()); + // test Organization_ID should not be the part of the default scopes at this point + assertThat(clientRep.getDefaultClientScopes()).doesNotContain("Organization_ID"); assertThat(clientRep.getOptionalClientScopes()).containsAll(kcConfig.getOptionalClientScopes()); var token = keycloakClient.getAccessToken(USER_ALICE, CLIENT_ID); @@ -165,6 +171,8 @@ void createUIClientAllOptionsFilled() { @Test @Order(2) void updateUIClient() { + // create Organization_ID as default scope + createOrgIdScope(); var CLIENT_ID = "test-ui-client"; operator.start(); @@ -199,6 +207,8 @@ void updateUIClient() { assertThat(clientRep.getDescription()).isEqualTo(kcConfig.getDescription()); // validate that attributes are all in assertThat(clientRep.getAttributes()).containsAllEntriesOf(kcConfig.getAttributes()); + // test if the new default scope for Realm Organization_ID was added correctly + assertThat(clientRep.getDefaultClientScopes()).contains("Organization_ID"); assertThat(clientRep.getOptionalClientScopes()).containsAll(kcConfig.getOptionalClientScopes()); var token = keycloakClient.getAccessToken(USER_ALICE, CLIENT_ID); @@ -363,6 +373,7 @@ void createMachineClient() { kcClientSpec.setKcConfig(kcConfig); kcConfig.setClientId(CLIENT_ID); kcConfig.setPassword(CLIENT_SECRET); + kcConfig.setDefaultClientScopes(List.of("create-scope-1", "create-scope-2")); kcConfig.setAttributes(Maps.of("create.attr.1", "create.values.1", "create.attr.2", "create.values.2")); data.setSpec(kcClientSpec); @@ -396,6 +407,32 @@ void createMachineClient() { assertThat(scopes).containsAll(kcConfig.getDefaultClientScopes()); } + void createOrgIdScope() { + ClientScopeRepresentation orgIdScope = new ClientScopeRepresentation(); + orgIdScope.setId("test-id-12345"); + orgIdScope.setName("Organization_ID"); + orgIdScope.setDescription("Tenant organization ID"); + orgIdScope.setProtocol("openid-connect"); + ProtocolMapperRepresentation pmr = new ProtocolMapperRepresentation(); + pmr.setId("pmr-test-1234"); + pmr.setName("OrgIdMapper"); + pmr.setProtocol("openid-connect"); + pmr.setProtocolMapper("oidc-usermodel-attribute-mapper"); + Map mapperConfig = new HashMap<>(); + mapperConfig.put("userinfo.token.claim", "true"); + mapperConfig.put("multivalued", "false"); + mapperConfig.put("user.attribute", "orgId"); + mapperConfig.put("id.token.claim", "true"); + mapperConfig.put("access.token.claim", "true"); + mapperConfig.put("claim.name", "orgId"); + mapperConfig.put("jsonType.label", "String"); + pmr.setConfig(mapperConfig); + orgIdScope.setProtocolMappers(List.of(pmr)); + try (Response res = keycloak.realm(REALM_QUARKUS).clientScopes().create(orgIdScope)) { + keycloak.realm(REALM_QUARKUS).addDefaultDefaultClientScope(orgIdScope.getId()); + } + } + @Test @Order(11) void updateMachineClient() {