From 4ca71a146284059905fd0cc36e5f6778e0abc355 Mon Sep 17 00:00:00 2001 From: pravussum Date: Tue, 22 Jun 2021 09:27:20 +0200 Subject: [PATCH 1/2] Fix defaultClientScopes and optionalClientScopes assignment --- pom.xml | 8 + .../client/AssignedClientScopesSyncer.java | 69 ++++++++ .../controller/client/ClientController.java | 8 +- .../AssignedClientScopesSyncerTest.java | 149 ++++++++++++++++++ 4 files changed, 233 insertions(+), 1 deletion(-) create mode 100644 src/main/java/com/kiwigrid/keycloak/controller/client/AssignedClientScopesSyncer.java create mode 100644 src/test/java/com/kiwigrid/keycloak/controller/client/AssignedClientScopesSyncerTest.java diff --git a/pom.xml b/pom.xml index 97de0b7..64f9471 100644 --- a/pom.xml +++ b/pom.xml @@ -50,6 +50,7 @@ 1.18.16 1.4.10 3.1.2 + 3.7.7 UTF-8 @@ -185,6 +186,13 @@ + + org.mockito + mockito-core + ${version.mockito} + test + + org.testcontainers testcontainers diff --git a/src/main/java/com/kiwigrid/keycloak/controller/client/AssignedClientScopesSyncer.java b/src/main/java/com/kiwigrid/keycloak/controller/client/AssignedClientScopesSyncer.java new file mode 100644 index 0000000..6f6d0eb --- /dev/null +++ b/src/main/java/com/kiwigrid/keycloak/controller/client/AssignedClientScopesSyncer.java @@ -0,0 +1,69 @@ +package com.kiwigrid.keycloak.controller.client; + +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import javax.inject.Singleton; +import org.keycloak.admin.client.resource.RealmResource; +import org.keycloak.representations.idm.ClientScopeRepresentation; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +@Singleton +public class AssignedClientScopesSyncer { + private final Logger log = LoggerFactory.getLogger(getClass()); + + public void manageClientScopes(RealmResource realmResource, String clientUuid, com.kiwigrid.keycloak.controller.client.ClientResource clientResource) { + var keycloak = clientResource.getSpec().getKeycloak(); + var realm = clientResource.getSpec().getRealm(); + var clientId = clientResource.getSpec().getClientId(); + + org.keycloak.admin.client.resource.ClientResource keycloakClientResource = realmResource.clients().get(clientUuid); + List existingDefaultClientScopeNames = keycloakClientResource.toRepresentation().getDefaultClientScopes(); + List existingOptionalClientScopeNames = keycloakClientResource.toRepresentation() + .getOptionalClientScopes(); + + List requestedDefaultClientScopes = clientResource.getSpec().getDefaultClientScopes().stream() + .map(String::toLowerCase) + .collect(Collectors.toList()); + List requestedOptionalClientScopes = clientResource.getSpec().getOptionalClientScopes().stream() + .map(String::toLowerCase) + .collect(Collectors.toList()); + + // add new + getClientScopesForName(realmResource, requestedDefaultClientScopes) + .filter(cs -> !existingDefaultClientScopeNames.contains(cs.getName())) + .forEach(cs -> { + keycloakClientResource.addDefaultClientScope(cs.getId()); + log.info("{}/{}/{}: added default client scope {}", keycloak, realm, clientId, cs.getName()); + }); + getClientScopesForName(realmResource, requestedOptionalClientScopes) + .filter(cs -> !existingOptionalClientScopeNames.contains(cs.getName())) + .forEach(cs -> { + keycloakClientResource.addOptionalClientScope(cs.getId()); + log.info("{}/{}/{}: added optional client scope {}", keycloak, realm, clientId, cs.getName()); + }); + + // remove obsolete + keycloakClientResource.getDefaultClientScopes().stream() + .filter(cs -> !requestedDefaultClientScopes.contains(cs.getName().toLowerCase())) + .forEach(cs -> { + keycloakClientResource.removeDefaultClientScope(cs.getId()); + log.info("{}/{}/{}: removed default client scope {}", keycloak, realm, clientId, cs.getName()); + }); + keycloakClientResource.getOptionalClientScopes().stream() + .filter(cs -> !requestedOptionalClientScopes.contains(cs.getName().toLowerCase())) + .forEach(cs -> { + keycloakClientResource.removeOptionalClientScope(cs.getId()); + log.info("{}/{}/{}: removed optional client scope {}", keycloak, realm, clientId, cs.getName()); + }); + } + + private Stream getClientScopesForName(RealmResource realmResource, List requestedClientScopes) { + return realmResource.clientScopes() + .findAll() + .stream() + .filter(cs -> requestedClientScopes.contains(cs.getName().toLowerCase())); + } +} diff --git a/src/main/java/com/kiwigrid/keycloak/controller/client/ClientController.java b/src/main/java/com/kiwigrid/keycloak/controller/client/ClientController.java index b062b4a..d70f842 100644 --- a/src/main/java/com/kiwigrid/keycloak/controller/client/ClientController.java +++ b/src/main/java/com/kiwigrid/keycloak/controller/client/ClientController.java @@ -29,11 +29,16 @@ public class ClientController extends KubernetesController { final KeycloakController keycloak; + final AssignedClientScopesSyncer assignedClientScopesSyncer; + + public ClientController(KeycloakController keycloak, + KubernetesClient kubernetes, + AssignedClientScopesSyncer assignedClientScopesSyncer) { - public ClientController(KeycloakController keycloak, KubernetesClient kubernetes) { super(kubernetes, ClientResource.DEFINITION, ClientResource.class, ClientResource.ClientResourceList.class, ClientResource.ClientResourceDoneable.class); this.keycloak = keycloak; + this.assignedClientScopesSyncer = assignedClientScopesSyncer; } @Override @@ -85,6 +90,7 @@ public void apply(ClientResource clientResource) { } manageMapper(realmResource, clientUuid, clientResource); manageRoles(realmResource, clientUuid, clientResource); + assignedClientScopesSyncer.manageClientScopes(realmResource, clientUuid, clientResource); if (clientResource.getSpec().getServiceAccountsEnabled() == Boolean.TRUE) { manageServiceAccountRealmRoles(realmResource, clientUuid, clientResource); diff --git a/src/test/java/com/kiwigrid/keycloak/controller/client/AssignedClientScopesSyncerTest.java b/src/test/java/com/kiwigrid/keycloak/controller/client/AssignedClientScopesSyncerTest.java new file mode 100644 index 0000000..ae2c588 --- /dev/null +++ b/src/test/java/com/kiwigrid/keycloak/controller/client/AssignedClientScopesSyncerTest.java @@ -0,0 +1,149 @@ +package com.kiwigrid.keycloak.controller.client; + +import java.util.List; +import java.util.stream.Collectors; + +import org.jetbrains.annotations.NotNull; +import org.junit.Test; +import org.keycloak.admin.client.resource.RealmResource; +import org.keycloak.representations.idm.ClientRepresentation; +import org.keycloak.representations.idm.ClientScopeRepresentation; +import org.mockito.Mockito; + +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +public class AssignedClientScopesSyncerTest { + + private final AssignedClientScopesSyncer assignedClientScopesSyncer = new AssignedClientScopesSyncer(); + + @Test + public void testNonRequestedClientScopesRemoved() { + String clientUuid = "clientUuid"; + RealmResource keycloakRealmResource = Mockito.mock(RealmResource.class); + org.keycloak.admin.client.resource.ClientResource keycloakClientResource = prepareClientResource( + clientUuid, + keycloakRealmResource, + List.of("dcs2", "dcs3"), + List.of("ocs2", "ocs3"), + List.of("dcs1", "dcs2", "dcs3", "another-unrelated-dcs", "ocs1", "ocs2", "ocs3", "another-unrelated-ocs")); + + ClientResource kubernetesClientResource = createKubernetesClientResource( + List.of("dcs1", "dcs2"), List.of("ocs1", "ocs2")); + + assignedClientScopesSyncer.manageClientScopes(keycloakRealmResource, clientUuid, kubernetesClientResource); + + verify(keycloakClientResource).removeDefaultClientScope("dcs3-id"); + verify(keycloakClientResource).removeOptionalClientScope("ocs3-id"); + } + + @Test + public void testRequestedClientScopesAdded() { + String clientUuid = "clientUuid"; + RealmResource keycloakRealmResource = Mockito.mock(RealmResource.class); + org.keycloak.admin.client.resource.ClientResource keycloakClientResource = prepareClientResource( + clientUuid, + keycloakRealmResource, + List.of("dcs2", "dcs3"), + List.of("ocs2", "ocs3"), + List.of("dcs1", "dcs2", "dcs3", "another-unrelated-dcs", "ocs1", "ocs2", "ocs3", "another-unrelated-ocs")); + + ClientResource kubernetesClientResource = createKubernetesClientResource( + List.of("dcs1", "dcs2"), List.of("ocs1", "ocs2")); + + assignedClientScopesSyncer.manageClientScopes(keycloakRealmResource, clientUuid, kubernetesClientResource); + + verify(keycloakClientResource).addDefaultClientScope("dcs1-id"); + verify(keycloakClientResource).addOptionalClientScope("ocs1-id"); + } + + @Test + public void testNonExistingClientScopeNotAdded() { + String clientUuid = "clientUuid"; + RealmResource keycloakRealmResource = Mockito.mock(RealmResource.class); + org.keycloak.admin.client.resource.ClientResource keycloakClientResource = prepareClientResource( + clientUuid, + keycloakRealmResource, + List.of("dcs1", "dcs2", "non-existing-dcs"), + List.of("ocs1", "ocs2", "non-existing-ocs"), + List.of("dcs1", "dcs2", "another-unrelated-dcs", "ocs1", "ocs2", "another-unrelated-ocs")); + + ClientResource kubernetesClientResource = createKubernetesClientResource( + List.of("dcs1", "dcs2"), List.of("ocs1", "ocs2")); + + assignedClientScopesSyncer.manageClientScopes(keycloakRealmResource, clientUuid, kubernetesClientResource); + + verify(keycloakClientResource, times(0)).addDefaultClientScope(anyString()); + verify(keycloakClientResource, times(0)).addOptionalClientScope(anyString()); + } + + @Test + public void testUnchangedClientScopeNotTouched() { + String clientUuid = "clientUuid"; + RealmResource keycloakRealmResource = Mockito.mock(RealmResource.class); + org.keycloak.admin.client.resource.ClientResource keycloakClientResource = prepareClientResource( + clientUuid, + keycloakRealmResource, + List.of("dcs1", "dcs2"), + List.of("ocs1", "ocs2"), + List.of("dcs1", "dcs2", "another-unrelated-dcs", "ocs1", "ocs2", "another-unrelated-ocs")); + + ClientResource kubernetesClientResource = createKubernetesClientResource( + List.of("dcs1", "dcs2"), List.of("ocs1", "ocs2")); + + assignedClientScopesSyncer.manageClientScopes(keycloakRealmResource, clientUuid, kubernetesClientResource); + + verify(keycloakClientResource, times(0)).addDefaultClientScope(anyString()); + verify(keycloakClientResource, times(0)).addOptionalClientScope(anyString()); + } + + @NotNull + private org.keycloak.admin.client.resource.ClientResource prepareClientResource(String clientUuid, RealmResource realmResource, + List defaultClientScopes, List optionalClientScopes, List availableClientScopes) { + ClientRepresentation clientRepresentation = new ClientRepresentation(); + org.keycloak.admin.client.resource.ClientsResource clientsResource = Mockito.mock(org.keycloak.admin.client.resource.ClientsResource.class); + org.keycloak.admin.client.resource.ClientResource clientResource = Mockito.mock(org.keycloak.admin.client.resource.ClientResource.class); + org.keycloak.admin.client.resource.ClientScopesResource clientScopesResource = Mockito.mock(org.keycloak.admin.client.resource.ClientScopesResource.class); + + clientRepresentation.setDefaultClientScopes(defaultClientScopes); + clientRepresentation.setOptionalClientScopes(optionalClientScopes); + + given(clientsResource.get(clientUuid)).willReturn(clientResource); + given(clientResource.toRepresentation()).willReturn(clientRepresentation); + given(clientResource.getDefaultClientScopes()).willReturn(defaultClientScopes.stream().map(this::mapToClientRepresentation).collect(Collectors.toList())); + given(clientResource.getOptionalClientScopes()).willReturn(optionalClientScopes.stream().map(this::mapToClientRepresentation).collect(Collectors.toList())); + given(realmResource.clients()).willReturn(clientsResource); + given(realmResource.clientScopes()).willReturn(clientScopesResource); + given(clientScopesResource.findAll()).willReturn(getClientScopeRepresentations(availableClientScopes)); + + return clientResource; + } + + @NotNull + private ClientScopeRepresentation mapToClientRepresentation(String cs) { + ClientScopeRepresentation representation = new ClientScopeRepresentation(); + representation.setName(cs); + representation.setId(cs + "-id"); + return representation; + } + + private com.kiwigrid.keycloak.controller.client.ClientResource createKubernetesClientResource( + List defaultClientScopes, List optionalClientScopes) { + com.kiwigrid.keycloak.controller.client.ClientResource clientResourceK8s = new com.kiwigrid.keycloak.controller.client.ClientResource(); + clientResourceK8s.setSpec(new com.kiwigrid.keycloak.controller.client.ClientResource.ClientResourceSpec()); + clientResourceK8s.getSpec().setDefaultClientScopes(defaultClientScopes); + clientResourceK8s.getSpec().setOptionalClientScopes(optionalClientScopes); + clientResourceK8s.getSpec().setRealm("realm"); + clientResourceK8s.getSpec().setKeycloak("keycloak"); + clientResourceK8s.getSpec().setClientId("clientId"); + return clientResourceK8s; + } + + private List getClientScopeRepresentations(List clientScopeNames) { + return clientScopeNames.stream() + .map(this::mapToClientRepresentation) + .collect(Collectors.toList()); + } +} \ No newline at end of file From 2dba6776c7c139ee0d19519099af095834d11bd9 Mon Sep 17 00:00:00 2001 From: pravussum Date: Tue, 22 Jun 2021 09:34:47 +0200 Subject: [PATCH 2/2] Update branch names in github projects (to main) --- .github/ci.provision.helm.sh | 2 +- .github/workflows/ci.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/ci.provision.helm.sh b/.github/ci.provision.helm.sh index 5bff287..83c051c 100755 --- a/.github/ci.provision.helm.sh +++ b/.github/ci.provision.helm.sh @@ -5,7 +5,7 @@ set -o errexit HELM_VERSION="${1}" echo -e "\n##### install helm #####\n" -curl --silent --show-error --fail --location --output get_helm.sh https://raw.githubusercontent.com/kubernetes/helm/master/scripts/get +curl --silent --show-error --fail --location --output get_helm.sh https://raw.githubusercontent.com/kubernetes/helm/main/scripts/get chmod 700 get_helm.sh ./get_helm.sh --version "${HELM_VERSION}" diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 71ec3b6..44b0ab8 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -46,7 +46,7 @@ jobs: - name: maven run: .github/ci.maven.sh - name: Create kind ${{ matrix.k8s-version }} cluster - uses: helm/kind-action@master + uses: helm/kind-action@main with: config: .github/kind-config.yaml node_image: kindest/node:${{ matrix.k8s-version }}